summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format1
-rw-r--r--.clang-tidy2
-rw-r--r--.reuse/dep514
-rw-r--r--Documentation/Doxyfile.in2372
-rw-r--r--Documentation/binning.svg5053
-rw-r--r--Documentation/camera-sensor-model.rst173
-rw-r--r--Documentation/code-of-conduct.rst94
-rw-r--r--Documentation/coding-style.rst2
-rw-r--r--Documentation/contributing.rst12
-rw-r--r--Documentation/environment_variables.rst7
-rw-r--r--Documentation/getting-started.rst1
-rw-r--r--Documentation/guides/application-developer.rst4
-rw-r--r--Documentation/guides/introduction.rst2
-rw-r--r--Documentation/guides/ipa.rst40
-rw-r--r--Documentation/guides/pipeline-handler.rst83
-rw-r--r--Documentation/images/rotation/rotate0.svg132
-rw-r--r--Documentation/images/rotation/rotate0Mirror.svg135
-rw-r--r--Documentation/images/rotation/rotate180.svg135
-rw-r--r--Documentation/images/rotation/rotate180Mirror.svg135
-rw-r--r--Documentation/images/rotation/rotate270.svg135
-rw-r--r--Documentation/images/rotation/rotate270Mirror.svg135
-rw-r--r--Documentation/images/rotation/rotate90.svg135
-rw-r--r--Documentation/images/rotation/rotate90Mirror.svg135
-rw-r--r--Documentation/index.rst2
-rw-r--r--Documentation/meson.build25
-rw-r--r--Documentation/python-bindings.rst10
-rw-r--r--Documentation/sensor_driver_requirements.rst19
-rw-r--r--Documentation/sensor_model.svg4870
-rw-r--r--Documentation/skipping.svg1720
-rw-r--r--Documentation/software-isp-benchmarking.rst77
-rw-r--r--Documentation/theme/layout.html5
-rw-r--r--LICENSES/CC-BY-4.0.txt156
-rw-r--r--README.rst65
-rw-r--r--include/android/system/core/include/system/graphics-base-v1.0.h1
-rw-r--r--include/android/system/core/include/system/graphics-base-v1.1.h1
-rw-r--r--include/android/system/core/include/system/graphics-base.h1
-rw-r--r--include/android/system/core/include/system/graphics-sw.h1
-rw-r--r--include/libcamera/base/bound_method.h2
-rw-r--r--include/libcamera/base/flags.h8
-rw-r--r--include/libcamera/base/log.h10
-rw-r--r--include/libcamera/base/meson.build28
-rw-r--r--include/libcamera/base/message.h2
-rw-r--r--include/libcamera/base/mutex.h2
-rw-r--r--include/libcamera/base/object.h4
-rw-r--r--include/libcamera/base/semaphore.h10
-rw-r--r--include/libcamera/base/signal.h19
-rw-r--r--include/libcamera/base/thread_annotations.h2
-rw-r--r--include/libcamera/base/utils.h14
-rw-r--r--include/libcamera/camera.h43
-rw-r--r--include/libcamera/camera_manager.h7
-rw-r--r--include/libcamera/color_space.h10
-rw-r--r--include/libcamera/control_ids.h.in6
-rw-r--r--include/libcamera/controls.h72
-rw-r--r--include/libcamera/framebuffer.h22
-rw-r--r--include/libcamera/internal/bayer_format.h2
-rw-r--r--include/libcamera/internal/camera.h1
-rw-r--r--include/libcamera/internal/camera_manager.h68
-rw-r--r--include/libcamera/internal/camera_sensor.h46
-rw-r--r--include/libcamera/internal/control_serializer.h4
-rw-r--r--include/libcamera/internal/converter.h108
-rw-r--r--include/libcamera/internal/converter/converter_v4l2_m2m.h (renamed from src/libcamera/pipeline/simple/converter.h)24
-rw-r--r--include/libcamera/internal/converter/meson.build5
-rw-r--r--include/libcamera/internal/delayed_controls.h7
-rw-r--r--include/libcamera/internal/dma_heaps.h (renamed from src/libcamera/pipeline/raspberrypi/dma_heaps.h)16
-rw-r--r--include/libcamera/internal/formats.h5
-rw-r--r--include/libcamera/internal/framebuffer.h10
-rw-r--r--include/libcamera/internal/ipa_data_serializer.h60
-rw-r--r--include/libcamera/internal/ipa_manager.h7
-rw-r--r--include/libcamera/internal/meson.build13
-rw-r--r--include/libcamera/internal/pipeline_handler.h66
-rw-r--r--include/libcamera/internal/pub_key.h8
-rw-r--r--include/libcamera/internal/request.h8
-rw-r--r--include/libcamera/internal/shared_mem_object.h127
-rw-r--r--include/libcamera/internal/software_isp/debayer_params.h29
-rw-r--r--include/libcamera/internal/software_isp/meson.build7
-rw-r--r--include/libcamera/internal/software_isp/software_isp.h99
-rw-r--r--include/libcamera/internal/software_isp/swisp_stats.h49
-rw-r--r--include/libcamera/internal/tracepoints/request.tp4
-rw-r--r--include/libcamera/internal/v4l2_device.h9
-rw-r--r--include/libcamera/internal/v4l2_pixelformat.h22
-rw-r--r--include/libcamera/internal/v4l2_subdevice.h130
-rw-r--r--include/libcamera/internal/v4l2_videodevice.h9
-rw-r--r--include/libcamera/internal/yaml_parser.h68
-rw-r--r--include/libcamera/ipa/core.mojom49
-rw-r--r--include/libcamera/ipa/ipa_interface.h5
-rw-r--r--include/libcamera/ipa/meson.build45
-rw-r--r--include/libcamera/ipa/raspberrypi.mojom267
-rw-r--r--include/libcamera/ipa/rkisp1.mojom18
-rw-r--r--include/libcamera/ipa/soft.mojom28
-rw-r--r--include/libcamera/ipa/vimc.mojom14
-rw-r--r--include/libcamera/meson.build57
-rw-r--r--include/libcamera/orientation.h30
-rw-r--r--include/libcamera/property_ids.h.in8
-rw-r--r--include/libcamera/stream.h5
-rw-r--r--include/libcamera/transform.h7
-rw-r--r--include/linux/README2
-rw-r--r--include/linux/bcm2835-isp.h2
-rw-r--r--include/linux/dma-buf.h88
-rw-r--r--include/linux/drm_fourcc.h210
-rw-r--r--include/linux/intel-ipu3.h42
-rw-r--r--include/linux/media-bus-format.h13
-rw-r--r--include/linux/media.h29
-rw-r--r--include/linux/rkisp1-config.h85
-rw-r--r--include/linux/v4l2-common.h39
-rw-r--r--include/linux/v4l2-controls.h1551
-rw-r--r--include/linux/v4l2-mediabus.h4
-rw-r--r--include/linux/v4l2-subdev.h108
-rw-r--r--include/linux/videodev2.h84
-rw-r--r--include/meson.build2
-rw-r--r--meson.build149
-rw-r--r--meson_options.txt38
-rw-r--r--src/android/camera_capabilities.cpp64
-rw-r--r--src/android/camera_device.cpp68
-rw-r--r--src/android/camera_device.h3
-rw-r--r--src/android/camera_hal_config.cpp9
-rw-r--r--src/android/camera_hal_manager.cpp9
-rw-r--r--src/android/camera_request.h3
-rw-r--r--src/android/cros/camera3_hal.cpp4
-rw-r--r--src/android/cros_mojo_token.h12
-rw-r--r--src/android/data/nautilus/camera_hal.yaml2
-rw-r--r--src/android/data/soraka/camera_hal.yaml2
-rw-r--r--src/android/frame_buffer_allocator.h7
-rw-r--r--src/android/hal_framebuffer.cpp22
-rw-r--r--src/android/hal_framebuffer.h26
-rw-r--r--src/android/jpeg/encoder.h5
-rw-r--r--src/android/jpeg/encoder_jea.cpp56
-rw-r--r--src/android/jpeg/encoder_jea.h31
-rw-r--r--src/android/jpeg/encoder_libjpeg.cpp13
-rw-r--r--src/android/jpeg/encoder_libjpeg.h3
-rw-r--r--src/android/jpeg/exif.cpp13
-rw-r--r--src/android/jpeg/exif.h5
-rw-r--r--src/android/jpeg/meson.build14
-rw-r--r--src/android/jpeg/post_processor_jpeg.cpp13
-rw-r--r--src/android/meson.build6
-rw-r--r--src/android/mm/cros_frame_buffer_allocator.cpp15
-rw-r--r--src/android/mm/generic_frame_buffer_allocator.cpp25
-rw-r--r--src/android/mm/libhardware_stub.c17
-rw-r--r--src/android/mm/meson.build8
-rw-r--r--src/apps/cam/camera_session.cpp (renamed from src/cam/camera_session.cpp)29
-rw-r--r--src/apps/cam/camera_session.h (renamed from src/cam/camera_session.h)2
-rw-r--r--src/apps/cam/capture-script.yaml (renamed from src/cam/capture-script.yaml)29
-rw-r--r--src/apps/cam/capture_script.cpp662
-rw-r--r--src/apps/cam/capture_script.h (renamed from src/cam/capture_script.h)15
-rw-r--r--src/apps/cam/drm.cpp (renamed from src/cam/drm.cpp)39
-rw-r--r--src/apps/cam/drm.h (renamed from src/cam/drm.h)1
-rw-r--r--src/apps/cam/file_sink.cpp (renamed from src/cam/file_sink.cpp)63
-rw-r--r--src/apps/cam/file_sink.h (renamed from src/cam/file_sink.h)9
-rw-r--r--src/apps/cam/frame_sink.cpp (renamed from src/cam/frame_sink.cpp)0
-rw-r--r--src/apps/cam/frame_sink.h (renamed from src/cam/frame_sink.h)0
-rw-r--r--src/apps/cam/kms_sink.cpp (renamed from src/cam/kms_sink.cpp)210
-rw-r--r--src/apps/cam/kms_sink.h (renamed from src/cam/kms_sink.h)16
-rw-r--r--src/apps/cam/main.cpp (renamed from src/cam/main.cpp)29
-rw-r--r--src/apps/cam/main.h (renamed from src/cam/main.h)1
-rw-r--r--src/apps/cam/meson.build (renamed from src/cam/meson.build)25
-rw-r--r--src/apps/cam/sdl_sink.cpp (renamed from src/cam/sdl_sink.cpp)41
-rw-r--r--src/apps/cam/sdl_sink.h (renamed from src/cam/sdl_sink.h)0
-rw-r--r--src/apps/cam/sdl_texture.cpp (renamed from src/cam/sdl_texture.cpp)6
-rw-r--r--src/apps/cam/sdl_texture.h (renamed from src/cam/sdl_texture.h)13
-rw-r--r--src/apps/cam/sdl_texture_mjpg.cpp83
-rw-r--r--src/apps/cam/sdl_texture_mjpg.h (renamed from src/cam/sdl_texture_mjpg.h)8
-rw-r--r--src/apps/cam/sdl_texture_yuv.cpp33
-rw-r--r--src/apps/cam/sdl_texture_yuv.h26
-rw-r--r--src/apps/common/dng_writer.cpp (renamed from src/qcam/dng_writer.cpp)83
-rw-r--r--src/apps/common/dng_writer.h (renamed from src/qcam/dng_writer.h)2
-rw-r--r--src/apps/common/event_loop.cpp (renamed from src/cam/event_loop.cpp)0
-rw-r--r--src/apps/common/event_loop.h (renamed from src/cam/event_loop.h)0
-rw-r--r--src/apps/common/image.cpp (renamed from src/cam/image.cpp)0
-rw-r--r--src/apps/common/image.h (renamed from src/cam/image.h)0
-rw-r--r--src/apps/common/meson.build27
-rw-r--r--src/apps/common/options.cpp (renamed from src/cam/options.cpp)0
-rw-r--r--src/apps/common/options.h (renamed from src/cam/options.h)0
-rw-r--r--src/apps/common/ppm_writer.cpp53
-rw-r--r--src/apps/common/ppm_writer.h20
-rw-r--r--src/apps/common/stream_options.cpp (renamed from src/cam/stream_options.cpp)54
-rw-r--r--src/apps/common/stream_options.h (renamed from src/cam/stream_options.h)9
-rw-r--r--src/apps/ipa-verify/main.cpp64
-rw-r--r--src/apps/ipa-verify/meson.build15
-rw-r--r--src/apps/lc-compliance/environment.cpp (renamed from src/lc-compliance/environment.cpp)0
-rw-r--r--src/apps/lc-compliance/environment.h (renamed from src/lc-compliance/environment.h)0
-rw-r--r--src/apps/lc-compliance/helpers/capture.cpp (renamed from src/lc-compliance/simple_capture.cpp)55
-rw-r--r--src/apps/lc-compliance/helpers/capture.h (renamed from src/lc-compliance/simple_capture.h)17
-rw-r--r--src/apps/lc-compliance/main.cpp (renamed from src/lc-compliance/main.cpp)3
-rw-r--r--src/apps/lc-compliance/meson.build (renamed from src/lc-compliance/meson.build)22
-rw-r--r--src/apps/lc-compliance/tests/capture_test.cpp (renamed from src/lc-compliance/capture_test.cpp)26
-rw-r--r--src/apps/meson.build22
-rw-r--r--src/apps/qcam/assets/feathericons/activity.svg (renamed from src/qcam/assets/feathericons/activity.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/airplay.svg (renamed from src/qcam/assets/feathericons/airplay.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/alert-circle.svg (renamed from src/qcam/assets/feathericons/alert-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/alert-octagon.svg (renamed from src/qcam/assets/feathericons/alert-octagon.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/alert-triangle.svg (renamed from src/qcam/assets/feathericons/alert-triangle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/align-center.svg (renamed from src/qcam/assets/feathericons/align-center.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/align-justify.svg (renamed from src/qcam/assets/feathericons/align-justify.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/align-left.svg (renamed from src/qcam/assets/feathericons/align-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/align-right.svg (renamed from src/qcam/assets/feathericons/align-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/anchor.svg (renamed from src/qcam/assets/feathericons/anchor.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/aperture.svg (renamed from src/qcam/assets/feathericons/aperture.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/archive.svg (renamed from src/qcam/assets/feathericons/archive.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-down-circle.svg (renamed from src/qcam/assets/feathericons/arrow-down-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-down-left.svg (renamed from src/qcam/assets/feathericons/arrow-down-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-down-right.svg (renamed from src/qcam/assets/feathericons/arrow-down-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-down.svg (renamed from src/qcam/assets/feathericons/arrow-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-left-circle.svg (renamed from src/qcam/assets/feathericons/arrow-left-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-left.svg (renamed from src/qcam/assets/feathericons/arrow-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-right-circle.svg (renamed from src/qcam/assets/feathericons/arrow-right-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-right.svg (renamed from src/qcam/assets/feathericons/arrow-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-up-circle.svg (renamed from src/qcam/assets/feathericons/arrow-up-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-up-left.svg (renamed from src/qcam/assets/feathericons/arrow-up-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-up-right.svg (renamed from src/qcam/assets/feathericons/arrow-up-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/arrow-up.svg (renamed from src/qcam/assets/feathericons/arrow-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/at-sign.svg (renamed from src/qcam/assets/feathericons/at-sign.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/award.svg (renamed from src/qcam/assets/feathericons/award.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bar-chart-2.svg (renamed from src/qcam/assets/feathericons/bar-chart-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bar-chart.svg (renamed from src/qcam/assets/feathericons/bar-chart.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/battery-charging.svg (renamed from src/qcam/assets/feathericons/battery-charging.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/battery.svg (renamed from src/qcam/assets/feathericons/battery.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bell-off.svg (renamed from src/qcam/assets/feathericons/bell-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bell.svg (renamed from src/qcam/assets/feathericons/bell.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bluetooth.svg (renamed from src/qcam/assets/feathericons/bluetooth.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bold.svg (renamed from src/qcam/assets/feathericons/bold.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/book-open.svg (renamed from src/qcam/assets/feathericons/book-open.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/book.svg (renamed from src/qcam/assets/feathericons/book.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/bookmark.svg (renamed from src/qcam/assets/feathericons/bookmark.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/box.svg (renamed from src/qcam/assets/feathericons/box.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/briefcase.svg (renamed from src/qcam/assets/feathericons/briefcase.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/calendar.svg (renamed from src/qcam/assets/feathericons/calendar.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/camera-off.svg (renamed from src/qcam/assets/feathericons/camera-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/camera.svg (renamed from src/qcam/assets/feathericons/camera.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cast.svg (renamed from src/qcam/assets/feathericons/cast.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/check-circle.svg (renamed from src/qcam/assets/feathericons/check-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/check-square.svg (renamed from src/qcam/assets/feathericons/check-square.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/check.svg (renamed from src/qcam/assets/feathericons/check.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevron-down.svg (renamed from src/qcam/assets/feathericons/chevron-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevron-left.svg (renamed from src/qcam/assets/feathericons/chevron-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevron-right.svg (renamed from src/qcam/assets/feathericons/chevron-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevron-up.svg (renamed from src/qcam/assets/feathericons/chevron-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevrons-down.svg (renamed from src/qcam/assets/feathericons/chevrons-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevrons-left.svg (renamed from src/qcam/assets/feathericons/chevrons-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevrons-right.svg (renamed from src/qcam/assets/feathericons/chevrons-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chevrons-up.svg (renamed from src/qcam/assets/feathericons/chevrons-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/chrome.svg (renamed from src/qcam/assets/feathericons/chrome.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/circle.svg (renamed from src/qcam/assets/feathericons/circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/clipboard.svg (renamed from src/qcam/assets/feathericons/clipboard.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/clock.svg (renamed from src/qcam/assets/feathericons/clock.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cloud-drizzle.svg (renamed from src/qcam/assets/feathericons/cloud-drizzle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cloud-lightning.svg (renamed from src/qcam/assets/feathericons/cloud-lightning.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cloud-off.svg (renamed from src/qcam/assets/feathericons/cloud-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cloud-rain.svg (renamed from src/qcam/assets/feathericons/cloud-rain.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cloud-snow.svg (renamed from src/qcam/assets/feathericons/cloud-snow.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cloud.svg (renamed from src/qcam/assets/feathericons/cloud.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/code.svg (renamed from src/qcam/assets/feathericons/code.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/codepen.svg (renamed from src/qcam/assets/feathericons/codepen.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/codesandbox.svg (renamed from src/qcam/assets/feathericons/codesandbox.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/coffee.svg (renamed from src/qcam/assets/feathericons/coffee.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/columns.svg (renamed from src/qcam/assets/feathericons/columns.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/command.svg (renamed from src/qcam/assets/feathericons/command.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/compass.svg (renamed from src/qcam/assets/feathericons/compass.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/copy.svg (renamed from src/qcam/assets/feathericons/copy.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-down-left.svg (renamed from src/qcam/assets/feathericons/corner-down-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-down-right.svg (renamed from src/qcam/assets/feathericons/corner-down-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-left-down.svg (renamed from src/qcam/assets/feathericons/corner-left-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-left-up.svg (renamed from src/qcam/assets/feathericons/corner-left-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-right-down.svg (renamed from src/qcam/assets/feathericons/corner-right-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-right-up.svg (renamed from src/qcam/assets/feathericons/corner-right-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-up-left.svg (renamed from src/qcam/assets/feathericons/corner-up-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/corner-up-right.svg (renamed from src/qcam/assets/feathericons/corner-up-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/cpu.svg (renamed from src/qcam/assets/feathericons/cpu.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/credit-card.svg (renamed from src/qcam/assets/feathericons/credit-card.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/crop.svg (renamed from src/qcam/assets/feathericons/crop.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/crosshair.svg (renamed from src/qcam/assets/feathericons/crosshair.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/database.svg (renamed from src/qcam/assets/feathericons/database.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/delete.svg (renamed from src/qcam/assets/feathericons/delete.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/disc.svg (renamed from src/qcam/assets/feathericons/disc.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/dollar-sign.svg (renamed from src/qcam/assets/feathericons/dollar-sign.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/download-cloud.svg (renamed from src/qcam/assets/feathericons/download-cloud.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/download.svg (renamed from src/qcam/assets/feathericons/download.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/droplet.svg (renamed from src/qcam/assets/feathericons/droplet.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/edit-2.svg (renamed from src/qcam/assets/feathericons/edit-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/edit-3.svg (renamed from src/qcam/assets/feathericons/edit-3.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/edit.svg (renamed from src/qcam/assets/feathericons/edit.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/external-link.svg (renamed from src/qcam/assets/feathericons/external-link.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/eye-off.svg (renamed from src/qcam/assets/feathericons/eye-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/eye.svg (renamed from src/qcam/assets/feathericons/eye.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/facebook.svg (renamed from src/qcam/assets/feathericons/facebook.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/fast-forward.svg (renamed from src/qcam/assets/feathericons/fast-forward.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/feather.svg (renamed from src/qcam/assets/feathericons/feather.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/feathericons.qrc (renamed from src/qcam/assets/feathericons/feathericons.qrc)0
-rw-r--r--src/apps/qcam/assets/feathericons/figma.svg (renamed from src/qcam/assets/feathericons/figma.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/file-minus.svg (renamed from src/qcam/assets/feathericons/file-minus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/file-plus.svg (renamed from src/qcam/assets/feathericons/file-plus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/file-text.svg (renamed from src/qcam/assets/feathericons/file-text.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/file.svg (renamed from src/qcam/assets/feathericons/file.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/film.svg (renamed from src/qcam/assets/feathericons/film.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/filter.svg (renamed from src/qcam/assets/feathericons/filter.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/flag.svg (renamed from src/qcam/assets/feathericons/flag.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/folder-minus.svg (renamed from src/qcam/assets/feathericons/folder-minus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/folder-plus.svg (renamed from src/qcam/assets/feathericons/folder-plus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/folder.svg (renamed from src/qcam/assets/feathericons/folder.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/framer.svg (renamed from src/qcam/assets/feathericons/framer.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/frown.svg (renamed from src/qcam/assets/feathericons/frown.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/gift.svg (renamed from src/qcam/assets/feathericons/gift.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/git-branch.svg (renamed from src/qcam/assets/feathericons/git-branch.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/git-commit.svg (renamed from src/qcam/assets/feathericons/git-commit.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/git-merge.svg (renamed from src/qcam/assets/feathericons/git-merge.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/git-pull-request.svg (renamed from src/qcam/assets/feathericons/git-pull-request.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/github.svg (renamed from src/qcam/assets/feathericons/github.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/gitlab.svg (renamed from src/qcam/assets/feathericons/gitlab.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/globe.svg (renamed from src/qcam/assets/feathericons/globe.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/grid.svg (renamed from src/qcam/assets/feathericons/grid.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/hard-drive.svg (renamed from src/qcam/assets/feathericons/hard-drive.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/hash.svg (renamed from src/qcam/assets/feathericons/hash.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/headphones.svg (renamed from src/qcam/assets/feathericons/headphones.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/heart.svg (renamed from src/qcam/assets/feathericons/heart.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/help-circle.svg (renamed from src/qcam/assets/feathericons/help-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/hexagon.svg (renamed from src/qcam/assets/feathericons/hexagon.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/home.svg (renamed from src/qcam/assets/feathericons/home.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/image.svg (renamed from src/qcam/assets/feathericons/image.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/inbox.svg (renamed from src/qcam/assets/feathericons/inbox.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/info.svg (renamed from src/qcam/assets/feathericons/info.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/instagram.svg (renamed from src/qcam/assets/feathericons/instagram.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/italic.svg (renamed from src/qcam/assets/feathericons/italic.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/key.svg (renamed from src/qcam/assets/feathericons/key.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/layers.svg (renamed from src/qcam/assets/feathericons/layers.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/layout.svg (renamed from src/qcam/assets/feathericons/layout.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/life-buoy.svg (renamed from src/qcam/assets/feathericons/life-buoy.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/link-2.svg (renamed from src/qcam/assets/feathericons/link-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/link.svg (renamed from src/qcam/assets/feathericons/link.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/linkedin.svg (renamed from src/qcam/assets/feathericons/linkedin.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/list.svg (renamed from src/qcam/assets/feathericons/list.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/loader.svg (renamed from src/qcam/assets/feathericons/loader.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/lock.svg (renamed from src/qcam/assets/feathericons/lock.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/log-in.svg (renamed from src/qcam/assets/feathericons/log-in.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/log-out.svg (renamed from src/qcam/assets/feathericons/log-out.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/mail.svg (renamed from src/qcam/assets/feathericons/mail.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/map-pin.svg (renamed from src/qcam/assets/feathericons/map-pin.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/map.svg (renamed from src/qcam/assets/feathericons/map.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/maximize-2.svg (renamed from src/qcam/assets/feathericons/maximize-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/maximize.svg (renamed from src/qcam/assets/feathericons/maximize.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/meh.svg (renamed from src/qcam/assets/feathericons/meh.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/menu.svg (renamed from src/qcam/assets/feathericons/menu.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/message-circle.svg (renamed from src/qcam/assets/feathericons/message-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/message-square.svg (renamed from src/qcam/assets/feathericons/message-square.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/mic-off.svg (renamed from src/qcam/assets/feathericons/mic-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/mic.svg (renamed from src/qcam/assets/feathericons/mic.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/minimize-2.svg (renamed from src/qcam/assets/feathericons/minimize-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/minimize.svg (renamed from src/qcam/assets/feathericons/minimize.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/minus-circle.svg (renamed from src/qcam/assets/feathericons/minus-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/minus-square.svg (renamed from src/qcam/assets/feathericons/minus-square.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/minus.svg (renamed from src/qcam/assets/feathericons/minus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/monitor.svg (renamed from src/qcam/assets/feathericons/monitor.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/moon.svg (renamed from src/qcam/assets/feathericons/moon.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/more-horizontal.svg (renamed from src/qcam/assets/feathericons/more-horizontal.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/more-vertical.svg (renamed from src/qcam/assets/feathericons/more-vertical.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/mouse-pointer.svg (renamed from src/qcam/assets/feathericons/mouse-pointer.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/move.svg (renamed from src/qcam/assets/feathericons/move.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/music.svg (renamed from src/qcam/assets/feathericons/music.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/navigation-2.svg (renamed from src/qcam/assets/feathericons/navigation-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/navigation.svg (renamed from src/qcam/assets/feathericons/navigation.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/octagon.svg (renamed from src/qcam/assets/feathericons/octagon.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/package.svg (renamed from src/qcam/assets/feathericons/package.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/paperclip.svg (renamed from src/qcam/assets/feathericons/paperclip.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/pause-circle.svg (renamed from src/qcam/assets/feathericons/pause-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/pause.svg (renamed from src/qcam/assets/feathericons/pause.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/pen-tool.svg (renamed from src/qcam/assets/feathericons/pen-tool.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/percent.svg (renamed from src/qcam/assets/feathericons/percent.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone-call.svg (renamed from src/qcam/assets/feathericons/phone-call.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone-forwarded.svg (renamed from src/qcam/assets/feathericons/phone-forwarded.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone-incoming.svg (renamed from src/qcam/assets/feathericons/phone-incoming.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone-missed.svg (renamed from src/qcam/assets/feathericons/phone-missed.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone-off.svg (renamed from src/qcam/assets/feathericons/phone-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone-outgoing.svg (renamed from src/qcam/assets/feathericons/phone-outgoing.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/phone.svg (renamed from src/qcam/assets/feathericons/phone.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/pie-chart.svg (renamed from src/qcam/assets/feathericons/pie-chart.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/play-circle.svg (renamed from src/qcam/assets/feathericons/play-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/play.svg (renamed from src/qcam/assets/feathericons/play.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/plus-circle.svg (renamed from src/qcam/assets/feathericons/plus-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/plus-square.svg (renamed from src/qcam/assets/feathericons/plus-square.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/plus.svg (renamed from src/qcam/assets/feathericons/plus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/pocket.svg (renamed from src/qcam/assets/feathericons/pocket.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/power.svg (renamed from src/qcam/assets/feathericons/power.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/printer.svg (renamed from src/qcam/assets/feathericons/printer.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/radio.svg (renamed from src/qcam/assets/feathericons/radio.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/refresh-ccw.svg (renamed from src/qcam/assets/feathericons/refresh-ccw.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/refresh-cw.svg (renamed from src/qcam/assets/feathericons/refresh-cw.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/repeat.svg (renamed from src/qcam/assets/feathericons/repeat.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/rewind.svg (renamed from src/qcam/assets/feathericons/rewind.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/rotate-ccw.svg (renamed from src/qcam/assets/feathericons/rotate-ccw.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/rotate-cw.svg (renamed from src/qcam/assets/feathericons/rotate-cw.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/rss.svg (renamed from src/qcam/assets/feathericons/rss.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/save.svg (renamed from src/qcam/assets/feathericons/save.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/scissors.svg (renamed from src/qcam/assets/feathericons/scissors.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/search.svg (renamed from src/qcam/assets/feathericons/search.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/send.svg (renamed from src/qcam/assets/feathericons/send.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/server.svg (renamed from src/qcam/assets/feathericons/server.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/settings.svg (renamed from src/qcam/assets/feathericons/settings.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/share-2.svg (renamed from src/qcam/assets/feathericons/share-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/share.svg (renamed from src/qcam/assets/feathericons/share.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/shield-off.svg (renamed from src/qcam/assets/feathericons/shield-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/shield.svg (renamed from src/qcam/assets/feathericons/shield.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/shopping-bag.svg (renamed from src/qcam/assets/feathericons/shopping-bag.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/shopping-cart.svg (renamed from src/qcam/assets/feathericons/shopping-cart.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/shuffle.svg (renamed from src/qcam/assets/feathericons/shuffle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/sidebar.svg (renamed from src/qcam/assets/feathericons/sidebar.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/skip-back.svg (renamed from src/qcam/assets/feathericons/skip-back.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/skip-forward.svg (renamed from src/qcam/assets/feathericons/skip-forward.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/slack.svg (renamed from src/qcam/assets/feathericons/slack.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/slash.svg (renamed from src/qcam/assets/feathericons/slash.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/sliders.svg (renamed from src/qcam/assets/feathericons/sliders.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/smartphone.svg (renamed from src/qcam/assets/feathericons/smartphone.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/smile.svg (renamed from src/qcam/assets/feathericons/smile.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/speaker.svg (renamed from src/qcam/assets/feathericons/speaker.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/square.svg (renamed from src/qcam/assets/feathericons/square.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/star.svg (renamed from src/qcam/assets/feathericons/star.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/stop-circle.svg (renamed from src/qcam/assets/feathericons/stop-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/sun.svg (renamed from src/qcam/assets/feathericons/sun.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/sunrise.svg (renamed from src/qcam/assets/feathericons/sunrise.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/sunset.svg (renamed from src/qcam/assets/feathericons/sunset.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/tablet.svg (renamed from src/qcam/assets/feathericons/tablet.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/tag.svg (renamed from src/qcam/assets/feathericons/tag.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/target.svg (renamed from src/qcam/assets/feathericons/target.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/terminal.svg (renamed from src/qcam/assets/feathericons/terminal.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/thermometer.svg (renamed from src/qcam/assets/feathericons/thermometer.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/thumbs-down.svg (renamed from src/qcam/assets/feathericons/thumbs-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/thumbs-up.svg (renamed from src/qcam/assets/feathericons/thumbs-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/toggle-left.svg (renamed from src/qcam/assets/feathericons/toggle-left.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/toggle-right.svg (renamed from src/qcam/assets/feathericons/toggle-right.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/tool.svg (renamed from src/qcam/assets/feathericons/tool.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/trash-2.svg (renamed from src/qcam/assets/feathericons/trash-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/trash.svg (renamed from src/qcam/assets/feathericons/trash.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/trello.svg (renamed from src/qcam/assets/feathericons/trello.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/trending-down.svg (renamed from src/qcam/assets/feathericons/trending-down.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/trending-up.svg (renamed from src/qcam/assets/feathericons/trending-up.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/triangle.svg (renamed from src/qcam/assets/feathericons/triangle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/truck.svg (renamed from src/qcam/assets/feathericons/truck.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/tv.svg (renamed from src/qcam/assets/feathericons/tv.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/twitch.svg (renamed from src/qcam/assets/feathericons/twitch.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/twitter.svg (renamed from src/qcam/assets/feathericons/twitter.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/type.svg (renamed from src/qcam/assets/feathericons/type.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/umbrella.svg (renamed from src/qcam/assets/feathericons/umbrella.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/underline.svg (renamed from src/qcam/assets/feathericons/underline.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/unlock.svg (renamed from src/qcam/assets/feathericons/unlock.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/upload-cloud.svg (renamed from src/qcam/assets/feathericons/upload-cloud.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/upload.svg (renamed from src/qcam/assets/feathericons/upload.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/user-check.svg (renamed from src/qcam/assets/feathericons/user-check.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/user-minus.svg (renamed from src/qcam/assets/feathericons/user-minus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/user-plus.svg (renamed from src/qcam/assets/feathericons/user-plus.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/user-x.svg (renamed from src/qcam/assets/feathericons/user-x.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/user.svg (renamed from src/qcam/assets/feathericons/user.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/users.svg (renamed from src/qcam/assets/feathericons/users.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/video-off.svg (renamed from src/qcam/assets/feathericons/video-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/video.svg (renamed from src/qcam/assets/feathericons/video.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/voicemail.svg (renamed from src/qcam/assets/feathericons/voicemail.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/volume-1.svg (renamed from src/qcam/assets/feathericons/volume-1.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/volume-2.svg (renamed from src/qcam/assets/feathericons/volume-2.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/volume-x.svg (renamed from src/qcam/assets/feathericons/volume-x.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/volume.svg (renamed from src/qcam/assets/feathericons/volume.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/watch.svg (renamed from src/qcam/assets/feathericons/watch.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/wifi-off.svg (renamed from src/qcam/assets/feathericons/wifi-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/wifi.svg (renamed from src/qcam/assets/feathericons/wifi.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/wind.svg (renamed from src/qcam/assets/feathericons/wind.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/x-circle.svg (renamed from src/qcam/assets/feathericons/x-circle.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/x-octagon.svg (renamed from src/qcam/assets/feathericons/x-octagon.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/x-square.svg (renamed from src/qcam/assets/feathericons/x-square.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/x.svg (renamed from src/qcam/assets/feathericons/x.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/youtube.svg (renamed from src/qcam/assets/feathericons/youtube.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/zap-off.svg (renamed from src/qcam/assets/feathericons/zap-off.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/zap.svg (renamed from src/qcam/assets/feathericons/zap.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/zoom-in.svg (renamed from src/qcam/assets/feathericons/zoom-in.svg)0
-rw-r--r--src/apps/qcam/assets/feathericons/zoom-out.svg (renamed from src/qcam/assets/feathericons/zoom-out.svg)0
-rw-r--r--src/apps/qcam/assets/shader/RGB.frag (renamed from src/qcam/assets/shader/RGB.frag)0
-rw-r--r--src/apps/qcam/assets/shader/YUV_2_planes.frag (renamed from src/qcam/assets/shader/YUV_2_planes.frag)29
-rw-r--r--src/apps/qcam/assets/shader/YUV_3_planes.frag (renamed from src/qcam/assets/shader/YUV_3_planes.frag)27
-rw-r--r--src/apps/qcam/assets/shader/YUV_packed.frag (renamed from src/qcam/assets/shader/YUV_packed.frag)17
-rw-r--r--src/apps/qcam/assets/shader/bayer_1x_packed.frag (renamed from src/qcam/assets/shader/bayer_1x_packed.frag)0
-rw-r--r--src/apps/qcam/assets/shader/bayer_8.frag (renamed from src/qcam/assets/shader/bayer_8.frag)3
-rw-r--r--src/apps/qcam/assets/shader/bayer_8.vert (renamed from src/qcam/assets/shader/bayer_8.vert)0
-rw-r--r--src/apps/qcam/assets/shader/identity.vert (renamed from src/qcam/assets/shader/identity.vert)0
-rw-r--r--src/apps/qcam/assets/shader/shaders.qrc (renamed from src/qcam/assets/shader/shaders.qrc)0
-rw-r--r--src/apps/qcam/cam_select_dialog.cpp111
-rw-r--r--src/apps/qcam/cam_select_dialog.h47
-rw-r--r--src/apps/qcam/format_converter.cpp (renamed from src/qcam/format_converter.cpp)6
-rw-r--r--src/apps/qcam/format_converter.h (renamed from src/qcam/format_converter.h)0
-rw-r--r--src/apps/qcam/main.cpp (renamed from src/qcam/main.cpp)5
-rw-r--r--src/apps/qcam/main_window.cpp (renamed from src/qcam/main_window.cpp)96
-rw-r--r--src/apps/qcam/main_window.h (renamed from src/qcam/main_window.h)29
-rw-r--r--src/apps/qcam/meson.build (renamed from src/qcam/meson.build)35
-rw-r--r--src/apps/qcam/message_handler.cpp (renamed from src/qcam/message_handler.cpp)0
-rw-r--r--src/apps/qcam/message_handler.h (renamed from src/qcam/message_handler.h)0
-rw-r--r--src/apps/qcam/viewfinder.h (renamed from src/qcam/viewfinder.h)2
-rw-r--r--src/apps/qcam/viewfinder_gl.cpp (renamed from src/qcam/viewfinder_gl.cpp)86
-rw-r--r--src/apps/qcam/viewfinder_gl.h (renamed from src/qcam/viewfinder_gl.h)3
-rw-r--r--src/apps/qcam/viewfinder_qt.cpp (renamed from src/qcam/viewfinder_qt.cpp)18
-rw-r--r--src/apps/qcam/viewfinder_qt.h (renamed from src/qcam/viewfinder_qt.h)1
-rw-r--r--src/cam/capture_script.cpp336
-rw-r--r--src/cam/sdl_texture_mjpg.cpp25
-rw-r--r--src/cam/sdl_texture_yuyv.cpp20
-rw-r--r--src/cam/sdl_texture_yuyv.h17
-rw-r--r--src/gstreamer/gstlibcamera-utils.cpp361
-rw-r--r--src/gstreamer/gstlibcamera-utils.h18
-rw-r--r--src/gstreamer/gstlibcamerapad.cpp55
-rw-r--r--src/gstreamer/gstlibcamerapad.h6
-rw-r--r--src/gstreamer/gstlibcamerapool.cpp7
-rw-r--r--src/gstreamer/gstlibcamerapool.h2
-rw-r--r--src/gstreamer/gstlibcameraprovider.cpp20
-rw-r--r--src/gstreamer/gstlibcamerasrc.cpp596
-rw-r--r--src/gstreamer/gstlibcamerasrc.h31
-rw-r--r--src/gstreamer/meson.build14
-rw-r--r--src/ipa/ipu3/algorithms/af.cpp78
-rw-r--r--src/ipa/ipu3/algorithms/af.h12
-rw-r--r--src/ipa/ipu3/algorithms/agc.cpp39
-rw-r--r--src/ipa/ipu3/algorithms/agc.h8
-rw-r--r--src/ipa/ipu3/algorithms/awb.cpp177
-rw-r--r--src/ipa/ipu3/algorithms/awb.h10
-rw-r--r--src/ipa/ipu3/algorithms/blc.cpp12
-rw-r--r--src/ipa/ipu3/algorithms/blc.h4
-rw-r--r--src/ipa/ipu3/algorithms/tone_mapping.cpp24
-rw-r--r--src/ipa/ipu3/algorithms/tone_mapping.h9
-rw-r--r--src/ipa/ipu3/data/meson.build9
-rw-r--r--src/ipa/ipu3/data/uncalibrated.yaml11
-rw-r--r--src/ipa/ipu3/ipa_context.cpp49
-rw-r--r--src/ipa/ipu3/ipa_context.h19
-rw-r--r--src/ipa/ipu3/ipu3-ipa-design-guide.rst2
-rw-r--r--src/ipa/ipu3/ipu3.cpp165
-rw-r--r--src/ipa/ipu3/meson.build3
-rw-r--r--src/ipa/libipa/algorithm.cpp27
-rw-r--r--src/ipa/libipa/algorithm.h18
-rw-r--r--src/ipa/libipa/camera_sensor_helper.cpp182
-rw-r--r--src/ipa/libipa/camera_sensor_helper.h49
-rw-r--r--src/ipa/libipa/fc_queue.cpp140
-rw-r--r--src/ipa/libipa/fc_queue.h118
-rw-r--r--src/ipa/libipa/histogram.cpp4
-rw-r--r--src/ipa/libipa/histogram.h2
-rw-r--r--src/ipa/libipa/meson.build2
-rw-r--r--src/ipa/libipa/module.cpp2
-rw-r--r--src/ipa/meson.build46
-rw-r--r--src/ipa/raspberrypi/cam_helper.cpp219
-rw-r--r--src/ipa/raspberrypi/cam_helper.hpp123
-rw-r--r--src/ipa/raspberrypi/cam_helper_imx290.cpp67
-rw-r--r--src/ipa/raspberrypi/cam_helper_imx296.cpp69
-rw-r--r--src/ipa/raspberrypi/controller/agc_algorithm.hpp32
-rw-r--r--src/ipa/raspberrypi/controller/agc_status.h41
-rw-r--r--src/ipa/raspberrypi/controller/algorithm.cpp44
-rw-r--r--src/ipa/raspberrypi/controller/algorithm.hpp60
-rw-r--r--src/ipa/raspberrypi/controller/alsc_status.h27
-rw-r--r--src/ipa/raspberrypi/controller/awb_algorithm.hpp23
-rw-r--r--src/ipa/raspberrypi/controller/awb_status.h26
-rw-r--r--src/ipa/raspberrypi/controller/black_level_status.h23
-rw-r--r--src/ipa/raspberrypi/controller/camera_mode.h50
-rw-r--r--src/ipa/raspberrypi/controller/ccm_algorithm.hpp21
-rw-r--r--src/ipa/raspberrypi/controller/contrast_algorithm.hpp22
-rw-r--r--src/ipa/raspberrypi/controller/contrast_status.h31
-rw-r--r--src/ipa/raspberrypi/controller/controller.cpp104
-rw-r--r--src/ipa/raspberrypi/controller/controller.hpp54
-rw-r--r--src/ipa/raspberrypi/controller/denoise_algorithm.hpp23
-rw-r--r--src/ipa/raspberrypi/controller/denoise_status.h24
-rw-r--r--src/ipa/raspberrypi/controller/device_status.cpp30
-rw-r--r--src/ipa/raspberrypi/controller/dpc_status.h21
-rw-r--r--src/ipa/raspberrypi/controller/focus_status.h26
-rw-r--r--src/ipa/raspberrypi/controller/histogram.cpp64
-rw-r--r--src/ipa/raspberrypi/controller/histogram.hpp44
-rw-r--r--src/ipa/raspberrypi/controller/lux_status.h29
-rw-r--r--src/ipa/raspberrypi/controller/metadata.hpp110
-rw-r--r--src/ipa/raspberrypi/controller/noise_status.h22
-rw-r--r--src/ipa/raspberrypi/controller/pwl.cpp246
-rw-r--r--src/ipa/raspberrypi/controller/pwl.hpp112
-rw-r--r--src/ipa/raspberrypi/controller/rpi/agc.cpp797
-rw-r--r--src/ipa/raspberrypi/controller/rpi/agc.hpp139
-rw-r--r--src/ipa/raspberrypi/controller/rpi/alsc.cpp787
-rw-r--r--src/ipa/raspberrypi/controller/rpi/alsc.hpp106
-rw-r--r--src/ipa/raspberrypi/controller/rpi/awb.cpp667
-rw-r--r--src/ipa/raspberrypi/controller/rpi/awb.hpp179
-rw-r--r--src/ipa/raspberrypi/controller/rpi/black_level.cpp63
-rw-r--r--src/ipa/raspberrypi/controller/rpi/black_level.hpp30
-rw-r--r--src/ipa/raspberrypi/controller/rpi/ccm.cpp169
-rw-r--r--src/ipa/raspberrypi/controller/rpi/contrast.cpp185
-rw-r--r--src/ipa/raspberrypi/controller/rpi/contrast.hpp50
-rw-r--r--src/ipa/raspberrypi/controller/rpi/dpc.cpp53
-rw-r--r--src/ipa/raspberrypi/controller/rpi/dpc.hpp32
-rw-r--r--src/ipa/raspberrypi/controller/rpi/focus.cpp50
-rw-r--r--src/ipa/raspberrypi/controller/rpi/geq.cpp81
-rw-r--r--src/ipa/raspberrypi/controller/rpi/geq.hpp34
-rw-r--r--src/ipa/raspberrypi/controller/rpi/lux.cpp104
-rw-r--r--src/ipa/raspberrypi/controller/rpi/lux.hpp43
-rw-r--r--src/ipa/raspberrypi/controller/rpi/noise.cpp76
-rw-r--r--src/ipa/raspberrypi/controller/rpi/noise.hpp32
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sdn.cpp75
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sdn.hpp32
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sharpen.cpp85
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sharpen.hpp34
-rw-r--r--src/ipa/raspberrypi/controller/sharpen_algorithm.hpp21
-rw-r--r--src/ipa/raspberrypi/controller/sharpen_status.h28
-rw-r--r--src/ipa/raspberrypi/data/imx219.json412
-rw-r--r--src/ipa/raspberrypi/data/imx219_noir.json344
-rw-r--r--src/ipa/raspberrypi/data/imx290.json165
-rw-r--r--src/ipa/raspberrypi/data/imx296.json191
-rw-r--r--src/ipa/raspberrypi/data/imx378.json338
-rw-r--r--src/ipa/raspberrypi/data/imx477.json430
-rw-r--r--src/ipa/raspberrypi/data/imx477_noir.json362
-rw-r--r--src/ipa/raspberrypi/data/imx519.json338
-rw-r--r--src/ipa/raspberrypi/data/ov5647.json409
-rw-r--r--src/ipa/raspberrypi/data/ov5647_noir.json341
-rw-r--r--src/ipa/raspberrypi/data/ov9281.json92
-rw-r--r--src/ipa/raspberrypi/data/se327m12.json341
-rw-r--r--src/ipa/raspberrypi/data/uncalibrated.json82
-rw-r--r--src/ipa/raspberrypi/md_parser_smia.cpp149
-rw-r--r--src/ipa/raspberrypi/meson.build66
-rw-r--r--src/ipa/raspberrypi/raspberrypi.cpp1460
-rw-r--r--src/ipa/rkisp1/algorithms/agc.cpp246
-rw-r--r--src/ipa/rkisp1/algorithms/agc.h29
-rw-r--r--src/ipa/rkisp1/algorithms/algorithm.h12
-rw-r--r--src/ipa/rkisp1/algorithms/awb.cpp319
-rw-r--r--src/ipa/rkisp1/algorithms/awb.h19
-rw-r--r--src/ipa/rkisp1/algorithms/blc.cpp55
-rw-r--r--src/ipa/rkisp1/algorithms/blc.h18
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.cpp111
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.h33
-rw-r--r--src/ipa/rkisp1/algorithms/dpcc.cpp251
-rw-r--r--src/ipa/rkisp1/algorithms/dpcc.h32
-rw-r--r--src/ipa/rkisp1/algorithms/dpf.cpp260
-rw-r--r--src/ipa/rkisp1/algorithms/dpf.h38
-rw-r--r--src/ipa/rkisp1/algorithms/filter.cpp216
-rw-r--r--src/ipa/rkisp1/algorithms/filter.h33
-rw-r--r--src/ipa/rkisp1/algorithms/gsl.cpp146
-rw-r--r--src/ipa/rkisp1/algorithms/gsl.h35
-rw-r--r--src/ipa/rkisp1/algorithms/lsc.cpp342
-rw-r--r--src/ipa/rkisp1/algorithms/lsc.h59
-rw-r--r--src/ipa/rkisp1/algorithms/meson.build6
-rw-r--r--src/ipa/rkisp1/data/imx219.yaml107
-rw-r--r--src/ipa/rkisp1/data/imx258.yaml54
-rw-r--r--src/ipa/rkisp1/data/meson.build4
-rw-r--r--src/ipa/rkisp1/data/ov2685.yaml41
-rw-r--r--src/ipa/rkisp1/data/ov4689.yaml13
-rw-r--r--src/ipa/rkisp1/data/ov5640.yaml243
-rw-r--r--src/ipa/rkisp1/data/ov5695.yaml41
-rw-r--r--src/ipa/rkisp1/data/ov8858.yaml54
-rw-r--r--src/ipa/rkisp1/data/uncalibrated.yaml2
-rw-r--r--src/ipa/rkisp1/ipa_context.cpp314
-rw-r--r--src/ipa/rkisp1/ipa_context.h106
-rw-r--r--src/ipa/rkisp1/meson.build2
-rw-r--r--src/ipa/rkisp1/rkisp1.cpp311
-rw-r--r--src/ipa/rpi/README.md (renamed from src/ipa/raspberrypi/README.md)0
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper.cpp265
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper.h132
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx219.cpp (renamed from src/ipa/raspberrypi/cam_helper_imx219.cpp)48
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx290.cpp75
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx296.cpp83
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx477.cpp (renamed from src/ipa/raspberrypi/cam_helper_imx477.cpp)101
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx519.cpp (renamed from src/ipa/raspberrypi/cam_helper_imx519.cpp)99
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx708.cpp382
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp (renamed from src/ipa/raspberrypi/cam_helper_ov5647.cpp)49
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp74
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp (renamed from src/ipa/raspberrypi/cam_helper_ov9281.cpp)33
-rw-r--r--src/ipa/rpi/cam_helper/md_parser.h (renamed from src/ipa/raspberrypi/md_parser.hpp)50
-rw-r--r--src/ipa/rpi/cam_helper/md_parser_smia.cpp152
-rw-r--r--src/ipa/rpi/cam_helper/meson.build27
-rw-r--r--src/ipa/rpi/common/ipa_base.cpp1456
-rw-r--r--src/ipa/rpi/common/ipa_base.h131
-rw-r--r--src/ipa/rpi/common/meson.build17
-rw-r--r--src/ipa/rpi/controller/af_algorithm.h76
-rw-r--r--src/ipa/rpi/controller/af_status.h35
-rw-r--r--src/ipa/rpi/controller/agc_algorithm.h38
-rw-r--r--src/ipa/rpi/controller/agc_status.h48
-rw-r--r--src/ipa/rpi/controller/algorithm.cpp56
-rw-r--r--src/ipa/rpi/controller/algorithm.h68
-rw-r--r--src/ipa/rpi/controller/alsc_status.h22
-rw-r--r--src/ipa/rpi/controller/awb_algorithm.h26
-rw-r--r--src/ipa/rpi/controller/awb_status.h20
-rw-r--r--src/ipa/rpi/controller/black_level_algorithm.h23
-rw-r--r--src/ipa/rpi/controller/black_level_status.h15
-rw-r--r--src/ipa/rpi/controller/cac_status.h16
-rw-r--r--src/ipa/rpi/controller/camera_mode.h59
-rw-r--r--src/ipa/rpi/controller/ccm_algorithm.h21
-rw-r--r--src/ipa/rpi/controller/ccm_status.h (renamed from src/ipa/raspberrypi/controller/ccm_status.h)12
-rw-r--r--src/ipa/rpi/controller/contrast_algorithm.h24
-rw-r--r--src/ipa/rpi/controller/contrast_status.h20
-rw-r--r--src/ipa/rpi/controller/controller.cpp220
-rw-r--r--src/ipa/rpi/controller/controller.h77
-rw-r--r--src/ipa/rpi/controller/denoise_algorithm.h27
-rw-r--r--src/ipa/rpi/controller/denoise_status.h35
-rw-r--r--src/ipa/rpi/controller/device_status.cpp31
-rw-r--r--src/ipa/rpi/controller/device_status.h (renamed from src/ipa/raspberrypi/controller/device_status.h)24
-rw-r--r--src/ipa/rpi/controller/dpc_status.h13
-rw-r--r--src/ipa/rpi/controller/geq_status.h (renamed from src/ipa/raspberrypi/controller/geq_status.h)12
-rw-r--r--src/ipa/rpi/controller/hdr_algorithm.h25
-rw-r--r--src/ipa/rpi/controller/hdr_status.h19
-rw-r--r--src/ipa/rpi/controller/histogram.cpp76
-rw-r--r--src/ipa/rpi/controller/histogram.h55
-rw-r--r--src/ipa/rpi/controller/lux_status.h23
-rw-r--r--src/ipa/rpi/controller/meson.build35
-rw-r--r--src/ipa/rpi/controller/metadata.h127
-rw-r--r--src/ipa/rpi/controller/noise_status.h14
-rw-r--r--src/ipa/rpi/controller/pdaf_data.h24
-rw-r--r--src/ipa/rpi/controller/pwl.cpp269
-rw-r--r--src/ipa/rpi/controller/pwl.h127
-rw-r--r--src/ipa/rpi/controller/region_stats.h123
-rw-r--r--src/ipa/rpi/controller/rpi/af.cpp797
-rw-r--r--src/ipa/rpi/controller/rpi/af.h165
-rw-r--r--src/ipa/rpi/controller/rpi/agc.cpp338
-rw-r--r--src/ipa/rpi/controller/rpi/agc.h58
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.cpp1022
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.h153
-rw-r--r--src/ipa/rpi/controller/rpi/alsc.cpp867
-rw-r--r--src/ipa/rpi/controller/rpi/alsc.h174
-rw-r--r--src/ipa/rpi/controller/rpi/awb.cpp751
-rw-r--r--src/ipa/rpi/controller/rpi/awb.h192
-rw-r--r--src/ipa/rpi/controller/rpi/black_level.cpp74
-rw-r--r--src/ipa/rpi/controller/rpi/black_level.h32
-rw-r--r--src/ipa/rpi/controller/rpi/cac.cpp107
-rw-r--r--src/ipa/rpi/controller/rpi/cac.h35
-rw-r--r--src/ipa/rpi/controller/rpi/ccm.cpp199
-rw-r--r--src/ipa/rpi/controller/rpi/ccm.h (renamed from src/ipa/raspberrypi/controller/rpi/ccm.hpp)24
-rw-r--r--src/ipa/rpi/controller/rpi/contrast.cpp192
-rw-r--r--src/ipa/rpi/controller/rpi/contrast.h54
-rw-r--r--src/ipa/rpi/controller/rpi/denoise.cpp198
-rw-r--r--src/ipa/rpi/controller/rpi/denoise.h59
-rw-r--r--src/ipa/rpi/controller/rpi/dpc.cpp59
-rw-r--r--src/ipa/rpi/controller/rpi/dpc.h32
-rw-r--r--src/ipa/rpi/controller/rpi/focus.h (renamed from src/ipa/raspberrypi/controller/rpi/focus.hpp)12
-rw-r--r--src/ipa/rpi/controller/rpi/geq.cpp89
-rw-r--r--src/ipa/rpi/controller/rpi/geq.h34
-rw-r--r--src/ipa/rpi/controller/rpi/hdr.cpp337
-rw-r--r--src/ipa/rpi/controller/rpi/hdr.h76
-rw-r--r--src/ipa/rpi/controller/rpi/lux.cpp115
-rw-r--r--src/ipa/rpi/controller/rpi/lux.h45
-rw-r--r--src/ipa/rpi/controller/rpi/noise.cpp89
-rw-r--r--src/ipa/rpi/controller/rpi/noise.h32
-rw-r--r--src/ipa/rpi/controller/rpi/saturation.cpp57
-rw-r--r--src/ipa/rpi/controller/rpi/saturation.h32
-rw-r--r--src/ipa/rpi/controller/rpi/sdn.cpp83
-rw-r--r--src/ipa/rpi/controller/rpi/sdn.h32
-rw-r--r--src/ipa/rpi/controller/rpi/sharpen.cpp92
-rw-r--r--src/ipa/rpi/controller/rpi/sharpen.h34
-rw-r--r--src/ipa/rpi/controller/rpi/tonemap.cpp61
-rw-r--r--src/ipa/rpi/controller/rpi/tonemap.h35
-rw-r--r--src/ipa/rpi/controller/saturation_status.h13
-rw-r--r--src/ipa/rpi/controller/sharpen_algorithm.h21
-rw-r--r--src/ipa/rpi/controller/sharpen_status.h20
-rw-r--r--src/ipa/rpi/controller/statistics.h78
-rw-r--r--src/ipa/rpi/controller/stitch_status.h17
-rw-r--r--src/ipa/rpi/controller/tonemap_status.h17
-rw-r--r--src/ipa/rpi/meson.build14
-rw-r--r--src/ipa/rpi/vc4/data/imx219.json670
-rw-r--r--src/ipa/rpi/vc4/data/imx219_noir.json604
-rw-r--r--src/ipa/rpi/vc4/data/imx290.json205
-rw-r--r--src/ipa/rpi/vc4/data/imx296.json434
-rw-r--r--src/ipa/rpi/vc4/data/imx296_mono.json231
-rw-r--r--src/ipa/rpi/vc4/data/imx378.json418
-rw-r--r--src/ipa/rpi/vc4/data/imx477.json675
-rw-r--r--src/ipa/rpi/vc4/data/imx477_noir.json631
-rw-r--r--src/ipa/rpi/vc4/data/imx477_scientific.json479
-rw-r--r--src/ipa/rpi/vc4/data/imx477_v1.json516
-rw-r--r--src/ipa/rpi/vc4/data/imx519.json418
-rw-r--r--src/ipa/rpi/vc4/data/imx708.json646
-rw-r--r--src/ipa/rpi/vc4/data/imx708_noir.json745
-rw-r--r--src/ipa/rpi/vc4/data/imx708_wide.json657
-rw-r--r--src/ipa/rpi/vc4/data/imx708_wide_noir.json648
-rw-r--r--src/ipa/rpi/vc4/data/meson.build (renamed from src/ipa/raspberrypi/data/meson.build)12
-rw-r--r--src/ipa/rpi/vc4/data/ov5647.json673
-rw-r--r--src/ipa/rpi/vc4/data/ov5647_noir.json403
-rw-r--r--src/ipa/rpi/vc4/data/ov64a40.json422
-rw-r--r--src/ipa/rpi/vc4/data/ov9281_mono.json133
-rw-r--r--src/ipa/rpi/vc4/data/se327m12.json423
-rw-r--r--src/ipa/rpi/vc4/data/uncalibrated.json128
-rw-r--r--src/ipa/rpi/vc4/meson.build48
-rw-r--r--src/ipa/rpi/vc4/vc4.cpp597
-rw-r--r--src/ipa/simple/black_level.cpp88
-rw-r--r--src/ipa/simple/black_level.h28
-rw-r--r--src/ipa/simple/data/meson.build10
-rw-r--r--src/ipa/simple/data/uncalibrated.yaml (renamed from test/pipeline/meson.build)7
-rw-r--r--src/ipa/simple/meson.build30
-rw-r--r--src/ipa/simple/soft_simple.cpp403
-rw-r--r--src/ipa/vimc/data/meson.build3
-rw-r--r--src/ipa/vimc/meson.build2
-rw-r--r--src/ipa/vimc/vimc.cpp26
-rw-r--r--src/libcamera/base/backtrace.cpp12
-rw-r--r--src/libcamera/base/bound_method.cpp1
-rw-r--r--src/libcamera/base/event_notifier.cpp6
-rw-r--r--src/libcamera/base/file.cpp5
-rw-r--r--src/libcamera/base/log.cpp54
-rw-r--r--src/libcamera/base/meson.build7
-rw-r--r--src/libcamera/base/object.cpp55
-rw-r--r--src/libcamera/base/semaphore.cpp4
-rw-r--r--src/libcamera/base/signal.cpp3
-rw-r--r--src/libcamera/base/thread.cpp17
-rw-r--r--src/libcamera/base/timer.cpp10
-rw-r--r--src/libcamera/base/utils.cpp68
-rw-r--r--src/libcamera/bayer_format.cpp44
-rw-r--r--src/libcamera/camera.cpp259
-rw-r--r--src/libcamera/camera_manager.cpp207
-rw-r--r--src/libcamera/color_space.cpp429
-rw-r--r--src/libcamera/control_ids.cpp.in14
-rw-r--r--src/libcamera/control_ids_core.yaml (renamed from src/libcamera/control_ids.yaml)408
-rw-r--r--src/libcamera/control_ids_draft.yaml230
-rw-r--r--src/libcamera/control_ids_rpi.yaml29
-rw-r--r--src/libcamera/control_ranges.yaml18
-rw-r--r--src/libcamera/control_serializer.cpp28
-rw-r--r--src/libcamera/controls.cpp68
-rw-r--r--src/libcamera/converter.cpp335
-rw-r--r--src/libcamera/converter/converter_v4l2_m2m.cpp (renamed from src/libcamera/pipeline/simple/converter.cpp)169
-rw-r--r--src/libcamera/converter/meson.build5
-rw-r--r--src/libcamera/delayed_controls.cpp14
-rw-r--r--src/libcamera/device_enumerator.cpp14
-rw-r--r--src/libcamera/device_enumerator_udev.cpp10
-rw-r--r--src/libcamera/dma_heaps.cpp165
-rw-r--r--src/libcamera/formats.cpp456
-rw-r--r--src/libcamera/formats.yaml30
-rw-r--r--src/libcamera/framebuffer.cpp58
-rw-r--r--src/libcamera/framebuffer_allocator.cpp19
-rw-r--r--src/libcamera/geometry.cpp8
-rw-r--r--src/libcamera/ipa/meson.build7
-rw-r--r--src/libcamera/ipa_manager.cpp20
-rw-r--r--src/libcamera/ipa_module.cpp23
-rw-r--r--src/libcamera/media_device.cpp18
-rw-r--r--src/libcamera/meson.build72
-rw-r--r--src/libcamera/orientation.cpp115
-rw-r--r--src/libcamera/pipeline/imx8-isi/imx8-isi.cpp1117
-rw-r--r--src/libcamera/pipeline/imx8-isi/meson.build5
-rw-r--r--src/libcamera/pipeline/ipu3/cio2.cpp17
-rw-r--r--src/libcamera/pipeline/ipu3/cio2.h4
-rw-r--r--src/libcamera/pipeline/ipu3/imgu.cpp6
-rw-r--r--src/libcamera/pipeline/ipu3/ipu3.cpp155
-rw-r--r--src/libcamera/pipeline/mali-c55/mali-c55.cpp1066
-rw-r--r--src/libcamera/pipeline/mali-c55/meson.build5
-rw-r--r--src/libcamera/pipeline/meson.build15
-rw-r--r--src/libcamera/pipeline/raspberrypi/dma_heaps.cpp90
-rw-r--r--src/libcamera/pipeline/raspberrypi/raspberrypi.cpp2200
-rw-r--r--src/libcamera/pipeline/raspberrypi/rpi_stream.h178
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1.cpp516
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1_path.cpp297
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1_path.h16
-rw-r--r--src/libcamera/pipeline/rpi/common/delayed_controls.cpp293
-rw-r--r--src/libcamera/pipeline/rpi/common/delayed_controls.h87
-rw-r--r--src/libcamera/pipeline/rpi/common/meson.build (renamed from src/libcamera/pipeline/raspberrypi/meson.build)4
-rw-r--r--src/libcamera/pipeline/rpi/common/pipeline_base.cpp1487
-rw-r--r--src/libcamera/pipeline/rpi/common/pipeline_base.h286
-rw-r--r--src/libcamera/pipeline/rpi/common/rpi_stream.cpp (renamed from src/libcamera/pipeline/raspberrypi/rpi_stream.cpp)157
-rw-r--r--src/libcamera/pipeline/rpi/common/rpi_stream.h199
-rw-r--r--src/libcamera/pipeline/rpi/meson.build12
-rw-r--r--src/libcamera/pipeline/rpi/vc4/data/example.yaml46
-rw-r--r--src/libcamera/pipeline/rpi/vc4/data/meson.build9
-rw-r--r--src/libcamera/pipeline/rpi/vc4/meson.build7
-rw-r--r--src/libcamera/pipeline/rpi/vc4/vc4.cpp1023
-rw-r--r--src/libcamera/pipeline/simple/meson.build1
-rw-r--r--src/libcamera/pipeline/simple/simple.cpp461
-rw-r--r--src/libcamera/pipeline/uvcvideo/uvcvideo.cpp233
-rw-r--r--src/libcamera/pipeline/vimc/vimc.cpp45
-rw-r--r--src/libcamera/pipeline_handler.cpp234
-rw-r--r--src/libcamera/process.cpp4
-rw-r--r--src/libcamera/property_ids.cpp.in14
-rw-r--r--src/libcamera/property_ids_core.yaml (renamed from src/libcamera/property_ids.yaml)50
-rw-r--r--src/libcamera/property_ids_draft.yaml39
-rw-r--r--src/libcamera/proxy/worker/meson.build2
-rw-r--r--src/libcamera/pub_key.cpp50
-rw-r--r--src/libcamera/request.cpp15
-rw-r--r--src/libcamera/sensor/camera_sensor.cpp (renamed from src/libcamera/camera_sensor.cpp)585
-rw-r--r--src/libcamera/sensor/camera_sensor_properties.cpp (renamed from src/libcamera/camera_sensor_properties.cpp)88
-rw-r--r--src/libcamera/sensor/meson.build6
-rw-r--r--src/libcamera/shared_mem_object.cpp236
-rw-r--r--src/libcamera/software_isp/TODO279
-rw-r--r--src/libcamera/software_isp/debayer.cpp132
-rw-r--r--src/libcamera/software_isp/debayer.h54
-rw-r--r--src/libcamera/software_isp/debayer_cpu.cpp807
-rw-r--r--src/libcamera/software_isp/debayer_cpu.h158
-rw-r--r--src/libcamera/software_isp/meson.build15
-rw-r--r--src/libcamera/software_isp/software_isp.cpp357
-rw-r--r--src/libcamera/software_isp/swstats_cpu.cpp432
-rw-r--r--src/libcamera/software_isp/swstats_cpu.h97
-rw-r--r--src/libcamera/stream.cpp21
-rw-r--r--src/libcamera/transform.cpp111
-rw-r--r--src/libcamera/v4l2_device.cpp86
-rw-r--r--src/libcamera/v4l2_pixelformat.cpp68
-rw-r--r--src/libcamera/v4l2_subdevice.cpp1282
-rw-r--r--src/libcamera/v4l2_videodevice.cpp114
-rw-r--r--src/libcamera/yaml_parser.cpp356
-rw-r--r--src/meson.build9
-rwxr-xr-xsrc/py/cam/cam.py52
-rw-r--r--src/py/cam/helpers.py6
-rwxr-xr-xsrc/py/examples/simple-cam.py20
-rwxr-xr-xsrc/py/examples/simple-capture.py33
-rwxr-xr-xsrc/py/examples/simple-continuous-capture.py26
-rwxr-xr-xsrc/py/libcamera/gen-py-controls.py90
-rw-r--r--src/py/libcamera/meson.build52
-rw-r--r--src/py/libcamera/py_camera_manager.cpp131
-rw-r--r--src/py/libcamera/py_camera_manager.h45
-rw-r--r--src/py/libcamera/py_color_space.cpp70
-rw-r--r--src/py/libcamera/py_controls_generated.cpp.in8
-rw-r--r--src/py/libcamera/py_enums.cpp12
-rw-r--r--src/py/libcamera/py_formats_generated.cpp.in2
-rw-r--r--src/py/libcamera/py_geometry.cpp2
-rw-r--r--src/py/libcamera/py_helpers.cpp97
-rw-r--r--src/py/libcamera/py_helpers.h13
-rw-r--r--src/py/libcamera/py_main.cpp455
-rw-r--r--src/py/libcamera/py_main.h14
-rw-r--r--src/py/libcamera/py_properties_generated.cpp.in8
-rw-r--r--src/py/libcamera/py_transform.cpp81
-rw-r--r--src/py/meson.build2
-rw-r--r--src/v4l2/meson.build7
-rw-r--r--src/v4l2/v4l2_camera.cpp5
-rw-r--r--src/v4l2/v4l2_camera.h14
-rw-r--r--src/v4l2/v4l2_camera_proxy.cpp18
-rw-r--r--src/v4l2/v4l2_camera_proxy.h12
-rw-r--r--src/v4l2/v4l2_compat_manager.cpp32
-rw-r--r--subprojects/.gitignore3
-rw-r--r--subprojects/gtest.wrap2
-rw-r--r--subprojects/libyaml.wrap2
-rw-r--r--subprojects/libyuv.wrap2
-rw-r--r--subprojects/packagefiles/pybind11/meson.build7
-rw-r--r--subprojects/pybind11.wrap9
-rw-r--r--test/camera-sensor.cpp13
-rw-r--r--test/camera/camera_reconfigure.cpp4
-rw-r--r--test/camera/meson.build18
-rw-r--r--test/color-space.cpp105
-rw-r--r--test/controls/control_info.cpp10
-rw-r--r--test/controls/control_info_map.cpp7
-rw-r--r--test/controls/control_list.cpp68
-rw-r--r--test/controls/meson.build14
-rw-r--r--test/delayed_controls.cpp23
-rw-r--r--test/event-thread.cpp38
-rw-r--r--test/gstreamer/gstreamer_device_provider_test.cpp77
-rw-r--r--test/gstreamer/gstreamer_multi_stream_test.cpp23
-rw-r--r--test/gstreamer/gstreamer_single_stream_test.cpp2
-rw-r--r--test/gstreamer/gstreamer_test.cpp65
-rw-r--r--test/gstreamer/gstreamer_test.h10
-rw-r--r--test/gstreamer/meson.build13
-rw-r--r--test/ipa/ipa_interface_test.cpp13
-rw-r--r--test/ipa/meson.build10
-rw-r--r--test/ipc/meson.build10
-rw-r--r--test/ipc/unixsocket.cpp2
-rw-r--r--test/libtest/buffer_source.cpp2
-rw-r--r--test/log/log_process.cpp12
-rw-r--r--test/log/meson.build10
-rw-r--r--test/media_device/meson.build12
-rw-r--r--test/meson.build119
-rw-r--r--test/message.cpp54
-rw-r--r--test/object-delete.cpp30
-rw-r--r--test/pipeline/ipu3/ipu3_pipeline_test.cpp126
-rw-r--r--test/pipeline/ipu3/meson.build14
-rw-r--r--test/pipeline/rkisp1/meson.build14
-rw-r--r--test/pipeline/rkisp1/rkisp1_pipeline_test.cpp115
-rw-r--r--test/process/meson.build8
-rw-r--r--test/py/meson.build17
-rwxr-xr-xtest/py/unittests.py151
-rw-r--r--test/serialization/generated_serializer/generated_serializer_test.cpp25
-rw-r--r--test/serialization/generated_serializer/include/libcamera/ipa/test.mojom10
-rw-r--r--test/serialization/ipa_data_serializer_test.cpp4
-rw-r--r--test/serialization/meson.build10
-rw-r--r--test/signal-threads.cpp24
-rw-r--r--test/stream/meson.build9
-rw-r--r--test/stream/stream_colorspace.cpp96
-rw-r--r--test/threads.cpp47
-rw-r--r--test/timer-fail.cpp109
-rw-r--r--test/timer-thread.cpp37
-rw-r--r--test/transform.cpp329
-rw-r--r--test/utils.cpp9
-rw-r--r--test/v4l2_compat/meson.build33
-rwxr-xr-xtest/v4l2_compat/v4l2_compat_test.py18
-rw-r--r--test/v4l2_subdevice/meson.build10
-rw-r--r--test/v4l2_videodevice/meson.build26
-rw-r--r--test/v4l2_videodevice/v4l2_m2mdevice.cpp5
-rw-r--r--test/v4l2_videodevice/v4l2_videodevice_test.cpp2
-rw-r--r--test/yaml-parser.cpp461
-rwxr-xr-xutils/abi-compat.sh212
-rwxr-xr-xutils/checkstyle.py236
-rwxr-xr-xutils/gen-controls.py280
-rwxr-xr-xutils/gen-version.sh8
-rwxr-xr-xutils/hooks/pre-push12
-rwxr-xr-xutils/ipc/extract-docs.py6
-rwxr-xr-xutils/ipc/generate.py12
-rw-r--r--utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl2
-rw-r--r--utils/ipc/generators/libcamera_templates/definition_functions.tmpl2
-rw-r--r--utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl2
-rw-r--r--utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl8
-rw-r--r--utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl3
-rw-r--r--utils/ipc/generators/libcamera_templates/proxy_functions.tmpl22
-rw-r--r--utils/ipc/generators/libcamera_templates/serializer.tmpl6
-rw-r--r--utils/ipc/generators/mojom_libcamera_generator.py47
-rw-r--r--utils/ipc/mojo/README2
-rw-r--r--utils/ipc/mojo/public/LICENSE2
-rw-r--r--utils/ipc/mojo/public/tools/BUILD.gn8
-rw-r--r--utils/ipc/mojo/public/tools/bindings/BUILD.gn36
-rw-r--r--utils/ipc/mojo/public/tools/bindings/README.md239
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/__init__.py0
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py170
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py194
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py34
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py62
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py173
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py102
-rw-r--r--utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py254
-rw-r--r--utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni51
-rw-r--r--utils/ipc/mojo/public/tools/bindings/compile_typescript.py27
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/concatenate-files.py5
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py10
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py36
-rw-r--r--utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py2
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/generate_type_mappings.py4
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/minify_with_terser.py47
-rw-r--r--utils/ipc/mojo/public/tools/bindings/mojom.gni845
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py62
-rw-r--r--utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py6
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py119
-rwxr-xr-xutils/ipc/mojo/public/tools/bindings/validate_typemap_config.py5
-rw-r--r--utils/ipc/mojo/public/tools/mojom/BUILD.gn18
-rwxr-xr-xutils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py69
-rwxr-xr-xutils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py87
-rw-r--r--utils/ipc/mojo/public/tools/mojom/const_unittest.py2
-rw-r--r--utils/ipc/mojo/public/tools/mojom/enum_unittest.py30
-rw-r--r--utils/ipc/mojo/public/tools/mojom/feature_unittest.py84
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn3
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/error.py2
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py3
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py7
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py26
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py93
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py11
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py9
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py787
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py2
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py151
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py30
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py2
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py464
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py82
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py145
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py12
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py21
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py155
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py8
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py10
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py108
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py39
-rwxr-xr-xutils/ipc/mojo/public/tools/mojom/mojom_parser.py119
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py6
-rw-r--r--utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py31
-rw-r--r--utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py2
-rw-r--r--utils/ipc/mojo/public/tools/mojom/union_unittest.py44
-rw-r--r--utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py73
-rwxr-xr-xutils/ipc/mojo/public/tools/run_all_python_unittests.py8
-rwxr-xr-xutils/ipc/parser.py2
-rw-r--r--utils/ipc/tools/README2
-rw-r--r--utils/ipc/tools/diagnosis/crbug_1001171.py2
-rwxr-xr-xutils/ipu3/ipu3-capture.sh3
-rw-r--r--utils/ipu3/ipu3-pack.c101
-rw-r--r--utils/ipu3/ipu3-unpack.c7
-rw-r--r--utils/ipu3/meson.build1
-rwxr-xr-xutils/raspberrypi/ctt/alsc_only.py34
-rw-r--r--utils/raspberrypi/ctt/colors.py30
-rwxr-xr-xutils/raspberrypi/ctt/convert_tuning.py46
-rwxr-xr-xutils/raspberrypi/ctt/ctt.py39
-rw-r--r--utils/raspberrypi/ctt/ctt_alsc.py4
-rw-r--r--utils/raspberrypi/ctt/ctt_awb.py2
-rw-r--r--utils/raspberrypi/ctt/ctt_ccm.py260
-rw-r--r--utils/raspberrypi/ctt/ctt_geq.py2
-rw-r--r--utils/raspberrypi/ctt/ctt_image_load.py39
-rw-r--r--utils/raspberrypi/ctt/ctt_lux.py2
-rw-r--r--utils/raspberrypi/ctt/ctt_macbeth_locator.py71
-rw-r--r--utils/raspberrypi/ctt/ctt_noise.py2
-rwxr-xr-x[-rw-r--r--]utils/raspberrypi/ctt/ctt_pretty_print_json.py194
-rw-r--r--utils/raspberrypi/ctt/ctt_ransac.py2
-rw-r--r--utils/raspberrypi/ctt/ctt_tools.py2
-rw-r--r--utils/raspberrypi/ctt/ctt_visualise.py43
-rw-r--r--utils/raspberrypi/delayedctrls_parse.py2
-rwxr-xr-xutils/release.sh46
-rwxr-xr-xutils/rkisp1/gen-csc-table.py215
-rwxr-xr-xutils/rkisp1/rkisp1-capture.sh61
-rwxr-xr-xutils/semver446
-rwxr-xr-xutils/tracepoints/gen-tp-header.py13
-rw-r--r--utils/tuning/README.rst11
-rw-r--r--utils/tuning/libtuning/__init__.py13
-rw-r--r--utils/tuning/libtuning/average.py21
-rw-r--r--utils/tuning/libtuning/generators/__init__.py6
-rw-r--r--utils/tuning/libtuning/generators/generator.py15
-rw-r--r--utils/tuning/libtuning/generators/raspberrypi_output.py114
-rw-r--r--utils/tuning/libtuning/generators/yaml_output.py123
-rw-r--r--utils/tuning/libtuning/gradient.py75
-rw-r--r--utils/tuning/libtuning/image.py136
-rw-r--r--utils/tuning/libtuning/libtuning.py208
-rw-r--r--utils/tuning/libtuning/macbeth.py516
-rw-r--r--utils/tuning/libtuning/macbeth_ref.pgm6
-rw-r--r--utils/tuning/libtuning/modules/__init__.py3
-rw-r--r--utils/tuning/libtuning/modules/lsc/__init__.py7
-rw-r--r--utils/tuning/libtuning/modules/lsc/lsc.py72
-rw-r--r--utils/tuning/libtuning/modules/lsc/raspberrypi.py246
-rw-r--r--utils/tuning/libtuning/modules/lsc/rkisp1.py112
-rw-r--r--utils/tuning/libtuning/modules/module.py32
-rw-r--r--utils/tuning/libtuning/parsers/__init__.py6
-rw-r--r--utils/tuning/libtuning/parsers/parser.py21
-rw-r--r--utils/tuning/libtuning/parsers/raspberrypi_parser.py93
-rw-r--r--utils/tuning/libtuning/parsers/yaml_parser.py17
-rw-r--r--utils/tuning/libtuning/smoothing.py24
-rw-r--r--utils/tuning/libtuning/utils.py125
-rw-r--r--utils/tuning/raspberrypi/__init__.py3
-rw-r--r--utils/tuning/raspberrypi/alsc.py19
-rwxr-xr-xutils/tuning/raspberrypi_alsc_only.py23
-rwxr-xr-xutils/tuning/rkisp1.py40
-rwxr-xr-xutils/update-kernel-headers.sh2
-rwxr-xr-xutils/update-mojo.sh52
1096 files changed, 72823 insertions, 24146 deletions
diff --git a/.clang-format b/.clang-format
index 5b8857da..cac7029f 100644
--- a/.clang-format
+++ b/.clang-format
@@ -66,7 +66,6 @@ ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- 'udev_list_entry_foreach'
-SortIncludes: true
IncludeBlocks: Regroup
IncludeCategories:
# Headers matching the name of the component are matched automatically.
diff --git a/.clang-tidy b/.clang-tidy
index b40e588a..8056d7a8 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,2 +1,4 @@
+# SPDX-License-Identifier: CC0-1.0
+
Checks: -clang-diagnostic-c99-designator
FormatStyle: file
diff --git a/.reuse/dep5 b/.reuse/dep5
index 96fefbe2..c5ef5e01 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -3,10 +3,20 @@ Upstream-Name: libcamera
Upstream-Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Source: https://git.libcamera.org/libcamera/libcamera.git/
-Files: src/ipa/raspberrypi/data/*.json
+Files: Documentation/binning.svg
+ Documentation/camera-sensor-model.rst
+ Documentation/sensor_model.svg
+Copyright: Copyright 2023 Ideas On Board Oy
+License: CC-BY-SA-4.0
+
+Files: Documentation/theme/static/search.png
+Copyright: 2022 Fonticons, Inc.
+License: CC-BY-4.0
+
+Files: src/ipa/rpi/vc4/data/*.json
utils/raspberrypi/ctt/ctt_config_example.json
utils/raspberrypi/ctt/ctt_ref.pgm
-Copyright: 2019-2020 Raspberry Pi (Trading) Ltd.
+Copyright: 2019-2020 Raspberry Pi Ltd
License: BSD-2-Clause
Files: src/qcam/assets/feathericons/*.svg
diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
index 4bb83d50..2be8d47b 100644
--- a/Documentation/Doxyfile.in
+++ b/Documentation/Doxyfile.in
@@ -1,881 +1,58 @@
# SPDX-License-Identifier: CC-BY-SA-4.0
-# Doxyfile 1.8.14
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See
-# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
+# Doxyfile 1.9.5
PROJECT_NAME = "libcamera"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
PROJECT_NUMBER = "@VERSION@"
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
PROJECT_BRIEF = "Supporting cameras in Linux since 2019"
-# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
-# in the documentation. The maximum height of the logo should not exceed 55
-# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
-# the logo to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = Documentation
-
-# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS = NO
-
-# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
-# characters to appear in the names of generated files. If set to NO, non-ASCII
-# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
-# U+3044.
-# The default value is: NO.
-
-ALLOW_UNICODE_NAMES = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF = "The $name class" \
- "The $name widget" \
- "The $name file" \
- is \
- provides \
- specifies \
- contains \
- represents \
- a \
- an \
- the
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES = YES
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+OUTPUT_DIRECTORY = "@OUTPUT_DIR@"
STRIP_FROM_PATH = "@TOP_SRCDIR@"
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
-# page for each member. If set to NO, the documentation of a member will be part
-# of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE = 4
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines (in the resulting output). You can put ^^ in the value part of an
-# alias to insert a newline as if a physical newline was in the original file.
-
-ALIASES = "context=\xrefitem context \"Thread Safety\" \"Thread Safety\""
-ALIASES += "threadbound=\ref thread-bound \"thread-bound\""
-ALIASES += "threadsafe=\ref thread-safe \"thread-safe\""
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
-# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
-# Fortran. In the later case the parser tries to guess whether the code is fixed
-# or free formatted code, this is the default for Fortran type files), VHDL. For
-# instance to make doxygen treat .inc files as Fortran files (default is PHP),
-# and .f files as C (default is Fortran), use: inc=Fortran f=C.
-#
-# Note: For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
+ALIASES = "context=\xrefitem context \"Thread Safety\" \"Thread Safety\"" \
+ "threadbound=\ref thread-bound \"thread-bound\"" \
+ "threadsafe=\ref thread-safe \"thread-safe\""
EXTENSION_MAPPING = h=C++
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-MARKDOWN_SUPPORT = YES
-
-# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
-# to that level are automatically included in the table of contents, even if
-# they do not have an id attribute.
-# Note: This feature currently applies only to Markdown headings.
-# Minimum value: 0, maximum value: 99, default value: 0.
-# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
-
TOC_INCLUDE_HEADINGS = 0
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by putting a % sign in front of the word or
-# globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-AUTOLINK_SUPPORT = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# If one adds a struct or class to a group and this option is enabled, then also
-# any nested class or struct is added to the same group. By default this option
-# is disabled and one has to add nested compounds explicitly via \ingroup.
-# The default value is: NO.
-
-GROUP_NESTED_COMPOUNDS = NO
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PACKAGE = NO
-
-# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO,
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. If set to YES, local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO, only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO, these classes will be included in the various overviews. This option
-# has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO, these declarations will be
-# included in the documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO, these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES, upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
CASE_SENSE_NAMES = YES
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES, the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
-# append additional text to a page's title, such as Class Reference. If set to
-# YES the compound reference will be hidden.
-# The default value is: NO.
-
-HIDE_COMPOUND_REFERENCE= NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-SHOW_GROUPED_MEMB_INC = NO
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
-# list. This list is created by putting \todo commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
-# list. This list is created by putting \test commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if <section_label> ... \endif and \cond <section_label>
-# ... \endcond blocks.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES, the
-# list will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. See also \cite for info how to create references.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
QUIET = YES
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC = NO
-
-# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
-# a warning is encountered.
-# The default value is: NO.
-
-WARN_AS_ERROR = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
-# Note: If this tag is empty the current directory is searched.
-
INPUT = "@TOP_SRCDIR@/include/libcamera" \
"@TOP_SRCDIR@/src/ipa/ipu3" \
- "@TOP_SRCDIR@/src/ipa/libipa" \
- "@TOP_SRCDIR@/src/libcamera" \
- "@TOP_BUILDDIR@/include/libcamera" \
- "@TOP_BUILDDIR@/src/libcamera"
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# read by doxygen.
-#
-# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
-# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
-# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
-# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+ "@TOP_SRCDIR@/src/ipa/libipa" \
+ "@TOP_SRCDIR@/src/libcamera" \
+ "@TOP_BUILDDIR@/include/libcamera" \
+ "@TOP_BUILDDIR@/src/libcamera"
FILE_PATTERNS = *.c \
*.cpp \
*.h
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
RECURSIVE = YES
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \
- @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \
- @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \
- @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \
- @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
- @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
- @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \
- @TOP_SRCDIR@/src/libcamera/pipeline/ \
- @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
- @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
- @TOP_BUILDDIR@/src/libcamera/proxy/
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
+ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \
+ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \
+ @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \
+ @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
+ @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
+ @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \
+ @TOP_SRCDIR@/src/libcamera/pipeline/ \
+ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
+ @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
+ @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \
+ @TOP_BUILDDIR@/src/libcamera/proxy/
EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
@TOP_BUILDDIR@/include/libcamera/ipa/*_proxy.h \
@TOP_BUILDDIR@/include/libcamera/ipa/ipu3_*.h \
@TOP_BUILDDIR@/include/libcamera/ipa/raspberrypi_*.h \
@TOP_BUILDDIR@/include/libcamera/ipa/rkisp1_*.h \
- @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h \
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
+ @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h
EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \
libcamera::BoundMethodBase \
@@ -890,1522 +67,23 @@ EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \
*::details \
std::*
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS = *
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-# <filter> <input-file>
-#
-# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-SOURCE_TOOLTIPS = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see https://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX = YES
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
+EXCLUDE_SYMLINKS = YES
HTML_OUTPUT = api-html
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# cascading style sheets that are included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefore more robust against future updates.
-# Doxygen will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list). For an example see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the style sheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to YES can help to show when doxygen was last run and thus if the
-# documentation is up to date.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP = NO
-
-# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
-# documentation will contain a main index with vertical navigation menus that
-# are dynamically created via Javascript. If disabled, the navigation index will
-# consists of multiple levels of tabs that are statically embedded in every HTML
-# page. Disable this option to support browsers that do not have Javascript,
-# like the Qt help browser.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_MENUS = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: https://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler (hhc.exe). If non-empty,
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated
-# (YES) or that it should be included in the master .chm file (NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated
-# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
-# enables the Previous and Next buttons.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH = 250
-
-# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# https://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_FORMAT = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from https://www.mathjax.org before deployment.
-# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_CODEFILE =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use <access key> + S
-# (what the <access key> is depends on the OS and browser, but it is typically
-# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
-# key> to jump into the search results window, the results can be navigated
-# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
-# the search. The filter options can be selected when the cursor is inside the
-# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
-# to select a filter and <Enter> or <escape> to activate or cancel the filter
-# option.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-SEARCHENGINE = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
-# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
-# setting. When disabled, doxygen will generate a PHP script for searching and
-# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
-# and searching needs to be provided by external tools. See the section
-# "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SERVER_BASED_SEARCH = NO
-
-# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
-# search results.
-#
-# Doxygen ships with an example indexer (doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: https://xapian.org/).
-#
-# See the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will return the search results when EXTERNAL_SEARCH is enabled.
-#
-# Doxygen ships with an example indexer (doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: https://xapian.org/). See the section "External Indexing and
-# Searching" for details.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHENGINE_URL =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-# The default file is: searchdata.xml.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHDATA_FILE = searchdata.xml
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH_ID =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
-# to a relative location where the documentation can be found. The format is:
-# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTRA_SEARCH_MAPPINGS =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
-# The default value is: YES.
-
GENERATE_LATEX = NO
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked.
-#
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
-# index for LaTeX.
-# The default file is: makeindex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used by the
-# printer.
-# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
-# 14 inches) and executive (7.25 x 10.5 inches).
-# The default value is: a4.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PAPER_TYPE = a4
-
-# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. The package can be specified just
-# by its name or with the correct syntax as to be used with the LaTeX
-# \usepackage command. To get the times font for instance you can specify :
-# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
-# To use the option intlimits with the amsmath package you can specify:
-# EXTRA_PACKAGES=[intlimits]{amsmath}
-# If left blank no extra packages will be included.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
-#
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
-# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
-# string, for the replacement values of the other commands the user is referred
-# to HTML_HEADER.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HEADER =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer. See
-# LATEX_HEADER for more information on how to generate a default footer and what
-# special commands can be used inside the footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_FOOTER =
-
-# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# LaTeX style sheets that are included after the standard style sheets created
-# by doxygen. Using this option one can overrule certain style aspects. Doxygen
-# will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list).
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_STYLESHEET =
-
-# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the LATEX_OUTPUT output
-# directory. Note that the files will be copied as-is; there are no commands or
-# markers available.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_FILES =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
-# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
-# contain links (just like the HTML output) instead of page references. This
-# makes the output suitable for online browsing using a PDF viewer.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PDF_HYPERLINKS = YES
-
-# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES, to get a
-# higher quality PDF documentation.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
-# command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BATCHMODE = NO
-
-# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
-# index chapters (such as File Index, Compound Index, etc.) in the output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HIDE_INDICES = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. See
-# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
-# The default value is: plain.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BIB_STYLE = plain
-
-# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_TIMESTAMP = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
-# RTF output is optimized for Word 97 and may not look too pretty with other RTF
-# readers/editors.
-# The default value is: NO.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: rtf.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
-# contain hyperlink fields. The RTF file will contain links (just like the HTML
-# output) instead of page references. This makes the output suitable for online
-# browsing using Word or some other Word compatible readers that support those
-# fields.
-#
-# Note: WordPad (write) and others do not support links.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
-#
-# See also section "Doxygen usage" for information on how to generate the
-# default style sheet that doxygen normally uses.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
-# classes and files.
-# The default value is: NO.
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it. A directory man3 will be created inside the directory specified by
-# MAN_OUTPUT.
-# The default directory is: man.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to the generated
-# man pages. In case the manual section does not start with a number, the number
-# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
-# optional.
-# The default value is: .3.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_EXTENSION = .3
-
-# The MAN_SUBDIR tag determines the name of the directory created within
-# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
-# MAN_EXTENSION with the initial . removed.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_SUBDIR =
-
-# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
-# will generate one additional man file for each entity documented in the real
-# man page(s). These additional files only source the real man page, but without
-# them the man command would be unable to find the correct page.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
-# captures the structure of the code including all documentation.
-# The default value is: NO.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: xml.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_OUTPUT = xml
-
-# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
-# listings (including syntax highlighting and cross-referencing information) to
-# the XML output. Note that enabling this will significantly increase the size
-# of the XML output.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
-# that can be used to generate PDF.
-# The default value is: NO.
-
-GENERATE_DOCBOOK = NO
-
-# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
-# front of it.
-# The default directory is: docbook.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_OUTPUT = docbook
-
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
-# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
-# the structure of the code including all documentation. Note that this feature
-# is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
-# file that captures the structure of the code including all documentation.
-#
-# Note that this feature is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
-# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
-# output from the Perl module output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
-# formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO, the
-# size of the Perl module output will be much smaller and Perl will parse it
-# just the same.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file are
-# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
-# so different doxyrules.make files included by the same Makefile don't
-# overwrite each other's variables.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
-# C-preprocessor directives found in the sources and include files.
-# The default value is: YES.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
-# in the source code. If set to NO, only conditional compilation will be
-# performed. Macro expansion can be done in a controlled way by setting
-# EXPAND_ONLY_PREDEF to YES.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
MACRO_EXPANSION = YES
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
-# the macro expansion is limited to the macros specified with the PREDEFINED and
-# EXPAND_AS_DEFINED tags.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
EXPAND_ONLY_PREDEF = YES
-# If the SEARCH_INCLUDES tag is set to YES, the include files in the
-# INCLUDE_PATH will be searched if a #include is found.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by the
-# preprocessor.
-# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-
INCLUDE_PATH = "@TOP_SRCDIR@/include/libcamera"
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will be
-# used.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
INCLUDE_FILE_PATTERNS = *.h
-# The PREDEFINED tag can be used to specify one or more macro names that are
-# defined before the preprocessor is started (similar to the -D option of e.g.
-# gcc). The argument of the tag is a list of macros of the form: name or
-# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
-# is assumed. To prevent a macro definition from being undefined via #undef or
-# recursively expanded use the := operator instead of the = operator.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+IMAGE_PATH = "@TOP_SRCDIR@/Documentation/images"
PREDEFINED = __DOXYGEN__ \
__cplusplus \
- __attribute__(x)=
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
-# tag can be used to specify a list of macro names that should be expanded. The
-# macro definition that is found in the sources will be used. Use the PREDEFINED
-# tag if you want to use a different macro definition that overrules the
-# definition found in the source code.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all references to function-like macros that are alone on a line, have
-# an all uppercase name, and do not end with a semicolon. Such function macros
-# are typically used for boiler-plate code, and will confuse the parser if not
-# removed.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tag files. For each tag
-# file the location of the external documentation should be added. The format of
-# a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where loc1 and loc2 can be relative or absolute paths or URLs. See the
-# section "Linking to external documentation" for more information about the use
-# of tag files.
-# Note: Each tag file must have a unique name (where the name does NOT include
-# the path). If a tag file is not located in the directory in which doxygen is
-# run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
-# tag file that is based on the input files it reads. See section "Linking to
-# external documentation" for more information about the usage of tag files.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
-# the class index. If set to NO, only the inherited external classes will be
-# listed.
-# The default value is: NO.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will be
-# listed.
-# The default value is: YES.
-
-EXTERNAL_GROUPS = YES
-
-# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
-# the related pages index. If set to NO, only the current project's pages will
-# be listed.
-# The default value is: YES.
-
-EXTERNAL_PAGES = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# You can include diagrams made with dia in doxygen documentation. Doxygen will
-# then run dia to produce the diagram and insert it in the documentation. The
-# DIA_PATH tag allows you to specify the directory where the dia binary resides.
-# If left empty dia is assumed to be found in the default search path.
-
-DIA_PATH =
-
-# If set to YES the inheritance and collaboration graphs will hide inheritance
-# and usage relations if the target is undocumented or is not a class.
-# The default value is: YES.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz (see:
-# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
-# Bell Labs. The other options in this section have no effect if this option is
-# set to NO
-# The default value is: NO.
+ __attribute__(x)= \
+ @PREDEFINED@
HAVE_DOT = YES
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
-# to run in parallel. When set to 0 doxygen will base this on the number of
-# processors available in the system. You can set it explicitly to a value
-# larger than 0 to get control over the balance between CPU load and processing
-# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_NUM_THREADS = 0
-
-# When you want a differently looking font in the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE = 10
-
-# By default doxygen will tell dot to use the default font as specified with
-# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
-# the path where dot can find it using this tag.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
-# graph for each documented class showing the direct and indirect implementation
-# dependencies (inheritance, containment, and class references variables) of the
-# class with other documented classes.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
-# groups, showing the direct groups dependencies.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LOOK = NO
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
-# class node. If there are many fields or methods and many nodes the graph may
-# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
-# number of items for each type to make the size more manageable. Set this to 0
-# for no limit. Note that the threshold may be exceeded by 50% before the limit
-# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
-# but if the number exceeds 15, the total amount of fields shown is limited to
-# 10.
-# Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LIMIT_NUM_FIELDS = 10
-
-# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
-# collaboration graphs will show the relations between templates and their
-# instances.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-TEMPLATE_RELATIONS = NO
-
-# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
-# YES then doxygen will generate a graph for each documented file showing the
-# direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDE_GRAPH = YES
-
-# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
-# set to YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command. Disabling a call graph can be
-# accomplished by means of the command \hidecallgraph.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command. Disabling a caller graph can be
-# accomplished by means of the command \hidecallergraph.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
-# hierarchy of all classes instead of a textual one.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
-# dependencies a directory has on other directories in a graphical way. The
-# dependency relations are determined by the #include relations between the
-# files in the directories.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. For an explanation of the image formats see the section
-# output formats in the documentation of the dot tool (Graphviz (see:
-# http://www.graphviz.org/)).
-# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
-# to make the SVG files visible in IE 9+ (other browsers do not have this
-# requirement).
-# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
-# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
-# png:gdiplus:gdiplus.
-# The default value is: png.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_IMAGE_FORMAT = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-#
-# Note that this requires a modern browser other than Internet Explorer. Tested
-# and working are Firefox, Chrome, Safari, and Opera.
-# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
-# the SVG files visible. Older versions of IE do not have SVG support.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INTERACTIVE_SVG = NO
-
-# The DOT_PATH tag can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the \dotfile
-# command).
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOTFILE_DIRS =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the \mscfile
-# command).
-
-MSCFILE_DIRS =
-
-# The DIAFILE_DIRS tag can be used to specify one or more directories that
-# contain dia files that are included in the documentation (see the \diafile
-# command).
-
-DIAFILE_DIRS =
-
-# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
-# path where java can find the plantuml.jar file. If left blank, it is assumed
-# PlantUML is not used or called during a preprocessing step. Doxygen will
-# generate a warning when it encounters a \startuml command in this case and
-# will not generate output for the diagram.
-
-PLANTUML_JAR_PATH =
-
-# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
-# configuration file for plantuml.
-
-PLANTUML_CFG_FILE =
-
-# When using plantuml, the specified paths are searched for files specified by
-# the !include statement in a plantuml block.
-
-PLANTUML_INCLUDE_PATH =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
-# that will be shown in the graph. If the number of nodes in a graph becomes
-# larger than this value, doxygen will truncate the graph, which is visualized
-# by representing a node as a red box. Note that doxygen if the number of direct
-# children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
-# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-# Minimum value: 0, maximum value: 10000, default value: 50.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
-# generated by dot. A depth value of 3 means that only nodes reachable from the
-# root by following a path via at most 3 edges will be shown. Nodes that lay
-# further from the root node will be omitted. Note that setting this option to 1
-# or 2 may greatly reduce the computation time needed for large code bases. Also
-# note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-# Minimum value: 0, maximum value: 1000, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10) support
-# this, this feature is disabled by default.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_MULTI_TARGETS = NO
-
-# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
-# explaining the meaning of the various boxes and arrows in the dot generated
-# graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
-# files that are used to generate the various graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_CLEANUP = YES
diff --git a/Documentation/binning.svg b/Documentation/binning.svg
new file mode 100644
index 00000000..c6a3b639
--- /dev/null
+++ b/Documentation/binning.svg
@@ -0,0 +1,5053 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="binning.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ inkscape:zoom="0.96951281"
+ inkscape:cx="513.66005"
+ inkscape:cy="278.49039"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458333"
+ spacingy="0.26458334"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1">
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-87"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-91" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-74"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-12" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-41"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-58"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-60" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-13" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-60" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-64"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-82"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-38" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-50"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-92"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-87" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-62" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-37" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-35" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-87-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-0-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-5-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-2-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-7-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-91-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-9-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-6-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-3-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-6-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-8-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-4-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-6-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-6-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-74-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-1-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-0-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-0-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-6-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-4-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-4-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-3-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-4-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-3-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-3-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-6-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-9-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-0-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-4-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-8-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-5-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-12-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-9-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-7-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-3-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-8-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-9-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-2-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-3-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-1-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-8-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-7-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-41-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-6-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-3-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-5-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-58-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-7-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-1-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-7-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-0-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-60-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-4-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-13-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-0-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-2-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-9-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-60-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-3-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-5-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-4-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-3-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-64-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-3-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-1-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-9-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-74" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-57"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-88" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-90"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-78" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-73" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-15"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-33" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-87-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-0-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-5-85"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-2-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-7-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-91-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-9-69"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-6-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-3-37"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-6-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-8-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-4-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-6-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-6-39" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-74-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-1-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-0-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-0-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-6-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-4-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-4-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-3-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-4-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-3-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-3-91"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-6-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-9-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-0-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-4-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-8-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-5-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-12-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-9-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-7-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-3-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-8-5" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-9-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-2-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-3-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-1-1" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-8-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-7-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-41-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-6-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-3-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-5-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-58-2"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-7-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-1-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-7-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-0-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-60-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-4-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-13-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-0-7"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-2-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-60-0-5-2-8-6-9-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-48-9-9-4-1-8-60-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-3-0"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-5-64" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-7-4-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-4-6-3-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-8-64-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-8-3-8" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-1-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path2-7-9-0" />
+ </marker>
+ </defs>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3"
+ width="13.873985"
+ height="13.536115"
+ x="65.388786"
+ y="21.368647" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6"
+ width="13.873985"
+ height="13.536115"
+ x="79.4552"
+ y="21.265594" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3"
+ width="13.873985"
+ height="13.536115"
+ x="65.400574"
+ y="35.581291" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3"
+ width="13.873985"
+ height="13.536115"
+ x="79.487213"
+ y="35.445648" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3"
+ width="13.873985"
+ height="13.536115"
+ x="36.816032"
+ y="21.611094" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9"
+ width="13.873985"
+ height="13.536115"
+ x="50.88245"
+ y="21.508043" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7"
+ width="13.873985"
+ height="13.536115"
+ x="36.827824"
+ y="35.823738" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2"
+ width="13.873985"
+ height="13.536115"
+ x="50.914459"
+ y="35.688095" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3"
+ width="13.873985"
+ height="13.536115"
+ x="65.473557"
+ y="49.829689" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5"
+ width="13.873985"
+ height="13.536115"
+ x="79.53997"
+ y="49.726639" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0"
+ width="13.873985"
+ height="13.536115"
+ x="65.485344"
+ y="64.042336" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8"
+ width="13.873985"
+ height="13.536115"
+ x="79.571983"
+ y="63.906689" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4"
+ width="13.873985"
+ height="13.536115"
+ x="36.900803"
+ y="50.072136" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1"
+ width="13.873985"
+ height="13.536115"
+ x="50.96722"
+ y="49.969086" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1"
+ width="13.873985"
+ height="13.536115"
+ x="36.912594"
+ y="64.284782" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3"
+ width="13.873985"
+ height="13.536115"
+ x="50.999229"
+ y="64.149139" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded)"
+ d="M 42.081702,56.601729 C 40.286141,52.204844 39.84416,48.248056 39.775934,43.793323 39.705994,39.227507 40.058028,34.155406 42.189147,28.949241"
+ id="path1"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60)"
+ d="M 55.099315,56.107712 C 46.749885,47.204301 45.5057,37.755811 55.174295,27.470139"
+ id="path1-8"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0)"
+ d="M 57.045954,72.804438 C 51.147833,63.901027 50.090593,54.779467 56.920539,44.493796"
+ id="path1-8-5"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5)"
+ d="M 73.072075,56.721348 C 57.027587,57.689095 47.292535,48.710474 44.701645,29.866502"
+ id="path1-8-5-6"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2)"
+ d="M 72.90507,70.575484 C 57.457506,78.709997 45.319286,62.459593 45.244064,43.230157"
+ id="path1-8-5-6-7"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8)"
+ d="M 86.158751,59.09319 C 69.785862,58.470577 60.1065,48.008767 56.857885,29.968538"
+ id="path1-8-5-6-7-8"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6)"
+ d="M 86.457671,71.13699 C 71.010108,79.271503 59.102258,64.163122 59.027036,44.933685"
+ id="path1-8-5-6-7-8-2"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded)"
+ d="m 44.687693,41.420418 c 6.448084,-5.278368 15.466061,-6.30545 28.431123,-0.80303"
+ id="path1-7"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6)"
+ d="M 57.078428,27.878124 C 67.151136,21.834217 72.464093,21.119079 86.668639,27.745841"
+ id="path1-7-3"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7)"
+ d="m 43.794332,28.085665 c 3.63222,-4.278367 8.633495,-5.657536 14.125001,-5.510111 4.339623,0.11649 8.774913,0.820414 14.546216,4.344855"
+ id="path1-7-3-3"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8)"
+ d="m 58.86173,43.233874 c 6.448089,-7.475501 14.979372,-8.678728 27.944435,-0.885912"
+ id="path1-7-1"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1)"
+ d="M 43.150248,70.196421 C 39.946602,64.842954 38.991849,59.890688 39.59491,54.079585 c 0.399898,-3.853407 1.143216,-8.419661 3.603258,-12.520737"
+ id="path1-5"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-4"
+ width="13.873985"
+ height="13.536115"
+ x="122.23671"
+ y="20.698904" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-7"
+ width="13.873985"
+ height="13.536115"
+ x="136.30313"
+ y="20.595854" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-2"
+ width="13.873985"
+ height="13.536115"
+ x="122.2485"
+ y="34.911549" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-1"
+ width="13.873985"
+ height="13.536115"
+ x="136.33514"
+ y="34.775906" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-9"
+ width="13.873985"
+ height="13.536115"
+ x="93.663963"
+ y="20.941351" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-5"
+ width="13.873985"
+ height="13.536115"
+ x="107.73038"
+ y="20.838301" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-6"
+ width="13.873985"
+ height="13.536115"
+ x="93.675751"
+ y="35.153996" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-8"
+ width="13.873985"
+ height="13.536115"
+ x="107.76238"
+ y="35.018353" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-5"
+ width="13.873985"
+ height="13.536115"
+ x="122.32149"
+ y="49.159946" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5"
+ width="13.873985"
+ height="13.536115"
+ x="136.38791"
+ y="49.056896" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-2"
+ width="13.873985"
+ height="13.536115"
+ x="122.33327"
+ y="63.372593" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-1"
+ width="13.873985"
+ height="13.536115"
+ x="136.41991"
+ y="63.23695" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9"
+ width="13.873985"
+ height="13.536115"
+ x="93.748734"
+ y="49.402393" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-9"
+ width="13.873985"
+ height="13.536115"
+ x="107.81515"
+ y="49.299343" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-1"
+ width="13.873985"
+ height="13.536115"
+ x="93.760521"
+ y="63.61504" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6"
+ width="13.873985"
+ height="13.536115"
+ x="107.84715"
+ y="63.479397" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-87)"
+ d="m 98.929634,55.931997 c -1.795575,-4.396885 -2.23755,-8.353673 -2.30576,-12.808407 -0.06986,-4.565816 0.282091,-9.637916 2.413212,-14.844081"
+ id="path3"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-5)"
+ d="m 111.94724,55.43798 c -8.34942,-8.903412 -9.59363,-18.351902 0.075,-28.637573"
+ id="path1-8-2"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-7)"
+ d="m 113.89388,72.134706 c -5.89813,-8.903412 -6.95537,-18.024971 -0.12542,-28.310642"
+ id="path1-8-5-1"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-9)"
+ d="M 129.92,56.051616 C 113.87551,57.019363 104.14045,48.040742 101.54958,29.196769"
+ id="path1-8-5-6-0"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-3)"
+ d="M 129.75299,69.905752 C 114.30543,78.040265 102.16722,61.789861 102.092,42.560424"
+ id="path1-8-5-6-7-5"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-8)"
+ d="M 143.00667,58.423457 C 126.63378,57.800845 116.95442,47.339035 113.70581,29.298805"
+ id="path1-8-5-6-7-8-3"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-6)"
+ d="M 143.30559,70.467258 C 127.85803,78.601771 115.95018,63.49339 115.87496,44.263953"
+ id="path1-8-5-6-7-8-2-2"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-87)"
+ d="m 101.53561,40.750686 c 6.44809,-5.278368 15.46606,-6.305451 28.43113,-0.803031"
+ id="path1-7-19"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-74)"
+ d="m 113.92635,27.208392 c 10.07271,-6.043908 15.38566,-6.759046 29.59021,-0.132283"
+ id="path1-7-3-7"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-0)"
+ d="m 100.64224,27.415933 c 3.63223,-4.278368 8.63351,-5.657536 14.12501,-5.510111 4.33963,0.11649 8.77492,0.820414 14.54622,4.344855"
+ id="path1-7-3-3-6"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-6)"
+ d="m 115.70965,42.564142 c 6.44809,-7.475501 14.97937,-8.678728 27.94444,-0.885913"
+ id="path1-7-1-8"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-4)"
+ d="M 99.998175,69.526688 C 96.79452,64.173222 95.83977,59.220956 96.442844,53.409853 c 0.3999,-3.853408 1.143197,-8.419661 3.603256,-12.520737"
+ id="path1-5-2"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-5"
+ width="13.873985"
+ height="13.536115"
+ x="179.51106"
+ y="20.926241" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-5"
+ width="13.873985"
+ height="13.536115"
+ x="193.57747"
+ y="20.823191" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0"
+ width="13.873985"
+ height="13.536115"
+ x="179.52284"
+ y="35.138885" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-12"
+ width="13.873985"
+ height="13.536115"
+ x="193.60948"
+ y="35.003242" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-4"
+ width="13.873985"
+ height="13.536115"
+ x="150.93829"
+ y="21.16869" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8"
+ width="13.873985"
+ height="13.536115"
+ x="165.00471"
+ y="21.065638" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-5"
+ width="13.873985"
+ height="13.536115"
+ x="150.95009"
+ y="35.381336" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-6"
+ width="13.873985"
+ height="13.536115"
+ x="165.03673"
+ y="35.245689" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-52"
+ width="13.873985"
+ height="13.536115"
+ x="179.59583"
+ y="49.387283" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-6"
+ width="13.873985"
+ height="13.536115"
+ x="193.66225"
+ y="49.284233" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-9"
+ width="13.873985"
+ height="13.536115"
+ x="179.60762"
+ y="63.59993" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-13"
+ width="13.873985"
+ height="13.536115"
+ x="193.69426"
+ y="63.464287" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-1"
+ width="13.873985"
+ height="13.536115"
+ x="151.02307"
+ y="49.629734" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-5"
+ width="13.873985"
+ height="13.536115"
+ x="165.08948"
+ y="49.526684" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2"
+ width="13.873985"
+ height="13.536115"
+ x="151.03485"
+ y="63.842381" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-69"
+ width="13.873985"
+ height="13.536115"
+ x="165.12149"
+ y="63.706734" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-4)"
+ d="m 156.20396,56.159335 c -1.79555,-4.396885 -2.23753,-8.353673 -2.30576,-12.808407 -0.0699,-4.565816 0.28209,-9.637916 2.41321,-14.844082"
+ id="path3-4"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-3)"
+ d="m 169.22158,55.665317 c -8.34944,-8.903411 -9.59362,-18.351901 0.075,-28.637572"
+ id="path1-8-4"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-9)"
+ d="M 171.16822,72.362043 C 165.2701,63.458632 164.21286,54.337072 171.04281,44.051401"
+ id="path1-8-5-60"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-4)"
+ d="M 187.19434,56.278954 C 171.14986,57.246701 161.4148,48.26808 158.8239,29.424107"
+ id="path1-8-5-6-74"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-5)"
+ d="m 187.02734,70.133089 c -15.44756,8.134513 -27.5858,-8.11589 -27.66102,-27.345327"
+ id="path1-8-5-6-7-3"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-9)"
+ d="M 200.28102,58.650795 C 183.90813,58.028182 174.22877,47.566373 170.98015,29.526143"
+ id="path1-8-5-6-7-8-7"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-3)"
+ d="M 200.57994,70.694595 C 185.13238,78.829108 173.22453,63.720728 173.14931,44.491291"
+ id="path1-8-5-6-7-8-2-9"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-4)"
+ d="m 158.80996,40.978023 c 6.44809,-5.278367 15.46606,-6.30545 28.43113,-0.80303"
+ id="path1-7-0"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-9)"
+ d="m 171.2007,27.43573 c 10.07271,-6.043908 15.38566,-6.759046 29.59021,-0.132283"
+ id="path1-7-3-0"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-3)"
+ d="m 157.91659,27.643271 c 3.63223,-4.278368 8.63351,-5.657536 14.12501,-5.510111 4.33963,0.11649 8.77492,0.820413 14.54622,4.344855"
+ id="path1-7-3-3-2"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-8)"
+ d="m 172.984,42.791479 c 6.44809,-7.4755 14.97937,-8.678727 27.94443,-0.885912"
+ id="path1-7-1-2"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-41)"
+ d="m 157.27252,69.754026 c -3.20365,-5.353467 -4.1584,-10.305732 -3.55535,-16.116836 0.3999,-3.853407 1.14322,-8.41966 3.60325,-12.520737"
+ id="path1-5-6"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-9"
+ width="13.873985"
+ height="13.536115"
+ x="236.369"
+ y="20.627245" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-4"
+ width="13.873985"
+ height="13.536115"
+ x="250.43542"
+ y="20.524195" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-5"
+ width="13.873985"
+ height="13.536115"
+ x="236.3808"
+ y="34.83989" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-6"
+ width="13.873985"
+ height="13.536115"
+ x="250.46744"
+ y="34.704247" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-5"
+ width="13.873985"
+ height="13.536115"
+ x="207.79625"
+ y="20.869694" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-9"
+ width="13.873985"
+ height="13.536115"
+ x="221.86267"
+ y="20.766644" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-1"
+ width="13.873985"
+ height="13.536115"
+ x="207.80804"
+ y="35.082336" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7"
+ width="13.873985"
+ height="13.536115"
+ x="221.89468"
+ y="34.946693" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-4"
+ width="13.873985"
+ height="13.536115"
+ x="236.45378"
+ y="49.088287" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-1"
+ width="13.873985"
+ height="13.536115"
+ x="250.5202"
+ y="48.985237" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-1"
+ width="13.873985"
+ height="13.536115"
+ x="236.46558"
+ y="63.300934" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-9"
+ width="13.873985"
+ height="13.536115"
+ x="250.5522"
+ y="63.165291" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-3"
+ width="13.873985"
+ height="13.536115"
+ x="207.88103"
+ y="49.330734" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-8"
+ width="13.873985"
+ height="13.536115"
+ x="221.94745"
+ y="49.227684" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-7"
+ width="13.873985"
+ height="13.536115"
+ x="207.89282"
+ y="63.543381" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-4"
+ width="13.873985"
+ height="13.536115"
+ x="221.97945"
+ y="63.407738" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-3)"
+ d="m 213.06192,55.860339 c -1.79558,-4.396885 -2.23755,-8.353673 -2.30579,-12.808407 -0.0699,-4.565816 0.28212,-9.637916 2.41321,-14.844081"
+ id="path3-9"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-58)"
+ d="M 226.07952,55.366322 C 217.7301,46.46291 216.4859,37.01442 226.15452,26.728749"
+ id="path1-8-3"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-1)"
+ d="M 228.02616,72.063048 C 222.12804,63.159636 221.0708,54.038077 227.90074,43.752406"
+ id="path1-8-5-13"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-0)"
+ d="M 244.05228,55.979958 C 228.00779,56.947705 218.27273,47.969084 215.68186,29.125111"
+ id="path1-8-5-6-8"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-4)"
+ d="M 243.88527,69.834094 C 228.43771,77.968606 216.2995,61.718203 216.22428,42.488766"
+ id="path1-8-5-6-7-7"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-0)"
+ d="M 257.13895,58.351799 C 240.76607,57.729187 231.0867,47.267377 227.83809,29.227147"
+ id="path1-8-5-6-7-8-9"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-9)"
+ d="M 257.43787,70.3956 C 241.99031,78.530112 230.08246,63.421732 230.00724,44.192295"
+ id="path1-8-5-6-7-8-2-3"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-3)"
+ d="m 215.66789,40.679027 c 6.44809,-5.278367 15.46607,-6.30545 28.43113,-0.80303"
+ id="path1-7-8"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-3)"
+ d="m 228.05863,27.136734 c 10.07271,-6.043908 15.38567,-6.759046 29.59021,-0.132283"
+ id="path1-7-3-8"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-4)"
+ d="m 214.77453,27.344275 c 3.63222,-4.278368 8.6335,-5.657536 14.12501,-5.510111 4.33962,0.11649 8.77491,0.820413 14.54621,4.344855"
+ id="path1-7-3-3-4"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-64)"
+ d="m 229.84193,42.492484 c 6.44809,-7.475501 14.97938,-8.678728 27.94444,-0.885913"
+ id="path1-7-1-0"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-1)"
+ d="m 214.13046,49.215575 c -3.20366,-5.353466 -4.15841,-10.305732 -3.55533,-16.116835 0.39987,-3.853408 1.14319,-8.419661 3.60325,-12.520737"
+ id="path1-5-0"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-46"
+ width="13.873985"
+ height="13.536115"
+ x="65.132538"
+ y="77.161232" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-8"
+ width="13.873985"
+ height="13.536115"
+ x="79.198952"
+ y="77.058182" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-3"
+ width="13.873985"
+ height="13.536115"
+ x="65.144325"
+ y="91.373878" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-2"
+ width="13.873985"
+ height="13.536115"
+ x="79.230965"
+ y="91.238235" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6"
+ width="13.873985"
+ height="13.536115"
+ x="36.559784"
+ y="77.403679" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-2"
+ width="13.873985"
+ height="13.536115"
+ x="50.626202"
+ y="77.300629" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-2"
+ width="13.873985"
+ height="13.536115"
+ x="36.571575"
+ y="91.616325" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-4"
+ width="13.873985"
+ height="13.536115"
+ x="50.658211"
+ y="91.480682" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-49"
+ width="13.873985"
+ height="13.536115"
+ x="65.217308"
+ y="105.62228" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-65"
+ width="13.873985"
+ height="13.536115"
+ x="79.283722"
+ y="105.51923" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-4"
+ width="13.873985"
+ height="13.536115"
+ x="65.229095"
+ y="119.83492" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-6"
+ width="13.873985"
+ height="13.536115"
+ x="79.315735"
+ y="119.69928" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-2"
+ width="13.873985"
+ height="13.536115"
+ x="36.644554"
+ y="105.86472" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-51"
+ width="13.873985"
+ height="13.536115"
+ x="50.710972"
+ y="105.76167" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-6"
+ width="13.873985"
+ height="13.536115"
+ x="36.656345"
+ y="120.07737" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-1"
+ width="13.873985"
+ height="13.536115"
+ x="50.742981"
+ y="119.94173" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-82)"
+ d="m 41.825452,112.39432 c -1.795561,-4.39688 -2.237542,-8.35367 -2.305768,-12.808406 -0.06994,-4.565816 0.282094,-9.637917 2.413213,-14.844082"
+ id="path6"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-50)"
+ d="m 54.843065,111.9003 c -8.34943,-8.90341 -9.593615,-18.351898 0.07498,-28.63757"
+ id="path1-8-37"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-92)"
+ d="m 56.789704,128.59703 c -5.898121,-8.90341 -6.955361,-18.02497 -0.125415,-28.31064"
+ id="path1-8-5-7"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-8)"
+ d="m 72.815825,112.51394 c -16.044488,0.96775 -25.77954,-8.01087 -28.37043,-26.854847"
+ id="path1-8-5-6-1"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-2)"
+ d="M 72.64882,126.36808 C 57.201256,134.50259 45.063036,118.25218 44.987814,99.022748"
+ id="path1-8-5-6-7-2"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-3)"
+ d="M 85.902501,114.88578 C 69.529612,114.26317 59.85025,103.80136 56.601635,85.761129"
+ id="path1-8-5-6-7-8-1"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-7)"
+ d="m 86.201421,126.92958 c -15.447563,8.13451 -27.355413,-6.97387 -27.430635,-26.2033"
+ id="path1-8-5-6-7-8-2-0"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-82)"
+ d="m 44.431443,97.213009 c 6.448084,-5.278368 15.466061,-6.30545 28.431123,-0.80303"
+ id="path1-7-89"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-6)"
+ d="M 56.822178,83.670715 C 66.894886,77.626808 72.207843,76.91167 86.412389,83.538432"
+ id="path1-7-3-01"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-9)"
+ d="m 43.538082,83.878256 c 3.63222,-4.278367 8.633495,-5.657536 14.125001,-5.510111 4.339623,0.11649 8.774913,0.820414 14.546216,4.344855"
+ id="path1-7-3-3-3"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-0)"
+ d="m 58.60548,99.026465 c 6.448089,-7.475501 14.979372,-8.678728 27.944435,-0.885912"
+ id="path1-7-1-4"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-2)"
+ d="m 42.893998,125.98901 c -3.203646,-5.35346 -4.158399,-10.30573 -3.555338,-16.11683 0.399898,-3.85341 1.143216,-8.41966 3.603258,-12.520741"
+ id="path1-5-8"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-4-8"
+ width="13.873985"
+ height="13.536115"
+ x="121.98046"
+ y="76.491493" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-7-2"
+ width="13.873985"
+ height="13.536115"
+ x="136.04688"
+ y="76.388443" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-2-2"
+ width="13.873985"
+ height="13.536115"
+ x="121.99226"
+ y="90.70414" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-1-0"
+ width="13.873985"
+ height="13.536115"
+ x="136.07889"
+ y="90.568497" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-9-3"
+ width="13.873985"
+ height="13.536115"
+ x="93.407707"
+ y="76.73394" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-5-6"
+ width="13.873985"
+ height="13.536115"
+ x="107.47413"
+ y="76.63089" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-6-1"
+ width="13.873985"
+ height="13.536115"
+ x="93.419502"
+ y="90.946587" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-8-0"
+ width="13.873985"
+ height="13.536115"
+ x="107.50613"
+ y="90.810944" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-5-7"
+ width="13.873985"
+ height="13.536115"
+ x="122.06523"
+ y="104.95254" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-4"
+ width="13.873985"
+ height="13.536115"
+ x="136.13165"
+ y="104.84949" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-2-2"
+ width="13.873985"
+ height="13.536115"
+ x="122.07703"
+ y="119.16518" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-1-0"
+ width="13.873985"
+ height="13.536115"
+ x="136.16367"
+ y="119.02954" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-3"
+ width="13.873985"
+ height="13.536115"
+ x="93.492477"
+ y="105.19498" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-9-3"
+ width="13.873985"
+ height="13.536115"
+ x="107.5589"
+ y="105.09193" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-1-9"
+ width="13.873985"
+ height="13.536115"
+ x="93.504272"
+ y="119.40763" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-0"
+ width="13.873985"
+ height="13.536115"
+ x="107.5909"
+ y="119.27199" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-87-1)"
+ d="m 98.673384,111.72459 c -1.795575,-4.39689 -2.23755,-8.35368 -2.30576,-12.808409 -0.06986,-4.565816 0.282091,-9.637916 2.413212,-14.844081"
+ id="path7"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-5-8)"
+ d="m 111.69099,111.23057 c -8.34942,-8.90341 -9.59363,-18.351901 0.075,-28.637572"
+ id="path1-8-2-4"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-7-4)"
+ d="m 113.63763,127.9273 c -5.89813,-8.90341 -6.95537,-18.02497 -0.12542,-28.310645"
+ id="path1-8-5-1-3"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-9-6)"
+ d="m 129.66375,111.84421 c -16.04449,0.96774 -25.77955,-8.01088 -28.37042,-26.85485"
+ id="path1-8-5-6-0-3"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-3-3)"
+ d="m 129.49674,125.69834 c -15.44756,8.13452 -27.58577,-8.11589 -27.66099,-27.345325"
+ id="path1-8-5-6-7-5-4"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-8-4)"
+ d="M 142.75042,114.21605 C 126.37753,113.59344 116.69817,103.13163 113.44956,85.091396"
+ id="path1-8-5-6-7-8-3-2"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-6-7)"
+ d="m 143.04934,126.25985 c -15.44756,8.13451 -27.35541,-6.97387 -27.43063,-26.20331"
+ id="path1-8-5-6-7-8-2-2-4"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-87-1)"
+ d="m 101.27936,96.543277 c 6.44809,-5.278368 15.46606,-6.305451 28.43113,-0.803031"
+ id="path1-7-19-5"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-74-5)"
+ d="m 113.6701,83.000983 c 10.07271,-6.043908 15.38566,-6.759046 29.59021,-0.132283"
+ id="path1-7-3-7-3"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-0-1)"
+ d="m 100.38599,83.208524 c 3.63223,-4.278368 8.63351,-5.657536 14.12501,-5.510111 4.33963,0.11649 8.77492,0.820414 14.54622,4.344855"
+ id="path1-7-3-3-6-8"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-6-5)"
+ d="m 115.4534,98.356733 c 6.44809,-7.475501 14.97937,-8.678728 27.94444,-0.885913"
+ id="path1-7-1-8-1"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-4-0)"
+ d="m 99.741925,125.31928 c -3.203655,-5.35347 -4.158405,-10.30573 -3.555331,-16.11684 0.3999,-3.8534 1.143197,-8.41966 3.603258,-12.520733"
+ id="path1-5-2-1"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-5-1"
+ width="13.873985"
+ height="13.536115"
+ x="179.25481"
+ y="76.718834" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-5-5"
+ width="13.873985"
+ height="13.536115"
+ x="193.32123"
+ y="76.615784" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-8"
+ width="13.873985"
+ height="13.536115"
+ x="179.2666"
+ y="90.93148" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-12-1"
+ width="13.873985"
+ height="13.536115"
+ x="193.35323"
+ y="90.79583" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-4-3"
+ width="13.873985"
+ height="13.536115"
+ x="150.68204"
+ y="76.961281" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9"
+ width="13.873985"
+ height="13.536115"
+ x="164.74846"
+ y="76.858231" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-5-7"
+ width="13.873985"
+ height="13.536115"
+ x="150.69383"
+ y="91.173927" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-6-5"
+ width="13.873985"
+ height="13.536115"
+ x="164.78047"
+ y="91.038284" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-52-4"
+ width="13.873985"
+ height="13.536115"
+ x="179.33958"
+ y="105.17988" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-6-9"
+ width="13.873985"
+ height="13.536115"
+ x="193.40599"
+ y="105.07683" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-9-6"
+ width="13.873985"
+ height="13.536115"
+ x="179.35136"
+ y="119.39252" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-13-7"
+ width="13.873985"
+ height="13.536115"
+ x="193.438"
+ y="119.25687" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-1-1"
+ width="13.873985"
+ height="13.536115"
+ x="150.76682"
+ y="105.42233" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-5-0"
+ width="13.873985"
+ height="13.536115"
+ x="164.83324"
+ y="105.31927" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-1"
+ width="13.873985"
+ height="13.536115"
+ x="150.77861"
+ y="119.63497" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-69-3"
+ width="13.873985"
+ height="13.536115"
+ x="164.86523"
+ y="119.49933" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-4-8)"
+ d="m 155.94771,111.95193 c -1.79555,-4.39689 -2.23753,-8.35368 -2.30576,-12.808411 -0.0699,-4.565816 0.28209,-9.637916 2.41321,-14.844082"
+ id="path3-4-7"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-3-9)"
+ d="m 168.96533,111.45791 c -8.34944,-8.90341 -9.59362,-18.351903 0.075,-28.637574"
+ id="path1-8-4-7"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-9-9)"
+ d="m 170.91197,128.15463 c -5.89812,-8.90341 -6.95536,-18.02497 -0.12541,-28.310638"
+ id="path1-8-5-60-6"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-4-6)"
+ d="m 186.93809,112.07154 c -16.04448,0.96775 -25.77954,-8.01087 -28.37044,-26.854842"
+ id="path1-8-5-6-74-3"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-5-5)"
+ d="m 186.77109,125.92568 c -15.44756,8.13451 -27.5858,-8.11589 -27.66102,-27.345327"
+ id="path1-8-5-6-7-3-9"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-9-5)"
+ d="M 200.02477,114.44339 C 183.65188,113.82077 173.97252,103.35896 170.7239,85.318734"
+ id="path1-8-5-6-7-8-7-1"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-3-0)"
+ d="m 200.32369,126.48719 c -15.44756,8.13451 -27.35541,-6.97387 -27.43063,-26.20331"
+ id="path1-8-5-6-7-8-2-9-0"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-4-8)"
+ d="m 158.55371,96.770614 c 6.44809,-5.278367 15.46606,-6.30545 28.43113,-0.80303"
+ id="path1-7-0-4"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-9-2)"
+ d="m 170.94445,83.228321 c 10.07271,-6.043908 15.38566,-6.759046 29.59021,-0.132283"
+ id="path1-7-3-0-1"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-3-9)"
+ d="m 157.66034,83.435862 c 3.63223,-4.278368 8.63351,-5.657536 14.12501,-5.510111 4.33963,0.11649 8.77492,0.820413 14.54622,4.344855"
+ id="path1-7-3-3-2-1"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-8-2)"
+ d="m 172.72775,98.58407 c 6.44809,-7.4755 14.97937,-8.678727 27.94443,-0.885912"
+ id="path1-7-1-2-8"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-41-6)"
+ d="m 157.01627,125.54662 c -3.20365,-5.35347 -4.1584,-10.30574 -3.55535,-16.11684 0.3999,-3.85341 1.14322,-8.41966 3.60325,-12.520736"
+ id="path1-5-6-6"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-9-3"
+ width="13.873985"
+ height="13.536115"
+ x="236.11276"
+ y="76.419838" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-4-4"
+ width="13.873985"
+ height="13.536115"
+ x="250.17918"
+ y="76.316788" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-5-7"
+ width="13.873985"
+ height="13.536115"
+ x="236.12454"
+ y="90.632484" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-6-7"
+ width="13.873985"
+ height="13.536115"
+ x="250.21118"
+ y="90.496841" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-5-7"
+ width="13.873985"
+ height="13.536115"
+ x="207.54001"
+ y="76.662285" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-9-4"
+ width="13.873985"
+ height="13.536115"
+ x="221.60643"
+ y="76.559235" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-1-5"
+ width="13.873985"
+ height="13.536115"
+ x="207.55179"
+ y="90.874931" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3"
+ width="13.873985"
+ height="13.536115"
+ x="221.63843"
+ y="90.739288" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-4-7"
+ width="13.873985"
+ height="13.536115"
+ x="236.19753"
+ y="104.88088" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-1-2"
+ width="13.873985"
+ height="13.536115"
+ x="250.26395"
+ y="104.77783" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-1-9"
+ width="13.873985"
+ height="13.536115"
+ x="236.20932"
+ y="119.09353" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-9-9"
+ width="13.873985"
+ height="13.536115"
+ x="250.29596"
+ y="118.95789" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-3-0"
+ width="13.873985"
+ height="13.536115"
+ x="207.62477"
+ y="105.12333" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-8-2"
+ width="13.873985"
+ height="13.536115"
+ x="221.69119"
+ y="105.02028" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-7-7"
+ width="13.873985"
+ height="13.536115"
+ x="207.63657"
+ y="119.33598" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-4-9"
+ width="13.873985"
+ height="13.536115"
+ x="221.72321"
+ y="119.20033" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-3-5)"
+ d="m 212.80567,111.65293 c -1.79558,-4.39689 -2.23755,-8.35368 -2.30579,-12.808407 -0.0699,-4.565816 0.28212,-9.637916 2.41321,-14.844081"
+ id="path3-9-9"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-58-8)"
+ d="m 225.82327,111.15891 c -8.34942,-8.90341 -9.59362,-18.351899 0.075,-28.63757"
+ id="path1-8-3-5"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-1-7)"
+ d="m 227.76991,127.85564 c -5.89812,-8.90342 -6.95536,-18.02498 -0.12542,-28.310643"
+ id="path1-8-5-13-2"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-0-6)"
+ d="m 243.79603,111.77255 c -16.04449,0.96774 -25.77955,-8.01088 -28.37042,-26.854848"
+ id="path1-8-5-6-8-0"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-4-2)"
+ d="m 243.62902,125.62668 c -15.44756,8.13451 -27.58577,-8.11589 -27.66099,-27.345323"
+ id="path1-8-5-6-7-7-8"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-0-9)"
+ d="M 256.8827,114.14439 C 240.50982,113.52177 230.83045,103.05996 227.58184,85.019738"
+ id="path1-8-5-6-7-8-9-3"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-9-0)"
+ d="m 257.18162,126.18819 c -15.44756,8.13451 -27.35541,-6.97387 -27.43063,-26.203304"
+ id="path1-8-5-6-7-8-2-3-5"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-3-5)"
+ d="m 215.41164,96.471618 c 6.44809,-5.278367 15.46607,-6.30545 28.43113,-0.80303"
+ id="path1-7-8-9"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-3-7)"
+ d="m 227.80238,82.929325 c 10.07271,-6.043908 15.38567,-6.759046 29.59021,-0.132283"
+ id="path1-7-3-8-6"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-4-5)"
+ d="m 214.51828,83.136866 c 3.63222,-4.278368 8.6335,-5.657536 14.12501,-5.510111 4.33962,0.11649 8.77491,0.820413 14.54621,4.344855"
+ id="path1-7-3-3-4-5"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-64-4)"
+ d="m 229.58568,98.285075 c 6.44809,-7.475501 14.97938,-8.678728 27.94444,-0.885913"
+ id="path1-7-1-0-6"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-1-0)"
+ d="m 213.87421,105.00816 c -3.20366,-5.35346 -4.15841,-10.305726 -3.55533,-16.116829 0.39987,-3.853408 1.14319,-8.419661 3.60325,-12.520737"
+ id="path1-5-0-3"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-6"
+ width="13.873985"
+ height="13.536115"
+ x="65.269989"
+ y="133.67944" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-43"
+ width="13.873985"
+ height="13.536115"
+ x="79.336411"
+ y="133.57639" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-8"
+ width="13.873985"
+ height="13.536115"
+ x="65.281784"
+ y="147.89209" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-67"
+ width="13.873985"
+ height="13.536115"
+ x="79.368416"
+ y="147.75644" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-8"
+ width="13.873985"
+ height="13.536115"
+ x="36.697243"
+ y="133.92189" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-1"
+ width="13.873985"
+ height="13.536115"
+ x="50.76366"
+ y="133.81883" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-9"
+ width="13.873985"
+ height="13.536115"
+ x="36.709034"
+ y="148.13454" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-81"
+ width="13.873985"
+ height="13.536115"
+ x="50.79567"
+ y="147.99889" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-48"
+ width="13.873985"
+ height="13.536115"
+ x="65.354759"
+ y="162.14049" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-50"
+ width="13.873985"
+ height="13.536115"
+ x="79.421181"
+ y="162.03743" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-15"
+ width="13.873985"
+ height="13.536115"
+ x="65.366554"
+ y="176.35313" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-97"
+ width="13.873985"
+ height="13.536115"
+ x="79.453186"
+ y="176.21748" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-6"
+ width="13.873985"
+ height="13.536115"
+ x="36.782013"
+ y="162.38293" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-2"
+ width="13.873985"
+ height="13.536115"
+ x="50.848431"
+ y="162.27988" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-8"
+ width="13.873985"
+ height="13.536115"
+ x="36.793804"
+ y="176.59558" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-9"
+ width="13.873985"
+ height="13.536115"
+ x="50.88044"
+ y="176.45993" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-0)"
+ d="m 41.962911,168.91253 c -1.795561,-4.39689 -2.237542,-8.35368 -2.305768,-12.80841 -0.06994,-4.56582 0.282094,-9.63792 2.413213,-14.84408"
+ id="path6-5"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-7)"
+ d="m 54.980524,168.41851 c -8.34943,-8.90341 -9.593615,-18.3519 0.07498,-28.63757"
+ id="path1-8-0"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-0)"
+ d="m 56.927163,185.11524 c -5.898121,-8.90342 -6.955361,-18.02498 -0.125415,-28.31065"
+ id="path1-8-5-2"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-6)"
+ d="m 72.953284,169.03215 c -16.044488,0.96774 -25.77954,-8.01088 -28.37043,-26.85485"
+ id="path1-8-5-6-72"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-57)"
+ d="M 72.786279,182.88628 C 57.338715,191.02079 45.200495,174.77039 45.125273,155.54095"
+ id="path1-8-5-6-7-81"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-90)"
+ d="M 86.03996,171.40399 C 69.667071,170.78137 59.987709,160.31956 56.739094,142.27933"
+ id="path1-8-5-6-7-8-77"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-0)"
+ d="M 86.33888,183.44779 C 70.891317,191.5823 58.983467,176.47392 58.908245,157.24448"
+ id="path1-8-5-6-7-8-2-97"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-0)"
+ d="m 44.568902,153.73121 c 6.448084,-5.27836 15.466061,-6.30545 28.431123,-0.80303"
+ id="path1-7-9"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-0)"
+ d="m 56.959637,140.18892 c 10.072708,-6.04391 15.385665,-6.75904 29.590211,-0.13228"
+ id="path1-7-3-88"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-6)"
+ d="m 43.675541,140.39646 c 3.63222,-4.27837 8.633495,-5.65753 14.125001,-5.51011 4.339623,0.11649 8.774913,0.82041 14.546216,4.34486"
+ id="path1-7-3-3-9"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-2)"
+ d="m 58.742939,155.54467 c 6.448089,-7.4755 14.979372,-8.67873 27.944435,-0.88591"
+ id="path1-7-1-80"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-15)"
+ d="m 43.031457,182.50722 c -3.203646,-5.35347 -4.158399,-10.30573 -3.555338,-16.11684 0.399898,-3.85341 1.143216,-8.41966 3.603258,-12.52074"
+ id="path1-5-5"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-4-0"
+ width="13.873985"
+ height="13.536115"
+ x="122.11791"
+ y="133.0097" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-7-24"
+ width="13.873985"
+ height="13.536115"
+ x="136.18434"
+ y="132.90665" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-2-3"
+ width="13.873985"
+ height="13.536115"
+ x="122.12971"
+ y="147.22234" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-1-7"
+ width="13.873985"
+ height="13.536115"
+ x="136.21634"
+ y="147.0867" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-9-0"
+ width="13.873985"
+ height="13.536115"
+ x="93.545166"
+ y="133.25215" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-5-0"
+ width="13.873985"
+ height="13.536115"
+ x="107.61157"
+ y="133.14909" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-6-15"
+ width="13.873985"
+ height="13.536115"
+ x="93.556953"
+ y="147.4648" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-8-8"
+ width="13.873985"
+ height="13.536115"
+ x="107.64359"
+ y="147.32915" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-5-0"
+ width="13.873985"
+ height="13.536115"
+ x="122.20268"
+ y="161.47075" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-0"
+ width="13.873985"
+ height="13.536115"
+ x="136.2691"
+ y="161.36769" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-2-4"
+ width="13.873985"
+ height="13.536115"
+ x="122.21448"
+ y="175.68338" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-1-7"
+ width="13.873985"
+ height="13.536115"
+ x="136.30112"
+ y="175.54774" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-31"
+ width="13.873985"
+ height="13.536115"
+ x="93.629936"
+ y="161.7132" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-9-4"
+ width="13.873985"
+ height="13.536115"
+ x="107.69634"
+ y="161.61014" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-1-2"
+ width="13.873985"
+ height="13.536115"
+ x="93.641724"
+ y="175.92583" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-01"
+ width="13.873985"
+ height="13.536115"
+ x="107.72836"
+ y="175.79019" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-87-8)"
+ d="m 98.810843,168.24279 c -1.795575,-4.39688 -2.23755,-8.35367 -2.30576,-12.8084 -0.06986,-4.56582 0.282091,-9.63792 2.413212,-14.84408"
+ id="path7-3"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-5-85)"
+ d="m 111.82845,167.74878 c -8.34942,-8.90341 -9.59363,-18.3519 0.075,-28.63758"
+ id="path1-8-2-0"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-7-0)"
+ d="m 113.77509,184.4455 c -5.89813,-8.90341 -6.95537,-18.02497 -0.12542,-28.31064"
+ id="path1-8-5-1-0"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-9-69)"
+ d="m 129.80121,168.36241 c -16.04449,0.96775 -25.77955,-8.01087 -28.37042,-26.85484"
+ id="path1-8-5-6-0-4"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-3-37)"
+ d="m 129.6342,182.21655 c -15.44756,8.13451 -27.58577,-8.11589 -27.66099,-27.34533"
+ id="path1-8-5-6-7-5-8"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-8-3)"
+ d="M 142.88788,170.73425 C 126.51499,170.11164 116.83563,159.64983 113.58702,141.6096"
+ id="path1-8-5-6-7-8-3-22"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-6-1)"
+ d="m 143.1868,182.77805 c -15.44756,8.13452 -27.35541,-6.97386 -27.43063,-26.2033"
+ id="path1-8-5-6-7-8-2-2-0"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-87-8)"
+ d="m 101.41682,153.06148 c 6.44809,-5.27836 15.46606,-6.30545 28.43113,-0.80303"
+ id="path1-7-19-2"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-74-4)"
+ d="m 113.80756,139.51919 c 10.07271,-6.04391 15.38566,-6.75905 29.59021,-0.13228"
+ id="path1-7-3-7-4"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-0-0)"
+ d="m 100.52345,139.72673 c 3.63223,-4.27837 8.63351,-5.65754 14.12501,-5.51011 4.33963,0.11649 8.77492,0.82041 14.54622,4.34485"
+ id="path1-7-3-3-6-5"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-6-9)"
+ d="m 115.59086,154.87494 c 6.44809,-7.4755 14.97937,-8.67873 27.94444,-0.88591"
+ id="path1-7-1-8-8"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-4-7)"
+ d="m 99.879384,181.83749 c -3.203655,-5.35347 -4.158405,-10.30574 -3.555331,-16.11684 0.3999,-3.85341 1.143197,-8.41966 3.603256,-12.52074"
+ id="path1-5-2-4"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-5-8"
+ width="13.873985"
+ height="13.536115"
+ x="179.39226"
+ y="133.23703" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-5-4"
+ width="13.873985"
+ height="13.536115"
+ x="193.45868"
+ y="133.13399" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-2"
+ width="13.873985"
+ height="13.536115"
+ x="179.40405"
+ y="147.44968" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-12-5"
+ width="13.873985"
+ height="13.536115"
+ x="193.49069"
+ y="147.31404" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-4-2"
+ width="13.873985"
+ height="13.536115"
+ x="150.8195"
+ y="133.47948" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-7"
+ width="13.873985"
+ height="13.536115"
+ x="164.88593"
+ y="133.37643" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-5-5"
+ width="13.873985"
+ height="13.536115"
+ x="150.83128"
+ y="147.69212" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-6-7"
+ width="13.873985"
+ height="13.536115"
+ x="164.91792"
+ y="147.55649" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-52-2"
+ width="13.873985"
+ height="13.536115"
+ x="179.47704"
+ y="161.69807" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-6-2"
+ width="13.873985"
+ height="13.536115"
+ x="193.54346"
+ y="161.59503" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-9-8"
+ width="13.873985"
+ height="13.536115"
+ x="179.48883"
+ y="175.91072" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-13-6"
+ width="13.873985"
+ height="13.536115"
+ x="193.57545"
+ y="175.77509" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-1-0"
+ width="13.873985"
+ height="13.536115"
+ x="150.90427"
+ y="161.94052" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-5-09"
+ width="13.873985"
+ height="13.536115"
+ x="164.97069"
+ y="161.83748" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-12"
+ width="13.873985"
+ height="13.536115"
+ x="150.91606"
+ y="176.15317" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-69-6"
+ width="13.873985"
+ height="13.536115"
+ x="165.0027"
+ y="176.01753" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-4-6)"
+ d="m 156.08517,168.47013 c -1.79555,-4.39688 -2.23753,-8.35367 -2.30576,-12.8084 -0.0699,-4.56582 0.28209,-9.63792 2.41321,-14.84409"
+ id="path3-4-9"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-3-91)"
+ d="m 169.10279,167.97611 c -8.34944,-8.90341 -9.59362,-18.3519 0.075,-28.63757"
+ id="path1-8-4-4"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-9-3)"
+ d="m 171.04943,184.67284 c -5.89812,-8.90341 -6.95536,-18.02497 -0.12541,-28.31064"
+ id="path1-8-5-60-8"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-4-8)"
+ d="m 187.07555,168.58975 c -16.04448,0.96775 -25.77954,-8.01087 -28.37044,-26.85485"
+ id="path1-8-5-6-74-2"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-5-3)"
+ d="m 186.90855,182.44389 c -15.44756,8.13451 -27.5858,-8.11589 -27.66102,-27.34533"
+ id="path1-8-5-6-7-3-8"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-9-9)"
+ d="m 200.16223,170.96159 c -16.37289,-0.62261 -26.05225,-11.08442 -29.30087,-29.12465"
+ id="path1-8-5-6-7-8-7-2"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-3-4)"
+ d="m 200.46115,183.00539 c -15.44756,8.13452 -27.35541,-6.97387 -27.43063,-26.2033"
+ id="path1-8-5-6-7-8-2-9-7"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-4-6)"
+ d="m 158.69117,153.28882 c 6.44809,-5.27837 15.46606,-6.30545 28.43113,-0.80303"
+ id="path1-7-0-8"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-9-1)"
+ d="m 171.08191,139.74653 c 10.07271,-6.04391 15.38566,-6.75905 29.59021,-0.13229"
+ id="path1-7-3-0-6"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-3-0)"
+ d="m 157.7978,139.95407 c 3.63223,-4.27837 8.63351,-5.65754 14.12501,-5.51011 4.33963,0.11649 8.77492,0.82041 14.54622,4.34485"
+ id="path1-7-3-3-2-7"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-8-3)"
+ d="m 172.86521,155.10228 c 6.44809,-7.4755 14.97937,-8.67873 27.94443,-0.88592"
+ id="path1-7-1-2-7"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-41-0)"
+ d="m 157.15373,182.06482 c -3.20365,-5.35346 -4.1584,-10.30573 -3.55535,-16.11683 0.3999,-3.85341 1.14322,-8.41966 3.60325,-12.52074"
+ id="path1-5-6-0"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-937-0-3-9-4"
+ width="13.873985"
+ height="13.536115"
+ x="236.25021"
+ y="132.93803" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-4-5"
+ width="13.873985"
+ height="13.536115"
+ x="250.31664"
+ y="132.83498" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-5-4"
+ width="13.873985"
+ height="13.536115"
+ x="236.26201"
+ y="147.15068" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-6-2"
+ width="13.873985"
+ height="13.536115"
+ x="250.34863"
+ y="147.01503" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-5-3"
+ width="13.873985"
+ height="13.536115"
+ x="207.67746"
+ y="133.18048" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-9-9"
+ width="13.873985"
+ height="13.536115"
+ x="221.74388"
+ y="133.07742" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-1-6"
+ width="13.873985"
+ height="13.536115"
+ x="207.68925"
+ y="147.39313" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-5"
+ width="13.873985"
+ height="13.536115"
+ x="221.77588"
+ y="147.25748" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-6-4-3-4-4"
+ width="13.873985"
+ height="13.536115"
+ x="236.33499"
+ y="161.39908" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-1-3"
+ width="13.873985"
+ height="13.536115"
+ x="250.4014"
+ y="161.29602" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-1-94"
+ width="13.873985"
+ height="13.536115"
+ x="236.34677"
+ y="175.61172" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-9-4"
+ width="13.873985"
+ height="13.536115"
+ x="250.43341"
+ y="175.47607" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-3-6"
+ width="13.873985"
+ height="13.536115"
+ x="207.76224"
+ y="161.64153" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-8-0"
+ width="13.873985"
+ height="13.536115"
+ x="221.82864"
+ y="161.53847" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-7-2"
+ width="13.873985"
+ height="13.536115"
+ x="207.77402"
+ y="175.85417" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370418;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-4-2"
+ width="13.873985"
+ height="13.536115"
+ x="221.86066"
+ y="175.71852" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-3-0)"
+ d="m 212.94313,168.17113 c -1.79558,-4.39688 -2.23755,-8.35367 -2.30579,-12.8084 -0.0699,-4.56582 0.28212,-9.63792 2.41321,-14.84409"
+ id="path3-9-8"
+ inkscape:transform-center-x="-0.76484503"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-58-2)"
+ d="m 225.96073,167.67712 c -8.34942,-8.90342 -9.59362,-18.35191 0.075,-28.63758"
+ id="path1-8-3-4"
+ inkscape:transform-center-x="-1.756365"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-1-4)"
+ d="m 227.90737,184.37384 c -5.89812,-8.90341 -6.95536,-18.02497 -0.12542,-28.31064"
+ id="path1-8-5-13-3"
+ inkscape:transform-center-x="-1.2407313"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-0-9)"
+ d="M 243.93349,168.29075 C 227.889,169.2585 218.15394,160.27988 215.56307,141.43591"
+ id="path1-8-5-6-8-1"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-4-5)"
+ d="M 243.76648,182.14489 C 228.31892,190.2794 216.18071,174.029 216.10549,154.79956"
+ id="path1-8-5-6-7-7-1"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-0-7)"
+ d="M 257.02016,170.66259 C 240.64728,170.03998 230.96791,159.57817 227.7193,141.53794"
+ id="path1-8-5-6-7-8-9-1"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-60-0-5-2-8-6-9-6)"
+ d="m 257.31908,182.70639 c -15.44756,8.13452 -27.35541,-6.97386 -27.43063,-26.2033"
+ id="path1-8-5-6-7-8-2-3-9"
+ inkscape:transform-center-x="-4.374983"
+ inkscape:transform-center-y="-2.6647327"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-3-0)"
+ d="m 215.5491,152.98982 c 6.44809,-5.27837 15.46607,-6.30545 28.43113,-0.80303"
+ id="path1-7-8-0"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.1779744"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-3-0)"
+ d="m 227.93984,139.44753 c 10.07271,-6.04391 15.38567,-6.75905 29.59021,-0.13229"
+ id="path1-7-3-8-0"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.5378977"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-6-7-4-3)"
+ d="m 214.65574,139.65507 c 3.63222,-4.27837 8.6335,-5.65754 14.12501,-5.51011 4.33962,0.11649 8.77491,0.82041 14.54621,4.34485"
+ id="path1-7-3-3-4-0"
+ inkscape:transform-center-x="1.0156762"
+ inkscape:transform-center-y="1.709327"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ArrowWideRounded-8-64-5)"
+ d="m 229.72314,154.80328 c 6.44809,-7.4755 14.97938,-8.67873 27.94444,-0.88591"
+ id="path1-7-1-0-2"
+ inkscape:transform-center-x="1.007168"
+ inkscape:transform-center-y="1.6683096"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-1-1)"
+ d="m 214.01167,161.52637 c -3.20366,-5.35347 -4.15841,-10.30573 -3.55533,-16.11684 0.39987,-3.8534 1.14319,-8.41966 3.60325,-12.52073"
+ id="path1-5-0-5"
+ inkscape:transform-center-x="-1.1208011"
+ inkscape:transform-center-y="1.8780721"
+ sodipodi:nodetypes="csc" />
+ </g>
+</svg>
diff --git a/Documentation/camera-sensor-model.rst b/Documentation/camera-sensor-model.rst
new file mode 100644
index 00000000..b66c880a
--- /dev/null
+++ b/Documentation/camera-sensor-model.rst
@@ -0,0 +1,173 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. _camera-sensor-model:
+
+.. todo: Move to Doxygen-generated documentation
+
+The libcamera camera sensor model
+=================================
+
+libcamera defines an abstract camera sensor model in order to provide
+a description of each of the processing steps that result in image data being
+sent on the media bus and that form the image stream delivered to applications.
+
+Applications should use the abstract camera sensor model defined here to
+precisely control the operations of the camera sensor.
+
+The libcamera camera sensor model targets image sensors producing frames in
+RAW format, delivered through a MIPI CSI-2 compliant bus implementation.
+
+The abstract sensor model maps libcamera components to the characteristics and
+operations of an image sensor, and serves as a reference to model the libcamera
+CameraSensor class and SensorConfiguration classes and operations.
+
+In order to control the configuration of the camera sensor through the
+SensorConfiguration class, applications should understand this model and map it
+to the combination of image sensor and kernel driver in use.
+
+The camera sensor model defined here is based on the *MIPI CCS specification*,
+particularly on *Section 8.2 - Image readout* of *Chapter 8 - Video Timings*.
+
+
+Glossary
+--------
+
+.. glossary::
+
+ Pixel array
+ The full grid of pixels, active and inactive ones
+
+ Pixel array active area
+ The portion(s) of the pixel array that contains valid and readable pixels;
+ corresponds to the libcamera properties::PixelArrayActiveAreas
+
+ Analog crop rectangle
+ The portion of the *pixel array active area* which is read out and passed
+ to further processing stages
+
+ Subsampling
+ Pixel processing techniques that reduce the image size by binning or by
+ skipping adjacent pixels
+
+ Digital crop
+ Crop of the sub-sampled image data before scaling
+
+ Frame output
+ The frame (image) as output on the media bus by the camera sensor
+
+Camera sensor model
+-------------------
+
+The abstract sensor model is described in the following diagram.
+
+.. figure:: sensor_model.svg
+
+
+1. The sensor reads pixels from the *pixel array*. The pixels being read out are
+ selected by the *analog crop rectangle*.
+
+2. The pixels can be subsampled to reduce the image size without affecting the
+ field of view. Two subsampling techniques can be used:
+
+ - Binning: combines adjacent pixels of the same colour by averaging or
+ summing their values, in the analog domain and/or the digital domain.
+
+ .. figure:: binning.svg
+
+
+ - Skipping: skips the read out of a number of adjacent pixels.
+
+ .. figure:: skipping.svg
+
+
+3. The output of the optional sub-sampling stage is then cropped after the
+ conversion of the analogue pixel values in the digital domain.
+
+4. The resulting output frame is sent on the media bus by the sensor.
+
+Camera Sensor configuration parameters
+--------------------------------------
+
+The libcamera camera sensor model defines parameters that allow users to
+control:
+
+1. The image format bit depth
+
+2. The size and position of the *Analog crop rectangle*
+
+3. The subsampling factors used to downscale the pixel array readout data to a
+ smaller frame size without reducing the image *field of view*. Two
+ configuration parameters are made available to control the downscaling
+ factor:
+
+ - binning
+ A vertical and horizontal binning factor can be specified, the image
+ will be downscaled in its vertical and horizontal sizes by the specified
+ factor.
+
+ .. code-block:: c
+ :caption: Definition: The horizontal and vertical binning factors
+
+ horizontal_binning = xBin;
+ vertical_binning = yBin;
+
+ - skipping
+ Skipping reduces the image resolution by skipping the read-out of a number
+ of adjacent pixels. The skipping factor is specified by the 'increment'
+ number (number of pixels to 'skip') in the vertical and horizontal
+ directions and for even and odd rows and columns.
+
+ .. code-block:: c
+ :caption: Definition: The horizontal and vertical skipping factors
+
+ horizontal_skipping = (xOddInc + xEvenInc) / 2;
+ vertical_skipping = (yOddInc + yEvenInc) / 2;
+
+ Different sensors perform the binning and skipping stages in different
+ orders. For the sake of computing the final output image size the order of
+ execution is not relevant. The overall down-scaling factor is obtained by
+ combining the binning and skipping factors.
+
+ .. code-block:: c
+ :caption: Definition: The total scaling factor (binning + sub-sampling)
+
+ total_horizontal_downscale = horizontal_binning + horizontal_skipping;
+ total_vertical_downscale = vertical_binning + vertical_skipping;
+
+
+4. The output size is used to specify any additional cropping on the sub-sampled
+ frame.
+
+5. The total line length and frame height (*visibile* pixels + *blankings*) as
+ sent on the MIPI CSI-2 bus.
+
+6. The pixel transmission rate on the MIPI CSI-2 bus.
+
+The above parameters are combined to obtain the following high-level
+configurations:
+
+- **frame output size**
+
+ Obtained by applying a crop to the physical pixel array size in the analog
+ domain, followed by optional binning and sub-sampling (in any order),
+ followed by an optional crop step in the output digital domain.
+
+- **frame rate**
+
+ The combination of the *total frame size*, the image format *bit depth* and
+ the *pixel rate* of the data sent on the MIPI CSI-2 bus allows to compute the
+ image stream frame rate. The equation is the well known:
+
+ .. code-block:: c
+
+ frame_duration = total_frame_size / pixel_rate;
+ frame_rate = 1 / frame_duration;
+
+
+ where the *pixel_rate* parameter is the result of the sensor's configuration
+ of the MIPI CSI-2 bus *(the following formula applies to MIPI CSI-2 when
+ used on MIPI D-PHY physical protocol layer only)*
+
+ .. code-block:: c
+
+ pixel_rate = csi_2_link_freq * 2 * nr_of_lanes / bits_per_sample;
diff --git a/Documentation/code-of-conduct.rst b/Documentation/code-of-conduct.rst
new file mode 100644
index 00000000..38b7d7ad
--- /dev/null
+++ b/Documentation/code-of-conduct.rst
@@ -0,0 +1,94 @@
+.. SPDX-License-Identifier: CC-BY-4.0
+
+.. _code-of-conduct:
+
+Contributor Covenant Code of Conduct
+====================================
+
+Our Pledge
+----------
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+Our Standards
+-------------
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+Our Responsibilities
+--------------------
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+Scope
+-----
+
+This Code of Conduct applies within all project spaces, and it also applies when
+an individual is representing the project or its community in public spaces.
+Examples of representing a project or community include using an official
+project e-mail address, posting via an official social media account, or acting
+as an appointed representative at an online or offline event. Representation of
+a project may be further defined and clarified by project maintainers.
+
+Enforcement
+-----------
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at conduct@libcamera.org, or directly to
+any member of the code of conduct team:
+
+* Kieran Bingham <kieran.bingham@ideasonboard.com>
+* Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+
+All complaints will be reviewed and investigated and will result in a response
+that is deemed necessary and appropriate to the circumstances. The project team
+is obligated to maintain confidentiality with regard to the reporter of an
+incident. Further details of specific enforcement policies may be posted
+separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+Attribution
+-----------
+
+This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+.. _Contributor Covenant: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
+
diff --git a/Documentation/coding-style.rst b/Documentation/coding-style.rst
index 053fdd99..72cb28d2 100644
--- a/Documentation/coding-style.rst
+++ b/Documentation/coding-style.rst
@@ -59,7 +59,7 @@ document:
underscores in between
* All formatting rules specified in the selected sections of the Linux kernel
Code Style for indentation, braces, spacing, etc
-* Header guards are formatted as '__LIBCAMERA_FILE_NAME_H__'
+* Headers are guarded by the use of '#pragma once'
Order of Includes
~~~~~~~~~~~~~~~~~
diff --git a/Documentation/contributing.rst b/Documentation/contributing.rst
index 6405c5fb..18b1914a 100644
--- a/Documentation/contributing.rst
+++ b/Documentation/contributing.rst
@@ -8,6 +8,10 @@ Whether you would like to help with coding, documentation, testing, proposing
new features, or just discussing the project with the community, you can join
our official public communication channels, or simply check out the code.
+The project adheres to a :ref:`code of conduct <code-of-conduct>` that
+maintainers, contributors and community members are expected to follow in all
+online and offline communication.
+
Mailing List
------------
@@ -68,6 +72,13 @@ code that is as easy to read, understand and maintain as possible. This is
made possible by a set of :ref:`coding-style-guidelines` that all submissions
are expected to follow.
+We also care about the quality of commit messages. A good commit message not
+only describes what a commit does, but why it does so. By conveying clear
+information about the purpose of the commit, it helps speeding up reviews.
+Regardless of whether you're new to git or have years of experience,
+https://cbea.ms/git-commit/ is always a good guide to read to improve your
+commit message writing skills.
+
The patch submission process for libcamera is similar to the Linux kernel, and
goes through the `libcamera-devel`_ mailing list. If you have no previous
experience with ``git-send-email``, or just experience trouble configuring it
@@ -127,4 +138,5 @@ By making a contribution to this project, I certify that:
.. toctree::
:hidden:
+ Code of Conduct <code-of-conduct>
Coding Style <coding-style>
diff --git a/Documentation/environment_variables.rst b/Documentation/environment_variables.rst
index f092cbbd..a9b230bc 100644
--- a/Documentation/environment_variables.rst
+++ b/Documentation/environment_variables.rst
@@ -37,6 +37,11 @@ LIBCAMERA_IPA_MODULE_PATH
Example value: ``${HOME}/.libcamera/lib:/opt/libcamera/vendor/lib``
+LIBCAMERA_RPI_CONFIG_FILE
+ Define a custom configuration file to use in the Raspberry Pi pipeline handler.
+
+ Example value: ``/usr/local/share/libcamera/pipeline/rpi/vc4/minimal_mem.yaml``
+
Further details
---------------
@@ -138,7 +143,7 @@ contain tuning parameters for the algorithms, in JSON format.
The ``LIBCAMERA_IPA_CONFIG_PATH`` variable can be used to specify custom
storage locations to search for those configuration files.
-`Examples <https://git.libcamera.org/libcamera/libcamera.git/tree/src/ipa/raspberrypi/data>`__
+`Examples <https://git.libcamera.org/libcamera/libcamera.git/tree/src/ipa/rpi/vc4/data>`__
IPA module
~~~~~~~~~~
diff --git a/Documentation/getting-started.rst b/Documentation/getting-started.rst
index 4cc34a17..987f43f7 100644
--- a/Documentation/getting-started.rst
+++ b/Documentation/getting-started.rst
@@ -1,3 +1,4 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
.. Getting started information is defined in the project README file.
.. include:: ../README.rst
:start-after: .. section-begin-getting-started
diff --git a/Documentation/guides/application-developer.rst b/Documentation/guides/application-developer.rst
index 1b2d7727..9a9905b1 100644
--- a/Documentation/guides/application-developer.rst
+++ b/Documentation/guides/application-developer.rst
@@ -5,7 +5,7 @@ Using libcamera in a C++ application
This tutorial shows how to create a C++ application that uses libcamera to
interface with a camera on a system, capture frames from it for 3 seconds, and
-write metadata about the frames to standard out.
+write metadata about the frames to standard output.
Application skeleton
--------------------
@@ -348,7 +348,7 @@ The libcamera library uses the concept of `signals and slots` (similar to `Qt
Signals and Slots`_) to connect events with callbacks to handle them.
.. _signals and slots: https://libcamera.org/api-html/classlibcamera_1_1Signal.html#details
-.. _Qt Signals and Slots: https://doc.qt.io/qt-5/signalsandslots.html
+.. _Qt Signals and Slots: https://doc.qt.io/qt-6/signalsandslots.html
The ``Camera`` device emits two signals that applications can connect to in
order to execute callbacks on frame completion events.
diff --git a/Documentation/guides/introduction.rst b/Documentation/guides/introduction.rst
index 2d1760c1..700ec2d3 100644
--- a/Documentation/guides/introduction.rst
+++ b/Documentation/guides/introduction.rst
@@ -288,7 +288,7 @@ with dedicated pipeline handlers:
- Intel IPU3 (ipu3)
- Rockchip RK3399 (rkisp1)
- - RaspberryPi 3 and 4 (raspberrypi)
+ - RaspberryPi 3 and 4 (rpi/vc4)
Furthermore, generic platform support is provided for the following:
diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst
index fc031745..25deadef 100644
--- a/Documentation/guides/ipa.rst
+++ b/Documentation/guides/ipa.rst
@@ -19,6 +19,16 @@ connect to, in order to receive data from the IPA asynchronously. In addition,
it contains any custom data structures that the pipeline handler and IPA may
pass to each other.
+It is possible to use the same IPA interface with multiple pipeline handlers
+on different hardware platforms. Generally in such cases, these platforms would
+have a common hardware ISP pipeline. For instance, the rkisp1 pipeline handler
+supports both the RK3399 and the i.MX8MP as they integrate the same ISP.
+However, the i.MX8MP has a more complex camera pipeline, which may call for a
+dedicated pipeline handler in the future. As the ISP is the same as for RK3399,
+the same IPA interface could be used for both pipeline handlers. The build files
+provide a mapping from pipeline handler to the IPA interface name as detailed in
+:ref:`compiling-section`.
+
The IPA protocol refers to the agreement between the pipeline handler and the
IPA regarding the expected response(s) from the IPA for given calls to the IPA.
This protocol doesn't need to be declared anywhere in code, but it shall be
@@ -43,7 +53,7 @@ interface definition is thus written by the pipeline handler author, based on
how they design the interactions between the pipeline handler and the IPA.
The entire IPA interface, including the functions, signals, and any custom
-structs shall be defined in a file named {pipeline_name}.mojom under
+structs shall be defined in a file named {interface_name}.mojom under
include/libcamera/ipa/.
.. _mojo Interface Definition Language: https://chromium.googlesource.com/chromium/src.git/+/master/mojo/public/tools/bindings/README.md
@@ -150,7 +160,7 @@ and the Event IPA interface, which describes the signals received by the
pipeline handler that the IPA can emit. Both must be defined. This section
focuses on the Main IPA interface.
-The main interface must be named as IPA{pipeline_name}Interface.
+The main interface must be named as IPA{interface_name}Interface.
The functions that the pipeline handler can call from the IPA may be
synchronous or asynchronous. Synchronous functions do not return until the IPA
@@ -243,7 +253,7 @@ then it may be empty. These emissions are meant to notify the pipeline handler
of some event, such as request data is ready, and *must not* be used to drive
the camera pipeline from the IPA.
-The event interface must be named as IPA{pipeline_name}EventInterface.
+The event interface must be named as IPA{interface_name}EventInterface.
Functions defined in the event interface are implicitly asynchronous.
Thus they cannot return any value. Specifying the [async] tag is not
@@ -266,38 +276,42 @@ The following is an example of an event interface definition:
setStaggered(libcamera.ControlList controls);
};
+.. _compiling-section:
+
Compiling the IPA interface
---------------------------
-After the IPA interface is defined in include/libcamera/ipa/{pipeline_name}.mojom,
+After the IPA interface is defined in include/libcamera/ipa/{interface_name}.mojom,
an entry for it must be added in meson so that it can be compiled. The filename
-must be added to the ipa_mojom_files object in include/libcamera/ipa/meson.build.
+must be added to the pipeline_ipa_mojom_mapping variable in
+include/libcamera/ipa/meson.build. This variable maps the pipeline handler name
+to its IPA interface file.
For example, adding the raspberrypi.mojom file to meson:
.. code-block:: none
- ipa_mojom_files = [
- 'raspberrypi.mojom',
+ pipeline_ipa_mojom_mapping = [
+ 'rpi/vc4': 'raspberrypi.mojom',
]
This will cause the mojo data definition file to be compiled. Specifically, it
generates five files:
- a header describing the custom data structures, and the complete IPA
- interface (at {$build_dir}/include/libcamera/ipa/{pipeline}_ipa_interface.h)
+ interface (at {$build_dir}/include/libcamera/ipa/{interface}_ipa_interface.h)
- a serializer implementing de/serialization for the custom data structures (at
- {$build_dir}/include/libcamera/ipa/{pipeline}_ipa_serializer.h)
+ {$build_dir}/include/libcamera/ipa/{interface}_ipa_serializer.h)
- a proxy header describing a specialized IPA proxy (at
- {$build_dir}/include/libcamera/ipa/{pipeline}_ipa_proxy.h)
+ {$build_dir}/include/libcamera/ipa/{interface}_ipa_proxy.h)
- a proxy source implementing the IPA proxy (at
- {$build_dir}/src/libcamera/proxy/{pipeline}_ipa_proxy.cpp)
+ {$build_dir}/src/libcamera/proxy/{interface}_ipa_proxy.cpp)
- a proxy worker source implementing the other end of the IPA proxy (at
- {$build_dir}/src/libcamera/proxy/worker/{pipeline}_ipa_proxy_worker.cpp)
+ {$build_dir}/src/libcamera/proxy/worker/{interface}_ipa_proxy_worker.cpp)
The IPA proxy serves as the layer between the pipeline handler and the IPA, and
handles threading vs isolation transparently. The pipeline handler and the IPA
@@ -312,7 +326,7 @@ file, the following header must be included:
.. code-block:: C++
- #include <libcamera/ipa/{pipeline_name}_ipa_interface.h>
+ #include <libcamera/ipa/{interface_name}_ipa_interface.h>
The POD types of the structs simply become their C++ counterparts, eg. uint32
in mojo will become uint32_t in C++. mojo map becomes C++ std::map, and mojo
diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst
index 2d55666d..728e9676 100644
--- a/Documentation/guides/pipeline-handler.rst
+++ b/Documentation/guides/pipeline-handler.rst
@@ -183,7 +183,7 @@ to the libcamera build options in the top level ``meson_options.txt``.
option('pipelines',
type : 'array',
- choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc', 'vivid'],
+ choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'uvcvideo', 'vimc', 'vivid'],
description : 'Select which pipeline handlers to include')
@@ -203,7 +203,7 @@ implementations for the overridden class members.
PipelineHandlerVivid(CameraManager *manager);
CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) override;
+ Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -223,7 +223,7 @@ implementations for the overridden class members.
}
CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
+ Span<const StreamRole> roles)
{
return nullptr;
}
@@ -289,7 +289,7 @@ features:
.. code-block:: cpp
#include <libcamera/base/log.h>
-
+
#include "libcamera/internal/pipeline_handler.h"
Run the following commands:
@@ -587,12 +587,12 @@ immutable properties of the ``Camera`` device.
The libcamera controls and properties are defined in YAML form which is
processed to automatically generate documentation and interfaces. Controls are
-defined by the src/libcamera/`control_ids.yaml`_ file and camera properties
-are defined by src/libcamera/`properties_ids.yaml`_.
+defined by the src/libcamera/`control_ids_core.yaml`_ file and camera properties
+are defined by src/libcamera/`properties_ids_core.yaml`_.
.. _controls framework: https://libcamera.org/api-html/controls_8h.html
-.. _control_ids.yaml: https://libcamera.org/api-html/control__ids_8h.html
-.. _properties_ids.yaml: https://libcamera.org/api-html/property__ids_8h.html
+.. _control_ids_core.yaml: https://libcamera.org/api-html/control__ids_8h.html
+.. _properties_ids_core.yaml: https://libcamera.org/api-html/property__ids_8h.html
Pipeline handlers can optionally register the list of controls an application
can set as well as a list of immutable camera properties. Being both
@@ -651,7 +651,7 @@ inline in our VividCameraData init:
ctrls.emplace(id, info);
}
- controlInfo_ = std::move(ctrls);
+ controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
The ``properties_`` field is a list of ``ControlId`` instances
associated with immutable values, which represent static characteristics that can
@@ -672,6 +672,58 @@ handling controls:
#include <libcamera/controls.h>
#include <libcamera/control_ids.h>
+Vendor-specific controls and properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Vendor-specific controls and properties must be defined in a separate YAML file
+and included in the build by defining the pipeline handler to file mapping in
+include/libcamera/meson.build. These YAML files live in the src/libcamera
+directory.
+
+For example, adding a Raspberry Pi vendor control file for the PiSP pipeline
+handler is done with the following mapping:
+
+.. code-block:: meson
+
+ controls_map = {
+ 'controls': {
+ 'draft': 'control_ids_draft.yaml',
+ 'libcamera': 'control_ids_core.yaml',
+ 'rpi/pisp': 'control_ids_rpi.yaml',
+ },
+
+ 'properties': {
+ 'draft': 'property_ids_draft.yaml',
+ 'libcamera': 'property_ids_core.yaml',
+ }
+ }
+
+The pipeline handler named above must match the pipeline handler option string
+specified in the meson build configuration.
+
+Vendor-specific controls and properties must contain a `vendor: <vendor_string>`
+tag in the YAML file. Every unique vendor tag must define a unique and
+non-overlapping range of reserved control IDs in src/libcamera/control_ranges.yaml.
+
+For example, the following block defines a vendor-specific control with the
+`rpi` vendor tag:
+
+.. code-block:: yaml
+
+ vendor: rpi
+ controls:
+ - PispConfigDumpFile:
+ type: string
+ description: |
+ Triggers the Raspberry Pi PiSP pipeline handler to generate a JSON
+ formatted dump of the Backend configuration to the filename given by the
+ value of the control.
+
+The controls will be generated in the vendor-specific namespace
+`libcamera::controls::rpi`. Additionally a `#define
+LIBCAMERA_HAS_RPI_VENDOR_CONTROLS` will be available to allow applications to
+test for the availability of these controls.
+
Generating a default configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -729,7 +781,7 @@ function.
.. _Camera::generateConfiguration(): https://libcamera.org/api-html/classlibcamera_1_1Camera.html#a25c80eb7fc9b1cf32692ce0c7f09991d
.. _PipelineHandler::generateConfiguration(): https://libcamera.org/api-html/classlibcamera_1_1PipelineHandler.html#a7932e87735695500ce1f8c7ae449b65b
-Configurations are generated by receiving a list of ``StreamRoles`` instances,
+Configurations are generated by receiving a list of ``StreamRole`` instances,
which libcamera uses as predefined ways an application intends to use a camera
(You can read the full list in the `StreamRole API`_ documentation). These are
optional hints on how an application intends to use a stream, and a pipeline
@@ -971,7 +1023,8 @@ with the fourcc and size attributes to apply directly to the capture device
node. The fourcc attribute is a `V4L2PixelFormat`_ and differs from the
``libcamera::PixelFormat``. Converting the format requires knowledge of the
plane configuration for multiplanar formats, so you must explicitly convert it
-using the helper ``V4L2PixelFormat::fromPixelFormat()``.
+using the helper ``V4L2VideoDevice::toV4L2PixelFormat()`` provided by the
+V4L2VideoDevice instance that the format will be applied on.
.. _V4L2DeviceFormat: https://libcamera.org/api-html/classlibcamera_1_1V4L2DeviceFormat.html
.. _V4L2PixelFormat: https://libcamera.org/api-html/classlibcamera_1_1V4L2PixelFormat.html
@@ -981,7 +1034,7 @@ Add the following code beneath the code from above:
.. code-block:: cpp
V4L2DeviceFormat format = {};
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
+ format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
Set the video device format defined above using the
@@ -1001,7 +1054,7 @@ Continue the implementation with the following code:
return ret;
if (format.size != cfg.size ||
- format.fourcc != V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat))
+ format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
return -EINVAL;
Finally, store and set stream-specific data reflecting the state of the stream.
@@ -1369,7 +1422,7 @@ emitted triggers the execution of the connected slots. A detailed description
of the libcamera implementation is available in the `libcamera Signal and Slot`_
classes documentation.
-.. _Qt Signals and Slots: https://doc.qt.io/qt-5/signalsandslots.html
+.. _Qt Signals and Slots: https://doc.qt.io/qt-6/signalsandslots.html
.. _libcamera Signal and Slot: https://libcamera.org/api-html/classlibcamera_1_1Signal.html#details
In order to notify applications about the availability of new frames and data,
@@ -1408,7 +1461,7 @@ function to the V4L2 device buffer signal.
video_->bufferReady.connect(this, &VividCameraData::bufferReady);
Create the matching ``VividCameraData::bufferReady`` function after your
-VividCameradata::init() impelementation.
+VividCameradata::init() implementation.
The ``bufferReady`` function obtains the request from the buffer using the
``request`` function, and notifies the ``Camera`` that the buffer and
diff --git a/Documentation/images/rotation/rotate0.svg b/Documentation/images/rotation/rotate0.svg
new file mode 100644
index 00000000..13cde16a
--- /dev/null
+++ b/Documentation/images/rotation/rotate0.svg
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate0.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="1.4854147"
+ inkscape:cx="666.48052"
+ inkscape:cy="448.35962"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;paint-order:markers stroke fill;stroke-dasharray:none"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="77.237244"
+ y="81.982094" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;paint-order:markers stroke fill;stroke-dasharray:none"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="92.612343"
+ y="98.912964" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="167.25099"
+ y="98.912964" />
+ <g
+ id="g4"
+ transform="translate(-0.98582077)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 244.95942,81.765726 62.444825,81.97209 154.25639,28.65633 Z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
+ d="m 199.76751,33.368887 0.0285,21.581353"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
+ d="m 215.59016,33.189206 0.0959,31.330304"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 194.42835,33.189356 25.2821,-0.220612"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
+ d="m 195.19248,33.096339 -0.0701,-5.375793 23.77787,-0.05613 0.0553,5.315811"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 194.20874,25.616264 25.25485,-0.02536"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 195.03436,26.298566 -0.0455,-5.426692 23.77787,-0.05613 0.0553,5.315811"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate0Mirror.svg b/Documentation/images/rotation/rotate0Mirror.svg
new file mode 100644
index 00000000..a7edda87
--- /dev/null
+++ b/Documentation/images/rotation/rotate0Mirror.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate0Mirror.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.82900578"
+ inkscape:cx="599.51331"
+ inkscape:cy="579.00682"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="-230.13463"
+ y="81.982094"
+ transform="scale(-1,1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="-214.75954"
+ y="98.912964"
+ transform="scale(-1,1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="-140.12088"
+ y="98.912964"
+ transform="scale(-1,1)" />
+ <g
+ id="g4"
+ transform="matrix(-1,0,0,1,308.35769,0)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 62.412454,81.765726 244.92705,81.97209 153.11548,28.65633 Z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 107.60436,33.368887 -0.0285,21.581353"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 91.781714,33.189206 -0.0959,31.330304"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 112.94352,33.189356 87.661424,32.968744"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 112.17939,33.096339 0.0701,-5.375793 -23.777866,-0.05613 -0.0553,5.315811"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 113.16313,25.616264 87.908284,25.590904"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 112.33751,26.298566 0.0455,-5.426692 -23.777866,-0.05613 -0.0553,5.315811"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate180.svg b/Documentation/images/rotation/rotate180.svg
new file mode 100644
index 00000000..d092a532
--- /dev/null
+++ b/Documentation/images/rotation/rotate180.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate180.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.94272086"
+ inkscape:cx="467.79489"
+ inkscape:cy="423.24299"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="-230.13461"
+ y="-140.22527"
+ transform="scale(-1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="-214.75951"
+ y="-123.2944"
+ transform="scale(-1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="-140.12086"
+ y="-123.2944"
+ transform="scale(-1)" />
+ <g
+ id="g4"
+ transform="rotate(180,154.17884,111.10368)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 62.412437,140.44163 182.514593,-0.20636 -91.81156,53.31576 z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 107.60435,188.83847 -0.0285,-21.58135"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 91.781697,189.01815 -0.0959,-31.3303"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 112.94351,189.018 -25.282103,0.22061"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 112.17938,189.11102 0.0701,5.37579 -23.777873,0.0561 -0.0553,-5.31581"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 113.16312,196.59109 -25.254853,0.0254"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 112.3375,195.90879 0.0455,5.42669 -23.777873,0.0561 -0.0553,-5.31581"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate180Mirror.svg b/Documentation/images/rotation/rotate180Mirror.svg
new file mode 100644
index 00000000..d4a77d50
--- /dev/null
+++ b/Documentation/images/rotation/rotate180Mirror.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate180Mirror.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.94272086"
+ inkscape:cx="467.79489"
+ inkscape:cy="423.24299"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="77.237228"
+ y="-140.22527"
+ transform="scale(1,-1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="92.612335"
+ y="-123.2944"
+ transform="scale(1,-1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="167.25098"
+ y="-123.2944"
+ transform="scale(1,-1)" />
+ <g
+ id="g4"
+ transform="matrix(1,0,0,-1,-0.98584226,222.20736)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 244.9594,140.44163 62.444808,140.23527 154.25637,193.55103 Z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 199.76749,188.83847 0.0285,-21.58135"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 215.59014,189.01815 0.0959,-31.3303"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 194.42833,189.018 25.2821,0.22061"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 195.19246,189.11102 -0.0701,5.37579 23.77787,0.0561 0.0553,-5.31581"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 194.20872,196.59109 25.25485,0.0254"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 195.03434,195.90879 -0.0455,5.42669 23.77787,0.0561 0.0553,-5.31581"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate270.svg b/Documentation/images/rotation/rotate270.svg
new file mode 100644
index 00000000..13ea1e5d
--- /dev/null
+++ b/Documentation/images/rotation/rotate270.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate270.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.94272086"
+ inkscape:cx="467.26451"
+ inkscape:cy="423.24299"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="-187.55237"
+ y="124.56432"
+ transform="rotate(-90)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="-172.17726"
+ y="141.49518"
+ transform="rotate(-90)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="-97.538612"
+ y="141.49518"
+ transform="rotate(-90)" />
+ <g
+ id="g4"
+ transform="rotate(-90,154.17883,111.5966)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 124.34796,19.830188 124.55432,202.34478 71.238559,110.53322 Z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.951119,65.022101 21.58135,-0.0285"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.771439,49.199448 31.330301,-0.0959"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 75.771589,70.361261 75.550979,45.079158"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.678569,69.597131 -5.37579,0.0701 -0.0561,-23.777873 5.31581,-0.0553"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 68.198499,70.580871 -0.0254,-25.254853"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 68.880799,69.755251 -5.42669,0.0455 -0.0561,-23.777873 5.31581,-0.0553"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate270Mirror.svg b/Documentation/images/rotation/rotate270Mirror.svg
new file mode 100644
index 00000000..6116f50a
--- /dev/null
+++ b/Documentation/images/rotation/rotate270Mirror.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate270Mirror.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.94272086"
+ inkscape:cx="467.79489"
+ inkscape:cy="423.24299"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="-187.55237"
+ y="-182.80751"
+ transform="matrix(0,-1,-1,0,0,0)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="-172.17726"
+ y="-165.87666"
+ transform="matrix(0,-1,-1,0,0,0)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="-97.538612"
+ y="-165.87666"
+ transform="matrix(0,-1,-1,0,0,0)" />
+ <g
+ id="g4"
+ transform="matrix(0,-1,-1,0,264.78961,265.77543)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 183.02388,19.830188 -0.20636,182.514592 53.31576,-91.81156 z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.42072,65.022101 -21.58135,-0.0285"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.6004,49.199448 -31.3303,-0.0959"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.60025,70.361261 0.22061,-25.282103"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.69327,69.597131 5.37579,0.0701 0.0561,-23.777873 -5.31581,-0.0553"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 239.17334,70.580871 0.0254,-25.254853"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 238.49104,69.755251 5.42669,0.0455 0.0561,-23.777873 -5.31581,-0.0553"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate90.svg b/Documentation/images/rotation/rotate90.svg
new file mode 100644
index 00000000..af627638
--- /dev/null
+++ b/Documentation/images/rotation/rotate90.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate90.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.94272086"
+ inkscape:cx="467.26451"
+ inkscape:cy="423.24299"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="34.65498"
+ y="-182.80751"
+ transform="rotate(90)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="50.030079"
+ y="-165.87665"
+ transform="rotate(90)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="124.66872"
+ y="-165.87665"
+ transform="rotate(90)" />
+ <g
+ id="g4"
+ transform="rotate(90,154.17885,110.61076)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 183.02388,202.37715 -0.20636,-182.51459 53.31576,91.81156 z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.42072,157.18524 -21.58135,0.0285"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.6004,173.00789 -31.3303,0.0959"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.60025,151.84608 0.22061,25.2821"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231.69327,152.61021 5.37579,-0.0701 0.0561,23.77787 -5.31581,0.0553"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 239.17334,151.62647 0.0254,25.25485"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 238.49104,152.45209 5.42669,-0.0455 0.0561,23.77787 -5.31581,0.0553"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/images/rotation/rotate90Mirror.svg b/Documentation/images/rotation/rotate90Mirror.svg
new file mode 100644
index 00000000..1760c462
--- /dev/null
+++ b/Documentation/images/rotation/rotate90Mirror.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="rotate90Mirror.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="false"
+ inkscape:zoom="0.94272086"
+ inkscape:cx="467.79489"
+ inkscape:cy="423.24299"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect1"
+ width="152.88184"
+ height="119.41136"
+ x="34.65498"
+ y="124.56432"
+ transform="matrix(0,1,1,0,0,0)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2"
+ width="49.755535"
+ height="36.468258"
+ x="50.030079"
+ y="141.49519"
+ transform="matrix(0,1,1,0,0,0)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect2-5"
+ width="49.755535"
+ height="36.468258"
+ x="124.66872"
+ y="141.49519"
+ transform="matrix(0,1,1,0,0,0)" />
+ <g
+ id="g4"
+ transform="matrix(0,1,1,0,42.582224,-43.56809)"
+ style="stroke-width:1.5875;stroke-dasharray:none">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="rect3"
+ width="40.994682"
+ height="43.605846"
+ x="134.16664"
+ y="157.24184" />
+ <ellipse
+ style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path3"
+ cx="140.15703"
+ cy="176.44627"
+ rx="1.889045"
+ ry="1.925626" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="M 124.34795,202.37715 124.55431,19.86256 71.238554,111.67412 Z"
+ id="path4"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.951114,157.18524 21.58135,0.0285"
+ id="path5" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.771434,173.00789 31.330296,0.0959"
+ id="path6" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.771584,151.84608 -0.22061,25.2821"
+ id="path7" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 75.678564,152.61021 -5.37579,-0.0701 -0.0561,23.77787 5.31581,0.0553"
+ id="path8" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 68.198494,151.62647 -0.0254,25.25485"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ d="m 68.880794,152.45209 -5.42669,-0.0455 -0.0561,23.77787 5.31581,0.0553"
+ id="path8-9"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 43d8b017..5442ae75 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -23,3 +23,5 @@
Sensor driver requirements <sensor_driver_requirements>
Lens driver requirements <lens_driver_requirements>
Python Bindings <python-bindings>
+ Camera Sensor Model <camera-sensor-model>
+ SoftwareISP Benchmarking <software-isp-benchmarking>
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 7695bcb1..3872e0a8 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -14,6 +14,14 @@ if doxygen.found() and dot.found()
cdata.set('VERSION', 'v@0@'.format(libcamera_git_version))
cdata.set('TOP_SRCDIR', meson.project_source_root())
cdata.set('TOP_BUILDDIR', meson.project_build_root())
+ cdata.set('OUTPUT_DIR', meson.current_build_dir())
+
+ doxygen_predefined = []
+ foreach key : config_h.keys()
+ doxygen_predefined += '@0@=@1@'.format(key, config_h.get(key))
+ endforeach
+
+ cdata.set('PREDEFINED', ' \\\n\t\t\t '.join(doxygen_predefined))
doxyfile = configure_file(input : 'Doxyfile.in',
output : 'Doxyfile',
@@ -41,7 +49,8 @@ if doxygen.found() and dot.found()
output : 'api-html',
command : [doxygen, doxyfile],
install : true,
- install_dir : doc_install_dir)
+ install_dir : doc_install_dir,
+ install_tag : 'doc')
endif
#
@@ -55,6 +64,8 @@ endif
if sphinx.found()
docs_sources = [
+ 'camera-sensor-model.rst',
+ 'code-of-conduct.rst',
'coding-style.rst',
'conf.py',
'contributing.rst',
@@ -69,6 +80,7 @@ if sphinx.found()
'lens_driver_requirements.rst',
'python-bindings.rst',
'sensor_driver_requirements.rst',
+ 'software-isp-benchmarking.rst',
'../README.rst',
]
@@ -81,11 +93,12 @@ if sphinx.found()
output : 'html',
build_by_default : true,
install : true,
- install_dir : doc_install_dir)
+ install_dir : doc_install_dir,
+ install_tag : 'doc')
custom_target('documentation-linkcheck',
- command: [sphinx, '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
- build_always_stale: true,
- input: docs_sources,
- output: 'linkcheck')
+ command : [sphinx, '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'],
+ build_always_stale : true,
+ input : docs_sources,
+ output : 'linkcheck')
endif
diff --git a/Documentation/python-bindings.rst b/Documentation/python-bindings.rst
index bac5cd34..ed9f686b 100644
--- a/Documentation/python-bindings.rst
+++ b/Documentation/python-bindings.rst
@@ -17,13 +17,13 @@ chapter lists the differences.
Mostly these differences fall under two categories:
1. Differences caused by the inherent differences between C++ and Python.
-These differences are usually caused by the use of threads or differences in
-C++ vs Python memory management.
+ These differences are usually caused by the use of threads or differences in
+ C++ vs Python memory management.
2. Differences caused by the code being work-in-progress. It's not always
-trivial to create a binding in a satisfying way, and the current bindings
-contain simplified versions of the C++ API just to get forward. These
-differences are expected to eventually go away.
+ trivial to create a binding in a satisfying way, and the current bindings
+ contain simplified versions of the C++ API just to get forward. These
+ differences are expected to eventually go away.
Coding Style
------------
diff --git a/Documentation/sensor_driver_requirements.rst b/Documentation/sensor_driver_requirements.rst
index b0854be3..0e516b34 100644
--- a/Documentation/sensor_driver_requirements.rst
+++ b/Documentation/sensor_driver_requirements.rst
@@ -24,16 +24,23 @@ The sensor driver is assumed to be fully compliant with the V4L2 specification.
For RAW sensors, the sensor driver shall support the following V4L2 controls:
+* `V4L2_CID_ANALOGUE_GAIN`_
* `V4L2_CID_EXPOSURE`_
* `V4L2_CID_HBLANK`_
* `V4L2_CID_PIXEL_RATE`_
* `V4L2_CID_VBLANK`_
+.. _V4L2_CID_ANALOGUE_GAIN: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/ext-ctrls-image-source.html
.. _V4L2_CID_EXPOSURE: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
.. _V4L2_CID_HBLANK: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/ext-ctrls-image-source.html
.. _V4L2_CID_PIXEL_RATE: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/ext-ctrls-image-process.html
.. _V4L2_CID_VBLANK: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/ext-ctrls-image-source.html
+The ``ANALOGUE_GAIN`` control units are sensor-specific. libcamera requires
+a sensor-specific CameraSensorHelper implementation to translate between the
+sensor specific ``gain code`` and the analogue ``gain value`` expressed as an
+absolute number as defined by ``controls::AnalogueGain``.
+
While V4L2 doesn't specify a unit for the ``EXPOSURE`` control, libcamera
requires it to be expressed as a number of image lines. Camera sensor drivers
that do not comply with this requirement will need to be adapted or will produce
@@ -55,6 +62,18 @@ The sensor driver should support the following V4L2 controls:
The controls are used to register the camera location and rotation.
+In order to support rotating the image the sensor driver should support
+
+* `V4L2_CID_HFLIP`_
+* `V4L2_CID_VFLIP`_
+
+.. _V4L2_CID_HFLIP: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+.. _V4L2_CID_VFLIP: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+
+The controls must be writable from userspace. In case of a RAW Bayer sensors,
+drivers should correctly report if vertical/horizontal flips modify the Bayer
+pattern ordering by reporting the `V4L2_CTRL_FLAG_MODIFY_LAYOUT` control flag.
+
The sensor driver should implement support for the V4L2 Selection API,
specifically it should implement support for the
`VIDIOC_SUBDEV_G_SELECTION`_ ioctl with support for the following selection
diff --git a/Documentation/sensor_model.svg b/Documentation/sensor_model.svg
new file mode 100644
index 00000000..02dc55a8
--- /dev/null
+++ b/Documentation/sensor_model.svg
@@ -0,0 +1,4870 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="sensor_model.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ inkscape:zoom="0.55815145"
+ inkscape:cx="535.6969"
+ inkscape:cy="472.9899"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1"
+ showguides="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid184"
+ originx="0"
+ originy="0"
+ spacingy="1"
+ spacingx="1"
+ units="mm"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2">
+ <marker
+ style="overflow:visible"
+ id="marker9374"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="TriangleStart"
+ markerWidth="5.32440803"
+ markerHeight="6.15538502"
+ viewBox="0 0 5.3244081 6.1553851"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ id="path9372" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="TriangleStart"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="TriangleStart"
+ markerWidth="5.3244081"
+ markerHeight="6.155385"
+ viewBox="0 0 5.3244081 6.1553851"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ id="path135" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker9374-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="TriangleStart"
+ markerWidth="5.3244081"
+ markerHeight="6.155385"
+ viewBox="0 0 5.3244081 6.1553851"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ id="path9372-6" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="TriangleStart-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="TriangleStart"
+ markerWidth="5.3244081"
+ markerHeight="6.155385"
+ viewBox="0 0 5.3244081 6.1553851"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ id="path135-6" />
+ </marker>
+ </defs>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g1052"
+ transform="translate(23.655977)"
+ style="opacity:0.5"
+ inkscape:export-filename="Documentation/camera-sensor-model_digiscale.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2"
+ transform="translate(13.062138,0.0898069)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0"
+ transform="translate(44.963283,-0.03138127)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26"
+ transform="translate(34.280458,-0.02885923)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20"
+ transform="translate(66.243325,-0.12103306)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28"
+ transform="translate(55.53615,-0.0663742)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9"
+ transform="translate(76.887707,-0.13032417)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-206"
+ transform="translate(2.5889069,0.05636587)"
+ style="opacity:0.502088">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-15"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-47"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-65"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-6"
+ transform="translate(23.672688,10.516298)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-4"
+ transform="translate(13.078849,10.606105)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-0"
+ transform="translate(44.979994,10.484917)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-8"
+ transform="translate(34.297169,10.487439)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-9"
+ transform="translate(66.260036,10.395265)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9"
+ transform="translate(55.552861,10.449924)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-9"
+ transform="translate(76.904418,10.385974)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-206-7"
+ transform="translate(2.6056179,10.572664)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-15-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-5-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-47-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-65-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-22"
+ transform="translate(23.70412,21.058784)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-5"
+ transform="translate(13.110281,21.148591)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-1"
+ transform="translate(45.011426,21.027403)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-77"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-9"
+ transform="translate(34.328601,21.029925)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-3"
+ transform="translate(66.291468,20.937751)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-63"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-4"
+ transform="translate(55.584293,20.99241)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-3"
+ transform="translate(76.93585,20.92846)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-08"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-206-0"
+ transform="translate(2.6370499,21.11515)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-15-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-47-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-65-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-5"
+ transform="translate(23.740609,31.433065)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-61"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-15"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-48"
+ transform="translate(13.14677,31.522872)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-4"
+ transform="translate(45.047915,31.401684)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-6"
+ transform="translate(34.36509,31.404206)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-96"
+ transform="translate(66.327957,31.312032)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-21"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-7"
+ transform="translate(55.620782,31.366691)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-18"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-97"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-5"
+ transform="translate(76.972339,31.302741)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-83"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-206-8"
+ transform="translate(2.6735389,31.489431)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-15-96"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-47-33"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-65-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-8"
+ transform="translate(23.725354,41.251089)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-60"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-48"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-89"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-7"
+ transform="translate(13.131515,41.340896)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-76"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-43"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-03"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-09"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-2"
+ transform="translate(45.03266,41.219708)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-40"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-4"
+ transform="translate(34.349835,41.22223)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-4"
+ transform="translate(66.312702,41.130056)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-75"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-81"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-2"
+ transform="translate(55.605527,41.184715)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-89"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-68"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-2"
+ transform="translate(76.957084,41.120765)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-206-85"
+ transform="translate(2.5889069,41.307455)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-15-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-5-64"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-47-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-65-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-8-5"
+ transform="translate(23.730747,51.649149)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-60-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-48-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-8-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-89-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-7-4"
+ transform="translate(13.136908,51.738956)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-76-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-43-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-03-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-09-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-2-6"
+ transform="translate(45.038053,51.617768)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-5-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-40-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-5-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-9-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-4-8"
+ transform="translate(34.355228,51.62029)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-6-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-9-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-2-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-2-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-4-0"
+ transform="translate(66.318095,51.528116)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-7-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-75-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-4-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-81-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-2-9"
+ transform="translate(55.61092,51.582775)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-89-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-3-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-68-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-0-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:0.5;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="129.98665"
+ y="78.113022" />
+ <rect
+ style="opacity:0.5;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="135.20201"
+ y="78.074852" />
+ <rect
+ style="opacity:0.5;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="129.99101"
+ y="83.377647" />
+ <g
+ id="g1052-9-4"
+ transform="translate(87.507934,-0.18332859)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-9-5"
+ transform="translate(87.524645,10.332969)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-3-2"
+ transform="translate(87.556077,20.875455)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-9-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-08-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-5-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-5-6"
+ transform="translate(87.592566,31.249736)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-3-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-8-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-83-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-1-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-2-1"
+ transform="translate(87.577311,41.06776)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-32"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:0.5;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-0-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="140.61124"
+ y="83.324638" />
+ <rect
+ style="opacity:0.5;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="135.21388"
+ y="83.3274" />
+ <rect
+ style="opacity:0.5;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-3-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="140.60687"
+ y="77.967453" />
+ <g
+ id="g1052-9-9-7-22"
+ transform="translate(97.600308,0.08245514)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-4-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-99-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-4-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-5-41"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-3-9-6"
+ transform="translate(97.686888,10.559974)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-9-4-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-08-22-78"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-8-6-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-5-4-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-5-3-27"
+ transform="translate(97.723368,20.934255)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-3-3-36"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-8-7-47"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-83-3-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-1-2-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-2-8-2"
+ transform="translate(97.708118,30.752279)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-9-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-15-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-4-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-92-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:0.5;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-8-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="145.82224"
+ y="77.929283" />
+ <rect
+ style="opacity:0.5;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-5-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="145.83411"
+ y="83.181831" />
+ <g
+ id="g1052-9-2-1-0"
+ transform="translate(97.884193,41.434681)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-32-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-1-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-1-3-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.074577"
+ y="37.001465" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-9-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:0.5;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-0-9-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="150.91812"
+ y="83.691559" />
+ <rect
+ style="opacity:0.5;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-3-1-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="150.91376"
+ y="78.334373" />
+ <rect
+ style="opacity:0.5;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-5-9-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="156.14098"
+ y="83.548752" />
+ <g
+ id="g1052-206-85-6"
+ transform="translate(2.5942999,51.705515)"
+ style="opacity:0.5">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-15-0-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-5-64-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-47-6-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-65-2-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:0.195644;fill:#000000;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect2827"
+ width="7.4583311"
+ height="61.587906"
+ x="47.724121"
+ y="26.798815" />
+ <rect
+ style="opacity:0.195644;fill:#000000;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect2829"
+ width="7.3512397"
+ height="61.724613"
+ x="161.43401"
+ y="26.690187" />
+ <rect
+ style="opacity:0.195644;fill:#000000;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect2831"
+ width="121.2337"
+ height="22.824373"
+ x="47.633518"
+ y="88.566978" />
+ <rect
+ style="opacity:0.195644;fill:#000000;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect2833"
+ width="121.20582"
+ height="8.0138454"
+ x="47.77354"
+ y="18.34436" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.8;stroke-dasharray:3.2, 0.8;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill"
+ id="rect3461"
+ width="84.249771"
+ height="41.517296"
+ x="66.555992"
+ y="36.749054" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.73, 0.865;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 149.57761,77.267496 301.74652,88.379163"
+ id="path4533-3-7"
+ sodipodi:nodetypes="cc" />
+ <g
+ id="g4698"
+ transform="translate(55.540721,9.7505553)">
+ <g
+ id="g1052-9-9-7"
+ transform="translate(174.2039,11.30255)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-99"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-6-1"
+ transform="translate(121.02732,11.367907)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-4-7"
+ transform="translate(110.43348,11.457714)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-0-4"
+ transform="translate(142.33462,11.336526)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-7-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-6-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-8-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-8-0"
+ transform="translate(131.6518,11.339048)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-3-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-1-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-4-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-9-0"
+ transform="translate(163.61466,11.246874)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-2-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-0-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-6-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9-0"
+ transform="translate(152.90749,11.301533)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-2-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-6-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-6-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-4-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-22-0"
+ transform="translate(121.05875,21.910393)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-5-9"
+ transform="translate(110.46491,22.0002)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-9-0"
+ transform="translate(131.68323,21.881534)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-7-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-7-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-6-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-7-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-1-4"
+ transform="translate(142.36605,21.879012)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-77-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-1-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-1-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-3-4"
+ transform="translate(163.64609,21.78936)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-6-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-5-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-63-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-9-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-4-4"
+ transform="translate(152.93892,21.844019)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-8-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-1-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-2-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-9-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-3-9"
+ transform="translate(174.29048,21.780069)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-9-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-08-22"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-8-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-5-1"
+ transform="translate(121.09524,32.284674)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-61-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-15-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-9-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-48-2"
+ transform="translate(110.5014,32.374481)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-1-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-0-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-3-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-0-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-4-8"
+ transform="translate(142.40254,32.253293)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-4-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-7-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-6-3"
+ transform="translate(131.71972,32.255815)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-3-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-1-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-7-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-5-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-96-6"
+ transform="translate(163.68258,32.163641)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-21-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-7-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-8-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-7-8"
+ transform="translate(152.97541,32.2183)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-18-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-5-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-97-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-5-3"
+ transform="translate(174.32696,32.15435)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-3-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-83-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-1-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-8-6"
+ transform="translate(121.07998,42.102698)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-60-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-48-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-8-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-89-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-7-8"
+ transform="translate(110.48614,42.192505)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-76-79"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-43-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-03-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-09-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-2-1"
+ transform="translate(142.38729,42.071317)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-5-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-40-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-5-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-9-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-4-0"
+ transform="translate(131.70446,42.073839)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-6-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-9-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-2-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-2-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-4-7"
+ transform="translate(163.66733,41.981665)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-7-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-75-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-4-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-81-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-2-8"
+ transform="translate(152.96015,42.036324)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-89-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-3-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-68-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-0-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-2-8"
+ transform="translate(174.31171,41.972374)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-15"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-92"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-9-7-5"
+ transform="translate(184.38619,11.471979)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-4-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-99-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-4-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-5-40"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-3-9-1"
+ transform="translate(184.47277,21.949498)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-9-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-08-22-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-8-6-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-5-4-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-5-3-1"
+ transform="translate(184.50925,32.323779)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-3-3-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-8-7-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-83-3-32"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-1-2-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-2-8-7"
+ transform="translate(184.494,42.141803)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-9-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-15-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-4-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-92-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ </g>
+ <g
+ id="g4698-2"
+ transform="translate(103.99571,147.10736)">
+ <g
+ id="g1052-9-9-7-2"
+ transform="translate(126.23318,-40.652773)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-99-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-6-1-6"
+ transform="translate(72.684847,-40.246111)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-4-7-44"
+ transform="translate(62.091007,-40.156304)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-0-4-5"
+ transform="translate(93.992147,-40.277492)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-7-2-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-8-9-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-6-6-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-8-1-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-8-0-4"
+ transform="translate(83.309327,-40.27497)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-4-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-3-2-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-1-2-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-4-2-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-9-0-1"
+ transform="translate(115.27219,-40.367144)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-2-5-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-0-5-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-6-2-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-8-9-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9-0-5"
+ transform="translate(104.56502,-40.312485)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-2-2-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-6-8-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-6-3-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-4-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-22-0-6"
+ transform="translate(72.716277,-29.703625)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-5-9-0"
+ transform="translate(62.122437,-29.613818)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-9-0-5"
+ transform="translate(83.340757,-29.732484)"
+ style="opacity:0.252344">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-7-5-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-7-0-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-6-2-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-7-9-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-1-4-5"
+ transform="translate(94.023577,-29.735006)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-77-9-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-1-9-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-1-3-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-5-6-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-3-4-6"
+ transform="translate(115.30362,-29.824658)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-6-3-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-5-5-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-63-1-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-9-7-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-4-4-7"
+ transform="translate(104.59645,-29.769999)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-8-3-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-1-1-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-2-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-9-6-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-5-1-0"
+ transform="translate(72.752767,-19.329344)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-61-2-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-15-8-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-9-8-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-8-9-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-48-2-6"
+ transform="translate(62.158927,-19.239537)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-1-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-0-8-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-3-8-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-0-6-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-4-8-2"
+ transform="translate(94.060067,-19.360725)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-4-3-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-4-8-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-4-3-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-7-3-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-6-3-2"
+ transform="translate(83.377247,-19.358203)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-3-8-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-1-0-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-7-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-5-7-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-96-6-0"
+ transform="translate(115.34011,-19.450377)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-21-8-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-7-9-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-8-0-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-5-6-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-7-8-2"
+ transform="translate(104.63294,-19.395718)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-4-7-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-18-9-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-5-0-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-97-3-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-5-3-2"
+ transform="translate(125.98449,-19.459668)">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-3-3-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-8-7-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-83-3-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-1-2-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-8-6-6"
+ transform="translate(72.737507,-9.5113204)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-60-5-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-48-2-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-8-6-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-89-5-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-7-8-2"
+ transform="translate(62.143667,-9.4215134)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-76-79-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-43-6-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-03-0-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-09-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-0-2-1-4"
+ transform="translate(94.044817,-9.5427014)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-93-5-0-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-6-40-4-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-0-5-8-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-6-9-7-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-4-0-1"
+ transform="translate(83.361987,-9.5401794)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-6-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-9-6-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-2-2-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-2-4-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-4-7-6"
+ transform="translate(115.32486,-9.6323534)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-7-9-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-75-3-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-4-9-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-81-2-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-9-0-1-2"
+ transform="translate(136.83954,-40.658221)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-2-5-3-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-0-5-3-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-6-2-3-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-8-9-8-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-3-4-6-0"
+ transform="translate(136.87097,-30.115735)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-6-3-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-5-5-9-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-63-1-9-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-9-7-0-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-3-4-6-0-7"
+ transform="translate(126.24242,-30.173373)"
+ style="opacity:0.252344">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-6-3-5-6-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-5-5-9-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-63-1-9-9-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-9-7-0-9-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-96-6-0-0"
+ transform="translate(136.90746,-19.741454)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-21-8-0-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-7-9-3-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-8-0-4-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-5-6-9-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-20-4-7-6-4"
+ transform="translate(136.89221,-9.9234312)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-2-7-9-9-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-3-75-3-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-75-4-9-1-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-92-81-2-3-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-2-8-3"
+ transform="translate(104.61768,-9.5776944)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-89-3-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-3-0-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-68-1-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-0-7-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-2-8-6"
+ transform="translate(125.96924,-9.6416444)"
+ style="opacity:0.15">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-1-9-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-05-15-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-1-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-10-92-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693"
+ width="21.177956"
+ height="20.838879"
+ x="115.31686"
+ y="-14.213038" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-4"
+ width="21.177956"
+ height="20.838879"
+ x="115.17234"
+ y="6.6754951" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-8"
+ width="21.177956"
+ height="20.838879"
+ x="136.78856"
+ y="-14.086202" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-1"
+ width="21.177956"
+ height="20.838879"
+ x="136.99756"
+ y="6.4101439" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-6"
+ width="21.177956"
+ height="20.838879"
+ x="158.1272"
+ y="-14.220315" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-85"
+ width="21.177956"
+ height="20.838879"
+ x="158.17397"
+ y="6.7189174" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-6-2"
+ width="21.177956"
+ height="20.838879"
+ x="179.34555"
+ y="-14.454299" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#040000;stroke-width:0.865;stroke-dasharray:3.46, 0.865;stroke-dashoffset:0;paint-order:markers stroke fill"
+ id="rect6693-85-1"
+ width="21.177956"
+ height="20.838879"
+ x="179.64828"
+ y="6.1961193" />
+ </g>
+ <path
+ style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.73, 0.865;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 66.659356,36.818342 218.82541,47.962052"
+ id="path4533"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.73, 0.865;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 150.73309,36.200165 302.902,47.311832"
+ id="path4533-3"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.73, 0.865;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 67.986904,77.950336 220.15295,89.094046"
+ id="path4533-7"
+ sodipodi:nodetypes="cc" />
+ <g
+ id="g1">
+ <g
+ id="g1052-9-9-7-0-7"
+ transform="translate(108.68336,116.60508)"
+ style="opacity:0.300974">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-4-8-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-99-1-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-4-9-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-5-9-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-4-7-4-0"
+ transform="translate(77.180174,116.39954)"
+ style="opacity:0.300974">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-9-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-0-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-2-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-0-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-8-0-2-1"
+ transform="translate(87.639831,116.58827)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-4-4-0-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-3-2-7-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-1-2-3-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-4-2-1-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9-0-7-9"
+ transform="translate(98.042911,116.4909)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-2-2-7-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-6-8-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-6-3-0-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-4-8-6-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-9-9-7-0-7-6"
+ transform="translate(108.66488,126.92391)"
+ style="opacity:0.300974">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-31-5-4-8-1-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-9-0-99-1-1-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-4-4-4-9-1-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-78-8-5-9-7-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-2-4-7-4-0-9"
+ transform="translate(77.161698,126.71837)"
+ style="opacity:0.300974">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-9-4-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-0-0-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-2-8-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-0-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-8-0-2-1-1"
+ transform="translate(87.741912,126.78654)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-1-4-4-0-6-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-8-3-2-7-6-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-7-1-2-3-2-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-9-4-2-1-1-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9-0-7-9-8"
+ transform="translate(98.024435,126.80973)"
+ style="opacity:1">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-97-2-2-7-6-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-36-6-8-4-4-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-1-6-3-0-8-5"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-2-4-8-6-0-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.8;stroke-dasharray:3.2, 0.8;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill"
+ id="rect7388"
+ width="20.576803"
+ height="20.307524"
+ x="140.76538"
+ y="143.06564" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#TriangleStart)"
+ d="m 261.72591,93.236032 0.16234,31.497498"
+ id="path9137"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#TriangleStart-3)"
+ d="m 67.685641,168.64157 0.16234,16.82752"
+ id="path9137-7"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker9374)"
+ d="m 212.12608,153.9478 c -0.39202,0.18905 -3.47785,0.26026 -7.09894,0.25872 -8.60075,-0.004 -21.95111,-0.0141 -21.95111,-0.0141"
+ id="path9370"
+ sodipodi:nodetypes="csc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="85.59259"
+ y="13.192001"
+ id="text9717"><tspan
+ sodipodi:role="line"
+ id="tspan9715"
+ style="stroke-width:0.264583px"
+ x="85.59259"
+ y="13.192001">Pixel array</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="230.03058"
+ y="35.165924"
+ id="text9717-7"><tspan
+ sodipodi:role="line"
+ id="tspan9715-6"
+ style="stroke-width:0.264583px"
+ x="230.03058"
+ y="35.165924">Analog crop</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="231.88487"
+ y="190.28648"
+ id="text9717-7-9"><tspan
+ sodipodi:role="line"
+ id="tspan9715-6-1"
+ style="stroke-width:0.264583px"
+ x="231.88487"
+ y="190.28648">Subsampling</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="125.35484"
+ y="177.69405"
+ id="text9717-7-9-1"><tspan
+ sodipodi:role="line"
+ id="tspan9715-6-1-9"
+ style="stroke-width:0.264583px"
+ x="125.35484"
+ y="177.69405">Digital crop</tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.264583px"
+ x="125.35484"
+ y="190.92317"
+ id="tspan9850" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="43.724319"
+ y="202.93666"
+ id="text9717-7-9-1-5"><tspan
+ sodipodi:role="line"
+ id="tspan9715-6-1-9-3"
+ style="stroke-width:0.264583px"
+ x="43.724319"
+ y="202.93666">Media Bus</tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.264583px"
+ x="43.724319"
+ y="216.16579"
+ id="tspan9850-5" /></text>
+ <g
+ id="g2132"
+ transform="matrix(1.3821489,0,0,1.4039825,12.968023,-62.501729)"
+ style="stroke-width:0.717863">
+ <g
+ id="g1052-9-9-7-0-7-7"
+ transform="translate(-3.2072982,115.91871)"
+ style="opacity:0.300974;stroke-width:0.717863" />
+ <g
+ id="g1052-26-8-0-2-1-3"
+ transform="matrix(0.67497349,0,0,0.67524564,-2.9671558,128.89065)"
+ style="stroke-width:1.06333">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-1-4-4-0-6-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-5-8-3-2-7-6-0"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-3-7-1-2-3-2-62"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-56-9-4-2-1-1-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9-0-7-9-1"
+ transform="matrix(0.67497349,0,0,0.67524564,4.0546474,128.8249)"
+ style="stroke-width:1.06333">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-97-2-2-7-6-8"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-5-36-6-8-4-4-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-3-1-6-3-0-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-56-2-4-8-6-0-20"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-26-8-0-2-1-1-3"
+ transform="matrix(0.67497349,0,0,0.67524564,-2.8982539,135.77698)"
+ style="stroke-width:1.06333">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-1-4-4-0-6-5-6"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-5-8-3-2-7-6-5-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-3-7-1-2-3-2-4-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-56-9-4-2-1-1-9-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <g
+ id="g1052-28-9-0-7-9-8-3"
+ transform="matrix(0.67497349,0,0,0.67524564,4.0421766,135.79264)"
+ style="stroke-width:1.06333">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-97-2-2-7-6-3-1"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-5-36-6-8-4-4-8-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-3-1-6-3-0-8-5-4"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.393875;paint-order:markers stroke fill"
+ id="rect288-56-2-4-8-6-0-2-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker9374-3)"
+ d="m 123.82202,153.26143 c -0.39202,0.18905 -3.47785,0.26026 -7.09894,0.25872 -8.60075,-0.004 -21.951106,-0.0141 -21.951106,-0.0141"
+ id="path9370-4"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 42.161302,205.87857 50.15057,-0.10808"
+ id="path1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 43.410067,193.21441 50.15057,-0.10808"
+ id="path1-6" />
+ <ellipse
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path2"
+ cx="263.63913"
+ cy="17.331734"
+ rx="8.0464878"
+ ry="7.6880746" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="261.26318"
+ y="21.157597"
+ id="text2"><tspan
+ sodipodi:role="line"
+ id="tspan2"
+ style="stroke-width:0.264583px"
+ x="261.26318"
+ y="21.157597">1</tspan></text>
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path2-7"
+ cx="264.95981"
+ cy="200.2849"
+ rx="8.0464878"
+ ry="7.6880746" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="262.07587"
+ y="204.11606"
+ id="text2-0"><tspan
+ sodipodi:role="line"
+ id="tspan2-9"
+ style="stroke-width:0.264583px"
+ x="262.07587"
+ y="204.11606">2</tspan></text>
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path2-6"
+ cx="44.883369"
+ cy="177.41577"
+ rx="8.0464878"
+ ry="7.6880746" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="41.983547"
+ y="181.24164"
+ id="text2-2"><tspan
+ sodipodi:role="line"
+ id="tspan2-6"
+ style="stroke-width:0.264583px"
+ x="41.983547"
+ y="181.24164">4</tspan></text>
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;paint-order:markers stroke fill"
+ id="path2-3"
+ cx="153.0576"
+ cy="191.86786"
+ rx="8.0464878"
+ ry="7.6880746" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="150.21071"
+ y="195.57201"
+ id="text2-6"><tspan
+ sodipodi:role="line"
+ id="tspan2-0"
+ style="stroke-width:0.264583px"
+ x="150.21071"
+ y="195.57201">3</tspan></text>
+ </g>
+</svg>
diff --git a/Documentation/skipping.svg b/Documentation/skipping.svg
new file mode 100644
index 00000000..7bef37cf
--- /dev/null
+++ b/Documentation/skipping.svg
@@ -0,0 +1,1720 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="297mm"
+ height="210mm"
+ viewBox="0 0 297 210"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="skipping.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ showguides="true"
+ inkscape:zoom="0.91421356"
+ inkscape:cx="601.60998"
+ inkscape:cy="360.96599"
+ inkscape:window-width="1916"
+ inkscape:window-height="1040"
+ inkscape:window-x="0"
+ inkscape:window-y="38"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g2">
+ <inkscape:grid
+ id="grid1"
+ units="px"
+ originx="0"
+ originy="0"
+ spacingx="0.26458334"
+ spacingy="0.26458333"
+ empcolor="#0000ff"
+ empopacity="0.25098039"
+ color="#0000ff"
+ opacity="0.1254902"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1">
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-0" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-3"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-9" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-9"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-7" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-8"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-0-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-3-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-9-3" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-6-6"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-4-2" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="ArrowWideRounded-1-8-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Wide, rounded arrow"
+ markerWidth="1"
+ markerHeight="1"
+ viewBox="0 0 1 1"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ style="fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:round"
+ d="M 3,-3 0,0 3,3"
+ transform="rotate(180,0.125,0)"
+ sodipodi:nodetypes="ccc"
+ id="path4-0-3-0" />
+ </marker>
+ </defs>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g4698-2"
+ transform="translate(-26.194788,73.07432)">
+ <g
+ id="g1" />
+ <g
+ id="g2">
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3"
+ width="11.54506"
+ height="11.262992"
+ x="126.11332"
+ y="-33.444302" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6"
+ width="11.54506"
+ height="11.262992"
+ x="137.8185"
+ y="-33.530048" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3"
+ width="11.54506"
+ height="11.262992"
+ x="126.12312"
+ y="-21.618387" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3"
+ width="11.54506"
+ height="11.262992"
+ x="137.84511"
+ y="-21.731256" />
+ <g
+ id="g1052-2-4-7-44"
+ transform="matrix(2.2443659,0,0,2.2462986,-16.668792,-92.981079)"
+ style="stroke-width:0.445369">
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.164972;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.024174"
+ y="26.5942" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.164972;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.23954"
+ y="26.556028" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.164972;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7"
+ width="5.1440187"
+ height="5.0140224"
+ x="53.028545"
+ y="31.858822" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.164972;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2"
+ width="5.1440187"
+ height="5.0140224"
+ x="58.251408"
+ y="31.808577" />
+ </g>
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3"
+ width="11.54506"
+ height="11.262992"
+ x="126.18385"
+ y="-9.7627277" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5"
+ width="11.54506"
+ height="11.262992"
+ x="137.88902"
+ y="-9.8484716" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0"
+ width="11.54506"
+ height="11.262992"
+ x="126.19365"
+ y="2.0631855" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8"
+ width="11.54506"
+ height="11.262992"
+ x="137.91565"
+ y="1.9503218" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4"
+ width="11.54506"
+ height="11.262992"
+ x="102.40739"
+ y="-9.5609922" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1"
+ width="11.54506"
+ height="11.262992"
+ x="114.11258"
+ y="-9.6467371" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1"
+ width="11.54506"
+ height="11.262992"
+ x="102.41722"
+ y="2.2649202" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3"
+ width="11.54506"
+ height="11.262992"
+ x="114.13924"
+ y="2.1520538" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0"
+ width="11.54506"
+ height="11.262992"
+ x="173.61343"
+ y="-33.721619" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9"
+ width="11.54506"
+ height="11.262992"
+ x="185.3186"
+ y="-33.807365" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0"
+ width="11.54506"
+ height="11.262992"
+ x="173.62323"
+ y="-21.895706" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7"
+ width="11.54506"
+ height="11.262992"
+ x="185.34526"
+ y="-22.00857" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6"
+ width="11.54506"
+ height="11.262992"
+ x="149.83696"
+ y="-33.519886" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8"
+ width="11.54506"
+ height="11.262992"
+ x="161.54218"
+ y="-33.605633" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7"
+ width="11.54506"
+ height="11.262992"
+ x="149.8468"
+ y="-21.693974" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7"
+ width="11.54506"
+ height="11.262992"
+ x="161.5688"
+ y="-21.806837" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-4"
+ width="11.54506"
+ height="11.262992"
+ x="125.8679"
+ y="14.089965" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-0"
+ width="11.54506"
+ height="11.262992"
+ x="137.57307"
+ y="14.004221" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-1"
+ width="11.54506"
+ height="11.262992"
+ x="125.8777"
+ y="25.91588" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-2"
+ width="11.54506"
+ height="11.262992"
+ x="137.59967"
+ y="25.803013" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-0"
+ width="11.54506"
+ height="11.262992"
+ x="102.38698"
+ y="14.184427" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-1"
+ width="11.54506"
+ height="11.262992"
+ x="114.09217"
+ y="14.098681" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-5"
+ width="11.54506"
+ height="11.262992"
+ x="102.39677"
+ y="26.010342" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-2"
+ width="11.54506"
+ height="11.262992"
+ x="114.1188"
+ y="25.897474" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-0"
+ width="11.54506"
+ height="11.262992"
+ x="173.368"
+ y="13.812649" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-7"
+ width="11.54506"
+ height="11.262992"
+ x="185.07317"
+ y="13.726904" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-1"
+ width="11.54506"
+ height="11.262992"
+ x="173.37778"
+ y="25.638563" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-6"
+ width="11.54506"
+ height="11.262992"
+ x="185.09981"
+ y="25.525694" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-4"
+ width="11.54506"
+ height="11.262992"
+ x="149.59154"
+ y="14.014381" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9"
+ width="11.54506"
+ height="11.262992"
+ x="161.29671"
+ y="13.928636" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-5"
+ width="11.54506"
+ height="11.262992"
+ x="149.60135"
+ y="25.840294" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3"
+ width="11.54506"
+ height="11.262992"
+ x="161.32336"
+ y="25.727428" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-6"
+ width="11.54506"
+ height="11.262992"
+ x="173.68398"
+ y="-10.040043" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5"
+ width="11.54506"
+ height="11.262992"
+ x="185.38916"
+ y="-10.125788" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-3"
+ width="11.54506"
+ height="11.262992"
+ x="173.69376"
+ y="1.7858695" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-7"
+ width="11.54506"
+ height="11.262992"
+ x="185.4158"
+ y="1.6730031" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9"
+ width="11.54506"
+ height="11.262992"
+ x="149.90752"
+ y="-9.8383083" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-7"
+ width="11.54506"
+ height="11.262992"
+ x="161.6127"
+ y="-9.9240551" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2"
+ width="11.54506"
+ height="11.262992"
+ x="149.91733"
+ y="1.9876043" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6"
+ width="11.54506"
+ height="11.262992"
+ x="161.63936"
+ y="1.8747379" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-3"
+ width="11.54506"
+ height="11.262992"
+ x="221.3436"
+ y="-34.173023" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-6"
+ width="11.54506"
+ height="11.262992"
+ x="233.04878"
+ y="-34.25877" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-3"
+ width="11.54506"
+ height="11.262992"
+ x="221.35339"
+ y="-22.347115" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-60"
+ width="11.54506"
+ height="11.262992"
+ x="233.07542"
+ y="-22.459978" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-44"
+ width="11.54506"
+ height="11.262992"
+ x="197.56715"
+ y="-33.971294" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-96"
+ width="11.54506"
+ height="11.262992"
+ x="209.27232"
+ y="-34.057037" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-6"
+ width="11.54506"
+ height="11.262992"
+ x="197.57697"
+ y="-22.145384" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-4"
+ width="11.54506"
+ height="11.262992"
+ x="209.29898"
+ y="-22.258247" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-0-1"
+ width="11.54506"
+ height="11.262992"
+ x="221.09816"
+ y="13.361241" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-7-9"
+ width="11.54506"
+ height="11.262992"
+ x="232.80333"
+ y="13.275496" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-1-8"
+ width="11.54506"
+ height="11.262992"
+ x="221.10796"
+ y="25.187153" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-6-0"
+ width="11.54506"
+ height="11.262992"
+ x="232.82999"
+ y="25.074286" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-4-4"
+ width="11.54506"
+ height="11.262992"
+ x="197.32172"
+ y="13.562973" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9-9"
+ width="11.54506"
+ height="11.262992"
+ x="209.02689"
+ y="13.477229" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-5-0"
+ width="11.54506"
+ height="11.262992"
+ x="197.33153"
+ y="25.388887" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3-5"
+ width="11.54506"
+ height="11.262992"
+ x="209.05351"
+ y="25.27602" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-6-5"
+ width="11.54506"
+ height="11.262992"
+ x="221.41415"
+ y="-10.491449" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-0"
+ width="11.54506"
+ height="11.262992"
+ x="233.11932"
+ y="-10.577197" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-3-1"
+ width="11.54506"
+ height="11.262992"
+ x="221.42395"
+ y="1.3344631" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-7-6"
+ width="11.54506"
+ height="11.262992"
+ x="233.14598"
+ y="1.2215967" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-0"
+ width="11.54506"
+ height="11.262992"
+ x="197.63768"
+ y="-10.289718" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-7-2"
+ width="11.54506"
+ height="11.262992"
+ x="209.34288"
+ y="-10.375462" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-8"
+ width="11.54506"
+ height="11.262992"
+ x="197.64748"
+ y="1.5361952" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-8"
+ width="11.54506"
+ height="11.262992"
+ x="209.36951"
+ y="1.4233288" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-3"
+ width="11.54506"
+ height="11.262992"
+ x="126.15435"
+ y="37.763531" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-8"
+ width="11.54506"
+ height="11.262992"
+ x="137.85956"
+ y="37.677784" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-6"
+ width="11.54506"
+ height="11.262992"
+ x="126.16419"
+ y="49.589439" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-1"
+ width="11.54506"
+ height="11.262992"
+ x="137.88618"
+ y="49.476574" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-6"
+ width="11.54506"
+ height="11.262992"
+ x="102.37791"
+ y="37.96526" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-0"
+ width="11.54506"
+ height="11.262992"
+ x="114.08312"
+ y="37.879513" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-9"
+ width="11.54506"
+ height="11.262992"
+ x="102.3877"
+ y="49.791176" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-1"
+ width="11.54506"
+ height="11.262992"
+ x="114.10973"
+ y="49.678307" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-4-9"
+ width="11.54506"
+ height="11.262992"
+ x="125.83842"
+ y="61.616215" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-0-1"
+ width="11.54506"
+ height="11.262992"
+ x="137.54358"
+ y="61.530472" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-1-5"
+ width="11.54506"
+ height="11.262992"
+ x="125.69932"
+ y="73.643074" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-2-0"
+ width="11.54506"
+ height="11.262992"
+ x="137.4213"
+ y="73.530205" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-0-9"
+ width="11.54506"
+ height="11.262992"
+ x="102.35748"
+ y="61.710678" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-1-4"
+ width="11.54506"
+ height="11.262992"
+ x="114.06268"
+ y="61.624939" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-5-9"
+ width="11.54506"
+ height="11.262992"
+ x="102.2184"
+ y="73.737526" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-2-6"
+ width="11.54506"
+ height="11.262992"
+ x="113.94043"
+ y="73.624664" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-0-8"
+ width="11.54506"
+ height="11.262992"
+ x="173.3385"
+ y="61.338898" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-7-2"
+ width="11.54506"
+ height="11.262992"
+ x="185.04369"
+ y="61.253159" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-1-4"
+ width="11.54506"
+ height="11.262992"
+ x="173.19939"
+ y="73.365753" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-6-6"
+ width="11.54506"
+ height="11.262992"
+ x="184.92143"
+ y="73.252884" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-4-5"
+ width="11.54506"
+ height="11.262992"
+ x="149.56206"
+ y="61.540638" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9-3"
+ width="11.54506"
+ height="11.262992"
+ x="161.26723"
+ y="61.454887" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-5-4"
+ width="11.54506"
+ height="11.262992"
+ x="149.42297"
+ y="73.56749" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3-7"
+ width="11.54506"
+ height="11.262992"
+ x="161.14496"
+ y="73.454613" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-6-4"
+ width="11.54506"
+ height="11.262992"
+ x="173.6545"
+ y="37.486214" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-7"
+ width="11.54506"
+ height="11.262992"
+ x="185.35968"
+ y="37.400467" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-3-14"
+ width="11.54506"
+ height="11.262992"
+ x="173.66429"
+ y="49.312122" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-7-0"
+ width="11.54506"
+ height="11.262992"
+ x="185.38632"
+ y="49.199261" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-4"
+ width="11.54506"
+ height="11.262992"
+ x="149.87804"
+ y="37.687943" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-7-8"
+ width="11.54506"
+ height="11.262992"
+ x="161.58322"
+ y="37.6022" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-3"
+ width="11.54506"
+ height="11.262992"
+ x="149.88785"
+ y="49.513859" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-3"
+ width="11.54506"
+ height="11.262992"
+ x="161.60985"
+ y="49.40099" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-0-1-4"
+ width="11.54506"
+ height="11.262992"
+ x="221.06868"
+ y="60.887497" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-7-9-6"
+ width="11.54506"
+ height="11.262992"
+ x="232.77385"
+ y="60.801754" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-1-8-1"
+ width="11.54506"
+ height="11.262992"
+ x="220.92955"
+ y="72.914345" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-6-0-5"
+ width="11.54506"
+ height="11.262992"
+ x="232.65158"
+ y="72.801476" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-4-4-5"
+ width="11.54506"
+ height="11.262992"
+ x="197.29221"
+ y="61.089226" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9-9-4"
+ width="11.54506"
+ height="11.262992"
+ x="208.99741"
+ y="61.003483" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-5-0-6"
+ width="11.54506"
+ height="11.262992"
+ x="197.15314"
+ y="73.116074" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3-5-6"
+ width="11.54506"
+ height="11.262992"
+ x="208.87514"
+ y="73.003212" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-6-5-9"
+ width="11.54506"
+ height="11.262992"
+ x="221.38467"
+ y="37.034805" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-0-6"
+ width="11.54506"
+ height="11.262992"
+ x="233.08984"
+ y="36.949059" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-3-1-7"
+ width="11.54506"
+ height="11.262992"
+ x="221.39447"
+ y="48.860718" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-7-6-5"
+ width="11.54506"
+ height="11.262992"
+ x="233.11649"
+ y="48.747845" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-0-6"
+ width="11.54506"
+ height="11.262992"
+ x="197.6082"
+ y="37.236534" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-7-2-4"
+ width="11.54506"
+ height="11.262992"
+ x="209.31339"
+ y="37.150791" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-8-3"
+ width="11.54506"
+ height="11.262992"
+ x="197.618"
+ y="49.062447" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-8-0"
+ width="11.54506"
+ height="11.262992"
+ x="209.34003"
+ y="48.949585" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-3-8"
+ width="11.54506"
+ height="11.262992"
+ x="268.99387"
+ y="-34.440758" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-6-0"
+ width="11.54506"
+ height="11.262992"
+ x="280.69904"
+ y="-34.526497" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-3-9"
+ width="11.54506"
+ height="11.262992"
+ x="269.00366"
+ y="-22.614843" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-60-6"
+ width="11.54506"
+ height="11.262992"
+ x="280.72571"
+ y="-22.727707" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-44-7"
+ width="11.54506"
+ height="11.262992"
+ x="245.21744"
+ y="-34.239029" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-96-2"
+ width="11.54506"
+ height="11.262992"
+ x="256.92261"
+ y="-34.324772" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-6-1"
+ width="11.54506"
+ height="11.262992"
+ x="245.22725"
+ y="-22.413116" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-4-4"
+ width="11.54506"
+ height="11.262992"
+ x="256.94925"
+ y="-22.525974" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-0-1-48"
+ width="11.54506"
+ height="11.262992"
+ x="268.74844"
+ y="13.09351" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-7-9-4"
+ width="11.54506"
+ height="11.262992"
+ x="280.45361"
+ y="13.007765" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-1-8-9"
+ width="11.54506"
+ height="11.262992"
+ x="268.75824"
+ y="24.919422" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-6-0-8"
+ width="11.54506"
+ height="11.262992"
+ x="280.48026"
+ y="24.806559" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-4-4-4"
+ width="11.54506"
+ height="11.262992"
+ x="244.97197"
+ y="13.29524" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9-9-41"
+ width="11.54506"
+ height="11.262992"
+ x="256.67715"
+ y="13.209496" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-5-0-9"
+ width="11.54506"
+ height="11.262992"
+ x="244.98181"
+ y="25.121157" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3-5-68"
+ width="11.54506"
+ height="11.262992"
+ x="256.7038"
+ y="25.008287" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-6-5-0"
+ width="11.54506"
+ height="11.262992"
+ x="269.06442"
+ y="-10.759184" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-0-9"
+ width="11.54506"
+ height="11.262992"
+ x="280.76959"
+ y="-10.844928" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-3-1-3"
+ width="11.54506"
+ height="11.262992"
+ x="269.07422"
+ y="1.0667289" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-7-6-6"
+ width="11.54506"
+ height="11.262992"
+ x="280.79623"
+ y="0.95386255" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-0-3"
+ width="11.54506"
+ height="11.262992"
+ x="245.28796"
+ y="-10.557449" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-7-2-8"
+ width="11.54506"
+ height="11.262992"
+ x="256.99316"
+ y="-10.643196" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-8-9"
+ width="11.54506"
+ height="11.262992"
+ x="245.29776"
+ y="1.2684637" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-8-5"
+ width="11.54506"
+ height="11.262992"
+ x="257.01981"
+ y="1.1555973" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-937-0-3-0-0-1-4-0"
+ width="11.54506"
+ height="11.262992"
+ x="268.71893"
+ y="60.619762" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-4-7-6-9-7-9-6-1"
+ width="11.54506"
+ height="11.262992"
+ x="280.42413"
+ y="60.534019" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-5-5-3-0-1-8-1-7"
+ width="11.54506"
+ height="11.262992"
+ x="268.57983"
+ y="72.646606" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-25-8-3-7-6-0-5-1"
+ width="11.54506"
+ height="11.262992"
+ x="280.30188"
+ y="72.533737" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-7-0-3-6-4-4-5-9"
+ width="11.54506"
+ height="11.262992"
+ x="244.94249"
+ y="60.821495" />
+ <rect
+ style="fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-4-4-9-8-9-9-4-8"
+ width="11.54506"
+ height="11.262992"
+ x="256.64767"
+ y="60.735752" />
+ <rect
+ style="fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-4-8-7-7-5-0-6-2"
+ width="11.54506"
+ height="11.262992"
+ x="244.80341"
+ y="72.848343" />
+ <rect
+ style="fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-3-0-2-7-3-5-6-6"
+ width="11.54506"
+ height="11.262992"
+ x="256.52542"
+ y="72.735474" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-6-4-3-6-5-9-7"
+ width="11.54506"
+ height="11.262992"
+ x="269.03494"
+ y="36.767071" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-10-0-5-5-0-6-5"
+ width="11.54506"
+ height="11.262992"
+ x="280.74011"
+ y="36.681328" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-6-9-0-3-1-7-9"
+ width="11.54506"
+ height="11.262992"
+ x="269.04474"
+ y="48.592983" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-1-1-8-7-6-5-3"
+ width="11.54506"
+ height="11.262992"
+ x="280.76675"
+ y="48.480114" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-9-9-6-4-9-0-6-9"
+ width="11.54506"
+ height="11.262992"
+ x="245.25848"
+ y="36.968803" />
+ <rect
+ style="opacity:0.15;fill:#0100ff;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-5-1-49-2-1-7-2-4-9"
+ width="11.54506"
+ height="11.262992"
+ x="256.96365"
+ y="36.883057" />
+ <rect
+ style="opacity:0.15;fill:#ff000a;fill-opacity:1;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-3-2-0-5-1-2-8-3-7"
+ width="11.54506"
+ height="11.262992"
+ x="245.26828"
+ y="48.794716" />
+ <rect
+ style="opacity:0.15;fill:#00ff00;stroke:#040000;stroke-width:0.370417;paint-order:markers stroke fill"
+ id="rect288-56-7-9-4-3-6-8-0-9"
+ width="11.54506"
+ height="11.262992"
+ x="256.99033"
+ y="48.681854" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded)"
+ d="m 107.31925,-51.577366 c 4.74513,-3.867921 8.45747,-3.127986 11.65845,-0.106568"
+ id="path2"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1)"
+ d="m 155.69201,-52.103201 c 4.74513,-3.867922 8.45748,-3.127987 11.65845,-0.106568"
+ id="path2-0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-8)"
+ d="m 205.6246,-51.786864 c 4.74513,-3.867923 8.45746,-3.127986 11.65845,-0.106568"
+ id="path2-0-9"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-1-8-5)"
+ d="m 252.68356,-50.911879 c 4.74513,-3.867922 8.45748,-3.127987 11.65845,-0.106568"
+ id="path2-0-9-9"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded)"
+ d="m 119.2485,-56.261218 c 10.02527,-3.47338 19.6883,-7.598722 35.75846,-0.191608"
+ id="path3"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-6)"
+ d="m 167.15143,-55.799841 c 10.02528,-3.473382 19.68831,-7.598723 35.75847,-0.191609"
+ id="path3-9"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.250001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-6-6)"
+ d="m 217.31498,-57.659731 c 10.02527,-3.473382 19.6883,-7.598723 35.75847,-0.191609"
+ id="path3-9-2"
+ sodipodi:nodetypes="cc" />
+ <ellipse
+ id="path5"
+ style="fill:#f6e9e9;stroke:#000000;stroke-width:0.264583"
+ cx="88.19117"
+ cy="-29.671202"
+ rx="0.020938689"
+ ry="0.020956721" />
+ <ellipse
+ id="path6"
+ style="fill:#f6e9e9;stroke:#000000;stroke-width:0.264583"
+ cx="88.29512"
+ cy="-17.772333"
+ rx="0.020938689"
+ ry="0.020956721" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded)"
+ d="m 88.210313,-29.749351 c -3.901538,3.593858 -2.316274,7.754777 0.03417,11.994772"
+ id="path7"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-3)"
+ d="m 88.476097,20.705783 c -3.901539,3.593858 -2.316274,7.754778 0.03417,11.994772"
+ id="path7-5"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowWideRounded-3-5)"
+ d="m 88.077894,70.899225 c -3.901538,3.593858 -2.316273,7.754778 0.03417,11.994772"
+ id="path7-5-0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#ArrowWideRounded)"
+ d="M 85.21112,-18.062254 C 77.854142,-3.5491421 78.306424,9.1772046 85.12651,20.446596"
+ id="path8"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#ArrowWideRounded-9)"
+ d="m 84.721251,32.988117 c -7.356992,14.513113 -6.90471,27.239459 -0.08461,38.508851"
+ id="path8-8"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="47.435753"
+ y="-48.114857"
+ id="text8"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ id="tspan8"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="47.435753"
+ y="-48.114857">x_even_inc = 1</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="47.435753"
+ y="-40.193825"
+ id="tspan14" /><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="47.435753"
+ y="-32.272789"
+ id="tspan9" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="33.799152"
+ y="-23.751602"
+ id="text8-5"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="33.799152"
+ y="-23.751602"
+ id="tspan13">y_even_inc = 1</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="33.799152"
+ y="-15.830566"
+ id="tspan9-8" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="105.17001"
+ y="-40.766998"
+ id="text8-5-9"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="105.17001"
+ y="-40.766998"
+ id="tspan13-1"> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="105.17001"
+ y="-32.845963"
+ id="tspan9-8-2" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="47.782906"
+ y="-57.348003"
+ id="text8-3"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ id="tspan8-4"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="47.782906"
+ y="-57.348003">x_odd_inc = 3 </tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="47.782906"
+ y="-49.426968"
+ id="tspan10" /><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="47.782906"
+ y="-41.505932"
+ id="tspan9-0" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="35.099522"
+ y="-13.290938"
+ id="text8-3-5"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="35.099522"
+ y="-13.290938"
+ id="tspan12">y_odd_inc = 3</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="35.099522"
+ y="-5.3699036"
+ id="tspan10-1" /><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="35.099522"
+ y="2.5511286"
+ id="tspan9-0-4" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.25663"
+ y="-25.301243"
+ id="text8-5-9-2"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.25663"
+ y="-25.301243"
+ id="tspan13-1-6">0</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.25663"
+ y="-17.380207"
+ id="tspan9-8-2-0" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="94.882782"
+ y="-12.704006"
+ id="text8-5-9-2-0"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="94.882782"
+ y="-12.704006"
+ id="tspan13-1-6-8">1</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="94.882782"
+ y="-4.7829742"
+ id="tspan9-8-2-0-6" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.313683"
+ y="-1.2592223"
+ id="text8-5-9-2-6"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.313683"
+ y="-1.2592223"
+ id="tspan13-1-6-0">2</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.313683"
+ y="6.6618114"
+ id="tspan9-8-2-0-5" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.326363"
+ y="9.9864616"
+ id="text8-5-9-2-5"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.326363"
+ y="9.9864616"
+ id="tspan13-1-6-04">3</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.326363"
+ y="17.907495"
+ id="tspan9-8-2-0-9" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.3517"
+ y="22.179893"
+ id="text8-5-9-2-09"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.3517"
+ y="22.179893"
+ id="tspan13-1-6-6">4</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.3517"
+ y="30.100925"
+ id="tspan9-8-2-0-60" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.307358"
+ y="33.597816"
+ id="text8-5-9-2-8"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.307358"
+ y="33.597816"
+ id="tspan13-1-6-68">5</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.307358"
+ y="41.518852"
+ id="tspan9-8-2-0-53" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.25663"
+ y="46.90731"
+ id="text8-5-9-2-7"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.25663"
+ y="46.90731"
+ id="tspan13-1-6-80">6</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.25663"
+ y="54.828346"
+ id="tspan9-8-2-0-8" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.23764"
+ y="58.228878"
+ id="text8-5-9-2-9"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.23764"
+ y="58.228878"
+ id="tspan13-1-6-7">7</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.23764"
+ y="66.14991"
+ id="tspan9-8-2-0-2" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.294655"
+ y="70.441017"
+ id="text8-5-9-2-80"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.294655"
+ y="70.441017"
+ id="tspan13-1-6-62">8</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.294655"
+ y="78.362045"
+ id="tspan9-8-2-0-65" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="95.288322"
+ y="82.355179"
+ id="text8-5-9-2-1"
+ transform="scale(0.99956973,1.0004305)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.288322"
+ y="82.355179"
+ id="tspan13-1-6-71">9</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.33683px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583px"
+ x="95.288322"
+ y="90.276215"
+ id="tspan9-8-2-0-3" /></text>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/Documentation/software-isp-benchmarking.rst b/Documentation/software-isp-benchmarking.rst
new file mode 100644
index 00000000..b3033132
--- /dev/null
+++ b/Documentation/software-isp-benchmarking.rst
@@ -0,0 +1,77 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. _software-isp-benchmarking:
+
+Software ISP benchmarking
+=========================
+
+The Software ISP is particularly sensitive to performance regressions therefore
+it is a good idea to always benchmark the Software ISP before and after making
+changes to it and ensure that there are no performance regressions.
+
+DebayerCpu class builtin benchmark
+----------------------------------
+
+The DebayerCpu class has a builtin benchmark. This benchmark measures the time
+spent on processing (collecting statistics and debayering) only, it does not
+measure the time spent on capturing or outputting the frames.
+
+The builtin benchmark always runs. So this can be used by simply running "cam"
+or "qcam" with a pipeline using the Software ISP.
+
+When it runs it will skip measuring the first 30 frames to allow the caches and
+the CPU temperature (turbo-ing) to warm-up and then it measures 30 fps and shows
+the total and per frame processing time using an info level log message:
+
+.. code-block:: text
+
+ INFO Debayer debayer_cpu.cpp:907 Processed 30 frames in 244317us, 8143 us/frame
+
+To get stable measurements it is advised to disable any other processes which
+may cause significant CPU usage (e.g. disable wifi, bluetooth and browsers).
+When possible it is also advisable to disable CPU turbo-ing and
+frequency-scaling.
+
+For example when benchmarking on a Lenovo ThinkPad X1 Yoga Gen 8, with the
+charger plugged in, the CPU can be fixed to run at 2 GHz using:
+
+.. code-block:: shell
+
+ sudo x86_energy_perf_policy --turbo-enable 0
+ sudo cpupower frequency-set -d 2GHz -u 2GHz
+
+with these settings the builtin bench reports a processing time of ~7.8ms/frame
+on this laptop for FHD SGRBG10 (unpacked) bayer data.
+
+Measuring power consumption
+---------------------------
+
+Since the Software ISP is often used on mobile devices it is also important to
+measure power consumption and ensure that that does not regress.
+
+For example to measure power consumption on a Lenovo ThinkPad X1 Yoga Gen 8 it
+needs to be running on battery and it should be configured with its
+platform-profile (/sys/firmware/acpi/platform_profile) set to balanced and with
+its default turbo and frequency-scaling behavior to match real world usage.
+
+Then start qcam to capture a FHD picture at 30 fps and position the qcam window
+so that it is fully visible. After this run the following command to monitor the
+power consumption:
+
+.. code-block:: shell
+
+ watch -n 10 cat /sys/class/power_supply/BAT0/power_now /sys/class/hwmon/hwmon6/fan?_input
+
+Note this not only measures the power consumption in µW it also monitors the
+speed of this laptop's 2 fans. This is important because depending on the
+ambient temperature the 2 fans may spin up while testing and this will cause an
+additional power consumption of approx. 0.5 W messing up the measurement.
+
+After starting qcam + the watch command let the laptop sit without using it for
+2 minutes for the readings to stabilize. Then check that the fans have not
+turned on and manually take a couple of consecutive power readings and average
+these.
+
+On the example Lenovo ThinkPad X1 Yoga Gen 8 laptop this results in a measured
+power consumption of approx. 13 W while running qcam versus approx. 4-5 W while
+setting idle with its OLED panel on.
diff --git a/Documentation/theme/layout.html b/Documentation/theme/layout.html
index fcc6d221..4fffefab 100644
--- a/Documentation/theme/layout.html
+++ b/Documentation/theme/layout.html
@@ -33,11 +33,6 @@ SPDX-License-Identifier: CC-BY-SA-4.0
{% endif %}
- {# RTD hosts this file, so just load on non RTD builds #}
- {% if not READTHEDOCS %}
- <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
- {% endif %}
-
{% for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{% endfor %}
diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt
new file mode 100644
index 00000000..13ca539f
--- /dev/null
+++ b/LICENSES/CC-BY-4.0.txt
@@ -0,0 +1,156 @@
+Creative Commons Attribution 4.0 International
+
+ Creative Commons Corporation (“Creative Commonsâ€) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is†basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
+
+Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
+
+Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
+
+Creative Commons Attribution 4.0 International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
+
+Section 1 – Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
+
+ c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
+
+ d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
+
+ e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
+
+ f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
+
+ g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
+
+ h. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
+
+ i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
+
+Section 2 – Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
+
+ A. reproduce and Share the Licensed Material, in whole or in part; and
+
+ B. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section 6(a).
+
+ 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
+
+ B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
+
+b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this Public License.
+
+ 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
+
+Section 3 – License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified form), You must:
+
+ A. retain the following if it is supplied by the Licensor with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
+
+ B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
+
+ C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
+
+ 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
+
+ 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
+
+Section 4 – Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
+For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
+
+Section 5 – Disclaimer of Warranties and Limitation of Liability.
+
+ a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
+
+ b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
+
+ c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
+
+Section 6 – Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
+
+ d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
+
+ e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
+
+Section 7 – Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
+
+Section 8 – Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
+
+ c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
+
+Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.†Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons†or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/README.rst b/README.rst
index b9e72d81..1da7a3d6 100644
--- a/README.rst
+++ b/README.rst
@@ -30,11 +30,11 @@ Getting Started
To fetch the sources, build and install:
-::
+.. code::
git clone https://git.libcamera.org/libcamera/libcamera.git
cd libcamera
- meson build
+ meson setup build
ninja -C build install
Dependencies
@@ -47,21 +47,16 @@ A C++ toolchain: [required]
Either {g++, clang}
Meson Build system: [required]
- meson (>= 0.56) ninja-build pkg-config
-
- If your distribution doesn't provide a recent enough version of meson,
- you can install or upgrade it using pip3.
-
- .. code::
-
- pip3 install --user meson
- pip3 install --user --upgrade meson
+ meson (>= 0.60) ninja-build pkg-config
for the libcamera core: [required]
libyaml-dev python3-yaml python3-ply python3-jinja2
-for IPA module signing: [required]
- libgnutls28-dev openssl
+for IPA module signing: [recommended]
+ Either libgnutls28-dev or libssl-dev, openssl
+
+ Without IPA module signing, all IPA modules will be isolated in a
+ separate process. This adds an unnecessary extra overhead at runtime.
for improved debugging: [optional]
libdw-dev libunwind-dev
@@ -71,12 +66,6 @@ for improved debugging: [optional]
information, and libunwind is not needed if both libdw and the glibc
backtrace() function are available.
-for the Raspberry Pi IPA: [optional]
- libboost-dev
-
- Support for Raspberry Pi can be disabled through the meson
- 'pipelines' option to avoid this dependency.
-
for device hotplug enumeration: [optional]
libudev-dev
@@ -86,17 +75,20 @@ for documentation: [optional]
for gstreamer: [optional]
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
+for Python bindings: [optional]
+ libpython3-dev pybind11-dev
+
for cam: [optional]
libevent-dev is required to support cam, however the following
optional dependencies bring more functionality to the cam test
tool:
- libdrm-dev: Enables the KMS sink
+ - libjpeg-dev: Enables MJPEG on the SDL sink
- libsdl2-dev: Enables the SDL sink
- - libsdl2-image-dev: Supports MJPEG on the SDL sink
for qcam: [optional]
- qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev
+ libtiff-dev qtbase5-dev qttools5-dev-tools
for tracing with lttng: [optional]
liblttng-ust-dev python3-jinja2 lttng-tools
@@ -104,8 +96,14 @@ for tracing with lttng: [optional]
for android: [optional]
libexif-dev libjpeg-dev
+for Python bindings: [optional]
+ pybind11-dev
+
for lc-compliance: [optional]
- libevent-dev
+ libevent-dev libgtest-dev
+
+for abi-compat.sh: [optional]
+ abi-compliance-checker
Basic testing with cam utility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -125,10 +123,13 @@ setting the ``LIBCAMERA_LOG_LEVELS`` environment variable:
Using GStreamer plugin
~~~~~~~~~~~~~~~~~~~~~~
-To use GStreamer plugin from source tree, set the following environment so that
-GStreamer can find it. This isn't necessary when libcamera is installed.
+To use the GStreamer plugin from the source tree, use the meson ``devenv``
+command. This will create a new shell instance with the ``GST_PLUGIN_PATH``
+environment set accordingly.
+
+.. code::
- export GST_PLUGIN_PATH=$(pwd)/build/src/gstreamer
+ meson devenv -C build
The debugging tool ``gst-launch-1.0`` can be used to construct a pipeline and
test it. The following pipeline will stream from the camera named "Camera 1"
@@ -136,7 +137,7 @@ onto the OpenGL accelerated display element on your system.
.. code::
- gst-launch-1.0 libcamerasrc camera-name="Camera 1" ! glimagesink
+ gst-launch-1.0 libcamerasrc camera-name="Camera 1" ! queue ! glimagesink
To show the first camera found you can omit the camera-name property, or you
can list the cameras and their capabilities using:
@@ -151,7 +152,7 @@ if desired with a pipeline such as:
.. code::
gst-launch-1.0 libcamerasrc ! 'video/x-raw,width=1280,height=720' ! \
- glimagesink
+ queue ! glimagesink
The libcamerasrc element has two log categories, named libcamera-provider (for
the video device provider) and libcamerasrc (for the operation of the camera).
@@ -167,7 +168,7 @@ the following example could be used as a starting point:
gst-launch-1.0 libcamerasrc ! \
video/x-raw,colorimetry=bt709,format=NV12,width=1280,height=720,framerate=30/1 ! \
- jpegenc ! multipartmux ! \
+ queue ! jpegenc ! multipartmux ! \
tcpserversink host=0.0.0.0 port=5000
Which can be received on another device over the network with:
@@ -194,8 +195,8 @@ the build.ninja module. This is a snippet of the error message.
This can be solved in two ways:
-1) Don't install meson again if it is already installed system-wide.
+1. Don't install meson again if it is already installed system-wide.
-2) If a version of meson which is different from the system-wide version is
-already installed, uninstall that meson using pip3, and install again without
-the --user argument.
+2. If a version of meson which is different from the system-wide version is
+ already installed, uninstall that meson using pip3, and install again without
+ the --user argument.
diff --git a/include/android/system/core/include/system/graphics-base-v1.0.h b/include/android/system/core/include/system/graphics-base-v1.0.h
index 44913cc6..7548d879 100644
--- a/include/android/system/core/include/system/graphics-base-v1.0.h
+++ b/include/android/system/core/include/system/graphics-base-v1.0.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: Apache-2.0 */
// This file is autogenerated by hidl-gen. Do not edit manually.
// Source: android.hardware.graphics.common@1.0
// Location: hardware/interfaces/graphics/common/1.0/
diff --git a/include/android/system/core/include/system/graphics-base-v1.1.h b/include/android/system/core/include/system/graphics-base-v1.1.h
index f95b9ba2..35130724 100644
--- a/include/android/system/core/include/system/graphics-base-v1.1.h
+++ b/include/android/system/core/include/system/graphics-base-v1.1.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: Apache-2.0 */
// This file is autogenerated by hidl-gen. Do not edit manually.
// Source: android.hardware.graphics.common@1.1
// Location: hardware/interfaces/graphics/common/1.1/
diff --git a/include/android/system/core/include/system/graphics-base.h b/include/android/system/core/include/system/graphics-base.h
index ea920071..d01e9874 100644
--- a/include/android/system/core/include/system/graphics-base.h
+++ b/include/android/system/core/include/system/graphics-base.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: Apache-2.0 */
#ifndef SYSTEM_CORE_GRAPHICS_BASE_H_
#define SYSTEM_CORE_GRAPHICS_BASE_H_
diff --git a/include/android/system/core/include/system/graphics-sw.h b/include/android/system/core/include/system/graphics-sw.h
index 9e1a88e9..4a1cf829 100644
--- a/include/android/system/core/include/system/graphics-sw.h
+++ b/include/android/system/core/include/system/graphics-sw.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: Apache-2.0 */
#ifndef SYSTEM_CORE_GRAPHICS_SW_H_
#define SYSTEM_CORE_GRAPHICS_SW_H_
diff --git a/include/libcamera/base/bound_method.h b/include/libcamera/base/bound_method.h
index e73a4d98..c0275249 100644
--- a/include/libcamera/base/bound_method.h
+++ b/include/libcamera/base/bound_method.h
@@ -72,7 +72,7 @@ public:
}
virtual ~BoundMethodBase() = default;
- template<typename T, typename std::enable_if_t<!std::is_same<Object, T>::value> * = nullptr>
+ template<typename T, std::enable_if_t<!std::is_same<Object, T>::value> * = nullptr>
bool match(T *obj) { return obj == obj_; }
bool match(Object *object) { return object == object_; }
diff --git a/include/libcamera/base/flags.h b/include/libcamera/base/flags.h
index bff3b93c..a1b404bd 100644
--- a/include/libcamera/base/flags.h
+++ b/include/libcamera/base/flags.h
@@ -147,7 +147,7 @@ struct flags_enable_operators {
};
template<typename E>
-typename std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
+std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
operator|(E lhs, E rhs)
{
using type = std::underlying_type_t<E>;
@@ -155,7 +155,7 @@ operator|(E lhs, E rhs)
}
template<typename E>
-typename std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
+std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
operator&(E lhs, E rhs)
{
using type = std::underlying_type_t<E>;
@@ -163,7 +163,7 @@ operator&(E lhs, E rhs)
}
template<typename E>
-typename std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
+std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
operator^(E lhs, E rhs)
{
using type = std::underlying_type_t<E>;
@@ -171,7 +171,7 @@ operator^(E lhs, E rhs)
}
template<typename E>
-typename std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
+std::enable_if_t<flags_enable_operators<E>::enable, Flags<E>>
operator~(E rhs)
{
using type = std::underlying_type_t<E>;
diff --git a/include/libcamera/base/log.h b/include/libcamera/base/log.h
index 3fc5ced3..dcaacbe0 100644
--- a/include/libcamera/base/log.h
+++ b/include/libcamera/base/log.h
@@ -29,16 +29,18 @@ enum LogSeverity {
class LogCategory
{
public:
- explicit LogCategory(const char *name);
+ static LogCategory *create(const char *name);
- const char *name() const { return name_; }
+ const std::string &name() const { return name_; }
LogSeverity severity() const { return severity_; }
void setSeverity(LogSeverity severity);
static const LogCategory &defaultCategory();
private:
- const char *name_;
+ explicit LogCategory(const char *name);
+
+ const std::string name_;
LogSeverity severity_;
};
@@ -49,7 +51,7 @@ extern const LogCategory &_LOG_CATEGORY(name)();
const LogCategory &_LOG_CATEGORY(name)() \
{ \
/* The instance will be deleted by the Logger destructor. */ \
- static LogCategory *category = new LogCategory(#name); \
+ static LogCategory *category = LogCategory::create(#name); \
return *category; \
}
diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build
index 4410aba8..bace25d5 100644
--- a/include/libcamera/base/meson.build
+++ b/include/libcamera/base/meson.build
@@ -2,31 +2,39 @@
libcamera_base_include_dir = libcamera_include_dir / 'base'
-libcamera_base_headers = files([
- 'backtrace.h',
+libcamera_base_public_headers = files([
'bound_method.h',
'class.h',
'compiler.h',
+ 'flags.h',
+ 'object.h',
+ 'shared_fd.h',
+ 'signal.h',
+ 'span.h',
+ 'unique_fd.h',
+])
+
+libcamera_base_private_headers = files([
+ 'backtrace.h',
'event_dispatcher.h',
'event_dispatcher_poll.h',
'event_notifier.h',
'file.h',
- 'flags.h',
'log.h',
'message.h',
'mutex.h',
- 'object.h',
'private.h',
'semaphore.h',
- 'shared_fd.h',
- 'signal.h',
- 'span.h',
'thread.h',
'thread_annotations.h',
'timer.h',
- 'unique_fd.h',
'utils.h',
])
-install_headers(libcamera_base_headers,
- subdir: libcamera_base_include_dir)
+libcamera_base_headers = [
+ libcamera_base_public_headers,
+ libcamera_base_private_headers,
+]
+
+install_headers(libcamera_base_public_headers,
+ subdir : libcamera_base_include_dir)
diff --git a/include/libcamera/base/message.h b/include/libcamera/base/message.h
index 65572c74..b939af6f 100644
--- a/include/libcamera/base/message.h
+++ b/include/libcamera/base/message.h
@@ -9,6 +9,8 @@
#include <atomic>
+#include <libcamera/base/private.h>
+
#include <libcamera/base/bound_method.h>
namespace libcamera {
diff --git a/include/libcamera/base/mutex.h b/include/libcamera/base/mutex.h
index 2d23e49e..52441c55 100644
--- a/include/libcamera/base/mutex.h
+++ b/include/libcamera/base/mutex.h
@@ -10,6 +10,8 @@
#include <condition_variable>
#include <mutex>
+#include <libcamera/base/private.h>
+
#include <libcamera/base/thread_annotations.h>
namespace libcamera {
diff --git a/include/libcamera/base/object.h b/include/libcamera/base/object.h
index eef1a2c9..cb7e0a13 100644
--- a/include/libcamera/base/object.h
+++ b/include/libcamera/base/object.h
@@ -32,7 +32,7 @@ public:
void postMessage(std::unique_ptr<Message> msg);
template<typename T, typename R, typename... FuncArgs, typename... Args,
- typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr>
+ std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr>
R invokeMethod(R (T::*func)(FuncArgs...), ConnectionType type,
Args&&... args)
{
@@ -49,6 +49,8 @@ public:
protected:
virtual void message(Message *msg);
+ bool assertThreadBound(const char *message);
+
private:
friend class SignalBase;
friend class Thread;
diff --git a/include/libcamera/base/semaphore.h b/include/libcamera/base/semaphore.h
index c11e8dd1..f1052317 100644
--- a/include/libcamera/base/semaphore.h
+++ b/include/libcamera/base/semaphore.h
@@ -18,15 +18,15 @@ class Semaphore
public:
Semaphore(unsigned int n = 0);
- unsigned int available();
- void acquire(unsigned int n = 1);
- bool tryAcquire(unsigned int n = 1);
- void release(unsigned int n = 1);
+ unsigned int available() LIBCAMERA_TSA_EXCLUDES(mutex_);
+ void acquire(unsigned int n = 1) LIBCAMERA_TSA_EXCLUDES(mutex_);
+ bool tryAcquire(unsigned int n = 1) LIBCAMERA_TSA_EXCLUDES(mutex_);
+ void release(unsigned int n = 1) LIBCAMERA_TSA_EXCLUDES(mutex_);
private:
Mutex mutex_;
ConditionVariable cv_;
- unsigned int available_;
+ unsigned int available_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
};
} /* namespace libcamera */
diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h
index 91000d0d..444997b4 100644
--- a/include/libcamera/base/signal.h
+++ b/include/libcamera/base/signal.h
@@ -13,10 +13,11 @@
#include <vector>
#include <libcamera/base/bound_method.h>
-#include <libcamera/base/object.h>
namespace libcamera {
+class Object;
+
class SignalBase
{
public:
@@ -44,7 +45,7 @@ public:
}
#ifndef __DOXYGEN__
- template<typename T, typename R, typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr>
+ template<typename T, typename R, std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr>
void connect(T *obj, R (T::*func)(Args...),
ConnectionType type = ConnectionTypeAuto)
{
@@ -52,7 +53,7 @@ public:
SignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, object, func, type));
}
- template<typename T, typename R, typename std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr>
+ template<typename T, typename R, std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr>
#else
template<typename T, typename R>
#endif
@@ -63,7 +64,11 @@ public:
#ifndef __DOXYGEN__
template<typename T, typename Func,
- typename std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr>
+ std::enable_if_t<std::is_base_of<Object, T>::value
+#if __cplusplus >= 201703L
+ && std::is_invocable_v<Func, Args...>
+#endif
+ > * = nullptr>
void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto)
{
Object *object = static_cast<Object *>(obj);
@@ -71,7 +76,11 @@ public:
}
template<typename T, typename Func,
- typename std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr>
+ std::enable_if_t<!std::is_base_of<Object, T>::value
+#if __cplusplus >= 201703L
+ && std::is_invocable_v<Func, Args...>
+#endif
+ > * = nullptr>
#else
template<typename T, typename Func>
#endif
diff --git a/include/libcamera/base/thread_annotations.h b/include/libcamera/base/thread_annotations.h
index e81929f6..25b3c7b6 100644
--- a/include/libcamera/base/thread_annotations.h
+++ b/include/libcamera/base/thread_annotations.h
@@ -7,6 +7,8 @@
#pragma once
+#include <libcamera/base/private.h>
+
/*
* Enable thread safety attributes only with clang.
* The attributes can be safely erased when compiling with other compilers.
diff --git a/include/libcamera/base/utils.h b/include/libcamera/base/utils.h
index cfff0583..922e4dfa 100644
--- a/include/libcamera/base/utils.h
+++ b/include/libcamera/base/utils.h
@@ -170,6 +170,12 @@ public:
class iterator
{
public:
+ using difference_type = std::size_t;
+ using value_type = std::string;
+ using pointer = value_type *;
+ using reference = value_type &;
+ using iterator_category = std::input_iterator_tag;
+
iterator(const StringSplitter *ss, std::string::size_type pos);
iterator &operator++();
@@ -361,6 +367,14 @@ decltype(auto) abs_diff(const T &a, const T &b)
return a - b;
}
+double strtod(const char *__restrict nptr, char **__restrict endptr);
+
+template<class Enum>
+constexpr std::underlying_type_t<Enum> to_underlying(Enum e) noexcept
+{
+ return static_cast<std::underlying_type_t<Enum>>(e);
+}
+
} /* namespace utils */
#ifndef __DOXYGEN__
diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h
index 5bb06584..ae35792d 100644
--- a/include/libcamera/camera.h
+++ b/include/libcamera/camera.h
@@ -7,7 +7,9 @@
#pragma once
+#include <initializer_list>
#include <memory>
+#include <optional>
#include <set>
#include <stdint.h>
#include <string>
@@ -18,9 +20,10 @@
#include <libcamera/base/signal.h>
#include <libcamera/controls.h>
+#include <libcamera/geometry.h>
+#include <libcamera/orientation.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
-#include <libcamera/transform.h>
namespace libcamera {
@@ -29,6 +32,30 @@ class FrameBufferAllocator;
class PipelineHandler;
class Request;
+class SensorConfiguration
+{
+public:
+ unsigned int bitDepth = 0;
+
+ Rectangle analogCrop;
+
+ struct {
+ unsigned int binX = 1;
+ unsigned int binY = 1;
+ } binning;
+
+ struct {
+ unsigned int xOddInc = 1;
+ unsigned int xEvenInc = 1;
+ unsigned int yOddInc = 1;
+ unsigned int yEvenInc = 1;
+ } skipping;
+
+ Size outputSize;
+
+ bool isValid() const;
+};
+
class CameraConfiguration
{
public:
@@ -65,7 +92,8 @@ public:
bool empty() const;
std::size_t size() const;
- Transform transform;
+ std::optional<SensorConfiguration> sensorConfig;
+ Orientation orientation;
protected:
CameraConfiguration();
@@ -105,7 +133,16 @@ public:
const ControlList &properties() const;
const std::set<Stream *> &streams() const;
- std::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});
+
+ std::unique_ptr<CameraConfiguration>
+ generateConfiguration(Span<const StreamRole> roles = {});
+
+ std::unique_ptr<CameraConfiguration>
+ generateConfiguration(std::initializer_list<StreamRole> roles)
+ {
+ return generateConfiguration(Span(roles.begin(), roles.end()));
+ }
+
int configure(CameraConfiguration *config);
std::unique_ptr<Request> createRequest(uint64_t cookie = 0);
diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
index 7647c2a1..1a891cac 100644
--- a/include/libcamera/camera_manager.h
+++ b/include/libcamera/camera_manager.h
@@ -31,12 +31,7 @@ public:
void stop();
std::vector<std::shared_ptr<Camera>> cameras() const;
- std::shared_ptr<Camera> get(const std::string &name);
- std::shared_ptr<Camera> get(dev_t devnum);
-
- void addCamera(std::shared_ptr<Camera> camera,
- const std::vector<dev_t> &devnums);
- void removeCamera(std::shared_ptr<Camera> camera);
+ std::shared_ptr<Camera> get(const std::string &id);
static const std::string &version() { return version_; }
diff --git a/include/libcamera/color_space.h b/include/libcamera/color_space.h
index 086c56c1..6d6c2829 100644
--- a/include/libcamera/color_space.h
+++ b/include/libcamera/color_space.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2021, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2021, Raspberry Pi Ltd
*
* color_space.h - color space definitions
*/
@@ -12,6 +12,8 @@
namespace libcamera {
+class PixelFormat;
+
class ColorSpace
{
public:
@@ -46,8 +48,8 @@ public:
}
static const ColorSpace Raw;
- static const ColorSpace Jpeg;
static const ColorSpace Srgb;
+ static const ColorSpace Sycc;
static const ColorSpace Smpte170m;
static const ColorSpace Rec709;
static const ColorSpace Rec2020;
@@ -59,6 +61,10 @@ public:
std::string toString() const;
static std::string toString(const std::optional<ColorSpace> &colorSpace);
+
+ static std::optional<ColorSpace> fromString(const std::string &str);
+
+ bool adjust(PixelFormat format);
};
bool operator==(const ColorSpace &lhs, const ColorSpace &rhs);
diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
index 0718a888..d53b1cf7 100644
--- a/include/libcamera/control_ids.h.in
+++ b/include/libcamera/control_ids.h.in
@@ -26,11 +26,7 @@ ${controls}
extern const ControlIdMap controls;
-namespace draft {
-
-${draft_controls}
-
-} /* namespace draft */
+${vendor_controls}
} /* namespace controls */
diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 665bcac1..82b95599 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -8,6 +8,7 @@
#pragma once
#include <assert.h>
+#include <optional>
#include <set>
#include <stdint.h>
#include <string>
@@ -98,10 +99,10 @@ public:
ControlValue();
#ifndef __DOXYGEN__
- template<typename T, typename std::enable_if_t<!details::is_span<T>::value &&
- details::control_type<T>::value &&
- !std::is_same<std::string, std::remove_cv_t<T>>::value,
- std::nullptr_t> = nullptr>
+ template<typename T, std::enable_if_t<!details::is_span<T>::value &&
+ details::control_type<T>::value &&
+ !std::is_same<std::string, std::remove_cv_t<T>>::value,
+ std::nullptr_t> = nullptr>
ControlValue(const T &value)
: type_(ControlTypeNone), numElements_(0)
{
@@ -109,9 +110,9 @@ public:
&value, 1, sizeof(T));
}
- template<typename T, typename std::enable_if_t<details::is_span<T>::value ||
- std::is_same<std::string, std::remove_cv_t<T>>::value,
- std::nullptr_t> = nullptr>
+ template<typename T, std::enable_if_t<details::is_span<T>::value ||
+ std::is_same<std::string, std::remove_cv_t<T>>::value,
+ std::nullptr_t> = nullptr>
#else
template<typename T>
#endif
@@ -143,9 +144,9 @@ public:
}
#ifndef __DOXYGEN__
- template<typename T, typename std::enable_if_t<!details::is_span<T>::value &&
- !std::is_same<std::string, std::remove_cv_t<T>>::value,
- std::nullptr_t> = nullptr>
+ template<typename T, std::enable_if_t<!details::is_span<T>::value &&
+ !std::is_same<std::string, std::remove_cv_t<T>>::value,
+ std::nullptr_t> = nullptr>
T get() const
{
assert(type_ == details::control_type<std::remove_cv_t<T>>::value);
@@ -154,9 +155,9 @@ public:
return *reinterpret_cast<const T *>(data().data());
}
- template<typename T, typename std::enable_if_t<details::is_span<T>::value ||
- std::is_same<std::string, std::remove_cv_t<T>>::value,
- std::nullptr_t> = nullptr>
+ template<typename T, std::enable_if_t<details::is_span<T>::value ||
+ std::is_same<std::string, std::remove_cv_t<T>>::value,
+ std::nullptr_t> = nullptr>
#else
template<typename T>
#endif
@@ -167,22 +168,22 @@ public:
using V = typename T::value_type;
const V *value = reinterpret_cast<const V *>(data().data());
- return { value, numElements_ };
+ return T{ value, numElements_ };
}
#ifndef __DOXYGEN__
- template<typename T, typename std::enable_if_t<!details::is_span<T>::value &&
- !std::is_same<std::string, std::remove_cv_t<T>>::value,
- std::nullptr_t> = nullptr>
+ template<typename T, std::enable_if_t<!details::is_span<T>::value &&
+ !std::is_same<std::string, std::remove_cv_t<T>>::value,
+ std::nullptr_t> = nullptr>
void set(const T &value)
{
set(details::control_type<std::remove_cv_t<T>>::value, false,
reinterpret_cast<const void *>(&value), 1, sizeof(T));
}
- template<typename T, typename std::enable_if_t<details::is_span<T>::value ||
- std::is_same<std::string, std::remove_cv_t<T>>::value,
- std::nullptr_t> = nullptr>
+ template<typename T, std::enable_if_t<details::is_span<T>::value ||
+ std::is_same<std::string, std::remove_cv_t<T>>::value,
+ std::nullptr_t> = nullptr>
#else
template<typename T>
#endif
@@ -267,9 +268,9 @@ private:
class ControlInfo
{
public:
- explicit ControlInfo(const ControlValue &min = 0,
- const ControlValue &max = 0,
- const ControlValue &def = 0);
+ explicit ControlInfo(const ControlValue &min = {},
+ const ControlValue &max = {},
+ const ControlValue &def = {});
explicit ControlInfo(Span<const ControlValue> values,
const ControlValue &def = {});
explicit ControlInfo(std::set<bool> values, bool def);
@@ -351,6 +352,11 @@ private:
using ControlListMap = std::unordered_map<unsigned int, ControlValue>;
public:
+ enum class MergePolicy {
+ KeepExisting = 0,
+ OverwriteExisting,
+ };
+
ControlList();
ControlList(const ControlIdMap &idmap, const ControlValidator *validator = nullptr);
ControlList(const ControlInfoMap &infoMap, const ControlValidator *validator = nullptr);
@@ -367,19 +373,19 @@ public:
std::size_t size() const { return controls_.size(); }
void clear() { controls_.clear(); }
- void merge(const ControlList &source);
+ void merge(const ControlList &source, MergePolicy policy = MergePolicy::KeepExisting);
- bool contains(const ControlId &id) const;
bool contains(unsigned int id) const;
template<typename T>
- T get(const Control<T> &ctrl) const
+ std::optional<T> get(const Control<T> &ctrl) const
{
- const ControlValue *val = find(ctrl.id());
- if (!val)
- return T{};
+ const auto entry = controls_.find(ctrl.id());
+ if (entry == controls_.end())
+ return std::nullopt;
- return val->get<T>();
+ const ControlValue &val = entry->second;
+ return val.get<T>();
}
template<typename T, typename V>
@@ -392,14 +398,14 @@ public:
val->set<T>(value);
}
- template<typename T, typename V>
- void set(const Control<T> &ctrl, const std::initializer_list<V> &value)
+ template<typename T, typename V, size_t Size>
+ void set(const Control<Span<T, Size>> &ctrl, const std::initializer_list<V> &value)
{
ControlValue *val = find(ctrl.id());
if (!val)
return;
- val->set<T>(Span<const typename std::remove_cv_t<V>>{ value.begin(), value.size() });
+ val->set(Span<const typename std::remove_cv_t<V>, Size>{ value.begin(), value.size() });
}
const ControlValue &get(unsigned int id) const;
diff --git a/include/libcamera/framebuffer.h b/include/libcamera/framebuffer.h
index 3b1118d1..61244829 100644
--- a/include/libcamera/framebuffer.h
+++ b/include/libcamera/framebuffer.h
@@ -46,7 +46,7 @@ private:
std::vector<Plane> planes_;
};
-class FrameBuffer final : public Extensible
+class FrameBuffer : public Extensible
{
LIBCAMERA_DECLARE_PRIVATE()
@@ -59,28 +59,20 @@ public:
};
FrameBuffer(const std::vector<Plane> &planes, unsigned int cookie = 0);
- FrameBuffer(std::unique_ptr<Private> d,
- const std::vector<Plane> &planes, unsigned int cookie = 0);
+ FrameBuffer(std::unique_ptr<Private> d);
+ virtual ~FrameBuffer() {}
- const std::vector<Plane> &planes() const { return planes_; }
+ const std::vector<Plane> &planes() const;
Request *request() const;
- const FrameMetadata &metadata() const { return metadata_; }
+ const FrameMetadata &metadata() const;
- unsigned int cookie() const { return cookie_; }
- void setCookie(unsigned int cookie) { cookie_ = cookie; }
+ uint64_t cookie() const;
+ void setCookie(uint64_t cookie);
std::unique_ptr<Fence> releaseFence();
private:
LIBCAMERA_DISABLE_COPY_AND_MOVE(FrameBuffer)
-
- friend class V4L2VideoDevice; /* Needed to update metadata_. */
-
- std::vector<Plane> planes_;
-
- FrameMetadata metadata_;
-
- unsigned int cookie_;
};
} /* namespace libcamera */
diff --git a/include/libcamera/internal/bayer_format.h b/include/libcamera/internal/bayer_format.h
index 7d3e37c6..78ba3969 100644
--- a/include/libcamera/internal/bayer_format.h
+++ b/include/libcamera/internal/bayer_format.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* bayer_format.h - Bayer Pixel Format
*/
diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h
index 597426a6..38dd94ff 100644
--- a/include/libcamera/internal/camera.h
+++ b/include/libcamera/internal/camera.h
@@ -50,6 +50,7 @@ private:
CameraRunning,
};
+ bool isAcquired() const;
bool isRunning() const;
int isAccessAllowed(State state, bool allowDisconnected = false,
const char *from = __builtin_FUNCTION()) const;
diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
new file mode 100644
index 00000000..33ebe069
--- /dev/null
+++ b/include/libcamera/internal/camera_manager.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Ideas on Board Oy.
+ *
+ * camera_manager.h - Camera manager private data
+ */
+
+#pragma once
+
+#include <libcamera/camera_manager.h>
+
+#include <map>
+#include <memory>
+#include <sys/types.h>
+#include <vector>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/mutex.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/thread_annotations.h>
+
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/process.h"
+
+namespace libcamera {
+
+class Camera;
+class DeviceEnumerator;
+
+class CameraManager::Private : public Extensible::Private, public Thread
+{
+ LIBCAMERA_DECLARE_PUBLIC(CameraManager)
+
+public:
+ Private();
+
+ int start();
+ void addCamera(std::shared_ptr<Camera> camera) LIBCAMERA_TSA_EXCLUDES(mutex_);
+ void removeCamera(std::shared_ptr<Camera> camera) LIBCAMERA_TSA_EXCLUDES(mutex_);
+
+protected:
+ void run() override;
+
+private:
+ int init();
+ void createPipelineHandlers();
+ void cleanup() LIBCAMERA_TSA_EXCLUDES(mutex_);
+
+ /*
+ * This mutex protects
+ *
+ * - initialized_ and status_ during initialization
+ * - cameras_ after initialization
+ */
+ mutable Mutex mutex_;
+ std::vector<std::shared_ptr<Camera>> cameras_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
+
+ ConditionVariable cv_;
+ bool initialized_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
+ int status_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
+
+ std::unique_ptr<DeviceEnumerator> enumerator_;
+
+ IPAManager ipaManager_;
+ ProcessManager processManager_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/camera_sensor.h b/include/libcamera/internal/camera_sensor.h
index b9f4d786..d05f48eb 100644
--- a/include/libcamera/internal/camera_sensor.h
+++ b/include/libcamera/internal/camera_sensor.h
@@ -17,20 +17,25 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/geometry.h>
+#include <libcamera/orientation.h>
+#include <libcamera/transform.h>
#include <libcamera/ipa/core_ipa_interface.h>
+#include "libcamera/internal/bayer_format.h"
#include "libcamera/internal/formats.h"
#include "libcamera/internal/v4l2_subdevice.h"
namespace libcamera {
-class BayerFormat;
class CameraLens;
class MediaEntity;
+class SensorConfiguration;
struct CameraSensorProperties;
+enum class Orientation;
+
class CameraSensor : protected Loggable
{
public:
@@ -41,32 +46,40 @@ public:
const std::string &model() const { return model_; }
const std::string &id() const { return id_; }
+
const MediaEntity *entity() const { return entity_; }
+ V4L2Subdevice *device() { return subdev_.get(); }
+
+ CameraLens *focusLens() { return focusLens_.get(); }
+
const std::vector<unsigned int> &mbusCodes() const { return mbusCodes_; }
std::vector<Size> sizes(unsigned int mbusCode) const;
Size resolution() const;
- const std::vector<controls::draft::TestPatternModeEnum> &testPatternModes() const
- {
- return testPatternModes_;
- }
- int setTestPatternMode(controls::draft::TestPatternModeEnum mode);
V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,
const Size &size) const;
- int setFormat(V4L2SubdeviceFormat *format);
+ int setFormat(V4L2SubdeviceFormat *format,
+ Transform transform = Transform::Identity);
+ int tryFormat(V4L2SubdeviceFormat *format) const;
- const ControlInfoMap &controls() const;
- ControlList getControls(const std::vector<uint32_t> &ids);
- int setControls(ControlList *ctrls);
-
- V4L2Subdevice *device() { return subdev_.get(); }
+ int applyConfiguration(const SensorConfiguration &config,
+ Transform transform = Transform::Identity,
+ V4L2SubdeviceFormat *sensorFormat = nullptr);
const ControlList &properties() const { return properties_; }
int sensorInfo(IPACameraSensorInfo *info) const;
+ Transform computeTransform(Orientation *orientation) const;
+ BayerFormat::Order bayerOrder(Transform t) const;
- void updateControlInfo();
+ const ControlInfoMap &controls() const;
+ ControlList getControls(const std::vector<uint32_t> &ids);
+ int setControls(ControlList *ctrls);
- CameraLens *focusLens() { return focusLens_.get(); }
+ const std::vector<controls::draft::TestPatternModeEnum> &testPatternModes() const
+ {
+ return testPatternModes_;
+ }
+ int setTestPatternMode(controls::draft::TestPatternModeEnum mode);
protected:
std::string logPrefix() const override;
@@ -80,8 +93,8 @@ private:
void initStaticProperties();
void initTestPatternModes();
int initProperties();
- int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);
int discoverAncillaryDevices();
+ int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);
const MediaEntity *entity_;
std::unique_ptr<V4L2Subdevice> subdev_;
@@ -101,6 +114,9 @@ private:
Size pixelArraySize_;
Rectangle activeArea_;
const BayerFormat *bayerFormat_;
+ bool supportFlips_;
+ bool flipsAlterBayerOrder_;
+ Orientation mountingOrientation_;
ControlList properties_;
diff --git a/include/libcamera/internal/control_serializer.h b/include/libcamera/internal/control_serializer.h
index 99e57fee..a38ca6b0 100644
--- a/include/libcamera/internal/control_serializer.h
+++ b/include/libcamera/internal/control_serializer.h
@@ -47,9 +47,9 @@ private:
static void store(const ControlValue &value, ByteStreamBuffer &buffer);
static void store(const ControlInfo &info, ByteStreamBuffer &buffer);
- ControlValue loadControlValue(ControlType type, ByteStreamBuffer &buffer,
+ ControlValue loadControlValue(ByteStreamBuffer &buffer,
bool isArray = false, unsigned int count = 1);
- ControlInfo loadControlInfo(ControlType type, ByteStreamBuffer &buffer);
+ ControlInfo loadControlInfo(ByteStreamBuffer &buffer);
unsigned int serial_;
unsigned int serialSeed_;
diff --git a/include/libcamera/internal/converter.h b/include/libcamera/internal/converter.h
new file mode 100644
index 00000000..834ec5ab
--- /dev/null
+++ b/include/libcamera/internal/converter.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
+ *
+ * converter.h - Generic format converter interface
+ */
+
+#pragma once
+
+#include <functional>
+#include <initializer_list>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+
+namespace libcamera {
+
+class FrameBuffer;
+class MediaDevice;
+class PixelFormat;
+struct StreamConfiguration;
+
+class Converter
+{
+public:
+ Converter(MediaDevice *media);
+ virtual ~Converter();
+
+ virtual int loadConfiguration(const std::string &filename) = 0;
+
+ virtual bool isValid() const = 0;
+
+ virtual std::vector<PixelFormat> formats(PixelFormat input) = 0;
+ virtual SizeRange sizes(const Size &input) = 0;
+
+ virtual std::tuple<unsigned int, unsigned int>
+ strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) = 0;
+
+ virtual int configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0;
+ virtual int exportBuffers(unsigned int output, unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0;
+
+ virtual int start() = 0;
+ virtual void stop() = 0;
+
+ virtual int queueBuffers(FrameBuffer *input,
+ const std::map<unsigned int, FrameBuffer *> &outputs) = 0;
+
+ Signal<FrameBuffer *> inputBufferReady;
+ Signal<FrameBuffer *> outputBufferReady;
+
+ const std::string &deviceNode() const { return deviceNode_; }
+
+private:
+ std::string deviceNode_;
+};
+
+class ConverterFactoryBase
+{
+public:
+ ConverterFactoryBase(const std::string name, std::initializer_list<std::string> compatibles);
+ virtual ~ConverterFactoryBase() = default;
+
+ const std::vector<std::string> &compatibles() const { return compatibles_; }
+
+ static std::unique_ptr<Converter> create(MediaDevice *media);
+ static std::vector<ConverterFactoryBase *> &factories();
+ static std::vector<std::string> names();
+
+private:
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(ConverterFactoryBase)
+
+ static void registerType(ConverterFactoryBase *factory);
+
+ virtual std::unique_ptr<Converter> createInstance(MediaDevice *media) const = 0;
+
+ std::string name_;
+ std::vector<std::string> compatibles_;
+};
+
+template<typename _Converter>
+class ConverterFactory : public ConverterFactoryBase
+{
+public:
+ ConverterFactory(const char *name, std::initializer_list<std::string> compatibles)
+ : ConverterFactoryBase(name, compatibles)
+ {
+ }
+
+ std::unique_ptr<Converter> createInstance(MediaDevice *media) const override
+ {
+ return std::make_unique<_Converter>(media);
+ }
+};
+
+#define REGISTER_CONVERTER(name, converter, compatibles) \
+ static ConverterFactory<converter> global_##converter##Factory(name, compatibles);
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/converter.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
index f0ebe2e0..84fb485f 100644
--- a/src/libcamera/pipeline/simple/converter.h
+++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
@@ -1,8 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
*
- * converter.h - Format converter for simple pipeline handler
+ * converter_v4l2_m2m.h - V4l2 M2M Format converter interface
*/
#pragma once
@@ -14,11 +15,13 @@
#include <tuple>
#include <vector>
-#include <libcamera/pixel_format.h>
-
#include <libcamera/base/log.h>
#include <libcamera/base/signal.h>
+#include <libcamera/pixel_format.h>
+
+#include "libcamera/internal/converter.h"
+
namespace libcamera {
class FrameBuffer;
@@ -28,11 +31,12 @@ class SizeRange;
struct StreamConfiguration;
class V4L2M2MDevice;
-class SimpleConverter
+class V4L2M2MConverter : public Converter
{
public:
- SimpleConverter(MediaDevice *media);
+ V4L2M2MConverter(MediaDevice *media);
+ int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
bool isValid() const { return m2m_ != nullptr; }
std::vector<PixelFormat> formats(PixelFormat input);
@@ -43,7 +47,7 @@ public:
int configure(const StreamConfiguration &inputCfg,
const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);
- int exportBuffers(unsigned int ouput, unsigned int count,
+ int exportBuffers(unsigned int output, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers);
int start();
@@ -52,14 +56,11 @@ public:
int queueBuffers(FrameBuffer *input,
const std::map<unsigned int, FrameBuffer *> &outputs);
- Signal<FrameBuffer *> inputBufferReady;
- Signal<FrameBuffer *> outputBufferReady;
-
private:
class Stream : protected Loggable
{
public:
- Stream(SimpleConverter *converter, unsigned int index);
+ Stream(V4L2M2MConverter *converter, unsigned int index);
bool isValid() const { return m2m_ != nullptr; }
@@ -80,7 +81,7 @@ private:
void captureBufferReady(FrameBuffer *buffer);
void outputBufferReady(FrameBuffer *buffer);
- SimpleConverter *converter_;
+ V4L2M2MConverter *converter_;
unsigned int index_;
std::unique_ptr<V4L2M2MDevice> m2m_;
@@ -88,7 +89,6 @@ private:
unsigned int outputBufferCount_;
};
- std::string deviceNode_;
std::unique_ptr<V4L2M2MDevice> m2m_;
std::vector<Stream> streams_;
diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build
new file mode 100644
index 00000000..891e79e7
--- /dev/null
+++ b/include/libcamera/internal/converter/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_internal_headers += files([
+ 'converter_v4l2_m2m.h',
+])
diff --git a/include/libcamera/internal/delayed_controls.h b/include/libcamera/internal/delayed_controls.h
index 703fdb66..aef37077 100644
--- a/include/libcamera/internal/delayed_controls.h
+++ b/include/libcamera/internal/delayed_controls.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* delayed_controls.h - Helper to deal with controls that take effect with a delay
*/
@@ -51,7 +51,7 @@ private:
bool updated;
};
- /* \todo: Make the listSize configurable at instance creation time. */
+ /* \todo Make the listSize configurable at instance creation time. */
static constexpr int listSize = 16;
class ControlRingBuffer : public std::array<Info, listSize>
{
@@ -72,9 +72,6 @@ private:
std::unordered_map<const ControlId *, ControlParams> controlParams_;
unsigned int maxDelay_;
- bool running_;
- uint32_t firstSequence_;
-
uint32_t queueCount_;
uint32_t writeCount_;
/* \todo Evaluate if we should index on ControlId * or unsigned int */
diff --git a/src/libcamera/pipeline/raspberrypi/dma_heaps.h b/include/libcamera/internal/dma_heaps.h
index d38f41ea..80bf29e7 100644
--- a/src/libcamera/pipeline/raspberrypi/dma_heaps.h
+++ b/include/libcamera/internal/dma_heaps.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* dma_heaps.h - Helper class for dma-heap allocations.
*/
@@ -9,16 +9,22 @@
#include <stddef.h>
+#include <libcamera/base/flags.h>
#include <libcamera/base/unique_fd.h>
namespace libcamera {
-namespace RPi {
-
class DmaHeap
{
public:
- DmaHeap();
+ enum class DmaHeapFlag {
+ Cma = 1 << 0,
+ System = 1 << 1,
+ };
+
+ using DmaHeapFlags = Flags<DmaHeapFlag>;
+
+ DmaHeap(DmaHeapFlags flags = DmaHeapFlag::Cma);
~DmaHeap();
bool isValid() const { return dmaHeapHandle_.isValid(); }
UniqueFD alloc(const char *name, std::size_t size);
@@ -27,6 +33,6 @@ private:
UniqueFD dmaHeapHandle_;
};
-} /* namespace RPi */
+LIBCAMERA_FLAGS_ENABLE_OPERATORS(DmaHeap::DmaHeapFlag)
} /* namespace libcamera */
diff --git a/include/libcamera/internal/formats.h b/include/libcamera/internal/formats.h
index ee599765..5b16c0a8 100644
--- a/include/libcamera/internal/formats.h
+++ b/include/libcamera/internal/formats.h
@@ -53,10 +53,7 @@ public:
/* \todo Add support for non-contiguous memory planes */
const char *name;
PixelFormat format;
- struct {
- V4L2PixelFormat single;
- V4L2PixelFormat multi;
- } v4l2Formats;
+ std::vector<V4L2PixelFormat> v4l2Formats;
unsigned int bitsPerPixel;
enum ColourEncoding colourEncoding;
bool packed;
diff --git a/include/libcamera/internal/framebuffer.h b/include/libcamera/internal/framebuffer.h
index 8a9cc98e..1f42a4fc 100644
--- a/include/libcamera/internal/framebuffer.h
+++ b/include/libcamera/internal/framebuffer.h
@@ -22,7 +22,7 @@ class FrameBuffer::Private : public Extensible::Private
LIBCAMERA_DECLARE_PUBLIC(FrameBuffer)
public:
- Private();
+ Private(const std::vector<Plane> &planes, uint64_t cookie = 0);
virtual ~Private();
void setRequest(Request *request) { request_ = request; }
@@ -31,9 +31,15 @@ public:
Fence *fence() const { return fence_.get(); }
void setFence(std::unique_ptr<Fence> fence) { fence_ = std::move(fence); }
- void cancel() { LIBCAMERA_O_PTR()->metadata_.status = FrameMetadata::FrameCancelled; }
+ void cancel() { metadata_.status = FrameMetadata::FrameCancelled; }
+
+ FrameMetadata &metadata() { return metadata_; }
private:
+ std::vector<Plane> planes_;
+ FrameMetadata metadata_;
+ uint64_t cookie_;
+
std::unique_ptr<Fence> fence_;
Request *request_;
bool isContiguous_;
diff --git a/include/libcamera/internal/ipa_data_serializer.h b/include/libcamera/internal/ipa_data_serializer.h
index a87449c9..085f1fed 100644
--- a/include/libcamera/internal/ipa_data_serializer.h
+++ b/include/libcamera/internal/ipa_data_serializer.h
@@ -14,6 +14,7 @@
#include <type_traits>
#include <vector>
+#include <libcamera/base/flags.h>
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
@@ -32,7 +33,7 @@ LOG_DECLARE_CATEGORY(IPADataSerializer)
namespace {
template<typename T,
- typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
+ std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
void appendPOD(std::vector<uint8_t> &vec, T val)
{
constexpr size_t byteWidth = sizeof(val);
@@ -134,7 +135,7 @@ public:
static std::vector<V> deserialize(std::vector<uint8_t> &data, ControlSerializer *cs = nullptr)
{
- return deserialize(data.cbegin(), data.end(), cs);
+ return deserialize(data.cbegin(), data.cend(), cs);
}
static std::vector<V> deserialize(std::vector<uint8_t>::const_iterator dataBegin,
@@ -142,13 +143,13 @@ public:
ControlSerializer *cs = nullptr)
{
std::vector<SharedFD> fds;
- return deserialize(dataBegin, dataEnd, fds.cbegin(), fds.end(), cs);
+ return deserialize(dataBegin, dataEnd, fds.cbegin(), fds.cend(), cs);
}
static std::vector<V> deserialize(std::vector<uint8_t> &data, std::vector<SharedFD> &fds,
ControlSerializer *cs = nullptr)
{
- return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
+ return deserialize(data.cbegin(), data.cend(), fds.cbegin(), fds.cend(), cs);
}
static std::vector<V> deserialize(std::vector<uint8_t>::const_iterator dataBegin,
@@ -240,7 +241,7 @@ public:
static std::map<K, V> deserialize(std::vector<uint8_t> &data, ControlSerializer *cs = nullptr)
{
- return deserialize(data.cbegin(), data.end(), cs);
+ return deserialize(data.cbegin(), data.cend(), cs);
}
static std::map<K, V> deserialize(std::vector<uint8_t>::const_iterator dataBegin,
@@ -248,13 +249,13 @@ public:
ControlSerializer *cs = nullptr)
{
std::vector<SharedFD> fds;
- return deserialize(dataBegin, dataEnd, fds.cbegin(), fds.end(), cs);
+ return deserialize(dataBegin, dataEnd, fds.cbegin(), fds.cend(), cs);
}
static std::map<K, V> deserialize(std::vector<uint8_t> &data, std::vector<SharedFD> &fds,
ControlSerializer *cs = nullptr)
{
- return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
+ return deserialize(data.cbegin(), data.cend(), fds.cbegin(), fds.cend(), cs);
}
static std::map<K, V> deserialize(std::vector<uint8_t>::const_iterator dataBegin,
@@ -301,6 +302,51 @@ public:
}
};
+/* Serialization format for Flags is same as for PODs */
+template<typename E>
+class IPADataSerializer<Flags<E>>
+{
+public:
+ static std::tuple<std::vector<uint8_t>, std::vector<SharedFD>>
+ serialize(const Flags<E> &data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+ {
+ std::vector<uint8_t> dataVec;
+ dataVec.reserve(sizeof(Flags<E>));
+ appendPOD<uint32_t>(dataVec, static_cast<typename Flags<E>::Type>(data));
+
+ return { dataVec, {} };
+ }
+
+ static Flags<E> deserialize(std::vector<uint8_t> &data,
+ [[maybe_unused]] ControlSerializer *cs = nullptr)
+ {
+ return deserialize(data.cbegin(), data.cend());
+ }
+
+ static Flags<E> deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ [[maybe_unused]] ControlSerializer *cs = nullptr)
+ {
+ return Flags<E>{ static_cast<E>(readPOD<uint32_t>(dataBegin, 0, dataEnd)) };
+ }
+
+ static Flags<E> deserialize(std::vector<uint8_t> &data,
+ [[maybe_unused]] std::vector<SharedFD> &fds,
+ [[maybe_unused]] ControlSerializer *cs = nullptr)
+ {
+ return deserialize(data.cbegin(), data.cend());
+ }
+
+ static Flags<E> deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+ std::vector<uint8_t>::const_iterator dataEnd,
+ [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsBegin,
+ [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsEnd,
+ [[maybe_unused]] ControlSerializer *cs = nullptr)
+ {
+ return deserialize(dataBegin, dataEnd);
+ }
+};
+
#endif /* __DOXYGEN__ */
} /* namespace libcamera */
diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
index 7f36e58e..bf823563 100644
--- a/include/libcamera/internal/ipa_manager.h
+++ b/include/libcamera/internal/ipa_manager.h
@@ -47,6 +47,13 @@ public:
return proxy;
}
+#if HAVE_IPA_PUBKEY
+ static const PubKey &pubKey()
+ {
+ return pubKey_;
+ }
+#endif
+
private:
static IPAManager *self_;
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 7a780d48..160fdc37 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -4,9 +4,9 @@ subdir('tracepoints')
libcamera_tracepoint_header = custom_target(
'tp_header',
- input: ['tracepoints.h.in', tracepoint_files],
- output: 'tracepoints.h',
- command: [gen_tracepoints_header, '@OUTPUT@', '@INPUT@'],
+ input : ['tracepoints.h.in', tracepoint_files],
+ output : 'tracepoints.h',
+ command : [gen_tracepoints_header, include_build_dir, '@OUTPUT@', '@INPUT@'],
)
libcamera_internal_headers = files([
@@ -15,14 +15,17 @@ libcamera_internal_headers = files([
'camera.h',
'camera_controls.h',
'camera_lens.h',
+ 'camera_manager.h',
'camera_sensor.h',
'camera_sensor_properties.h',
'control_serializer.h',
'control_validator.h',
+ 'converter.h',
'delayed_controls.h',
'device_enumerator.h',
'device_enumerator_sysfs.h',
'device_enumerator_udev.h',
+ 'dma_heaps.h',
'formats.h',
'framebuffer.h',
'ipa_manager.h',
@@ -36,6 +39,7 @@ libcamera_internal_headers = files([
'process.h',
'pub_key.h',
'request.h',
+ 'shared_mem_object.h',
'source_paths.h',
'sysfs.h',
'v4l2_device.h',
@@ -44,3 +48,6 @@ libcamera_internal_headers = files([
'v4l2_videodevice.h',
'yaml_parser.h',
])
+
+subdir('converter')
+subdir('software_isp')
diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h
index c3e4c258..c96944f4 100644
--- a/include/libcamera/internal/pipeline_handler.h
+++ b/include/libcamera/internal/pipeline_handler.h
@@ -45,11 +45,11 @@ public:
MediaDevice *acquireMediaDevice(DeviceEnumerator *enumerator,
const DeviceMatch &dm);
- bool lock();
- void unlock();
+ bool acquire();
+ void release(Camera *camera);
- virtual CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) = 0;
+ virtual std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) = 0;
virtual int configure(Camera *camera, CameraConfiguration *config) = 0;
virtual int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -65,6 +65,9 @@ public:
bool completeBuffer(Request *request, FrameBuffer *buffer);
void completeRequest(Request *request);
+ std::string configurationFile(const std::string &subdir,
+ const std::string &name) const;
+
const char *name() const { return name_; }
protected:
@@ -74,9 +77,13 @@ protected:
virtual int queueRequestDevice(Camera *camera, Request *request) = 0;
virtual void stopDevice(Camera *camera) = 0;
+ virtual void releaseDevice(Camera *camera);
+
CameraManager *manager_;
private:
+ void unlockMediaDevices();
+
void mediaDeviceDisconnected(MediaDevice *media);
virtual void disconnect();
@@ -91,42 +98,49 @@ private:
const char *name_;
Mutex lock_;
- bool lockOwner_ LIBCAMERA_TSA_GUARDED_BY(lock_); /* *Not* ownership of lock_ */
+ unsigned int useCount_ LIBCAMERA_TSA_GUARDED_BY(lock_);
- friend class PipelineHandlerFactory;
+ friend class PipelineHandlerFactoryBase;
};
-class PipelineHandlerFactory
+class PipelineHandlerFactoryBase
{
public:
- PipelineHandlerFactory(const char *name);
- virtual ~PipelineHandlerFactory() = default;
+ PipelineHandlerFactoryBase(const char *name);
+ virtual ~PipelineHandlerFactoryBase() = default;
- std::shared_ptr<PipelineHandler> create(CameraManager *manager);
+ std::shared_ptr<PipelineHandler> create(CameraManager *manager) const;
const std::string &name() const { return name_; }
- static void registerType(PipelineHandlerFactory *factory);
- static std::vector<PipelineHandlerFactory *> &factories();
+ static std::vector<PipelineHandlerFactoryBase *> &factories();
private:
- virtual PipelineHandler *createInstance(CameraManager *manager) = 0;
+ static void registerType(PipelineHandlerFactoryBase *factory);
+
+ virtual std::unique_ptr<PipelineHandler>
+ createInstance(CameraManager *manager) const = 0;
std::string name_;
};
-#define REGISTER_PIPELINE_HANDLER(handler) \
-class handler##Factory final : public PipelineHandlerFactory \
-{ \
-public: \
- handler##Factory() : PipelineHandlerFactory(#handler) {} \
- \
-private: \
- PipelineHandler *createInstance(CameraManager *manager) \
- { \
- return new handler(manager); \
- } \
-}; \
-static handler##Factory global_##handler##Factory;
+template<typename _PipelineHandler>
+class PipelineHandlerFactory final : public PipelineHandlerFactoryBase
+{
+public:
+ PipelineHandlerFactory(const char *name)
+ : PipelineHandlerFactoryBase(name)
+ {
+ }
+
+ std::unique_ptr<PipelineHandler>
+ createInstance(CameraManager *manager) const override
+ {
+ return std::make_unique<_PipelineHandler>(manager);
+ }
+};
+
+#define REGISTER_PIPELINE_HANDLER(handler) \
+static PipelineHandlerFactory<handler> global_##handler##Factory(#handler);
} /* namespace libcamera */
diff --git a/include/libcamera/internal/pub_key.h b/include/libcamera/internal/pub_key.h
index a22ba037..8653a912 100644
--- a/include/libcamera/internal/pub_key.h
+++ b/include/libcamera/internal/pub_key.h
@@ -11,7 +11,9 @@
#include <libcamera/base/span.h>
-#if HAVE_GNUTLS
+#if HAVE_CRYPTO
+struct evp_pkey_st;
+#elif HAVE_GNUTLS
struct gnutls_pubkey_st;
#endif
@@ -28,7 +30,9 @@ public:
private:
bool valid_;
-#if HAVE_GNUTLS
+#if HAVE_CRYPTO
+ struct evp_pkey_st *pubkey_;
+#elif HAVE_GNUTLS
struct gnutls_pubkey_st *pubkey_;
#endif
};
diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h
index 9dadd6c6..3454cf5a 100644
--- a/include/libcamera/internal/request.h
+++ b/include/libcamera/internal/request.h
@@ -4,8 +4,8 @@
*
* request.h - Request class private data
*/
-#ifndef __LIBCAMERA_INTERNAL_REQUEST_H__
-#define __LIBCAMERA_INTERNAL_REQUEST_H__
+
+#pragma once
#include <chrono>
#include <map>
@@ -37,7 +37,7 @@ public:
bool completeBuffer(FrameBuffer *buffer);
void complete();
void cancel();
- void reuse();
+ void reset();
void prepare(std::chrono::milliseconds timeout = 0ms);
Signal<> prepared;
@@ -62,5 +62,3 @@ private:
};
} /* namespace libcamera */
-
-#endif /* __LIBCAMERA_INTERNAL_REQUEST_H__ */
diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
new file mode 100644
index 00000000..9b1d9393
--- /dev/null
+++ b/include/libcamera/internal/shared_mem_object.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ * Copyright (C) 2024 Andrei Konovalov
+ * Copyright (C) 2024 Dennis Bonke
+ *
+ * shared_mem_object.h - Helpers for shared memory allocations
+ */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <sys/mman.h>
+#include <type_traits>
+#include <utility>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/shared_fd.h>
+#include <libcamera/base/span.h>
+
+namespace libcamera {
+
+class SharedMem
+{
+public:
+ SharedMem();
+
+ SharedMem(const std::string &name, std::size_t size);
+ SharedMem(SharedMem &&rhs);
+
+ virtual ~SharedMem();
+
+ SharedMem &operator=(SharedMem &&rhs);
+
+ const SharedFD &fd() const
+ {
+ return fd_;
+ }
+
+ Span<uint8_t> mem() const
+ {
+ return mem_;
+ }
+
+ explicit operator bool() const
+ {
+ return !mem_.empty();
+ }
+
+private:
+ LIBCAMERA_DISABLE_COPY(SharedMem)
+
+ SharedFD fd_;
+
+ Span<uint8_t> mem_;
+};
+
+template<class T, typename = std::enable_if_t<std::is_standard_layout<T>::value>>
+class SharedMemObject : public SharedMem
+{
+public:
+ static constexpr std::size_t kSize = sizeof(T);
+
+ SharedMemObject()
+ : SharedMem(), obj_(nullptr)
+ {
+ }
+
+ template<class... Args>
+ SharedMemObject(const std::string &name, Args &&...args)
+ : SharedMem(name, kSize), obj_(nullptr)
+ {
+ if (mem().empty())
+ return;
+
+ obj_ = new (mem().data()) T(std::forward<Args>(args)...);
+ }
+
+ SharedMemObject(SharedMemObject<T> &&rhs)
+ : SharedMem(std::move(rhs))
+ {
+ this->obj_ = rhs.obj_;
+ rhs.obj_ = nullptr;
+ }
+
+ ~SharedMemObject()
+ {
+ if (obj_)
+ obj_->~T();
+ }
+
+ SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs)
+ {
+ SharedMem::operator=(std::move(rhs));
+ this->obj_ = rhs.obj_;
+ rhs.obj_ = nullptr;
+ return *this;
+ }
+
+ T *operator->()
+ {
+ return obj_;
+ }
+
+ const T *operator->() const
+ {
+ return obj_;
+ }
+
+ T &operator*()
+ {
+ return *obj_;
+ }
+
+ const T &operator*() const
+ {
+ return *obj_;
+ }
+
+private:
+ LIBCAMERA_DISABLE_COPY(SharedMemObject)
+
+ T *obj_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
new file mode 100644
index 00000000..32cd448a
--- /dev/null
+++ b/include/libcamera/internal/software_isp/debayer_params.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * debayer_params.h - DebayerParams header
+ */
+
+#pragma once
+
+namespace libcamera {
+
+struct DebayerParams {
+ static constexpr unsigned int kGain10 = 256;
+
+ unsigned int gainR;
+ unsigned int gainG;
+ unsigned int gainB;
+
+ float gamma;
+ /**
+ * \brief Level of the black point, 0..255, 0 is no correction.
+ */
+ unsigned int blackLevel;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
new file mode 100644
index 00000000..508ddddc
--- /dev/null
+++ b/include/libcamera/internal/software_isp/meson.build
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_internal_headers += files([
+ 'debayer_params.h',
+ 'software_isp.h',
+ 'swisp_stats.h',
+])
diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
new file mode 100644
index 00000000..42e96dcf
--- /dev/null
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * software_isp.h - Simple software ISP implementation
+ */
+
+#pragma once
+
+#include <functional>
+#include <initializer_list>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/signal.h>
+#include <libcamera/base/thread.h>
+
+#include <libcamera/geometry.h>
+#include <libcamera/pixel_format.h>
+
+#include <libcamera/ipa/soft_ipa_interface.h>
+#include <libcamera/ipa/soft_ipa_proxy.h>
+
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/dma_heaps.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/shared_mem_object.h"
+#include "libcamera/internal/software_isp/debayer_params.h"
+
+namespace libcamera {
+
+class DebayerCpu;
+class FrameBuffer;
+class PixelFormat;
+struct StreamConfiguration;
+
+LOG_DECLARE_CATEGORY(SoftwareIsp)
+
+class SoftwareIsp
+{
+public:
+ SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor);
+ ~SoftwareIsp();
+
+ int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
+
+ bool isValid() const;
+
+ std::vector<PixelFormat> formats(PixelFormat input);
+
+ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
+
+ std::tuple<unsigned int, unsigned int>
+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
+
+ int configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+ const ControlInfoMap &sensorControls);
+
+ int exportBuffers(unsigned int output, unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers);
+
+ void processStats(const ControlList &sensorControls);
+
+ int start();
+ void stop();
+
+ int queueBuffers(FrameBuffer *input,
+ const std::map<unsigned int, FrameBuffer *> &outputs);
+
+ void process(FrameBuffer *input, FrameBuffer *output);
+
+ Signal<FrameBuffer *> inputBufferReady;
+ Signal<FrameBuffer *> outputBufferReady;
+ Signal<> ispStatsReady;
+ Signal<const ControlList &> setSensorControls;
+
+private:
+ void saveIspParams();
+ void setSensorCtrls(const ControlList &sensorControls);
+ void statsReady();
+ void inputReady(FrameBuffer *input);
+ void outputReady(FrameBuffer *output);
+
+ std::unique_ptr<DebayerCpu> debayer_;
+ Thread ispWorkerThread_;
+ SharedMemObject<DebayerParams> sharedParams_;
+ DebayerParams debayerParams_;
+ DmaHeap dmaHeap_;
+
+ std::unique_ptr<ipa::soft::IPAProxySoft> ipa_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
new file mode 100644
index 00000000..4ca8d647
--- /dev/null
+++ b/include/libcamera/internal/software_isp/swisp_stats.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * swisp_stats.h - Statistics data format used by the software ISP and software IPA
+ */
+
+#pragma once
+
+#include <array>
+#include <stdint.h>
+
+namespace libcamera {
+
+/**
+ * \brief Struct that holds the statistics for the Software ISP
+ *
+ * The struct value types are large enough to not overflow.
+ * Should they still overflow for some reason, no check is performed and they
+ * wrap around.
+ */
+struct SwIspStats {
+ /**
+ * \brief Holds the sum of all sampled red pixels
+ */
+ uint64_t sumR_;
+ /**
+ * \brief Holds the sum of all sampled green pixels
+ */
+ uint64_t sumG_;
+ /**
+ * \brief Holds the sum of all sampled blue pixels
+ */
+ uint64_t sumB_;
+ /**
+ * \brief Number of bins in the yHistogram
+ */
+ static constexpr unsigned int kYHistogramSize = 64;
+ /**
+ * \brief Type of the histogram.
+ */
+ using Histogram = std::array<uint32_t, kYHistogramSize>;
+ /**
+ * \brief A histogram of luminance values
+ */
+ Histogram yHistogram;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/tracepoints/request.tp b/include/libcamera/internal/tracepoints/request.tp
index f1e54497..4f367e91 100644
--- a/include/libcamera/internal/tracepoints/request.tp
+++ b/include/libcamera/internal/tracepoints/request.tp
@@ -5,10 +5,10 @@
* request.tp - Tracepoints for the request object
*/
-#include <libcamera/internal/request.h>
-
#include <libcamera/framebuffer.h>
+#include "libcamera/internal/request.h"
+
TRACEPOINT_EVENT_CLASS(
libcamera,
request,
diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h
index a52a5f2c..50d4adbc 100644
--- a/include/libcamera/internal/v4l2_device.h
+++ b/include/libcamera/internal/v4l2_device.h
@@ -22,6 +22,8 @@
#include <libcamera/color_space.h>
#include <libcamera/controls.h>
+#include "libcamera/internal/formats.h"
+
namespace libcamera {
class EventNotifier;
@@ -59,7 +61,8 @@ protected:
int fd() const { return fd_.get(); }
template<typename T>
- static std::optional<ColorSpace> toColorSpace(const T &v4l2Format);
+ static std::optional<ColorSpace> toColorSpace(const T &v4l2Format,
+ PixelFormatInfo::ColourEncoding colourEncoding);
template<typename T>
static int fromColorSpace(const std::optional<ColorSpace> &colorSpace, T &v4l2Format);
@@ -67,8 +70,8 @@ protected:
private:
static ControlType v4l2CtrlType(uint32_t ctrlType);
static std::unique_ptr<ControlId> v4l2ControlId(const v4l2_query_ext_ctrl &ctrl);
- ControlInfo v4l2ControlInfo(const v4l2_query_ext_ctrl &ctrl);
- ControlInfo v4l2MenuControlInfo(const v4l2_query_ext_ctrl &ctrl);
+ std::optional<ControlInfo> v4l2ControlInfo(const v4l2_query_ext_ctrl &ctrl);
+ std::optional<ControlInfo> v4l2MenuControlInfo(const v4l2_query_ext_ctrl &ctrl);
void listControls();
void updateControls(ControlList *ctrls,
diff --git a/include/libcamera/internal/v4l2_pixelformat.h b/include/libcamera/internal/v4l2_pixelformat.h
index fb2d5d0b..44439fff 100644
--- a/include/libcamera/internal/v4l2_pixelformat.h
+++ b/include/libcamera/internal/v4l2_pixelformat.h
@@ -1,16 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* v4l2_pixelformat.h - V4L2 Pixel Format
*/
#pragma once
+#include <functional>
#include <ostream>
#include <stdint.h>
#include <string>
+#include <vector>
#include <linux/videodev2.h>
@@ -43,9 +45,9 @@ public:
std::string toString() const;
const char *description() const;
- PixelFormat toPixelFormat() const;
- static V4L2PixelFormat fromPixelFormat(const PixelFormat &pixelFormat,
- bool multiplanar = false);
+ PixelFormat toPixelFormat(bool warn = true) const;
+ static const std::vector<V4L2PixelFormat> &
+ fromPixelFormat(const PixelFormat &pixelFormat);
private:
uint32_t fourcc_;
@@ -54,3 +56,15 @@ private:
std::ostream &operator<<(std::ostream &out, const V4L2PixelFormat &f);
} /* namespace libcamera */
+
+namespace std {
+
+template<>
+struct hash<libcamera::V4L2PixelFormat> {
+ size_t operator()(libcamera::V4L2PixelFormat const &format) const noexcept
+ {
+ return format.fourcc();
+ }
+};
+
+} /* namespace std */
diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h
index 6fda52ad..01ed4c2f 100644
--- a/include/libcamera/internal/v4l2_subdevice.h
+++ b/include/libcamera/internal/v4l2_subdevice.h
@@ -13,6 +13,8 @@
#include <string>
#include <vector>
+#include <linux/v4l2-subdev.h>
+
#include <libcamera/base/class.h>
#include <libcamera/base/log.h>
@@ -27,13 +29,43 @@ namespace libcamera {
class MediaDevice;
+class MediaBusFormatInfo
+{
+public:
+ enum class Type {
+ Image,
+ Metadata,
+ EmbeddedData,
+ };
+
+ bool isValid() const { return code != 0; }
+
+ static const MediaBusFormatInfo &info(uint32_t code);
+
+ const char *name;
+ uint32_t code;
+ Type type;
+ unsigned int bitsPerPixel;
+ PixelFormatInfo::ColourEncoding colourEncoding;
+};
+
+struct V4L2SubdeviceCapability final : v4l2_subdev_capability {
+ bool isReadOnly() const
+ {
+ return capabilities & V4L2_SUBDEV_CAP_RO_SUBDEV;
+ }
+ bool hasStreams() const
+ {
+ return capabilities & V4L2_SUBDEV_CAP_STREAMS;
+ }
+};
+
struct V4L2SubdeviceFormat {
- uint32_t mbus_code;
+ uint32_t code;
Size size;
std::optional<ColorSpace> colorSpace;
const std::string toString() const;
- uint8_t bitsPerPixel() const;
};
std::ostream &operator<<(std::ostream &out, const V4L2SubdeviceFormat &f);
@@ -44,10 +76,43 @@ public:
using Formats = std::map<unsigned int, std::vector<SizeRange>>;
enum Whence {
- ActiveFormat,
- TryFormat,
+ TryFormat = V4L2_SUBDEV_FORMAT_TRY,
+ ActiveFormat = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ struct Stream {
+ Stream()
+ : pad(0), stream(0)
+ {
+ }
+
+ Stream(unsigned int p, unsigned int s)
+ : pad(p), stream(s)
+ {
+ }
+
+ unsigned int pad;
+ unsigned int stream;
};
+ struct Route {
+ Route()
+ : flags(0)
+ {
+ }
+
+ Route(const Stream &snk, const Stream &src, uint32_t f)
+ : sink(snk), source(src), flags(f)
+ {
+ }
+
+ Stream sink;
+ Stream source;
+ uint32_t flags;
+ };
+
+ using Routing = std::vector<Route>;
+
explicit V4L2Subdevice(const MediaEntity *entity);
~V4L2Subdevice();
@@ -55,19 +120,45 @@ public:
const MediaEntity *entity() const { return entity_; }
- int getSelection(unsigned int pad, unsigned int target,
+ int getSelection(const Stream &stream, unsigned int target,
Rectangle *rect);
- int setSelection(unsigned int pad, unsigned int target,
+ int getSelection(unsigned int pad, unsigned int target, Rectangle *rect)
+ {
+ return getSelection({ pad, 0 }, target, rect);
+ }
+ int setSelection(const Stream &stream, unsigned int target,
Rectangle *rect);
-
- Formats formats(unsigned int pad);
-
+ int setSelection(unsigned int pad, unsigned int target, Rectangle *rect)
+ {
+ return setSelection({ pad, 0 }, target, rect);
+ }
+
+ Formats formats(const Stream &stream);
+ Formats formats(unsigned int pad)
+ {
+ return formats({ pad, 0 });
+ }
+
+ int getFormat(const Stream &stream, V4L2SubdeviceFormat *format,
+ Whence whence = ActiveFormat);
int getFormat(unsigned int pad, V4L2SubdeviceFormat *format,
+ Whence whence = ActiveFormat)
+ {
+ return getFormat({ pad, 0 }, format, whence);
+ }
+ int setFormat(const Stream &stream, V4L2SubdeviceFormat *format,
Whence whence = ActiveFormat);
int setFormat(unsigned int pad, V4L2SubdeviceFormat *format,
- Whence whence = ActiveFormat);
+ Whence whence = ActiveFormat)
+ {
+ return setFormat({ pad, 0 }, format, whence);
+ }
+
+ int getRouting(Routing *routing, Whence whence = ActiveFormat);
+ int setRouting(Routing *routing, Whence whence = ActiveFormat);
const std::string &model();
+ const V4L2SubdeviceCapability &caps() const { return caps_; }
static std::unique_ptr<V4L2Subdevice>
fromEntityName(const MediaDevice *media, const std::string &entity);
@@ -78,13 +169,28 @@ protected:
private:
LIBCAMERA_DISABLE_COPY(V4L2Subdevice)
- std::vector<unsigned int> enumPadCodes(unsigned int pad);
- std::vector<SizeRange> enumPadSizes(unsigned int pad,
+ std::optional<ColorSpace>
+ toColorSpace(const v4l2_mbus_framefmt &format) const;
+
+ std::vector<unsigned int> enumPadCodes(const Stream &stream);
+ std::vector<SizeRange> enumPadSizes(const Stream &stream,
unsigned int code);
const MediaEntity *entity_;
std::string model_;
+ struct V4L2SubdeviceCapability caps_;
};
+bool operator==(const V4L2Subdevice::Stream &lhs, const V4L2Subdevice::Stream &rhs);
+static inline bool operator!=(const V4L2Subdevice::Stream &lhs,
+ const V4L2Subdevice::Stream &rhs)
+{
+ return !(lhs == rhs);
+}
+
+std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Stream &stream);
+std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route);
+std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing);
+
} /* namespace libcamera */
diff --git a/include/libcamera/internal/v4l2_videodevice.h b/include/libcamera/internal/v4l2_videodevice.h
index 8525acbc..d157a447 100644
--- a/include/libcamera/internal/v4l2_videodevice.h
+++ b/include/libcamera/internal/v4l2_videodevice.h
@@ -14,6 +14,7 @@
#include <ostream>
#include <stdint.h>
#include <string>
+#include <unordered_set>
#include <vector>
#include <linux/videodev2.h>
@@ -228,6 +229,8 @@ public:
static std::unique_ptr<V4L2VideoDevice>
fromEntityName(const MediaDevice *media, const std::string &entity);
+ V4L2PixelFormat toV4L2PixelFormat(const PixelFormat &pixelFormat) const;
+
protected:
std::string logPrefix() const override;
@@ -240,6 +243,8 @@ private:
Stopped,
};
+ int initFormats();
+
int getFormatMeta(V4L2DeviceFormat *format);
int trySetFormatMeta(V4L2DeviceFormat *format, bool set);
@@ -263,9 +268,13 @@ private:
void watchdogExpired();
+ template<typename T>
+ static std::optional<ColorSpace> toColorSpace(const T &v4l2Format);
+
V4L2Capability caps_;
V4L2DeviceFormat format_;
const PixelFormatInfo *formatInfo_;
+ std::unordered_set<V4L2PixelFormat> pixelFormats_;
enum v4l2_buf_type bufferType_;
enum v4l2_memory memoryType_;
diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
index 064cf443..8ca71df8 100644
--- a/include/libcamera/internal/yaml_parser.h
+++ b/include/libcamera/internal/yaml_parser.h
@@ -9,6 +9,7 @@
#include <iterator>
#include <map>
+#include <optional>
#include <string>
#include <vector>
@@ -24,12 +25,21 @@ class YamlParserContext;
class YamlObject
{
private:
- using DictContainer = std::map<std::string, std::unique_ptr<YamlObject>>;
+ struct Value {
+ Value(std::string &&k, std::unique_ptr<YamlObject> &&v)
+ : key(std::move(k)), value(std::move(v))
+ {
+ }
+ std::string key;
+ std::unique_ptr<YamlObject> value;
+ };
+
+ using Container = std::vector<Value>;
using ListContainer = std::vector<std::unique_ptr<YamlObject>>;
public:
#ifndef __DOXYGEN__
- template<typename Container, typename Derived>
+ template<typename Derived>
class Iterator
{
public:
@@ -65,10 +75,10 @@ public:
}
protected:
- typename Container::const_iterator it_;
+ Container::const_iterator it_;
};
- template<typename Container, typename Iterator>
+ template<typename Iterator>
class Adapter
{
public:
@@ -91,7 +101,7 @@ public:
const Container &container_;
};
- class ListIterator : public Iterator<ListContainer, ListIterator>
+ class ListIterator : public Iterator<ListIterator>
{
public:
using value_type = const YamlObject &;
@@ -100,16 +110,16 @@ public:
value_type operator*() const
{
- return *it_->get();
+ return *it_->value.get();
}
pointer operator->() const
{
- return it_->get();
+ return it_->value.get();
}
};
- class DictIterator : public Iterator<DictContainer, DictIterator>
+ class DictIterator : public Iterator<DictIterator>
{
public:
using value_type = std::pair<const std::string &, const YamlObject &>;
@@ -118,17 +128,17 @@ public:
value_type operator*() const
{
- return { it_->first, *it_->second.get() };
+ return { it_->key, *it_->value.get() };
}
};
- class DictAdapter : public Adapter<DictContainer, DictIterator>
+ class DictAdapter : public Adapter<DictIterator>
{
public:
using key_type = std::string;
};
- class ListAdapter : public Adapter<ListContainer, ListIterator>
+ class ListAdapter : public Adapter<ListIterator>
{
};
#endif /* __DOXYGEN__ */
@@ -153,9 +163,35 @@ public:
#ifndef __DOXYGEN__
template<typename T,
- typename std::enable_if_t<
+ std::enable_if_t<
+ std::is_same_v<bool, T> ||
+ std::is_same_v<double, T> ||
+ std::is_same_v<int8_t, T> ||
+ std::is_same_v<uint8_t, T> ||
+ std::is_same_v<int16_t, T> ||
+ std::is_same_v<uint16_t, T> ||
+ std::is_same_v<int32_t, T> ||
+ std::is_same_v<uint32_t, T> ||
+ std::is_same_v<std::string, T> ||
+ std::is_same_v<Size, T>> * = nullptr>
+#else
+ template<typename T>
+#endif
+ std::optional<T> get() const;
+
+ template<typename T>
+ T get(const T &defaultValue) const
+ {
+ return get<T>().value_or(defaultValue);
+ }
+
+#ifndef __DOXYGEN__
+ template<typename T,
+ std::enable_if_t<
std::is_same_v<bool, T> ||
std::is_same_v<double, T> ||
+ std::is_same_v<int8_t, T> ||
+ std::is_same_v<uint8_t, T> ||
std::is_same_v<int16_t, T> ||
std::is_same_v<uint16_t, T> ||
std::is_same_v<int32_t, T> ||
@@ -165,9 +201,9 @@ public:
#else
template<typename T>
#endif
- T get(const T &defaultValue, bool *ok = nullptr) const;
+ std::optional<std::vector<T>> getList() const;
- DictAdapter asDict() const { return DictAdapter{ dictionary_ }; }
+ DictAdapter asDict() const { return DictAdapter{ list_ }; }
ListAdapter asList() const { return ListAdapter{ list_ }; }
const YamlObject &operator[](std::size_t index) const;
@@ -189,8 +225,8 @@ private:
Type type_;
std::string value_;
- ListContainer list_;
- DictContainer dictionary_;
+ Container list_;
+ std::map<std::string, YamlObject *> dictionary_;
};
class YamlParser final
diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom
index 74f3339e..bce79724 100644
--- a/include/libcamera/ipa/core.mojom
+++ b/include/libcamera/ipa/core.mojom
@@ -14,8 +14,8 @@ module libcamera;
* - structs
*
* Attributes:
- * - skipHeader - structs only, and only in core.mojom
- * - Do not generate a C++ definition for the structure
+ * - skipHeader - allowed only for structs and enums in core.mojom
+ * - Do not generate a C++ definition for the structure or enum
* - Any type used in a mojom interface definition must have a corresponding
* definition in a mojom file for the code generator to accept it, except
* for types solely used as map/array members for which a definition is not
@@ -33,6 +33,15 @@ module libcamera;
* available for the type and there's no need to generate one
* - hasFd - struct fields or empty structs only
* - Designate that this field or empty struct contains a SharedFD
+ * - scopedEnum - enum definitions
+ * - Designate that this enum should be an enum class, as opposed to a pure
+ * enum
+ * - flags - struct fields or function parameters that are enums
+ * - Designate that this enum type E should be Flags<E> in the generated C++
+ * code
+ * - For example, if a struct field is defined as `[flags] ErrorFlag f;`
+ * (where ErrorFlag is defined as an enum elsewhere in mojom), then the
+ * generated code for this field will be `Flags<ErrorFlag> f`
*
* Rules:
* - If the type is defined in a libcamera C++ header *and* a (de)serializer is
@@ -43,6 +52,8 @@ module libcamera;
* then the type definition in the core.mojom file should have the
* [skipHeader] attribute only
* - A (de)serializer will be generated for the type
+ * - enums that are defined in a libcamera C++ header also fall in this
+ * category
* - If a type definition has [skipHeader], then the header where the type is
* defined must be included in ipa_interface.h
* - Types that are solely used as array/map members do not require a mojom
@@ -130,6 +141,18 @@ module libcamera;
*/
/**
+ * \var IPACameraSensorInfo::cfaPattern
+ * \brief The arrangement of colour filters on the image sensor
+ *
+ * This takes a value defined by properties::draft::ColorFilterArrangementEnum.
+ * For non-Bayer colour sensors, the cfaPattern will be set to
+ * properties::draft::ColorFilterArrangementEnum::RGB.
+ *
+ * \todo Make this variable optional once mojom supports it, instead of using
+ * RGB for sensors that don't have a CFA.
+ */
+
+/**
* \var IPACameraSensorInfo::activeAreaSize
* \brief The size of the pixel array active area of the sensor
*/
@@ -172,10 +195,17 @@ module libcamera;
*/
/**
- * \var IPACameraSensorInfo::lineLength
- * \brief Total line length in pixels
+ * \var IPACameraSensorInfo::minLineLength
+ * \brief The minimum line length in pixels
*
- * The total line length in pixel clock periods, including blanking.
+ * The minimum allowable line length in pixel clock periods, including blanking.
+ */
+
+/**
+ * \var IPACameraSensorInfo::maxLineLength
+ * \brief The maximum line length in pixels
+ *
+ * The maximum allowable line length in pixel clock periods, including blanking.
*/
/**
@@ -189,7 +219,7 @@ module libcamera;
* To obtain the minimum frame duration:
*
* \verbatim
- frameDuration(s) = minFrameLength(lines) * lineLength(pixels) / pixelRate(pixels per second)
+ frameDuration(s) = minFrameLength(lines) * minLineLength(pixels) / pixelRate(pixels per second)
\endverbatim
*/
@@ -204,20 +234,23 @@ module libcamera;
* To obtain the maximum frame duration:
*
* \verbatim
- frameDuration(s) = maxFrameLength(lines) * lineLength(pixels) / pixelRate(pixels per second)
+ frameDuration(s) = maxFrameLength(lines) * maxLineLength(pixels) / pixelRate(pixels per second)
\endverbatim
*/
struct IPACameraSensorInfo {
string model;
uint32 bitsPerPixel;
+ uint32 cfaPattern;
Size activeAreaSize;
Rectangle analogCrop;
Size outputSize;
uint64 pixelRate;
- uint32 lineLength;
+
+ uint32 minLineLength;
+ uint32 maxLineLength;
uint32 minFrameLength;
uint32 maxFrameLength;
diff --git a/include/libcamera/ipa/ipa_interface.h b/include/libcamera/ipa/ipa_interface.h
index 50ca0e7b..8884f0ed 100644
--- a/include/libcamera/ipa/ipa_interface.h
+++ b/include/libcamera/ipa/ipa_interface.h
@@ -13,6 +13,7 @@
#include <map>
#include <vector>
+#include <libcamera/base/flags.h>
#include <libcamera/base/signal.h>
#include <libcamera/controls.h>
@@ -22,8 +23,8 @@
namespace libcamera {
/*
- * Structs that are defined in core.mojom and have the skipHeader tag must be
- * #included here.
+ * Structs and enums that are defined in core.mojom that have the skipHeader
+ * tag must be #included here.
*/
class IPAInterface
diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
index 442ca3dd..3352d08f 100644
--- a/include/libcamera/ipa/meson.build
+++ b/include/libcamera/ipa/meson.build
@@ -9,7 +9,7 @@ libcamera_ipa_headers = files([
])
install_headers(libcamera_ipa_headers,
- subdir: libcamera_ipa_include_dir)
+ subdir : libcamera_ipa_include_dir)
libcamera_generated_ipa_headers = []
@@ -60,14 +60,14 @@ libcamera_generated_ipa_headers += custom_target('core_ipa_serializer_h',
'./' +'@INPUT@'
])
-ipa_mojom_files = [
- 'ipu3.mojom',
- 'raspberrypi.mojom',
- 'rkisp1.mojom',
- 'vimc.mojom',
-]
-
-ipa_mojoms = []
+# Mapping from pipeline handler name to mojom file
+pipeline_ipa_mojom_mapping = {
+ 'ipu3': 'ipu3.mojom',
+ 'rkisp1': 'rkisp1.mojom',
+ 'rpi/vc4': 'raspberrypi.mojom',
+ 'simple': 'soft.mojom',
+ 'vimc': 'vimc.mojom',
+}
#
# Generate headers from templates.
@@ -75,14 +75,23 @@ ipa_mojoms = []
# TODO Define per-pipeline ControlInfoMap with yaml?
-foreach file : ipa_mojom_files
+ipa_mojoms = []
+mojoms_built = []
+foreach pipeline, file : pipeline_ipa_mojom_mapping
name = file.split('.')[0]
- if name not in pipelines
+ # Avoid building duplicate mojom interfaces with the same interface file
+ if name in mojoms_built
continue
endif
- # {pipeline}.mojom-module
+ if pipeline not in pipelines
+ continue
+ endif
+
+ mojoms_built += name
+
+ # {interface}.mojom-module
mojom = custom_target(name + '_mojom_module',
input : file,
output : file + '-module',
@@ -94,7 +103,7 @@ foreach file : ipa_mojom_files
'--mojoms', '@INPUT@'
])
- # {pipeline}_ipa_interface.h
+ # {interface}_ipa_interface.h
header = custom_target(name + '_ipa_interface_h',
input : mojom,
output : name + '_ipa_interface.h',
@@ -110,7 +119,7 @@ foreach file : ipa_mojom_files
'./' +'@INPUT@'
])
- # {pipeline}_ipa_serializer.h
+ # {interface}_ipa_serializer.h
serializer = custom_target(name + '_ipa_serializer_h',
input : mojom,
output : name + '_ipa_serializer.h',
@@ -124,7 +133,7 @@ foreach file : ipa_mojom_files
'./' +'@INPUT@'
])
- # {pipeline}_ipa_proxy.h
+ # {interface}_ipa_proxy.h
proxy_header = custom_target(name + '_proxy_h',
input : mojom,
output : name + '_ipa_proxy.h',
@@ -146,6 +155,12 @@ foreach file : ipa_mojom_files
libcamera_generated_ipa_headers += [header, serializer, proxy_header]
endforeach
+ipa_mojom_files = []
+foreach pipeline, file : pipeline_ipa_mojom_mapping
+ if file not in ipa_mojom_files
+ ipa_mojom_files += file
+ endif
+endforeach
ipa_mojom_files = files(ipa_mojom_files)
# Pass this to the documentation generator in src/libcamera/ipa
diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
index c0de435b..5986c436 100644
--- a/include/libcamera/ipa/raspberrypi.mojom
+++ b/include/libcamera/ipa/raspberrypi.mojom
@@ -8,82 +8,139 @@ module ipa.RPi;
import "include/libcamera/ipa/core.mojom";
-enum BufferMask {
- MaskID = 0x00ffff,
- MaskStats = 0x010000,
- MaskEmbeddedData = 0x020000,
- MaskBayerData = 0x040000,
- MaskExternalBuffer = 0x100000,
-};
-
-/* Size of the LS grid allocation. */
+/* Size of the LS grid allocation on VC4. */
const uint32 MaxLsGridSize = 0x8000;
struct SensorConfig {
uint32 gainDelay;
uint32 exposureDelay;
uint32 vblankDelay;
+ uint32 hblankDelay;
uint32 sensorMetadata;
};
-struct IPAInitResult {
+struct InitParams {
+ bool lensPresent;
+ libcamera.IPACameraSensorInfo sensorInfo;
+ /* PISP specific */
+ libcamera.SharedFD fe;
+ libcamera.SharedFD be;
+};
+
+struct InitResult {
SensorConfig sensorConfig;
libcamera.ControlInfoMap controlInfo;
};
-struct ISPConfig {
- uint32 embeddedBufferId;
- uint32 bayerBufferId;
- bool embeddedBufferPresent;
- libcamera.ControlList controls;
+struct BufferIds {
+ uint32 bayer;
+ uint32 embedded;
+ uint32 stats;
};
-struct IPAConfig {
+struct ConfigParams {
uint32 transform;
+ libcamera.ControlInfoMap sensorControls;
+ libcamera.ControlInfoMap ispControls;
+ libcamera.ControlInfoMap lensControls;
+ /* VC4 specific */
libcamera.SharedFD lsTableHandle;
};
-struct IPAConfigResult {
- float modeSensitivity;
- libcamera.ControlInfoMap controlInfo;
+struct ConfigResult {
+ float modeSensitivity;
+ libcamera.ControlInfoMap controlInfo;
+ libcamera.ControlList sensorControls;
+ libcamera.ControlList lensControls;
};
-struct StartConfig {
+struct StartResult {
libcamera.ControlList controls;
int32 dropFrameCount;
- uint32 maxSensorFrameLengthMs;
+};
+
+struct PrepareParams {
+ BufferIds buffers;
+ libcamera.ControlList sensorControls;
+ libcamera.ControlList requestControls;
+ uint32 ipaContext;
+ uint32 delayContext;
+};
+
+struct ProcessParams {
+ BufferIds buffers;
+ uint32 ipaContext;
};
interface IPARPiInterface {
- init(libcamera.IPASettings settings)
- => (int32 ret, IPAInitResult result);
- start(libcamera.ControlList controls) => (StartConfig startConfig);
+ /**
+ * \fn init()
+ * \brief Initialise the IPA
+ * \param[in] settings Camera sensor information and configuration file
+ * \param[in] params Platform specific initialisation parameters
+ * \param[out] ret 0 on success or a negative error code otherwise
+ * \param[out] result Static sensor configuration and controls available
+ *
+ * This function initialises the IPA for a particular sensor from the
+ * pipeline handler.
+ *
+ * The \a settings conveys information about the camera sensor and
+ * configuration file requested by the pipeline handler.
+ *
+ * The \a result parameter returns the sensor delay for the given camera
+ * as well as a ControlInfoMap of available controls that can be handled
+ * by the IPA.
+ */
+ init(libcamera.IPASettings settings, InitParams params)
+ => (int32 ret, InitResult result);
+
+ /**
+ * \fn start()
+ * \brief Start the IPA
+ * \param[in] controls List of control to handle
+ * \param[out] result Controls to apply and number of dropped frames
+ *
+ * This function sets the IPA to a started state.
+ *
+ * The \a controls provide a list of controls to handle immediately. The
+ * actual controls to apply on the sensor and ISP in the pipeline
+ * handler are returned in \a result.
+ *
+ * The number of convergence frames to be dropped is also returned in
+ * \a result.
+ */
+ start(libcamera.ControlList controls) => (StartResult result);
+
+ /**
+ * \fn start()
+ * \brief Stop the IPA
+ *
+ * This function sets the IPA to a stopped state.
+ */
stop();
/**
* \fn configure()
- * \brief Configure the IPA stream and sensor settings
- * \param[in] sensorInfo Camera sensor information
- * \param[in] streamConfig Configuration of all active streams
- * \param[in] entityControls Controls provided by the pipeline entities
- * \param[in] ipaConfig Pipeline-handler-specific configuration data
- * \param[out] controls Controls to apply by the pipeline entity
- * \param[out] result Other results that the pipeline handler may require
- *
- * This function shall be called when the camera is configured to inform
- * the IPA of the camera's streams and the sensor settings.
- *
- * The \a sensorInfo conveys information about the camera sensor settings that
- * the pipeline handler has selected for the configuration.
- *
- * The \a ipaConfig and \a controls parameters carry data passed by the
- * pipeline handler to the IPA and back.
+ * \brief Configure the IPA
+ * \param[in] sensorInfo Sensor mode configuration
+ * \param[in] params Platform configuration parameters
+ * \param[out] ret 0 on success or a negative error code otherwise
+ * \param[out] result Results of the configuration operation
+ *
+ * This function configures the IPA for a particular camera
+ * configuration
+ *
+ * The \a params parameter provides a list of available controls for the
+ * ISP, sensor and lens devices, and the user requested transform
+ * operation. It can also provide platform specific configuration
+ * parameters, e.g. the lens shading table memory handle for VC4.
+ *
+ * The \a result parameter returns the available controls for the given
+ * camera mode, a list of controls to apply to the sensor device, and
+ * the requested mode's sensitivity characteristics.
*/
- configure(libcamera.IPACameraSensorInfo sensorInfo,
- map<uint32, libcamera.IPAStream> streamConfig,
- map<uint32, libcamera.ControlInfoMap> entityControls,
- IPAConfig ipaConfig)
- => (int32 ret, libcamera.ControlList controls, IPAConfigResult result);
+ configure(libcamera.IPACameraSensorInfo sensorInfo, ConfigParams params)
+ => (int32 ret, ConfigResult result);
/**
* \fn mapBuffers()
@@ -106,7 +163,7 @@ interface IPARPiInterface {
* depending on the IPA protocol. Regardless of the protocol, all
* buffers mapped at a given time shall have unique numerical IDs.
*
- * The numerical IDs have no meaning defined by the IPA interface, and
+ * The numerical IDs have no meaning defined by the IPA interface, and
* should be treated as opaque handles by IPAs, with the only exception
* that ID zero is invalid.
*
@@ -126,15 +183,119 @@ interface IPARPiInterface {
*/
unmapBuffers(array<uint32> ids);
- [async] signalStatReady(uint32 bufferId);
- [async] signalQueueRequest(libcamera.ControlList controls);
- [async] signalIspPrepare(ISPConfig data);
+ /**
+ * \fn prepareIsp()
+ * \brief Prepare the ISP configuration for a frame
+ * \param[in] params Parameter set for the frame to process
+ *
+ * This function call into all the algorithms in preparation for the
+ * frame to be processed by the ISP.
+ *
+ * The \a params parameter lists the buffer IDs for the Bayer and
+ * embedded data buffers, a ControlList of sensor frame params, and
+ * a ControlList of request controls for the current frame.
+ *
+ * Additionally, \a params also contains the IPA context (ipaContext) to
+ * use as an index location to store control algorithm results, and a
+ * historical IPA context (delayContext) that was active when the sensor
+ * settings were requested by the IPA.
+ */
+ [async] prepareIsp(PrepareParams params);
+
+ /**
+ * \fn processStats()
+ * \brief Process the statistics provided by the ISP
+ * \param[in] params Parameter set for the statistics to process
+ *
+ * This function call into all the algorithms to provide the statistics
+ * generated by the ISP for the processed frame.
+ *
+ * The \a params parameter lists the buffer ID for the statistics buffer
+ * and an IPA context (ipaContext) to use as an index location to store
+ * algorithm results.
+ */
+ [async] processStats(ProcessParams params);
};
interface IPARPiEventInterface {
- statsMetadataComplete(uint32 bufferId, libcamera.ControlList controls);
- runIsp(uint32 bufferId);
- embeddedComplete(uint32 bufferId);
+ /**
+ * \fn prepareIspComplete()
+ * \brief Signal completion of \a prepareIsp
+ * \param[in] buffers Bayer and embedded buffers actioned.
+ * \param[in] stitchSwapBuffers Whether the stitch block buffers need to be swapped.
+ *
+ * This asynchronous event is signalled to the pipeline handler once
+ * the \a prepareIsp signal has completed, and the ISP is ready to start
+ * processing the frame. The embedded data buffer may be recycled after
+ * this event.
+ */
+ prepareIspComplete(BufferIds buffers, bool stitchSwapBuffers);
+
+ /**
+ * \fn processStatsComplete()
+ * \brief Signal completion of \a processStats
+ * \param[in] buffers Statistics buffers actioned.
+ *
+ * This asynchronous event is signalled to the pipeline handler once
+ * the \a processStats signal has completed. The statistics buffer may
+ * be recycled after this event.
+ */
+ processStatsComplete(BufferIds buffers);
+
+ /**
+ * \fn metadataReady()
+ * \brief Signal request metadata is to be merged
+ * \param[in] metadata Control list of metadata to be merged
+ *
+ * This asynchronous event is signalled to the pipeline handler once
+ * all the frame metadata has been gathered. The pipeline handler will
+ * copy or merge this metadata into the \a Request returned back to the
+ * application.
+ */
+ metadataReady(libcamera.ControlList metadata);
+
+ /**
+ * \fn setIspControls()
+ * \brief Signal ISP controls to be applied.
+ * \param[in] controls List of controls to be applied.
+ *
+ * This asynchronous event is signalled to the pipeline handler during
+ * the \a prepareISP signal after all algorithms have been run and the
+ * IPA requires ISP controls to be applied for the frame.
+ */
setIspControls(libcamera.ControlList controls);
- setDelayedControls(libcamera.ControlList controls);
+
+ /**
+ * \fn setDelayedControls()
+ * \brief Signal Sensor controls to be applied.
+ * \param[in] controls List of controls to be applied.
+ * \param[in] delayContext IPA context index used for this request
+ *
+ * This asynchronous event is signalled to the pipeline handler when
+ * the IPA requires sensor specific controls (e.g. shutter speed, gain,
+ * blanking) to be applied.
+ */
+ setDelayedControls(libcamera.ControlList controls, uint32 delayContext);
+
+ /**
+ * \fn setLensControls()
+ * \brief Signal lens controls to be applied.
+ * \param[in] controls List of controls to be applied.
+ *
+ * This asynchronous event is signalled to the pipeline handler when
+ * the IPA requires a lens movement control to be applied.
+ */
+ setLensControls(libcamera.ControlList controls);
+
+ /**
+ * \fn setCameraTimeout()
+ * \brief Request a watchdog timeout value to use
+ * \param[in] maxFrameLengthMs Timeout value in ms
+ *
+ * This asynchronous event is used by the IPA to inform the pipeline
+ * handler of an acceptable watchdog timer value to use for the sensor
+ * stream. This value is based on the history of frame lengths requested
+ * by the IPA.
+ */
+ setCameraTimeout(uint32 maxFrameLengthMs);
};
diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom
index e3537385..1009e970 100644
--- a/include/libcamera/ipa/rkisp1.mojom
+++ b/include/libcamera/ipa/rkisp1.mojom
@@ -8,17 +8,23 @@ module ipa.rkisp1;
import "include/libcamera/ipa/core.mojom";
+struct IPAConfigInfo {
+ libcamera.IPACameraSensorInfo sensorInfo;
+ libcamera.ControlInfoMap sensorControls;
+};
+
interface IPARkISP1Interface {
init(libcamera.IPASettings settings,
- uint32 hwRevision)
- => (int32 ret);
+ uint32 hwRevision,
+ libcamera.IPACameraSensorInfo sensorInfo,
+ libcamera.ControlInfoMap sensorControls)
+ => (int32 ret, libcamera.ControlInfoMap ipaControls);
start() => (int32 ret);
stop();
- configure(libcamera.IPACameraSensorInfo sensorInfo,
- map<uint32, libcamera.IPAStream> streamConfig,
- map<uint32, libcamera.ControlInfoMap> entityControls)
- => (int32 ret);
+ configure(IPAConfigInfo configInfo,
+ map<uint32, libcamera.IPAStream> streamConfig)
+ => (int32 ret, libcamera.ControlInfoMap ipaControls);
mapBuffers(array<libcamera.IPABuffer> buffers);
unmapBuffers(array<uint32> ids);
diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
new file mode 100644
index 00000000..3aa2066e
--- /dev/null
+++ b/include/libcamera/ipa/soft.mojom
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/*
+ * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
+ */
+
+module ipa.soft;
+
+import "include/libcamera/ipa/core.mojom";
+
+interface IPASoftInterface {
+ init(libcamera.IPASettings settings,
+ libcamera.SharedFD fdStats,
+ libcamera.SharedFD fdParams,
+ libcamera.ControlInfoMap sensorCtrlInfoMap)
+ => (int32 ret);
+ start() => (int32 ret);
+ stop();
+ configure(libcamera.ControlInfoMap sensorCtrlInfoMap)
+ => (int32 ret);
+
+ [async] processStats(libcamera.ControlList sensorControls);
+};
+
+interface IPASoftEventInterface {
+ setSensorControls(libcamera.ControlList sensorControls);
+ setIspParams();
+};
diff --git a/include/libcamera/ipa/vimc.mojom b/include/libcamera/ipa/vimc.mojom
index 718b9674..dd991f7e 100644
--- a/include/libcamera/ipa/vimc.mojom
+++ b/include/libcamera/ipa/vimc.mojom
@@ -17,8 +17,18 @@ enum IPAOperationCode {
IPAOperationStop,
};
+[scopedEnum] enum TestFlag {
+ Flag1 = 0x1,
+ Flag2 = 0x2,
+ Flag3 = 0x4,
+ Flag4 = 0x8,
+};
+
interface IPAVimcInterface {
- init(libcamera.IPASettings settings) => (int32 ret);
+ init(libcamera.IPASettings settings,
+ IPAOperationCode code,
+ [flags] TestFlag inFlags)
+ => (int32 ret, [flags] TestFlag outFlags);
configure(libcamera.IPACameraSensorInfo sensorInfo,
map<uint32, libcamera.IPAStream> streamConfig,
@@ -41,5 +51,5 @@ interface IPAVimcInterface {
};
interface IPAVimcEventInterface {
- paramsBufferReady(uint32 bufferId);
+ paramsBufferReady(uint32 bufferId, [flags] TestFlag flags);
};
diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
index 408b7acf..84c6c4cb 100644
--- a/include/libcamera/meson.build
+++ b/include/libcamera/meson.build
@@ -12,6 +12,7 @@ libcamera_public_headers = files([
'framebuffer_allocator.h',
'geometry.h',
'logging.h',
+ 'orientation.h',
'pixel_format.h',
'request.h',
'stream.h',
@@ -31,20 +32,58 @@ install_headers(libcamera_public_headers,
libcamera_headers_install_dir = get_option('includedir') / libcamera_include_dir
-# control_ids.h and property_ids.h
-control_source_files = [
- 'control_ids',
- 'property_ids',
-]
+controls_map = {
+ 'controls': {
+ 'draft': 'control_ids_draft.yaml',
+ 'core': 'control_ids_core.yaml',
+ 'rpi/vc4': 'control_ids_rpi.yaml',
+ },
+
+ 'properties': {
+ 'draft': 'property_ids_draft.yaml',
+ 'core': 'property_ids_core.yaml',
+ }
+}
control_headers = []
+controls_files = []
+properties_files = []
+
+foreach mode, entry : controls_map
+ files_list = []
+ input_files = []
+ foreach vendor, header : entry
+ if vendor != 'core' and vendor != 'draft'
+ if vendor not in pipelines
+ continue
+ endif
+ endif
+
+ if header in files_list
+ continue
+ endif
+
+ files_list += header
+ input_files += files('../../src/libcamera/' + header)
+ endforeach
+
+ outfile = ''
+ if mode == 'controls'
+ outfile = 'control_ids.h'
+ controls_files += files_list
+ else
+ outfile = 'property_ids.h'
+ properties_files += files_list
+ endif
-foreach header : control_source_files
- input_files = files('../../src/libcamera/' + header +'.yaml', header + '.h.in')
+ template_file = files(outfile + '.in')
+ ranges_file = files('../../src/libcamera/control_ranges.yaml')
control_headers += custom_target(header + '_h',
input : input_files,
- output : header + '.h',
- command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'],
+ output : outfile,
+ command : [gen_controls, '-o', '@OUTPUT@',
+ '--mode', mode, '-t', template_file,
+ '-r', ranges_file, '@INPUT@'],
install : true,
install_dir : libcamera_headers_install_dir)
endforeach
diff --git a/include/libcamera/orientation.h b/include/libcamera/orientation.h
new file mode 100644
index 00000000..9a2c2fb2
--- /dev/null
+++ b/include/libcamera/orientation.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Ideas On Board Oy
+ *
+ * orientation.h - Image orientation
+ */
+
+#pragma once
+
+#include <iostream>
+
+namespace libcamera {
+
+enum class Orientation {
+ /* EXIF tag 274 starts from '1' */
+ Rotate0 = 1,
+ Rotate0Mirror,
+ Rotate180,
+ Rotate180Mirror,
+ Rotate90Mirror,
+ Rotate270,
+ Rotate270Mirror,
+ Rotate90,
+};
+
+Orientation orientationFromRotation(int angle, bool *success = nullptr);
+
+std::ostream &operator<<(std::ostream &out, const Orientation &orientation);
+
+} /* namespace libcamera */
diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in
index ff019408..43372c71 100644
--- a/include/libcamera/property_ids.h.in
+++ b/include/libcamera/property_ids.h.in
@@ -23,14 +23,10 @@ ${ids}
${controls}
-namespace draft {
-
-${draft_controls}
-
-} /* namespace draft */
-
extern const ControlIdMap properties;
+${vendor_controls}
+
} /* namespace properties */
} /* namespace libcamera */
diff --git a/include/libcamera/stream.h b/include/libcamera/stream.h
index f0ae7e62..4e94187d 100644
--- a/include/libcamera/stream.h
+++ b/include/libcamera/stream.h
@@ -9,6 +9,7 @@
#include <map>
#include <memory>
+#include <ostream>
#include <string>
#include <vector>
@@ -61,14 +62,14 @@ private:
StreamFormats formats_;
};
-enum StreamRole {
+enum class StreamRole {
Raw,
StillCapture,
VideoRecording,
Viewfinder,
};
-using StreamRoles = std::vector<StreamRole>;
+std::ostream &operator<<(std::ostream &out, StreamRole role);
class Stream
{
diff --git a/include/libcamera/transform.h b/include/libcamera/transform.h
index 2e76b940..44cb4c6f 100644
--- a/include/libcamera/transform.h
+++ b/include/libcamera/transform.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* transform.h - 2D plane transforms
*/
@@ -11,6 +11,8 @@
namespace libcamera {
+enum class Orientation;
+
enum class Transform : int {
Identity = 0,
Rot0 = Identity,
@@ -70,6 +72,9 @@ constexpr Transform operator~(Transform t)
Transform transformFromRotation(int angle, bool *success = nullptr);
+Transform operator/(const Orientation &o1, const Orientation &o2);
+Orientation operator*(const Orientation &o, const Transform &t);
+
const char *transformToString(Transform t);
} /* namespace libcamera */
diff --git a/include/linux/README b/include/linux/README
index 4e314b98..101e4997 100644
--- a/include/linux/README
+++ b/include/linux/README
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0
-Files in this directory are imported from v5.16-rc7 of the Linux kernel. Do not
+Files in this directory are imported from v6.7 of the Linux kernel. Do not
modify them manually.
diff --git a/include/linux/bcm2835-isp.h b/include/linux/bcm2835-isp.h
index 94c3af94..5f0f78e3 100644
--- a/include/linux/bcm2835-isp.h
+++ b/include/linux/bcm2835-isp.h
@@ -4,7 +4,7 @@
*
* BCM2835 ISP driver - user space header file.
*
- * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd.
+ * Copyright © 2019-2020 Raspberry Pi Ltd
*
* Author: Naushir Patuck (naush@raspberrypi.com)
*
diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h
index 8e4a2ca0..5a6fda66 100644
--- a/include/linux/dma-buf.h
+++ b/include/linux/dma-buf.h
@@ -85,6 +85,88 @@ struct dma_buf_sync {
#define DMA_BUF_NAME_LEN 32
+/**
+ * struct dma_buf_export_sync_file - Get a sync_file from a dma-buf
+ *
+ * Userspace can perform a DMA_BUF_IOCTL_EXPORT_SYNC_FILE to retrieve the
+ * current set of fences on a dma-buf file descriptor as a sync_file. CPU
+ * waits via poll() or other driver-specific mechanisms typically wait on
+ * whatever fences are on the dma-buf at the time the wait begins. This
+ * is similar except that it takes a snapshot of the current fences on the
+ * dma-buf for waiting later instead of waiting immediately. This is
+ * useful for modern graphics APIs such as Vulkan which assume an explicit
+ * synchronization model but still need to inter-operate with dma-buf.
+ *
+ * The intended usage pattern is the following:
+ *
+ * 1. Export a sync_file with flags corresponding to the expected GPU usage
+ * via DMA_BUF_IOCTL_EXPORT_SYNC_FILE.
+ *
+ * 2. Submit rendering work which uses the dma-buf. The work should wait on
+ * the exported sync file before rendering and produce another sync_file
+ * when complete.
+ *
+ * 3. Import the rendering-complete sync_file into the dma-buf with flags
+ * corresponding to the GPU usage via DMA_BUF_IOCTL_IMPORT_SYNC_FILE.
+ *
+ * Unlike doing implicit synchronization via a GPU kernel driver's exec ioctl,
+ * the above is not a single atomic operation. If userspace wants to ensure
+ * ordering via these fences, it is the respnosibility of userspace to use
+ * locks or other mechanisms to ensure that no other context adds fences or
+ * submits work between steps 1 and 3 above.
+ */
+struct dma_buf_export_sync_file {
+ /**
+ * @flags: Read/write flags
+ *
+ * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both.
+ *
+ * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set,
+ * the returned sync file waits on any writers of the dma-buf to
+ * complete. Waiting on the returned sync file is equivalent to
+ * poll() with POLLIN.
+ *
+ * If DMA_BUF_SYNC_WRITE is set, the returned sync file waits on
+ * any users of the dma-buf (read or write) to complete. Waiting
+ * on the returned sync file is equivalent to poll() with POLLOUT.
+ * If both DMA_BUF_SYNC_WRITE and DMA_BUF_SYNC_READ are set, this
+ * is equivalent to just DMA_BUF_SYNC_WRITE.
+ */
+ __u32 flags;
+ /** @fd: Returned sync file descriptor */
+ __s32 fd;
+};
+
+/**
+ * struct dma_buf_import_sync_file - Insert a sync_file into a dma-buf
+ *
+ * Userspace can perform a DMA_BUF_IOCTL_IMPORT_SYNC_FILE to insert a
+ * sync_file into a dma-buf for the purposes of implicit synchronization
+ * with other dma-buf consumers. This allows clients using explicitly
+ * synchronized APIs such as Vulkan to inter-op with dma-buf consumers
+ * which expect implicit synchronization such as OpenGL or most media
+ * drivers/video.
+ */
+struct dma_buf_import_sync_file {
+ /**
+ * @flags: Read/write flags
+ *
+ * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both.
+ *
+ * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set,
+ * this inserts the sync_file as a read-only fence. Any subsequent
+ * implicitly synchronized writes to this dma-buf will wait on this
+ * fence but reads will not.
+ *
+ * If DMA_BUF_SYNC_WRITE is set, this inserts the sync_file as a
+ * write fence. All subsequent implicitly synchronized access to
+ * this dma-buf will wait on this fence.
+ */
+ __u32 flags;
+ /** @fd: Sync file descriptor */
+ __s32 fd;
+};
+
#define DMA_BUF_BASE 'b'
#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
@@ -92,7 +174,9 @@ struct dma_buf_sync {
* between them in actual uapi, they're just different numbers.
*/
#define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 1, const char *)
-#define DMA_BUF_SET_NAME_A _IOW(DMA_BUF_BASE, 1, u32)
-#define DMA_BUF_SET_NAME_B _IOW(DMA_BUF_BASE, 1, u64)
+#define DMA_BUF_SET_NAME_A _IOW(DMA_BUF_BASE, 1, __u32)
+#define DMA_BUF_SET_NAME_B _IOW(DMA_BUF_BASE, 1, __u64)
+#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file)
+#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file)
#endif
diff --git a/include/linux/drm_fourcc.h b/include/linux/drm_fourcc.h
index ea11dcb4..d6c83d9c 100644
--- a/include/linux/drm_fourcc.h
+++ b/include/linux/drm_fourcc.h
@@ -88,6 +88,18 @@ extern "C" {
*
* The authoritative list of format modifier codes is found in
* `include/uapi/drm/drm_fourcc.h`
+ *
+ * Open Source User Waiver
+ * -----------------------
+ *
+ * Because this is the authoritative source for pixel formats and modifiers
+ * referenced by GL, Vulkan extensions and other standards and hence used both
+ * by open source and closed source driver stacks, the usual requirement for an
+ * upstream in-kernel or open source userspace user does not apply.
+ *
+ * To ensure, as much as feasible, compatibility across stacks and avoid
+ * confusion with incompatible enumerations stakeholders for all relevant driver
+ * stacks should approve additions.
*/
#define fourcc_code(a, b, c, d) ((__u32)(a) | ((__u32)(b) << 8) | \
@@ -99,18 +111,42 @@ extern "C" {
#define DRM_FORMAT_INVALID 0
/* color index */
+#define DRM_FORMAT_C1 fourcc_code('C', '1', ' ', ' ') /* [7:0] C0:C1:C2:C3:C4:C5:C6:C7 1:1:1:1:1:1:1:1 eight pixels/byte */
+#define DRM_FORMAT_C2 fourcc_code('C', '2', ' ', ' ') /* [7:0] C0:C1:C2:C3 2:2:2:2 four pixels/byte */
+#define DRM_FORMAT_C4 fourcc_code('C', '4', ' ', ' ') /* [7:0] C0:C1 4:4 two pixels/byte */
#define DRM_FORMAT_C8 fourcc_code('C', '8', ' ', ' ') /* [7:0] C */
-/* 8 bpp Red */
+/* 1 bpp Darkness (inverse relationship between channel value and brightness) */
+#define DRM_FORMAT_D1 fourcc_code('D', '1', ' ', ' ') /* [7:0] D0:D1:D2:D3:D4:D5:D6:D7 1:1:1:1:1:1:1:1 eight pixels/byte */
+
+/* 2 bpp Darkness (inverse relationship between channel value and brightness) */
+#define DRM_FORMAT_D2 fourcc_code('D', '2', ' ', ' ') /* [7:0] D0:D1:D2:D3 2:2:2:2 four pixels/byte */
+
+/* 4 bpp Darkness (inverse relationship between channel value and brightness) */
+#define DRM_FORMAT_D4 fourcc_code('D', '4', ' ', ' ') /* [7:0] D0:D1 4:4 two pixels/byte */
+
+/* 8 bpp Darkness (inverse relationship between channel value and brightness) */
+#define DRM_FORMAT_D8 fourcc_code('D', '8', ' ', ' ') /* [7:0] D */
+
+/* 1 bpp Red (direct relationship between channel value and brightness) */
+#define DRM_FORMAT_R1 fourcc_code('R', '1', ' ', ' ') /* [7:0] R0:R1:R2:R3:R4:R5:R6:R7 1:1:1:1:1:1:1:1 eight pixels/byte */
+
+/* 2 bpp Red (direct relationship between channel value and brightness) */
+#define DRM_FORMAT_R2 fourcc_code('R', '2', ' ', ' ') /* [7:0] R0:R1:R2:R3 2:2:2:2 four pixels/byte */
+
+/* 4 bpp Red (direct relationship between channel value and brightness) */
+#define DRM_FORMAT_R4 fourcc_code('R', '4', ' ', ' ') /* [7:0] R0:R1 4:4 two pixels/byte */
+
+/* 8 bpp Red (direct relationship between channel value and brightness) */
#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */
-/* 10 bpp Red */
+/* 10 bpp Red (direct relationship between channel value and brightness) */
#define DRM_FORMAT_R10 fourcc_code('R', '1', '0', ' ') /* [15:0] x:R 6:10 little endian */
-/* 12 bpp Red */
+/* 12 bpp Red (direct relationship between channel value and brightness) */
#define DRM_FORMAT_R12 fourcc_code('R', '1', '2', ' ') /* [15:0] x:R 4:12 little endian */
-/* 16 bpp Red */
+/* 16 bpp Red (direct relationship between channel value and brightness) */
#define DRM_FORMAT_R16 fourcc_code('R', '1', '6', ' ') /* [15:0] R little endian */
/* 16 bpp RG */
@@ -205,7 +241,9 @@ extern "C" {
#define DRM_FORMAT_VYUY fourcc_code('V', 'Y', 'U', 'Y') /* [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian */
#define DRM_FORMAT_AYUV fourcc_code('A', 'Y', 'U', 'V') /* [31:0] A:Y:Cb:Cr 8:8:8:8 little endian */
+#define DRM_FORMAT_AVUY8888 fourcc_code('A', 'V', 'U', 'Y') /* [31:0] A:Cr:Cb:Y 8:8:8:8 little endian */
#define DRM_FORMAT_XYUV8888 fourcc_code('X', 'Y', 'U', 'V') /* [31:0] X:Y:Cb:Cr 8:8:8:8 little endian */
+#define DRM_FORMAT_XVUY8888 fourcc_code('X', 'V', 'U', 'Y') /* [31:0] X:Cr:Cb:Y 8:8:8:8 little endian */
#define DRM_FORMAT_VUY888 fourcc_code('V', 'U', '2', '4') /* [23:0] Cr:Cb:Y 8:8:8 little endian */
#define DRM_FORMAT_VUY101010 fourcc_code('V', 'U', '3', '0') /* Y followed by U then V, 10:10:10. Non-linear modifier only */
@@ -285,6 +323,8 @@ extern "C" {
* index 1 = Cr:Cb plane, [39:0] Cr1:Cb1:Cr0:Cb0 little endian
*/
#define DRM_FORMAT_NV15 fourcc_code('N', 'V', '1', '5') /* 2x2 subsampled Cr:Cb plane */
+#define DRM_FORMAT_NV20 fourcc_code('N', 'V', '2', '0') /* 2x1 subsampled Cr:Cb plane */
+#define DRM_FORMAT_NV30 fourcc_code('N', 'V', '3', '0') /* non-subsampled Cr:Cb plane */
/*
* 2 plane YCbCr MSB aligned
@@ -314,6 +354,13 @@ extern "C" {
*/
#define DRM_FORMAT_P016 fourcc_code('P', '0', '1', '6') /* 2x2 subsampled Cr:Cb plane 16 bits per channel */
+/* 2 plane YCbCr420.
+ * 3 10 bit components and 2 padding bits packed into 4 bytes.
+ * index 0 = Y plane, [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian
+ * index 1 = Cr:Cb plane, [63:0] x:Cr2:Cb2:Cr1:x:Cb1:Cr0:Cb0 [2:10:10:10:2:10:10:10] little endian
+ */
+#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') /* 2x2 subsampled Cr:Cb plane 10 bits per channel packed */
+
/* 3 plane non-subsampled (444) YCbCr
* 16 bits per component, but only 10 bits are used and 6 bits are padded
* index 0: Y plane, [15:0] Y:x [10:6] little endian
@@ -617,7 +664,7 @@ extern "C" {
*
* The main surface is Y-tiled and is at plane index 0 whereas CCS is linear
* and at index 1. The clear color is stored at index 2, and the pitch should
- * be ignored. The clear color structure is 256 bits. The first 128 bits
+ * be 64 bytes aligned. The clear color structure is 256 bits. The first 128 bits
* represents Raw Clear Color Red, Green, Blue and Alpha color each represented
* by 32 bits. The raw clear color is consumed by the 3d engine and generates
* the converted clear color of size 64 bits. The first 32 bits store the Lower
@@ -631,6 +678,96 @@ extern "C" {
#define I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC fourcc_mod_code(INTEL, 8)
/*
+ * Intel Tile 4 layout
+ *
+ * This is a tiled layout using 4KB tiles in a row-major layout. It has the same
+ * shape as Tile Y at two granularities: 4KB (128B x 32) and 64B (16B x 4). It
+ * only differs from Tile Y at the 256B granularity in between. At this
+ * granularity, Tile Y has a shape of 16B x 32 rows, but this tiling has a shape
+ * of 64B x 8 rows.
+ */
+#define I915_FORMAT_MOD_4_TILED fourcc_mod_code(INTEL, 9)
+
+/*
+ * Intel color control surfaces (CCS) for DG2 render compression.
+ *
+ * The main surface is Tile 4 and at plane index 0. The CCS data is stored
+ * outside of the GEM object in a reserved memory area dedicated for the
+ * storage of the CCS data for all RC/RC_CC/MC compressible GEM objects. The
+ * main surface pitch is required to be a multiple of four Tile 4 widths.
+ */
+#define I915_FORMAT_MOD_4_TILED_DG2_RC_CCS fourcc_mod_code(INTEL, 10)
+
+/*
+ * Intel color control surfaces (CCS) for DG2 media compression.
+ *
+ * The main surface is Tile 4 and at plane index 0. For semi-planar formats
+ * like NV12, the Y and UV planes are Tile 4 and are located at plane indices
+ * 0 and 1, respectively. The CCS for all planes are stored outside of the
+ * GEM object in a reserved memory area dedicated for the storage of the
+ * CCS data for all RC/RC_CC/MC compressible GEM objects. The main surface
+ * pitch is required to be a multiple of four Tile 4 widths.
+ */
+#define I915_FORMAT_MOD_4_TILED_DG2_MC_CCS fourcc_mod_code(INTEL, 11)
+
+/*
+ * Intel Color Control Surface with Clear Color (CCS) for DG2 render compression.
+ *
+ * The main surface is Tile 4 and at plane index 0. The CCS data is stored
+ * outside of the GEM object in a reserved memory area dedicated for the
+ * storage of the CCS data for all RC/RC_CC/MC compressible GEM objects. The
+ * main surface pitch is required to be a multiple of four Tile 4 widths. The
+ * clear color is stored at plane index 1 and the pitch should be 64 bytes
+ * aligned. The format of the 256 bits of clear color data matches the one used
+ * for the I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC modifier, see its description
+ * for details.
+ */
+#define I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC fourcc_mod_code(INTEL, 12)
+
+/*
+ * Intel Color Control Surfaces (CCS) for display ver. 14 render compression.
+ *
+ * The main surface is tile4 and at plane index 0, the CCS is linear and
+ * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in
+ * main surface. In other words, 4 bits in CCS map to a main surface cache
+ * line pair. The main surface pitch is required to be a multiple of four
+ * tile4 widths.
+ */
+#define I915_FORMAT_MOD_4_TILED_MTL_RC_CCS fourcc_mod_code(INTEL, 13)
+
+/*
+ * Intel Color Control Surfaces (CCS) for display ver. 14 media compression
+ *
+ * The main surface is tile4 and at plane index 0, the CCS is linear and
+ * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in
+ * main surface. In other words, 4 bits in CCS map to a main surface cache
+ * line pair. The main surface pitch is required to be a multiple of four
+ * tile4 widths. For semi-planar formats like NV12, CCS planes follow the
+ * Y and UV planes i.e., planes 0 and 1 are used for Y and UV surfaces,
+ * planes 2 and 3 for the respective CCS.
+ */
+#define I915_FORMAT_MOD_4_TILED_MTL_MC_CCS fourcc_mod_code(INTEL, 14)
+
+/*
+ * Intel Color Control Surface with Clear Color (CCS) for display ver. 14 render
+ * compression.
+ *
+ * The main surface is tile4 and is at plane index 0 whereas CCS is linear
+ * and at index 1. The clear color is stored at index 2, and the pitch should
+ * be ignored. The clear color structure is 256 bits. The first 128 bits
+ * represents Raw Clear Color Red, Green, Blue and Alpha color each represented
+ * by 32 bits. The raw clear color is consumed by the 3d engine and generates
+ * the converted clear color of size 64 bits. The first 32 bits store the Lower
+ * Converted Clear Color value and the next 32 bits store the Higher Converted
+ * Clear Color value when applicable. The Converted Clear Color values are
+ * consumed by the DE. The last 64 bits are used to store Color Discard Enable
+ * and Depth Clear Value Valid which are ignored by the DE. A CCS cache line
+ * corresponds to an area of 4x1 tiles in the main surface. The main surface
+ * pitch is required to be a multiple of 4 tile widths.
+ */
+#define I915_FORMAT_MOD_4_TILED_MTL_RC_CCS_CC fourcc_mod_code(INTEL, 15)
+
+/*
* IPU3 Bayer packing layout
*
* The IPU3 raw Bayer formats use a custom packing layout where there are no
@@ -638,7 +775,7 @@ extern "C" {
* the 6 most significant bits in the last byte unused. The format is little
* endian.
*/
-#define IPU3_FORMAT_MOD_PACKED fourcc_mod_code(INTEL, 9)
+#define IPU3_FORMAT_MOD_PACKED fourcc_mod_code(INTEL, 13)
/*
* Tiled, NV12MT, grouped in 64 (pixels) x 32 (lines) -sized macroblocks
@@ -677,6 +814,28 @@ extern "C" {
*/
#define DRM_FORMAT_MOD_QCOM_COMPRESSED fourcc_mod_code(QCOM, 1)
+/*
+ * Qualcomm Tiled Format
+ *
+ * Similar to DRM_FORMAT_MOD_QCOM_COMPRESSED but not compressed.
+ * Implementation may be platform and base-format specific.
+ *
+ * Each macrotile consists of m x n (mostly 4 x 4) tiles.
+ * Pixel data pitch/stride is aligned with macrotile width.
+ * Pixel data height is aligned with macrotile height.
+ * Entire pixel data buffer is aligned with 4k(bytes).
+ */
+#define DRM_FORMAT_MOD_QCOM_TILED3 fourcc_mod_code(QCOM, 3)
+
+/*
+ * Qualcomm Alternate Tiled Format
+ *
+ * Alternate tiled format typically only used within GMEM.
+ * Implementation may be platform and base-format specific.
+ */
+#define DRM_FORMAT_MOD_QCOM_TILED2 fourcc_mod_code(QCOM, 2)
+
+
/* Vivante framebuffer modifiers */
/*
@@ -717,6 +876,35 @@ extern "C" {
*/
#define DRM_FORMAT_MOD_VIVANTE_SPLIT_SUPER_TILED fourcc_mod_code(VIVANTE, 4)
+/*
+ * Vivante TS (tile-status) buffer modifiers. They can be combined with all of
+ * the color buffer tiling modifiers defined above. When TS is present it's a
+ * separate buffer containing the clear/compression status of each tile. The
+ * modifiers are defined as VIVANTE_MOD_TS_c_s, where c is the color buffer
+ * tile size in bytes covered by one entry in the status buffer and s is the
+ * number of status bits per entry.
+ * We reserve the top 8 bits of the Vivante modifier space for tile status
+ * clear/compression modifiers, as future cores might add some more TS layout
+ * variations.
+ */
+#define VIVANTE_MOD_TS_64_4 (1ULL << 48)
+#define VIVANTE_MOD_TS_64_2 (2ULL << 48)
+#define VIVANTE_MOD_TS_128_4 (3ULL << 48)
+#define VIVANTE_MOD_TS_256_4 (4ULL << 48)
+#define VIVANTE_MOD_TS_MASK (0xfULL << 48)
+
+/*
+ * Vivante compression modifiers. Those depend on a TS modifier being present
+ * as the TS bits get reinterpreted as compression tags instead of simple
+ * clear markers when compression is enabled.
+ */
+#define VIVANTE_MOD_COMP_DEC400 (1ULL << 52)
+#define VIVANTE_MOD_COMP_MASK (0xfULL << 52)
+
+/* Masking out the extension bits will yield the base modifier. */
+#define VIVANTE_MOD_EXT_MASK (VIVANTE_MOD_TS_MASK | \
+ VIVANTE_MOD_COMP_MASK)
+
/* NVIDIA frame buffer modifiers */
/*
@@ -929,6 +1117,10 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
* and UV. Some SAND-using hardware stores UV in a separate tiled
* image from Y to reduce the column height, which is not supported
* with these modifiers.
+ *
+ * The DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT modifier is also
+ * supported for DRM_FORMAT_P030 where the columns remain as 128 bytes
+ * wide, but as this is a 10 bpp format that translates to 96 pixels.
*/
#define DRM_FORMAT_MOD_BROADCOM_SAND32_COL_HEIGHT(v) \
@@ -1358,6 +1550,7 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
#define AMD_FMT_MOD_TILE_VER_GFX9 1
#define AMD_FMT_MOD_TILE_VER_GFX10 2
#define AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS 3
+#define AMD_FMT_MOD_TILE_VER_GFX11 4
/*
* 64K_S is the same for GFX9/GFX10/GFX10_RBPLUS and hence has GFX9 as canonical
@@ -1373,6 +1566,7 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
#define AMD_FMT_MOD_TILE_GFX9_64K_S_X 25
#define AMD_FMT_MOD_TILE_GFX9_64K_D_X 26
#define AMD_FMT_MOD_TILE_GFX9_64K_R_X 27
+#define AMD_FMT_MOD_TILE_GFX11_256K_R_X 31
#define AMD_FMT_MOD_DCC_BLOCK_64B 0
#define AMD_FMT_MOD_DCC_BLOCK_128B 1
@@ -1439,11 +1633,11 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
#define AMD_FMT_MOD_PIPE_MASK 0x7
#define AMD_FMT_MOD_SET(field, value) \
- ((uint64_t)(value) << AMD_FMT_MOD_##field##_SHIFT)
+ ((__u64)(value) << AMD_FMT_MOD_##field##_SHIFT)
#define AMD_FMT_MOD_GET(field, value) \
(((value) >> AMD_FMT_MOD_##field##_SHIFT) & AMD_FMT_MOD_##field##_MASK)
#define AMD_FMT_MOD_CLEAR(field) \
- (~((uint64_t)AMD_FMT_MOD_##field##_MASK << AMD_FMT_MOD_##field##_SHIFT))
+ (~((__u64)AMD_FMT_MOD_##field##_MASK << AMD_FMT_MOD_##field##_SHIFT))
/* Mobile Industry Processor Interface (MIPI) modifiers */
diff --git a/include/linux/intel-ipu3.h b/include/linux/intel-ipu3.h
index f30dce43..bd771f1b 100644
--- a/include/linux/intel-ipu3.h
+++ b/include/linux/intel-ipu3.h
@@ -34,11 +34,17 @@
* struct ipu3_uapi_grid_config - Grid plane config
*
* @width: Grid horizontal dimensions, in number of grid blocks(cells).
+ * For AWB, the range is (16, 80).
+ * For AF/AE, the range is (16, 32).
* @height: Grid vertical dimensions, in number of grid cells.
+ * For AWB, the range is (16, 60).
+ * For AF/AE, the range is (16, 24).
* @block_width_log2: Log2 of the width of each cell in pixels.
- * for (2^3, 2^4, 2^5, 2^6, 2^7), values [3, 7].
+ * For AWB, the range is [3, 6].
+ * For AF/AE, the range is [3, 7].
* @block_height_log2: Log2 of the height of each cell in pixels.
- * for (2^3, 2^4, 2^5, 2^6, 2^7), values [3, 7].
+ * For AWB, the range is [3, 6].
+ * For AF/AE, the range is [3, 7].
* @height_per_slice: The number of blocks in vertical axis per slice.
* Default 2.
* @x_start: X value of top left corner of Region of Interest(ROI).
@@ -68,21 +74,21 @@ struct ipu3_uapi_grid_config {
* @R_avg: Red average in the cell.
* @B_avg: Blue average in the cell.
* @Gb_avg: Green average for blue lines in the cell.
- * @sat_ratio: Percentage of pixels over a given threshold set in
+ * @sat_ratio: Percentage of pixels over the thresholds specified in
* ipu3_uapi_awb_config_s, coded from 0 to 255.
- * @padding0: Unused byte for padding.
- * @padding1: Unused byte for padding.
- * @padding2: Unused byte for padding.
+ * @padding0: Unused byte for padding.
+ * @padding1: Unused byte for padding.
+ * @padding2: Unused byte for padding.
*/
struct ipu3_uapi_awb_set_item {
- unsigned char Gr_avg;
- unsigned char R_avg;
- unsigned char B_avg;
- unsigned char Gb_avg;
- unsigned char sat_ratio;
- unsigned char padding0;
- unsigned char padding1;
- unsigned char padding2;
+ __u8 Gr_avg;
+ __u8 R_avg;
+ __u8 B_avg;
+ __u8 Gb_avg;
+ __u8 sat_ratio;
+ __u8 padding0;
+ __u8 padding1;
+ __u8 padding2;
} __attribute__((packed));
/*
@@ -98,7 +104,6 @@ struct ipu3_uapi_awb_set_item {
(IPU3_UAPI_AWB_MAX_SETS * \
(IPU3_UAPI_AWB_SET_SIZE + IPU3_UAPI_AWB_SPARE_FOR_BUBBLES))
-
/**
* struct ipu3_uapi_awb_raw_buffer - AWB raw buffer
*
@@ -621,8 +626,11 @@ struct ipu3_uapi_stats_3a {
* @b: white balance gain for B channel.
* @gb: white balance gain for Gb channel.
*
- * Precision u3.13, range [0, 8). White balance correction is done by applying
- * a multiplicative gain to each color channels prior to BNR.
+ * For BNR parameters WB gain factor for the three channels [Ggr, Ggb, Gb, Gr].
+ * Their precision is U3.13 and the range is (0, 8) and the actual gain is
+ * Gx + 1, it is typically Gx = 1.
+ *
+ * Pout = {Pin * (1 + Gx)}.
*/
struct ipu3_uapi_bnr_static_config_wb_gains_config {
__u16 gr;
diff --git a/include/linux/media-bus-format.h b/include/linux/media-bus-format.h
index 0dfc11ee..f05f747e 100644
--- a/include/linux/media-bus-format.h
+++ b/include/linux/media-bus-format.h
@@ -34,7 +34,7 @@
#define MEDIA_BUS_FMT_FIXED 0x0001
-/* RGB - next is 0x101e */
+/* RGB - next is 0x1026 */
#define MEDIA_BUS_FMT_RGB444_1X12 0x1016
#define MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE 0x1001
#define MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE 0x1002
@@ -46,8 +46,12 @@
#define MEDIA_BUS_FMT_RGB565_2X8_BE 0x1007
#define MEDIA_BUS_FMT_RGB565_2X8_LE 0x1008
#define MEDIA_BUS_FMT_RGB666_1X18 0x1009
+#define MEDIA_BUS_FMT_RGB666_2X9_BE 0x1025
+#define MEDIA_BUS_FMT_BGR666_1X18 0x1023
#define MEDIA_BUS_FMT_RBG888_1X24 0x100e
#define MEDIA_BUS_FMT_RGB666_1X24_CPADHI 0x1015
+#define MEDIA_BUS_FMT_BGR666_1X24_CPADHI 0x1024
+#define MEDIA_BUS_FMT_RGB565_1X24_CPADHI 0x1022
#define MEDIA_BUS_FMT_RGB666_1X7X3_SPWG 0x1010
#define MEDIA_BUS_FMT_BGR888_1X24 0x1013
#define MEDIA_BUS_FMT_BGR888_3X8 0x101b
@@ -59,13 +63,17 @@
#define MEDIA_BUS_FMT_RGB888_3X8_DELTA 0x101d
#define MEDIA_BUS_FMT_RGB888_1X7X4_SPWG 0x1011
#define MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA 0x1012
+#define MEDIA_BUS_FMT_RGB666_1X30_CPADLO 0x101e
+#define MEDIA_BUS_FMT_RGB888_1X30_CPADLO 0x101f
#define MEDIA_BUS_FMT_ARGB8888_1X32 0x100d
#define MEDIA_BUS_FMT_RGB888_1X32_PADHI 0x100f
#define MEDIA_BUS_FMT_RGB101010_1X30 0x1018
+#define MEDIA_BUS_FMT_RGB666_1X36_CPADLO 0x1020
+#define MEDIA_BUS_FMT_RGB888_1X36_CPADLO 0x1021
#define MEDIA_BUS_FMT_RGB121212_1X36 0x1019
#define MEDIA_BUS_FMT_RGB161616_1X48 0x101a
-/* YUV (including grey) - next is 0x202e */
+/* YUV (including grey) - next is 0x202f */
#define MEDIA_BUS_FMT_Y8_1X8 0x2001
#define MEDIA_BUS_FMT_UV8_1X8 0x2015
#define MEDIA_BUS_FMT_UYVY8_1_5X8 0x2002
@@ -88,6 +96,7 @@
#define MEDIA_BUS_FMT_YUYV12_2X12 0x201e
#define MEDIA_BUS_FMT_YVYU12_2X12 0x201f
#define MEDIA_BUS_FMT_Y14_1X14 0x202d
+#define MEDIA_BUS_FMT_Y16_1X16 0x202e
#define MEDIA_BUS_FMT_UYVY8_1X16 0x200f
#define MEDIA_BUS_FMT_VYUY8_1X16 0x2010
#define MEDIA_BUS_FMT_YUYV8_1X16 0x2011
diff --git a/include/linux/media.h b/include/linux/media.h
index e3123d1a..b5a77bbf 100644
--- a/include/linux/media.h
+++ b/include/linux/media.h
@@ -20,7 +20,6 @@
#ifndef __LINUX_MEDIA_H
#define __LINUX_MEDIA_H
-#include <stdint.h>
#include <linux/ioctl.h>
#include <linux/types.h>
@@ -141,8 +140,8 @@ struct media_device_info {
#define MEDIA_ENT_F_DV_ENCODER (MEDIA_ENT_F_BASE + 0x6002)
/* Entity flags */
-#define MEDIA_ENT_FL_DEFAULT (1 << 0)
-#define MEDIA_ENT_FL_CONNECTOR (1 << 1)
+#define MEDIA_ENT_FL_DEFAULT (1U << 0)
+#define MEDIA_ENT_FL_CONNECTOR (1U << 1)
/* OR with the entity id value to find the next entity */
#define MEDIA_ENT_ID_FLAG_NEXT (1U << 31)
@@ -204,9 +203,9 @@ struct media_entity_desc {
};
};
-#define MEDIA_PAD_FL_SINK (1 << 0)
-#define MEDIA_PAD_FL_SOURCE (1 << 1)
-#define MEDIA_PAD_FL_MUST_CONNECT (1 << 2)
+#define MEDIA_PAD_FL_SINK (1U << 0)
+#define MEDIA_PAD_FL_SOURCE (1U << 1)
+#define MEDIA_PAD_FL_MUST_CONNECT (1U << 2)
struct media_pad_desc {
__u32 entity; /* entity ID */
@@ -215,14 +214,14 @@ struct media_pad_desc {
__u32 reserved[2];
};
-#define MEDIA_LNK_FL_ENABLED (1 << 0)
-#define MEDIA_LNK_FL_IMMUTABLE (1 << 1)
-#define MEDIA_LNK_FL_DYNAMIC (1 << 2)
+#define MEDIA_LNK_FL_ENABLED (1U << 0)
+#define MEDIA_LNK_FL_IMMUTABLE (1U << 1)
+#define MEDIA_LNK_FL_DYNAMIC (1U << 2)
#define MEDIA_LNK_FL_LINK_TYPE (0xf << 28)
-# define MEDIA_LNK_FL_DATA_LINK (0 << 28)
-# define MEDIA_LNK_FL_INTERFACE_LINK (1 << 28)
-# define MEDIA_LNK_FL_ANCILLARY_LINK (2 << 28)
+# define MEDIA_LNK_FL_DATA_LINK (0U << 28)
+# define MEDIA_LNK_FL_INTERFACE_LINK (1U << 28)
+# define MEDIA_LNK_FL_ANCILLARY_LINK (2U << 28)
struct media_link_desc {
struct media_pad_desc source;
@@ -277,7 +276,7 @@ struct media_links_enum {
* struct media_device_info.
*/
#define MEDIA_V2_ENTITY_HAS_FLAGS(media_version) \
- ((media_version) >= ((4 << 16) | (19 << 8) | 0))
+ ((media_version) >= ((4U << 16) | (19U << 8) | 0U))
struct media_v2_entity {
__u32 id;
@@ -312,7 +311,7 @@ struct media_v2_interface {
* struct media_device_info.
*/
#define MEDIA_V2_PAD_HAS_INDEX(media_version) \
- ((media_version) >= ((4 << 16) | (19 << 8) | 0))
+ ((media_version) >= ((4U << 16) | (19U << 8) | 0U))
struct media_v2_pad {
__u32 id;
@@ -415,7 +414,7 @@ struct media_v2_topology {
#define MEDIA_INTF_T_ALSA_TIMER (MEDIA_INTF_T_ALSA_BASE + 7)
/* Obsolete symbol for media_version, no longer used in the kernel */
-#define MEDIA_API_VERSION ((0 << 16) | (1 << 8) | 0)
+#define MEDIA_API_VERSION ((0U << 16) | (1U << 8) | 0U)
#endif /* __LINUX_MEDIA_H */
diff --git a/include/linux/rkisp1-config.h b/include/linux/rkisp1-config.h
index 012293e3..2d1c448a 100644
--- a/include/linux/rkisp1-config.h
+++ b/include/linux/rkisp1-config.h
@@ -4,8 +4,8 @@
* Copyright (C) 2017 Rockchip Electronics Co., Ltd.
*/
-#ifndef _RKISP1_CONFIG_H
-#define _RKISP1_CONFIG_H
+#ifndef _UAPI_RKISP1_CONFIG_H
+#define _UAPI_RKISP1_CONFIG_H
#include <linux/types.h>
@@ -117,7 +117,46 @@
/*
* Defect Pixel Cluster Correction
*/
-#define RKISP1_CIF_ISP_DPCC_METHODS_MAX 3
+#define RKISP1_CIF_ISP_DPCC_METHODS_MAX 3
+
+#define RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE (1U << 2)
+
+#define RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER (1U << 0)
+#define RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_RB_CENTER (1U << 1)
+#define RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_G_3X3 (1U << 2)
+#define RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_RB_3X3 (1U << 3)
+
+/* 0-2 for sets 1-3 */
+#define RKISP1_CIF_ISP_DPCC_SET_USE_STAGE1_USE_SET(n) ((n) << 0)
+#define RKISP1_CIF_ISP_DPCC_SET_USE_STAGE1_USE_FIX_SET (1U << 3)
+
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_PG_GREEN_ENABLE (1U << 0)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_GREEN_ENABLE (1U << 1)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_RO_GREEN_ENABLE (1U << 2)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_GREEN_ENABLE (1U << 3)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_RG_GREEN_ENABLE (1U << 4)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_PG_RED_BLUE_ENABLE (1U << 8)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_RED_BLUE_ENABLE (1U << 9)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_RO_RED_BLUE_ENABLE (1U << 10)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_RED_BLUE_ENABLE (1U << 11)
+#define RKISP1_CIF_ISP_DPCC_METHODS_SET_RG_RED_BLUE_ENABLE (1U << 12)
+
+#define RKISP1_CIF_ISP_DPCC_LINE_THRESH_G(v) ((v) << 0)
+#define RKISP1_CIF_ISP_DPCC_LINE_THRESH_RB(v) ((v) << 8)
+#define RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_G(v) ((v) << 0)
+#define RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_RB(v) ((v) << 8)
+#define RKISP1_CIF_ISP_DPCC_PG_FAC_G(v) ((v) << 0)
+#define RKISP1_CIF_ISP_DPCC_PG_FAC_RB(v) ((v) << 8)
+#define RKISP1_CIF_ISP_DPCC_RND_THRESH_G(v) ((v) << 0)
+#define RKISP1_CIF_ISP_DPCC_RND_THRESH_RB(v) ((v) << 8)
+#define RKISP1_CIF_ISP_DPCC_RG_FAC_G(v) ((v) << 0)
+#define RKISP1_CIF_ISP_DPCC_RG_FAC_RB(v) ((v) << 8)
+
+#define RKISP1_CIF_ISP_DPCC_RO_LIMITS_n_G(n, v) ((v) << ((n) * 4))
+#define RKISP1_CIF_ISP_DPCC_RO_LIMITS_n_RB(n, v) ((v) << ((n) * 4 + 2))
+
+#define RKISP1_CIF_ISP_DPCC_RND_OFFS_n_G(n, v) ((v) << ((n) * 4))
+#define RKISP1_CIF_ISP_DPCC_RND_OFFS_n_RB(n, v) ((v) << ((n) * 4 + 2))
/*
* Denoising pre filter
@@ -140,12 +179,14 @@
* @RKISP1_V11: declared in the original vendor code, but not used
* @RKISP1_V12: used at least in rk3326 and px30
* @RKISP1_V13: used at least in rk1808
+ * @RKISP1_V_IMX8MP: used in at least imx8mp
*/
enum rkisp1_cif_isp_version {
RKISP1_V10 = 10,
RKISP1_V11,
RKISP1_V12,
RKISP1_V13,
+ RKISP1_V_IMX8MP,
};
enum rkisp1_cif_isp_histogram_mode {
@@ -249,16 +290,20 @@ struct rkisp1_cif_isp_bls_config {
};
/**
- * struct rkisp1_cif_isp_dpcc_methods_config - Methods Configuration used by DPCC
+ * struct rkisp1_cif_isp_dpcc_methods_config - DPCC methods set configuration
*
- * Methods Configuration used by Defect Pixel Cluster Correction
+ * This structure stores the configuration of one set of methods for the DPCC
+ * algorithm. Multiple methods can be selected in each set (independently for
+ * the Green and Red/Blue components) through the @method field, the result is
+ * the logical AND of all enabled methods. The remaining fields set thresholds
+ * and factors for each method.
*
- * @method: Method enable bits
- * @line_thresh: Line threshold
- * @line_mad_fac: Line MAD factor
- * @pg_fac: Peak gradient factor
- * @rnd_thresh: Rank Neighbor Difference threshold
- * @rg_fac: Rank gradient factor
+ * @method: Method enable bits (RKISP1_CIF_ISP_DPCC_METHODS_SET_*)
+ * @line_thresh: Line threshold (RKISP1_CIF_ISP_DPCC_LINE_THRESH_*)
+ * @line_mad_fac: Line Mean Absolute Difference factor (RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_*)
+ * @pg_fac: Peak gradient factor (RKISP1_CIF_ISP_DPCC_PG_FAC_*)
+ * @rnd_thresh: Rank Neighbor Difference threshold (RKISP1_CIF_ISP_DPCC_RND_THRESH_*)
+ * @rg_fac: Rank gradient factor (RKISP1_CIF_ISP_DPCC_RG_FAC_*)
*/
struct rkisp1_cif_isp_dpcc_methods_config {
__u32 method;
@@ -272,14 +317,16 @@ struct rkisp1_cif_isp_dpcc_methods_config {
/**
* struct rkisp1_cif_isp_dpcc_config - Configuration used by DPCC
*
- * Configuration used by Defect Pixel Cluster Correction
+ * Configuration used by Defect Pixel Cluster Correction. Three sets of methods
+ * can be configured and selected through the @set_use field. The result is the
+ * logical OR of all enabled sets.
*
- * @mode: dpcc output mode
- * @output_mode: whether use hard coded methods
- * @set_use: stage1 methods set
- * @methods: methods config
- * @ro_limits: rank order limits
- * @rnd_offs: differential rank offsets for rank neighbor difference
+ * @mode: DPCC mode (RKISP1_CIF_ISP_DPCC_MODE_*)
+ * @output_mode: Interpolation output mode (RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_*)
+ * @set_use: Methods sets selection (RKISP1_CIF_ISP_DPCC_SET_USE_*)
+ * @methods: Methods sets configuration
+ * @ro_limits: Rank order limits (RKISP1_CIF_ISP_DPCC_RO_LIMITS_*)
+ * @rnd_offs: Differential rank offsets for rank neighbor difference (RKISP1_CIF_ISP_DPCC_RND_OFFS_*)
*/
struct rkisp1_cif_isp_dpcc_config {
__u32 mode;
@@ -947,4 +994,4 @@ struct rkisp1_stat_buffer {
struct rkisp1_cif_isp_stat params;
};
-#endif /* _RKISP1_CONFIG_H */
+#endif /* _UAPI_RKISP1_CONFIG_H */
diff --git a/include/linux/v4l2-common.h b/include/linux/v4l2-common.h
index 14de1731..c3ca11e3 100644
--- a/include/linux/v4l2-common.h
+++ b/include/linux/v4l2-common.h
@@ -10,45 +10,6 @@
*
* Copyright (C) 2012 Nokia Corporation
* Contact: Sakari Ailus <sakari.ailus@iki.fi>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Alternatively you can redistribute this file under the terms of the
- * BSD license as stated below:
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- * 3. The names of its contributors may not be used to endorse or promote
- * products derived from this software without specific prior written
- * permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
*/
#ifndef __V4L2_COMMON__
diff --git a/include/linux/v4l2-controls.h b/include/linux/v4l2-controls.h
index a055d257..b9f64810 100644
--- a/include/linux/v4l2-controls.h
+++ b/include/linux/v4l2-controls.h
@@ -4,44 +4,6 @@
*
* Copyright (C) 1999-2012 the contributors
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Alternatively you can redistribute this file under the terms of the
- * BSD license as stated below:
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- * 3. The names of its contributors may not be used to endorse or promote
- * products derived from this software without specific prior written
- * permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
* The contents of this header was split off from videodev2.h. All control
* definitions should be added to this header, which is included by
* videodev2.h.
@@ -128,6 +90,7 @@ enum v4l2_colorfx {
V4L2_COLORFX_SOLARIZATION = 13,
V4L2_COLORFX_ANTIQUE = 14,
V4L2_COLORFX_SET_CBCR = 15,
+ V4L2_COLORFX_SET_RGB = 16,
};
#define V4L2_CID_AUTOBRIGHTNESS (V4L2_CID_BASE+32)
#define V4L2_CID_BAND_STOP_FILTER (V4L2_CID_BASE+33)
@@ -145,14 +108,17 @@ enum v4l2_colorfx {
#define V4L2_CID_ALPHA_COMPONENT (V4L2_CID_BASE+41)
#define V4L2_CID_COLORFX_CBCR (V4L2_CID_BASE+42)
+#define V4L2_CID_COLORFX_RGB (V4L2_CID_BASE+43)
/* last CID + 1 */
-#define V4L2_CID_LASTP1 (V4L2_CID_BASE+43)
+#define V4L2_CID_LASTP1 (V4L2_CID_BASE+44)
/* USER-class private control IDs */
-/* The base for the meye driver controls. See linux/meye.h for the list
- * of controls. We reserve 16 controls for this driver. */
+/*
+ * The base for the meye driver controls. This driver was removed, but
+ * we keep this define in case any software still uses it.
+ */
#define V4L2_CID_USER_MEYE_BASE (V4L2_CID_USER_BASE + 0x1000)
/* The base for the bttv driver controls.
@@ -221,6 +187,30 @@ enum v4l2_colorfx {
*/
#define V4L2_CID_USER_ALLEGRO_BASE (V4L2_CID_USER_BASE + 0x1170)
+/*
+ * The base for the isl7998x driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_ISL7998X_BASE (V4L2_CID_USER_BASE + 0x1180)
+
+/*
+ * The base for DW100 driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_DW100_BASE (V4L2_CID_USER_BASE + 0x1190)
+
+/*
+ * The base for Aspeed driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_ASPEED_BASE (V4L2_CID_USER_BASE + 0x11a0)
+
+/*
+ * The base for Nuvoton NPCM driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_NPCM_BASE (V4L2_CID_USER_BASE + 0x11b0)
+
/* MPEG-class control IDs */
/* The MPEG controls are applicable to all codec controls
* and the 'MPEG' part of the define is historical */
@@ -443,6 +433,11 @@ enum v4l2_mpeg_video_multi_slice_mode {
#define V4L2_CID_MPEG_VIDEO_USE_LTR_FRAMES (V4L2_CID_CODEC_BASE+234)
#define V4L2_CID_MPEG_VIDEO_DEC_CONCEAL_COLOR (V4L2_CID_CODEC_BASE+235)
#define V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD (V4L2_CID_CODEC_BASE+236)
+#define V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE (V4L2_CID_CODEC_BASE+237)
+enum v4l2_mpeg_video_intra_refresh_period_type {
+ V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM = 0,
+ V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC = 1,
+};
/* CIDs for the MPEG-2 Part 2 (H.262) codec */
#define V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL (V4L2_CID_CODEC_BASE+270)
@@ -815,6 +810,88 @@ enum v4l2_mpeg_video_frame_skip_mode {
#define V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY (V4L2_CID_CODEC_BASE + 653)
#define V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE (V4L2_CID_CODEC_BASE + 654)
+#define V4L2_CID_MPEG_VIDEO_AV1_PROFILE (V4L2_CID_CODEC_BASE + 655)
+/**
+ * enum v4l2_mpeg_video_av1_profile - AV1 profiles
+ *
+ * @V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN: compliant decoders must be able to decode
+ * streams with seq_profile equal to 0.
+ * @V4L2_MPEG_VIDEO_AV1_PROFILE_HIGH: compliant decoders must be able to decode
+ * streams with seq_profile equal less than or equal to 1.
+ * @V4L2_MPEG_VIDEO_AV1_PROFILE_PROFESSIONAL: compliant decoders must be able to
+ * decode streams with seq_profile less than or equal to 2.
+ *
+ * Conveys the highest profile a decoder can work with.
+ */
+enum v4l2_mpeg_video_av1_profile {
+ V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN = 0,
+ V4L2_MPEG_VIDEO_AV1_PROFILE_HIGH = 1,
+ V4L2_MPEG_VIDEO_AV1_PROFILE_PROFESSIONAL = 2,
+};
+
+#define V4L2_CID_MPEG_VIDEO_AV1_LEVEL (V4L2_CID_CODEC_BASE + 656)
+/**
+ * enum v4l2_mpeg_video_av1_level - AV1 levels
+ *
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_0: Level 2.0.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_1: Level 2.1.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_2: Level 2.2.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_3: Level 2.3.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_0: Level 3.0.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_1: Level 3.1.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_2: Level 3.2.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_3: Level 3.3.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_0: Level 4.0.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_1: Level 4.1.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_2: Level 4.2.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_3: Level 4.3.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_0: Level 5.0.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_1: Level 5.1.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_2: Level 5.2.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_3: Level 5.3.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_0: Level 6.0.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_1: Level 6.1.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_2: Level 6.2.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_3: Level 6.3.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_0: Level 7.0.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_1: Level 7.1.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_2: Level 7.2.
+ * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_3: Level 7.3.
+ *
+ * Conveys the highest level a decoder can work with.
+ */
+enum v4l2_mpeg_video_av1_level {
+ V4L2_MPEG_VIDEO_AV1_LEVEL_2_0 = 0,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_2_1 = 1,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_2_2 = 2,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_2_3 = 3,
+
+ V4L2_MPEG_VIDEO_AV1_LEVEL_3_0 = 4,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_3_1 = 5,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_3_2 = 6,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_3_3 = 7,
+
+ V4L2_MPEG_VIDEO_AV1_LEVEL_4_0 = 8,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_4_1 = 9,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_4_2 = 10,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_4_3 = 11,
+
+ V4L2_MPEG_VIDEO_AV1_LEVEL_5_0 = 12,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_5_1 = 13,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_5_2 = 14,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_5_3 = 15,
+
+ V4L2_MPEG_VIDEO_AV1_LEVEL_6_0 = 16,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_6_1 = 17,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_6_2 = 18,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_6_3 = 19,
+
+ V4L2_MPEG_VIDEO_AV1_LEVEL_7_0 = 20,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_7_1 = 21,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_7_2 = 22,
+ V4L2_MPEG_VIDEO_AV1_LEVEL_7_3 = 23
+};
+
/* MPEG-class control IDs specific to the CX2341x driver as defined by V4L2 */
#define V4L2_CID_CODEC_CX2341X_BASE (V4L2_CTRL_CLASS_CODEC | 0x1000)
#define V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE (V4L2_CID_CODEC_CX2341X_BASE+0)
@@ -1002,6 +1079,8 @@ enum v4l2_auto_focus_range {
#define V4L2_CID_CAMERA_SENSOR_ROTATION (V4L2_CID_CAMERA_CLASS_BASE+35)
+#define V4L2_CID_HDR_SENSOR_MODE (V4L2_CID_CAMERA_CLASS_BASE+36)
+
/* FM Modulator class control IDs */
#define V4L2_CID_FM_TX_CLASS_BASE (V4L2_CTRL_CLASS_FM_TX | 0x900)
@@ -1563,6 +1642,8 @@ struct v4l2_h264_dpb_entry {
#define V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC 0x01
#define V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC 0x02
#define V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD 0x04
+#define V4L2_H264_DECODE_PARAM_FLAG_PFRAME 0x08
+#define V4L2_H264_DECODE_PARAM_FLAG_BFRAME 0x10
#define V4L2_CID_STATELESS_H264_DECODE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 7)
/**
@@ -1717,7 +1798,7 @@ struct v4l2_vp8_segment {
* @sharpness_level: matches sharpness_level syntax element.
* @level: matches loop_filter_level syntax element.
* @padding: padding field. Should be zeroed by applications.
- * @flags: see V4L2_VP8_LF_FLAG_{}.
+ * @flags: see V4L2_VP8_LF_{}.
*
* This structure contains loop filter related parameters.
* See the 'mb_lf_adjustments()' part of the frame header syntax,
@@ -1984,6 +2065,469 @@ struct v4l2_ctrl_mpeg2_quantisation {
__u8 chroma_non_intra_quantiser_matrix[64];
};
+#define V4L2_CID_STATELESS_HEVC_SPS (V4L2_CID_CODEC_STATELESS_BASE + 400)
+#define V4L2_CID_STATELESS_HEVC_PPS (V4L2_CID_CODEC_STATELESS_BASE + 401)
+#define V4L2_CID_STATELESS_HEVC_SLICE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 402)
+#define V4L2_CID_STATELESS_HEVC_SCALING_MATRIX (V4L2_CID_CODEC_STATELESS_BASE + 403)
+#define V4L2_CID_STATELESS_HEVC_DECODE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 404)
+#define V4L2_CID_STATELESS_HEVC_DECODE_MODE (V4L2_CID_CODEC_STATELESS_BASE + 405)
+#define V4L2_CID_STATELESS_HEVC_START_CODE (V4L2_CID_CODEC_STATELESS_BASE + 406)
+#define V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS (V4L2_CID_CODEC_STATELESS_BASE + 407)
+
+enum v4l2_stateless_hevc_decode_mode {
+ V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED,
+ V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+};
+
+enum v4l2_stateless_hevc_start_code {
+ V4L2_STATELESS_HEVC_START_CODE_NONE,
+ V4L2_STATELESS_HEVC_START_CODE_ANNEX_B,
+};
+
+#define V4L2_HEVC_SLICE_TYPE_B 0
+#define V4L2_HEVC_SLICE_TYPE_P 1
+#define V4L2_HEVC_SLICE_TYPE_I 2
+
+#define V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE (1ULL << 0)
+#define V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED (1ULL << 1)
+#define V4L2_HEVC_SPS_FLAG_AMP_ENABLED (1ULL << 2)
+#define V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET (1ULL << 3)
+#define V4L2_HEVC_SPS_FLAG_PCM_ENABLED (1ULL << 4)
+#define V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED (1ULL << 5)
+#define V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT (1ULL << 6)
+#define V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED (1ULL << 7)
+#define V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED (1ULL << 8)
+
+/**
+ * struct v4l2_ctrl_hevc_sps - ITU-T Rec. H.265: Sequence parameter set
+ *
+ * @video_parameter_set_id: specifies the value of the
+ * vps_video_parameter_set_id of the active VPS
+ * @seq_parameter_set_id: provides an identifier for the SPS for
+ * reference by other syntax elements
+ * @pic_width_in_luma_samples: specifies the width of each decoded picture
+ * in units of luma samples
+ * @pic_height_in_luma_samples: specifies the height of each decoded picture
+ * in units of luma samples
+ * @bit_depth_luma_minus8: this value plus 8specifies the bit depth of the
+ * samples of the luma array
+ * @bit_depth_chroma_minus8: this value plus 8 specifies the bit depth of the
+ * samples of the chroma arrays
+ * @log2_max_pic_order_cnt_lsb_minus4: this value plus 4 specifies the value of
+ * the variable MaxPicOrderCntLsb
+ * @sps_max_dec_pic_buffering_minus1: this value plus 1 specifies the maximum
+ * required size of the decoded picture
+ * buffer for the codec video sequence
+ * @sps_max_num_reorder_pics: indicates the maximum allowed number of pictures
+ * @sps_max_latency_increase_plus1: not equal to 0 is used to compute the
+ * value of SpsMaxLatencyPictures array
+ * @log2_min_luma_coding_block_size_minus3: plus 3 specifies the minimum
+ * luma coding block size
+ * @log2_diff_max_min_luma_coding_block_size: specifies the difference between
+ * the maximum and minimum luma
+ * coding block size
+ * @log2_min_luma_transform_block_size_minus2: plus 2 specifies the minimum luma
+ * transform block size
+ * @log2_diff_max_min_luma_transform_block_size: specifies the difference between
+ * the maximum and minimum luma
+ * transform block size
+ * @max_transform_hierarchy_depth_inter: specifies the maximum hierarchy
+ * depth for transform units of
+ * coding units coded in inter
+ * prediction mode
+ * @max_transform_hierarchy_depth_intra: specifies the maximum hierarchy
+ * depth for transform units of
+ * coding units coded in intra
+ * prediction mode
+ * @pcm_sample_bit_depth_luma_minus1: this value plus 1 specifies the number of
+ * bits used to represent each of PCM sample
+ * values of the luma component
+ * @pcm_sample_bit_depth_chroma_minus1: this value plus 1 specifies the number
+ * of bits used to represent each of PCM
+ * sample values of the chroma components
+ * @log2_min_pcm_luma_coding_block_size_minus3: this value plus 3 specifies the
+ * minimum size of coding blocks
+ * @log2_diff_max_min_pcm_luma_coding_block_size: specifies the difference between
+ * the maximum and minimum size of
+ * coding blocks
+ * @num_short_term_ref_pic_sets: specifies the number of st_ref_pic_set()
+ * syntax structures included in the SPS
+ * @num_long_term_ref_pics_sps: specifies the number of candidate long-term
+ * reference pictures that are specified in the SPS
+ * @chroma_format_idc: specifies the chroma sampling
+ * @sps_max_sub_layers_minus1: this value plus 1 specifies the maximum number
+ * of temporal sub-layers
+ * @reserved: padding field. Should be zeroed by applications.
+ * @flags: see V4L2_HEVC_SPS_FLAG_{}
+ */
+struct v4l2_ctrl_hevc_sps {
+ __u8 video_parameter_set_id;
+ __u8 seq_parameter_set_id;
+ __u16 pic_width_in_luma_samples;
+ __u16 pic_height_in_luma_samples;
+ __u8 bit_depth_luma_minus8;
+ __u8 bit_depth_chroma_minus8;
+ __u8 log2_max_pic_order_cnt_lsb_minus4;
+ __u8 sps_max_dec_pic_buffering_minus1;
+ __u8 sps_max_num_reorder_pics;
+ __u8 sps_max_latency_increase_plus1;
+ __u8 log2_min_luma_coding_block_size_minus3;
+ __u8 log2_diff_max_min_luma_coding_block_size;
+ __u8 log2_min_luma_transform_block_size_minus2;
+ __u8 log2_diff_max_min_luma_transform_block_size;
+ __u8 max_transform_hierarchy_depth_inter;
+ __u8 max_transform_hierarchy_depth_intra;
+ __u8 pcm_sample_bit_depth_luma_minus1;
+ __u8 pcm_sample_bit_depth_chroma_minus1;
+ __u8 log2_min_pcm_luma_coding_block_size_minus3;
+ __u8 log2_diff_max_min_pcm_luma_coding_block_size;
+ __u8 num_short_term_ref_pic_sets;
+ __u8 num_long_term_ref_pics_sps;
+ __u8 chroma_format_idc;
+ __u8 sps_max_sub_layers_minus1;
+
+ __u8 reserved[6];
+ __u64 flags;
+};
+
+#define V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED (1ULL << 0)
+#define V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT (1ULL << 1)
+#define V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED (1ULL << 2)
+#define V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT (1ULL << 3)
+#define V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED (1ULL << 4)
+#define V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED (1ULL << 5)
+#define V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED (1ULL << 6)
+#define V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT (1ULL << 7)
+#define V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED (1ULL << 8)
+#define V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED (1ULL << 9)
+#define V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED (1ULL << 10)
+#define V4L2_HEVC_PPS_FLAG_TILES_ENABLED (1ULL << 11)
+#define V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED (1ULL << 12)
+#define V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED (1ULL << 13)
+#define V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED (1ULL << 14)
+#define V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED (1ULL << 15)
+#define V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER (1ULL << 16)
+#define V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT (1ULL << 17)
+#define V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT (1ULL << 18)
+#define V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT (1ULL << 19)
+#define V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING (1ULL << 20)
+
+/**
+ * struct v4l2_ctrl_hevc_pps - ITU-T Rec. H.265: Picture parameter set
+ *
+ * @pic_parameter_set_id: identifies the PPS for reference by other
+ * syntax elements
+ * @num_extra_slice_header_bits: specifies the number of extra slice header
+ * bits that are present in the slice header RBSP
+ * for coded pictures referring to the PPS.
+ * @num_ref_idx_l0_default_active_minus1: this value plus 1 specifies the
+ * inferred value of num_ref_idx_l0_active_minus1
+ * @num_ref_idx_l1_default_active_minus1: this value plus 1 specifies the
+ * inferred value of num_ref_idx_l1_active_minus1
+ * @init_qp_minus26: this value plus 26 specifies the initial value of SliceQp Y for
+ * each slice referring to the PPS
+ * @diff_cu_qp_delta_depth: specifies the difference between the luma coding
+ * tree block size and the minimum luma coding block
+ * size of coding units that convey cu_qp_delta_abs
+ * and cu_qp_delta_sign_flag
+ * @pps_cb_qp_offset: specify the offsets to the luma quantization parameter Cb
+ * @pps_cr_qp_offset: specify the offsets to the luma quantization parameter Cr
+ * @num_tile_columns_minus1: this value plus 1 specifies the number of tile columns
+ * partitioning the picture
+ * @num_tile_rows_minus1: this value plus 1 specifies the number of tile rows partitioning
+ * the picture
+ * @column_width_minus1: this value plus 1 specifies the width of the each tile column in
+ * units of coding tree blocks
+ * @row_height_minus1: this value plus 1 specifies the height of the each tile row in
+ * units of coding tree blocks
+ * @pps_beta_offset_div2: specify the default deblocking parameter offsets for
+ * beta divided by 2
+ * @pps_tc_offset_div2: specify the default deblocking parameter offsets for tC
+ * divided by 2
+ * @log2_parallel_merge_level_minus2: this value plus 2 specifies the value of
+ * the variable Log2ParMrgLevel
+ * @reserved: padding field. Should be zeroed by applications.
+ * @flags: see V4L2_HEVC_PPS_FLAG_{}
+ */
+struct v4l2_ctrl_hevc_pps {
+ __u8 pic_parameter_set_id;
+ __u8 num_extra_slice_header_bits;
+ __u8 num_ref_idx_l0_default_active_minus1;
+ __u8 num_ref_idx_l1_default_active_minus1;
+ __s8 init_qp_minus26;
+ __u8 diff_cu_qp_delta_depth;
+ __s8 pps_cb_qp_offset;
+ __s8 pps_cr_qp_offset;
+ __u8 num_tile_columns_minus1;
+ __u8 num_tile_rows_minus1;
+ __u8 column_width_minus1[20];
+ __u8 row_height_minus1[22];
+ __s8 pps_beta_offset_div2;
+ __s8 pps_tc_offset_div2;
+ __u8 log2_parallel_merge_level_minus2;
+ __u8 reserved;
+ __u64 flags;
+};
+
+#define V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE 0x01
+
+#define V4L2_HEVC_SEI_PIC_STRUCT_FRAME 0
+#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_FIELD 1
+#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_FIELD 2
+#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_BOTTOM 3
+#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_TOP 4
+#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_BOTTOM_TOP 5
+#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_TOP_BOTTOM 6
+#define V4L2_HEVC_SEI_PIC_STRUCT_FRAME_DOUBLING 7
+#define V4L2_HEVC_SEI_PIC_STRUCT_FRAME_TRIPLING 8
+#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_PAIRED_PREVIOUS_BOTTOM 9
+#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_PAIRED_PREVIOUS_TOP 10
+#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_PAIRED_NEXT_BOTTOM 11
+#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_PAIRED_NEXT_TOP 12
+
+#define V4L2_HEVC_DPB_ENTRIES_NUM_MAX 16
+
+/**
+ * struct v4l2_hevc_dpb_entry - HEVC decoded picture buffer entry
+ *
+ * @timestamp: timestamp of the V4L2 capture buffer to use as reference.
+ * @flags: long term flag for the reference frame
+ * @field_pic: whether the reference is a field picture or a frame.
+ * @reserved: padding field. Should be zeroed by applications.
+ * @pic_order_cnt_val: the picture order count of the current picture.
+ */
+struct v4l2_hevc_dpb_entry {
+ __u64 timestamp;
+ __u8 flags;
+ __u8 field_pic;
+ __u16 reserved;
+ __s32 pic_order_cnt_val;
+};
+
+/**
+ * struct v4l2_hevc_pred_weight_table - HEVC weighted prediction parameters
+ *
+ * @delta_luma_weight_l0: the difference of the weighting factor applied
+ * to the luma prediction value for list 0
+ * @luma_offset_l0: the additive offset applied to the luma prediction value
+ * for list 0
+ * @delta_chroma_weight_l0: the difference of the weighting factor applied
+ * to the chroma prediction values for list 0
+ * @chroma_offset_l0: the difference of the additive offset applied to
+ * the chroma prediction values for list 0
+ * @delta_luma_weight_l1: the difference of the weighting factor applied
+ * to the luma prediction value for list 1
+ * @luma_offset_l1: the additive offset applied to the luma prediction value
+ * for list 1
+ * @delta_chroma_weight_l1: the difference of the weighting factor applied
+ * to the chroma prediction values for list 1
+ * @chroma_offset_l1: the difference of the additive offset applied to
+ * the chroma prediction values for list 1
+ * @luma_log2_weight_denom: the base 2 logarithm of the denominator for
+ * all luma weighting factors
+ * @delta_chroma_log2_weight_denom: the difference of the base 2 logarithm
+ * of the denominator for all chroma
+ * weighting factors
+ */
+struct v4l2_hevc_pred_weight_table {
+ __s8 delta_luma_weight_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __s8 luma_offset_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __s8 delta_chroma_weight_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2];
+ __s8 chroma_offset_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2];
+
+ __s8 delta_luma_weight_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __s8 luma_offset_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __s8 delta_chroma_weight_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2];
+ __s8 chroma_offset_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2];
+
+ __u8 luma_log2_weight_denom;
+ __s8 delta_chroma_log2_weight_denom;
+};
+
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA (1ULL << 0)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA (1ULL << 1)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED (1ULL << 2)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO (1ULL << 3)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT (1ULL << 4)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0 (1ULL << 5)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_USE_INTEGER_MV (1ULL << 6)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED (1ULL << 7)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED (1ULL << 8)
+#define V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT (1ULL << 9)
+
+/**
+ * struct v4l2_ctrl_hevc_slice_params - HEVC slice parameters
+ *
+ * This control is a dynamically sized 1-dimensional array,
+ * V4L2_CTRL_FLAG_DYNAMIC_ARRAY flag must be set when using it.
+ *
+ * @bit_size: size (in bits) of the current slice data
+ * @data_byte_offset: offset (in bytes) to the video data in the current slice data
+ * @num_entry_point_offsets: specifies the number of entry point offset syntax
+ * elements in the slice header.
+ * @nal_unit_type: specifies the coding type of the slice (B, P or I)
+ * @nuh_temporal_id_plus1: minus 1 specifies a temporal identifier for the NAL unit
+ * @slice_type: see V4L2_HEVC_SLICE_TYPE_{}
+ * @colour_plane_id: specifies the colour plane associated with the current slice
+ * @slice_pic_order_cnt: specifies the picture order count
+ * @num_ref_idx_l0_active_minus1: this value plus 1 specifies the maximum
+ * reference index for reference picture list 0
+ * that may be used to decode the slice
+ * @num_ref_idx_l1_active_minus1: this value plus 1 specifies the maximum
+ * reference index for reference picture list 1
+ * that may be used to decode the slice
+ * @collocated_ref_idx: specifies the reference index of the collocated picture used
+ * for temporal motion vector prediction
+ * @five_minus_max_num_merge_cand: specifies the maximum number of merging
+ * motion vector prediction candidates supported in
+ * the slice subtracted from 5
+ * @slice_qp_delta: specifies the initial value of QpY to be used for the coding
+ * blocks in the slice
+ * @slice_cb_qp_offset: specifies a difference to be added to the value of pps_cb_qp_offset
+ * @slice_cr_qp_offset: specifies a difference to be added to the value of pps_cr_qp_offset
+ * @slice_act_y_qp_offset: screen content extension parameters
+ * @slice_act_cb_qp_offset: screen content extension parameters
+ * @slice_act_cr_qp_offset: screen content extension parameters
+ * @slice_beta_offset_div2: specify the deblocking parameter offsets for beta divided by 2
+ * @slice_tc_offset_div2: specify the deblocking parameter offsets for tC divided by 2
+ * @pic_struct: indicates whether a picture should be displayed as a frame or as one or
+ * more fields
+ * @reserved0: padding field. Should be zeroed by applications.
+ * @slice_segment_addr: specifies the address of the first coding tree block in
+ * the slice segment
+ * @ref_idx_l0: the list of L0 reference elements as indices in the DPB
+ * @ref_idx_l1: the list of L1 reference elements as indices in the DPB
+ * @short_term_ref_pic_set_size: specifies the size of short-term reference
+ * pictures set included in the SPS
+ * @long_term_ref_pic_set_size: specifies the size of long-term reference
+ * pictures set include in the SPS
+ * @pred_weight_table: the prediction weight coefficients for inter-picture
+ * prediction
+ * @reserved1: padding field. Should be zeroed by applications.
+ * @flags: see V4L2_HEVC_SLICE_PARAMS_FLAG_{}
+ */
+struct v4l2_ctrl_hevc_slice_params {
+ __u32 bit_size;
+ __u32 data_byte_offset;
+ __u32 num_entry_point_offsets;
+
+ /* ISO/IEC 23008-2, ITU-T Rec. H.265: NAL unit header */
+ __u8 nal_unit_type;
+ __u8 nuh_temporal_id_plus1;
+
+ /* ISO/IEC 23008-2, ITU-T Rec. H.265: General slice segment header */
+ __u8 slice_type;
+ __u8 colour_plane_id;
+ __s32 slice_pic_order_cnt;
+ __u8 num_ref_idx_l0_active_minus1;
+ __u8 num_ref_idx_l1_active_minus1;
+ __u8 collocated_ref_idx;
+ __u8 five_minus_max_num_merge_cand;
+ __s8 slice_qp_delta;
+ __s8 slice_cb_qp_offset;
+ __s8 slice_cr_qp_offset;
+ __s8 slice_act_y_qp_offset;
+ __s8 slice_act_cb_qp_offset;
+ __s8 slice_act_cr_qp_offset;
+ __s8 slice_beta_offset_div2;
+ __s8 slice_tc_offset_div2;
+
+ /* ISO/IEC 23008-2, ITU-T Rec. H.265: Picture timing SEI message */
+ __u8 pic_struct;
+
+ __u8 reserved0[3];
+ /* ISO/IEC 23008-2, ITU-T Rec. H.265: General slice segment header */
+ __u32 slice_segment_addr;
+ __u8 ref_idx_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __u8 ref_idx_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __u16 short_term_ref_pic_set_size;
+ __u16 long_term_ref_pic_set_size;
+
+ /* ISO/IEC 23008-2, ITU-T Rec. H.265: Weighted prediction parameter */
+ struct v4l2_hevc_pred_weight_table pred_weight_table;
+
+ __u8 reserved1[2];
+ __u64 flags;
+};
+
+#define V4L2_HEVC_DECODE_PARAM_FLAG_IRAP_PIC 0x1
+#define V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC 0x2
+#define V4L2_HEVC_DECODE_PARAM_FLAG_NO_OUTPUT_OF_PRIOR 0x4
+
+/**
+ * struct v4l2_ctrl_hevc_decode_params - HEVC decode parameters
+ *
+ * @pic_order_cnt_val: picture order count
+ * @short_term_ref_pic_set_size: specifies the size of short-term reference
+ * pictures set included in the SPS of the first slice
+ * @long_term_ref_pic_set_size: specifies the size of long-term reference
+ * pictures set include in the SPS of the first slice
+ * @num_active_dpb_entries: the number of entries in dpb
+ * @num_poc_st_curr_before: the number of reference pictures in the short-term
+ * set that come before the current frame
+ * @num_poc_st_curr_after: the number of reference pictures in the short-term
+ * set that come after the current frame
+ * @num_poc_lt_curr: the number of reference pictures in the long-term set
+ * @poc_st_curr_before: provides the index of the short term before references
+ * in DPB array
+ * @poc_st_curr_after: provides the index of the short term after references
+ * in DPB array
+ * @poc_lt_curr: provides the index of the long term references in DPB array
+ * @num_delta_pocs_of_ref_rps_idx: same as the derived value NumDeltaPocs[RefRpsIdx],
+ * can be used to parse the RPS data in slice headers
+ * instead of skipping it with @short_term_ref_pic_set_size.
+ * @reserved: padding field. Should be zeroed by applications.
+ * @dpb: the decoded picture buffer, for meta-data about reference frames
+ * @flags: see V4L2_HEVC_DECODE_PARAM_FLAG_{}
+ */
+struct v4l2_ctrl_hevc_decode_params {
+ __s32 pic_order_cnt_val;
+ __u16 short_term_ref_pic_set_size;
+ __u16 long_term_ref_pic_set_size;
+ __u8 num_active_dpb_entries;
+ __u8 num_poc_st_curr_before;
+ __u8 num_poc_st_curr_after;
+ __u8 num_poc_lt_curr;
+ __u8 poc_st_curr_before[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __u8 poc_st_curr_after[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __u8 poc_lt_curr[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __u8 num_delta_pocs_of_ref_rps_idx;
+ __u8 reserved[3];
+ struct v4l2_hevc_dpb_entry dpb[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+ __u64 flags;
+};
+
+/**
+ * struct v4l2_ctrl_hevc_scaling_matrix - HEVC scaling lists parameters
+ *
+ * @scaling_list_4x4: scaling list is used for the scaling process for
+ * transform coefficients. The values on each scaling
+ * list are expected in raster scan order
+ * @scaling_list_8x8: scaling list is used for the scaling process for
+ * transform coefficients. The values on each scaling
+ * list are expected in raster scan order
+ * @scaling_list_16x16: scaling list is used for the scaling process for
+ * transform coefficients. The values on each scaling
+ * list are expected in raster scan order
+ * @scaling_list_32x32: scaling list is used for the scaling process for
+ * transform coefficients. The values on each scaling
+ * list are expected in raster scan order
+ * @scaling_list_dc_coef_16x16: scaling list is used for the scaling process
+ * for transform coefficients. The values on each
+ * scaling list are expected in raster scan order.
+ * @scaling_list_dc_coef_32x32: scaling list is used for the scaling process
+ * for transform coefficients. The values on each
+ * scaling list are expected in raster scan order.
+ */
+struct v4l2_ctrl_hevc_scaling_matrix {
+ __u8 scaling_list_4x4[6][16];
+ __u8 scaling_list_8x8[6][64];
+ __u8 scaling_list_16x16[6][64];
+ __u8 scaling_list_32x32[2][64];
+ __u8 scaling_list_dc_coef_16x16[6];
+ __u8 scaling_list_dc_coef_32x32[2];
+};
+
#define V4L2_CID_COLORIMETRY_CLASS_BASE (V4L2_CTRL_CLASS_COLORIMETRY | 0x900)
#define V4L2_CID_COLORIMETRY_CLASS (V4L2_CTRL_CLASS_COLORIMETRY | 1)
@@ -2018,6 +2562,929 @@ struct v4l2_ctrl_hdr10_mastering_display {
__u32 min_display_mastering_luminance;
};
+/* Stateless VP9 controls */
+
+#define V4L2_VP9_LOOP_FILTER_FLAG_DELTA_ENABLED 0x1
+#define V4L2_VP9_LOOP_FILTER_FLAG_DELTA_UPDATE 0x2
+
+/**
+ * struct v4l2_vp9_loop_filter - VP9 loop filter parameters
+ *
+ * @ref_deltas: contains the adjustment needed for the filter level based on the
+ * chosen reference frame. If this syntax element is not present in the bitstream,
+ * users should pass its last value.
+ * @mode_deltas: contains the adjustment needed for the filter level based on the
+ * chosen mode. If this syntax element is not present in the bitstream, users should
+ * pass its last value.
+ * @level: indicates the loop filter strength.
+ * @sharpness: indicates the sharpness level.
+ * @flags: combination of V4L2_VP9_LOOP_FILTER_FLAG_{} flags.
+ * @reserved: padding field. Should be zeroed by applications.
+ *
+ * This structure contains all loop filter related parameters. See sections
+ * '7.2.8 Loop filter semantics' of the VP9 specification for more details.
+ */
+struct v4l2_vp9_loop_filter {
+ __s8 ref_deltas[4];
+ __s8 mode_deltas[2];
+ __u8 level;
+ __u8 sharpness;
+ __u8 flags;
+ __u8 reserved[7];
+};
+
+/**
+ * struct v4l2_vp9_quantization - VP9 quantization parameters
+ *
+ * @base_q_idx: indicates the base frame qindex.
+ * @delta_q_y_dc: indicates the Y DC quantizer relative to base_q_idx.
+ * @delta_q_uv_dc: indicates the UV DC quantizer relative to base_q_idx.
+ * @delta_q_uv_ac: indicates the UV AC quantizer relative to base_q_idx.
+ * @reserved: padding field. Should be zeroed by applications.
+ *
+ * Encodes the quantization parameters. See section '7.2.9 Quantization params
+ * syntax' of the VP9 specification for more details.
+ */
+struct v4l2_vp9_quantization {
+ __u8 base_q_idx;
+ __s8 delta_q_y_dc;
+ __s8 delta_q_uv_dc;
+ __s8 delta_q_uv_ac;
+ __u8 reserved[4];
+};
+
+#define V4L2_VP9_SEGMENTATION_FLAG_ENABLED 0x01
+#define V4L2_VP9_SEGMENTATION_FLAG_UPDATE_MAP 0x02
+#define V4L2_VP9_SEGMENTATION_FLAG_TEMPORAL_UPDATE 0x04
+#define V4L2_VP9_SEGMENTATION_FLAG_UPDATE_DATA 0x08
+#define V4L2_VP9_SEGMENTATION_FLAG_ABS_OR_DELTA_UPDATE 0x10
+
+#define V4L2_VP9_SEG_LVL_ALT_Q 0
+#define V4L2_VP9_SEG_LVL_ALT_L 1
+#define V4L2_VP9_SEG_LVL_REF_FRAME 2
+#define V4L2_VP9_SEG_LVL_SKIP 3
+#define V4L2_VP9_SEG_LVL_MAX 4
+
+#define V4L2_VP9_SEGMENT_FEATURE_ENABLED(id) (1 << (id))
+#define V4L2_VP9_SEGMENT_FEATURE_ENABLED_MASK 0xf
+
+/**
+ * struct v4l2_vp9_segmentation - VP9 segmentation parameters
+ *
+ * @feature_data: data attached to each feature. Data entry is only valid if
+ * the feature is enabled. The array shall be indexed with segment number as
+ * the first dimension (0..7) and one of V4L2_VP9_SEG_{} as the second dimension.
+ * @feature_enabled: bitmask defining which features are enabled in each segment.
+ * The value for each segment is a combination of V4L2_VP9_SEGMENT_FEATURE_ENABLED(id)
+ * values where id is one of V4L2_VP9_SEG_LVL_{}.
+ * @tree_probs: specifies the probability values to be used when decoding a
+ * Segment-ID. See '5.15. Segmentation map' section of the VP9 specification
+ * for more details.
+ * @pred_probs: specifies the probability values to be used when decoding a
+ * Predicted-Segment-ID. See '6.4.14. Get segment id syntax' section of :ref:`vp9`
+ * for more details.
+ * @flags: combination of V4L2_VP9_SEGMENTATION_FLAG_{} flags.
+ * @reserved: padding field. Should be zeroed by applications.
+ *
+ * Encodes the quantization parameters. See section '7.2.10 Segmentation params syntax' of
+ * the VP9 specification for more details.
+ */
+struct v4l2_vp9_segmentation {
+ __s16 feature_data[8][4];
+ __u8 feature_enabled[8];
+ __u8 tree_probs[7];
+ __u8 pred_probs[3];
+ __u8 flags;
+ __u8 reserved[5];
+};
+
+#define V4L2_VP9_FRAME_FLAG_KEY_FRAME 0x001
+#define V4L2_VP9_FRAME_FLAG_SHOW_FRAME 0x002
+#define V4L2_VP9_FRAME_FLAG_ERROR_RESILIENT 0x004
+#define V4L2_VP9_FRAME_FLAG_INTRA_ONLY 0x008
+#define V4L2_VP9_FRAME_FLAG_ALLOW_HIGH_PREC_MV 0x010
+#define V4L2_VP9_FRAME_FLAG_REFRESH_FRAME_CTX 0x020
+#define V4L2_VP9_FRAME_FLAG_PARALLEL_DEC_MODE 0x040
+#define V4L2_VP9_FRAME_FLAG_X_SUBSAMPLING 0x080
+#define V4L2_VP9_FRAME_FLAG_Y_SUBSAMPLING 0x100
+#define V4L2_VP9_FRAME_FLAG_COLOR_RANGE_FULL_SWING 0x200
+
+#define V4L2_VP9_SIGN_BIAS_LAST 0x1
+#define V4L2_VP9_SIGN_BIAS_GOLDEN 0x2
+#define V4L2_VP9_SIGN_BIAS_ALT 0x4
+
+#define V4L2_VP9_RESET_FRAME_CTX_NONE 0
+#define V4L2_VP9_RESET_FRAME_CTX_SPEC 1
+#define V4L2_VP9_RESET_FRAME_CTX_ALL 2
+
+#define V4L2_VP9_INTERP_FILTER_EIGHTTAP 0
+#define V4L2_VP9_INTERP_FILTER_EIGHTTAP_SMOOTH 1
+#define V4L2_VP9_INTERP_FILTER_EIGHTTAP_SHARP 2
+#define V4L2_VP9_INTERP_FILTER_BILINEAR 3
+#define V4L2_VP9_INTERP_FILTER_SWITCHABLE 4
+
+#define V4L2_VP9_REFERENCE_MODE_SINGLE_REFERENCE 0
+#define V4L2_VP9_REFERENCE_MODE_COMPOUND_REFERENCE 1
+#define V4L2_VP9_REFERENCE_MODE_SELECT 2
+
+#define V4L2_VP9_PROFILE_MAX 3
+
+#define V4L2_CID_STATELESS_VP9_FRAME (V4L2_CID_CODEC_STATELESS_BASE + 300)
+/**
+ * struct v4l2_ctrl_vp9_frame - VP9 frame decoding control
+ *
+ * @lf: loop filter parameters. See &v4l2_vp9_loop_filter for more details.
+ * @quant: quantization parameters. See &v4l2_vp9_quantization for more details.
+ * @seg: segmentation parameters. See &v4l2_vp9_segmentation for more details.
+ * @flags: combination of V4L2_VP9_FRAME_FLAG_{} flags.
+ * @compressed_header_size: compressed header size in bytes.
+ * @uncompressed_header_size: uncompressed header size in bytes.
+ * @frame_width_minus_1: add 1 to it and you'll get the frame width expressed in pixels.
+ * @frame_height_minus_1: add 1 to it and you'll get the frame height expressed in pixels.
+ * @render_width_minus_1: add 1 to it and you'll get the expected render width expressed in
+ * pixels. This is not used during the decoding process but might be used by HW scalers
+ * to prepare a frame that's ready for scanout.
+ * @render_height_minus_1: add 1 to it and you'll get the expected render height expressed in
+ * pixels. This is not used during the decoding process but might be used by HW scalers
+ * to prepare a frame that's ready for scanout.
+ * @last_frame_ts: "last" reference buffer timestamp.
+ * The timestamp refers to the timestamp field in struct v4l2_buffer.
+ * Use v4l2_timeval_to_ns() to convert the struct timeval to a __u64.
+ * @golden_frame_ts: "golden" reference buffer timestamp.
+ * The timestamp refers to the timestamp field in struct v4l2_buffer.
+ * Use v4l2_timeval_to_ns() to convert the struct timeval to a __u64.
+ * @alt_frame_ts: "alt" reference buffer timestamp.
+ * The timestamp refers to the timestamp field in struct v4l2_buffer.
+ * Use v4l2_timeval_to_ns() to convert the struct timeval to a __u64.
+ * @ref_frame_sign_bias: a bitfield specifying whether the sign bias is set for a given
+ * reference frame. Either of V4L2_VP9_SIGN_BIAS_{}.
+ * @reset_frame_context: specifies whether the frame context should be reset to default values.
+ * Either of V4L2_VP9_RESET_FRAME_CTX_{}.
+ * @frame_context_idx: frame context that should be used/updated.
+ * @profile: VP9 profile. Can be 0, 1, 2 or 3.
+ * @bit_depth: bits per components. Can be 8, 10 or 12. Note that not all profiles support
+ * 10 and/or 12 bits depths.
+ * @interpolation_filter: specifies the filter selection used for performing inter prediction.
+ * Set to one of V4L2_VP9_INTERP_FILTER_{}.
+ * @tile_cols_log2: specifies the base 2 logarithm of the width of each tile (where the width
+ * is measured in units of 8x8 blocks). Shall be less than or equal to 6.
+ * @tile_rows_log2: specifies the base 2 logarithm of the height of each tile (where the height
+ * is measured in units of 8x8 blocks).
+ * @reference_mode: specifies the type of inter prediction to be used.
+ * Set to one of V4L2_VP9_REFERENCE_MODE_{}.
+ * @reserved: padding field. Should be zeroed by applications.
+ */
+struct v4l2_ctrl_vp9_frame {
+ struct v4l2_vp9_loop_filter lf;
+ struct v4l2_vp9_quantization quant;
+ struct v4l2_vp9_segmentation seg;
+ __u32 flags;
+ __u16 compressed_header_size;
+ __u16 uncompressed_header_size;
+ __u16 frame_width_minus_1;
+ __u16 frame_height_minus_1;
+ __u16 render_width_minus_1;
+ __u16 render_height_minus_1;
+ __u64 last_frame_ts;
+ __u64 golden_frame_ts;
+ __u64 alt_frame_ts;
+ __u8 ref_frame_sign_bias;
+ __u8 reset_frame_context;
+ __u8 frame_context_idx;
+ __u8 profile;
+ __u8 bit_depth;
+ __u8 interpolation_filter;
+ __u8 tile_cols_log2;
+ __u8 tile_rows_log2;
+ __u8 reference_mode;
+ __u8 reserved[7];
+};
+
+#define V4L2_VP9_NUM_FRAME_CTX 4
+
+/**
+ * struct v4l2_vp9_mv_probs - VP9 Motion vector probability updates
+ * @joint: motion vector joint probability updates.
+ * @sign: motion vector sign probability updates.
+ * @classes: motion vector class probability updates.
+ * @class0_bit: motion vector class0 bit probability updates.
+ * @bits: motion vector bits probability updates.
+ * @class0_fr: motion vector class0 fractional bit probability updates.
+ * @fr: motion vector fractional bit probability updates.
+ * @class0_hp: motion vector class0 high precision fractional bit probability updates.
+ * @hp: motion vector high precision fractional bit probability updates.
+ *
+ * This structure contains new values of motion vector probabilities.
+ * A value of zero in an array element means there is no update of the relevant probability.
+ * See `struct v4l2_vp9_prob_updates` for details.
+ */
+struct v4l2_vp9_mv_probs {
+ __u8 joint[3];
+ __u8 sign[2];
+ __u8 classes[2][10];
+ __u8 class0_bit[2];
+ __u8 bits[2][10];
+ __u8 class0_fr[2][2][3];
+ __u8 fr[2][3];
+ __u8 class0_hp[2];
+ __u8 hp[2];
+};
+
+#define V4L2_CID_STATELESS_VP9_COMPRESSED_HDR (V4L2_CID_CODEC_STATELESS_BASE + 301)
+
+#define V4L2_VP9_TX_MODE_ONLY_4X4 0
+#define V4L2_VP9_TX_MODE_ALLOW_8X8 1
+#define V4L2_VP9_TX_MODE_ALLOW_16X16 2
+#define V4L2_VP9_TX_MODE_ALLOW_32X32 3
+#define V4L2_VP9_TX_MODE_SELECT 4
+
+/**
+ * struct v4l2_ctrl_vp9_compressed_hdr - VP9 probability updates control
+ * @tx_mode: specifies the TX mode. Set to one of V4L2_VP9_TX_MODE_{}.
+ * @tx8: TX 8x8 probability updates.
+ * @tx16: TX 16x16 probability updates.
+ * @tx32: TX 32x32 probability updates.
+ * @coef: coefficient probability updates.
+ * @skip: skip probability updates.
+ * @inter_mode: inter mode probability updates.
+ * @interp_filter: interpolation filter probability updates.
+ * @is_inter: is inter-block probability updates.
+ * @comp_mode: compound prediction mode probability updates.
+ * @single_ref: single ref probability updates.
+ * @comp_ref: compound ref probability updates.
+ * @y_mode: Y prediction mode probability updates.
+ * @uv_mode: UV prediction mode probability updates.
+ * @partition: partition probability updates.
+ * @mv: motion vector probability updates.
+ *
+ * This structure holds the probabilities update as parsed in the compressed
+ * header (Spec 6.3). These values represent the value of probability update after
+ * being translated with inv_map_table[] (see 6.3.5). A value of zero in an array element
+ * means that there is no update of the relevant probability.
+ *
+ * This control is optional and needs to be used when dealing with the hardware which is
+ * not capable of parsing the compressed header itself. Only drivers which need it will
+ * implement it.
+ */
+struct v4l2_ctrl_vp9_compressed_hdr {
+ __u8 tx_mode;
+ __u8 tx8[2][1];
+ __u8 tx16[2][2];
+ __u8 tx32[2][3];
+ __u8 coef[4][2][2][6][6][3];
+ __u8 skip[3];
+ __u8 inter_mode[7][3];
+ __u8 interp_filter[4][2];
+ __u8 is_inter[4];
+ __u8 comp_mode[5];
+ __u8 single_ref[5][2];
+ __u8 comp_ref[5];
+ __u8 y_mode[4][9];
+ __u8 uv_mode[10][9];
+ __u8 partition[16][3];
+
+ struct v4l2_vp9_mv_probs mv;
+};
+
+/* Stateless AV1 controls */
+
+#define V4L2_AV1_TOTAL_REFS_PER_FRAME 8
+#define V4L2_AV1_CDEF_MAX 8
+#define V4L2_AV1_NUM_PLANES_MAX 3 /* 1 if monochrome, 3 otherwise */
+#define V4L2_AV1_MAX_SEGMENTS 8
+#define V4L2_AV1_MAX_OPERATING_POINTS (1 << 5) /* 5 bits to encode */
+#define V4L2_AV1_REFS_PER_FRAME 7
+#define V4L2_AV1_MAX_NUM_Y_POINTS (1 << 4) /* 4 bits to encode */
+#define V4L2_AV1_MAX_NUM_CB_POINTS (1 << 4) /* 4 bits to encode */
+#define V4L2_AV1_MAX_NUM_CR_POINTS (1 << 4) /* 4 bits to encode */
+#define V4L2_AV1_AR_COEFFS_SIZE 25 /* (2 * 3 * (3 + 1)) + 1 */
+#define V4L2_AV1_MAX_NUM_PLANES 3
+#define V4L2_AV1_MAX_TILE_COLS 64
+#define V4L2_AV1_MAX_TILE_ROWS 64
+#define V4L2_AV1_MAX_TILE_COUNT 512
+
+#define V4L2_AV1_SEQUENCE_FLAG_STILL_PICTURE 0x00000001
+#define V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK 0x00000002
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_FILTER_INTRA 0x00000004
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTRA_EDGE_FILTER 0x00000008
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTERINTRA_COMPOUND 0x00000010
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_MASKED_COMPOUND 0x00000020
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_WARPED_MOTION 0x00000040
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_DUAL_FILTER 0x00000080
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT 0x00000100
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_JNT_COMP 0x00000200
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_REF_FRAME_MVS 0x00000400
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_SUPERRES 0x00000800
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF 0x00001000
+#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_RESTORATION 0x00002000
+#define V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME 0x00004000
+#define V4L2_AV1_SEQUENCE_FLAG_COLOR_RANGE 0x00008000
+#define V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X 0x00010000
+#define V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y 0x00020000
+#define V4L2_AV1_SEQUENCE_FLAG_FILM_GRAIN_PARAMS_PRESENT 0x00040000
+#define V4L2_AV1_SEQUENCE_FLAG_SEPARATE_UV_DELTA_Q 0x00080000
+
+#define V4L2_CID_STATELESS_AV1_SEQUENCE (V4L2_CID_CODEC_STATELESS_BASE + 500)
+/**
+ * struct v4l2_ctrl_av1_sequence - AV1 Sequence
+ *
+ * Represents an AV1 Sequence OBU. See section 5.5 "Sequence header OBU syntax"
+ * for more details.
+ *
+ * @flags: See V4L2_AV1_SEQUENCE_FLAG_{}.
+ * @seq_profile: specifies the features that can be used in the coded video
+ * sequence.
+ * @order_hint_bits: specifies the number of bits used for the order_hint field
+ * at each frame.
+ * @bit_depth: the bitdepth to use for the sequence as described in section
+ * 5.5.2 "Color config syntax".
+ * @reserved: padding field. Should be zeroed by applications.
+ * @max_frame_width_minus_1: specifies the maximum frame width minus 1 for the
+ * frames represented by this sequence header.
+ * @max_frame_height_minus_1: specifies the maximum frame height minus 1 for the
+ * frames represented by this sequence header.
+ */
+struct v4l2_ctrl_av1_sequence {
+ __u32 flags;
+ __u8 seq_profile;
+ __u8 order_hint_bits;
+ __u8 bit_depth;
+ __u8 reserved;
+ __u16 max_frame_width_minus_1;
+ __u16 max_frame_height_minus_1;
+};
+
+#define V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY (V4L2_CID_CODEC_STATELESS_BASE + 501)
+/**
+ * struct v4l2_ctrl_av1_tile_group_entry - AV1 Tile Group entry
+ *
+ * Represents a single AV1 tile inside an AV1 Tile Group. Note that MiRowStart,
+ * MiRowEnd, MiColStart and MiColEnd can be retrieved from struct
+ * v4l2_av1_tile_info in struct v4l2_ctrl_av1_frame using tile_row and
+ * tile_col. See section 6.10.1 "General tile group OBU semantics" for more
+ * details.
+ *
+ * @tile_offset: offset from the OBU data, i.e. where the coded tile data
+ * actually starts.
+ * @tile_size: specifies the size in bytes of the coded tile. Equivalent to
+ * "TileSize" in the AV1 Specification.
+ * @tile_row: specifies the row of the current tile. Equivalent to "TileRow" in
+ * the AV1 Specification.
+ * @tile_col: specifies the col of the current tile. Equivalent to "TileCol" in
+ * the AV1 Specification.
+ */
+struct v4l2_ctrl_av1_tile_group_entry {
+ __u32 tile_offset;
+ __u32 tile_size;
+ __u32 tile_row;
+ __u32 tile_col;
+};
+
+/**
+ * enum v4l2_av1_warp_model - AV1 Warp Model as described in section 3
+ * "Symbols and abbreviated terms" of the AV1 Specification.
+ *
+ * @V4L2_AV1_WARP_MODEL_IDENTITY: Warp model is just an identity transform.
+ * @V4L2_AV1_WARP_MODEL_TRANSLATION: Warp model is a pure translation.
+ * @V4L2_AV1_WARP_MODEL_ROTZOOM: Warp model is a rotation + symmetric zoom +
+ * translation.
+ * @V4L2_AV1_WARP_MODEL_AFFINE: Warp model is a general affine transform.
+ */
+enum v4l2_av1_warp_model {
+ V4L2_AV1_WARP_MODEL_IDENTITY = 0,
+ V4L2_AV1_WARP_MODEL_TRANSLATION = 1,
+ V4L2_AV1_WARP_MODEL_ROTZOOM = 2,
+ V4L2_AV1_WARP_MODEL_AFFINE = 3,
+};
+
+/**
+ * enum v4l2_av1_reference_frame - AV1 reference frames
+ *
+ * @V4L2_AV1_REF_INTRA_FRAME: Intra Frame Reference
+ * @V4L2_AV1_REF_LAST_FRAME: Last Reference Frame
+ * @V4L2_AV1_REF_LAST2_FRAME: Last2 Reference Frame
+ * @V4L2_AV1_REF_LAST3_FRAME: Last3 Reference Frame
+ * @V4L2_AV1_REF_GOLDEN_FRAME: Golden Reference Frame
+ * @V4L2_AV1_REF_BWDREF_FRAME: BWD Reference Frame
+ * @V4L2_AV1_REF_ALTREF2_FRAME: Alternative2 Reference Frame
+ * @V4L2_AV1_REF_ALTREF_FRAME: Alternative Reference Frame
+ */
+enum v4l2_av1_reference_frame {
+ V4L2_AV1_REF_INTRA_FRAME = 0,
+ V4L2_AV1_REF_LAST_FRAME = 1,
+ V4L2_AV1_REF_LAST2_FRAME = 2,
+ V4L2_AV1_REF_LAST3_FRAME = 3,
+ V4L2_AV1_REF_GOLDEN_FRAME = 4,
+ V4L2_AV1_REF_BWDREF_FRAME = 5,
+ V4L2_AV1_REF_ALTREF2_FRAME = 6,
+ V4L2_AV1_REF_ALTREF_FRAME = 7,
+};
+
+#define V4L2_AV1_GLOBAL_MOTION_IS_INVALID(ref) (1 << (ref))
+
+#define V4L2_AV1_GLOBAL_MOTION_FLAG_IS_GLOBAL 0x1
+#define V4L2_AV1_GLOBAL_MOTION_FLAG_IS_ROT_ZOOM 0x2
+#define V4L2_AV1_GLOBAL_MOTION_FLAG_IS_TRANSLATION 0x4
+/**
+ * struct v4l2_av1_global_motion - AV1 Global Motion parameters as described in
+ * section 6.8.17 "Global motion params semantics" of the AV1 specification.
+ *
+ * @flags: A bitfield containing the flags per reference frame. See
+ * V4L2_AV1_GLOBAL_MOTION_FLAG_{}
+ * @type: The type of global motion transform used.
+ * @params: this field has the same meaning as "gm_params" in the AV1
+ * specification.
+ * @invalid: bitfield indicating whether the global motion params are invalid
+ * for a given reference frame. See section 7.11.3.6 Setup shear process and
+ * the variable "warpValid". Use V4L2_AV1_GLOBAL_MOTION_IS_INVALID(ref) to
+ * create a suitable mask.
+ * @reserved: padding field. Should be zeroed by applications.
+ */
+
+struct v4l2_av1_global_motion {
+ __u8 flags[V4L2_AV1_TOTAL_REFS_PER_FRAME];
+ enum v4l2_av1_warp_model type[V4L2_AV1_TOTAL_REFS_PER_FRAME];
+ __s32 params[V4L2_AV1_TOTAL_REFS_PER_FRAME][6];
+ __u8 invalid;
+ __u8 reserved[3];
+};
+
+/**
+ * enum v4l2_av1_frame_restoration_type - AV1 Frame Restoration Type
+ * @V4L2_AV1_FRAME_RESTORE_NONE: no filtering is applied.
+ * @V4L2_AV1_FRAME_RESTORE_WIENER: Wiener filter process is invoked.
+ * @V4L2_AV1_FRAME_RESTORE_SGRPROJ: self guided filter process is invoked.
+ * @V4L2_AV1_FRAME_RESTORE_SWITCHABLE: restoration filter is swichtable.
+ */
+enum v4l2_av1_frame_restoration_type {
+ V4L2_AV1_FRAME_RESTORE_NONE = 0,
+ V4L2_AV1_FRAME_RESTORE_WIENER = 1,
+ V4L2_AV1_FRAME_RESTORE_SGRPROJ = 2,
+ V4L2_AV1_FRAME_RESTORE_SWITCHABLE = 3,
+};
+
+#define V4L2_AV1_LOOP_RESTORATION_FLAG_USES_LR 0x1
+#define V4L2_AV1_LOOP_RESTORATION_FLAG_USES_CHROMA_LR 0x2
+
+/**
+ * struct v4l2_av1_loop_restoration - AV1 Loop Restauration as described in
+ * section 6.10.15 "Loop restoration params semantics" of the AV1 specification.
+ *
+ * @flags: See V4L2_AV1_LOOP_RESTORATION_FLAG_{}.
+ * @lr_unit_shift: specifies if the luma restoration size should be halved.
+ * @lr_uv_shift: specifies if the chroma size should be half the luma size.
+ * @reserved: padding field. Should be zeroed by applications.
+ * @frame_restoration_type: specifies the type of restoration used for each
+ * plane. See enum v4l2_av1_frame_restoration_type.
+ * @loop_restoration_size: specifies the size of loop restoration units in units
+ * of samples in the current plane.
+ */
+struct v4l2_av1_loop_restoration {
+ __u8 flags;
+ __u8 lr_unit_shift;
+ __u8 lr_uv_shift;
+ __u8 reserved;
+ enum v4l2_av1_frame_restoration_type frame_restoration_type[V4L2_AV1_NUM_PLANES_MAX];
+ __u32 loop_restoration_size[V4L2_AV1_MAX_NUM_PLANES];
+};
+
+/**
+ * struct v4l2_av1_cdef - AV1 CDEF params semantics as described in section
+ * 6.10.14 "CDEF params semantics" of the AV1 specification
+ *
+ * @damping_minus_3: controls the amount of damping in the deringing filter.
+ * @bits: specifies the number of bits needed to specify which CDEF filter to
+ * apply.
+ * @y_pri_strength: specifies the strength of the primary filter.
+ * @y_sec_strength: specifies the strength of the secondary filter.
+ * @uv_pri_strength: specifies the strength of the primary filter.
+ * @uv_sec_strength: specifies the strength of the secondary filter.
+ */
+struct v4l2_av1_cdef {
+ __u8 damping_minus_3;
+ __u8 bits;
+ __u8 y_pri_strength[V4L2_AV1_CDEF_MAX];
+ __u8 y_sec_strength[V4L2_AV1_CDEF_MAX];
+ __u8 uv_pri_strength[V4L2_AV1_CDEF_MAX];
+ __u8 uv_sec_strength[V4L2_AV1_CDEF_MAX];
+};
+
+#define V4L2_AV1_SEGMENTATION_FLAG_ENABLED 0x1
+#define V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP 0x2
+#define V4L2_AV1_SEGMENTATION_FLAG_TEMPORAL_UPDATE 0x4
+#define V4L2_AV1_SEGMENTATION_FLAG_UPDATE_DATA 0x8
+#define V4L2_AV1_SEGMENTATION_FLAG_SEG_ID_PRE_SKIP 0x10
+
+/**
+ * enum v4l2_av1_segment_feature - AV1 segment features as described in section
+ * 3 "Symbols and abbreviated terms" of the AV1 specification.
+ *
+ * @V4L2_AV1_SEG_LVL_ALT_Q: Index for quantizer segment feature.
+ * @V4L2_AV1_SEG_LVL_ALT_LF_Y_V: Index for vertical luma loop filter segment
+ * feature.
+ * @V4L2_AV1_SEG_LVL_REF_FRAME: Index for reference frame segment feature.
+ * @V4L2_AV1_SEG_LVL_REF_SKIP: Index for skip segment feature.
+ * @V4L2_AV1_SEG_LVL_REF_GLOBALMV: Index for global mv feature.
+ * @V4L2_AV1_SEG_LVL_MAX: Number of segment features.
+ */
+enum v4l2_av1_segment_feature {
+ V4L2_AV1_SEG_LVL_ALT_Q = 0,
+ V4L2_AV1_SEG_LVL_ALT_LF_Y_V = 1,
+ V4L2_AV1_SEG_LVL_REF_FRAME = 5,
+ V4L2_AV1_SEG_LVL_REF_SKIP = 6,
+ V4L2_AV1_SEG_LVL_REF_GLOBALMV = 7,
+ V4L2_AV1_SEG_LVL_MAX = 8
+};
+
+#define V4L2_AV1_SEGMENT_FEATURE_ENABLED(id) (1 << (id))
+
+/**
+ * struct v4l2_av1_segmentation - AV1 Segmentation params as defined in section
+ * 6.8.13 "Segmentation params semantics" of the AV1 specification.
+ *
+ * @flags: see V4L2_AV1_SEGMENTATION_FLAG_{}.
+ * @last_active_seg_id: indicates the highest numbered segment id that has some
+ * enabled feature. This is used when decoding the segment id to only decode
+ * choices corresponding to used segments.
+ * @feature_enabled: bitmask defining which features are enabled in each
+ * segment. Use V4L2_AV1_SEGMENT_FEATURE_ENABLED to build a suitable mask.
+ * @feature_data: data attached to each feature. Data entry is only valid if the
+ * feature is enabled
+ */
+struct v4l2_av1_segmentation {
+ __u8 flags;
+ __u8 last_active_seg_id;
+ __u8 feature_enabled[V4L2_AV1_MAX_SEGMENTS];
+ __s16 feature_data[V4L2_AV1_MAX_SEGMENTS][V4L2_AV1_SEG_LVL_MAX];
+};
+
+#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_ENABLED 0x1
+#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_UPDATE 0x2
+#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_LF_PRESENT 0x4
+#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_LF_MULTI 0x8
+
+/**
+ * struct v4l2_av1_loop_filter - AV1 Loop filter params as defined in section
+ * 6.8.10 "Loop filter semantics" and 6.8.16 "Loop filter delta parameters
+ * semantics" of the AV1 specification.
+ *
+ * @flags: see V4L2_AV1_LOOP_FILTER_FLAG_{}
+ * @level: an array containing loop filter strength values. Different loop
+ * filter strength values from the array are used depending on the image plane
+ * being filtered, and the edge direction (vertical or horizontal) being
+ * filtered.
+ * @sharpness: indicates the sharpness level. The loop_filter_level and
+ * loop_filter_sharpness together determine when a block edge is filtered, and
+ * by how much the filtering can change the sample values. The loop filter
+ * process is described in section 7.14 of the AV1 specification.
+ * @ref_deltas: contains the adjustment needed for the filter level based on the
+ * chosen reference frame. If this syntax element is not present, it maintains
+ * its previous value.
+ * @mode_deltas: contains the adjustment needed for the filter level based on
+ * the chosen mode. If this syntax element is not present, it maintains its
+ * previous value.
+ * @delta_lf_res: specifies the left shift which should be applied to decoded
+ * loop filter delta values.
+ */
+struct v4l2_av1_loop_filter {
+ __u8 flags;
+ __u8 level[4];
+ __u8 sharpness;
+ __s8 ref_deltas[V4L2_AV1_TOTAL_REFS_PER_FRAME];
+ __s8 mode_deltas[2];
+ __u8 delta_lf_res;
+};
+
+#define V4L2_AV1_QUANTIZATION_FLAG_DIFF_UV_DELTA 0x1
+#define V4L2_AV1_QUANTIZATION_FLAG_USING_QMATRIX 0x2
+#define V4L2_AV1_QUANTIZATION_FLAG_DELTA_Q_PRESENT 0x4
+
+/**
+ * struct v4l2_av1_quantization - AV1 Quantization params as defined in section
+ * 6.8.11 "Quantization params semantics" of the AV1 specification.
+ *
+ * @flags: see V4L2_AV1_QUANTIZATION_FLAG_{}
+ * @base_q_idx: indicates the base frame qindex. This is used for Y AC
+ * coefficients and as the base value for the other quantizers.
+ * @delta_q_y_dc: indicates the Y DC quantizer relative to base_q_idx.
+ * @delta_q_u_dc: indicates the U DC quantizer relative to base_q_idx.
+ * @delta_q_u_ac: indicates the U AC quantizer relative to base_q_idx.
+ * @delta_q_v_dc: indicates the V DC quantizer relative to base_q_idx.
+ * @delta_q_v_ac: indicates the V AC quantizer relative to base_q_idx.
+ * @qm_y: specifies the level in the quantizer matrix that should be used for
+ * luma plane decoding.
+ * @qm_u: specifies the level in the quantizer matrix that should be used for
+ * chroma U plane decoding.
+ * @qm_v: specifies the level in the quantizer matrix that should be used for
+ * chroma V plane decoding.
+ * @delta_q_res: specifies the left shift which should be applied to decoded
+ * quantizer index delta values.
+ */
+struct v4l2_av1_quantization {
+ __u8 flags;
+ __u8 base_q_idx;
+ __s8 delta_q_y_dc;
+ __s8 delta_q_u_dc;
+ __s8 delta_q_u_ac;
+ __s8 delta_q_v_dc;
+ __s8 delta_q_v_ac;
+ __u8 qm_y;
+ __u8 qm_u;
+ __u8 qm_v;
+ __u8 delta_q_res;
+};
+
+#define V4L2_AV1_TILE_INFO_FLAG_UNIFORM_TILE_SPACING 0x1
+
+/**
+ * struct v4l2_av1_tile_info - AV1 Tile info as defined in section 6.8.14 "Tile
+ * info semantics" of the AV1 specification.
+ *
+ * @flags: see V4L2_AV1_TILE_INFO_FLAG_{}
+ * @context_update_tile_id: specifies which tile to use for the CDF update.
+ * @tile_rows: specifies the number of tiles down the frame.
+ * @tile_cols: specifies the number of tiles across the frame.
+ * @mi_col_starts: an array specifying the start column (in units of 4x4 luma
+ * samples) for each tile across the image.
+ * @mi_row_starts: an array specifying the start row (in units of 4x4 luma
+ * samples) for each tile down the image.
+ * @width_in_sbs_minus_1: specifies the width of a tile minus 1 in units of
+ * superblocks.
+ * @height_in_sbs_minus_1: specifies the height of a tile minus 1 in units of
+ * superblocks.
+ * @tile_size_bytes: specifies the number of bytes needed to code each tile
+ * size.
+ * @reserved: padding field. Should be zeroed by applications.
+ */
+struct v4l2_av1_tile_info {
+ __u8 flags;
+ __u8 context_update_tile_id;
+ __u8 tile_cols;
+ __u8 tile_rows;
+ __u32 mi_col_starts[V4L2_AV1_MAX_TILE_COLS + 1];
+ __u32 mi_row_starts[V4L2_AV1_MAX_TILE_ROWS + 1];
+ __u32 width_in_sbs_minus_1[V4L2_AV1_MAX_TILE_COLS];
+ __u32 height_in_sbs_minus_1[V4L2_AV1_MAX_TILE_ROWS];
+ __u8 tile_size_bytes;
+ __u8 reserved[3];
+};
+
+/**
+ * enum v4l2_av1_frame_type - AV1 Frame Type
+ *
+ * @V4L2_AV1_KEY_FRAME: Key frame
+ * @V4L2_AV1_INTER_FRAME: Inter frame
+ * @V4L2_AV1_INTRA_ONLY_FRAME: Intra-only frame
+ * @V4L2_AV1_SWITCH_FRAME: Switch frame
+ */
+enum v4l2_av1_frame_type {
+ V4L2_AV1_KEY_FRAME = 0,
+ V4L2_AV1_INTER_FRAME = 1,
+ V4L2_AV1_INTRA_ONLY_FRAME = 2,
+ V4L2_AV1_SWITCH_FRAME = 3
+};
+
+/**
+ * enum v4l2_av1_interpolation_filter - AV1 interpolation filter types
+ *
+ * @V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP: eight tap filter
+ * @V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH: eight tap smooth filter
+ * @V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP: eight tap sharp filter
+ * @V4L2_AV1_INTERPOLATION_FILTER_BILINEAR: bilinear filter
+ * @V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE: filter selection is signaled at
+ * the block level
+ *
+ * See section 6.8.9 "Interpolation filter semantics" of the AV1 specification
+ * for more details.
+ */
+enum v4l2_av1_interpolation_filter {
+ V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP = 0,
+ V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH = 1,
+ V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP = 2,
+ V4L2_AV1_INTERPOLATION_FILTER_BILINEAR = 3,
+ V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE = 4,
+};
+
+/**
+ * enum v4l2_av1_tx_mode - AV1 Tx mode as described in section 6.8.21 "TX mode
+ * semantics" of the AV1 specification.
+ * @V4L2_AV1_TX_MODE_ONLY_4X4: the inverse transform will use only 4x4
+ * transforms
+ * @V4L2_AV1_TX_MODE_LARGEST: the inverse transform will use the largest
+ * transform size that fits inside the block
+ * @V4L2_AV1_TX_MODE_SELECT: the choice of transform size is specified
+ * explicitly for each block.
+ */
+enum v4l2_av1_tx_mode {
+ V4L2_AV1_TX_MODE_ONLY_4X4 = 0,
+ V4L2_AV1_TX_MODE_LARGEST = 1,
+ V4L2_AV1_TX_MODE_SELECT = 2
+};
+
+#define V4L2_AV1_FRAME_FLAG_SHOW_FRAME 0x00000001
+#define V4L2_AV1_FRAME_FLAG_SHOWABLE_FRAME 0x00000002
+#define V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE 0x00000004
+#define V4L2_AV1_FRAME_FLAG_DISABLE_CDF_UPDATE 0x00000008
+#define V4L2_AV1_FRAME_FLAG_ALLOW_SCREEN_CONTENT_TOOLS 0x00000010
+#define V4L2_AV1_FRAME_FLAG_FORCE_INTEGER_MV 0x00000020
+#define V4L2_AV1_FRAME_FLAG_ALLOW_INTRABC 0x00000040
+#define V4L2_AV1_FRAME_FLAG_USE_SUPERRES 0x00000080
+#define V4L2_AV1_FRAME_FLAG_ALLOW_HIGH_PRECISION_MV 0x00000100
+#define V4L2_AV1_FRAME_FLAG_IS_MOTION_MODE_SWITCHABLE 0x00000200
+#define V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS 0x00000400
+#define V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF 0x00000800
+#define V4L2_AV1_FRAME_FLAG_ALLOW_WARPED_MOTION 0x00001000
+#define V4L2_AV1_FRAME_FLAG_REFERENCE_SELECT 0x00002000
+#define V4L2_AV1_FRAME_FLAG_REDUCED_TX_SET 0x00004000
+#define V4L2_AV1_FRAME_FLAG_SKIP_MODE_ALLOWED 0x00008000
+#define V4L2_AV1_FRAME_FLAG_SKIP_MODE_PRESENT 0x00010000
+#define V4L2_AV1_FRAME_FLAG_FRAME_SIZE_OVERRIDE 0x00020000
+#define V4L2_AV1_FRAME_FLAG_BUFFER_REMOVAL_TIME_PRESENT 0x00040000
+#define V4L2_AV1_FRAME_FLAG_FRAME_REFS_SHORT_SIGNALING 0x00080000
+
+#define V4L2_CID_STATELESS_AV1_FRAME (V4L2_CID_CODEC_STATELESS_BASE + 502)
+/**
+ * struct v4l2_ctrl_av1_frame - Represents an AV1 Frame Header OBU.
+ *
+ * @tile_info: tile info
+ * @quantization: quantization params
+ * @segmentation: segmentation params
+ * @superres_denom: the denominator for the upscaling ratio.
+ * @loop_filter: loop filter params
+ * @cdef: cdef params
+ * @skip_mode_frame: specifies the frames to use for compound prediction when
+ * skip_mode is equal to 1.
+ * @primary_ref_frame: specifies which reference frame contains the CDF values
+ * and other state that should be loaded at the start of the frame.
+ * @loop_restoration: loop restoration params
+ * @global_motion: global motion params
+ * @flags: see V4L2_AV1_FRAME_FLAG_{}
+ * @frame_type: specifies the AV1 frame type
+ * @order_hint: specifies OrderHintBits least significant bits of the expected
+ * output order for this frame.
+ * @upscaled_width: the upscaled width.
+ * @interpolation_filter: specifies the filter selection used for performing
+ * inter prediction.
+ * @tx_mode: specifies how the transform size is determined.
+ * @frame_width_minus_1: add 1 to get the frame's width.
+ * @frame_height_minus_1: add 1 to get the frame's height
+ * @render_width_minus_1: add 1 to get the render width of the frame in luma
+ * samples.
+ * @render_height_minus_1: add 1 to get the render height of the frame in luma
+ * samples.
+ * @current_frame_id: specifies the frame id number for the current frame. Frame
+ * id numbers are additional information that do not affect the decoding
+ * process, but provide decoders with a way of detecting missing reference
+ * frames so that appropriate action can be taken.
+ * @buffer_removal_time: specifies the frame removal time in units of DecCT clock
+ * ticks counted from the removal time of the last random access point for
+ * operating point opNum.
+ * @reserved: padding field. Should be zeroed by applications.
+ * @order_hints: specifies the expected output order hint for each reference
+ * frame. This field corresponds to the OrderHints variable from the
+ * specification (section 5.9.2 "Uncompressed header syntax"). As such, this is
+ * only used for non-intra frames and ignored otherwise. order_hints[0] is
+ * always ignored.
+ * @reference_frame_ts: the V4L2 timestamp of the reference frame slots.
+ * @ref_frame_idx: used to index into @reference_frame_ts when decoding
+ * inter-frames. The meaning of this array is the same as in the specification.
+ * The timestamp refers to the timestamp field in struct v4l2_buffer. Use
+ * v4l2_timeval_to_ns() to convert the struct timeval to a __u64.
+ * @refresh_frame_flags: contains a bitmask that specifies which reference frame
+ * slots will be updated with the current frame after it is decoded.
+ */
+struct v4l2_ctrl_av1_frame {
+ struct v4l2_av1_tile_info tile_info;
+ struct v4l2_av1_quantization quantization;
+ __u8 superres_denom;
+ struct v4l2_av1_segmentation segmentation;
+ struct v4l2_av1_loop_filter loop_filter;
+ struct v4l2_av1_cdef cdef;
+ __u8 skip_mode_frame[2];
+ __u8 primary_ref_frame;
+ struct v4l2_av1_loop_restoration loop_restoration;
+ struct v4l2_av1_global_motion global_motion;
+ __u32 flags;
+ enum v4l2_av1_frame_type frame_type;
+ __u32 order_hint;
+ __u32 upscaled_width;
+ enum v4l2_av1_interpolation_filter interpolation_filter;
+ enum v4l2_av1_tx_mode tx_mode;
+ __u32 frame_width_minus_1;
+ __u32 frame_height_minus_1;
+ __u16 render_width_minus_1;
+ __u16 render_height_minus_1;
+
+ __u32 current_frame_id;
+ __u32 buffer_removal_time[V4L2_AV1_MAX_OPERATING_POINTS];
+ __u8 reserved[4];
+ __u32 order_hints[V4L2_AV1_TOTAL_REFS_PER_FRAME];
+ __u64 reference_frame_ts[V4L2_AV1_TOTAL_REFS_PER_FRAME];
+ __s8 ref_frame_idx[V4L2_AV1_REFS_PER_FRAME];
+ __u8 refresh_frame_flags;
+};
+
+#define V4L2_AV1_FILM_GRAIN_FLAG_APPLY_GRAIN 0x1
+#define V4L2_AV1_FILM_GRAIN_FLAG_UPDATE_GRAIN 0x2
+#define V4L2_AV1_FILM_GRAIN_FLAG_CHROMA_SCALING_FROM_LUMA 0x4
+#define V4L2_AV1_FILM_GRAIN_FLAG_OVERLAP 0x8
+#define V4L2_AV1_FILM_GRAIN_FLAG_CLIP_TO_RESTRICTED_RANGE 0x10
+
+#define V4L2_CID_STATELESS_AV1_FILM_GRAIN (V4L2_CID_CODEC_STATELESS_BASE + 505)
+/**
+ * struct v4l2_ctrl_av1_film_grain - AV1 Film Grain parameters.
+ *
+ * Film grain parameters as specified by section 6.8.20 of the AV1 Specification.
+ *
+ * @flags: see V4L2_AV1_FILM_GRAIN_{}.
+ * @cr_mult: represents a multiplier for the cr component used in derivation of
+ * the input index to the cr component scaling function.
+ * @grain_seed: specifies the starting value for the pseudo-random numbers used
+ * during film grain synthesis.
+ * @film_grain_params_ref_idx: indicates which reference frame contains the
+ * film grain parameters to be used for this frame.
+ * @num_y_points: specifies the number of points for the piece-wise linear
+ * scaling function of the luma component.
+ * @point_y_value: represents the x (luma value) coordinate for the i-th point
+ * of the piecewise linear scaling function for luma component. The values are
+ * signaled on the scale of 0..255. In case of 10 bit video, these values
+ * correspond to luma values divided by 4. In case of 12 bit video, these values
+ * correspond to luma values divided by 16.
+ * @point_y_scaling: represents the scaling (output) value for the i-th point
+ * of the piecewise linear scaling function for luma component.
+ * @num_cb_points: specifies the number of points for the piece-wise linear
+ * scaling function of the cb component.
+ * @point_cb_value: represents the x coordinate for the i-th point of the
+ * piece-wise linear scaling function for cb component. The values are signaled
+ * on the scale of 0..255.
+ * @point_cb_scaling: represents the scaling (output) value for the i-th point
+ * of the piecewise linear scaling function for cb component.
+ * @num_cr_points: specifies represents the number of points for the piece-wise
+ * linear scaling function of the cr component.
+ * @point_cr_value: represents the x coordinate for the i-th point of the
+ * piece-wise linear scaling function for cr component. The values are signaled
+ * on the scale of 0..255.
+ * @point_cr_scaling: represents the scaling (output) value for the i-th point
+ * of the piecewise linear scaling function for cr component.
+ * @grain_scaling_minus_8: represents the shift – 8 applied to the values of the
+ * chroma component. The grain_scaling_minus_8 can take values of 0..3 and
+ * determines the range and quantization step of the standard deviation of film
+ * grain.
+ * @ar_coeff_lag: specifies the number of auto-regressive coefficients for luma
+ * and chroma.
+ * @ar_coeffs_y_plus_128: specifies auto-regressive coefficients used for the Y
+ * plane.
+ * @ar_coeffs_cb_plus_128: specifies auto-regressive coefficients used for the U
+ * plane.
+ * @ar_coeffs_cr_plus_128: specifies auto-regressive coefficients used for the V
+ * plane.
+ * @ar_coeff_shift_minus_6: specifies the range of the auto-regressive
+ * coefficients. Values of 0, 1, 2, and 3 correspond to the ranges for
+ * auto-regressive coefficients of [-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25,
+ * 0.25) respectively.
+ * @grain_scale_shift: specifies how much the Gaussian random numbers should be
+ * scaled down during the grain synthesis process.
+ * @cb_mult: represents a multiplier for the cb component used in derivation of
+ * the input index to the cb component scaling function.
+ * @cb_luma_mult: represents a multiplier for the average luma component used in
+ * derivation of the input index to the cb component scaling function.
+ * @cr_luma_mult: represents a multiplier for the average luma component used in
+ * derivation of the input index to the cr component scaling function.
+ * @cb_offset: represents an offset used in derivation of the input index to the
+ * cb component scaling function.
+ * @cr_offset: represents an offset used in derivation of the input index to the
+ * cr component scaling function.
+ * @reserved: padding field. Should be zeroed by applications.
+ */
+struct v4l2_ctrl_av1_film_grain {
+ __u8 flags;
+ __u8 cr_mult;
+ __u16 grain_seed;
+ __u8 film_grain_params_ref_idx;
+ __u8 num_y_points;
+ __u8 point_y_value[V4L2_AV1_MAX_NUM_Y_POINTS];
+ __u8 point_y_scaling[V4L2_AV1_MAX_NUM_Y_POINTS];
+ __u8 num_cb_points;
+ __u8 point_cb_value[V4L2_AV1_MAX_NUM_CB_POINTS];
+ __u8 point_cb_scaling[V4L2_AV1_MAX_NUM_CB_POINTS];
+ __u8 num_cr_points;
+ __u8 point_cr_value[V4L2_AV1_MAX_NUM_CR_POINTS];
+ __u8 point_cr_scaling[V4L2_AV1_MAX_NUM_CR_POINTS];
+ __u8 grain_scaling_minus_8;
+ __u8 ar_coeff_lag;
+ __u8 ar_coeffs_y_plus_128[V4L2_AV1_AR_COEFFS_SIZE];
+ __u8 ar_coeffs_cb_plus_128[V4L2_AV1_AR_COEFFS_SIZE];
+ __u8 ar_coeffs_cr_plus_128[V4L2_AV1_AR_COEFFS_SIZE];
+ __u8 ar_coeff_shift_minus_6;
+ __u8 grain_scale_shift;
+ __u8 cb_mult;
+ __u8 cb_luma_mult;
+ __u8 cr_luma_mult;
+ __u16 cb_offset;
+ __u16 cr_offset;
+ __u8 reserved[4];
+};
+
/* MPEG-compression definitions kept for backwards compatibility */
#define V4L2_CTRL_CLASS_MPEG V4L2_CTRL_CLASS_CODEC
#define V4L2_CID_MPEG_CLASS V4L2_CID_CODEC_CLASS
diff --git a/include/linux/v4l2-mediabus.h b/include/linux/v4l2-mediabus.h
index 846dadfb..2c318de1 100644
--- a/include/linux/v4l2-mediabus.h
+++ b/include/linux/v4l2-mediabus.h
@@ -3,10 +3,6 @@
* Media Bus API header
*
* Copyright (C) 2009, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#ifndef __LINUX_V4L2_MEDIABUS_H
diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h
index 658106f5..b383c2fe 100644
--- a/include/linux/v4l2-subdev.h
+++ b/include/linux/v4l2-subdev.h
@@ -6,24 +6,12 @@
*
* Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
* Sakari Ailus <sakari.ailus@iki.fi>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __LINUX_V4L2_SUBDEV_H
#define __LINUX_V4L2_SUBDEV_H
+#include <linux/const.h>
#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/v4l2-common.h>
@@ -44,13 +32,15 @@ enum v4l2_subdev_format_whence {
* @which: format type (from enum v4l2_subdev_format_whence)
* @pad: pad number, as reported by the media API
* @format: media bus format (format code and frame size)
+ * @stream: stream number, defined in subdev routing
* @reserved: drivers and applications must zero this array
*/
struct v4l2_subdev_format {
__u32 which;
__u32 pad;
struct v4l2_mbus_framefmt format;
- __u32 reserved[8];
+ __u32 stream;
+ __u32 reserved[7];
};
/**
@@ -58,13 +48,15 @@ struct v4l2_subdev_format {
* @which: format type (from enum v4l2_subdev_format_whence)
* @pad: pad number, as reported by the media API
* @rect: pad crop rectangle boundaries
+ * @stream: stream number, defined in subdev routing
* @reserved: drivers and applications must zero this array
*/
struct v4l2_subdev_crop {
__u32 which;
__u32 pad;
struct v4l2_rect rect;
- __u32 reserved[8];
+ __u32 stream;
+ __u32 reserved[7];
};
#define V4L2_SUBDEV_MBUS_CODE_CSC_COLORSPACE 0x00000001
@@ -80,6 +72,7 @@ struct v4l2_subdev_crop {
* @code: format code (MEDIA_BUS_FMT_ definitions)
* @which: format type (from enum v4l2_subdev_format_whence)
* @flags: flags set by the driver, (V4L2_SUBDEV_MBUS_CODE_*)
+ * @stream: stream number, defined in subdev routing
* @reserved: drivers and applications must zero this array
*/
struct v4l2_subdev_mbus_code_enum {
@@ -88,7 +81,8 @@ struct v4l2_subdev_mbus_code_enum {
__u32 code;
__u32 which;
__u32 flags;
- __u32 reserved[7];
+ __u32 stream;
+ __u32 reserved[6];
};
/**
@@ -101,6 +95,7 @@ struct v4l2_subdev_mbus_code_enum {
* @min_height: minimum frame height, in pixels
* @max_height: maximum frame height, in pixels
* @which: format type (from enum v4l2_subdev_format_whence)
+ * @stream: stream number, defined in subdev routing
* @reserved: drivers and applications must zero this array
*/
struct v4l2_subdev_frame_size_enum {
@@ -112,19 +107,22 @@ struct v4l2_subdev_frame_size_enum {
__u32 min_height;
__u32 max_height;
__u32 which;
- __u32 reserved[8];
+ __u32 stream;
+ __u32 reserved[7];
};
/**
* struct v4l2_subdev_frame_interval - Pad-level frame rate
* @pad: pad number, as reported by the media API
* @interval: frame interval in seconds
+ * @stream: stream number, defined in subdev routing
* @reserved: drivers and applications must zero this array
*/
struct v4l2_subdev_frame_interval {
__u32 pad;
struct v4l2_fract interval;
- __u32 reserved[9];
+ __u32 stream;
+ __u32 reserved[8];
};
/**
@@ -136,6 +134,7 @@ struct v4l2_subdev_frame_interval {
* @height: frame height in pixels
* @interval: frame interval in seconds
* @which: format type (from enum v4l2_subdev_format_whence)
+ * @stream: stream number, defined in subdev routing
* @reserved: drivers and applications must zero this array
*/
struct v4l2_subdev_frame_interval_enum {
@@ -146,7 +145,8 @@ struct v4l2_subdev_frame_interval_enum {
__u32 height;
struct v4l2_fract interval;
__u32 which;
- __u32 reserved[8];
+ __u32 stream;
+ __u32 reserved[7];
};
/**
@@ -158,6 +158,7 @@ struct v4l2_subdev_frame_interval_enum {
* defined in v4l2-common.h; V4L2_SEL_TGT_* .
* @flags: constraint flags, defined in v4l2-common.h; V4L2_SEL_FLAG_*.
* @r: coordinates of the selection window
+ * @stream: stream number, defined in subdev routing
* @reserved: for future use, set to zero for now
*
* Hardware may use multiple helper windows to process a video stream.
@@ -170,7 +171,8 @@ struct v4l2_subdev_selection {
__u32 target;
__u32 flags;
struct v4l2_rect r;
- __u32 reserved[8];
+ __u32 stream;
+ __u32 reserved[7];
};
/**
@@ -188,6 +190,67 @@ struct v4l2_subdev_capability {
/* The v4l2 sub-device video device node is registered in read-only mode. */
#define V4L2_SUBDEV_CAP_RO_SUBDEV 0x00000001
+/* The v4l2 sub-device supports routing and multiplexed streams. */
+#define V4L2_SUBDEV_CAP_STREAMS 0x00000002
+
+/*
+ * Is the route active? An active route will start when streaming is enabled
+ * on a video node.
+ */
+#define V4L2_SUBDEV_ROUTE_FL_ACTIVE (1U << 0)
+
+/**
+ * struct v4l2_subdev_route - A route inside a subdev
+ *
+ * @sink_pad: the sink pad index
+ * @sink_stream: the sink stream identifier
+ * @source_pad: the source pad index
+ * @source_stream: the source stream identifier
+ * @flags: route flags V4L2_SUBDEV_ROUTE_FL_*
+ * @reserved: drivers and applications must zero this array
+ */
+struct v4l2_subdev_route {
+ __u32 sink_pad;
+ __u32 sink_stream;
+ __u32 source_pad;
+ __u32 source_stream;
+ __u32 flags;
+ __u32 reserved[5];
+};
+
+/**
+ * struct v4l2_subdev_routing - Subdev routing information
+ *
+ * @which: configuration type (from enum v4l2_subdev_format_whence)
+ * @num_routes: the total number of routes in the routes array
+ * @routes: pointer to the routes array
+ * @reserved: drivers and applications must zero this array
+ */
+struct v4l2_subdev_routing {
+ __u32 which;
+ __u32 num_routes;
+ __u64 routes;
+ __u32 reserved[6];
+};
+
+/*
+ * The client is aware of streams. Setting this flag enables the use of 'stream'
+ * fields (referring to the stream number) with various ioctls. If this is not
+ * set (which is the default), the 'stream' fields will be forced to 0 by the
+ * kernel.
+ */
+ #define V4L2_SUBDEV_CLIENT_CAP_STREAMS (1ULL << 0)
+
+/**
+ * struct v4l2_subdev_client_capability - Capabilities of the client accessing
+ * the subdev
+ *
+ * @capabilities: A bitmask of V4L2_SUBDEV_CLIENT_CAP_* flags.
+ */
+struct v4l2_subdev_client_capability {
+ __u64 capabilities;
+};
+
/* Backwards compatibility define --- to be removed */
#define v4l2_subdev_edid v4l2_edid
@@ -203,6 +266,11 @@ struct v4l2_subdev_capability {
#define VIDIOC_SUBDEV_S_CROP _IOWR('V', 60, struct v4l2_subdev_crop)
#define VIDIOC_SUBDEV_G_SELECTION _IOWR('V', 61, struct v4l2_subdev_selection)
#define VIDIOC_SUBDEV_S_SELECTION _IOWR('V', 62, struct v4l2_subdev_selection)
+#define VIDIOC_SUBDEV_G_ROUTING _IOWR('V', 38, struct v4l2_subdev_routing)
+#define VIDIOC_SUBDEV_S_ROUTING _IOWR('V', 39, struct v4l2_subdev_routing)
+#define VIDIOC_SUBDEV_G_CLIENT_CAP _IOR('V', 101, struct v4l2_subdev_client_capability)
+#define VIDIOC_SUBDEV_S_CLIENT_CAP _IOWR('V', 102, struct v4l2_subdev_client_capability)
+
/* The following ioctls are identical to the ioctls in videodev2.h */
#define VIDIOC_SUBDEV_G_STD _IOR('V', 23, v4l2_std_id)
#define VIDIOC_SUBDEV_S_STD _IOW('V', 24, v4l2_std_id)
diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
index dcc0b01d..7e556911 100644
--- a/include/linux/videodev2.h
+++ b/include/linux/videodev2.h
@@ -243,6 +243,7 @@ enum v4l2_colorspace {
/* DCI-P3 colorspace, used by cinema projectors */
V4L2_COLORSPACE_DCI_P3 = 12,
+
};
/*
@@ -474,7 +475,6 @@ struct v4l2_capability {
#define V4L2_CAP_META_CAPTURE 0x00800000 /* Is a metadata capture device */
#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */
-#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */
#define V4L2_CAP_META_OUTPUT 0x08000000 /* Is a metadata output device */
@@ -549,6 +549,13 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_RGBX32 v4l2_fourcc('X', 'B', '2', '4') /* 32 RGBX-8-8-8-8 */
#define V4L2_PIX_FMT_ARGB32 v4l2_fourcc('B', 'A', '2', '4') /* 32 ARGB-8-8-8-8 */
#define V4L2_PIX_FMT_XRGB32 v4l2_fourcc('B', 'X', '2', '4') /* 32 XRGB-8-8-8-8 */
+#define V4L2_PIX_FMT_RGBX1010102 v4l2_fourcc('R', 'X', '3', '0') /* 32 RGBX-10-10-10-2 */
+#define V4L2_PIX_FMT_RGBA1010102 v4l2_fourcc('R', 'A', '3', '0') /* 32 RGBA-10-10-10-2 */
+#define V4L2_PIX_FMT_ARGB2101010 v4l2_fourcc('A', 'R', '3', '0') /* 32 ARGB-2-10-10-10 */
+
+/* RGB formats (6 or 8 bytes per pixel) */
+#define V4L2_PIX_FMT_BGR48_12 v4l2_fourcc('B', '3', '1', '2') /* 48 BGR 12-bit per component */
+#define V4L2_PIX_FMT_ABGR64_12 v4l2_fourcc('B', '4', '1', '2') /* 64 BGRA 12-bit per component */
/* Grey formats */
#define V4L2_PIX_FMT_GREY v4l2_fourcc('G', 'R', 'E', 'Y') /* 8 Greyscale */
@@ -556,6 +563,7 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_Y6 v4l2_fourcc('Y', '0', '6', ' ') /* 6 Greyscale */
#define V4L2_PIX_FMT_Y10 v4l2_fourcc('Y', '1', '0', ' ') /* 10 Greyscale */
#define V4L2_PIX_FMT_Y12 v4l2_fourcc('Y', '1', '2', ' ') /* 12 Greyscale */
+#define V4L2_PIX_FMT_Y012 v4l2_fourcc('Y', '0', '1', '2') /* 12 Greyscale */
#define V4L2_PIX_FMT_Y14 v4l2_fourcc('Y', '1', '4', ' ') /* 14 Greyscale */
#define V4L2_PIX_FMT_Y16 v4l2_fourcc('Y', '1', '6', ' ') /* 16 Greyscale */
#define V4L2_PIX_FMT_Y16_BE v4l2_fourcc_be('Y', '1', '6', ' ') /* 16 Greyscale BE */
@@ -563,6 +571,7 @@ struct v4l2_pix_format {
/* Grey bit-packed formats */
#define V4L2_PIX_FMT_Y10BPACK v4l2_fourcc('Y', '1', '0', 'B') /* 10 Greyscale bit-packed */
#define V4L2_PIX_FMT_Y10P v4l2_fourcc('Y', '1', '0', 'P') /* 10 Greyscale, MIPI RAW10 packed */
+#define V4L2_PIX_FMT_IPU3_Y10 v4l2_fourcc('i', 'p', '3', 'y') /* IPU3 packed 10-bit greyscale */
/* Palette formats */
#define V4L2_PIX_FMT_PAL8 v4l2_fourcc('P', 'A', 'L', '8') /* 8 8-bit palette */
@@ -586,7 +595,18 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_XYUV32 v4l2_fourcc('X', 'Y', 'U', 'V') /* 32 XYUV-8-8-8-8 */
#define V4L2_PIX_FMT_VUYA32 v4l2_fourcc('V', 'U', 'Y', 'A') /* 32 VUYA-8-8-8-8 */
#define V4L2_PIX_FMT_VUYX32 v4l2_fourcc('V', 'U', 'Y', 'X') /* 32 VUYX-8-8-8-8 */
+#define V4L2_PIX_FMT_YUVA32 v4l2_fourcc('Y', 'U', 'V', 'A') /* 32 YUVA-8-8-8-8 */
+#define V4L2_PIX_FMT_YUVX32 v4l2_fourcc('Y', 'U', 'V', 'X') /* 32 YUVX-8-8-8-8 */
#define V4L2_PIX_FMT_M420 v4l2_fourcc('M', '4', '2', '0') /* 12 YUV 4:2:0 2 lines y, 1 line uv interleaved */
+#define V4L2_PIX_FMT_YUV48_12 v4l2_fourcc('Y', '3', '1', '2') /* 48 YUV 4:4:4 12-bit per component */
+
+/*
+ * YCbCr packed format. For each Y2xx format, xx bits of valid data occupy the MSBs
+ * of the 16 bit components, and 16-xx bits of zero padding occupy the LSBs.
+ */
+#define V4L2_PIX_FMT_Y210 v4l2_fourcc('Y', '2', '1', '0') /* 32 YUYV 4:2:2 */
+#define V4L2_PIX_FMT_Y212 v4l2_fourcc('Y', '2', '1', '2') /* 32 YUYV 4:2:2 */
+#define V4L2_PIX_FMT_Y216 v4l2_fourcc('Y', '2', '1', '6') /* 32 YUYV 4:2:2 */
/* two planes -- one Y, one Cr + Cb interleaved */
#define V4L2_PIX_FMT_NV12 v4l2_fourcc('N', 'V', '1', '2') /* 12 Y/CbCr 4:2:0 */
@@ -595,12 +615,15 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_NV61 v4l2_fourcc('N', 'V', '6', '1') /* 16 Y/CrCb 4:2:2 */
#define V4L2_PIX_FMT_NV24 v4l2_fourcc('N', 'V', '2', '4') /* 24 Y/CbCr 4:4:4 */
#define V4L2_PIX_FMT_NV42 v4l2_fourcc('N', 'V', '4', '2') /* 24 Y/CrCb 4:4:4 */
+#define V4L2_PIX_FMT_P010 v4l2_fourcc('P', '0', '1', '0') /* 24 Y/CbCr 4:2:0 10-bit per component */
+#define V4L2_PIX_FMT_P012 v4l2_fourcc('P', '0', '1', '2') /* 24 Y/CbCr 4:2:0 12-bit per component */
/* two non contiguous planes - one Y, one Cr + Cb interleaved */
#define V4L2_PIX_FMT_NV12M v4l2_fourcc('N', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 */
#define V4L2_PIX_FMT_NV21M v4l2_fourcc('N', 'M', '2', '1') /* 21 Y/CrCb 4:2:0 */
#define V4L2_PIX_FMT_NV16M v4l2_fourcc('N', 'M', '1', '6') /* 16 Y/CbCr 4:2:2 */
#define V4L2_PIX_FMT_NV61M v4l2_fourcc('N', 'M', '6', '1') /* 16 Y/CrCb 4:2:2 */
+#define V4L2_PIX_FMT_P012M v4l2_fourcc('P', 'M', '1', '2') /* 24 Y/CbCr 4:2:0 12-bit per component */
/* three planes - Y Cb, Cr */
#define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y', 'U', 'V', '9') /* 9 YUV 4:1:0 */
@@ -622,10 +645,16 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_NV12_4L4 v4l2_fourcc('V', 'T', '1', '2') /* 12 Y/CbCr 4:2:0 4x4 tiles */
#define V4L2_PIX_FMT_NV12_16L16 v4l2_fourcc('H', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 16x16 tiles */
#define V4L2_PIX_FMT_NV12_32L32 v4l2_fourcc('S', 'T', '1', '2') /* 12 Y/CbCr 4:2:0 32x32 tiles */
+#define V4L2_PIX_FMT_NV15_4L4 v4l2_fourcc('V', 'T', '1', '5') /* 15 Y/CbCr 4:2:0 10-bit 4x4 tiles */
+#define V4L2_PIX_FMT_P010_4L4 v4l2_fourcc('T', '0', '1', '0') /* 12 Y/CbCr 4:2:0 10-bit 4x4 macroblocks */
+#define V4L2_PIX_FMT_NV12_8L128 v4l2_fourcc('A', 'T', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */
+#define V4L2_PIX_FMT_NV12_10BE_8L128 v4l2_fourcc_be('A', 'X', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */
/* Tiled YUV formats, non contiguous planes */
#define V4L2_PIX_FMT_NV12MT v4l2_fourcc('T', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 64x32 tiles */
#define V4L2_PIX_FMT_NV12MT_16X16 v4l2_fourcc('V', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 16x16 tiles */
+#define V4L2_PIX_FMT_NV12M_8L128 v4l2_fourcc('N', 'A', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */
+#define V4L2_PIX_FMT_NV12M_10BE_8L128 v4l2_fourcc_be('N', 'T', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */
/* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
#define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B', 'A', '8', '1') /* 8 BGBG.. GRGR.. */
@@ -697,10 +726,16 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_VP8 v4l2_fourcc('V', 'P', '8', '0') /* VP8 */
#define V4L2_PIX_FMT_VP8_FRAME v4l2_fourcc('V', 'P', '8', 'F') /* VP8 parsed frame */
#define V4L2_PIX_FMT_VP9 v4l2_fourcc('V', 'P', '9', '0') /* VP9 */
+#define V4L2_PIX_FMT_VP9_FRAME v4l2_fourcc('V', 'P', '9', 'F') /* VP9 parsed frame */
#define V4L2_PIX_FMT_HEVC v4l2_fourcc('H', 'E', 'V', 'C') /* HEVC aka H.265 */
#define V4L2_PIX_FMT_FWHT v4l2_fourcc('F', 'W', 'H', 'T') /* Fast Walsh Hadamard Transform (vicodec) */
#define V4L2_PIX_FMT_FWHT_STATELESS v4l2_fourcc('S', 'F', 'W', 'H') /* Stateless FWHT (vicodec) */
#define V4L2_PIX_FMT_H264_SLICE v4l2_fourcc('S', '2', '6', '4') /* H264 parsed slices */
+#define V4L2_PIX_FMT_HEVC_SLICE v4l2_fourcc('S', '2', '6', '5') /* HEVC parsed slices */
+#define V4L2_PIX_FMT_AV1_FRAME v4l2_fourcc('A', 'V', '1', 'F') /* AV1 parsed frame */
+#define V4L2_PIX_FMT_SPK v4l2_fourcc('S', 'P', 'K', '0') /* Sorenson Spark */
+#define V4L2_PIX_FMT_RV30 v4l2_fourcc('R', 'V', '3', '0') /* RealVideo 8 */
+#define V4L2_PIX_FMT_RV40 v4l2_fourcc('R', 'V', '4', '0') /* RealVideo 9 & 10 */
/* Vendor-specific formats */
#define V4L2_PIX_FMT_CPIA1 v4l2_fourcc('C', 'P', 'I', 'A') /* cpia1 YUV */
@@ -734,11 +769,17 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_Z16 v4l2_fourcc('Z', '1', '6', ' ') /* Depth data 16-bit */
#define V4L2_PIX_FMT_MT21C v4l2_fourcc('M', 'T', '2', '1') /* Mediatek compressed block mode */
#define V4L2_PIX_FMT_MM21 v4l2_fourcc('M', 'M', '2', '1') /* Mediatek 8-bit block mode, two non-contiguous planes */
+#define V4L2_PIX_FMT_MT2110T v4l2_fourcc('M', 'T', '2', 'T') /* Mediatek 10-bit block tile mode */
+#define V4L2_PIX_FMT_MT2110R v4l2_fourcc('M', 'T', '2', 'R') /* Mediatek 10-bit block raster mode */
#define V4L2_PIX_FMT_INZI v4l2_fourcc('I', 'N', 'Z', 'I') /* Intel Planar Greyscale 10-bit and Depth 16-bit */
#define V4L2_PIX_FMT_CNF4 v4l2_fourcc('C', 'N', 'F', '4') /* Intel 4-bit packed depth confidence information */
#define V4L2_PIX_FMT_HI240 v4l2_fourcc('H', 'I', '2', '4') /* BTTV 8-bit dithered RGB */
+#define V4L2_PIX_FMT_QC08C v4l2_fourcc('Q', '0', '8', 'C') /* Qualcomm 8-bit compressed */
+#define V4L2_PIX_FMT_QC10C v4l2_fourcc('Q', '1', '0', 'C') /* Qualcomm 10-bit compressed */
+#define V4L2_PIX_FMT_AJPG v4l2_fourcc('A', 'J', 'P', 'G') /* Aspeed JPEG */
+#define V4L2_PIX_FMT_HEXTILE v4l2_fourcc('H', 'X', 'T', 'L') /* Hextile compressed */
-/* 10bit raw bayer packed, 32 bytes for every 25 pixels, last LSB 6 bits unused */
+/* 10bit raw packed, 32 bytes for every 25 pixels, last LSB 6 bits unused */
#define V4L2_PIX_FMT_IPU3_SBGGR10 v4l2_fourcc('i', 'p', '3', 'b') /* IPU3 packed 10-bit BGGR bayer */
#define V4L2_PIX_FMT_IPU3_SGBRG10 v4l2_fourcc('i', 'p', '3', 'g') /* IPU3 packed 10-bit GBRG bayer */
#define V4L2_PIX_FMT_IPU3_SGRBG10 v4l2_fourcc('i', 'p', '3', 'G') /* IPU3 packed 10-bit GRBG bayer */
@@ -1542,7 +1583,8 @@ struct v4l2_bt_timings {
((bt)->width + V4L2_DV_BT_BLANKING_WIDTH(bt))
#define V4L2_DV_BT_BLANKING_HEIGHT(bt) \
((bt)->vfrontporch + (bt)->vsync + (bt)->vbackporch + \
- (bt)->il_vfrontporch + (bt)->il_vsync + (bt)->il_vbackporch)
+ ((bt)->interlaced ? \
+ ((bt)->il_vfrontporch + (bt)->il_vsync + (bt)->il_vbackporch) : 0))
#define V4L2_DV_BT_FRAME_HEIGHT(bt) \
((bt)->height + V4L2_DV_BT_BLANKING_HEIGHT(bt))
@@ -1633,7 +1675,7 @@ struct v4l2_input {
__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
- __u32 tuner; /* enum v4l2_tuner_type */
+ __u32 tuner; /* Tuner index */
v4l2_std_id std;
__u32 status;
__u32 capabilities;
@@ -1720,6 +1762,8 @@ struct v4l2_ext_control {
__u8 *p_u8;
__u16 *p_u16;
__u32 *p_u32;
+ __s32 *p_s32;
+ __s64 *p_s64;
struct v4l2_area *p_area;
struct v4l2_ctrl_h264_sps *p_h264_sps;
struct v4l2_ctrl_h264_pps *p_h264_pps;
@@ -1732,6 +1776,17 @@ struct v4l2_ext_control {
struct v4l2_ctrl_mpeg2_sequence *p_mpeg2_sequence;
struct v4l2_ctrl_mpeg2_picture *p_mpeg2_picture;
struct v4l2_ctrl_mpeg2_quantisation *p_mpeg2_quantisation;
+ struct v4l2_ctrl_vp9_compressed_hdr *p_vp9_compressed_hdr_probs;
+ struct v4l2_ctrl_vp9_frame *p_vp9_frame;
+ struct v4l2_ctrl_hevc_sps *p_hevc_sps;
+ struct v4l2_ctrl_hevc_pps *p_hevc_pps;
+ struct v4l2_ctrl_hevc_slice_params *p_hevc_slice_params;
+ struct v4l2_ctrl_hevc_scaling_matrix *p_hevc_scaling_matrix;
+ struct v4l2_ctrl_hevc_decode_params *p_hevc_decode_params;
+ struct v4l2_ctrl_av1_sequence *p_av1_sequence;
+ struct v4l2_ctrl_av1_tile_group_entry *p_av1_tile_group_entry;
+ struct v4l2_ctrl_av1_frame *p_av1_frame;
+ struct v4l2_ctrl_av1_film_grain *p_av1_film_grain;
void *ptr;
};
} __attribute__ ((packed));
@@ -1792,6 +1847,20 @@ enum v4l2_ctrl_type {
V4L2_CTRL_TYPE_MPEG2_QUANTISATION = 0x0250,
V4L2_CTRL_TYPE_MPEG2_SEQUENCE = 0x0251,
V4L2_CTRL_TYPE_MPEG2_PICTURE = 0x0252,
+
+ V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR = 0x0260,
+ V4L2_CTRL_TYPE_VP9_FRAME = 0x0261,
+
+ V4L2_CTRL_TYPE_HEVC_SPS = 0x0270,
+ V4L2_CTRL_TYPE_HEVC_PPS = 0x0271,
+ V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS = 0x0272,
+ V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX = 0x0273,
+ V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS = 0x0274,
+
+ V4L2_CTRL_TYPE_AV1_SEQUENCE = 0x280,
+ V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY = 0x281,
+ V4L2_CTRL_TYPE_AV1_FRAME = 0x282,
+ V4L2_CTRL_TYPE_AV1_FILM_GRAIN = 0x283,
};
/* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */
@@ -1847,6 +1916,7 @@ struct v4l2_querymenu {
#define V4L2_CTRL_FLAG_HAS_PAYLOAD 0x0100
#define V4L2_CTRL_FLAG_EXECUTE_ON_WRITE 0x0200
#define V4L2_CTRL_FLAG_MODIFY_LAYOUT 0x0400
+#define V4L2_CTRL_FLAG_DYNAMIC_ARRAY 0x0800
/* Query flags, to be ORed with the control ID */
#define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000
@@ -2354,6 +2424,7 @@ struct v4l2_event_vsync {
#define V4L2_EVENT_CTRL_CH_VALUE (1 << 0)
#define V4L2_EVENT_CTRL_CH_FLAGS (1 << 1)
#define V4L2_EVENT_CTRL_CH_RANGE (1 << 2)
+#define V4L2_EVENT_CTRL_CH_DIMENSIONS (1 << 3)
struct v4l2_event_ctrl {
__u32 changes;
@@ -2596,5 +2667,10 @@ struct v4l2_create_buffers {
/* Deprecated definitions kept for backwards compatibility */
#define V4L2_PIX_FMT_HM12 V4L2_PIX_FMT_NV12_16L16
#define V4L2_PIX_FMT_SUNXI_TILED_NV12 V4L2_PIX_FMT_NV12_32L32
+/*
+ * This capability was never implemented, anyone using this cap should drop it
+ * from their code.
+ */
+#define V4L2_CAP_ASYNCIO 0x02000000
#endif /* __LINUX_VIDEODEV2_H */
diff --git a/include/meson.build b/include/meson.build
index 27ce2f41..19b93a7b 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
+include_build_dir = meson.current_build_dir()
+
subdir('android')
subdir('libcamera')
diff --git a/meson.build b/meson.build
index 6d7d3ca6..740ead1b 100644
--- a/meson.build
+++ b/meson.build
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: CC0-1.0
project('libcamera', 'c', 'cpp',
- meson_version : '>= 0.56',
- version : '0.0.0',
+ meson_version : '>= 0.60',
+ version : '0.2.0',
default_options : [
'werror=true',
'warning_level=2',
@@ -11,22 +11,62 @@ project('libcamera', 'c', 'cpp',
license : 'LGPL 2.1+')
# Generate version information. The libcamera_git_version variable contains the
-# full version with git patch count and SHA1 (e.g. 1.2.3+211-c94a24f4), while
-# the libcamera_version variable contains the major.minor.patch (e.g. 1.2.3)
-# only. If the source tree isn't under git control, or if it matches the last
-# git version tag, the build metadata (e.g. +211-c94a24f4) is omitted from
-# libcamera_git_version.
+# full version with build metadata (patch count and SHA1, e.g.
+# 1.2.3+211-c94a24f4), while the libcamera_version variable contains the
+# major.minor.patch (e.g. 1.2.3) only.
+#
+# If the source tree matches the last git version tag, the build metadata
+# (e.g. +211-c94a24f4) is omitted from libcamera_git_version.
libcamera_git_version = run_command('utils/gen-version.sh',
meson.project_build_root(),
meson.project_source_root(),
- check: false).stdout().strip()
+ check : false).stdout().strip()
+
+# If the source tree isn't under git control, set libcamera_git_version to the
+# meson project version.
if libcamera_git_version == ''
libcamera_git_version = meson.project_version()
endif
libcamera_version = libcamera_git_version.split('+')[0]
+project_version = meson.project_version().split('+')[0]
+
+# A shallow clone, or a clone without a reachable tag equivalent to the
+# meson.project_version() could leave the project in a mis-described state.
+# Produce a warning in this event, and fix to a best effort.
+if libcamera_version != project_version
+ warning('The sources and meson.build disagree about the version: '
+ + libcamera_version + ' != ' + project_version)
+
+ summary({'libcamera git version' : libcamera_git_version,
+ 'Source version match' : false,
+ },
+ bool_yn : true, section : 'Versions')
+
+ # Re-run gen-version.sh to replace the git version (major.minor.patch) with
+ # the meson project version. The build metadata provided by git are kept.
+ libcamera_git_version = run_command('utils/gen-version.sh',
+ meson.project_build_root(),
+ meson.project_source_root(),
+ project_version,
+ check : false).stdout().strip()
+ libcamera_version = project_version
+
+ # Append a marker to show we have modified this version string.
+ libcamera_git_version += '-nvm'
+endif
+
+# The major and minor libcamera version components are used as the soname.
+# No ABI/API compatibility is guaranteed between releases (x.y).
+#
+# When we declare a stable ABI/API we will provide a 1.0 release and the
+# soversion at that point will be the 'major' release value (x).
+semver = libcamera_version.split('.')
+libcamera_soversion = semver[0] + '.' + semver[1]
-# This script gererates the .tarball-version file on a 'meson dist' command.
+summary({ 'Sources': libcamera_git_version, }, section : 'Versions')
+
+# This script generates the .tarball-version file on a 'meson dist' command.
meson.add_dist_script('utils/run-dist.sh')
# Configure the build environment.
@@ -38,13 +78,17 @@ if cc.has_header_symbol('unistd.h', 'issetugid')
config_h.set('HAVE_ISSETUGID', 1)
endif
+if cc.has_header_symbol('locale.h', 'locale_t', prefix : '#define _GNU_SOURCE')
+ config_h.set('HAVE_LOCALE_T', 1)
+endif
+
if cc.has_header_symbol('stdlib.h', 'secure_getenv', prefix : '#define _GNU_SOURCE')
config_h.set('HAVE_SECURE_GETENV', 1)
endif
common_arguments = [
'-Wshadow',
- '-include', 'config.h',
+ '-include', meson.current_build_dir() / 'config.h',
]
c_arguments = []
@@ -55,17 +99,23 @@ if cc.get_id() == 'clang'
error('clang version is too old, libcamera requires 9.0 or newer')
endif
- # Turn _FORTIFY_SOURCE by default on optimised builds (as it requires -O1
- # or higher). This is needed on clang only as gcc enables it by default.
+ # Turn _FORTIFY_SOURCE by default on. This is needed on clang only as gcc
+ # enables it by default. FORTIFY will not work properly with `-O0`, and may
+ # result in macro redefinition errors if the user already has a setting for
+ # `-D_FORTIFY_SOURCE`. Do not enable FORTIFY in either of those cases.
if get_option('optimization') != '0'
- common_arguments += [
- '-D_FORTIFY_SOURCE=2',
- ]
+ fortify = cc.get_define('_FORTIFY_SOURCE')
+ if fortify == ''
+ message('Adding _FORTIFY_SOURCE')
+ common_arguments += [
+ '-D_FORTIFY_SOURCE=2',
+ ]
+ endif
endif
# Use libc++ by default if available instead of libstdc++ when compiling
# with clang.
- if cc.find_library('libc++', required: false).found()
+ if cc.find_library('c++', required : false).found()
cpp_arguments += [
'-stdlib=libc++',
]
@@ -90,6 +140,21 @@ if cc.get_id() == 'gcc'
]
endif
+ # gcc 13 implements the C++23 version of automatic move from local
+ # variables in return statements (see
+ # https://en.cppreference.com/w/cpp/language/return). As a result, some
+ # previously required explicit std::move() in return statements generate
+ # warnings. Those moves can't be removed as older compiler versions could
+ # use copy constructors instead of move constructors. The easiest fix is to
+ # disable the warning. With -Wpessimizing-move enabled, the compiler will
+ # still warn of pessimizing moves, only the redundant but not pessimizing
+ # moves will be ignored.
+ if cc.version().version_compare('>=13')
+ cpp_arguments += [
+ '-Wno-redundant-move',
+ ]
+ endif
+
# gcc 7.1 introduced processor-specific ABI breakages related to parameter
# passing on ARM platforms. This generates a large number of messages
# during compilation. Silence them.
@@ -122,17 +187,46 @@ libcamera_includes = include_directories('include')
py_modules = []
# Libraries used by multiple components
-liblttng = cc.find_library('lttng-ust', required : get_option('tracing'))
+liblttng = dependency('lttng-ust', required : get_option('tracing'))
# Pipeline handlers
#
-# Tests require the vimc pipeline handler, include it automatically when tests
-# are enabled.
pipelines = get_option('pipelines')
-if get_option('test') and 'vimc' not in pipelines
- message('Enabling vimc pipeline handler to support tests')
- pipelines += ['vimc']
+arch_arm = ['arm', 'aarch64']
+arch_x86 = ['x86', 'x86_64']
+pipelines_support = {
+ 'imx8-isi': arch_arm,
+ 'ipu3': arch_x86,
+ 'mali-c55': arch_arm,
+ 'rkisp1': arch_arm,
+ 'rpi/vc4': arch_arm,
+ 'simple': arch_arm,
+ 'uvcvideo': ['any'],
+ 'vimc': ['test'],
+}
+
+if pipelines.contains('all')
+ pipelines = pipelines_support.keys()
+elif pipelines.contains('auto')
+ host_cpu = host_machine.cpu_family()
+ pipelines = []
+ foreach pipeline, archs : pipelines_support
+ if host_cpu in archs or 'any' in archs
+ pipelines += pipeline
+ endif
+ endforeach
+endif
+
+# Tests require the vimc pipeline handler, include it automatically when tests
+# are enabled.
+if get_option('test')
+ foreach pipeline, archs : pipelines_support
+ if 'test' in archs and pipeline not in pipelines
+ message('Enabling ' + pipeline + ' pipeline handler for tests')
+ pipelines += pipeline
+ endif
+ endforeach
endif
# Utilities are parsed first to provide support for other components.
@@ -149,7 +243,7 @@ subdir('test')
if not meson.is_cross_build()
kernel_version_req = '>= 5.0.0'
- kernel_version = run_command('uname', '-r', check: true).stdout().strip()
+ kernel_version = run_command('uname', '-r', check : true).stdout().strip()
if not kernel_version.version_compare(kernel_version_req)
warning('The current running kernel version @0@ is too old to run libcamera.'
.format(kernel_version))
@@ -162,18 +256,21 @@ endif
# running libcamera from the build directory to locate resources in the source
# directory (such as IPA configuration files).
run_command('ln', '-fsT', meson.project_source_root(), meson.project_build_root() / 'source',
- check: true)
+ check : true)
configure_file(output : 'config.h', configuration : config_h)
# Check for python installation and modules.
py_mod = import('python')
-py_mod.find_installation('python3', modules: py_modules)
+py_mod.find_installation('python3', modules : py_modules)
## Summarise Configurations
summary({
'Enabled pipelines': pipelines,
- 'Enabled IPA modules': ipa_modules,
+ 'Enabled IPA modules': enabled_ipa_names,
+ 'Controls files': controls_files,
+ 'Properties files': properties_files,
+ 'Hotplug support': libudev.found(),
'Tracing support': tracing_enabled,
'Android support': android_enabled,
'GStreamer support': gst_enabled,
diff --git a/meson_options.txt b/meson_options.txt
index 7a9aecfc..c61eb555 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -27,7 +27,7 @@ option('gstreamer',
option('ipas',
type : 'array',
- choices : ['ipu3', 'raspberrypi', 'rkisp1', 'vimc'],
+ choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'],
description : 'Select which IPA modules to build')
option('lc-compliance',
@@ -37,8 +37,25 @@ option('lc-compliance',
option('pipelines',
type : 'array',
- choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],
- description : 'Select which pipeline handlers to include')
+ value : ['auto'],
+ choices : [
+ 'all',
+ 'auto',
+ 'imx8-isi',
+ 'ipu3',
+ 'mali-c55',
+ 'rkisp1',
+ 'rpi/vc4',
+ 'simple',
+ 'uvcvideo',
+ 'vimc'
+ ],
+ description : 'Select which pipeline handlers to build. If this is set to "auto", all the pipelines applicable to the target architecture will be built. If this is set to "all", all the pipelines will be built. If both are selected then "all" will take precedence.')
+
+option('pycamera',
+ type : 'feature',
+ value : 'auto',
+ description : 'Enable libcamera Python bindings (experimental)')
option('qcam',
type : 'feature',
@@ -47,19 +64,20 @@ option('qcam',
option('test',
type : 'boolean',
- description: 'Compile and include the tests')
+ value : false,
+ description : 'Compile and include the tests')
option('tracing',
type : 'feature',
value : 'auto',
- description: 'Enable tracing (based on lttng)')
+ description : 'Enable tracing (based on lttng)')
+
+option('udev',
+ type : 'feature',
+ value : 'auto',
+ description : 'Enable udev support for hotplug')
option('v4l2',
type : 'boolean',
value : false,
description : 'Compile the V4L2 compatibility layer')
-
-option('pycamera',
- type : 'feature',
- value : 'disabled',
- description : 'Enable libcamera Python bindings (experimental)')
diff --git a/src/android/camera_capabilities.cpp b/src/android/camera_capabilities.cpp
index 6f197eb8..1bfeaea4 100644
--- a/src/android/camera_capabilities.cpp
+++ b/src/android/camera_capabilities.cpp
@@ -31,13 +31,20 @@ namespace {
/*
* \var camera3Resolutions
- * \brief The list of image resolutions defined as mandatory to be supported by
- * the Android Camera3 specification
+ * \brief The list of image resolutions commonly supported by Android
+ *
+ * The following are defined as mandatory to be supported by the Android
+ * Camera3 specification: (320x240), (640x480), (1280x720), (1920x1080).
+ *
+ * The following 4:3 resolutions are defined as optional, but commonly
+ * supported by Android devices: (1280x960), (1600x1200).
*/
const std::vector<Size> camera3Resolutions = {
{ 320, 240 },
{ 640, 480 },
{ 1280, 720 },
+ { 1280, 960 },
+ { 1600, 1200 },
{ 1920, 1080 }
};
@@ -367,14 +374,20 @@ void CameraCapabilities::computeHwLevel(
camera_metadata_enum_android_info_supported_hardware_level
hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_FULL;
- if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR))
+ if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+ LOG(HAL, Info) << noFull << "missing manual sensor";
hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+ }
- if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING))
+ if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING)) {
+ LOG(HAL, Info) << noFull << "missing manual post processing";
hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+ }
- if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE))
+ if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
+ LOG(HAL, Info) << noFull << "missing burst capture";
hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+ }
found = staticMetadata_->getEntry(ANDROID_SYNC_MAX_LATENCY, &entry);
if (!found || *entry.data.i32 != 0) {
@@ -475,7 +488,7 @@ int CameraCapabilities::initializeStreamConfigurations()
* \todo Get this from the camera properties once defined
*/
std::unique_ptr<CameraConfiguration> cameraConfig =
- camera_->generateConfiguration({ StillCapture });
+ camera_->generateConfiguration({ StreamRole::StillCapture });
if (!cameraConfig) {
LOG(HAL, Error) << "Failed to get maximum resolution";
return -EINVAL;
@@ -492,8 +505,8 @@ int CameraCapabilities::initializeStreamConfigurations()
/*
* Build the list of supported image resolutions.
*
- * The resolutions listed in camera3Resolution are mandatory to be
- * supported, up to the camera maximum resolution.
+ * The resolutions listed in camera3Resolution are supported, up to the
+ * camera maximum resolution.
*
* Augment the list by adding resolutions calculated from the camera
* maximum one.
@@ -687,6 +700,14 @@ int CameraCapabilities::initializeStreamConfigurations()
minFrameDuration = minFrameDurationCap;
}
+ /*
+ * Calculate FPS as CTS does and adjust the minimum
+ * frame duration accordingly: see
+ * Camera2SurfaceViewTestCase.java:getSuitableFpsRangeForDuration()
+ */
+ minFrameDuration =
+ 1e9 / static_cast<unsigned int>(floor(1e9 / minFrameDuration + 0.05f));
+
streamConfigurations_.push_back({
res, androidFormat, minFrameDuration, maxFrameDuration,
});
@@ -1042,18 +1063,18 @@ int CameraCapabilities::initializeStaticMetadata()
/* Sensor static metadata. */
std::array<int32_t, 2> pixelArraySize;
{
- const Size &size = properties.get(properties::PixelArraySize);
+ const Size &size = properties.get(properties::PixelArraySize).value_or(Size{});
pixelArraySize[0] = size.width;
pixelArraySize[1] = size.height;
staticMetadata_->addEntry(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
pixelArraySize);
}
- if (properties.contains(properties::UnitCellSize)) {
- const Size &cellSize = properties.get<Size>(properties::UnitCellSize);
+ const auto &cellSize = properties.get<Size>(properties::UnitCellSize);
+ if (cellSize) {
std::array<float, 2> physicalSize{
- cellSize.width * pixelArraySize[0] / 1e6f,
- cellSize.height * pixelArraySize[1] / 1e6f
+ cellSize->width * pixelArraySize[0] / 1e6f,
+ cellSize->height * pixelArraySize[1] / 1e6f
};
staticMetadata_->addEntry(ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
physicalSize);
@@ -1061,7 +1082,7 @@ int CameraCapabilities::initializeStaticMetadata()
{
const Span<const Rectangle> &rects =
- properties.get(properties::PixelArrayActiveAreas);
+ properties.get(properties::PixelArrayActiveAreas).value_or(Span<const Rectangle>{});
std::vector<int32_t> data{
static_cast<int32_t>(rects[0].x),
static_cast<int32_t>(rects[0].y),
@@ -1079,11 +1100,10 @@ int CameraCapabilities::initializeStaticMetadata()
sensitivityRange);
/* Report the color filter arrangement if the camera reports it. */
- if (properties.contains(properties::draft::ColorFilterArrangement)) {
- uint8_t filterArr = properties.get(properties::draft::ColorFilterArrangement);
+ const auto &filterArr = properties.get(properties::draft::ColorFilterArrangement);
+ if (filterArr)
staticMetadata_->addEntry(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
- filterArr);
- }
+ *filterArr);
const auto &exposureInfo = controlsInfo.find(&controls::ExposureTime);
if (exposureInfo != controlsInfo.end()) {
@@ -1287,12 +1307,10 @@ int CameraCapabilities::initializeStaticMetadata()
* recording profile. Inspecting the Intel IPU3 HAL
* implementation confirms this but no reference has been found
* in the metadata documentation.
- *
- * Calculate FPS as CTS does: see
- * Camera2SurfaceViewTestCase.java:getSuitableFpsRangeForDuration()
*/
- unsigned int fps = static_cast<unsigned int>
- (floor(1e9 / entry.minFrameDurationNsec + 0.05f));
+ unsigned int fps =
+ static_cast<unsigned int>(floor(1e9 / entry.minFrameDurationNsec));
+
if (entry.androidFormat != HAL_PIXEL_FORMAT_BLOB && fps < 30)
continue;
diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index 8c039fb9..1b6f3f3a 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -30,6 +30,7 @@
#include "camera_hal_config.h"
#include "camera_ops.h"
#include "camera_request.h"
+#include "hal_framebuffer.h"
using namespace libcamera;
@@ -305,9 +306,9 @@ int CameraDevice::initialize(const CameraConfigData *cameraConfigData)
*/
const ControlList &properties = camera_->properties();
- if (properties.contains(properties::Location)) {
- int32_t location = properties.get(properties::Location);
- switch (location) {
+ const auto &location = properties.get(properties::Location);
+ if (location) {
+ switch (*location) {
case properties::CameraLocationFront:
facing_ = CAMERA_FACING_FRONT;
break;
@@ -355,9 +356,9 @@ int CameraDevice::initialize(const CameraConfigData *cameraConfigData)
* value for clockwise direction as required by the Android orientation
* metadata.
*/
- if (properties.contains(properties::Rotation)) {
- int rotation = properties.get(properties::Rotation);
- orientation_ = (360 - rotation) % 360;
+ const auto &rotation = properties.get(properties::Rotation);
+ if (rotation) {
+ orientation_ = (360 - *rotation) % 360;
if (cameraConfigData && cameraConfigData->rotation != -1 &&
orientation_ != cameraConfigData->rotation) {
LOG(HAL, Warning)
@@ -432,8 +433,6 @@ void CameraDevice::flush()
void CameraDevice::stop()
{
MutexLocker stateLock(stateMutex_);
- if (state_ == State::Stopped)
- return;
camera_->stop();
@@ -771,7 +770,7 @@ int CameraDevice::configureStreams(camera3_stream_configuration_t *stream_list)
return 0;
}
-std::unique_ptr<FrameBuffer>
+std::unique_ptr<HALFrameBuffer>
CameraDevice::createFrameBuffer(const buffer_handle_t camera3buffer,
PixelFormat pixelFormat, const Size &size)
{
@@ -794,7 +793,7 @@ CameraDevice::createFrameBuffer(const buffer_handle_t camera3buffer,
planes[i].length = buf.size(i);
}
- return std::make_unique<FrameBuffer>(planes);
+ return std::make_unique<HALFrameBuffer>(planes, camera3buffer);
}
int CameraDevice::processControls(Camera3RequestDescriptor *descriptor)
@@ -951,8 +950,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
*/
if (camera3Request->settings)
lastSettings_ = camera3Request->settings;
- else
- descriptor->settings_ = lastSettings_;
+
+ descriptor->settings_ = lastSettings_;
LOG(HAL, Debug) << "Queueing request " << descriptor->request_->cookie()
<< " with " << descriptor->buffers_.size() << " streams";
@@ -1076,7 +1075,7 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
descriptor->request_->addBuffer(sourceStream->stream(),
frameBuffer, nullptr);
- requestedStreams.erase(sourceStream);
+ requestedStreams.insert(sourceStream);
}
/*
@@ -1107,6 +1106,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
}
if (state_ == State::Stopped) {
+ lastSettings_ = {};
+
ret = camera_->start();
if (ret) {
LOG(HAL, Error) << "Failed to start camera";
@@ -1181,7 +1182,8 @@ void CameraDevice::requestComplete(Request *request)
* as soon as possible, earlier than request completion time.
*/
uint64_t sensorTimestamp = static_cast<uint64_t>(request->metadata()
- .get(controls::SensorTimestamp));
+ .get(controls::SensorTimestamp)
+ .value_or(0));
notifyShutter(descriptor->frameNumber_, sensorTimestamp);
LOG(HAL, Debug) << "Request " << request->cookie() << " completed with "
@@ -1560,29 +1562,27 @@ CameraDevice::getResultMetadata(const Camera3RequestDescriptor &descriptor) cons
rolling_shutter_skew);
/* Add metadata tags reported by libcamera. */
- const int64_t timestamp = metadata.get(controls::SensorTimestamp);
+ const int64_t timestamp = metadata.get(controls::SensorTimestamp).value_or(0);
resultMetadata->addEntry(ANDROID_SENSOR_TIMESTAMP, timestamp);
- if (metadata.contains(controls::draft::PipelineDepth)) {
- uint8_t pipeline_depth =
- metadata.get<int32_t>(controls::draft::PipelineDepth);
+ const auto &pipelineDepth = metadata.get(controls::draft::PipelineDepth);
+ if (pipelineDepth)
resultMetadata->addEntry(ANDROID_REQUEST_PIPELINE_DEPTH,
- pipeline_depth);
- }
+ *pipelineDepth);
- if (metadata.contains(controls::ExposureTime)) {
- int64_t exposure = metadata.get(controls::ExposureTime) * 1000ULL;
- resultMetadata->addEntry(ANDROID_SENSOR_EXPOSURE_TIME, exposure);
- }
+ const auto &exposureTime = metadata.get(controls::ExposureTime);
+ if (exposureTime)
+ resultMetadata->addEntry(ANDROID_SENSOR_EXPOSURE_TIME,
+ *exposureTime * 1000ULL);
- if (metadata.contains(controls::FrameDuration)) {
- int64_t duration = metadata.get(controls::FrameDuration) * 1000;
+ const auto &frameDuration = metadata.get(controls::FrameDuration);
+ if (frameDuration)
resultMetadata->addEntry(ANDROID_SENSOR_FRAME_DURATION,
- duration);
- }
+ *frameDuration * 1000);
- if (metadata.contains(controls::ScalerCrop)) {
- Rectangle crop = metadata.get(controls::ScalerCrop);
+ const auto &scalerCrop = metadata.get(controls::ScalerCrop);
+ if (scalerCrop) {
+ const Rectangle &crop = *scalerCrop;
int32_t cropRect[] = {
crop.x, crop.y, static_cast<int32_t>(crop.width),
static_cast<int32_t>(crop.height),
@@ -1590,12 +1590,10 @@ CameraDevice::getResultMetadata(const Camera3RequestDescriptor &descriptor) cons
resultMetadata->addEntry(ANDROID_SCALER_CROP_REGION, cropRect);
}
- if (metadata.contains(controls::draft::TestPatternMode)) {
- const int32_t testPatternMode =
- metadata.get(controls::draft::TestPatternMode);
+ const auto &testPatternMode = metadata.get(controls::draft::TestPatternMode);
+ if (testPatternMode)
resultMetadata->addEntry(ANDROID_SENSOR_TEST_PATTERN_MODE,
- testPatternMode);
- }
+ *testPatternMode);
/*
* Return the result metadata pack even is not valid: get() will return
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index 64050416..43ee0159 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -29,6 +29,7 @@
#include "camera_capabilities.h"
#include "camera_metadata.h"
#include "camera_stream.h"
+#include "hal_framebuffer.h"
#include "jpeg/encoder.h"
class Camera3RequestDescriptor;
@@ -83,7 +84,7 @@ private:
void stop() LIBCAMERA_TSA_EXCLUDES(stateMutex_);
- std::unique_ptr<libcamera::FrameBuffer>
+ std::unique_ptr<HALFrameBuffer>
createFrameBuffer(const buffer_handle_t camera3buffer,
libcamera::PixelFormat pixelFormat,
const libcamera::Size &size);
diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
index bacfe4b9..0e7cde63 100644
--- a/src/android/camera_hal_config.cpp
+++ b/src/android/camera_hal_config.cpp
@@ -6,7 +6,6 @@
*/
#include "camera_hal_config.h"
-#include <filesystem>
#include <stdlib.h>
#include <string>
@@ -160,15 +159,15 @@ CameraHalConfig::CameraHalConfig()
*/
int CameraHalConfig::parseConfigurationFile()
{
- std::filesystem::path filePath = LIBCAMERA_SYSCONF_DIR;
- filePath /= "camera_hal.yaml";
- if (!std::filesystem::is_regular_file(filePath)) {
+ std::string filePath = LIBCAMERA_SYSCONF_DIR "/camera_hal.yaml";
+
+ File file(filePath);
+ if (!file.exists()) {
LOG(HALConfig, Debug)
<< "Configuration file: \"" << filePath << "\" not found";
return -ENOENT;
}
- File file(filePath);
if (!file.open(File::OpenModeFlag::ReadOnly)) {
int ret = file.error();
LOG(HALConfig, Error) << "Failed to open configuration file "
diff --git a/src/android/camera_hal_manager.cpp b/src/android/camera_hal_manager.cpp
index 5f7bfe26..a86e23d4 100644
--- a/src/android/camera_hal_manager.cpp
+++ b/src/android/camera_hal_manager.cpp
@@ -140,7 +140,8 @@ void CameraHalManager::cameraAdded(std::shared_ptr<Camera> cam)
*/
if (!isCameraExternal && !halConfig_.exists()) {
LOG(HAL, Error)
- << "HAL configuration file is mandatory for internal cameras";
+ << "HAL configuration file is mandatory for internal cameras."
+ << " Camera " << cam->id() << " failed to load";
return;
}
@@ -228,11 +229,7 @@ void CameraHalManager::cameraRemoved(std::shared_ptr<Camera> cam)
int32_t CameraHalManager::cameraLocation(const Camera *cam)
{
- const ControlList &properties = cam->properties();
- if (!properties.contains(properties::Location))
- return -1;
-
- return properties.get(properties::Location);
+ return cam->properties().get(properties::Location).value_or(-1);
}
CameraDevice *CameraHalManager::cameraDeviceFromHalId(unsigned int id)
diff --git a/src/android/camera_request.h b/src/android/camera_request.h
index 37b6ae32..20aba79d 100644
--- a/src/android/camera_request.h
+++ b/src/android/camera_request.h
@@ -21,6 +21,7 @@
#include <hardware/camera3.h>
#include "camera_metadata.h"
+#include "hal_framebuffer.h"
class CameraBuffer;
class CameraStream;
@@ -44,7 +45,7 @@ public:
CameraStream *stream;
buffer_handle_t *camera3Buffer;
- std::unique_ptr<libcamera::FrameBuffer> frameBuffer;
+ std::unique_ptr<HALFrameBuffer> frameBuffer;
libcamera::UniqueFD fence;
Status status = Status::Success;
libcamera::FrameBuffer *internalBuffer = nullptr;
diff --git a/src/android/cros/camera3_hal.cpp b/src/android/cros/camera3_hal.cpp
index fb863b5f..71acb441 100644
--- a/src/android/cros/camera3_hal.cpp
+++ b/src/android/cros/camera3_hal.cpp
@@ -8,9 +8,11 @@
#include <cros-camera/cros_camera_hal.h>
#include "../camera_hal_manager.h"
+#include "../cros_mojo_token.h"
-static void set_up([[maybe_unused]] cros::CameraMojoChannelManagerToken *token)
+static void set_up(cros::CameraMojoChannelManagerToken *token)
{
+ gCrosMojoToken = token;
}
static void tear_down()
diff --git a/src/android/cros_mojo_token.h b/src/android/cros_mojo_token.h
new file mode 100644
index 00000000..043c752a
--- /dev/null
+++ b/src/android/cros_mojo_token.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * cros_mojo_token.h - cros-specific mojo token
+ */
+
+#pragma once
+
+#include <cros-camera/cros_camera_hal.h>
+
+inline cros::CameraMojoChannelManagerToken *gCrosMojoToken = nullptr;
diff --git a/src/android/data/nautilus/camera_hal.yaml b/src/android/data/nautilus/camera_hal.yaml
index faddd29e..2105fcca 100644
--- a/src/android/data/nautilus/camera_hal.yaml
+++ b/src/android/data/nautilus/camera_hal.yaml
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
cameras:
"\\_SB_.PCI0.I2C2.CAM0":
location: back
diff --git a/src/android/data/soraka/camera_hal.yaml b/src/android/data/soraka/camera_hal.yaml
index 2e996403..d886af06 100644
--- a/src/android/data/soraka/camera_hal.yaml
+++ b/src/android/data/soraka/camera_hal.yaml
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
cameras:
"\\_SB_.PCI0.I2C4.CAM1":
location: front
diff --git a/src/android/frame_buffer_allocator.h b/src/android/frame_buffer_allocator.h
index 5d2eeda1..e5c94922 100644
--- a/src/android/frame_buffer_allocator.h
+++ b/src/android/frame_buffer_allocator.h
@@ -13,9 +13,10 @@
#include <libcamera/base/class.h>
#include <libcamera/camera.h>
-#include <libcamera/framebuffer.h>
#include <libcamera/geometry.h>
+#include "hal_framebuffer.h"
+
class CameraDevice;
class PlatformFrameBufferAllocator : libcamera::Extensible
@@ -31,7 +32,7 @@ public:
* Note: The returned FrameBuffer needs to be destroyed before
* PlatformFrameBufferAllocator is destroyed.
*/
- std::unique_ptr<libcamera::FrameBuffer> allocate(
+ std::unique_ptr<HALFrameBuffer> allocate(
int halPixelFormat, const libcamera::Size &size, uint32_t usage);
};
@@ -44,7 +45,7 @@ PlatformFrameBufferAllocator::PlatformFrameBufferAllocator( \
PlatformFrameBufferAllocator::~PlatformFrameBufferAllocator() \
{ \
} \
-std::unique_ptr<libcamera::FrameBuffer> \
+std::unique_ptr<HALFrameBuffer> \
PlatformFrameBufferAllocator::allocate(int halPixelFormat, \
const libcamera::Size &size, \
uint32_t usage) \
diff --git a/src/android/hal_framebuffer.cpp b/src/android/hal_framebuffer.cpp
new file mode 100644
index 00000000..3f3d1ed1
--- /dev/null
+++ b/src/android/hal_framebuffer.cpp
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * hal_framebuffer.cpp - HAL Frame Buffer Handling
+ */
+
+#include "hal_framebuffer.h"
+
+#include <hardware/camera3.h>
+
+HALFrameBuffer::HALFrameBuffer(std::unique_ptr<Private> d,
+ buffer_handle_t handle)
+ : FrameBuffer(std::move(d)), handle_(handle)
+{
+}
+
+HALFrameBuffer::HALFrameBuffer(const std::vector<Plane> &planes,
+ buffer_handle_t handle)
+ : FrameBuffer(planes), handle_(handle)
+{
+}
diff --git a/src/android/hal_framebuffer.h b/src/android/hal_framebuffer.h
new file mode 100644
index 00000000..dc96a7e1
--- /dev/null
+++ b/src/android/hal_framebuffer.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * hal_framebuffer.h - HAL Frame Buffer Handling
+ */
+
+#pragma once
+
+#include "libcamera/internal/framebuffer.h"
+
+#include <hardware/camera3.h>
+
+class HALFrameBuffer final : public libcamera::FrameBuffer
+{
+public:
+ HALFrameBuffer(std::unique_ptr<Private> d,
+ buffer_handle_t handle);
+ HALFrameBuffer(const std::vector<Plane> &planes,
+ buffer_handle_t handle);
+
+ buffer_handle_t handle() const { return handle_; }
+
+private:
+ buffer_handle_t handle_;
+};
diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
index b974d367..31f26895 100644
--- a/src/android/jpeg/encoder.h
+++ b/src/android/jpeg/encoder.h
@@ -12,14 +12,15 @@
#include <libcamera/framebuffer.h>
#include <libcamera/stream.h>
+#include "../camera_request.h"
+
class Encoder
{
public:
virtual ~Encoder() = default;
virtual int configure(const libcamera::StreamConfiguration &cfg) = 0;
- virtual int encode(const libcamera::FrameBuffer &source,
- libcamera::Span<uint8_t> destination,
+ virtual int encode(Camera3RequestDescriptor::StreamBuffer *buffer,
libcamera::Span<const uint8_t> exifData,
unsigned int quality) = 0;
};
diff --git a/src/android/jpeg/encoder_jea.cpp b/src/android/jpeg/encoder_jea.cpp
new file mode 100644
index 00000000..7880a6bd
--- /dev/null
+++ b/src/android/jpeg/encoder_jea.cpp
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * encoder_jea.cpp - JPEG encoding using CrOS JEA
+ */
+
+#include "encoder_jea.h"
+
+#include "libcamera/internal/mapped_framebuffer.h"
+
+#include <cros-camera/camera_mojo_channel_manager_token.h>
+
+#include "../cros_mojo_token.h"
+#include "../hal_framebuffer.h"
+
+EncoderJea::EncoderJea() = default;
+
+EncoderJea::~EncoderJea() = default;
+
+int EncoderJea::configure(const libcamera::StreamConfiguration &cfg)
+{
+ size_ = cfg.size;
+
+ if (jpegCompressor_)
+ return 0;
+
+ if (gCrosMojoToken == nullptr)
+ return -ENOTSUP;
+
+ jpegCompressor_ = cros::JpegCompressor::GetInstance(gCrosMojoToken);
+
+ return 0;
+}
+
+int EncoderJea::encode(Camera3RequestDescriptor::StreamBuffer *buffer,
+ libcamera::Span<const uint8_t> exifData,
+ unsigned int quality)
+{
+ if (!jpegCompressor_)
+ return -ENOTSUP;
+
+ uint32_t outDataSize = 0;
+ const HALFrameBuffer *fb =
+ dynamic_cast<const HALFrameBuffer *>(buffer->srcBuffer);
+
+ if (!jpegCompressor_->CompressImageFromHandle(fb->handle(),
+ *buffer->camera3Buffer,
+ size_.width, size_.height,
+ quality, exifData.data(),
+ exifData.size(),
+ &outDataSize))
+ return -EBUSY;
+
+ return outDataSize;
+}
diff --git a/src/android/jpeg/encoder_jea.h b/src/android/jpeg/encoder_jea.h
new file mode 100644
index 00000000..ffe9df27
--- /dev/null
+++ b/src/android/jpeg/encoder_jea.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * encoder_jea.h - JPEG encoding using CrOS JEA
+ */
+
+#pragma once
+
+#include <libcamera/geometry.h>
+
+#include <cros-camera/jpeg_compressor.h>
+
+#include "encoder.h"
+
+class EncoderJea : public Encoder
+{
+public:
+ EncoderJea();
+ ~EncoderJea();
+
+ int configure(const libcamera::StreamConfiguration &cfg) override;
+ int encode(Camera3RequestDescriptor::StreamBuffer *buffer,
+ libcamera::Span<const uint8_t> exifData,
+ unsigned int quality) override;
+
+private:
+ libcamera::Size size_;
+
+ std::unique_ptr<cros::JpegCompressor> jpegCompressor_;
+};
diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
index fd62bd9c..f4e8dfad 100644
--- a/src/android/jpeg/encoder_libjpeg.cpp
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -24,6 +24,8 @@
#include "libcamera/internal/formats.h"
#include "libcamera/internal/mapped_framebuffer.h"
+#include "../camera_buffer.h"
+
using namespace libcamera;
LOG_DECLARE_CATEGORY(JPEG)
@@ -178,17 +180,20 @@ void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
}
}
-int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t> dest,
- Span<const uint8_t> exifData, unsigned int quality)
+int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer *buffer,
+ libcamera::Span<const uint8_t> exifData,
+ unsigned int quality)
{
- MappedFrameBuffer frame(&source, MappedFrameBuffer::MapFlag::Read);
+ MappedFrameBuffer frame(buffer->srcBuffer,
+ MappedFrameBuffer::MapFlag::Read);
if (!frame.isValid()) {
LOG(JPEG, Error) << "Failed to map FrameBuffer : "
<< strerror(frame.error());
return frame.error();
}
- return encode(frame.planes(), dest, exifData, quality);
+ return encode(frame.planes(), buffer->dstBuffer->plane(0),
+ exifData, quality);
}
int EncoderLibJpeg::encode(const std::vector<Span<uint8_t>> &src,
diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h
index 1b3ac067..146a6a72 100644
--- a/src/android/jpeg/encoder_libjpeg.h
+++ b/src/android/jpeg/encoder_libjpeg.h
@@ -22,8 +22,7 @@ public:
~EncoderLibJpeg();
int configure(const libcamera::StreamConfiguration &cfg) override;
- int encode(const libcamera::FrameBuffer &source,
- libcamera::Span<uint8_t> destination,
+ int encode(Camera3RequestDescriptor::StreamBuffer *buffer,
libcamera::Span<const uint8_t> exifData,
unsigned int quality) override;
int encode(const std::vector<libcamera::Span<uint8_t>> &planes,
diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp
index 3220b458..6b1d0f1f 100644
--- a/src/android/jpeg/exif.cpp
+++ b/src/android/jpeg/exif.cpp
@@ -430,16 +430,13 @@ void Exif::setOrientation(int orientation)
setShort(EXIF_IFD_0, EXIF_TAG_ORIENTATION, value);
}
-/*
- * The thumbnail data should remain valid until the Exif object is destroyed.
- * Failing to do so, might result in no thumbnail data being set even after a
- * call to Exif::setThumbnail().
- */
-void Exif::setThumbnail(Span<const unsigned char> thumbnail,
+void Exif::setThumbnail(std::vector<unsigned char> &&thumbnail,
Compression compression)
{
- data_->data = const_cast<unsigned char *>(thumbnail.data());
- data_->size = thumbnail.size();
+ thumbnailData_ = std::move(thumbnail);
+
+ data_->data = thumbnailData_.data();
+ data_->size = thumbnailData_.size();
setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression);
}
diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h
index 2ff8fb78..e68716f3 100644
--- a/src/android/jpeg/exif.h
+++ b/src/android/jpeg/exif.h
@@ -10,6 +10,7 @@
#include <chrono>
#include <string>
#include <time.h>
+#include <vector>
#include <libexif/exif-data.h>
@@ -60,7 +61,7 @@ public:
void setOrientation(int orientation);
void setSize(const libcamera::Size &size);
- void setThumbnail(libcamera::Span<const unsigned char> thumbnail,
+ void setThumbnail(std::vector<unsigned char> &&thumbnail,
Compression compression);
void setTimestamp(time_t timestamp, std::chrono::milliseconds msec);
@@ -106,4 +107,6 @@ private:
unsigned char *exifData_;
unsigned int size_;
+
+ std::vector<unsigned char> thumbnailData_;
};
diff --git a/src/android/jpeg/meson.build b/src/android/jpeg/meson.build
new file mode 100644
index 00000000..3402e614
--- /dev/null
+++ b/src/android/jpeg/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+
+android_hal_sources += files([
+ 'encoder_libjpeg.cpp',
+ 'exif.cpp',
+ 'post_processor_jpeg.cpp',
+ 'thumbnailer.cpp'
+])
+
+platform = get_option('android_platform')
+if platform == 'cros'
+ android_hal_sources += files(['encoder_jea.cpp'])
+ android_deps += [dependency('libcros_camera')]
+endif
diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp
index d72ebc3c..40261652 100644
--- a/src/android/jpeg/post_processor_jpeg.cpp
+++ b/src/android/jpeg/post_processor_jpeg.cpp
@@ -12,7 +12,11 @@
#include "../camera_device.h"
#include "../camera_metadata.h"
#include "../camera_request.h"
+#if defined(OS_CHROMEOS)
+#include "encoder_jea.h"
+#else /* !defined(OS_CHROMEOS) */
#include "encoder_libjpeg.h"
+#endif
#include "exif.h"
#include <libcamera/base/log.h>
@@ -46,7 +50,11 @@ int PostProcessorJpeg::configure(const StreamConfiguration &inCfg,
thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
+#if defined(OS_CHROMEOS)
+ encoder_ = std::make_unique<EncoderJea>();
+#else /* !defined(OS_CHROMEOS) */
encoder_ = std::make_unique<EncoderLibJpeg>();
+#endif
return encoder_->configure(inCfg);
}
@@ -166,7 +174,7 @@ void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
std::vector<unsigned char> thumbnail;
generateThumbnail(source, thumbnailSize, quality, &thumbnail);
if (!thumbnail.empty())
- exif.setThumbnail(thumbnail, Exif::Compression::JPEG);
+ exif.setThumbnail(std::move(thumbnail), Exif::Compression::JPEG);
}
resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE, data, 2);
@@ -194,8 +202,7 @@ void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
const uint8_t quality = ret ? *entry.data.u8 : 95;
resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
- int jpeg_size = encoder_->encode(source, destination->plane(0),
- exif.data(), quality);
+ int jpeg_size = encoder_->encode(streamBuffer, exif.data(), quality);
if (jpeg_size < 0) {
LOG(JPEG, Error) << "Failed to encode stream image";
processComplete.emit(streamBuffer, PostProcessor::Status::Error);
diff --git a/src/android/meson.build b/src/android/meson.build
index 1bba54de..68646120 100644
--- a/src/android/meson.build
+++ b/src/android/meson.build
@@ -46,16 +46,14 @@ android_hal_sources = files([
'camera_ops.cpp',
'camera_request.cpp',
'camera_stream.cpp',
- 'jpeg/encoder_libjpeg.cpp',
- 'jpeg/exif.cpp',
- 'jpeg/post_processor_jpeg.cpp',
- 'jpeg/thumbnailer.cpp',
+ 'hal_framebuffer.cpp',
'yuv/post_processor_yuv.cpp'
])
android_cpp_args = []
subdir('cros')
+subdir('jpeg')
subdir('mm')
android_camera_metadata_sources = files([
diff --git a/src/android/mm/cros_frame_buffer_allocator.cpp b/src/android/mm/cros_frame_buffer_allocator.cpp
index 52e8c180..0a5c59f2 100644
--- a/src/android/mm/cros_frame_buffer_allocator.cpp
+++ b/src/android/mm/cros_frame_buffer_allocator.cpp
@@ -16,6 +16,7 @@
#include "../camera_device.h"
#include "../frame_buffer_allocator.h"
+#include "../hal_framebuffer.h"
#include "cros-camera/camera_buffer_manager.h"
using namespace libcamera;
@@ -28,8 +29,9 @@ class CrosFrameBufferData : public FrameBuffer::Private
LIBCAMERA_DECLARE_PUBLIC(FrameBuffer)
public:
- CrosFrameBufferData(cros::ScopedBufferHandle scopedHandle)
- : FrameBuffer::Private(), scopedHandle_(std::move(scopedHandle))
+ CrosFrameBufferData(cros::ScopedBufferHandle scopedHandle,
+ const std::vector<FrameBuffer::Plane> &planes)
+ : FrameBuffer::Private(planes), scopedHandle_(std::move(scopedHandle))
{
}
@@ -47,11 +49,11 @@ public:
{
}
- std::unique_ptr<libcamera::FrameBuffer>
+ std::unique_ptr<HALFrameBuffer>
allocate(int halPixelFormat, const libcamera::Size &size, uint32_t usage);
};
-std::unique_ptr<libcamera::FrameBuffer>
+std::unique_ptr<HALFrameBuffer>
PlatformFrameBufferAllocator::Private::allocate(int halPixelFormat,
const libcamera::Size &size,
uint32_t usage)
@@ -80,9 +82,8 @@ PlatformFrameBufferAllocator::Private::allocate(int halPixelFormat,
plane.length = cros::CameraBufferManager::GetPlaneSize(handle, i);
}
- return std::make_unique<FrameBuffer>(
- std::make_unique<CrosFrameBufferData>(std::move(scopedHandle)),
- planes);
+ return std::make_unique<HALFrameBuffer>(
+ std::make_unique<CrosFrameBufferData>(std::move(scopedHandle), planes), handle);
}
PUBLIC_FRAME_BUFFER_ALLOCATOR_IMPLEMENTATION
diff --git a/src/android/mm/generic_frame_buffer_allocator.cpp b/src/android/mm/generic_frame_buffer_allocator.cpp
index acb2fa2b..7ecef2c6 100644
--- a/src/android/mm/generic_frame_buffer_allocator.cpp
+++ b/src/android/mm/generic_frame_buffer_allocator.cpp
@@ -5,6 +5,7 @@
* generic_camera_buffer.cpp - Allocate FrameBuffer using gralloc API
*/
+#include <dlfcn.h>
#include <memory>
#include <vector>
@@ -20,6 +21,7 @@
#include "../camera_device.h"
#include "../frame_buffer_allocator.h"
+#include "../hal_framebuffer.h"
using namespace libcamera;
@@ -32,8 +34,10 @@ class GenericFrameBufferData : public FrameBuffer::Private
public:
GenericFrameBufferData(struct alloc_device_t *allocDevice,
- buffer_handle_t handle)
- : allocDevice_(allocDevice), handle_(handle)
+ buffer_handle_t handle,
+ const std::vector<FrameBuffer::Plane> &planes)
+ : FrameBuffer::Private(planes), allocDevice_(allocDevice),
+ handle_(handle)
{
ASSERT(allocDevice_);
ASSERT(handle_);
@@ -69,20 +73,21 @@ class PlatformFrameBufferAllocator::Private : public Extensible::Private
public:
Private(CameraDevice *const cameraDevice)
: cameraDevice_(cameraDevice),
- hardwareModule_(cameraDevice->camera3Device()->common.module),
+ hardwareModule_(nullptr),
allocDevice_(nullptr)
{
+ hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &hardwareModule_);
ASSERT(hardwareModule_);
}
~Private() override;
- std::unique_ptr<libcamera::FrameBuffer>
+ std::unique_ptr<HALFrameBuffer>
allocate(int halPixelFormat, const libcamera::Size &size, uint32_t usage);
private:
const CameraDevice *const cameraDevice_;
- struct hw_module_t *const hardwareModule_;
+ const struct hw_module_t *hardwareModule_;
struct alloc_device_t *allocDevice_;
};
@@ -90,9 +95,10 @@ PlatformFrameBufferAllocator::Private::~Private()
{
if (allocDevice_)
gralloc_close(allocDevice_);
+ dlclose(hardwareModule_->dso);
}
-std::unique_ptr<libcamera::FrameBuffer>
+std::unique_ptr<HALFrameBuffer>
PlatformFrameBufferAllocator::Private::allocate(int halPixelFormat,
const libcamera::Size &size,
uint32_t usage)
@@ -135,9 +141,10 @@ PlatformFrameBufferAllocator::Private::allocate(int halPixelFormat,
offset += planeSize;
}
- return std::make_unique<FrameBuffer>(
- std::make_unique<GenericFrameBufferData>(allocDevice_, handle),
- planes);
+ return std::make_unique<HALFrameBuffer>(
+ std::make_unique<GenericFrameBufferData>(
+ allocDevice_, handle, planes),
+ handle);
}
PUBLIC_FRAME_BUFFER_ALLOCATOR_IMPLEMENTATION
diff --git a/src/android/mm/libhardware_stub.c b/src/android/mm/libhardware_stub.c
new file mode 100644
index 00000000..00f15cd9
--- /dev/null
+++ b/src/android/mm/libhardware_stub.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/*
+ * Copyright (C) 2023, Ideas on Board
+ *
+ * libhardware_stub.c - Android libhardware stub for test compilation
+ */
+
+#include <errno.h>
+
+#include <hardware/hardware.h>
+
+int hw_get_module(const char *id __attribute__((__unused__)),
+ const struct hw_module_t **module)
+{
+ *module = NULL;
+ return -ENOTSUP;
+}
diff --git a/src/android/mm/meson.build b/src/android/mm/meson.build
index d40a3b0b..e3e0484c 100644
--- a/src/android/mm/meson.build
+++ b/src/android/mm/meson.build
@@ -4,6 +4,14 @@ platform = get_option('android_platform')
if platform == 'generic'
android_hal_sources += files(['generic_camera_buffer.cpp',
'generic_frame_buffer_allocator.cpp'])
+ android_deps += [libdl]
+
+ libhardware = dependency('libhardware', required : false)
+ if libhardware.found()
+ android_deps += [libhardware]
+ else
+ android_hal_sources += files(['libhardware_stub.c'])
+ endif
elif platform == 'cros'
android_hal_sources += files(['cros_camera_buffer.cpp',
'cros_frame_buffer_allocator.cpp'])
diff --git a/src/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp
index 238186a3..8447f932 100644
--- a/src/cam/camera_session.cpp
+++ b/src/apps/cam/camera_session.cpp
@@ -13,9 +13,11 @@
#include <libcamera/control_ids.h>
#include <libcamera/property_ids.h>
+#include "../common/event_loop.h"
+#include "../common/stream_options.h"
+
#include "camera_session.h"
#include "capture_script.h"
-#include "event_loop.h"
#include "file_sink.h"
#ifdef HAVE_KMS
#include "kms_sink.h"
@@ -24,7 +26,6 @@
#ifdef HAVE_SDL
#include "sdl_sink.h"
#endif
-#include "stream_options.h"
using namespace libcamera;
@@ -54,7 +55,7 @@ CameraSession::CameraSession(CameraManager *cm,
return;
}
- StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
+ std::vector<StreamRole> roles = StreamKeyValueParser::roles(options_[OptStream]);
std::unique_ptr<CameraConfiguration> config =
camera_->generateConfiguration(roles);
@@ -64,6 +65,24 @@ CameraSession::CameraSession(CameraManager *cm,
return;
}
+ if (options_.isSet(OptOrientation)) {
+ std::string orientOpt = options_[OptOrientation].toString();
+ static const std::map<std::string, libcamera::Orientation> orientations{
+ { "rot0", libcamera::Orientation::Rotate0 },
+ { "rot180", libcamera::Orientation::Rotate180 },
+ { "mirror", libcamera::Orientation::Rotate0Mirror },
+ { "flip", libcamera::Orientation::Rotate180Mirror },
+ };
+
+ auto orientation = orientations.find(orientOpt);
+ if (orientation == orientations.end()) {
+ std::cerr << "Invalid orientation " << orientOpt << std::endl;
+ return;
+ }
+
+ config->orientation = orientation->second;
+ }
+
/* Apply configuration if explicitly requested. */
if (StreamKeyValueParser::updateConfiguration(config.get(),
options_[OptStream])) {
@@ -207,10 +226,10 @@ int CameraSession::start()
if (options_.isSet(OptFile)) {
if (!options_[OptFile].toString().empty())
- sink_ = std::make_unique<FileSink>(streamNames_,
+ sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_,
options_[OptFile]);
else
- sink_ = std::make_unique<FileSink>(streamNames_);
+ sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_);
}
if (sink_) {
diff --git a/src/cam/camera_session.h b/src/apps/cam/camera_session.h
index d562caae..0bab519f 100644
--- a/src/cam/camera_session.h
+++ b/src/apps/cam/camera_session.h
@@ -21,7 +21,7 @@
#include <libcamera/request.h>
#include <libcamera/stream.h>
-#include "options.h"
+#include "../common/options.h"
class CaptureScript;
class FrameSink;
diff --git a/src/cam/capture-script.yaml b/src/apps/cam/capture-script.yaml
index 6a749bc6..7118865e 100644
--- a/src/cam/capture-script.yaml
+++ b/src/apps/cam/capture-script.yaml
@@ -4,6 +4,19 @@
#
# A capture script allows to associate a list of controls and their values
# to frame numbers.
+#
+# The script allows defining a list of frames associated with controls
+# and an optional list of properties that can control the script behaviour.
+
+# properties:
+# # Repeat the controls every 'idx' frames.
+# - loop: idx
+#
+# # List of frame number with associated a list of controls to be applied
+# frames:
+# - frame-number:
+# Control1: value1
+# Control2: value2
# \todo Formally define the capture script structure with a schema
@@ -12,10 +25,16 @@
# libcamera::controls:: enumeration
# - Controls not supported by the camera currently operated are ignored
# - Frame numbers shall be monotonically incrementing, gaps are allowed
+# - If a loop limit is specified, frame numbers in the 'frames' list shall be
+# less than the loop control
+
+# Example: Turn brightness up and down every 460 frames
+
+properties:
+ - loop: 460
-# Example:
frames:
- - 1:
+ - 0:
Brightness: 0.0
- 40:
@@ -44,3 +63,9 @@ frames:
- 340:
Brightness: -0.8
+
+ - 380:
+ Brightness: -0.4
+
+ - 420:
+ Brightness: -0.2
diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp
new file mode 100644
index 00000000..1215713f
--- /dev/null
+++ b/src/apps/cam/capture_script.cpp
@@ -0,0 +1,662 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * capture_script.cpp - Capture session configuration script
+ */
+
+#include "capture_script.h"
+
+#include <iostream>
+#include <stdio.h>
+#include <stdlib.h>
+
+using namespace libcamera;
+
+CaptureScript::CaptureScript(std::shared_ptr<Camera> camera,
+ const std::string &fileName)
+ : camera_(camera), loop_(0), valid_(false)
+{
+ FILE *fh = fopen(fileName.c_str(), "r");
+ if (!fh) {
+ int ret = -errno;
+ std::cerr << "Failed to open capture script " << fileName
+ << ": " << strerror(-ret) << std::endl;
+ return;
+ }
+
+ /*
+ * Map the camera's controls to their name so that they can be
+ * easily identified when parsing the script file.
+ */
+ for (const auto &[control, info] : camera_->controls())
+ controls_[control->name()] = control;
+
+ int ret = parseScript(fh);
+ fclose(fh);
+ if (ret)
+ return;
+
+ valid_ = true;
+}
+
+/* Retrieve the control list associated with a frame number. */
+const ControlList &CaptureScript::frameControls(unsigned int frame)
+{
+ static ControlList controls{};
+ unsigned int idx = frame;
+
+ /* If we loop, repeat the controls every 'loop_' frames. */
+ if (loop_)
+ idx = frame % loop_;
+
+ auto it = frameControls_.find(idx);
+ if (it == frameControls_.end())
+ return controls;
+
+ return it->second;
+}
+
+CaptureScript::EventPtr CaptureScript::nextEvent(yaml_event_type_t expectedType)
+{
+ EventPtr event(new yaml_event_t);
+
+ if (!yaml_parser_parse(&parser_, event.get()))
+ return nullptr;
+
+ if (expectedType != YAML_NO_EVENT && !checkEvent(event, expectedType))
+ return nullptr;
+
+ return event;
+}
+
+bool CaptureScript::checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const
+{
+ if (event->type != expectedType) {
+ std::cerr << "Capture script error on line " << event->start_mark.line
+ << " column " << event->start_mark.column << ": "
+ << "Expected " << eventTypeName(expectedType)
+ << " event, got " << eventTypeName(event->type)
+ << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+std::string CaptureScript::eventScalarValue(const EventPtr &event)
+{
+ return std::string(reinterpret_cast<char *>(event->data.scalar.value),
+ event->data.scalar.length);
+}
+
+std::string CaptureScript::eventTypeName(yaml_event_type_t type)
+{
+ static const std::map<yaml_event_type_t, std::string> typeNames = {
+ { YAML_STREAM_START_EVENT, "stream-start" },
+ { YAML_STREAM_END_EVENT, "stream-end" },
+ { YAML_DOCUMENT_START_EVENT, "document-start" },
+ { YAML_DOCUMENT_END_EVENT, "document-end" },
+ { YAML_ALIAS_EVENT, "alias" },
+ { YAML_SCALAR_EVENT, "scalar" },
+ { YAML_SEQUENCE_START_EVENT, "sequence-start" },
+ { YAML_SEQUENCE_END_EVENT, "sequence-end" },
+ { YAML_MAPPING_START_EVENT, "mapping-start" },
+ { YAML_MAPPING_END_EVENT, "mapping-end" },
+ };
+
+ auto it = typeNames.find(type);
+ if (it == typeNames.end())
+ return "[type " + std::to_string(type) + "]";
+
+ return it->second;
+}
+
+int CaptureScript::parseScript(FILE *script)
+{
+ int ret = yaml_parser_initialize(&parser_);
+ if (!ret) {
+ std::cerr << "Failed to initialize yaml parser" << std::endl;
+ return ret;
+ }
+
+ /* Delete the parser upon function exit. */
+ struct ParserDeleter {
+ ParserDeleter(yaml_parser_t *parser) : parser_(parser) { }
+ ~ParserDeleter() { yaml_parser_delete(parser_); }
+ yaml_parser_t *parser_;
+ } deleter(&parser_);
+
+ yaml_parser_set_input_file(&parser_, script);
+
+ EventPtr event = nextEvent(YAML_STREAM_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ event = nextEvent(YAML_DOCUMENT_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ event = nextEvent(YAML_MAPPING_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ while (1) {
+ event = nextEvent();
+ if (!event)
+ return -EINVAL;
+
+ if (event->type == YAML_MAPPING_END_EVENT)
+ return 0;
+
+ if (!checkEvent(event, YAML_SCALAR_EVENT))
+ return -EINVAL;
+
+ std::string section = eventScalarValue(event);
+
+ if (section == "properties") {
+ ret = parseProperties();
+ if (ret)
+ return ret;
+ } else if (section == "frames") {
+ ret = parseFrames();
+ if (ret)
+ return ret;
+ } else {
+ std::cerr << "Unsupported section '" << section << "'"
+ << std::endl;
+ return -EINVAL;
+ }
+ }
+}
+
+int CaptureScript::parseProperty()
+{
+ EventPtr event = nextEvent(YAML_MAPPING_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ std::string prop = parseScalar();
+ if (prop.empty())
+ return -EINVAL;
+
+ if (prop == "loop") {
+ event = nextEvent();
+ if (!event)
+ return -EINVAL;
+
+ std::string value = eventScalarValue(event);
+ if (value.empty())
+ return -EINVAL;
+
+ loop_ = atoi(value.c_str());
+ if (!loop_) {
+ std::cerr << "Invalid loop limit '" << loop_ << "'"
+ << std::endl;
+ return -EINVAL;
+ }
+ } else {
+ std::cerr << "Unsupported property '" << prop << "'" << std::endl;
+ return -EINVAL;
+ }
+
+ event = nextEvent(YAML_MAPPING_END_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ return 0;
+}
+
+int CaptureScript::parseProperties()
+{
+ EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ while (1) {
+ if (event->type == YAML_SEQUENCE_END_EVENT)
+ return 0;
+
+ int ret = parseProperty();
+ if (ret)
+ return ret;
+
+ event = nextEvent();
+ if (!event)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int CaptureScript::parseFrames()
+{
+ EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ while (1) {
+ event = nextEvent();
+ if (!event)
+ return -EINVAL;
+
+ if (event->type == YAML_SEQUENCE_END_EVENT)
+ return 0;
+
+ int ret = parseFrame(std::move(event));
+ if (ret)
+ return ret;
+ }
+}
+
+int CaptureScript::parseFrame(EventPtr event)
+{
+ if (!checkEvent(event, YAML_MAPPING_START_EVENT))
+ return -EINVAL;
+
+ std::string key = parseScalar();
+ if (key.empty())
+ return -EINVAL;
+
+ unsigned int frameId = atoi(key.c_str());
+ if (loop_ && frameId >= loop_) {
+ std::cerr
+ << "Frame id (" << frameId << ") shall be smaller than"
+ << "loop limit (" << loop_ << ")" << std::endl;
+ return -EINVAL;
+ }
+
+ event = nextEvent(YAML_MAPPING_START_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ ControlList controls{};
+
+ while (1) {
+ event = nextEvent();
+ if (!event)
+ return -EINVAL;
+
+ if (event->type == YAML_MAPPING_END_EVENT)
+ break;
+
+ int ret = parseControl(std::move(event), controls);
+ if (ret)
+ return ret;
+ }
+
+ frameControls_[frameId] = std::move(controls);
+
+ event = nextEvent(YAML_MAPPING_END_EVENT);
+ if (!event)
+ return -EINVAL;
+
+ return 0;
+}
+
+int CaptureScript::parseControl(EventPtr event, ControlList &controls)
+{
+ /* We expect a value after a key. */
+ std::string name = eventScalarValue(event);
+ if (name.empty())
+ return -EINVAL;
+
+ /* If the camera does not support the control just ignore it. */
+ auto it = controls_.find(name);
+ if (it == controls_.end()) {
+ std::cerr << "Unsupported control '" << name << "'" << std::endl;
+ return -EINVAL;
+ }
+
+ const ControlId *controlId = it->second;
+
+ ControlValue val = unpackControl(controlId);
+ if (val.isNone()) {
+ std::cerr << "Error unpacking control '" << name << "'"
+ << std::endl;
+ return -EINVAL;
+ }
+
+ controls.set(controlId->id(), val);
+
+ return 0;
+}
+
+std::string CaptureScript::parseScalar()
+{
+ EventPtr event = nextEvent(YAML_SCALAR_EVENT);
+ if (!event)
+ return "";
+
+ return eventScalarValue(event);
+}
+
+ControlValue CaptureScript::parseRectangles()
+{
+ std::vector<libcamera::Rectangle> rectangles;
+
+ std::vector<std::vector<std::string>> arrays = parseArrays();
+ if (arrays.empty())
+ return {};
+
+ for (const std::vector<std::string> &values : arrays) {
+ if (values.size() != 4) {
+ std::cerr << "Error parsing Rectangle: expected "
+ << "array with 4 parameters" << std::endl;
+ return {};
+ }
+
+ Rectangle rect = unpackRectangle(values);
+ rectangles.push_back(rect);
+ }
+
+ ControlValue controlValue;
+ if (rectangles.size() == 1)
+ controlValue.set(rectangles.at(0));
+ else
+ controlValue.set(Span<const Rectangle>(rectangles));
+
+ return controlValue;
+}
+
+std::vector<std::vector<std::string>> CaptureScript::parseArrays()
+{
+ EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
+ if (!event)
+ return {};
+
+ event = nextEvent();
+ if (!event)
+ return {};
+
+ std::vector<std::vector<std::string>> valueArrays;
+
+ /* Parse single array. */
+ if (event->type == YAML_SCALAR_EVENT) {
+ std::string firstValue = eventScalarValue(event);
+ if (firstValue.empty())
+ return {};
+
+ std::vector<std::string> remaining = parseSingleArray();
+
+ std::vector<std::string> values = { firstValue };
+ values.insert(std::end(values),
+ std::begin(remaining), std::end(remaining));
+ valueArrays.push_back(values);
+
+ return valueArrays;
+ }
+
+ /* Parse array of arrays. */
+ while (1) {
+ switch (event->type) {
+ case YAML_SEQUENCE_START_EVENT: {
+ std::vector<std::string> values = parseSingleArray();
+ valueArrays.push_back(values);
+ break;
+ }
+ case YAML_SEQUENCE_END_EVENT:
+ return valueArrays;
+ default:
+ return {};
+ }
+
+ event = nextEvent();
+ if (!event)
+ return {};
+ }
+}
+
+std::vector<std::string> CaptureScript::parseSingleArray()
+{
+ std::vector<std::string> values;
+
+ while (1) {
+ EventPtr event = nextEvent();
+ if (!event)
+ return {};
+
+ switch (event->type) {
+ case YAML_SCALAR_EVENT: {
+ std::string value = eventScalarValue(event);
+ if (value.empty())
+ return {};
+ values.push_back(value);
+ break;
+ }
+ case YAML_SEQUENCE_END_EVENT:
+ return values;
+ default:
+ return {};
+ }
+ }
+}
+
+void CaptureScript::unpackFailure(const ControlId *id, const std::string &repr)
+{
+ static const std::map<unsigned int, const char *> typeNames = {
+ { ControlTypeNone, "none" },
+ { ControlTypeBool, "bool" },
+ { ControlTypeByte, "byte" },
+ { ControlTypeInteger32, "int32" },
+ { ControlTypeInteger64, "int64" },
+ { ControlTypeFloat, "float" },
+ { ControlTypeString, "string" },
+ { ControlTypeRectangle, "Rectangle" },
+ { ControlTypeSize, "Size" },
+ };
+
+ const char *typeName;
+ auto it = typeNames.find(id->type());
+ if (it != typeNames.end())
+ typeName = it->second;
+ else
+ typeName = "unknown";
+
+ std::cerr << "Unsupported control '" << repr << "' for "
+ << typeName << " control " << id->name() << std::endl;
+}
+
+ControlValue CaptureScript::parseScalarControl(const ControlId *id,
+ const std::string repr)
+{
+ ControlValue value{};
+
+ switch (id->type()) {
+ case ControlTypeNone:
+ break;
+ case ControlTypeBool: {
+ bool val;
+
+ if (repr == "true") {
+ val = true;
+ } else if (repr == "false") {
+ val = false;
+ } else {
+ unpackFailure(id, repr);
+ return value;
+ }
+
+ value.set<bool>(val);
+ break;
+ }
+ case ControlTypeByte: {
+ uint8_t val = strtol(repr.c_str(), NULL, 10);
+ value.set<uint8_t>(val);
+ break;
+ }
+ case ControlTypeInteger32: {
+ int32_t val = strtol(repr.c_str(), NULL, 10);
+ value.set<int32_t>(val);
+ break;
+ }
+ case ControlTypeInteger64: {
+ int64_t val = strtoll(repr.c_str(), NULL, 10);
+ value.set<int64_t>(val);
+ break;
+ }
+ case ControlTypeFloat: {
+ float val = strtof(repr.c_str(), NULL);
+ value.set<float>(val);
+ break;
+ }
+ case ControlTypeString: {
+ value.set<std::string>(repr);
+ break;
+ }
+ default:
+ std::cerr << "Unsupported control type" << std::endl;
+ break;
+ }
+
+ return value;
+}
+
+ControlValue CaptureScript::parseArrayControl(const ControlId *id,
+ const std::vector<std::string> &repr)
+{
+ ControlValue value{};
+
+ switch (id->type()) {
+ case ControlTypeNone:
+ break;
+ case ControlTypeBool: {
+ /*
+ * This is unpleasant, but we cannot use an std::vector<> as its
+ * boolean type overload does not allow to access the raw data,
+ * as boolean values are stored in a bitmask for efficiency.
+ *
+ * As we need a contiguous memory region to wrap in a Span<>,
+ * use an array instead but be strict about not overflowing it
+ * by limiting the number of controls we can store.
+ *
+ * Be loud but do not fail, as the issue would present at
+ * runtime and it's not fatal.
+ */
+ static constexpr unsigned int kMaxNumBooleanControls = 1024;
+ std::array<bool, kMaxNumBooleanControls> values;
+ unsigned int idx = 0;
+
+ for (const std::string &s : repr) {
+ bool val;
+
+ if (s == "true") {
+ val = true;
+ } else if (s == "false") {
+ val = false;
+ } else {
+ unpackFailure(id, s);
+ return value;
+ }
+
+ if (idx == kMaxNumBooleanControls) {
+ std::cerr << "Cannot parse more than "
+ << kMaxNumBooleanControls
+ << " boolean controls" << std::endl;
+ break;
+ }
+
+ values[idx++] = val;
+ }
+
+ value = Span<bool>(values.data(), idx);
+ break;
+ }
+ case ControlTypeByte: {
+ std::vector<uint8_t> values;
+ for (const std::string &s : repr) {
+ uint8_t val = strtoll(s.c_str(), NULL, 10);
+ values.push_back(val);
+ }
+
+ value = Span<const uint8_t>(values.data(), values.size());
+ break;
+ }
+ case ControlTypeInteger32: {
+ std::vector<int32_t> values;
+ for (const std::string &s : repr) {
+ int32_t val = strtoll(s.c_str(), NULL, 10);
+ values.push_back(val);
+ }
+
+ value = Span<const int32_t>(values.data(), values.size());
+ break;
+ }
+ case ControlTypeInteger64: {
+ std::vector<int64_t> values;
+ for (const std::string &s : repr) {
+ int64_t val = strtoll(s.c_str(), NULL, 10);
+ values.push_back(val);
+ }
+
+ value = Span<const int64_t>(values.data(), values.size());
+ break;
+ }
+ case ControlTypeFloat: {
+ std::vector<float> values;
+ for (const std::string &s : repr)
+ values.push_back(strtof(s.c_str(), NULL));
+
+ value = Span<const float>(values.data(), values.size());
+ break;
+ }
+ case ControlTypeString: {
+ value = Span<const std::string>(repr.data(), repr.size());
+ break;
+ }
+ default:
+ std::cerr << "Unsupported control type" << std::endl;
+ break;
+ }
+
+ return value;
+}
+
+ControlValue CaptureScript::unpackControl(const ControlId *id)
+{
+ /* Parse complex types. */
+ switch (id->type()) {
+ case ControlTypeRectangle:
+ return parseRectangles();
+ case ControlTypeSize:
+ /* \todo Parse Sizes. */
+ return {};
+ default:
+ break;
+ }
+
+ /* Check if the control has a single scalar value or is an array. */
+ EventPtr event = nextEvent();
+ if (!event)
+ return {};
+
+ switch (event->type) {
+ case YAML_SCALAR_EVENT: {
+ const std::string repr = eventScalarValue(event);
+ if (repr.empty())
+ return {};
+
+ return parseScalarControl(id, repr);
+ }
+ case YAML_SEQUENCE_START_EVENT: {
+ std::vector<std::string> array = parseSingleArray();
+ if (array.empty())
+ return {};
+
+ return parseArrayControl(id, array);
+ }
+ default:
+ std::cerr << "Unexpected event type: " << event->type << std::endl;
+ return {};
+ }
+}
+
+libcamera::Rectangle CaptureScript::unpackRectangle(const std::vector<std::string> &strVec)
+{
+ int x = strtol(strVec[0].c_str(), NULL, 10);
+ int y = strtol(strVec[1].c_str(), NULL, 10);
+ unsigned int width = strtoul(strVec[2].c_str(), NULL, 10);
+ unsigned int height = strtoul(strVec[3].c_str(), NULL, 10);
+
+ return Rectangle(x, y, width, height);
+}
diff --git a/src/cam/capture_script.h b/src/apps/cam/capture_script.h
index 8b4f8f62..40042c03 100644
--- a/src/cam/capture_script.h
+++ b/src/apps/cam/capture_script.h
@@ -40,6 +40,7 @@ private:
std::map<unsigned int, libcamera::ControlList> frameControls_;
std::shared_ptr<libcamera::Camera> camera_;
yaml_parser_t parser_;
+ unsigned int loop_;
bool valid_;
EventPtr nextEvent(yaml_event_type_t expectedType = YAML_NO_EVENT);
@@ -49,14 +50,24 @@ private:
int parseScript(FILE *script);
+ int parseProperties();
+ int parseProperty();
int parseFrames();
int parseFrame(EventPtr event);
int parseControl(EventPtr event, libcamera::ControlList &controls);
+ libcamera::ControlValue parseScalarControl(const libcamera::ControlId *id,
+ const std::string repr);
+ libcamera::ControlValue parseArrayControl(const libcamera::ControlId *id,
+ const std::vector<std::string> &repr);
+
std::string parseScalar();
+ libcamera::ControlValue parseRectangles();
+ std::vector<std::vector<std::string>> parseArrays();
+ std::vector<std::string> parseSingleArray();
void unpackFailure(const libcamera::ControlId *id,
const std::string &repr);
- libcamera::ControlValue unpackControl(const libcamera::ControlId *id,
- const std::string &repr);
+ libcamera::ControlValue unpackControl(const libcamera::ControlId *id);
+ libcamera::Rectangle unpackRectangle(const std::vector<std::string> &strVec);
};
diff --git a/src/cam/drm.cpp b/src/apps/cam/drm.cpp
index fbfc0a59..8779a713 100644
--- a/src/cam/drm.cpp
+++ b/src/apps/cam/drm.cpp
@@ -24,7 +24,7 @@
#include <libdrm/drm_mode.h>
-#include "event_loop.h"
+#include "../common/event_loop.h"
namespace DRM {
@@ -377,6 +377,8 @@ int AtomicRequest::commit(unsigned int flags)
drmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
if (flags & FlagAsync)
drmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
+ if (flags & FlagTestOnly)
+ drmFlags |= DRM_MODE_ATOMIC_TEST_ONLY;
return drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this);
}
@@ -428,7 +430,8 @@ int Device::init()
int Device::openCard()
{
const std::string dirName = "/dev/dri/";
- int ret = -ENOENT;
+ bool found = false;
+ int ret;
/*
* Open the first DRM/KMS device beginning with /dev/dri/card. The
@@ -447,24 +450,42 @@ int Device::openCard()
}
for (struct dirent *res; (res = readdir(folder));) {
+ uint64_t cap;
+
if (strncmp(res->d_name, "card", 4))
continue;
const std::string devName = dirName + res->d_name;
fd_ = open(devName.c_str(), O_RDWR | O_CLOEXEC);
- if (fd_ >= 0) {
- ret = 0;
- break;
+ if (fd_ < 0) {
+ ret = -errno;
+ std::cerr << "Failed to open DRM/KMS device " << devName << ": "
+ << strerror(-ret) << std::endl;
+ continue;
}
- ret = -errno;
- std::cerr << "Failed to open DRM/KMS device " << devName << ": "
- << strerror(-ret) << std::endl;
+ /*
+ * Skip devices that don't support the modeset API, to avoid
+ * selecting a DRM device corresponding to a GPU. There is no
+ * modeset capability, but the kernel returns an error for most
+ * caps if mode setting isn't support by the driver. The
+ * DRM_CAP_DUMB_BUFFER capability is one of those, other would
+ * do as well. The capability value itself isn't relevant.
+ */
+ ret = drmGetCap(fd_, DRM_CAP_DUMB_BUFFER, &cap);
+ if (ret < 0) {
+ drmClose(fd_);
+ fd_ = -1;
+ continue;
+ }
+
+ found = true;
+ break;
}
closedir(folder);
- return ret;
+ return found ? 0 : -ENOENT;
}
int Device::getResources()
diff --git a/src/cam/drm.h b/src/apps/cam/drm.h
index 655a7509..ebaea04d 100644
--- a/src/cam/drm.h
+++ b/src/apps/cam/drm.h
@@ -251,6 +251,7 @@ public:
enum Flags {
FlagAllowModeset = (1 << 0),
FlagAsync = (1 << 1),
+ FlagTestOnly = (1 << 2),
};
AtomicRequest(Device *dev);
diff --git a/src/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp
index 45213d4a..906b50e6 100644
--- a/src/cam/file_sink.cpp
+++ b/src/apps/cam/file_sink.cpp
@@ -15,14 +15,22 @@
#include <libcamera/camera.h>
+#include "../common/dng_writer.h"
+#include "../common/image.h"
+#include "../common/ppm_writer.h"
+
#include "file_sink.h"
-#include "image.h"
using namespace libcamera;
-FileSink::FileSink(const std::map<const libcamera::Stream *, std::string> &streamNames,
+FileSink::FileSink([[maybe_unused]] const libcamera::Camera *camera,
+ const std::map<const libcamera::Stream *, std::string> &streamNames,
const std::string &pattern)
- : streamNames_(streamNames), pattern_(pattern)
+ :
+#ifdef HAVE_TIFF
+ camera_(camera),
+#endif
+ streamNames_(streamNames), pattern_(pattern)
{
}
@@ -51,12 +59,13 @@ void FileSink::mapBuffer(FrameBuffer *buffer)
bool FileSink::processRequest(Request *request)
{
for (auto [stream, buffer] : request->buffers())
- writeBuffer(stream, buffer);
+ writeBuffer(stream, buffer, request->metadata());
return true;
}
-void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer)
+void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,
+ [[maybe_unused]] const ControlList &metadata)
{
std::string filename;
size_t pos;
@@ -65,6 +74,11 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer)
if (!pattern_.empty())
filename = pattern_;
+#ifdef HAVE_TIFF
+ bool dng = filename.find(".dng", filename.size() - 4) != std::string::npos;
+#endif /* HAVE_TIFF */
+ bool ppm = filename.find(".ppm", filename.size() - 4) != std::string::npos;
+
if (filename.empty() || filename.back() == '/')
filename += "frame-#.bin";
@@ -76,6 +90,30 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer)
filename.replace(pos, 1, ss.str());
}
+ Image *image = mappedBuffers_[buffer].get();
+
+#ifdef HAVE_TIFF
+ if (dng) {
+ ret = DNGWriter::write(filename.c_str(), camera_,
+ stream->configuration(), metadata,
+ buffer, image->data(0).data());
+ if (ret < 0)
+ std::cerr << "failed to write DNG file `" << filename
+ << "'" << std::endl;
+
+ return;
+ }
+#endif /* HAVE_TIFF */
+ if (ppm) {
+ ret = PPMWriter::write(filename.c_str(), stream->configuration(),
+ image->data(0));
+ if (ret < 0)
+ std::cerr << "failed to write PPM file `" << filename
+ << "'" << std::endl;
+
+ return;
+ }
+
fd = open(filename.c_str(), O_CREAT | O_WRONLY |
(pos == std::string::npos ? O_APPEND : O_TRUNC),
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
@@ -86,16 +124,19 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer)
return;
}
- Image *image = mappedBuffers_[buffer].get();
-
for (unsigned int i = 0; i < buffer->planes().size(); ++i) {
- const FrameMetadata::Plane &meta = buffer->metadata().planes()[i];
+ /*
+ * This was formerly a local "const FrameMetadata::Plane &"
+ * however this causes a false positive warning for dangling
+ * references on gcc 13.
+ */
+ const unsigned int bytesused = buffer->metadata().planes()[i].bytesused;
Span<uint8_t> data = image->data(i);
- unsigned int length = std::min<unsigned int>(meta.bytesused, data.size());
+ const unsigned int length = std::min<unsigned int>(bytesused, data.size());
- if (meta.bytesused > data.size())
- std::cerr << "payload size " << meta.bytesused
+ if (bytesused > data.size())
+ std::cerr << "payload size " << bytesused
<< " larger than plane size " << data.size()
<< std::endl;
diff --git a/src/cam/file_sink.h b/src/apps/cam/file_sink.h
index 067736f5..300edf8d 100644
--- a/src/cam/file_sink.h
+++ b/src/apps/cam/file_sink.h
@@ -20,7 +20,8 @@ class Image;
class FileSink : public FrameSink
{
public:
- FileSink(const std::map<const libcamera::Stream *, std::string> &streamNames,
+ FileSink(const libcamera::Camera *camera,
+ const std::map<const libcamera::Stream *, std::string> &streamNames,
const std::string &pattern = "");
~FileSink();
@@ -32,8 +33,12 @@ public:
private:
void writeBuffer(const libcamera::Stream *stream,
- libcamera::FrameBuffer *buffer);
+ libcamera::FrameBuffer *buffer,
+ const libcamera::ControlList &metadata);
+#ifdef HAVE_TIFF
+ const libcamera::Camera *camera_;
+#endif
std::map<const libcamera::Stream *, std::string> streamNames_;
std::string pattern_;
std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>> mappedBuffers_;
diff --git a/src/cam/frame_sink.cpp b/src/apps/cam/frame_sink.cpp
index af21d575..af21d575 100644
--- a/src/cam/frame_sink.cpp
+++ b/src/apps/cam/frame_sink.cpp
diff --git a/src/cam/frame_sink.h b/src/apps/cam/frame_sink.h
index ca4347cb..ca4347cb 100644
--- a/src/cam/frame_sink.h
+++ b/src/apps/cam/frame_sink.h
diff --git a/src/cam/kms_sink.cpp b/src/apps/cam/kms_sink.cpp
index 37a3bd50..6991c3fa 100644
--- a/src/cam/kms_sink.cpp
+++ b/src/apps/cam/kms_sink.cpp
@@ -149,6 +149,81 @@ int KMSSink::configure(const libcamera::CameraConfiguration &config)
size_ = cfg.size;
stride_ = cfg.stride;
+ /* Configure color space. */
+ colorEncoding_ = std::nullopt;
+ colorRange_ = std::nullopt;
+
+ if (cfg.colorSpace->ycbcrEncoding == libcamera::ColorSpace::YcbcrEncoding::None)
+ return 0;
+
+ /*
+ * The encoding and range enums are defined in the kernel but not
+ * exposed in public headers.
+ */
+ enum drm_color_encoding {
+ DRM_COLOR_YCBCR_BT601,
+ DRM_COLOR_YCBCR_BT709,
+ DRM_COLOR_YCBCR_BT2020,
+ };
+
+ enum drm_color_range {
+ DRM_COLOR_YCBCR_LIMITED_RANGE,
+ DRM_COLOR_YCBCR_FULL_RANGE,
+ };
+
+ const DRM::Property *colorEncoding = plane_->property("COLOR_ENCODING");
+ const DRM::Property *colorRange = plane_->property("COLOR_RANGE");
+
+ if (colorEncoding) {
+ drm_color_encoding encoding;
+
+ switch (cfg.colorSpace->ycbcrEncoding) {
+ case libcamera::ColorSpace::YcbcrEncoding::Rec601:
+ default:
+ encoding = DRM_COLOR_YCBCR_BT601;
+ break;
+ case libcamera::ColorSpace::YcbcrEncoding::Rec709:
+ encoding = DRM_COLOR_YCBCR_BT709;
+ break;
+ case libcamera::ColorSpace::YcbcrEncoding::Rec2020:
+ encoding = DRM_COLOR_YCBCR_BT2020;
+ break;
+ }
+
+ for (const auto &[id, name] : colorEncoding->enums()) {
+ if (id == encoding) {
+ colorEncoding_ = encoding;
+ break;
+ }
+ }
+ }
+
+ if (colorRange) {
+ drm_color_range range;
+
+ switch (cfg.colorSpace->range) {
+ case libcamera::ColorSpace::Range::Limited:
+ default:
+ range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+ break;
+ case libcamera::ColorSpace::Range::Full:
+ range = DRM_COLOR_YCBCR_FULL_RANGE;
+ break;
+ }
+
+ for (const auto &[id, name] : colorRange->enums()) {
+ if (id == range) {
+ colorRange_ = range;
+ break;
+ }
+ }
+ }
+
+ if (!colorEncoding_ || !colorRange_)
+ std::cerr << "Color space " << cfg.colorSpace->toString()
+ << " not supported by the display device."
+ << " Colors may be wrong." << std::endl;
+
return 0;
}
@@ -228,24 +303,22 @@ int KMSSink::configurePipeline(const libcamera::PixelFormat &format)
int KMSSink::start()
{
- std::unique_ptr<DRM::AtomicRequest> request;
-
int ret = FrameSink::start();
if (ret < 0)
return ret;
/* Disable all CRTCs and planes to start from a known valid state. */
- request = std::make_unique<DRM::AtomicRequest>(&dev_);
+ DRM::AtomicRequest request(&dev_);
for (const DRM::Crtc &crtc : dev_.crtcs())
- request->addProperty(&crtc, "ACTIVE", 0);
+ request.addProperty(&crtc, "ACTIVE", 0);
for (const DRM::Plane &plane : dev_.planes()) {
- request->addProperty(&plane, "CRTC_ID", 0);
- request->addProperty(&plane, "FB_ID", 0);
+ request.addProperty(&plane, "CRTC_ID", 0);
+ request.addProperty(&plane, "FB_ID", 0);
}
- ret = request->commit(DRM::AtomicRequest::FlagAllowModeset);
+ ret = request.commit(DRM::AtomicRequest::FlagAllowModeset);
if (ret < 0) {
std::cerr
<< "Failed to disable CRTCs and planes: "
@@ -284,6 +357,94 @@ int KMSSink::stop()
return FrameSink::stop();
}
+bool KMSSink::testModeSet(DRM::FrameBuffer *drmBuffer,
+ const libcamera::Rectangle &src,
+ const libcamera::Rectangle &dst)
+{
+ DRM::AtomicRequest drmRequest{ &dev_ };
+
+ drmRequest.addProperty(connector_, "CRTC_ID", crtc_->id());
+
+ drmRequest.addProperty(crtc_, "ACTIVE", 1);
+ drmRequest.addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_));
+
+ drmRequest.addProperty(plane_, "CRTC_ID", crtc_->id());
+ drmRequest.addProperty(plane_, "FB_ID", drmBuffer->id());
+ drmRequest.addProperty(plane_, "SRC_X", src.x << 16);
+ drmRequest.addProperty(plane_, "SRC_Y", src.y << 16);
+ drmRequest.addProperty(plane_, "SRC_W", src.width << 16);
+ drmRequest.addProperty(plane_, "SRC_H", src.height << 16);
+ drmRequest.addProperty(plane_, "CRTC_X", dst.x);
+ drmRequest.addProperty(plane_, "CRTC_Y", dst.y);
+ drmRequest.addProperty(plane_, "CRTC_W", dst.width);
+ drmRequest.addProperty(plane_, "CRTC_H", dst.height);
+
+ return !drmRequest.commit(DRM::AtomicRequest::FlagAllowModeset |
+ DRM::AtomicRequest::FlagTestOnly);
+}
+
+bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)
+{
+ /*
+ * Test composition options, from most to least desirable, to select the
+ * best one.
+ */
+ const libcamera::Rectangle framebuffer{ size_ };
+ const libcamera::Rectangle display{ 0, 0, mode_->hdisplay, mode_->vdisplay };
+
+ /* 1. Scale the frame buffer to full screen, preserving aspect ratio. */
+ libcamera::Rectangle src = framebuffer;
+ libcamera::Rectangle dst = display.size().boundedToAspectRatio(framebuffer.size())
+ .centeredTo(display.center());
+
+ if (testModeSet(drmBuffer, src, dst)) {
+ std::cout << "KMS: full-screen scaled output, square pixels"
+ << std::endl;
+ src_ = src;
+ dst_ = dst;
+ return true;
+ }
+
+ /*
+ * 2. Scale the frame buffer to full screen, without preserving aspect
+ * ratio.
+ */
+ src = framebuffer;
+ dst = display;
+
+ if (testModeSet(drmBuffer, src, dst)) {
+ std::cout << "KMS: full-screen scaled output, non-square pixels"
+ << std::endl;
+ src_ = src;
+ dst_ = dst;
+ return true;
+ }
+
+ /* 3. Center the frame buffer on the display. */
+ src = display.size().centeredTo(framebuffer.center()).boundedTo(framebuffer);
+ dst = framebuffer.size().centeredTo(display.center()).boundedTo(display);
+
+ if (testModeSet(drmBuffer, src, dst)) {
+ std::cout << "KMS: centered output" << std::endl;
+ src_ = src;
+ dst_ = dst;
+ return true;
+ }
+
+ /* 4. Align the frame buffer on the top-left of the display. */
+ src = framebuffer.boundedTo(display);
+ dst = display.boundedTo(framebuffer);
+
+ if (testModeSet(drmBuffer, src, dst)) {
+ std::cout << "KMS: top-left aligned output" << std::endl;
+ src_ = src;
+ dst_ = dst;
+ return true;
+ }
+
+ return false;
+}
+
bool KMSSink::processRequest(libcamera::Request *camRequest)
{
/*
@@ -301,35 +462,46 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)
DRM::FrameBuffer *drmBuffer = iter->second.get();
unsigned int flags = DRM::AtomicRequest::FlagAsync;
- DRM::AtomicRequest *drmRequest = new DRM::AtomicRequest(&dev_);
+ std::unique_ptr<DRM::AtomicRequest> drmRequest =
+ std::make_unique<DRM::AtomicRequest>(&dev_);
drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id());
if (!active_ && !queued_) {
/* Enable the display pipeline on the first frame. */
+ if (!setupComposition(drmBuffer)) {
+ std::cerr << "Failed to setup composition" << std::endl;
+ return true;
+ }
+
drmRequest->addProperty(connector_, "CRTC_ID", crtc_->id());
drmRequest->addProperty(crtc_, "ACTIVE", 1);
drmRequest->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_));
drmRequest->addProperty(plane_, "CRTC_ID", crtc_->id());
- drmRequest->addProperty(plane_, "SRC_X", 0 << 16);
- drmRequest->addProperty(plane_, "SRC_Y", 0 << 16);
- drmRequest->addProperty(plane_, "SRC_W", size_.width << 16);
- drmRequest->addProperty(plane_, "SRC_H", size_.height << 16);
- drmRequest->addProperty(plane_, "CRTC_X", 0);
- drmRequest->addProperty(plane_, "CRTC_Y", 0);
- drmRequest->addProperty(plane_, "CRTC_W", size_.width);
- drmRequest->addProperty(plane_, "CRTC_H", size_.height);
+ drmRequest->addProperty(plane_, "SRC_X", src_.x << 16);
+ drmRequest->addProperty(plane_, "SRC_Y", src_.y << 16);
+ drmRequest->addProperty(plane_, "SRC_W", src_.width << 16);
+ drmRequest->addProperty(plane_, "SRC_H", src_.height << 16);
+ drmRequest->addProperty(plane_, "CRTC_X", dst_.x);
+ drmRequest->addProperty(plane_, "CRTC_Y", dst_.y);
+ drmRequest->addProperty(plane_, "CRTC_W", dst_.width);
+ drmRequest->addProperty(plane_, "CRTC_H", dst_.height);
+
+ if (colorEncoding_)
+ drmRequest->addProperty(plane_, "COLOR_ENCODING", *colorEncoding_);
+ if (colorRange_)
+ drmRequest->addProperty(plane_, "COLOR_RANGE", *colorRange_);
flags |= DRM::AtomicRequest::FlagAllowModeset;
}
- pending_ = std::make_unique<Request>(drmRequest, camRequest);
+ pending_ = std::make_unique<Request>(std::move(drmRequest), camRequest);
std::lock_guard<std::mutex> lock(lock_);
if (!queued_) {
- int ret = drmRequest->commit(flags);
+ int ret = pending_->drmRequest_->commit(flags);
if (ret < 0) {
std::cerr
<< "Failed to commit atomic request: "
@@ -343,7 +515,7 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)
return false;
}
-void KMSSink::requestComplete(DRM::AtomicRequest *request)
+void KMSSink::requestComplete([[maybe_unused]] DRM::AtomicRequest *request)
{
std::lock_guard<std::mutex> lock(lock_);
diff --git a/src/cam/kms_sink.h b/src/apps/cam/kms_sink.h
index 4a0a872c..e2c618a1 100644
--- a/src/cam/kms_sink.h
+++ b/src/apps/cam/kms_sink.h
@@ -10,6 +10,7 @@
#include <list>
#include <memory>
#include <mutex>
+#include <optional>
#include <string>
#include <utility>
@@ -38,8 +39,9 @@ private:
class Request
{
public:
- Request(DRM::AtomicRequest *drmRequest, libcamera::Request *camRequest)
- : drmRequest_(drmRequest), camRequest_(camRequest)
+ Request(std::unique_ptr<DRM::AtomicRequest> drmRequest,
+ libcamera::Request *camRequest)
+ : drmRequest_(std::move(drmRequest)), camRequest_(camRequest)
{
}
@@ -49,6 +51,11 @@ private:
int selectPipeline(const libcamera::PixelFormat &format);
int configurePipeline(const libcamera::PixelFormat &format);
+ bool testModeSet(DRM::FrameBuffer *drmBuffer,
+ const libcamera::Rectangle &src,
+ const libcamera::Rectangle &dst);
+ bool setupComposition(DRM::FrameBuffer *drmBuffer);
+
void requestComplete(DRM::AtomicRequest *request);
DRM::Device dev_;
@@ -61,6 +68,11 @@ private:
libcamera::PixelFormat format_;
libcamera::Size size_;
unsigned int stride_;
+ std::optional<unsigned int> colorEncoding_;
+ std::optional<unsigned int> colorRange_;
+
+ libcamera::Rectangle src_;
+ libcamera::Rectangle dst_;
std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;
diff --git a/src/cam/main.cpp b/src/apps/cam/main.cpp
index 79875ed7..1aabee01 100644
--- a/src/cam/main.cpp
+++ b/src/apps/cam/main.cpp
@@ -14,11 +14,12 @@
#include <libcamera/libcamera.h>
#include <libcamera/property_ids.h>
+#include "../common/event_loop.h"
+#include "../common/options.h"
+#include "../common/stream_options.h"
+
#include "camera_session.h"
-#include "event_loop.h"
#include "main.h"
-#include "options.h"
-#include "stream_options.h"
using namespace libcamera;
@@ -132,6 +133,11 @@ int CamApp::parseOptions(int argc, char *argv[])
"Capture until interrupted by user or until <count> frames captured",
"capture", ArgumentOptional, "count", false,
OptCamera);
+
+ parser.addOption(OptOrientation, OptionString,
+ "Desired image orientation (rot0, rot180, mirror, flip)",
+ "orientation", ArgumentRequired, "orientation", false,
+ OptCamera);
#ifdef HAVE_KMS
parser.addOption(OptDisplay, OptionString,
"Display viewfinder through DRM/KMS on specified connector",
@@ -144,6 +150,12 @@ int CamApp::parseOptions(int argc, char *argv[])
"to write files, using the default file name. Otherwise it sets the\n"
"full file path and name. The first '#' character in the file name\n"
"is expanded to the camera index, stream name and frame sequence number.\n"
+#ifdef HAVE_TIFF
+ "If the file name ends with '.dng', then the frame will be written to\n"
+ "the output file(s) in DNG format.\n"
+#endif
+ "If the file name ends with '.ppm', then the frame will be written to\n"
+ "the output file(s) in PPM format.\n"
"The default file name is 'frame-#.bin'.",
"file", ArgumentOptional, "filename", false,
OptCamera);
@@ -300,8 +312,9 @@ std::string CamApp::cameraName(const Camera *camera)
* Construct the name from the camera location, model and ID. The model
* is only used if the location isn't present or is set to External.
*/
- if (props.contains(properties::Location)) {
- switch (props.get(properties::Location)) {
+ const auto &location = props.get(properties::Location);
+ if (location) {
+ switch (*location) {
case properties::CameraLocationFront:
addModel = false;
name = "Internal front camera ";
@@ -316,12 +329,14 @@ std::string CamApp::cameraName(const Camera *camera)
}
}
- if (addModel && props.contains(properties::Model)) {
+ if (addModel) {
/*
* If the camera location is not availble use the camera model
* to build the camera name.
*/
- name = "'" + props.get(properties::Model) + "' ";
+ const auto &model = props.get(properties::Model);
+ if (model)
+ name = "'" + *model + "' ";
}
name += "(" + camera->id() + ")";
diff --git a/src/cam/main.h b/src/apps/cam/main.h
index 526aecec..4aa959b3 100644
--- a/src/cam/main.h
+++ b/src/apps/cam/main.h
@@ -17,6 +17,7 @@ enum {
OptList = 'l',
OptListProperties = 'p',
OptMonitor = 'm',
+ OptOrientation = 'o',
OptSDL = 'S',
OptStream = 's',
OptListControls = 256,
diff --git a/src/cam/meson.build b/src/apps/cam/meson.build
index 5957ce14..c70ca3cd 100644
--- a/src/cam/meson.build
+++ b/src/apps/cam/meson.build
@@ -1,8 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libevent = dependency('libevent_pthreads', required : get_option('cam'))
-
-if not libevent.found()
+if opt_cam.disabled() or not libevent.found()
cam_enabled = false
subdir_done()
endif
@@ -12,20 +10,16 @@ cam_enabled = true
cam_sources = files([
'camera_session.cpp',
'capture_script.cpp',
- 'event_loop.cpp',
'file_sink.cpp',
'frame_sink.cpp',
- 'image.cpp',
'main.cpp',
- 'options.cpp',
- 'stream_options.cpp',
])
-cam_cpp_args = []
+cam_cpp_args = [apps_cpp_args]
libdrm = dependency('libdrm', required : false)
+libjpeg = dependency('libjpeg', required : false)
libsdl2 = dependency('SDL2', required : false)
-libsdl2_image = dependency('SDL2_image', required : false)
if libdrm.found()
cam_cpp_args += [ '-DHAVE_KMS' ]
@@ -40,11 +34,11 @@ if libsdl2.found()
cam_sources += files([
'sdl_sink.cpp',
'sdl_texture.cpp',
- 'sdl_texture_yuyv.cpp'
+ 'sdl_texture_yuv.cpp',
])
- if libsdl2_image.found()
- cam_cpp_args += ['-DHAVE_SDL_IMAGE']
+ if libjpeg.found()
+ cam_cpp_args += ['-DHAVE_LIBJPEG']
cam_sources += files([
'sdl_texture_mjpg.cpp'
])
@@ -52,14 +46,17 @@ if libsdl2.found()
endif
cam = executable('cam', cam_sources,
+ link_with : apps_lib,
dependencies : [
libatomic,
libcamera_public,
libdrm,
libevent,
+ libjpeg,
libsdl2,
- libsdl2_image,
+ libtiff,
libyaml,
],
cpp_args : cam_cpp_args,
- install : true)
+ install : true,
+ install_tag : 'bin')
diff --git a/src/cam/sdl_sink.cpp b/src/apps/cam/sdl_sink.cpp
index f8e3e95d..a2f4abc1 100644
--- a/src/cam/sdl_sink.cpp
+++ b/src/apps/cam/sdl_sink.cpp
@@ -19,12 +19,13 @@
#include <libcamera/camera.h>
#include <libcamera/formats.h>
-#include "event_loop.h"
-#include "image.h"
-#ifdef HAVE_SDL_IMAGE
+#include "../common/event_loop.h"
+#include "../common/image.h"
+
+#ifdef HAVE_LIBJPEG
#include "sdl_texture_mjpg.h"
#endif
-#include "sdl_texture_yuyv.h"
+#include "sdl_texture_yuv.h"
using namespace libcamera;
@@ -62,13 +63,18 @@ int SDLSink::configure(const libcamera::CameraConfiguration &config)
rect_.h = cfg.size.height;
switch (cfg.pixelFormat) {
-#ifdef HAVE_SDL_IMAGE
+#ifdef HAVE_LIBJPEG
case libcamera::formats::MJPEG:
texture_ = std::make_unique<SDLTextureMJPG>(rect_);
break;
#endif
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ case libcamera::formats::NV12:
+ texture_ = std::make_unique<SDLTextureNV12>(rect_, cfg.stride);
+ break;
+#endif
case libcamera::formats::YUYV:
- texture_ = std::make_unique<SDLTextureYUYV>(rect_);
+ texture_ = std::make_unique<SDLTextureYUYV>(rect_, cfg.stride);
break;
default:
std::cerr << "Unsupported pixel format "
@@ -185,16 +191,23 @@ void SDLSink::renderBuffer(FrameBuffer *buffer)
{
Image *image = mappedBuffers_[buffer].get();
- /* \todo Implement support for multi-planar formats. */
- const FrameMetadata::Plane &meta = buffer->metadata().planes()[0];
+ std::vector<Span<const uint8_t>> planes;
+ unsigned int i = 0;
- Span<uint8_t> data = image->data(0);
- if (meta.bytesused > data.size())
- std::cerr << "payload size " << meta.bytesused
- << " larger than plane size " << data.size()
- << std::endl;
+ planes.reserve(buffer->metadata().planes().size());
+
+ for (const FrameMetadata::Plane &meta : buffer->metadata().planes()) {
+ Span<uint8_t> data = image->data(i);
+ if (meta.bytesused > data.size())
+ std::cerr << "payload size " << meta.bytesused
+ << " larger than plane size " << data.size()
+ << std::endl;
+
+ planes.push_back(data);
+ i++;
+ }
- texture_->update(data);
+ texture_->update(planes);
SDL_RenderClear(renderer_);
SDL_RenderCopy(renderer_, texture_->get(), nullptr, nullptr);
diff --git a/src/cam/sdl_sink.h b/src/apps/cam/sdl_sink.h
index 6c19c663..6c19c663 100644
--- a/src/cam/sdl_sink.h
+++ b/src/apps/cam/sdl_sink.h
diff --git a/src/cam/sdl_texture.cpp b/src/apps/cam/sdl_texture.cpp
index 2ca2add2..e9040bc5 100644
--- a/src/cam/sdl_texture.cpp
+++ b/src/apps/cam/sdl_texture.cpp
@@ -9,9 +9,9 @@
#include <iostream>
-SDLTexture::SDLTexture(const SDL_Rect &rect, SDL_PixelFormatEnum pixelFormat,
- const int pitch)
- : ptr_(nullptr), rect_(rect), pixelFormat_(pixelFormat), pitch_(pitch)
+SDLTexture::SDLTexture(const SDL_Rect &rect, uint32_t pixelFormat,
+ const int stride)
+ : ptr_(nullptr), rect_(rect), pixelFormat_(pixelFormat), stride_(stride)
{
}
diff --git a/src/cam/sdl_texture.h b/src/apps/cam/sdl_texture.h
index 90974798..3993dd46 100644
--- a/src/cam/sdl_texture.h
+++ b/src/apps/cam/sdl_texture.h
@@ -7,23 +7,24 @@
#pragma once
+#include <vector>
+
#include <SDL2/SDL.h>
-#include "image.h"
+#include "../common/image.h"
class SDLTexture
{
public:
- SDLTexture(const SDL_Rect &rect, SDL_PixelFormatEnum pixelFormat,
- const int pitch);
+ SDLTexture(const SDL_Rect &rect, uint32_t pixelFormat, const int stride);
virtual ~SDLTexture();
int create(SDL_Renderer *renderer);
- virtual void update(const libcamera::Span<uint8_t> &data) = 0;
+ virtual void update(const std::vector<libcamera::Span<const uint8_t>> &data) = 0;
SDL_Texture *get() const { return ptr_; }
protected:
SDL_Texture *ptr_;
const SDL_Rect rect_;
- const SDL_PixelFormatEnum pixelFormat_;
- const int pitch_;
+ const uint32_t pixelFormat_;
+ const int stride_;
};
diff --git a/src/apps/cam/sdl_texture_mjpg.cpp b/src/apps/cam/sdl_texture_mjpg.cpp
new file mode 100644
index 00000000..da958e03
--- /dev/null
+++ b/src/apps/cam/sdl_texture_mjpg.cpp
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture_mjpg.cpp - SDL Texture MJPG
+ */
+
+#include "sdl_texture_mjpg.h"
+
+#include <iostream>
+#include <setjmp.h>
+#include <stdio.h>
+
+#include <jpeglib.h>
+
+using namespace libcamera;
+
+struct JpegErrorManager : public jpeg_error_mgr {
+ JpegErrorManager()
+ {
+ jpeg_std_error(this);
+ error_exit = errorExit;
+ output_message = outputMessage;
+ }
+
+ static void errorExit(j_common_ptr cinfo)
+ {
+ JpegErrorManager *self =
+ static_cast<JpegErrorManager *>(cinfo->err);
+ longjmp(self->escape_, 1);
+ }
+
+ static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
+ {
+ }
+
+ jmp_buf escape_;
+};
+
+SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
+ : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
+ rgb_(std::make_unique<unsigned char[]>(stride_ * rect.h))
+{
+}
+
+int SDLTextureMJPG::decompress(Span<const uint8_t> data)
+{
+ struct jpeg_decompress_struct cinfo;
+
+ JpegErrorManager errorManager;
+ if (setjmp(errorManager.escape_)) {
+ /* libjpeg found an error */
+ jpeg_destroy_decompress(&cinfo);
+ std::cerr << "JPEG decompression error" << std::endl;
+ return -EINVAL;
+ }
+
+ cinfo.err = &errorManager;
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_mem_src(&cinfo, data.data(), data.size());
+
+ jpeg_read_header(&cinfo, TRUE);
+
+ jpeg_start_decompress(&cinfo);
+
+ for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
+ JSAMPROW rowptr = rgb_.get() + i * stride_;
+ jpeg_read_scanlines(&cinfo, &rowptr, 1);
+ }
+
+ jpeg_finish_decompress(&cinfo);
+
+ jpeg_destroy_decompress(&cinfo);
+
+ return 0;
+}
+
+void SDLTextureMJPG::update(const std::vector<libcamera::Span<const uint8_t>> &data)
+{
+ decompress(data[0]);
+ SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), stride_);
+}
diff --git a/src/cam/sdl_texture_mjpg.h b/src/apps/cam/sdl_texture_mjpg.h
index b103f801..814ca79a 100644
--- a/src/cam/sdl_texture_mjpg.h
+++ b/src/apps/cam/sdl_texture_mjpg.h
@@ -13,5 +13,11 @@ class SDLTextureMJPG : public SDLTexture
{
public:
SDLTextureMJPG(const SDL_Rect &rect);
- void update(const libcamera::Span<uint8_t> &data) override;
+
+ void update(const std::vector<libcamera::Span<const uint8_t>> &data) override;
+
+private:
+ int decompress(libcamera::Span<const uint8_t> data);
+
+ std::unique_ptr<unsigned char[]> rgb_;
};
diff --git a/src/apps/cam/sdl_texture_yuv.cpp b/src/apps/cam/sdl_texture_yuv.cpp
new file mode 100644
index 00000000..b29c3b93
--- /dev/null
+++ b/src/apps/cam/sdl_texture_yuv.cpp
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture_yuv.cpp - SDL YUV Textures
+ */
+
+#include "sdl_texture_yuv.h"
+
+using namespace libcamera;
+
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+SDLTextureNV12::SDLTextureNV12(const SDL_Rect &rect, unsigned int stride)
+ : SDLTexture(rect, SDL_PIXELFORMAT_NV12, stride)
+{
+}
+
+void SDLTextureNV12::update(const std::vector<libcamera::Span<const uint8_t>> &data)
+{
+ SDL_UpdateNVTexture(ptr_, &rect_, data[0].data(), stride_,
+ data[1].data(), stride_);
+}
+#endif
+
+SDLTextureYUYV::SDLTextureYUYV(const SDL_Rect &rect, unsigned int stride)
+ : SDLTexture(rect, SDL_PIXELFORMAT_YUY2, stride)
+{
+}
+
+void SDLTextureYUYV::update(const std::vector<libcamera::Span<const uint8_t>> &data)
+{
+ SDL_UpdateTexture(ptr_, &rect_, data[0].data(), stride_);
+}
diff --git a/src/apps/cam/sdl_texture_yuv.h b/src/apps/cam/sdl_texture_yuv.h
new file mode 100644
index 00000000..310e4e50
--- /dev/null
+++ b/src/apps/cam/sdl_texture_yuv.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture_yuv.h - SDL YUV Textures
+ */
+
+#pragma once
+
+#include "sdl_texture.h"
+
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+class SDLTextureNV12 : public SDLTexture
+{
+public:
+ SDLTextureNV12(const SDL_Rect &rect, unsigned int stride);
+ void update(const std::vector<libcamera::Span<const uint8_t>> &data) override;
+};
+#endif
+
+class SDLTextureYUYV : public SDLTexture
+{
+public:
+ SDLTextureYUYV(const SDL_Rect &rect, unsigned int stride);
+ void update(const std::vector<libcamera::Span<const uint8_t>> &data) override;
+};
diff --git a/src/qcam/dng_writer.cpp b/src/apps/common/dng_writer.cpp
index 34c8df5a..82bc065a 100644
--- a/src/qcam/dng_writer.cpp
+++ b/src/apps/common/dng_writer.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* dng_writer.cpp - DNG writer
*/
@@ -126,6 +126,14 @@ struct Matrix3d {
float m[9];
};
+void packScanlineSBGGR8(void *output, const void *input, unsigned int width)
+{
+ const uint8_t *in = static_cast<const uint8_t *>(input);
+ uint8_t *out = static_cast<uint8_t *>(output);
+
+ std::copy(in, in + width, out);
+}
+
void packScanlineSBGGR10P(void *output, const void *input, unsigned int width)
{
const uint8_t *in = static_cast<const uint8_t *>(input);
@@ -240,6 +248,7 @@ void thumbScanlineIPU3([[maybe_unused]] const FormatInfo &info, void *output,
uint16_t val1, val2, val3, val4;
switch (pixelInBlock % 4) {
+ default:
case 0:
val1 = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
val2 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
@@ -274,6 +283,30 @@ void thumbScanlineIPU3([[maybe_unused]] const FormatInfo &info, void *output,
}
static const std::map<PixelFormat, FormatInfo> formatInfo = {
+ { formats::SBGGR8, {
+ .bitsPerSample = 8,
+ .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
+ .packScanline = packScanlineSBGGR8,
+ .thumbScanline = thumbScanlineSBGGRxxP,
+ } },
+ { formats::SGBRG8, {
+ .bitsPerSample = 8,
+ .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
+ .packScanline = packScanlineSBGGR8,
+ .thumbScanline = thumbScanlineSBGGRxxP,
+ } },
+ { formats::SGRBG8, {
+ .bitsPerSample = 8,
+ .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
+ .packScanline = packScanlineSBGGR8,
+ .thumbScanline = thumbScanlineSBGGRxxP,
+ } },
+ { formats::SRGGB8, {
+ .bitsPerSample = 8,
+ .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
+ .packScanline = packScanlineSBGGR8,
+ .thumbScanline = thumbScanlineSBGGRxxP,
+ } },
{ formats::SBGGR10_CSI2P, {
.bitsPerSample = 10,
.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
@@ -391,9 +424,9 @@ int DNGWriter::write(const char *filename, const Camera *camera,
TIFFSetField(tif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
TIFFSetField(tif, TIFFTAG_MAKE, "libcamera");
- if (cameraProperties.contains(properties::Model)) {
- std::string model = cameraProperties.get(properties::Model);
- TIFFSetField(tif, TIFFTAG_MODEL, model.c_str());
+ const auto &model = cameraProperties.get(properties::Model);
+ if (model) {
+ TIFFSetField(tif, TIFFTAG_MODEL, model->c_str());
/* \todo set TIFFTAG_UNIQUECAMERAMODEL. */
}
@@ -437,17 +470,18 @@ int DNGWriter::write(const char *filename, const Camera *camera,
*/
const double eps = 1e-2;
- if (metadata.contains(controls::ColourGains)) {
- Span<const float> const &colourGains = metadata.get(controls::ColourGains);
- if (colourGains[0] > eps && colourGains[1] > eps) {
- wbGain = Matrix3d::diag(colourGains[0], 1, colourGains[1]);
- neutral[0] = 1.0 / colourGains[0]; /* red */
- neutral[2] = 1.0 / colourGains[1]; /* blue */
+ const auto &colourGains = metadata.get(controls::ColourGains);
+ if (colourGains) {
+ if ((*colourGains)[0] > eps && (*colourGains)[1] > eps) {
+ wbGain = Matrix3d::diag((*colourGains)[0], 1, (*colourGains)[1]);
+ neutral[0] = 1.0 / (*colourGains)[0]; /* red */
+ neutral[2] = 1.0 / (*colourGains)[1]; /* blue */
}
}
- if (metadata.contains(controls::ColourCorrectionMatrix)) {
- Span<const float> const &coeffs = metadata.get(controls::ColourCorrectionMatrix);
- Matrix3d ccmSupplied(coeffs);
+
+ const auto &ccmControl = metadata.get(controls::ColourCorrectionMatrix);
+ if (ccmControl) {
+ Matrix3d ccmSupplied(*ccmControl);
if (ccmSupplied.determinant() > eps)
ccm = ccmSupplied;
}
@@ -506,7 +540,10 @@ int DNGWriter::write(const char *filename, const Camera *camera,
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfaRepeatPatternDim);
- TIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern);
+ if (TIFFLIB_VERSION < 20201219)
+ TIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern);
+ else
+ TIFFSetField(tif, TIFFTAG_CFAPATTERN, 4, info->pattern);
TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, cfaPlaneColor);
TIFFSetField(tif, TIFFTAG_CFALAYOUT, 1);
@@ -514,8 +551,9 @@ int DNGWriter::write(const char *filename, const Camera *camera,
float blackLevel[] = { 0.0f, 0.0f, 0.0f, 0.0f };
uint32_t whiteLevel = (1 << info->bitsPerSample) - 1;
- if (metadata.contains(controls::SensorBlackLevels)) {
- Span<const int32_t> levels = metadata.get(controls::SensorBlackLevels);
+ const auto &blackLevels = metadata.get(controls::SensorBlackLevels);
+ if (blackLevels) {
+ Span<const int32_t, 4> levels = *blackLevels;
/*
* The black levels control is specified in R, Gr, Gb, B order.
@@ -592,16 +630,15 @@ int DNGWriter::write(const char *filename, const Camera *camera,
TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, strTime);
TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, strTime);
- if (metadata.contains(controls::AnalogueGain)) {
- float gain = metadata.get(controls::AnalogueGain);
- uint16_t iso = std::min(std::max(gain * 100, 0.0f), 65535.0f);
+ const auto &analogGain = metadata.get(controls::AnalogueGain);
+ if (analogGain) {
+ uint16_t iso = std::min(std::max(*analogGain * 100, 0.0f), 65535.0f);
TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, &iso);
}
- if (metadata.contains(controls::ExposureTime)) {
- float exposureTime = metadata.get(controls::ExposureTime) / 1e6;
- TIFFSetField(tif, EXIFTAG_EXPOSURETIME, exposureTime);
- }
+ const auto &exposureTime = metadata.get(controls::ExposureTime);
+ if (exposureTime)
+ TIFFSetField(tif, EXIFTAG_EXPOSURETIME, *exposureTime / 1e6);
TIFFWriteCustomDirectory(tif, &exifIFDOffset);
diff --git a/src/qcam/dng_writer.h b/src/apps/common/dng_writer.h
index c044bf8b..38f38f62 100644
--- a/src/qcam/dng_writer.h
+++ b/src/apps/common/dng_writer.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* dng_writer.h - DNG writer
*/
diff --git a/src/cam/event_loop.cpp b/src/apps/common/event_loop.cpp
index cb83845c..cb83845c 100644
--- a/src/cam/event_loop.cpp
+++ b/src/apps/common/event_loop.cpp
diff --git a/src/cam/event_loop.h b/src/apps/common/event_loop.h
index ef79e8e5..ef79e8e5 100644
--- a/src/cam/event_loop.h
+++ b/src/apps/common/event_loop.h
diff --git a/src/cam/image.cpp b/src/apps/common/image.cpp
index fe2cc6da..fe2cc6da 100644
--- a/src/cam/image.cpp
+++ b/src/apps/common/image.cpp
diff --git a/src/cam/image.h b/src/apps/common/image.h
index 7953b177..7953b177 100644
--- a/src/cam/image.h
+++ b/src/apps/common/image.h
diff --git a/src/apps/common/meson.build b/src/apps/common/meson.build
new file mode 100644
index 00000000..5b683390
--- /dev/null
+++ b/src/apps/common/meson.build
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: CC0-1.0
+
+apps_sources = files([
+ 'image.cpp',
+ 'options.cpp',
+ 'ppm_writer.cpp',
+ 'stream_options.cpp',
+])
+
+apps_cpp_args = []
+
+if libevent.found()
+ apps_sources += files([
+ 'event_loop.cpp',
+ ])
+endif
+
+if libtiff.found()
+ apps_cpp_args += ['-DHAVE_TIFF']
+ apps_sources += files([
+ 'dng_writer.cpp',
+ ])
+endif
+
+apps_lib = static_library('apps', apps_sources,
+ cpp_args : apps_cpp_args,
+ dependencies : [libcamera_public])
diff --git a/src/cam/options.cpp b/src/apps/common/options.cpp
index 4f7e8691..4f7e8691 100644
--- a/src/cam/options.cpp
+++ b/src/apps/common/options.cpp
diff --git a/src/cam/options.h b/src/apps/common/options.h
index 4ddd4987..4ddd4987 100644
--- a/src/cam/options.h
+++ b/src/apps/common/options.h
diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp
new file mode 100644
index 00000000..a8ccf67a
--- /dev/null
+++ b/src/apps/common/ppm_writer.cpp
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * ppm_writer.cpp - PPM writer
+ */
+
+#include "ppm_writer.h"
+
+#include <fstream>
+#include <iostream>
+
+#include <libcamera/formats.h>
+#include <libcamera/pixel_format.h>
+
+using namespace libcamera;
+
+int PPMWriter::write(const char *filename,
+ const StreamConfiguration &config,
+ const Span<uint8_t> &data)
+{
+ if (config.pixelFormat != formats::BGR888) {
+ std::cerr << "Only BGR888 output pixel format is supported ("
+ << config.pixelFormat << " requested)" << std::endl;
+ return -EINVAL;
+ }
+
+ std::ofstream output(filename, std::ios::binary);
+ if (!output) {
+ std::cerr << "Failed to open ppm file: " << filename << std::endl;
+ return -EINVAL;
+ }
+
+ output << "P6" << std::endl
+ << config.size.width << " " << config.size.height << std::endl
+ << "255" << std::endl;
+ if (!output) {
+ std::cerr << "Failed to write the file header" << std::endl;
+ return -EINVAL;
+ }
+
+ const unsigned int rowLength = config.size.width * 3;
+ const char *row = reinterpret_cast<const char *>(data.data());
+ for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
+ output.write(row, rowLength);
+ if (!output) {
+ std::cerr << "Failed to write image data at row " << y << std::endl;
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/apps/common/ppm_writer.h b/src/apps/common/ppm_writer.h
new file mode 100644
index 00000000..4c38f5ce
--- /dev/null
+++ b/src/apps/common/ppm_writer.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat, Inc.
+ *
+ * ppm_writer.h - PPM writer
+ */
+
+#pragma once
+
+#include <libcamera/base/span.h>
+
+#include <libcamera/stream.h>
+
+class PPMWriter
+{
+public:
+ static int write(const char *filename,
+ const libcamera::StreamConfiguration &config,
+ const libcamera::Span<uint8_t> &data);
+};
diff --git a/src/cam/stream_options.cpp b/src/apps/common/stream_options.cpp
index 150bd27c..663b979a 100644
--- a/src/cam/stream_options.cpp
+++ b/src/apps/common/stream_options.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* stream_options.cpp - Helper to parse options for streams
*/
@@ -8,6 +8,8 @@
#include <iostream>
+#include <libcamera/color_space.h>
+
using namespace libcamera;
StreamKeyValueParser::StreamKeyValueParser()
@@ -21,15 +23,15 @@ StreamKeyValueParser::StreamKeyValueParser()
ArgumentRequired);
addOption("pixelformat", OptionString, "Pixel format name",
ArgumentRequired);
+ addOption("colorspace", OptionString, "Color space",
+ ArgumentRequired);
}
KeyValueParser::Options StreamKeyValueParser::parse(const char *arguments)
{
KeyValueParser::Options options = KeyValueParser::parse(arguments);
- StreamRole role;
- if (options.valid() && options.isSet("role") &&
- !parseRole(&role, options)) {
+ if (options.valid() && options.isSet("role") && !parseRole(options)) {
std::cerr << "Unknown stream role "
<< options["role"].toString() << std::endl;
options.invalidate();
@@ -38,7 +40,7 @@ KeyValueParser::Options StreamKeyValueParser::parse(const char *arguments)
return options;
}
-StreamRoles StreamKeyValueParser::roles(const OptionValue &values)
+std::vector<StreamRole> StreamKeyValueParser::roles(const OptionValue &values)
{
/* If no configuration values to examine default to viewfinder. */
if (values.empty())
@@ -46,15 +48,10 @@ StreamRoles StreamKeyValueParser::roles(const OptionValue &values)
const std::vector<OptionValue> &streamParameters = values.toArray();
- StreamRoles roles;
+ std::vector<StreamRole> roles;
for (auto const &value : streamParameters) {
- StreamRole role;
-
- /* If role is invalid or not set default to viewfinder. */
- if (!parseRole(&role, value.toKeyValues()))
- role = StreamRole::Viewfinder;
-
- roles.push_back(role);
+ /* If a role is invalid default it to viewfinder. */
+ roles.push_back(parseRole(value.toKeyValues()).value_or(StreamRole::Viewfinder));
}
return roles;
@@ -96,32 +93,29 @@ int StreamKeyValueParser::updateConfiguration(CameraConfiguration *config,
if (opts.isSet("pixelformat"))
cfg.pixelFormat = PixelFormat::fromString(opts["pixelformat"].toString());
+
+ if (opts.isSet("colorspace"))
+ cfg.colorSpace = ColorSpace::fromString(opts["colorspace"].toString());
}
return 0;
}
-bool StreamKeyValueParser::parseRole(StreamRole *role,
- const KeyValueParser::Options &options)
+std::optional<libcamera::StreamRole> StreamKeyValueParser::parseRole(const KeyValueParser::Options &options)
{
if (!options.isSet("role"))
- return false;
+ return {};
std::string name = options["role"].toString();
- if (name == "viewfinder") {
- *role = StreamRole::Viewfinder;
- return true;
- } else if (name == "video") {
- *role = StreamRole::VideoRecording;
- return true;
- } else if (name == "still") {
- *role = StreamRole::StillCapture;
- return true;
- } else if (name == "raw") {
- *role = StreamRole::Raw;
- return true;
- }
+ if (name == "viewfinder")
+ return StreamRole::Viewfinder;
+ else if (name == "video")
+ return StreamRole::VideoRecording;
+ else if (name == "still")
+ return StreamRole::StillCapture;
+ else if (name == "raw")
+ return StreamRole::Raw;
- return false;
+ return {};
}
diff --git a/src/cam/stream_options.h b/src/apps/common/stream_options.h
index d235b77f..a5f3bde0 100644
--- a/src/cam/stream_options.h
+++ b/src/apps/common/stream_options.h
@@ -1,12 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* stream_options.h - Helper to parse options for streams
*/
#pragma once
+#include <optional>
+
#include <libcamera/camera.h>
#include "options.h"
@@ -18,11 +20,10 @@ public:
KeyValueParser::Options parse(const char *arguments) override;
- static libcamera::StreamRoles roles(const OptionValue &values);
+ static std::vector<libcamera::StreamRole> roles(const OptionValue &values);
static int updateConfiguration(libcamera::CameraConfiguration *config,
const OptionValue &values);
private:
- static bool parseRole(libcamera::StreamRole *role,
- const KeyValueParser::Options &options);
+ static std::optional<libcamera::StreamRole> parseRole(const KeyValueParser::Options &options);
};
diff --git a/src/apps/ipa-verify/main.cpp b/src/apps/ipa-verify/main.cpp
new file mode 100644
index 00000000..76ba5073
--- /dev/null
+++ b/src/apps/ipa-verify/main.cpp
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Ideas on Board Oy
+ *
+ * ipa_verify.cpp - Verify signature on an IPA module
+ */
+
+#include <iostream>
+#include <libgen.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/span.h>
+
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/ipa_module.h"
+
+using namespace libcamera;
+
+namespace {
+
+bool isSignatureValid(IPAModule *ipa)
+{
+ File file{ ipa->path() };
+ if (!file.open(File::OpenModeFlag::ReadOnly))
+ return false;
+
+ Span<uint8_t> data = file.map();
+ if (data.empty())
+ return false;
+
+ return IPAManager::pubKey().verify(data, ipa->signature());
+}
+
+void usage(char *argv0)
+{
+ std::cout << "Usage: " << basename(argv0) << " ipa_name.so" << std::endl;
+ std::cout << std::endl;
+ std::cout << "Verify the signature of an IPA module. The signature file ipa_name.so.sign is" << std::endl;
+ std::cout << "expected to be in the same directory as the IPA module." << std::endl;
+}
+
+} /* namespace */
+
+int main(int argc, char **argv)
+{
+ if (argc != 2) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ IPAModule module{ argv[1] };
+ if (!module.isValid()) {
+ std::cout << "Invalid IPA module " << argv[1] << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ if (!isSignatureValid(&module)) {
+ std::cout << "IPA module signature is invalid" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ std::cout << "IPA module signature is valid" << std::endl;
+ return 0;
+}
diff --git a/src/apps/ipa-verify/meson.build b/src/apps/ipa-verify/meson.build
new file mode 100644
index 00000000..7fdda3b9
--- /dev/null
+++ b/src/apps/ipa-verify/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: CC0-1.0
+
+if not ipa_sign_module
+ subdir_done()
+endif
+
+ipa_verify_sources = files([
+ 'main.cpp',
+])
+
+ipa_verify = executable('ipa_verify', ipa_verify_sources,
+ dependencies : [
+ libcamera_private,
+ ],
+ install : false)
diff --git a/src/lc-compliance/environment.cpp b/src/apps/lc-compliance/environment.cpp
index 5eb3775f..5eb3775f 100644
--- a/src/lc-compliance/environment.cpp
+++ b/src/apps/lc-compliance/environment.cpp
diff --git a/src/lc-compliance/environment.h b/src/apps/lc-compliance/environment.h
index 0debbcce..0debbcce 100644
--- a/src/lc-compliance/environment.h
+++ b/src/apps/lc-compliance/environment.h
diff --git a/src/lc-compliance/simple_capture.cpp b/src/apps/lc-compliance/helpers/capture.cpp
index ab5cb35c..5aab973f 100644
--- a/src/lc-compliance/simple_capture.cpp
+++ b/src/apps/lc-compliance/helpers/capture.cpp
@@ -5,24 +5,24 @@
* simple_capture.cpp - Simple capture helper
*/
-#include <gtest/gtest.h>
+#include "capture.h"
-#include "simple_capture.h"
+#include <gtest/gtest.h>
using namespace libcamera;
-SimpleCapture::SimpleCapture(std::shared_ptr<Camera> camera)
+Capture::Capture(std::shared_ptr<Camera> camera)
: loop_(nullptr), camera_(camera),
allocator_(std::make_unique<FrameBufferAllocator>(camera))
{
}
-SimpleCapture::~SimpleCapture()
+Capture::~Capture()
{
stop();
}
-void SimpleCapture::configure(StreamRole role)
+void Capture::configure(StreamRole role)
{
config_ = camera_->generateConfiguration({ role });
@@ -42,7 +42,7 @@ void SimpleCapture::configure(StreamRole role)
}
}
-void SimpleCapture::start()
+void Capture::start()
{
Stream *stream = config_->at(0).stream();
int count = allocator_->allocate(stream);
@@ -50,12 +50,12 @@ void SimpleCapture::start()
ASSERT_GE(count, 0) << "Failed to allocate buffers";
EXPECT_EQ(count, config_->at(0).bufferCount) << "Allocated less buffers than expected";
- camera_->requestCompleted.connect(this, &SimpleCapture::requestComplete);
+ camera_->requestCompleted.connect(this, &Capture::requestComplete);
ASSERT_EQ(camera_->start(), 0) << "Failed to start camera";
}
-void SimpleCapture::stop()
+void Capture::stop()
{
if (!config_ || !allocator_->allocated())
return;
@@ -65,17 +65,18 @@ void SimpleCapture::stop()
camera_->requestCompleted.disconnect(this);
Stream *stream = config_->at(0).stream();
+ requests_.clear();
allocator_->free(stream);
}
-/* SimpleCaptureBalanced */
+/* CaptureBalanced */
-SimpleCaptureBalanced::SimpleCaptureBalanced(std::shared_ptr<Camera> camera)
- : SimpleCapture(camera)
+CaptureBalanced::CaptureBalanced(std::shared_ptr<Camera> camera)
+ : Capture(camera)
{
}
-void SimpleCaptureBalanced::capture(unsigned int numRequests)
+void CaptureBalanced::capture(unsigned int numRequests)
{
start();
@@ -94,8 +95,7 @@ void SimpleCaptureBalanced::capture(unsigned int numRequests)
captureCount_ = 0;
captureLimit_ = numRequests;
- /* Queue the recommended number of reqeuests. */
- std::vector<std::unique_ptr<libcamera::Request>> requests;
+ /* Queue the recommended number of requests. */
for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
std::unique_ptr<Request> request = camera_->createRequest();
ASSERT_TRUE(request) << "Can't create request";
@@ -104,7 +104,7 @@ void SimpleCaptureBalanced::capture(unsigned int numRequests)
ASSERT_EQ(queueRequest(request.get()), 0) << "Failed to queue request";
- requests.push_back(std::move(request));
+ requests_.push_back(std::move(request));
}
/* Run capture session. */
@@ -116,7 +116,7 @@ void SimpleCaptureBalanced::capture(unsigned int numRequests)
ASSERT_EQ(captureCount_, captureLimit_);
}
-int SimpleCaptureBalanced::queueRequest(Request *request)
+int CaptureBalanced::queueRequest(Request *request)
{
queueCount_++;
if (queueCount_ > captureLimit_)
@@ -125,8 +125,11 @@ int SimpleCaptureBalanced::queueRequest(Request *request)
return camera_->queueRequest(request);
}
-void SimpleCaptureBalanced::requestComplete(Request *request)
+void CaptureBalanced::requestComplete(Request *request)
{
+ EXPECT_EQ(request->status(), Request::Status::RequestComplete)
+ << "Request didn't complete successfully";
+
captureCount_++;
if (captureCount_ >= captureLimit_) {
loop_->exit(0);
@@ -138,14 +141,14 @@ void SimpleCaptureBalanced::requestComplete(Request *request)
loop_->exit(-EINVAL);
}
-/* SimpleCaptureUnbalanced */
+/* CaptureUnbalanced */
-SimpleCaptureUnbalanced::SimpleCaptureUnbalanced(std::shared_ptr<Camera> camera)
- : SimpleCapture(camera)
+CaptureUnbalanced::CaptureUnbalanced(std::shared_ptr<Camera> camera)
+ : Capture(camera)
{
}
-void SimpleCaptureUnbalanced::capture(unsigned int numRequests)
+void CaptureUnbalanced::capture(unsigned int numRequests)
{
start();
@@ -155,8 +158,7 @@ void SimpleCaptureUnbalanced::capture(unsigned int numRequests)
captureCount_ = 0;
captureLimit_ = numRequests;
- /* Queue the recommended number of reqeuests. */
- std::vector<std::unique_ptr<libcamera::Request>> requests;
+ /* Queue the recommended number of requests. */
for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
std::unique_ptr<Request> request = camera_->createRequest();
ASSERT_TRUE(request) << "Can't create request";
@@ -165,7 +167,7 @@ void SimpleCaptureUnbalanced::capture(unsigned int numRequests)
ASSERT_EQ(camera_->queueRequest(request.get()), 0) << "Failed to queue request";
- requests.push_back(std::move(request));
+ requests_.push_back(std::move(request));
}
/* Run capture session. */
@@ -177,7 +179,7 @@ void SimpleCaptureUnbalanced::capture(unsigned int numRequests)
ASSERT_EQ(status, 0);
}
-void SimpleCaptureUnbalanced::requestComplete(Request *request)
+void CaptureUnbalanced::requestComplete(Request *request)
{
captureCount_++;
if (captureCount_ >= captureLimit_) {
@@ -185,6 +187,9 @@ void SimpleCaptureUnbalanced::requestComplete(Request *request)
return;
}
+ EXPECT_EQ(request->status(), Request::Status::RequestComplete)
+ << "Request didn't complete successfully";
+
request->reuse(Request::ReuseBuffers);
if (camera_->queueRequest(request))
loop_->exit(-EINVAL);
diff --git a/src/lc-compliance/simple_capture.h b/src/apps/lc-compliance/helpers/capture.h
index 9d31f7cb..0574ab1c 100644
--- a/src/lc-compliance/simple_capture.h
+++ b/src/apps/lc-compliance/helpers/capture.h
@@ -11,16 +11,16 @@
#include <libcamera/libcamera.h>
-#include "../cam/event_loop.h"
+#include "../common/event_loop.h"
-class SimpleCapture
+class Capture
{
public:
void configure(libcamera::StreamRole role);
protected:
- SimpleCapture(std::shared_ptr<libcamera::Camera> camera);
- virtual ~SimpleCapture();
+ Capture(std::shared_ptr<libcamera::Camera> camera);
+ virtual ~Capture();
void start();
void stop();
@@ -32,12 +32,13 @@ protected:
std::shared_ptr<libcamera::Camera> camera_;
std::unique_ptr<libcamera::FrameBufferAllocator> allocator_;
std::unique_ptr<libcamera::CameraConfiguration> config_;
+ std::vector<std::unique_ptr<libcamera::Request>> requests_;
};
-class SimpleCaptureBalanced : public SimpleCapture
+class CaptureBalanced : public Capture
{
public:
- SimpleCaptureBalanced(std::shared_ptr<libcamera::Camera> camera);
+ CaptureBalanced(std::shared_ptr<libcamera::Camera> camera);
void capture(unsigned int numRequests);
@@ -50,10 +51,10 @@ private:
unsigned int captureLimit_;
};
-class SimpleCaptureUnbalanced : public SimpleCapture
+class CaptureUnbalanced : public Capture
{
public:
- SimpleCaptureUnbalanced(std::shared_ptr<libcamera::Camera> camera);
+ CaptureUnbalanced(std::shared_ptr<libcamera::Camera> camera);
void capture(unsigned int numRequests);
diff --git a/src/lc-compliance/main.cpp b/src/apps/lc-compliance/main.cpp
index 7eb52ae4..74e0d4df 100644
--- a/src/lc-compliance/main.cpp
+++ b/src/apps/lc-compliance/main.cpp
@@ -14,8 +14,9 @@
#include <libcamera/libcamera.h>
+#include "../common/options.h"
+
#include "environment.h"
-#include "../cam/options.h"
using namespace libcamera;
diff --git a/src/lc-compliance/meson.build b/src/apps/lc-compliance/meson.build
index 8b57474b..b1f605f3 100644
--- a/src/lc-compliance/meson.build
+++ b/src/apps/lc-compliance/meson.build
@@ -1,10 +1,10 @@
# SPDX-License-Identifier: CC0-1.0
-libevent = dependency('libevent_pthreads', required : get_option('lc-compliance'))
-libgtest = dependency('gtest', required : get_option('lc-compliance'),
+libgtest = dependency('gtest', version : '>=1.10.0',
+ required : get_option('lc-compliance'),
fallback : ['gtest', 'gtest_dep'])
-if not (libevent.found() and libgtest.found())
+if opt_lc_compliance.disabled() or not libevent.found() or not libgtest.found()
lc_compliance_enabled = false
subdir_done()
endif
@@ -12,20 +12,26 @@ endif
lc_compliance_enabled = true
lc_compliance_sources = files([
- '../cam/event_loop.cpp',
- '../cam/options.cpp',
'environment.cpp',
+ 'helpers/capture.cpp',
'main.cpp',
- 'simple_capture.cpp',
- 'capture_test.cpp',
+ 'tests/capture_test.cpp',
+])
+
+lc_compliance_includes = ([
+ include_directories('.'),
+ include_directories('helpers/')
])
lc_compliance = executable('lc-compliance', lc_compliance_sources,
cpp_args : [ '-fexceptions' ],
+ link_with : apps_lib,
dependencies : [
libatomic,
libcamera_public,
libevent,
libgtest,
],
- install : true)
+ include_directories : lc_compliance_includes,
+ install : true,
+ install_tag : 'bin-devel')
diff --git a/src/lc-compliance/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp
index 52578207..284d3630 100644
--- a/src/lc-compliance/capture_test.cpp
+++ b/src/apps/lc-compliance/tests/capture_test.cpp
@@ -6,17 +6,23 @@
* capture_test.cpp - Test camera capture
*/
+#include "capture.h"
+
#include <iostream>
#include <gtest/gtest.h>
#include "environment.h"
-#include "simple_capture.h"
using namespace libcamera;
const std::vector<int> NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
-const std::vector<StreamRole> ROLES = { Raw, StillCapture, VideoRecording, Viewfinder };
+const std::vector<StreamRole> ROLES = {
+ StreamRole::Raw,
+ StreamRole::StillCapture,
+ StreamRole::VideoRecording,
+ StreamRole::Viewfinder
+};
class SingleStream : public testing::TestWithParam<std::tuple<StreamRole, int>>
{
@@ -54,10 +60,12 @@ void SingleStream::TearDown()
std::string SingleStream::nameParameters(const testing::TestParamInfo<SingleStream::ParamType> &info)
{
- std::map<StreamRole, std::string> rolesMap = { { Raw, "Raw" },
- { StillCapture, "StillCapture" },
- { VideoRecording, "VideoRecording" },
- { Viewfinder, "Viewfinder" } };
+ std::map<StreamRole, std::string> rolesMap = {
+ { StreamRole::Raw, "Raw" },
+ { StreamRole::StillCapture, "StillCapture" },
+ { StreamRole::VideoRecording, "VideoRecording" },
+ { StreamRole::Viewfinder, "Viewfinder" }
+ };
std::string roleName = rolesMap[std::get<0>(info.param)];
std::string numRequestsName = std::to_string(std::get<1>(info.param));
@@ -76,7 +84,7 @@ TEST_P(SingleStream, Capture)
{
auto [role, numRequests] = GetParam();
- SimpleCaptureBalanced capture(camera_);
+ CaptureBalanced capture(camera_);
capture.configure(role);
@@ -95,7 +103,7 @@ TEST_P(SingleStream, CaptureStartStop)
auto [role, numRequests] = GetParam();
unsigned int numRepeats = 3;
- SimpleCaptureBalanced capture(camera_);
+ CaptureBalanced capture(camera_);
capture.configure(role);
@@ -114,7 +122,7 @@ TEST_P(SingleStream, UnbalancedStop)
{
auto [role, numRequests] = GetParam();
- SimpleCaptureUnbalanced capture(camera_);
+ CaptureUnbalanced capture(camera_);
capture.configure(role);
diff --git a/src/apps/meson.build b/src/apps/meson.build
new file mode 100644
index 00000000..af632b9a
--- /dev/null
+++ b/src/apps/meson.build
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: CC0-1.0
+
+opt_cam = get_option('cam')
+opt_lc_compliance = get_option('lc-compliance')
+
+# libevent is needed by cam and lc-compliance. As they are both feature options,
+# they can't be combined with simple boolean logic.
+libevent = dependency('libevent_pthreads', required : opt_cam)
+if not libevent.found()
+ libevent = dependency('libevent_pthreads', required : opt_lc_compliance)
+endif
+
+libtiff = dependency('libtiff-4', required : false)
+
+subdir('common')
+
+subdir('lc-compliance')
+
+subdir('cam')
+subdir('qcam')
+
+subdir('ipa-verify')
diff --git a/src/qcam/assets/feathericons/activity.svg b/src/apps/qcam/assets/feathericons/activity.svg
index 669a57a7..669a57a7 100644
--- a/src/qcam/assets/feathericons/activity.svg
+++ b/src/apps/qcam/assets/feathericons/activity.svg
diff --git a/src/qcam/assets/feathericons/airplay.svg b/src/apps/qcam/assets/feathericons/airplay.svg
index 7ce73022..7ce73022 100644
--- a/src/qcam/assets/feathericons/airplay.svg
+++ b/src/apps/qcam/assets/feathericons/airplay.svg
diff --git a/src/qcam/assets/feathericons/alert-circle.svg b/src/apps/qcam/assets/feathericons/alert-circle.svg
index 8d02b7d1..8d02b7d1 100644
--- a/src/qcam/assets/feathericons/alert-circle.svg
+++ b/src/apps/qcam/assets/feathericons/alert-circle.svg
diff --git a/src/qcam/assets/feathericons/alert-octagon.svg b/src/apps/qcam/assets/feathericons/alert-octagon.svg
index de9b03f2..de9b03f2 100644
--- a/src/qcam/assets/feathericons/alert-octagon.svg
+++ b/src/apps/qcam/assets/feathericons/alert-octagon.svg
diff --git a/src/qcam/assets/feathericons/alert-triangle.svg b/src/apps/qcam/assets/feathericons/alert-triangle.svg
index 6dcb0963..6dcb0963 100644
--- a/src/qcam/assets/feathericons/alert-triangle.svg
+++ b/src/apps/qcam/assets/feathericons/alert-triangle.svg
diff --git a/src/qcam/assets/feathericons/align-center.svg b/src/apps/qcam/assets/feathericons/align-center.svg
index 5b8842ea..5b8842ea 100644
--- a/src/qcam/assets/feathericons/align-center.svg
+++ b/src/apps/qcam/assets/feathericons/align-center.svg
diff --git a/src/qcam/assets/feathericons/align-justify.svg b/src/apps/qcam/assets/feathericons/align-justify.svg
index 0539876f..0539876f 100644
--- a/src/qcam/assets/feathericons/align-justify.svg
+++ b/src/apps/qcam/assets/feathericons/align-justify.svg
diff --git a/src/qcam/assets/feathericons/align-left.svg b/src/apps/qcam/assets/feathericons/align-left.svg
index 9ac852a5..9ac852a5 100644
--- a/src/qcam/assets/feathericons/align-left.svg
+++ b/src/apps/qcam/assets/feathericons/align-left.svg
diff --git a/src/qcam/assets/feathericons/align-right.svg b/src/apps/qcam/assets/feathericons/align-right.svg
index ef139ffa..ef139ffa 100644
--- a/src/qcam/assets/feathericons/align-right.svg
+++ b/src/apps/qcam/assets/feathericons/align-right.svg
diff --git a/src/qcam/assets/feathericons/anchor.svg b/src/apps/qcam/assets/feathericons/anchor.svg
index e01627a3..e01627a3 100644
--- a/src/qcam/assets/feathericons/anchor.svg
+++ b/src/apps/qcam/assets/feathericons/anchor.svg
diff --git a/src/qcam/assets/feathericons/aperture.svg b/src/apps/qcam/assets/feathericons/aperture.svg
index 9936e868..9936e868 100644
--- a/src/qcam/assets/feathericons/aperture.svg
+++ b/src/apps/qcam/assets/feathericons/aperture.svg
diff --git a/src/qcam/assets/feathericons/archive.svg b/src/apps/qcam/assets/feathericons/archive.svg
index 428882c8..428882c8 100644
--- a/src/qcam/assets/feathericons/archive.svg
+++ b/src/apps/qcam/assets/feathericons/archive.svg
diff --git a/src/qcam/assets/feathericons/arrow-down-circle.svg b/src/apps/qcam/assets/feathericons/arrow-down-circle.svg
index 3238091b..3238091b 100644
--- a/src/qcam/assets/feathericons/arrow-down-circle.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-down-circle.svg
diff --git a/src/qcam/assets/feathericons/arrow-down-left.svg b/src/apps/qcam/assets/feathericons/arrow-down-left.svg
index 72483584..72483584 100644
--- a/src/qcam/assets/feathericons/arrow-down-left.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-down-left.svg
diff --git a/src/qcam/assets/feathericons/arrow-down-right.svg b/src/apps/qcam/assets/feathericons/arrow-down-right.svg
index 81d9822b..81d9822b 100644
--- a/src/qcam/assets/feathericons/arrow-down-right.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-down-right.svg
diff --git a/src/qcam/assets/feathericons/arrow-down.svg b/src/apps/qcam/assets/feathericons/arrow-down.svg
index 4f84f627..4f84f627 100644
--- a/src/qcam/assets/feathericons/arrow-down.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-down.svg
diff --git a/src/qcam/assets/feathericons/arrow-left-circle.svg b/src/apps/qcam/assets/feathericons/arrow-left-circle.svg
index 3b19ff8a..3b19ff8a 100644
--- a/src/qcam/assets/feathericons/arrow-left-circle.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-left-circle.svg
diff --git a/src/qcam/assets/feathericons/arrow-left.svg b/src/apps/qcam/assets/feathericons/arrow-left.svg
index a5058fc7..a5058fc7 100644
--- a/src/qcam/assets/feathericons/arrow-left.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-left.svg
diff --git a/src/qcam/assets/feathericons/arrow-right-circle.svg b/src/apps/qcam/assets/feathericons/arrow-right-circle.svg
index ff01dd58..ff01dd58 100644
--- a/src/qcam/assets/feathericons/arrow-right-circle.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-right-circle.svg
diff --git a/src/qcam/assets/feathericons/arrow-right.svg b/src/apps/qcam/assets/feathericons/arrow-right.svg
index 939b57c5..939b57c5 100644
--- a/src/qcam/assets/feathericons/arrow-right.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-right.svg
diff --git a/src/qcam/assets/feathericons/arrow-up-circle.svg b/src/apps/qcam/assets/feathericons/arrow-up-circle.svg
index 044a75d3..044a75d3 100644
--- a/src/qcam/assets/feathericons/arrow-up-circle.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-up-circle.svg
diff --git a/src/qcam/assets/feathericons/arrow-up-left.svg b/src/apps/qcam/assets/feathericons/arrow-up-left.svg
index cea55e87..cea55e87 100644
--- a/src/qcam/assets/feathericons/arrow-up-left.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-up-left.svg
diff --git a/src/qcam/assets/feathericons/arrow-up-right.svg b/src/apps/qcam/assets/feathericons/arrow-up-right.svg
index 95678e00..95678e00 100644
--- a/src/qcam/assets/feathericons/arrow-up-right.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-up-right.svg
diff --git a/src/qcam/assets/feathericons/arrow-up.svg b/src/apps/qcam/assets/feathericons/arrow-up.svg
index 16b13aba..16b13aba 100644
--- a/src/qcam/assets/feathericons/arrow-up.svg
+++ b/src/apps/qcam/assets/feathericons/arrow-up.svg
diff --git a/src/qcam/assets/feathericons/at-sign.svg b/src/apps/qcam/assets/feathericons/at-sign.svg
index 5a5e5d0d..5a5e5d0d 100644
--- a/src/qcam/assets/feathericons/at-sign.svg
+++ b/src/apps/qcam/assets/feathericons/at-sign.svg
diff --git a/src/qcam/assets/feathericons/award.svg b/src/apps/qcam/assets/feathericons/award.svg
index be70d5a1..be70d5a1 100644
--- a/src/qcam/assets/feathericons/award.svg
+++ b/src/apps/qcam/assets/feathericons/award.svg
diff --git a/src/qcam/assets/feathericons/bar-chart-2.svg b/src/apps/qcam/assets/feathericons/bar-chart-2.svg
index 864167a6..864167a6 100644
--- a/src/qcam/assets/feathericons/bar-chart-2.svg
+++ b/src/apps/qcam/assets/feathericons/bar-chart-2.svg
diff --git a/src/qcam/assets/feathericons/bar-chart.svg b/src/apps/qcam/assets/feathericons/bar-chart.svg
index 074d7c1a..074d7c1a 100644
--- a/src/qcam/assets/feathericons/bar-chart.svg
+++ b/src/apps/qcam/assets/feathericons/bar-chart.svg
diff --git a/src/qcam/assets/feathericons/battery-charging.svg b/src/apps/qcam/assets/feathericons/battery-charging.svg
index 644cb59c..644cb59c 100644
--- a/src/qcam/assets/feathericons/battery-charging.svg
+++ b/src/apps/qcam/assets/feathericons/battery-charging.svg
diff --git a/src/qcam/assets/feathericons/battery.svg b/src/apps/qcam/assets/feathericons/battery.svg
index 7fe87710..7fe87710 100644
--- a/src/qcam/assets/feathericons/battery.svg
+++ b/src/apps/qcam/assets/feathericons/battery.svg
diff --git a/src/qcam/assets/feathericons/bell-off.svg b/src/apps/qcam/assets/feathericons/bell-off.svg
index 4b07c848..4b07c848 100644
--- a/src/qcam/assets/feathericons/bell-off.svg
+++ b/src/apps/qcam/assets/feathericons/bell-off.svg
diff --git a/src/qcam/assets/feathericons/bell.svg b/src/apps/qcam/assets/feathericons/bell.svg
index bba561c1..bba561c1 100644
--- a/src/qcam/assets/feathericons/bell.svg
+++ b/src/apps/qcam/assets/feathericons/bell.svg
diff --git a/src/qcam/assets/feathericons/bluetooth.svg b/src/apps/qcam/assets/feathericons/bluetooth.svg
index cebed7b1..cebed7b1 100644
--- a/src/qcam/assets/feathericons/bluetooth.svg
+++ b/src/apps/qcam/assets/feathericons/bluetooth.svg
diff --git a/src/qcam/assets/feathericons/bold.svg b/src/apps/qcam/assets/feathericons/bold.svg
index d1a4efd3..d1a4efd3 100644
--- a/src/qcam/assets/feathericons/bold.svg
+++ b/src/apps/qcam/assets/feathericons/bold.svg
diff --git a/src/qcam/assets/feathericons/book-open.svg b/src/apps/qcam/assets/feathericons/book-open.svg
index 5e0ca0ab..5e0ca0ab 100644
--- a/src/qcam/assets/feathericons/book-open.svg
+++ b/src/apps/qcam/assets/feathericons/book-open.svg
diff --git a/src/qcam/assets/feathericons/book.svg b/src/apps/qcam/assets/feathericons/book.svg
index 12ffcbc4..12ffcbc4 100644
--- a/src/qcam/assets/feathericons/book.svg
+++ b/src/apps/qcam/assets/feathericons/book.svg
diff --git a/src/qcam/assets/feathericons/bookmark.svg b/src/apps/qcam/assets/feathericons/bookmark.svg
index 2239cc58..2239cc58 100644
--- a/src/qcam/assets/feathericons/bookmark.svg
+++ b/src/apps/qcam/assets/feathericons/bookmark.svg
diff --git a/src/qcam/assets/feathericons/box.svg b/src/apps/qcam/assets/feathericons/box.svg
index d89be30f..d89be30f 100644
--- a/src/qcam/assets/feathericons/box.svg
+++ b/src/apps/qcam/assets/feathericons/box.svg
diff --git a/src/qcam/assets/feathericons/briefcase.svg b/src/apps/qcam/assets/feathericons/briefcase.svg
index e3af0506..e3af0506 100644
--- a/src/qcam/assets/feathericons/briefcase.svg
+++ b/src/apps/qcam/assets/feathericons/briefcase.svg
diff --git a/src/qcam/assets/feathericons/calendar.svg b/src/apps/qcam/assets/feathericons/calendar.svg
index 6c7fd870..6c7fd870 100644
--- a/src/qcam/assets/feathericons/calendar.svg
+++ b/src/apps/qcam/assets/feathericons/calendar.svg
diff --git a/src/qcam/assets/feathericons/camera-off.svg b/src/apps/qcam/assets/feathericons/camera-off.svg
index daa3e25f..daa3e25f 100644
--- a/src/qcam/assets/feathericons/camera-off.svg
+++ b/src/apps/qcam/assets/feathericons/camera-off.svg
diff --git a/src/qcam/assets/feathericons/camera.svg b/src/apps/qcam/assets/feathericons/camera.svg
index 0e7f0603..0e7f0603 100644
--- a/src/qcam/assets/feathericons/camera.svg
+++ b/src/apps/qcam/assets/feathericons/camera.svg
diff --git a/src/qcam/assets/feathericons/cast.svg b/src/apps/qcam/assets/feathericons/cast.svg
index 63c954d9..63c954d9 100644
--- a/src/qcam/assets/feathericons/cast.svg
+++ b/src/apps/qcam/assets/feathericons/cast.svg
diff --git a/src/qcam/assets/feathericons/check-circle.svg b/src/apps/qcam/assets/feathericons/check-circle.svg
index f2f4fd1a..f2f4fd1a 100644
--- a/src/qcam/assets/feathericons/check-circle.svg
+++ b/src/apps/qcam/assets/feathericons/check-circle.svg
diff --git a/src/qcam/assets/feathericons/check-square.svg b/src/apps/qcam/assets/feathericons/check-square.svg
index 72ab7a80..72ab7a80 100644
--- a/src/qcam/assets/feathericons/check-square.svg
+++ b/src/apps/qcam/assets/feathericons/check-square.svg
diff --git a/src/qcam/assets/feathericons/check.svg b/src/apps/qcam/assets/feathericons/check.svg
index 1c209899..1c209899 100644
--- a/src/qcam/assets/feathericons/check.svg
+++ b/src/apps/qcam/assets/feathericons/check.svg
diff --git a/src/qcam/assets/feathericons/chevron-down.svg b/src/apps/qcam/assets/feathericons/chevron-down.svg
index 278c6a31..278c6a31 100644
--- a/src/qcam/assets/feathericons/chevron-down.svg
+++ b/src/apps/qcam/assets/feathericons/chevron-down.svg
diff --git a/src/qcam/assets/feathericons/chevron-left.svg b/src/apps/qcam/assets/feathericons/chevron-left.svg
index 747d46d9..747d46d9 100644
--- a/src/qcam/assets/feathericons/chevron-left.svg
+++ b/src/apps/qcam/assets/feathericons/chevron-left.svg
diff --git a/src/qcam/assets/feathericons/chevron-right.svg b/src/apps/qcam/assets/feathericons/chevron-right.svg
index 258de414..258de414 100644
--- a/src/qcam/assets/feathericons/chevron-right.svg
+++ b/src/apps/qcam/assets/feathericons/chevron-right.svg
diff --git a/src/qcam/assets/feathericons/chevron-up.svg b/src/apps/qcam/assets/feathericons/chevron-up.svg
index 4eb5ecc3..4eb5ecc3 100644
--- a/src/qcam/assets/feathericons/chevron-up.svg
+++ b/src/apps/qcam/assets/feathericons/chevron-up.svg
diff --git a/src/qcam/assets/feathericons/chevrons-down.svg b/src/apps/qcam/assets/feathericons/chevrons-down.svg
index e67ef2fb..e67ef2fb 100644
--- a/src/qcam/assets/feathericons/chevrons-down.svg
+++ b/src/apps/qcam/assets/feathericons/chevrons-down.svg
diff --git a/src/qcam/assets/feathericons/chevrons-left.svg b/src/apps/qcam/assets/feathericons/chevrons-left.svg
index c32e3983..c32e3983 100644
--- a/src/qcam/assets/feathericons/chevrons-left.svg
+++ b/src/apps/qcam/assets/feathericons/chevrons-left.svg
diff --git a/src/qcam/assets/feathericons/chevrons-right.svg b/src/apps/qcam/assets/feathericons/chevrons-right.svg
index f5068145..f5068145 100644
--- a/src/qcam/assets/feathericons/chevrons-right.svg
+++ b/src/apps/qcam/assets/feathericons/chevrons-right.svg
diff --git a/src/qcam/assets/feathericons/chevrons-up.svg b/src/apps/qcam/assets/feathericons/chevrons-up.svg
index 0eaf5183..0eaf5183 100644
--- a/src/qcam/assets/feathericons/chevrons-up.svg
+++ b/src/apps/qcam/assets/feathericons/chevrons-up.svg
diff --git a/src/qcam/assets/feathericons/chrome.svg b/src/apps/qcam/assets/feathericons/chrome.svg
index 9189815e..9189815e 100644
--- a/src/qcam/assets/feathericons/chrome.svg
+++ b/src/apps/qcam/assets/feathericons/chrome.svg
diff --git a/src/qcam/assets/feathericons/circle.svg b/src/apps/qcam/assets/feathericons/circle.svg
index b0090882..b0090882 100644
--- a/src/qcam/assets/feathericons/circle.svg
+++ b/src/apps/qcam/assets/feathericons/circle.svg
diff --git a/src/qcam/assets/feathericons/clipboard.svg b/src/apps/qcam/assets/feathericons/clipboard.svg
index ccee454d..ccee454d 100644
--- a/src/qcam/assets/feathericons/clipboard.svg
+++ b/src/apps/qcam/assets/feathericons/clipboard.svg
diff --git a/src/qcam/assets/feathericons/clock.svg b/src/apps/qcam/assets/feathericons/clock.svg
index ea3f5e50..ea3f5e50 100644
--- a/src/qcam/assets/feathericons/clock.svg
+++ b/src/apps/qcam/assets/feathericons/clock.svg
diff --git a/src/qcam/assets/feathericons/cloud-drizzle.svg b/src/apps/qcam/assets/feathericons/cloud-drizzle.svg
index 13af6bb5..13af6bb5 100644
--- a/src/qcam/assets/feathericons/cloud-drizzle.svg
+++ b/src/apps/qcam/assets/feathericons/cloud-drizzle.svg
diff --git a/src/qcam/assets/feathericons/cloud-lightning.svg b/src/apps/qcam/assets/feathericons/cloud-lightning.svg
index 32d154cc..32d154cc 100644
--- a/src/qcam/assets/feathericons/cloud-lightning.svg
+++ b/src/apps/qcam/assets/feathericons/cloud-lightning.svg
diff --git a/src/qcam/assets/feathericons/cloud-off.svg b/src/apps/qcam/assets/feathericons/cloud-off.svg
index 1e1e7d60..1e1e7d60 100644
--- a/src/qcam/assets/feathericons/cloud-off.svg
+++ b/src/apps/qcam/assets/feathericons/cloud-off.svg
diff --git a/src/qcam/assets/feathericons/cloud-rain.svg b/src/apps/qcam/assets/feathericons/cloud-rain.svg
index 3e0b85b0..3e0b85b0 100644
--- a/src/qcam/assets/feathericons/cloud-rain.svg
+++ b/src/apps/qcam/assets/feathericons/cloud-rain.svg
diff --git a/src/qcam/assets/feathericons/cloud-snow.svg b/src/apps/qcam/assets/feathericons/cloud-snow.svg
index e4eb8207..e4eb8207 100644
--- a/src/qcam/assets/feathericons/cloud-snow.svg
+++ b/src/apps/qcam/assets/feathericons/cloud-snow.svg
diff --git a/src/qcam/assets/feathericons/cloud.svg b/src/apps/qcam/assets/feathericons/cloud.svg
index 0ee0c632..0ee0c632 100644
--- a/src/qcam/assets/feathericons/cloud.svg
+++ b/src/apps/qcam/assets/feathericons/cloud.svg
diff --git a/src/qcam/assets/feathericons/code.svg b/src/apps/qcam/assets/feathericons/code.svg
index c4954b55..c4954b55 100644
--- a/src/qcam/assets/feathericons/code.svg
+++ b/src/apps/qcam/assets/feathericons/code.svg
diff --git a/src/qcam/assets/feathericons/codepen.svg b/src/apps/qcam/assets/feathericons/codepen.svg
index ab2a815a..ab2a815a 100644
--- a/src/qcam/assets/feathericons/codepen.svg
+++ b/src/apps/qcam/assets/feathericons/codepen.svg
diff --git a/src/qcam/assets/feathericons/codesandbox.svg b/src/apps/qcam/assets/feathericons/codesandbox.svg
index 49848f52..49848f52 100644
--- a/src/qcam/assets/feathericons/codesandbox.svg
+++ b/src/apps/qcam/assets/feathericons/codesandbox.svg
diff --git a/src/qcam/assets/feathericons/coffee.svg b/src/apps/qcam/assets/feathericons/coffee.svg
index 32905e52..32905e52 100644
--- a/src/qcam/assets/feathericons/coffee.svg
+++ b/src/apps/qcam/assets/feathericons/coffee.svg
diff --git a/src/qcam/assets/feathericons/columns.svg b/src/apps/qcam/assets/feathericons/columns.svg
index d264b557..d264b557 100644
--- a/src/qcam/assets/feathericons/columns.svg
+++ b/src/apps/qcam/assets/feathericons/columns.svg
diff --git a/src/qcam/assets/feathericons/command.svg b/src/apps/qcam/assets/feathericons/command.svg
index 93f554c3..93f554c3 100644
--- a/src/qcam/assets/feathericons/command.svg
+++ b/src/apps/qcam/assets/feathericons/command.svg
diff --git a/src/qcam/assets/feathericons/compass.svg b/src/apps/qcam/assets/feathericons/compass.svg
index 32962608..32962608 100644
--- a/src/qcam/assets/feathericons/compass.svg
+++ b/src/apps/qcam/assets/feathericons/compass.svg
diff --git a/src/qcam/assets/feathericons/copy.svg b/src/apps/qcam/assets/feathericons/copy.svg
index 4e0b09f1..4e0b09f1 100644
--- a/src/qcam/assets/feathericons/copy.svg
+++ b/src/apps/qcam/assets/feathericons/copy.svg
diff --git a/src/qcam/assets/feathericons/corner-down-left.svg b/src/apps/qcam/assets/feathericons/corner-down-left.svg
index 9fffb3e9..9fffb3e9 100644
--- a/src/qcam/assets/feathericons/corner-down-left.svg
+++ b/src/apps/qcam/assets/feathericons/corner-down-left.svg
diff --git a/src/qcam/assets/feathericons/corner-down-right.svg b/src/apps/qcam/assets/feathericons/corner-down-right.svg
index b27d408d..b27d408d 100644
--- a/src/qcam/assets/feathericons/corner-down-right.svg
+++ b/src/apps/qcam/assets/feathericons/corner-down-right.svg
diff --git a/src/qcam/assets/feathericons/corner-left-down.svg b/src/apps/qcam/assets/feathericons/corner-left-down.svg
index 24b8375c..24b8375c 100644
--- a/src/qcam/assets/feathericons/corner-left-down.svg
+++ b/src/apps/qcam/assets/feathericons/corner-left-down.svg
diff --git a/src/qcam/assets/feathericons/corner-left-up.svg b/src/apps/qcam/assets/feathericons/corner-left-up.svg
index e54527cd..e54527cd 100644
--- a/src/qcam/assets/feathericons/corner-left-up.svg
+++ b/src/apps/qcam/assets/feathericons/corner-left-up.svg
diff --git a/src/qcam/assets/feathericons/corner-right-down.svg b/src/apps/qcam/assets/feathericons/corner-right-down.svg
index a49e6d6c..a49e6d6c 100644
--- a/src/qcam/assets/feathericons/corner-right-down.svg
+++ b/src/apps/qcam/assets/feathericons/corner-right-down.svg
diff --git a/src/qcam/assets/feathericons/corner-right-up.svg b/src/apps/qcam/assets/feathericons/corner-right-up.svg
index a5c5dce5..a5c5dce5 100644
--- a/src/qcam/assets/feathericons/corner-right-up.svg
+++ b/src/apps/qcam/assets/feathericons/corner-right-up.svg
diff --git a/src/qcam/assets/feathericons/corner-up-left.svg b/src/apps/qcam/assets/feathericons/corner-up-left.svg
index 0a1ffd61..0a1ffd61 100644
--- a/src/qcam/assets/feathericons/corner-up-left.svg
+++ b/src/apps/qcam/assets/feathericons/corner-up-left.svg
diff --git a/src/qcam/assets/feathericons/corner-up-right.svg b/src/apps/qcam/assets/feathericons/corner-up-right.svg
index 0b8f961b..0b8f961b 100644
--- a/src/qcam/assets/feathericons/corner-up-right.svg
+++ b/src/apps/qcam/assets/feathericons/corner-up-right.svg
diff --git a/src/qcam/assets/feathericons/cpu.svg b/src/apps/qcam/assets/feathericons/cpu.svg
index 2ed16ef7..2ed16ef7 100644
--- a/src/qcam/assets/feathericons/cpu.svg
+++ b/src/apps/qcam/assets/feathericons/cpu.svg
diff --git a/src/qcam/assets/feathericons/credit-card.svg b/src/apps/qcam/assets/feathericons/credit-card.svg
index 1b7fd029..1b7fd029 100644
--- a/src/qcam/assets/feathericons/credit-card.svg
+++ b/src/apps/qcam/assets/feathericons/credit-card.svg
diff --git a/src/qcam/assets/feathericons/crop.svg b/src/apps/qcam/assets/feathericons/crop.svg
index ffbfd045..ffbfd045 100644
--- a/src/qcam/assets/feathericons/crop.svg
+++ b/src/apps/qcam/assets/feathericons/crop.svg
diff --git a/src/qcam/assets/feathericons/crosshair.svg b/src/apps/qcam/assets/feathericons/crosshair.svg
index ba394015..ba394015 100644
--- a/src/qcam/assets/feathericons/crosshair.svg
+++ b/src/apps/qcam/assets/feathericons/crosshair.svg
diff --git a/src/qcam/assets/feathericons/database.svg b/src/apps/qcam/assets/feathericons/database.svg
index c296fbcf..c296fbcf 100644
--- a/src/qcam/assets/feathericons/database.svg
+++ b/src/apps/qcam/assets/feathericons/database.svg
diff --git a/src/qcam/assets/feathericons/delete.svg b/src/apps/qcam/assets/feathericons/delete.svg
index 8c6074b9..8c6074b9 100644
--- a/src/qcam/assets/feathericons/delete.svg
+++ b/src/apps/qcam/assets/feathericons/delete.svg
diff --git a/src/qcam/assets/feathericons/disc.svg b/src/apps/qcam/assets/feathericons/disc.svg
index 2595b444..2595b444 100644
--- a/src/qcam/assets/feathericons/disc.svg
+++ b/src/apps/qcam/assets/feathericons/disc.svg
diff --git a/src/qcam/assets/feathericons/dollar-sign.svg b/src/apps/qcam/assets/feathericons/dollar-sign.svg
index 1a124d26..1a124d26 100644
--- a/src/qcam/assets/feathericons/dollar-sign.svg
+++ b/src/apps/qcam/assets/feathericons/dollar-sign.svg
diff --git a/src/qcam/assets/feathericons/download-cloud.svg b/src/apps/qcam/assets/feathericons/download-cloud.svg
index f3126fc3..f3126fc3 100644
--- a/src/qcam/assets/feathericons/download-cloud.svg
+++ b/src/apps/qcam/assets/feathericons/download-cloud.svg
diff --git a/src/qcam/assets/feathericons/download.svg b/src/apps/qcam/assets/feathericons/download.svg
index 76767a92..76767a92 100644
--- a/src/qcam/assets/feathericons/download.svg
+++ b/src/apps/qcam/assets/feathericons/download.svg
diff --git a/src/qcam/assets/feathericons/droplet.svg b/src/apps/qcam/assets/feathericons/droplet.svg
index ca093014..ca093014 100644
--- a/src/qcam/assets/feathericons/droplet.svg
+++ b/src/apps/qcam/assets/feathericons/droplet.svg
diff --git a/src/qcam/assets/feathericons/edit-2.svg b/src/apps/qcam/assets/feathericons/edit-2.svg
index 06830c9d..06830c9d 100644
--- a/src/qcam/assets/feathericons/edit-2.svg
+++ b/src/apps/qcam/assets/feathericons/edit-2.svg
diff --git a/src/qcam/assets/feathericons/edit-3.svg b/src/apps/qcam/assets/feathericons/edit-3.svg
index d728efcc..d728efcc 100644
--- a/src/qcam/assets/feathericons/edit-3.svg
+++ b/src/apps/qcam/assets/feathericons/edit-3.svg
diff --git a/src/qcam/assets/feathericons/edit.svg b/src/apps/qcam/assets/feathericons/edit.svg
index ec7b4ca2..ec7b4ca2 100644
--- a/src/qcam/assets/feathericons/edit.svg
+++ b/src/apps/qcam/assets/feathericons/edit.svg
diff --git a/src/qcam/assets/feathericons/external-link.svg b/src/apps/qcam/assets/feathericons/external-link.svg
index 6236df3e..6236df3e 100644
--- a/src/qcam/assets/feathericons/external-link.svg
+++ b/src/apps/qcam/assets/feathericons/external-link.svg
diff --git a/src/qcam/assets/feathericons/eye-off.svg b/src/apps/qcam/assets/feathericons/eye-off.svg
index 77c54cb4..77c54cb4 100644
--- a/src/qcam/assets/feathericons/eye-off.svg
+++ b/src/apps/qcam/assets/feathericons/eye-off.svg
diff --git a/src/qcam/assets/feathericons/eye.svg b/src/apps/qcam/assets/feathericons/eye.svg
index 9cde2437..9cde2437 100644
--- a/src/qcam/assets/feathericons/eye.svg
+++ b/src/apps/qcam/assets/feathericons/eye.svg
diff --git a/src/qcam/assets/feathericons/facebook.svg b/src/apps/qcam/assets/feathericons/facebook.svg
index 2570f56a..2570f56a 100644
--- a/src/qcam/assets/feathericons/facebook.svg
+++ b/src/apps/qcam/assets/feathericons/facebook.svg
diff --git a/src/qcam/assets/feathericons/fast-forward.svg b/src/apps/qcam/assets/feathericons/fast-forward.svg
index fa39877a..fa39877a 100644
--- a/src/qcam/assets/feathericons/fast-forward.svg
+++ b/src/apps/qcam/assets/feathericons/fast-forward.svg
diff --git a/src/qcam/assets/feathericons/feather.svg b/src/apps/qcam/assets/feathericons/feather.svg
index ac3b868d..ac3b868d 100644
--- a/src/qcam/assets/feathericons/feather.svg
+++ b/src/apps/qcam/assets/feathericons/feather.svg
diff --git a/src/qcam/assets/feathericons/feathericons.qrc b/src/apps/qcam/assets/feathericons/feathericons.qrc
index c5302040..c5302040 100644
--- a/src/qcam/assets/feathericons/feathericons.qrc
+++ b/src/apps/qcam/assets/feathericons/feathericons.qrc
diff --git a/src/qcam/assets/feathericons/figma.svg b/src/apps/qcam/assets/feathericons/figma.svg
index 66fd2178..66fd2178 100644
--- a/src/qcam/assets/feathericons/figma.svg
+++ b/src/apps/qcam/assets/feathericons/figma.svg
diff --git a/src/qcam/assets/feathericons/file-minus.svg b/src/apps/qcam/assets/feathericons/file-minus.svg
index 345756ef..345756ef 100644
--- a/src/qcam/assets/feathericons/file-minus.svg
+++ b/src/apps/qcam/assets/feathericons/file-minus.svg
diff --git a/src/qcam/assets/feathericons/file-plus.svg b/src/apps/qcam/assets/feathericons/file-plus.svg
index eed12004..eed12004 100644
--- a/src/qcam/assets/feathericons/file-plus.svg
+++ b/src/apps/qcam/assets/feathericons/file-plus.svg
diff --git a/src/qcam/assets/feathericons/file-text.svg b/src/apps/qcam/assets/feathericons/file-text.svg
index 4197ddd4..4197ddd4 100644
--- a/src/qcam/assets/feathericons/file-text.svg
+++ b/src/apps/qcam/assets/feathericons/file-text.svg
diff --git a/src/qcam/assets/feathericons/file.svg b/src/apps/qcam/assets/feathericons/file.svg
index 378519ab..378519ab 100644
--- a/src/qcam/assets/feathericons/file.svg
+++ b/src/apps/qcam/assets/feathericons/file.svg
diff --git a/src/qcam/assets/feathericons/film.svg b/src/apps/qcam/assets/feathericons/film.svg
index ac46360d..ac46360d 100644
--- a/src/qcam/assets/feathericons/film.svg
+++ b/src/apps/qcam/assets/feathericons/film.svg
diff --git a/src/qcam/assets/feathericons/filter.svg b/src/apps/qcam/assets/feathericons/filter.svg
index 38a47e04..38a47e04 100644
--- a/src/qcam/assets/feathericons/filter.svg
+++ b/src/apps/qcam/assets/feathericons/filter.svg
diff --git a/src/qcam/assets/feathericons/flag.svg b/src/apps/qcam/assets/feathericons/flag.svg
index 037737cb..037737cb 100644
--- a/src/qcam/assets/feathericons/flag.svg
+++ b/src/apps/qcam/assets/feathericons/flag.svg
diff --git a/src/qcam/assets/feathericons/folder-minus.svg b/src/apps/qcam/assets/feathericons/folder-minus.svg
index d5b7af65..d5b7af65 100644
--- a/src/qcam/assets/feathericons/folder-minus.svg
+++ b/src/apps/qcam/assets/feathericons/folder-minus.svg
diff --git a/src/qcam/assets/feathericons/folder-plus.svg b/src/apps/qcam/assets/feathericons/folder-plus.svg
index 898f2fc9..898f2fc9 100644
--- a/src/qcam/assets/feathericons/folder-plus.svg
+++ b/src/apps/qcam/assets/feathericons/folder-plus.svg
diff --git a/src/qcam/assets/feathericons/folder.svg b/src/apps/qcam/assets/feathericons/folder.svg
index 134458b9..134458b9 100644
--- a/src/qcam/assets/feathericons/folder.svg
+++ b/src/apps/qcam/assets/feathericons/folder.svg
diff --git a/src/qcam/assets/feathericons/framer.svg b/src/apps/qcam/assets/feathericons/framer.svg
index 3e663478..3e663478 100644
--- a/src/qcam/assets/feathericons/framer.svg
+++ b/src/apps/qcam/assets/feathericons/framer.svg
diff --git a/src/qcam/assets/feathericons/frown.svg b/src/apps/qcam/assets/feathericons/frown.svg
index f3122547..f3122547 100644
--- a/src/qcam/assets/feathericons/frown.svg
+++ b/src/apps/qcam/assets/feathericons/frown.svg
diff --git a/src/qcam/assets/feathericons/gift.svg b/src/apps/qcam/assets/feathericons/gift.svg
index d2c14bd6..d2c14bd6 100644
--- a/src/qcam/assets/feathericons/gift.svg
+++ b/src/apps/qcam/assets/feathericons/gift.svg
diff --git a/src/qcam/assets/feathericons/git-branch.svg b/src/apps/qcam/assets/feathericons/git-branch.svg
index 44003726..44003726 100644
--- a/src/qcam/assets/feathericons/git-branch.svg
+++ b/src/apps/qcam/assets/feathericons/git-branch.svg
diff --git a/src/qcam/assets/feathericons/git-commit.svg b/src/apps/qcam/assets/feathericons/git-commit.svg
index e959d725..e959d725 100644
--- a/src/qcam/assets/feathericons/git-commit.svg
+++ b/src/apps/qcam/assets/feathericons/git-commit.svg
diff --git a/src/qcam/assets/feathericons/git-merge.svg b/src/apps/qcam/assets/feathericons/git-merge.svg
index c65fffdd..c65fffdd 100644
--- a/src/qcam/assets/feathericons/git-merge.svg
+++ b/src/apps/qcam/assets/feathericons/git-merge.svg
diff --git a/src/qcam/assets/feathericons/git-pull-request.svg b/src/apps/qcam/assets/feathericons/git-pull-request.svg
index fc80bdfd..fc80bdfd 100644
--- a/src/qcam/assets/feathericons/git-pull-request.svg
+++ b/src/apps/qcam/assets/feathericons/git-pull-request.svg
diff --git a/src/qcam/assets/feathericons/github.svg b/src/apps/qcam/assets/feathericons/github.svg
index ff0af481..ff0af481 100644
--- a/src/qcam/assets/feathericons/github.svg
+++ b/src/apps/qcam/assets/feathericons/github.svg
diff --git a/src/qcam/assets/feathericons/gitlab.svg b/src/apps/qcam/assets/feathericons/gitlab.svg
index 85d54a1e..85d54a1e 100644
--- a/src/qcam/assets/feathericons/gitlab.svg
+++ b/src/apps/qcam/assets/feathericons/gitlab.svg
diff --git a/src/qcam/assets/feathericons/globe.svg b/src/apps/qcam/assets/feathericons/globe.svg
index 0a0586d3..0a0586d3 100644
--- a/src/qcam/assets/feathericons/globe.svg
+++ b/src/apps/qcam/assets/feathericons/globe.svg
diff --git a/src/qcam/assets/feathericons/grid.svg b/src/apps/qcam/assets/feathericons/grid.svg
index 8ef2e9d8..8ef2e9d8 100644
--- a/src/qcam/assets/feathericons/grid.svg
+++ b/src/apps/qcam/assets/feathericons/grid.svg
diff --git a/src/qcam/assets/feathericons/hard-drive.svg b/src/apps/qcam/assets/feathericons/hard-drive.svg
index 8e90fa1b..8e90fa1b 100644
--- a/src/qcam/assets/feathericons/hard-drive.svg
+++ b/src/apps/qcam/assets/feathericons/hard-drive.svg
diff --git a/src/qcam/assets/feathericons/hash.svg b/src/apps/qcam/assets/feathericons/hash.svg
index c9c8d41f..c9c8d41f 100644
--- a/src/qcam/assets/feathericons/hash.svg
+++ b/src/apps/qcam/assets/feathericons/hash.svg
diff --git a/src/qcam/assets/feathericons/headphones.svg b/src/apps/qcam/assets/feathericons/headphones.svg
index fd8915b4..fd8915b4 100644
--- a/src/qcam/assets/feathericons/headphones.svg
+++ b/src/apps/qcam/assets/feathericons/headphones.svg
diff --git a/src/qcam/assets/feathericons/heart.svg b/src/apps/qcam/assets/feathericons/heart.svg
index a083b7e2..a083b7e2 100644
--- a/src/qcam/assets/feathericons/heart.svg
+++ b/src/apps/qcam/assets/feathericons/heart.svg
diff --git a/src/qcam/assets/feathericons/help-circle.svg b/src/apps/qcam/assets/feathericons/help-circle.svg
index 51fddd80..51fddd80 100644
--- a/src/qcam/assets/feathericons/help-circle.svg
+++ b/src/apps/qcam/assets/feathericons/help-circle.svg
diff --git a/src/qcam/assets/feathericons/hexagon.svg b/src/apps/qcam/assets/feathericons/hexagon.svg
index eae7f255..eae7f255 100644
--- a/src/qcam/assets/feathericons/hexagon.svg
+++ b/src/apps/qcam/assets/feathericons/hexagon.svg
diff --git a/src/qcam/assets/feathericons/home.svg b/src/apps/qcam/assets/feathericons/home.svg
index 7bb31b23..7bb31b23 100644
--- a/src/qcam/assets/feathericons/home.svg
+++ b/src/apps/qcam/assets/feathericons/home.svg
diff --git a/src/qcam/assets/feathericons/image.svg b/src/apps/qcam/assets/feathericons/image.svg
index a7d84b98..a7d84b98 100644
--- a/src/qcam/assets/feathericons/image.svg
+++ b/src/apps/qcam/assets/feathericons/image.svg
diff --git a/src/qcam/assets/feathericons/inbox.svg b/src/apps/qcam/assets/feathericons/inbox.svg
index 03a13b4e..03a13b4e 100644
--- a/src/qcam/assets/feathericons/inbox.svg
+++ b/src/apps/qcam/assets/feathericons/inbox.svg
diff --git a/src/qcam/assets/feathericons/info.svg b/src/apps/qcam/assets/feathericons/info.svg
index a09fa5f1..a09fa5f1 100644
--- a/src/qcam/assets/feathericons/info.svg
+++ b/src/apps/qcam/assets/feathericons/info.svg
diff --git a/src/qcam/assets/feathericons/instagram.svg b/src/apps/qcam/assets/feathericons/instagram.svg
index 9fdb8e35..9fdb8e35 100644
--- a/src/qcam/assets/feathericons/instagram.svg
+++ b/src/apps/qcam/assets/feathericons/instagram.svg
diff --git a/src/qcam/assets/feathericons/italic.svg b/src/apps/qcam/assets/feathericons/italic.svg
index a123d371..a123d371 100644
--- a/src/qcam/assets/feathericons/italic.svg
+++ b/src/apps/qcam/assets/feathericons/italic.svg
diff --git a/src/qcam/assets/feathericons/key.svg b/src/apps/qcam/assets/feathericons/key.svg
index e778e74e..e778e74e 100644
--- a/src/qcam/assets/feathericons/key.svg
+++ b/src/apps/qcam/assets/feathericons/key.svg
diff --git a/src/qcam/assets/feathericons/layers.svg b/src/apps/qcam/assets/feathericons/layers.svg
index ea788c22..ea788c22 100644
--- a/src/qcam/assets/feathericons/layers.svg
+++ b/src/apps/qcam/assets/feathericons/layers.svg
diff --git a/src/qcam/assets/feathericons/layout.svg b/src/apps/qcam/assets/feathericons/layout.svg
index 28743d92..28743d92 100644
--- a/src/qcam/assets/feathericons/layout.svg
+++ b/src/apps/qcam/assets/feathericons/layout.svg
diff --git a/src/qcam/assets/feathericons/life-buoy.svg b/src/apps/qcam/assets/feathericons/life-buoy.svg
index 54c2bd7d..54c2bd7d 100644
--- a/src/qcam/assets/feathericons/life-buoy.svg
+++ b/src/apps/qcam/assets/feathericons/life-buoy.svg
diff --git a/src/qcam/assets/feathericons/link-2.svg b/src/apps/qcam/assets/feathericons/link-2.svg
index 8cc7f6dd..8cc7f6dd 100644
--- a/src/qcam/assets/feathericons/link-2.svg
+++ b/src/apps/qcam/assets/feathericons/link-2.svg
diff --git a/src/qcam/assets/feathericons/link.svg b/src/apps/qcam/assets/feathericons/link.svg
index c89dd41c..c89dd41c 100644
--- a/src/qcam/assets/feathericons/link.svg
+++ b/src/apps/qcam/assets/feathericons/link.svg
diff --git a/src/qcam/assets/feathericons/linkedin.svg b/src/apps/qcam/assets/feathericons/linkedin.svg
index 39531094..39531094 100644
--- a/src/qcam/assets/feathericons/linkedin.svg
+++ b/src/apps/qcam/assets/feathericons/linkedin.svg
diff --git a/src/qcam/assets/feathericons/list.svg b/src/apps/qcam/assets/feathericons/list.svg
index 5ce38eaa..5ce38eaa 100644
--- a/src/qcam/assets/feathericons/list.svg
+++ b/src/apps/qcam/assets/feathericons/list.svg
diff --git a/src/qcam/assets/feathericons/loader.svg b/src/apps/qcam/assets/feathericons/loader.svg
index e1a70c12..e1a70c12 100644
--- a/src/qcam/assets/feathericons/loader.svg
+++ b/src/apps/qcam/assets/feathericons/loader.svg
diff --git a/src/qcam/assets/feathericons/lock.svg b/src/apps/qcam/assets/feathericons/lock.svg
index de09d9db..de09d9db 100644
--- a/src/qcam/assets/feathericons/lock.svg
+++ b/src/apps/qcam/assets/feathericons/lock.svg
diff --git a/src/qcam/assets/feathericons/log-in.svg b/src/apps/qcam/assets/feathericons/log-in.svg
index ba0da59a..ba0da59a 100644
--- a/src/qcam/assets/feathericons/log-in.svg
+++ b/src/apps/qcam/assets/feathericons/log-in.svg
diff --git a/src/qcam/assets/feathericons/log-out.svg b/src/apps/qcam/assets/feathericons/log-out.svg
index c9002c90..c9002c90 100644
--- a/src/qcam/assets/feathericons/log-out.svg
+++ b/src/apps/qcam/assets/feathericons/log-out.svg
diff --git a/src/qcam/assets/feathericons/mail.svg b/src/apps/qcam/assets/feathericons/mail.svg
index 2af169e8..2af169e8 100644
--- a/src/qcam/assets/feathericons/mail.svg
+++ b/src/apps/qcam/assets/feathericons/mail.svg
diff --git a/src/qcam/assets/feathericons/map-pin.svg b/src/apps/qcam/assets/feathericons/map-pin.svg
index d5548e92..d5548e92 100644
--- a/src/qcam/assets/feathericons/map-pin.svg
+++ b/src/apps/qcam/assets/feathericons/map-pin.svg
diff --git a/src/qcam/assets/feathericons/map.svg b/src/apps/qcam/assets/feathericons/map.svg
index ecebd7bf..ecebd7bf 100644
--- a/src/qcam/assets/feathericons/map.svg
+++ b/src/apps/qcam/assets/feathericons/map.svg
diff --git a/src/qcam/assets/feathericons/maximize-2.svg b/src/apps/qcam/assets/feathericons/maximize-2.svg
index e41fc0b7..e41fc0b7 100644
--- a/src/qcam/assets/feathericons/maximize-2.svg
+++ b/src/apps/qcam/assets/feathericons/maximize-2.svg
diff --git a/src/qcam/assets/feathericons/maximize.svg b/src/apps/qcam/assets/feathericons/maximize.svg
index fc305189..fc305189 100644
--- a/src/qcam/assets/feathericons/maximize.svg
+++ b/src/apps/qcam/assets/feathericons/maximize.svg
diff --git a/src/qcam/assets/feathericons/meh.svg b/src/apps/qcam/assets/feathericons/meh.svg
index 6f57fff2..6f57fff2 100644
--- a/src/qcam/assets/feathericons/meh.svg
+++ b/src/apps/qcam/assets/feathericons/meh.svg
diff --git a/src/qcam/assets/feathericons/menu.svg b/src/apps/qcam/assets/feathericons/menu.svg
index e8a84a95..e8a84a95 100644
--- a/src/qcam/assets/feathericons/menu.svg
+++ b/src/apps/qcam/assets/feathericons/menu.svg
diff --git a/src/qcam/assets/feathericons/message-circle.svg b/src/apps/qcam/assets/feathericons/message-circle.svg
index 4b21b32b..4b21b32b 100644
--- a/src/qcam/assets/feathericons/message-circle.svg
+++ b/src/apps/qcam/assets/feathericons/message-circle.svg
diff --git a/src/qcam/assets/feathericons/message-square.svg b/src/apps/qcam/assets/feathericons/message-square.svg
index 6a2e4e59..6a2e4e59 100644
--- a/src/qcam/assets/feathericons/message-square.svg
+++ b/src/apps/qcam/assets/feathericons/message-square.svg
diff --git a/src/qcam/assets/feathericons/mic-off.svg b/src/apps/qcam/assets/feathericons/mic-off.svg
index 0786219c..0786219c 100644
--- a/src/qcam/assets/feathericons/mic-off.svg
+++ b/src/apps/qcam/assets/feathericons/mic-off.svg
diff --git a/src/qcam/assets/feathericons/mic.svg b/src/apps/qcam/assets/feathericons/mic.svg
index dc5f780c..dc5f780c 100644
--- a/src/qcam/assets/feathericons/mic.svg
+++ b/src/apps/qcam/assets/feathericons/mic.svg
diff --git a/src/qcam/assets/feathericons/minimize-2.svg b/src/apps/qcam/assets/feathericons/minimize-2.svg
index a720fa6c..a720fa6c 100644
--- a/src/qcam/assets/feathericons/minimize-2.svg
+++ b/src/apps/qcam/assets/feathericons/minimize-2.svg
diff --git a/src/qcam/assets/feathericons/minimize.svg b/src/apps/qcam/assets/feathericons/minimize.svg
index 46d61196..46d61196 100644
--- a/src/qcam/assets/feathericons/minimize.svg
+++ b/src/apps/qcam/assets/feathericons/minimize.svg
diff --git a/src/qcam/assets/feathericons/minus-circle.svg b/src/apps/qcam/assets/feathericons/minus-circle.svg
index 80c0de1e..80c0de1e 100644
--- a/src/qcam/assets/feathericons/minus-circle.svg
+++ b/src/apps/qcam/assets/feathericons/minus-circle.svg
diff --git a/src/qcam/assets/feathericons/minus-square.svg b/src/apps/qcam/assets/feathericons/minus-square.svg
index 4862832a..4862832a 100644
--- a/src/qcam/assets/feathericons/minus-square.svg
+++ b/src/apps/qcam/assets/feathericons/minus-square.svg
diff --git a/src/qcam/assets/feathericons/minus.svg b/src/apps/qcam/assets/feathericons/minus.svg
index 93cc7340..93cc7340 100644
--- a/src/qcam/assets/feathericons/minus.svg
+++ b/src/apps/qcam/assets/feathericons/minus.svg
diff --git a/src/qcam/assets/feathericons/monitor.svg b/src/apps/qcam/assets/feathericons/monitor.svg
index 6c3556db..6c3556db 100644
--- a/src/qcam/assets/feathericons/monitor.svg
+++ b/src/apps/qcam/assets/feathericons/monitor.svg
diff --git a/src/qcam/assets/feathericons/moon.svg b/src/apps/qcam/assets/feathericons/moon.svg
index dbf7c6cf..dbf7c6cf 100644
--- a/src/qcam/assets/feathericons/moon.svg
+++ b/src/apps/qcam/assets/feathericons/moon.svg
diff --git a/src/qcam/assets/feathericons/more-horizontal.svg b/src/apps/qcam/assets/feathericons/more-horizontal.svg
index dc6a8556..dc6a8556 100644
--- a/src/qcam/assets/feathericons/more-horizontal.svg
+++ b/src/apps/qcam/assets/feathericons/more-horizontal.svg
diff --git a/src/qcam/assets/feathericons/more-vertical.svg b/src/apps/qcam/assets/feathericons/more-vertical.svg
index cba6958f..cba6958f 100644
--- a/src/qcam/assets/feathericons/more-vertical.svg
+++ b/src/apps/qcam/assets/feathericons/more-vertical.svg
diff --git a/src/qcam/assets/feathericons/mouse-pointer.svg b/src/apps/qcam/assets/feathericons/mouse-pointer.svg
index f5af5591..f5af5591 100644
--- a/src/qcam/assets/feathericons/mouse-pointer.svg
+++ b/src/apps/qcam/assets/feathericons/mouse-pointer.svg
diff --git a/src/qcam/assets/feathericons/move.svg b/src/apps/qcam/assets/feathericons/move.svg
index 4e251b56..4e251b56 100644
--- a/src/qcam/assets/feathericons/move.svg
+++ b/src/apps/qcam/assets/feathericons/move.svg
diff --git a/src/qcam/assets/feathericons/music.svg b/src/apps/qcam/assets/feathericons/music.svg
index 7bee2f7e..7bee2f7e 100644
--- a/src/qcam/assets/feathericons/music.svg
+++ b/src/apps/qcam/assets/feathericons/music.svg
diff --git a/src/qcam/assets/feathericons/navigation-2.svg b/src/apps/qcam/assets/feathericons/navigation-2.svg
index ae31db96..ae31db96 100644
--- a/src/qcam/assets/feathericons/navigation-2.svg
+++ b/src/apps/qcam/assets/feathericons/navigation-2.svg
diff --git a/src/qcam/assets/feathericons/navigation.svg b/src/apps/qcam/assets/feathericons/navigation.svg
index f600a414..f600a414 100644
--- a/src/qcam/assets/feathericons/navigation.svg
+++ b/src/apps/qcam/assets/feathericons/navigation.svg
diff --git a/src/qcam/assets/feathericons/octagon.svg b/src/apps/qcam/assets/feathericons/octagon.svg
index 124c5483..124c5483 100644
--- a/src/qcam/assets/feathericons/octagon.svg
+++ b/src/apps/qcam/assets/feathericons/octagon.svg
diff --git a/src/qcam/assets/feathericons/package.svg b/src/apps/qcam/assets/feathericons/package.svg
index f1e09eec..f1e09eec 100644
--- a/src/qcam/assets/feathericons/package.svg
+++ b/src/apps/qcam/assets/feathericons/package.svg
diff --git a/src/qcam/assets/feathericons/paperclip.svg b/src/apps/qcam/assets/feathericons/paperclip.svg
index b1f69b7a..b1f69b7a 100644
--- a/src/qcam/assets/feathericons/paperclip.svg
+++ b/src/apps/qcam/assets/feathericons/paperclip.svg
diff --git a/src/qcam/assets/feathericons/pause-circle.svg b/src/apps/qcam/assets/feathericons/pause-circle.svg
index f6b1a8df..f6b1a8df 100644
--- a/src/qcam/assets/feathericons/pause-circle.svg
+++ b/src/apps/qcam/assets/feathericons/pause-circle.svg
diff --git a/src/qcam/assets/feathericons/pause.svg b/src/apps/qcam/assets/feathericons/pause.svg
index 4e78038d..4e78038d 100644
--- a/src/qcam/assets/feathericons/pause.svg
+++ b/src/apps/qcam/assets/feathericons/pause.svg
diff --git a/src/qcam/assets/feathericons/pen-tool.svg b/src/apps/qcam/assets/feathericons/pen-tool.svg
index 0d26fa1e..0d26fa1e 100644
--- a/src/qcam/assets/feathericons/pen-tool.svg
+++ b/src/apps/qcam/assets/feathericons/pen-tool.svg
diff --git a/src/qcam/assets/feathericons/percent.svg b/src/apps/qcam/assets/feathericons/percent.svg
index 2cb9719d..2cb9719d 100644
--- a/src/qcam/assets/feathericons/percent.svg
+++ b/src/apps/qcam/assets/feathericons/percent.svg
diff --git a/src/qcam/assets/feathericons/phone-call.svg b/src/apps/qcam/assets/feathericons/phone-call.svg
index 8b866602..8b866602 100644
--- a/src/qcam/assets/feathericons/phone-call.svg
+++ b/src/apps/qcam/assets/feathericons/phone-call.svg
diff --git a/src/qcam/assets/feathericons/phone-forwarded.svg b/src/apps/qcam/assets/feathericons/phone-forwarded.svg
index aa21befc..aa21befc 100644
--- a/src/qcam/assets/feathericons/phone-forwarded.svg
+++ b/src/apps/qcam/assets/feathericons/phone-forwarded.svg
diff --git a/src/qcam/assets/feathericons/phone-incoming.svg b/src/apps/qcam/assets/feathericons/phone-incoming.svg
index b2d523a8..b2d523a8 100644
--- a/src/qcam/assets/feathericons/phone-incoming.svg
+++ b/src/apps/qcam/assets/feathericons/phone-incoming.svg
diff --git a/src/qcam/assets/feathericons/phone-missed.svg b/src/apps/qcam/assets/feathericons/phone-missed.svg
index 4950f09f..4950f09f 100644
--- a/src/qcam/assets/feathericons/phone-missed.svg
+++ b/src/apps/qcam/assets/feathericons/phone-missed.svg
diff --git a/src/qcam/assets/feathericons/phone-off.svg b/src/apps/qcam/assets/feathericons/phone-off.svg
index 4d00fb3d..4d00fb3d 100644
--- a/src/qcam/assets/feathericons/phone-off.svg
+++ b/src/apps/qcam/assets/feathericons/phone-off.svg
diff --git a/src/qcam/assets/feathericons/phone-outgoing.svg b/src/apps/qcam/assets/feathericons/phone-outgoing.svg
index fea27a37..fea27a37 100644
--- a/src/qcam/assets/feathericons/phone-outgoing.svg
+++ b/src/apps/qcam/assets/feathericons/phone-outgoing.svg
diff --git a/src/qcam/assets/feathericons/phone.svg b/src/apps/qcam/assets/feathericons/phone.svg
index 2a35154a..2a35154a 100644
--- a/src/qcam/assets/feathericons/phone.svg
+++ b/src/apps/qcam/assets/feathericons/phone.svg
diff --git a/src/qcam/assets/feathericons/pie-chart.svg b/src/apps/qcam/assets/feathericons/pie-chart.svg
index b5bbe67c..b5bbe67c 100644
--- a/src/qcam/assets/feathericons/pie-chart.svg
+++ b/src/apps/qcam/assets/feathericons/pie-chart.svg
diff --git a/src/qcam/assets/feathericons/play-circle.svg b/src/apps/qcam/assets/feathericons/play-circle.svg
index 8766dc7b..8766dc7b 100644
--- a/src/qcam/assets/feathericons/play-circle.svg
+++ b/src/apps/qcam/assets/feathericons/play-circle.svg
diff --git a/src/qcam/assets/feathericons/play.svg b/src/apps/qcam/assets/feathericons/play.svg
index fd76e30d..fd76e30d 100644
--- a/src/qcam/assets/feathericons/play.svg
+++ b/src/apps/qcam/assets/feathericons/play.svg
diff --git a/src/qcam/assets/feathericons/plus-circle.svg b/src/apps/qcam/assets/feathericons/plus-circle.svg
index 4291ff05..4291ff05 100644
--- a/src/qcam/assets/feathericons/plus-circle.svg
+++ b/src/apps/qcam/assets/feathericons/plus-circle.svg
diff --git a/src/qcam/assets/feathericons/plus-square.svg b/src/apps/qcam/assets/feathericons/plus-square.svg
index c380e24b..c380e24b 100644
--- a/src/qcam/assets/feathericons/plus-square.svg
+++ b/src/apps/qcam/assets/feathericons/plus-square.svg
diff --git a/src/qcam/assets/feathericons/plus.svg b/src/apps/qcam/assets/feathericons/plus.svg
index 703c5b7b..703c5b7b 100644
--- a/src/qcam/assets/feathericons/plus.svg
+++ b/src/apps/qcam/assets/feathericons/plus.svg
diff --git a/src/qcam/assets/feathericons/pocket.svg b/src/apps/qcam/assets/feathericons/pocket.svg
index a3b25619..a3b25619 100644
--- a/src/qcam/assets/feathericons/pocket.svg
+++ b/src/apps/qcam/assets/feathericons/pocket.svg
diff --git a/src/qcam/assets/feathericons/power.svg b/src/apps/qcam/assets/feathericons/power.svg
index 598308fc..598308fc 100644
--- a/src/qcam/assets/feathericons/power.svg
+++ b/src/apps/qcam/assets/feathericons/power.svg
diff --git a/src/qcam/assets/feathericons/printer.svg b/src/apps/qcam/assets/feathericons/printer.svg
index 8a9a7ace..8a9a7ace 100644
--- a/src/qcam/assets/feathericons/printer.svg
+++ b/src/apps/qcam/assets/feathericons/printer.svg
diff --git a/src/qcam/assets/feathericons/radio.svg b/src/apps/qcam/assets/feathericons/radio.svg
index 5abfcd13..5abfcd13 100644
--- a/src/qcam/assets/feathericons/radio.svg
+++ b/src/apps/qcam/assets/feathericons/radio.svg
diff --git a/src/qcam/assets/feathericons/refresh-ccw.svg b/src/apps/qcam/assets/feathericons/refresh-ccw.svg
index 10cff0ec..10cff0ec 100644
--- a/src/qcam/assets/feathericons/refresh-ccw.svg
+++ b/src/apps/qcam/assets/feathericons/refresh-ccw.svg
diff --git a/src/qcam/assets/feathericons/refresh-cw.svg b/src/apps/qcam/assets/feathericons/refresh-cw.svg
index 06c358dd..06c358dd 100644
--- a/src/qcam/assets/feathericons/refresh-cw.svg
+++ b/src/apps/qcam/assets/feathericons/refresh-cw.svg
diff --git a/src/qcam/assets/feathericons/repeat.svg b/src/apps/qcam/assets/feathericons/repeat.svg
index c7657b08..c7657b08 100644
--- a/src/qcam/assets/feathericons/repeat.svg
+++ b/src/apps/qcam/assets/feathericons/repeat.svg
diff --git a/src/qcam/assets/feathericons/rewind.svg b/src/apps/qcam/assets/feathericons/rewind.svg
index 7b0fa3d5..7b0fa3d5 100644
--- a/src/qcam/assets/feathericons/rewind.svg
+++ b/src/apps/qcam/assets/feathericons/rewind.svg
diff --git a/src/qcam/assets/feathericons/rotate-ccw.svg b/src/apps/qcam/assets/feathericons/rotate-ccw.svg
index ade5dc42..ade5dc42 100644
--- a/src/qcam/assets/feathericons/rotate-ccw.svg
+++ b/src/apps/qcam/assets/feathericons/rotate-ccw.svg
diff --git a/src/qcam/assets/feathericons/rotate-cw.svg b/src/apps/qcam/assets/feathericons/rotate-cw.svg
index 83dca351..83dca351 100644
--- a/src/qcam/assets/feathericons/rotate-cw.svg
+++ b/src/apps/qcam/assets/feathericons/rotate-cw.svg
diff --git a/src/qcam/assets/feathericons/rss.svg b/src/apps/qcam/assets/feathericons/rss.svg
index c9a13684..c9a13684 100644
--- a/src/qcam/assets/feathericons/rss.svg
+++ b/src/apps/qcam/assets/feathericons/rss.svg
diff --git a/src/qcam/assets/feathericons/save.svg b/src/apps/qcam/assets/feathericons/save.svg
index 46c72990..46c72990 100644
--- a/src/qcam/assets/feathericons/save.svg
+++ b/src/apps/qcam/assets/feathericons/save.svg
diff --git a/src/qcam/assets/feathericons/scissors.svg b/src/apps/qcam/assets/feathericons/scissors.svg
index fd0647ff..fd0647ff 100644
--- a/src/qcam/assets/feathericons/scissors.svg
+++ b/src/apps/qcam/assets/feathericons/scissors.svg
diff --git a/src/qcam/assets/feathericons/search.svg b/src/apps/qcam/assets/feathericons/search.svg
index 8710306d..8710306d 100644
--- a/src/qcam/assets/feathericons/search.svg
+++ b/src/apps/qcam/assets/feathericons/search.svg
diff --git a/src/qcam/assets/feathericons/send.svg b/src/apps/qcam/assets/feathericons/send.svg
index 42ef2a24..42ef2a24 100644
--- a/src/qcam/assets/feathericons/send.svg
+++ b/src/apps/qcam/assets/feathericons/send.svg
diff --git a/src/qcam/assets/feathericons/server.svg b/src/apps/qcam/assets/feathericons/server.svg
index 54ce094a..54ce094a 100644
--- a/src/qcam/assets/feathericons/server.svg
+++ b/src/apps/qcam/assets/feathericons/server.svg
diff --git a/src/qcam/assets/feathericons/settings.svg b/src/apps/qcam/assets/feathericons/settings.svg
index 19c27265..19c27265 100644
--- a/src/qcam/assets/feathericons/settings.svg
+++ b/src/apps/qcam/assets/feathericons/settings.svg
diff --git a/src/qcam/assets/feathericons/share-2.svg b/src/apps/qcam/assets/feathericons/share-2.svg
index 09b1c7bc..09b1c7bc 100644
--- a/src/qcam/assets/feathericons/share-2.svg
+++ b/src/apps/qcam/assets/feathericons/share-2.svg
diff --git a/src/qcam/assets/feathericons/share.svg b/src/apps/qcam/assets/feathericons/share.svg
index df38c14d..df38c14d 100644
--- a/src/qcam/assets/feathericons/share.svg
+++ b/src/apps/qcam/assets/feathericons/share.svg
diff --git a/src/qcam/assets/feathericons/shield-off.svg b/src/apps/qcam/assets/feathericons/shield-off.svg
index 18692ddd..18692ddd 100644
--- a/src/qcam/assets/feathericons/shield-off.svg
+++ b/src/apps/qcam/assets/feathericons/shield-off.svg
diff --git a/src/qcam/assets/feathericons/shield.svg b/src/apps/qcam/assets/feathericons/shield.svg
index c7c48413..c7c48413 100644
--- a/src/qcam/assets/feathericons/shield.svg
+++ b/src/apps/qcam/assets/feathericons/shield.svg
diff --git a/src/qcam/assets/feathericons/shopping-bag.svg b/src/apps/qcam/assets/feathericons/shopping-bag.svg
index eaa39e81..eaa39e81 100644
--- a/src/qcam/assets/feathericons/shopping-bag.svg
+++ b/src/apps/qcam/assets/feathericons/shopping-bag.svg
diff --git a/src/qcam/assets/feathericons/shopping-cart.svg b/src/apps/qcam/assets/feathericons/shopping-cart.svg
index 17a40bf4..17a40bf4 100644
--- a/src/qcam/assets/feathericons/shopping-cart.svg
+++ b/src/apps/qcam/assets/feathericons/shopping-cart.svg
diff --git a/src/qcam/assets/feathericons/shuffle.svg b/src/apps/qcam/assets/feathericons/shuffle.svg
index 8cfb5db5..8cfb5db5 100644
--- a/src/qcam/assets/feathericons/shuffle.svg
+++ b/src/apps/qcam/assets/feathericons/shuffle.svg
diff --git a/src/qcam/assets/feathericons/sidebar.svg b/src/apps/qcam/assets/feathericons/sidebar.svg
index 8ba817e6..8ba817e6 100644
--- a/src/qcam/assets/feathericons/sidebar.svg
+++ b/src/apps/qcam/assets/feathericons/sidebar.svg
diff --git a/src/qcam/assets/feathericons/skip-back.svg b/src/apps/qcam/assets/feathericons/skip-back.svg
index 88d024e2..88d024e2 100644
--- a/src/qcam/assets/feathericons/skip-back.svg
+++ b/src/apps/qcam/assets/feathericons/skip-back.svg
diff --git a/src/qcam/assets/feathericons/skip-forward.svg b/src/apps/qcam/assets/feathericons/skip-forward.svg
index f3fdac3a..f3fdac3a 100644
--- a/src/qcam/assets/feathericons/skip-forward.svg
+++ b/src/apps/qcam/assets/feathericons/skip-forward.svg
diff --git a/src/qcam/assets/feathericons/slack.svg b/src/apps/qcam/assets/feathericons/slack.svg
index 5d973466..5d973466 100644
--- a/src/qcam/assets/feathericons/slack.svg
+++ b/src/apps/qcam/assets/feathericons/slack.svg
diff --git a/src/qcam/assets/feathericons/slash.svg b/src/apps/qcam/assets/feathericons/slash.svg
index f4131b85..f4131b85 100644
--- a/src/qcam/assets/feathericons/slash.svg
+++ b/src/apps/qcam/assets/feathericons/slash.svg
diff --git a/src/qcam/assets/feathericons/sliders.svg b/src/apps/qcam/assets/feathericons/sliders.svg
index 19c93852..19c93852 100644
--- a/src/qcam/assets/feathericons/sliders.svg
+++ b/src/apps/qcam/assets/feathericons/sliders.svg
diff --git a/src/qcam/assets/feathericons/smartphone.svg b/src/apps/qcam/assets/feathericons/smartphone.svg
index 0171a95a..0171a95a 100644
--- a/src/qcam/assets/feathericons/smartphone.svg
+++ b/src/apps/qcam/assets/feathericons/smartphone.svg
diff --git a/src/qcam/assets/feathericons/smile.svg b/src/apps/qcam/assets/feathericons/smile.svg
index 24dc8a26..24dc8a26 100644
--- a/src/qcam/assets/feathericons/smile.svg
+++ b/src/apps/qcam/assets/feathericons/smile.svg
diff --git a/src/qcam/assets/feathericons/speaker.svg b/src/apps/qcam/assets/feathericons/speaker.svg
index 75d5ff9c..75d5ff9c 100644
--- a/src/qcam/assets/feathericons/speaker.svg
+++ b/src/apps/qcam/assets/feathericons/speaker.svg
diff --git a/src/qcam/assets/feathericons/square.svg b/src/apps/qcam/assets/feathericons/square.svg
index 6eabc77d..6eabc77d 100644
--- a/src/qcam/assets/feathericons/square.svg
+++ b/src/apps/qcam/assets/feathericons/square.svg
diff --git a/src/qcam/assets/feathericons/star.svg b/src/apps/qcam/assets/feathericons/star.svg
index bcdc31aa..bcdc31aa 100644
--- a/src/qcam/assets/feathericons/star.svg
+++ b/src/apps/qcam/assets/feathericons/star.svg
diff --git a/src/qcam/assets/feathericons/stop-circle.svg b/src/apps/qcam/assets/feathericons/stop-circle.svg
index c10d9d47..c10d9d47 100644
--- a/src/qcam/assets/feathericons/stop-circle.svg
+++ b/src/apps/qcam/assets/feathericons/stop-circle.svg
diff --git a/src/qcam/assets/feathericons/sun.svg b/src/apps/qcam/assets/feathericons/sun.svg
index 7f51b94d..7f51b94d 100644
--- a/src/qcam/assets/feathericons/sun.svg
+++ b/src/apps/qcam/assets/feathericons/sun.svg
diff --git a/src/qcam/assets/feathericons/sunrise.svg b/src/apps/qcam/assets/feathericons/sunrise.svg
index eff4b1e4..eff4b1e4 100644
--- a/src/qcam/assets/feathericons/sunrise.svg
+++ b/src/apps/qcam/assets/feathericons/sunrise.svg
diff --git a/src/qcam/assets/feathericons/sunset.svg b/src/apps/qcam/assets/feathericons/sunset.svg
index a5a22215..a5a22215 100644
--- a/src/qcam/assets/feathericons/sunset.svg
+++ b/src/apps/qcam/assets/feathericons/sunset.svg
diff --git a/src/qcam/assets/feathericons/tablet.svg b/src/apps/qcam/assets/feathericons/tablet.svg
index 9c80b40a..9c80b40a 100644
--- a/src/qcam/assets/feathericons/tablet.svg
+++ b/src/apps/qcam/assets/feathericons/tablet.svg
diff --git a/src/qcam/assets/feathericons/tag.svg b/src/apps/qcam/assets/feathericons/tag.svg
index 7219b15f..7219b15f 100644
--- a/src/qcam/assets/feathericons/tag.svg
+++ b/src/apps/qcam/assets/feathericons/tag.svg
diff --git a/src/qcam/assets/feathericons/target.svg b/src/apps/qcam/assets/feathericons/target.svg
index be84b17c..be84b17c 100644
--- a/src/qcam/assets/feathericons/target.svg
+++ b/src/apps/qcam/assets/feathericons/target.svg
diff --git a/src/qcam/assets/feathericons/terminal.svg b/src/apps/qcam/assets/feathericons/terminal.svg
index af459c04..af459c04 100644
--- a/src/qcam/assets/feathericons/terminal.svg
+++ b/src/apps/qcam/assets/feathericons/terminal.svg
diff --git a/src/qcam/assets/feathericons/thermometer.svg b/src/apps/qcam/assets/feathericons/thermometer.svg
index 33142ccc..33142ccc 100644
--- a/src/qcam/assets/feathericons/thermometer.svg
+++ b/src/apps/qcam/assets/feathericons/thermometer.svg
diff --git a/src/qcam/assets/feathericons/thumbs-down.svg b/src/apps/qcam/assets/feathericons/thumbs-down.svg
index 3e7bcd6d..3e7bcd6d 100644
--- a/src/qcam/assets/feathericons/thumbs-down.svg
+++ b/src/apps/qcam/assets/feathericons/thumbs-down.svg
diff --git a/src/qcam/assets/feathericons/thumbs-up.svg b/src/apps/qcam/assets/feathericons/thumbs-up.svg
index 226c44d8..226c44d8 100644
--- a/src/qcam/assets/feathericons/thumbs-up.svg
+++ b/src/apps/qcam/assets/feathericons/thumbs-up.svg
diff --git a/src/qcam/assets/feathericons/toggle-left.svg b/src/apps/qcam/assets/feathericons/toggle-left.svg
index 240be290..240be290 100644
--- a/src/qcam/assets/feathericons/toggle-left.svg
+++ b/src/apps/qcam/assets/feathericons/toggle-left.svg
diff --git a/src/qcam/assets/feathericons/toggle-right.svg b/src/apps/qcam/assets/feathericons/toggle-right.svg
index fc6e81c1..fc6e81c1 100644
--- a/src/qcam/assets/feathericons/toggle-right.svg
+++ b/src/apps/qcam/assets/feathericons/toggle-right.svg
diff --git a/src/qcam/assets/feathericons/tool.svg b/src/apps/qcam/assets/feathericons/tool.svg
index f3cbf3d9..f3cbf3d9 100644
--- a/src/qcam/assets/feathericons/tool.svg
+++ b/src/apps/qcam/assets/feathericons/tool.svg
diff --git a/src/qcam/assets/feathericons/trash-2.svg b/src/apps/qcam/assets/feathericons/trash-2.svg
index f24d55bf..f24d55bf 100644
--- a/src/qcam/assets/feathericons/trash-2.svg
+++ b/src/apps/qcam/assets/feathericons/trash-2.svg
diff --git a/src/qcam/assets/feathericons/trash.svg b/src/apps/qcam/assets/feathericons/trash.svg
index 55650bd4..55650bd4 100644
--- a/src/qcam/assets/feathericons/trash.svg
+++ b/src/apps/qcam/assets/feathericons/trash.svg
diff --git a/src/qcam/assets/feathericons/trello.svg b/src/apps/qcam/assets/feathericons/trello.svg
index b2f599b6..b2f599b6 100644
--- a/src/qcam/assets/feathericons/trello.svg
+++ b/src/apps/qcam/assets/feathericons/trello.svg
diff --git a/src/qcam/assets/feathericons/trending-down.svg b/src/apps/qcam/assets/feathericons/trending-down.svg
index a9d4cfa5..a9d4cfa5 100644
--- a/src/qcam/assets/feathericons/trending-down.svg
+++ b/src/apps/qcam/assets/feathericons/trending-down.svg
diff --git a/src/qcam/assets/feathericons/trending-up.svg b/src/apps/qcam/assets/feathericons/trending-up.svg
index 52026a4d..52026a4d 100644
--- a/src/qcam/assets/feathericons/trending-up.svg
+++ b/src/apps/qcam/assets/feathericons/trending-up.svg
diff --git a/src/qcam/assets/feathericons/triangle.svg b/src/apps/qcam/assets/feathericons/triangle.svg
index 274b6528..274b6528 100644
--- a/src/qcam/assets/feathericons/triangle.svg
+++ b/src/apps/qcam/assets/feathericons/triangle.svg
diff --git a/src/qcam/assets/feathericons/truck.svg b/src/apps/qcam/assets/feathericons/truck.svg
index 33898373..33898373 100644
--- a/src/qcam/assets/feathericons/truck.svg
+++ b/src/apps/qcam/assets/feathericons/truck.svg
diff --git a/src/qcam/assets/feathericons/tv.svg b/src/apps/qcam/assets/feathericons/tv.svg
index 955bbfff..955bbfff 100644
--- a/src/qcam/assets/feathericons/tv.svg
+++ b/src/apps/qcam/assets/feathericons/tv.svg
diff --git a/src/qcam/assets/feathericons/twitch.svg b/src/apps/qcam/assets/feathericons/twitch.svg
index 17062495..17062495 100644
--- a/src/qcam/assets/feathericons/twitch.svg
+++ b/src/apps/qcam/assets/feathericons/twitch.svg
diff --git a/src/qcam/assets/feathericons/twitter.svg b/src/apps/qcam/assets/feathericons/twitter.svg
index f8886eca..f8886eca 100644
--- a/src/qcam/assets/feathericons/twitter.svg
+++ b/src/apps/qcam/assets/feathericons/twitter.svg
diff --git a/src/qcam/assets/feathericons/type.svg b/src/apps/qcam/assets/feathericons/type.svg
index c6b2de33..c6b2de33 100644
--- a/src/qcam/assets/feathericons/type.svg
+++ b/src/apps/qcam/assets/feathericons/type.svg
diff --git a/src/qcam/assets/feathericons/umbrella.svg b/src/apps/qcam/assets/feathericons/umbrella.svg
index dc77c0cb..dc77c0cb 100644
--- a/src/qcam/assets/feathericons/umbrella.svg
+++ b/src/apps/qcam/assets/feathericons/umbrella.svg
diff --git a/src/qcam/assets/feathericons/underline.svg b/src/apps/qcam/assets/feathericons/underline.svg
index 044945d4..044945d4 100644
--- a/src/qcam/assets/feathericons/underline.svg
+++ b/src/apps/qcam/assets/feathericons/underline.svg
diff --git a/src/qcam/assets/feathericons/unlock.svg b/src/apps/qcam/assets/feathericons/unlock.svg
index 01dc3597..01dc3597 100644
--- a/src/qcam/assets/feathericons/unlock.svg
+++ b/src/apps/qcam/assets/feathericons/unlock.svg
diff --git a/src/qcam/assets/feathericons/upload-cloud.svg b/src/apps/qcam/assets/feathericons/upload-cloud.svg
index a1db297c..a1db297c 100644
--- a/src/qcam/assets/feathericons/upload-cloud.svg
+++ b/src/apps/qcam/assets/feathericons/upload-cloud.svg
diff --git a/src/qcam/assets/feathericons/upload.svg b/src/apps/qcam/assets/feathericons/upload.svg
index 91eaff75..91eaff75 100644
--- a/src/qcam/assets/feathericons/upload.svg
+++ b/src/apps/qcam/assets/feathericons/upload.svg
diff --git a/src/qcam/assets/feathericons/user-check.svg b/src/apps/qcam/assets/feathericons/user-check.svg
index 42f91b29..42f91b29 100644
--- a/src/qcam/assets/feathericons/user-check.svg
+++ b/src/apps/qcam/assets/feathericons/user-check.svg
diff --git a/src/qcam/assets/feathericons/user-minus.svg b/src/apps/qcam/assets/feathericons/user-minus.svg
index 44b75f5a..44b75f5a 100644
--- a/src/qcam/assets/feathericons/user-minus.svg
+++ b/src/apps/qcam/assets/feathericons/user-minus.svg
diff --git a/src/qcam/assets/feathericons/user-plus.svg b/src/apps/qcam/assets/feathericons/user-plus.svg
index 21460f6e..21460f6e 100644
--- a/src/qcam/assets/feathericons/user-plus.svg
+++ b/src/apps/qcam/assets/feathericons/user-plus.svg
diff --git a/src/qcam/assets/feathericons/user-x.svg b/src/apps/qcam/assets/feathericons/user-x.svg
index 0c41a481..0c41a481 100644
--- a/src/qcam/assets/feathericons/user-x.svg
+++ b/src/apps/qcam/assets/feathericons/user-x.svg
diff --git a/src/qcam/assets/feathericons/user.svg b/src/apps/qcam/assets/feathericons/user.svg
index 7bb5f291..7bb5f291 100644
--- a/src/qcam/assets/feathericons/user.svg
+++ b/src/apps/qcam/assets/feathericons/user.svg
diff --git a/src/qcam/assets/feathericons/users.svg b/src/apps/qcam/assets/feathericons/users.svg
index aacf6b08..aacf6b08 100644
--- a/src/qcam/assets/feathericons/users.svg
+++ b/src/apps/qcam/assets/feathericons/users.svg
diff --git a/src/qcam/assets/feathericons/video-off.svg b/src/apps/qcam/assets/feathericons/video-off.svg
index 08ec6973..08ec6973 100644
--- a/src/qcam/assets/feathericons/video-off.svg
+++ b/src/apps/qcam/assets/feathericons/video-off.svg
diff --git a/src/qcam/assets/feathericons/video.svg b/src/apps/qcam/assets/feathericons/video.svg
index 8ff156aa..8ff156aa 100644
--- a/src/qcam/assets/feathericons/video.svg
+++ b/src/apps/qcam/assets/feathericons/video.svg
diff --git a/src/qcam/assets/feathericons/voicemail.svg b/src/apps/qcam/assets/feathericons/voicemail.svg
index 5d78a8e7..5d78a8e7 100644
--- a/src/qcam/assets/feathericons/voicemail.svg
+++ b/src/apps/qcam/assets/feathericons/voicemail.svg
diff --git a/src/qcam/assets/feathericons/volume-1.svg b/src/apps/qcam/assets/feathericons/volume-1.svg
index 150e875f..150e875f 100644
--- a/src/qcam/assets/feathericons/volume-1.svg
+++ b/src/apps/qcam/assets/feathericons/volume-1.svg
diff --git a/src/qcam/assets/feathericons/volume-2.svg b/src/apps/qcam/assets/feathericons/volume-2.svg
index 03d521c7..03d521c7 100644
--- a/src/qcam/assets/feathericons/volume-2.svg
+++ b/src/apps/qcam/assets/feathericons/volume-2.svg
diff --git a/src/qcam/assets/feathericons/volume-x.svg b/src/apps/qcam/assets/feathericons/volume-x.svg
index be442406..be442406 100644
--- a/src/qcam/assets/feathericons/volume-x.svg
+++ b/src/apps/qcam/assets/feathericons/volume-x.svg
diff --git a/src/qcam/assets/feathericons/volume.svg b/src/apps/qcam/assets/feathericons/volume.svg
index 53bfe15e..53bfe15e 100644
--- a/src/qcam/assets/feathericons/volume.svg
+++ b/src/apps/qcam/assets/feathericons/volume.svg
diff --git a/src/qcam/assets/feathericons/watch.svg b/src/apps/qcam/assets/feathericons/watch.svg
index a1099da3..a1099da3 100644
--- a/src/qcam/assets/feathericons/watch.svg
+++ b/src/apps/qcam/assets/feathericons/watch.svg
diff --git a/src/qcam/assets/feathericons/wifi-off.svg b/src/apps/qcam/assets/feathericons/wifi-off.svg
index 35eae43b..35eae43b 100644
--- a/src/qcam/assets/feathericons/wifi-off.svg
+++ b/src/apps/qcam/assets/feathericons/wifi-off.svg
diff --git a/src/qcam/assets/feathericons/wifi.svg b/src/apps/qcam/assets/feathericons/wifi.svg
index 748c285e..748c285e 100644
--- a/src/qcam/assets/feathericons/wifi.svg
+++ b/src/apps/qcam/assets/feathericons/wifi.svg
diff --git a/src/qcam/assets/feathericons/wind.svg b/src/apps/qcam/assets/feathericons/wind.svg
index 82b36468..82b36468 100644
--- a/src/qcam/assets/feathericons/wind.svg
+++ b/src/apps/qcam/assets/feathericons/wind.svg
diff --git a/src/qcam/assets/feathericons/x-circle.svg b/src/apps/qcam/assets/feathericons/x-circle.svg
index 94aad5e5..94aad5e5 100644
--- a/src/qcam/assets/feathericons/x-circle.svg
+++ b/src/apps/qcam/assets/feathericons/x-circle.svg
diff --git a/src/qcam/assets/feathericons/x-octagon.svg b/src/apps/qcam/assets/feathericons/x-octagon.svg
index 85431985..85431985 100644
--- a/src/qcam/assets/feathericons/x-octagon.svg
+++ b/src/apps/qcam/assets/feathericons/x-octagon.svg
diff --git a/src/qcam/assets/feathericons/x-square.svg b/src/apps/qcam/assets/feathericons/x-square.svg
index 7677c387..7677c387 100644
--- a/src/qcam/assets/feathericons/x-square.svg
+++ b/src/apps/qcam/assets/feathericons/x-square.svg
diff --git a/src/qcam/assets/feathericons/x.svg b/src/apps/qcam/assets/feathericons/x.svg
index 7d5875ca..7d5875ca 100644
--- a/src/qcam/assets/feathericons/x.svg
+++ b/src/apps/qcam/assets/feathericons/x.svg
diff --git a/src/qcam/assets/feathericons/youtube.svg b/src/apps/qcam/assets/feathericons/youtube.svg
index c4824385..c4824385 100644
--- a/src/qcam/assets/feathericons/youtube.svg
+++ b/src/apps/qcam/assets/feathericons/youtube.svg
diff --git a/src/qcam/assets/feathericons/zap-off.svg b/src/apps/qcam/assets/feathericons/zap-off.svg
index c636f8bb..c636f8bb 100644
--- a/src/qcam/assets/feathericons/zap-off.svg
+++ b/src/apps/qcam/assets/feathericons/zap-off.svg
diff --git a/src/qcam/assets/feathericons/zap.svg b/src/apps/qcam/assets/feathericons/zap.svg
index 8fdafa93..8fdafa93 100644
--- a/src/qcam/assets/feathericons/zap.svg
+++ b/src/apps/qcam/assets/feathericons/zap.svg
diff --git a/src/qcam/assets/feathericons/zoom-in.svg b/src/apps/qcam/assets/feathericons/zoom-in.svg
index da4572d2..da4572d2 100644
--- a/src/qcam/assets/feathericons/zoom-in.svg
+++ b/src/apps/qcam/assets/feathericons/zoom-in.svg
diff --git a/src/qcam/assets/feathericons/zoom-out.svg b/src/apps/qcam/assets/feathericons/zoom-out.svg
index fd678d72..fd678d72 100644
--- a/src/qcam/assets/feathericons/zoom-out.svg
+++ b/src/apps/qcam/assets/feathericons/zoom-out.svg
diff --git a/src/qcam/assets/shader/RGB.frag b/src/apps/qcam/assets/shader/RGB.frag
index 4c374ac9..4c374ac9 100644
--- a/src/qcam/assets/shader/RGB.frag
+++ b/src/apps/qcam/assets/shader/RGB.frag
diff --git a/src/qcam/assets/shader/YUV_2_planes.frag b/src/apps/qcam/assets/shader/YUV_2_planes.frag
index 254463c0..1d5d1206 100644
--- a/src/qcam/assets/shader/YUV_2_planes.frag
+++ b/src/apps/qcam/assets/shader/YUV_2_planes.frag
@@ -13,27 +13,30 @@ varying vec2 textureOut;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
+const mat3 yuv2rgb_matrix = mat3(
+ YUV2RGB_MATRIX
+);
+
+const vec3 yuv2rgb_offset = vec3(
+ YUV2RGB_Y_OFFSET / 255.0, 128.0 / 255.0, 128.0 / 255.0
+);
+
void main(void)
{
vec3 yuv;
- vec3 rgb;
- mat3 yuv2rgb_bt601_mat = mat3(
- vec3(1.164, 1.164, 1.164),
- vec3(0.000, -0.392, 2.017),
- vec3(1.596, -0.813, 0.000)
- );
-
- yuv.x = texture2D(tex_y, textureOut).r - 0.063;
+
+ yuv.x = texture2D(tex_y, textureOut).r;
#if defined(YUV_PATTERN_UV)
- yuv.y = texture2D(tex_u, textureOut).r - 0.500;
- yuv.z = texture2D(tex_u, textureOut).a - 0.500;
+ yuv.y = texture2D(tex_u, textureOut).r;
+ yuv.z = texture2D(tex_u, textureOut).a;
#elif defined(YUV_PATTERN_VU)
- yuv.y = texture2D(tex_u, textureOut).a - 0.500;
- yuv.z = texture2D(tex_u, textureOut).r - 0.500;
+ yuv.y = texture2D(tex_u, textureOut).a;
+ yuv.z = texture2D(tex_u, textureOut).r;
#else
#error Invalid pattern
#endif
- rgb = yuv2rgb_bt601_mat * yuv;
+ vec3 rgb = yuv2rgb_matrix * (yuv - yuv2rgb_offset);
+
gl_FragColor = vec4(rgb, 1.0);
}
diff --git a/src/qcam/assets/shader/YUV_3_planes.frag b/src/apps/qcam/assets/shader/YUV_3_planes.frag
index 2be74b5d..8f788e90 100644
--- a/src/qcam/assets/shader/YUV_3_planes.frag
+++ b/src/apps/qcam/assets/shader/YUV_3_planes.frag
@@ -14,20 +14,23 @@ uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
+const mat3 yuv2rgb_matrix = mat3(
+ YUV2RGB_MATRIX
+);
+
+const vec3 yuv2rgb_offset = vec3(
+ YUV2RGB_Y_OFFSET / 255.0, 128.0 / 255.0, 128.0 / 255.0
+);
+
void main(void)
{
vec3 yuv;
- vec3 rgb;
- mat3 yuv2rgb_bt601_mat = mat3(
- vec3(1.164, 1.164, 1.164),
- vec3(0.000, -0.392, 2.017),
- vec3(1.596, -0.813, 0.000)
- );
-
- yuv.x = texture2D(tex_y, textureOut).r - 0.063;
- yuv.y = texture2D(tex_u, textureOut).r - 0.500;
- yuv.z = texture2D(tex_v, textureOut).r - 0.500;
-
- rgb = yuv2rgb_bt601_mat * yuv;
+
+ yuv.x = texture2D(tex_y, textureOut).r;
+ yuv.y = texture2D(tex_u, textureOut).r;
+ yuv.z = texture2D(tex_v, textureOut).r;
+
+ vec3 rgb = yuv2rgb_matrix * (yuv - yuv2rgb_offset);
+
gl_FragColor = vec4(rgb, 1.0);
}
diff --git a/src/qcam/assets/shader/YUV_packed.frag b/src/apps/qcam/assets/shader/YUV_packed.frag
index d6efd4ce..b9ef9d41 100644
--- a/src/qcam/assets/shader/YUV_packed.frag
+++ b/src/apps/qcam/assets/shader/YUV_packed.frag
@@ -14,15 +14,16 @@ varying vec2 textureOut;
uniform sampler2D tex_y;
uniform vec2 tex_step;
+const mat3 yuv2rgb_matrix = mat3(
+ YUV2RGB_MATRIX
+);
+
+const vec3 yuv2rgb_offset = vec3(
+ YUV2RGB_Y_OFFSET / 255.0, 128.0 / 255.0, 128.0 / 255.0
+);
+
void main(void)
{
- mat3 yuv2rgb_bt601_mat = mat3(
- vec3(1.164, 1.164, 1.164),
- vec3(0.000, -0.392, 2.017),
- vec3(1.596, -0.813, 0.000)
- );
- vec3 yuv2rgb_bt601_offset = vec3(0.063, 0.500, 0.500);
-
/*
* The sampler won't interpolate the texture correctly along the X axis,
* as each RGBA pixel effectively stores two pixels. We thus need to
@@ -76,7 +77,7 @@ void main(void)
float y = mix(y_left, y_right, step(0.5, f_x));
- vec3 rgb = yuv2rgb_bt601_mat * (vec3(y, uv) - yuv2rgb_bt601_offset);
+ vec3 rgb = yuv2rgb_matrix * (vec3(y, uv) - yuv2rgb_offset);
gl_FragColor = vec4(rgb, 1.0);
}
diff --git a/src/qcam/assets/shader/bayer_1x_packed.frag b/src/apps/qcam/assets/shader/bayer_1x_packed.frag
index f53f5575..f53f5575 100644
--- a/src/qcam/assets/shader/bayer_1x_packed.frag
+++ b/src/apps/qcam/assets/shader/bayer_1x_packed.frag
diff --git a/src/qcam/assets/shader/bayer_8.frag b/src/apps/qcam/assets/shader/bayer_8.frag
index 4ece44ab..7e35ca88 100644
--- a/src/qcam/assets/shader/bayer_8.frag
+++ b/src/apps/qcam/assets/shader/bayer_8.frag
@@ -15,6 +15,9 @@ Copyright (C) 2021, Linaro
*/
//Pixel Shader
+#ifdef GL_ES
+precision mediump float;
+#endif
/** Monochrome RGBA or GL_LUMINANCE Bayer encoded texture.*/
uniform sampler2D tex_y;
diff --git a/src/qcam/assets/shader/bayer_8.vert b/src/apps/qcam/assets/shader/bayer_8.vert
index 3695a5e9..3695a5e9 100644
--- a/src/qcam/assets/shader/bayer_8.vert
+++ b/src/apps/qcam/assets/shader/bayer_8.vert
diff --git a/src/qcam/assets/shader/identity.vert b/src/apps/qcam/assets/shader/identity.vert
index 12c41377..12c41377 100644
--- a/src/qcam/assets/shader/identity.vert
+++ b/src/apps/qcam/assets/shader/identity.vert
diff --git a/src/qcam/assets/shader/shaders.qrc b/src/apps/qcam/assets/shader/shaders.qrc
index 96c709f9..96c709f9 100644
--- a/src/qcam/assets/shader/shaders.qrc
+++ b/src/apps/qcam/assets/shader/shaders.qrc
diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
new file mode 100644
index 00000000..3c8b12a9
--- /dev/null
+++ b/src/apps/qcam/cam_select_dialog.cpp
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Utkarsh Tiwari <utkarsh02t@gmail.com>
+ *
+ * cam_select_dialog.cpp - qcam - Camera Selection dialog
+ */
+
+#include "cam_select_dialog.h"
+
+#include <memory>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QFormLayout>
+#include <QLabel>
+#include <QString>
+
+CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager,
+ QWidget *parent)
+ : QDialog(parent), cm_(cameraManager)
+{
+ /* Use a QFormLayout for the dialog. */
+ QFormLayout *layout = new QFormLayout(this);
+
+ /* Setup the camera id combo-box. */
+ cameraIdComboBox_ = new QComboBox;
+ for (const auto &cam : cm_->cameras())
+ cameraIdComboBox_->addItem(QString::fromStdString(cam->id()));
+
+ /* Set camera information labels. */
+ cameraLocation_ = new QLabel;
+ cameraModel_ = new QLabel;
+
+ updateCameraInfo(cameraIdComboBox_->currentText());
+ connect(cameraIdComboBox_, &QComboBox::currentTextChanged,
+ this, &CameraSelectorDialog::updateCameraInfo);
+
+ /* Setup the QDialogButton Box */
+ QDialogButtonBox *buttonBox =
+ new QDialogButtonBox(QDialogButtonBox::Ok |
+ QDialogButtonBox::Cancel);
+
+ connect(buttonBox, &QDialogButtonBox::accepted,
+ this, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected,
+ this, &QDialog::reject);
+
+ /* Set the layout. */
+ layout->addRow("Camera:", cameraIdComboBox_);
+ layout->addRow("Location:", cameraLocation_);
+ layout->addRow("Model:", cameraModel_);
+ layout->addWidget(buttonBox);
+}
+
+CameraSelectorDialog::~CameraSelectorDialog() = default;
+
+std::string CameraSelectorDialog::getCameraId()
+{
+ return cameraIdComboBox_->currentText().toStdString();
+}
+
+/* Hotplug / Unplug Support. */
+void CameraSelectorDialog::addCamera(QString cameraId)
+{
+ cameraIdComboBox_->addItem(cameraId);
+}
+
+void CameraSelectorDialog::removeCamera(QString cameraId)
+{
+ int cameraIndex = cameraIdComboBox_->findText(cameraId);
+ cameraIdComboBox_->removeItem(cameraIndex);
+}
+
+/* Camera Information */
+void CameraSelectorDialog::updateCameraInfo(QString cameraId)
+{
+ const std::shared_ptr<libcamera::Camera> &camera =
+ cm_->get(cameraId.toStdString());
+
+ if (!camera)
+ return;
+
+ const libcamera::ControlList &properties = camera->properties();
+
+ const auto &location = properties.get(libcamera::properties::Location);
+ if (location) {
+ switch (*location) {
+ case libcamera::properties::CameraLocationFront:
+ cameraLocation_->setText("Internal front camera");
+ break;
+ case libcamera::properties::CameraLocationBack:
+ cameraLocation_->setText("Internal back camera");
+ break;
+ case libcamera::properties::CameraLocationExternal:
+ cameraLocation_->setText("External camera");
+ break;
+ default:
+ cameraLocation_->setText("Unknown");
+ }
+ } else {
+ cameraLocation_->setText("Unknown");
+ }
+
+ const auto &model = properties.get(libcamera::properties::Model)
+ .value_or("Unknown");
+
+ cameraModel_->setText(QString::fromStdString(model));
+}
diff --git a/src/apps/qcam/cam_select_dialog.h b/src/apps/qcam/cam_select_dialog.h
new file mode 100644
index 00000000..0b7709ed
--- /dev/null
+++ b/src/apps/qcam/cam_select_dialog.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Utkarsh Tiwari <utkarsh02t@gmail.com>
+ *
+ * cam_select_dialog.h - qcam - Camera Selection dialog
+ */
+
+#pragma once
+
+#include <string>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
+#include <libcamera/property_ids.h>
+
+#include <QDialog>
+#include <QString>
+
+class QComboBox;
+class QLabel;
+
+class CameraSelectorDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ CameraSelectorDialog(libcamera::CameraManager *cameraManager,
+ QWidget *parent);
+ ~CameraSelectorDialog();
+
+ std::string getCameraId();
+
+ /* Hotplug / Unplug Support. */
+ void addCamera(QString cameraId);
+ void removeCamera(QString cameraId);
+
+ /* Camera Information */
+ void updateCameraInfo(QString cameraId);
+
+private:
+ libcamera::CameraManager *cm_;
+
+ /* UI elements. */
+ QComboBox *cameraIdComboBox_;
+ QLabel *cameraLocation_;
+ QLabel *cameraModel_;
+};
diff --git a/src/qcam/format_converter.cpp b/src/apps/qcam/format_converter.cpp
index d4d3223b..de76b32c 100644
--- a/src/qcam/format_converter.cpp
+++ b/src/apps/qcam/format_converter.cpp
@@ -14,7 +14,7 @@
#include <libcamera/formats.h>
-#include "../cam/image.h"
+#include "../common/image.h"
#define RGBSHIFT 8
#ifndef MAX
@@ -93,6 +93,7 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
bpp_ = 3;
break;
case libcamera::formats::ARGB8888:
+ case libcamera::formats::XRGB8888:
formatFamily_ = RGB;
r_pos_ = 2;
g_pos_ = 1;
@@ -100,6 +101,7 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
bpp_ = 4;
break;
case libcamera::formats::RGBA8888:
+ case libcamera::formats::RGBX8888:
formatFamily_ = RGB;
r_pos_ = 3;
g_pos_ = 2;
@@ -107,6 +109,7 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
bpp_ = 4;
break;
case libcamera::formats::ABGR8888:
+ case libcamera::formats::XBGR8888:
formatFamily_ = RGB;
r_pos_ = 0;
g_pos_ = 1;
@@ -114,6 +117,7 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
bpp_ = 4;
break;
case libcamera::formats::BGRA8888:
+ case libcamera::formats::BGRX8888:
formatFamily_ = RGB;
r_pos_ = 1;
g_pos_ = 2;
diff --git a/src/qcam/format_converter.h b/src/apps/qcam/format_converter.h
index 37dbfae2..37dbfae2 100644
--- a/src/qcam/format_converter.h
+++ b/src/apps/qcam/format_converter.h
diff --git a/src/qcam/main.cpp b/src/apps/qcam/main.cpp
index d3f01a85..36cb93a5 100644
--- a/src/qcam/main.cpp
+++ b/src/apps/qcam/main.cpp
@@ -13,8 +13,9 @@
#include <libcamera/camera_manager.h>
-#include "../cam/options.h"
-#include "../cam/stream_options.h"
+#include "../common/options.h"
+#include "../common/stream_options.h"
+
#include "main_window.h"
#include "message_handler.h"
diff --git a/src/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp
index dd0e51f5..0f16c038 100644
--- a/src/qcam/main_window.cpp
+++ b/src/apps/qcam/main_window.cpp
@@ -14,12 +14,10 @@
#include <libcamera/camera_manager.h>
#include <libcamera/version.h>
-#include <QComboBox>
#include <QCoreApplication>
#include <QFileDialog>
#include <QImage>
#include <QImageWriter>
-#include <QInputDialog>
#include <QMutexLocker>
#include <QStandardPaths>
#include <QStringList>
@@ -28,8 +26,10 @@
#include <QToolButton>
#include <QtDebug>
-#include "../cam/image.h"
-#include "dng_writer.h"
+#include "../common/dng_writer.h"
+#include "../common/image.h"
+
+#include "cam_select_dialog.h"
#ifndef QT_NO_OPENGL
#include "viewfinder_gl.h"
#endif
@@ -119,14 +119,14 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
if (renderType == "qt") {
ViewFinderQt *viewfinder = new ViewFinderQt(this);
connect(viewfinder, &ViewFinderQt::renderComplete,
- this, &MainWindow::queueRequest);
+ this, &MainWindow::renderComplete);
viewfinder_ = viewfinder;
setCentralWidget(viewfinder);
#ifndef QT_NO_OPENGL
} else if (renderType == "gles") {
ViewFinderGL *viewfinder = new ViewFinderGL(this);
connect(viewfinder, &ViewFinderGL::renderComplete,
- this, &MainWindow::queueRequest);
+ this, &MainWindow::renderComplete);
viewfinder_ = viewfinder;
setCentralWidget(viewfinder);
#endif
@@ -143,6 +143,8 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
cm_->cameraAdded.connect(this, &MainWindow::addCamera);
cm_->cameraRemoved.connect(this, &MainWindow::removeCamera);
+ cameraSelectorDialog_ = new CameraSelectorDialog(cm_, this);
+
/* Open the camera and start capture. */
ret = openCamera();
if (ret < 0) {
@@ -192,14 +194,11 @@ int MainWindow::createToolbars()
connect(action, &QAction::triggered, this, &MainWindow::quit);
/* Camera selector. */
- cameraCombo_ = new QComboBox();
- connect(cameraCombo_, QOverload<int>::of(&QComboBox::activated),
+ cameraSelectButton_ = new QPushButton;
+ connect(cameraSelectButton_, &QPushButton::clicked,
this, &MainWindow::switchCamera);
- for (const std::shared_ptr<Camera> &cam : cm_->cameras())
- cameraCombo_->addItem(QString::fromStdString(cam->id()));
-
- toolbar_->addWidget(cameraCombo_);
+ toolbar_->addWidget(cameraSelectButton_);
toolbar_->addSeparator();
@@ -259,14 +258,18 @@ void MainWindow::updateTitle()
* Camera Selection
*/
-void MainWindow::switchCamera(int index)
+void MainWindow::switchCamera()
{
/* Get and acquire the new camera. */
- const auto &cameras = cm_->cameras();
- if (static_cast<unsigned int>(index) >= cameras.size())
+ std::string newCameraId = chooseCamera();
+
+ if (newCameraId.empty())
+ return;
+
+ if (camera_ && newCameraId == camera_->id())
return;
- const std::shared_ptr<Camera> &cam = cameras[index];
+ const std::shared_ptr<Camera> &cam = cm_->get(newCameraId);
if (cam->acquire()) {
qInfo() << "Failed to acquire camera" << cam->id().c_str();
@@ -281,32 +284,23 @@ void MainWindow::switchCamera(int index)
*/
startStopAction_->setChecked(false);
- camera_->release();
+ if (camera_)
+ camera_->release();
+
camera_ = cam;
startStopAction_->setChecked(true);
+
+ /* Display the current cameraId in the toolbar .*/
+ cameraSelectButton_->setText(QString::fromStdString(newCameraId));
}
std::string MainWindow::chooseCamera()
{
- QStringList cameras;
- bool result;
-
- /* If only one camera is available, use it automatically. */
- if (cm_->cameras().size() == 1)
- return cm_->cameras()[0]->id();
-
- /* Present a dialog box to pick a camera. */
- for (const std::shared_ptr<Camera> &cam : cm_->cameras())
- cameras.append(QString::fromStdString(cam->id()));
-
- QString id = QInputDialog::getItem(this, "Select Camera",
- "Camera:", cameras, 0,
- false, &result);
- if (!result)
+ if (cameraSelectorDialog_->exec() != QDialog::Accepted)
return std::string();
- return id.toStdString();
+ return cameraSelectorDialog_->getCameraId();
}
int MainWindow::openCamera()
@@ -338,8 +332,8 @@ int MainWindow::openCamera()
return -EBUSY;
}
- /* Set the combo-box entry with the currently selected Camera. */
- cameraCombo_->setCurrentText(QString::fromStdString(cameraName));
+ /* Set the camera switch button with the currently selected Camera id. */
+ cameraSelectButton_->setText(QString::fromStdString(cameraName));
return 0;
}
@@ -368,7 +362,7 @@ void MainWindow::toggleCapture(bool start)
*/
int MainWindow::startCapture()
{
- StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
+ std::vector<StreamRole> roles = StreamKeyValueParser::roles(options_[OptStream]);
int ret;
/* Verify roles are supported. */
@@ -387,11 +381,8 @@ int MainWindow::startCapture()
}
break;
default:
- if (roles.size() != 1) {
- qWarning() << "Unsupported stream configuration";
- return -EINVAL;
- }
- break;
+ qWarning() << "Unsupported stream configuration";
+ return -EINVAL;
}
/* Configure the camera. */
@@ -446,9 +437,13 @@ int MainWindow::startCapture()
else
rawStream_ = nullptr;
- /* Configure the viewfinder. */
+ /*
+ * Configure the viewfinder. If no color space is reported, default to
+ * sYCC.
+ */
ret = viewfinder_->setFormat(vfConfig.pixelFormat,
QSize(vfConfig.size.width, vfConfig.size.height),
+ vfConfig.colorSpace.value_or(ColorSpace::Sycc),
vfConfig.stride);
if (ret < 0) {
qInfo() << "Failed to set viewfinder format";
@@ -521,7 +516,7 @@ int MainWindow::startCapture()
/* Queue all requests. */
for (std::unique_ptr<Request> &request : requests_) {
- ret = camera_->queueRequest(request.get());
+ ret = queueRequest(request.get());
if (ret < 0) {
qWarning() << "Can't queue request";
goto error_disconnect;
@@ -601,21 +596,20 @@ void MainWindow::stopCapture()
void MainWindow::processHotplug(HotplugEvent *e)
{
Camera *camera = e->camera();
+ QString cameraId = QString::fromStdString(camera->id());
HotplugEvent::PlugEvent event = e->hotplugEvent();
if (event == HotplugEvent::HotPlug) {
- cameraCombo_->addItem(QString::fromStdString(camera->id()));
+ cameraSelectorDialog_->addCamera(cameraId);
} else if (event == HotplugEvent::HotUnplug) {
/* Check if the currently-streaming camera is removed. */
if (camera == camera_.get()) {
toggleCapture(false);
camera_->release();
camera_.reset();
- cameraCombo_->setCurrentIndex(0);
}
- int camIndex = cameraCombo_->findText(QString::fromStdString(camera->id()));
- cameraCombo_->removeItem(camIndex);
+ cameraSelectorDialog_->removeCamera(cameraId);
}
}
@@ -755,7 +749,7 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)
viewfinder_->render(buffer, mappedBuffers_[buffer].get());
}
-void MainWindow::queueRequest(FrameBuffer *buffer)
+void MainWindow::renderComplete(FrameBuffer *buffer)
{
Request *request;
{
@@ -784,6 +778,10 @@ void MainWindow::queueRequest(FrameBuffer *buffer)
qWarning() << "No free buffer available for RAW capture";
}
}
+ queueRequest(request);
+}
- camera_->queueRequest(request);
+int MainWindow::queueRequest(Request *request)
+{
+ return camera_->queueRequest(request);
}
diff --git a/src/qcam/main_window.h b/src/apps/qcam/main_window.h
index 3fbe872c..2e3e1b5c 100644
--- a/src/qcam/main_window.h
+++ b/src/apps/qcam/main_window.h
@@ -10,28 +10,30 @@
#include <memory>
#include <vector>
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/framebuffer_allocator.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.h>
+
#include <QElapsedTimer>
#include <QIcon>
#include <QMainWindow>
#include <QMutex>
#include <QObject>
+#include <QPushButton>
#include <QQueue>
#include <QTimer>
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-#include <libcamera/controls.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/framebuffer_allocator.h>
-#include <libcamera/request.h>
-#include <libcamera/stream.h>
+#include "../common/stream_options.h"
-#include "../cam/stream_options.h"
#include "viewfinder.h"
class QAction;
-class QComboBox;
+class CameraSelectorDialog;
class Image;
class HotplugEvent;
@@ -58,7 +60,7 @@ private Q_SLOTS:
void quit();
void updateTitle();
- void switchCamera(int index);
+ void switchCamera();
void toggleCapture(bool start);
void saveImageAs();
@@ -66,7 +68,7 @@ private Q_SLOTS:
void processRaw(libcamera::FrameBuffer *buffer,
const libcamera::ControlList &metadata);
- void queueRequest(libcamera::FrameBuffer *buffer);
+ void renderComplete(libcamera::FrameBuffer *buffer);
private:
int createToolbars();
@@ -80,6 +82,7 @@ private:
void addCamera(std::shared_ptr<libcamera::Camera> camera);
void removeCamera(std::shared_ptr<libcamera::Camera> camera);
+ int queueRequest(libcamera::Request *request);
void requestComplete(libcamera::Request *request);
void processCapture();
void processHotplug(HotplugEvent *e);
@@ -88,7 +91,7 @@ private:
/* UI elements */
QToolBar *toolbar_;
QAction *startStopAction_;
- QComboBox *cameraCombo_;
+ QPushButton *cameraSelectButton_;
QAction *saveRaw_;
ViewFinder *viewfinder_;
@@ -98,6 +101,8 @@ private:
QString title_;
QTimer titleTimer_;
+ CameraSelectorDialog *cameraSelectorDialog_;
+
/* Options */
const OptionsParser::Options &options_;
diff --git a/src/qcam/meson.build b/src/apps/qcam/meson.build
index c46f4631..6cf4c171 100644
--- a/src/qcam/meson.build
+++ b/src/apps/qcam/meson.build
@@ -15,9 +15,7 @@ endif
qcam_enabled = true
qcam_sources = files([
- '../cam/image.cpp',
- '../cam/options.cpp',
- '../cam/stream_options.cpp',
+ 'cam_select_dialog.cpp',
'format_converter.cpp',
'main.cpp',
'main_window.cpp',
@@ -26,6 +24,7 @@ qcam_sources = files([
])
qcam_moc_headers = files([
+ 'cam_select_dialog.h',
'main_window.h',
'viewfinder_qt.h',
])
@@ -34,22 +33,7 @@ qcam_resources = files([
'assets/feathericons/feathericons.qrc',
])
-qcam_deps = [
- libatomic,
- libcamera_public,
- qt5_dep,
-]
-
-qt5_cpp_args = ['-DQT_NO_KEYWORDS']
-
-tiff_dep = dependency('libtiff-4', required : false)
-if tiff_dep.found()
- qt5_cpp_args += ['-DHAVE_TIFF']
- qcam_deps += [tiff_dep]
- qcam_sources += files([
- 'dng_writer.cpp',
- ])
-endif
+qt5_cpp_args = [apps_cpp_args, '-DQT_NO_KEYWORDS']
if cxx.has_header_symbol('QOpenGLWidget', 'QOpenGLWidget',
dependencies : qt5_dep, args : '-fPIC')
@@ -73,11 +57,18 @@ if ((cc.get_id() == 'gcc' and cc.version().version_compare('>=9.0') and
qt5_cpp_args += ['-Wno-deprecated-copy']
endif
-resources = qt5.preprocess(moc_headers: qcam_moc_headers,
+resources = qt5.preprocess(moc_headers : qcam_moc_headers,
qresources : qcam_resources,
- dependencies: qt5_dep)
+ dependencies : qt5_dep)
qcam = executable('qcam', qcam_sources, resources,
install : true,
- dependencies : qcam_deps,
+ install_tag : 'bin',
+ link_with : apps_lib,
+ dependencies : [
+ libatomic,
+ libcamera_public,
+ libtiff,
+ qt5_dep,
+ ],
cpp_args : qt5_cpp_args)
diff --git a/src/qcam/message_handler.cpp b/src/apps/qcam/message_handler.cpp
index 261623e1..261623e1 100644
--- a/src/qcam/message_handler.cpp
+++ b/src/apps/qcam/message_handler.cpp
diff --git a/src/qcam/message_handler.h b/src/apps/qcam/message_handler.h
index 56294d37..56294d37 100644
--- a/src/qcam/message_handler.h
+++ b/src/apps/qcam/message_handler.h
diff --git a/src/qcam/viewfinder.h b/src/apps/qcam/viewfinder.h
index 260074ae..a57446e8 100644
--- a/src/qcam/viewfinder.h
+++ b/src/apps/qcam/viewfinder.h
@@ -11,6 +11,7 @@
#include <QList>
#include <QSize>
+#include <libcamera/color_space.h>
#include <libcamera/formats.h>
#include <libcamera/framebuffer.h>
@@ -24,6 +25,7 @@ public:
virtual const QList<libcamera::PixelFormat> &nativeFormats() const = 0;
virtual int setFormat(const libcamera::PixelFormat &format, const QSize &size,
+ const libcamera::ColorSpace &colorSpace,
unsigned int stride) = 0;
virtual void render(libcamera::FrameBuffer *buffer, Image *image) = 0;
virtual void stop() = 0;
diff --git a/src/qcam/viewfinder_gl.cpp b/src/apps/qcam/viewfinder_gl.cpp
index 3ae8b03a..f83b99ad 100644
--- a/src/qcam/viewfinder_gl.cpp
+++ b/src/apps/qcam/viewfinder_gl.cpp
@@ -7,13 +7,16 @@
#include "viewfinder_gl.h"
+#include <array>
+
#include <QByteArray>
#include <QFile>
#include <QImage>
+#include <QStringList>
#include <libcamera/formats.h>
-#include "../cam/image.h"
+#include "../common/image.h"
static const QList<libcamera::PixelFormat> supportedFormats{
/* YUV - packed (single plane) */
@@ -56,7 +59,8 @@ static const QList<libcamera::PixelFormat> supportedFormats{
};
ViewFinderGL::ViewFinderGL(QWidget *parent)
- : QOpenGLWidget(parent), buffer_(nullptr), image_(nullptr),
+ : QOpenGLWidget(parent), buffer_(nullptr),
+ colorSpace_(libcamera::ColorSpace::Raw), image_(nullptr),
vertexBuffer_(QOpenGLBuffer::VertexBuffer)
{
}
@@ -71,10 +75,11 @@ const QList<libcamera::PixelFormat> &ViewFinderGL::nativeFormats() const
return supportedFormats;
}
-int ViewFinderGL::setFormat(const libcamera::PixelFormat &format,
- const QSize &size, unsigned int stride)
+int ViewFinderGL::setFormat(const libcamera::PixelFormat &format, const QSize &size,
+ const libcamera::ColorSpace &colorSpace,
+ unsigned int stride)
{
- if (format != format_) {
+ if (format != format_ || colorSpace != colorSpace_) {
/*
* If the fragment already exists, remove it and create a new
* one for the new format.
@@ -88,7 +93,10 @@ int ViewFinderGL::setFormat(const libcamera::PixelFormat &format,
if (!selectFormat(format))
return -1;
+ selectColorSpace(colorSpace);
+
format_ = format;
+ colorSpace_ = colorSpace;
}
size_ = size;
@@ -317,6 +325,74 @@ bool ViewFinderGL::selectFormat(const libcamera::PixelFormat &format)
return ret;
}
+void ViewFinderGL::selectColorSpace(const libcamera::ColorSpace &colorSpace)
+{
+ std::array<double, 9> yuv2rgb;
+
+ /* OpenGL stores arrays in column-major order. */
+ switch (colorSpace.ycbcrEncoding) {
+ case libcamera::ColorSpace::YcbcrEncoding::None:
+ default:
+ yuv2rgb = {
+ 1.0000, 0.0000, 0.0000,
+ 0.0000, 1.0000, 0.0000,
+ 0.0000, 0.0000, 1.0000,
+ };
+ break;
+
+ case libcamera::ColorSpace::YcbcrEncoding::Rec601:
+ yuv2rgb = {
+ 1.0000, 1.0000, 1.0000,
+ 0.0000, -0.3441, 1.7720,
+ 1.4020, -0.7141, 0.0000,
+ };
+ break;
+
+ case libcamera::ColorSpace::YcbcrEncoding::Rec709:
+ yuv2rgb = {
+ 1.0000, 1.0000, 1.0000,
+ 0.0000, -0.1873, 1.8856,
+ 1.5748, -0.4681, 0.0000,
+ };
+ break;
+
+ case libcamera::ColorSpace::YcbcrEncoding::Rec2020:
+ yuv2rgb = {
+ 1.0000, 1.0000, 1.0000,
+ 0.0000, -0.1646, 1.8814,
+ 1.4746, -0.5714, 0.0000,
+ };
+ break;
+ }
+
+ double offset;
+
+ switch (colorSpace.range) {
+ case libcamera::ColorSpace::Range::Full:
+ default:
+ offset = 0.0;
+ break;
+
+ case libcamera::ColorSpace::Range::Limited:
+ offset = 16.0;
+
+ for (unsigned int i = 0; i < 3; ++i)
+ yuv2rgb[i] *= 255.0 / 219.0;
+ for (unsigned int i = 4; i < 9; ++i)
+ yuv2rgb[i] *= 255.0 / 224.0;
+ break;
+ }
+
+ QStringList matrix;
+
+ for (double coeff : yuv2rgb)
+ matrix.append(QString::number(coeff, 'f'));
+
+ fragmentShaderDefines_.append("#define YUV2RGB_MATRIX " + matrix.join(", "));
+ fragmentShaderDefines_.append(QString("#define YUV2RGB_Y_OFFSET %1")
+ .arg(offset, 0, 'f', 1));
+}
+
bool ViewFinderGL::createVertexShader()
{
/* Create Vertex Shader */
diff --git a/src/qcam/viewfinder_gl.h b/src/apps/qcam/viewfinder_gl.h
index 0a9275ba..68c2912d 100644
--- a/src/qcam/viewfinder_gl.h
+++ b/src/apps/qcam/viewfinder_gl.h
@@ -39,6 +39,7 @@ public:
const QList<libcamera::PixelFormat> &nativeFormats() const override;
int setFormat(const libcamera::PixelFormat &format, const QSize &size,
+ const libcamera::ColorSpace &colorSpace,
unsigned int stride) override;
void render(libcamera::FrameBuffer *buffer, Image *image) override;
void stop() override;
@@ -56,6 +57,7 @@ protected:
private:
bool selectFormat(const libcamera::PixelFormat &format);
+ void selectColorSpace(const libcamera::ColorSpace &colorSpace);
void configureTexture(QOpenGLTexture &texture);
bool createFragmentShader();
@@ -66,6 +68,7 @@ private:
/* Captured image size, format and buffer */
libcamera::FrameBuffer *buffer_;
libcamera::PixelFormat format_;
+ libcamera::ColorSpace colorSpace_;
QSize size_;
unsigned int stride_;
Image *image_;
diff --git a/src/qcam/viewfinder_qt.cpp b/src/apps/qcam/viewfinder_qt.cpp
index 6844f998..62ed5e7c 100644
--- a/src/qcam/viewfinder_qt.cpp
+++ b/src/apps/qcam/viewfinder_qt.cpp
@@ -11,6 +11,8 @@
#include <stdint.h>
#include <utility>
+#include <libcamera/formats.h>
+
#include <QImage>
#include <QImageWriter>
#include <QMap>
@@ -18,21 +20,23 @@
#include <QPainter>
#include <QtDebug>
-#include <libcamera/formats.h>
+#include "../common/image.h"
-#include "../cam/image.h"
#include "format_converter.h"
static const QMap<libcamera::PixelFormat, QImage::Format> nativeFormats
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
- { libcamera::formats::ABGR8888, QImage::Format_RGBA8888 },
+ { libcamera::formats::ABGR8888, QImage::Format_RGBX8888 },
+ { libcamera::formats::XBGR8888, QImage::Format_RGBX8888 },
#endif
{ libcamera::formats::ARGB8888, QImage::Format_RGB32 },
+ { libcamera::formats::XRGB8888, QImage::Format_RGB32 },
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
{ libcamera::formats::RGB888, QImage::Format_BGR888 },
#endif
{ libcamera::formats::BGR888, QImage::Format_RGB888 },
+ { libcamera::formats::RGB565, QImage::Format_RGB16 },
};
ViewFinderQt::ViewFinderQt(QWidget *parent)
@@ -51,8 +55,9 @@ const QList<libcamera::PixelFormat> &ViewFinderQt::nativeFormats() const
return formats;
}
-int ViewFinderQt::setFormat(const libcamera::PixelFormat &format,
- const QSize &size, unsigned int stride)
+int ViewFinderQt::setFormat(const libcamera::PixelFormat &format, const QSize &size,
+ [[maybe_unused]] const libcamera::ColorSpace &colorSpace,
+ unsigned int stride)
{
image_ = QImage();
@@ -67,7 +72,8 @@ int ViewFinderQt::setFormat(const libcamera::PixelFormat &format,
image_ = QImage(size, QImage::Format_RGB32);
- qInfo() << "Using software format conversion from" << format;
+ qInfo() << "Using software format conversion from"
+ << format.toString().c_str();
} else {
qInfo() << "Zero-copy enabled";
}
diff --git a/src/qcam/viewfinder_qt.h b/src/apps/qcam/viewfinder_qt.h
index 8c621452..eb3a9988 100644
--- a/src/qcam/viewfinder_qt.h
+++ b/src/apps/qcam/viewfinder_qt.h
@@ -32,6 +32,7 @@ public:
const QList<libcamera::PixelFormat> &nativeFormats() const override;
int setFormat(const libcamera::PixelFormat &format, const QSize &size,
+ const libcamera::ColorSpace &colorSpace,
unsigned int stride) override;
void render(libcamera::FrameBuffer *buffer, Image *image) override;
void stop() override;
diff --git a/src/cam/capture_script.cpp b/src/cam/capture_script.cpp
deleted file mode 100644
index 9f22d5f7..00000000
--- a/src/cam/capture_script.cpp
+++ /dev/null
@@ -1,336 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * capture_script.cpp - Capture session configuration script
- */
-
-#include "capture_script.h"
-
-#include <iostream>
-#include <stdio.h>
-#include <stdlib.h>
-
-using namespace libcamera;
-
-CaptureScript::CaptureScript(std::shared_ptr<Camera> camera,
- const std::string &fileName)
- : camera_(camera), valid_(false)
-{
- FILE *fh = fopen(fileName.c_str(), "r");
- if (!fh) {
- int ret = -errno;
- std::cerr << "Failed to open capture script " << fileName
- << ": " << strerror(-ret) << std::endl;
- return;
- }
-
- /*
- * Map the camera's controls to their name so that they can be
- * easily identified when parsing the script file.
- */
- for (const auto &[control, info] : camera_->controls())
- controls_[control->name()] = control;
-
- int ret = parseScript(fh);
- fclose(fh);
- if (ret)
- return;
-
- valid_ = true;
-}
-
-/* Retrieve the control list associated with a frame number. */
-const ControlList &CaptureScript::frameControls(unsigned int frame)
-{
- static ControlList controls{};
-
- auto it = frameControls_.find(frame);
- if (it == frameControls_.end())
- return controls;
-
- return it->second;
-}
-
-CaptureScript::EventPtr CaptureScript::nextEvent(yaml_event_type_t expectedType)
-{
- EventPtr event(new yaml_event_t);
-
- if (!yaml_parser_parse(&parser_, event.get()))
- return nullptr;
-
- if (expectedType != YAML_NO_EVENT && !checkEvent(event, expectedType))
- return nullptr;
-
- return event;
-}
-
-bool CaptureScript::checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const
-{
- if (event->type != expectedType) {
- std::cerr << "Capture script error on line " << event->start_mark.line
- << " column " << event->start_mark.column << ": "
- << "Expected " << eventTypeName(expectedType)
- << " event, got " << eventTypeName(event->type)
- << std::endl;
- return false;
- }
-
- return true;
-}
-
-std::string CaptureScript::eventScalarValue(const EventPtr &event)
-{
- return std::string(reinterpret_cast<char *>(event->data.scalar.value),
- event->data.scalar.length);
-}
-
-std::string CaptureScript::eventTypeName(yaml_event_type_t type)
-{
- static const std::map<yaml_event_type_t, std::string> typeNames = {
- { YAML_STREAM_START_EVENT, "stream-start" },
- { YAML_STREAM_END_EVENT, "stream-end" },
- { YAML_DOCUMENT_START_EVENT, "document-start" },
- { YAML_DOCUMENT_END_EVENT, "document-end" },
- { YAML_ALIAS_EVENT, "alias" },
- { YAML_SCALAR_EVENT, "scalar" },
- { YAML_SEQUENCE_START_EVENT, "sequence-start" },
- { YAML_SEQUENCE_END_EVENT, "sequence-end" },
- { YAML_MAPPING_START_EVENT, "mapping-start" },
- { YAML_MAPPING_END_EVENT, "mapping-end" },
- };
-
- auto it = typeNames.find(type);
- if (it == typeNames.end())
- return "[type " + std::to_string(type) + "]";
-
- return it->second;
-}
-
-int CaptureScript::parseScript(FILE *script)
-{
- int ret = yaml_parser_initialize(&parser_);
- if (!ret) {
- std::cerr << "Failed to initialize yaml parser" << std::endl;
- return ret;
- }
-
- /* Delete the parser upon function exit. */
- struct ParserDeleter {
- ParserDeleter(yaml_parser_t *parser) : parser_(parser) { }
- ~ParserDeleter() { yaml_parser_delete(parser_); }
- yaml_parser_t *parser_;
- } deleter(&parser_);
-
- yaml_parser_set_input_file(&parser_, script);
-
- EventPtr event = nextEvent(YAML_STREAM_START_EVENT);
- if (!event)
- return -EINVAL;
-
- event = nextEvent(YAML_DOCUMENT_START_EVENT);
- if (!event)
- return -EINVAL;
-
- event = nextEvent(YAML_MAPPING_START_EVENT);
- if (!event)
- return -EINVAL;
-
- while (1) {
- event = nextEvent();
- if (!event)
- return -EINVAL;
-
- if (event->type == YAML_MAPPING_END_EVENT)
- return 0;
-
- if (!checkEvent(event, YAML_SCALAR_EVENT))
- return -EINVAL;
-
- std::string section = eventScalarValue(event);
-
- if (section == "frames") {
- parseFrames();
- } else {
- std::cerr << "Unsupported section '" << section << "'"
- << std::endl;
- return -EINVAL;
- }
- }
-}
-
-int CaptureScript::parseFrames()
-{
- EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
- if (!event)
- return -EINVAL;
-
- while (1) {
- event = nextEvent();
- if (!event)
- return -EINVAL;
-
- if (event->type == YAML_SEQUENCE_END_EVENT)
- return 0;
-
- int ret = parseFrame(std::move(event));
- if (ret)
- return ret;
- }
-}
-
-int CaptureScript::parseFrame(EventPtr event)
-{
- if (!checkEvent(event, YAML_MAPPING_START_EVENT))
- return -EINVAL;
-
- std::string key = parseScalar();
- if (key.empty())
- return -EINVAL;
-
- unsigned int frameId = atoi(key.c_str());
-
- event = nextEvent(YAML_MAPPING_START_EVENT);
- if (!event)
- return -EINVAL;
-
- ControlList controls{};
-
- while (1) {
- event = nextEvent();
- if (!event)
- return -EINVAL;
-
- if (event->type == YAML_MAPPING_END_EVENT)
- break;
-
- int ret = parseControl(std::move(event), controls);
- if (ret)
- return ret;
- }
-
- frameControls_[frameId] = std::move(controls);
-
- event = nextEvent(YAML_MAPPING_END_EVENT);
- if (!event)
- return -EINVAL;
-
- return 0;
-}
-
-int CaptureScript::parseControl(EventPtr event, ControlList &controls)
-{
- /* We expect a value after a key. */
- std::string name = eventScalarValue(event);
- if (name.empty())
- return -EINVAL;
-
- /* If the camera does not support the control just ignore it. */
- auto it = controls_.find(name);
- if (it == controls_.end()) {
- std::cerr << "Unsupported control '" << name << "'" << std::endl;
- return -EINVAL;
- }
-
- std::string value = parseScalar();
- if (value.empty())
- return -EINVAL;
-
- const ControlId *controlId = it->second;
- ControlValue val = unpackControl(controlId, value);
- controls.set(controlId->id(), val);
-
- return 0;
-}
-
-std::string CaptureScript::parseScalar()
-{
- EventPtr event = nextEvent(YAML_SCALAR_EVENT);
- if (!event)
- return "";
-
- return eventScalarValue(event);
-}
-
-void CaptureScript::unpackFailure(const ControlId *id, const std::string &repr)
-{
- static const std::map<unsigned int, const char *> typeNames = {
- { ControlTypeNone, "none" },
- { ControlTypeBool, "bool" },
- { ControlTypeByte, "byte" },
- { ControlTypeInteger32, "int32" },
- { ControlTypeInteger64, "int64" },
- { ControlTypeFloat, "float" },
- { ControlTypeString, "string" },
- { ControlTypeRectangle, "Rectangle" },
- { ControlTypeSize, "Size" },
- };
-
- const char *typeName;
- auto it = typeNames.find(id->type());
- if (it != typeNames.end())
- typeName = it->second;
- else
- typeName = "unknown";
-
- std::cerr << "Unsupported control '" << repr << "' for "
- << typeName << " control " << id->name() << std::endl;
-}
-
-ControlValue CaptureScript::unpackControl(const ControlId *id,
- const std::string &repr)
-{
- ControlValue value{};
-
- switch (id->type()) {
- case ControlTypeNone:
- break;
- case ControlTypeBool: {
- bool val;
-
- if (repr == "true") {
- val = true;
- } else if (repr == "false") {
- val = false;
- } else {
- unpackFailure(id, repr);
- return value;
- }
-
- value.set<bool>(val);
- break;
- }
- case ControlTypeByte: {
- uint8_t val = strtol(repr.c_str(), NULL, 10);
- value.set<uint8_t>(val);
- break;
- }
- case ControlTypeInteger32: {
- int32_t val = strtol(repr.c_str(), NULL, 10);
- value.set<int32_t>(val);
- break;
- }
- case ControlTypeInteger64: {
- int64_t val = strtoll(repr.c_str(), NULL, 10);
- value.set<int64_t>(val);
- break;
- }
- case ControlTypeFloat: {
- float val = strtof(repr.c_str(), NULL);
- value.set<float>(val);
- break;
- }
- case ControlTypeString: {
- value.set<std::string>(repr);
- break;
- }
- case ControlTypeRectangle:
- /* \todo Parse rectangles. */
- break;
- case ControlTypeSize:
- /* \todo Parse Sizes. */
- break;
- }
-
- return value;
-}
diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
deleted file mode 100644
index 69e99ad3..00000000
--- a/src/cam/sdl_texture_mjpg.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_mjpg.cpp - SDL Texture MJPG
- */
-
-#include "sdl_texture_mjpg.h"
-
-#include <SDL2/SDL_image.h>
-
-using namespace libcamera;
-
-SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
- : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, 0)
-{
-}
-
-void SDLTextureMJPG::update(const Span<uint8_t> &data)
-{
- SDL_RWops *bufferStream = SDL_RWFromMem(data.data(), data.size());
- SDL_Surface *frame = IMG_Load_RW(bufferStream, 0);
- SDL_UpdateTexture(ptr_, nullptr, frame->pixels, frame->pitch);
- SDL_FreeSurface(frame);
-}
diff --git a/src/cam/sdl_texture_yuyv.cpp b/src/cam/sdl_texture_yuyv.cpp
deleted file mode 100644
index cc161b2c..00000000
--- a/src/cam/sdl_texture_yuyv.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_yuyv.cpp - SDL Texture YUYV
- */
-
-#include "sdl_texture_yuyv.h"
-
-using namespace libcamera;
-
-SDLTextureYUYV::SDLTextureYUYV(const SDL_Rect &rect)
- : SDLTexture(rect, SDL_PIXELFORMAT_YUY2, 4 * ((rect.w + 1) / 2))
-{
-}
-
-void SDLTextureYUYV::update(const Span<uint8_t> &data)
-{
- SDL_UpdateTexture(ptr_, &rect_, data.data(), pitch_);
-}
diff --git a/src/cam/sdl_texture_yuyv.h b/src/cam/sdl_texture_yuyv.h
deleted file mode 100644
index 9f7c72f0..00000000
--- a/src/cam/sdl_texture_yuyv.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2022, Ideas on Board Oy
- *
- * sdl_texture_yuyv.h - SDL Texture YUYV
- */
-
-#pragma once
-
-#include "sdl_texture.h"
-
-class SDLTextureYUYV : public SDLTexture
-{
-public:
- SDLTextureYUYV(const SDL_Rect &rect);
- void update(const libcamera::Span<uint8_t> &data) override;
-};
diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index 3f242286..469ac810 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -8,6 +8,7 @@
#include "gstlibcamera-utils.h"
+#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
using namespace libcamera;
@@ -19,9 +20,47 @@ static struct {
/* Compressed */
{ GST_VIDEO_FORMAT_ENCODED, formats::MJPEG },
- /* RGB */
+ /* Bayer formats, gstreamer only supports 8-bit */
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB8 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB10 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB12 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB14 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR16 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG16 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG16 },
+ { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB16 },
+
+ /* Monochrome */
+ { GST_VIDEO_FORMAT_GRAY8, formats::R8 },
+ { GST_VIDEO_FORMAT_GRAY16_LE, formats::R16 },
+
+ /* RGB16 */
+ { GST_VIDEO_FORMAT_RGB16, formats::RGB565 },
+
+ /* RGB24 */
{ GST_VIDEO_FORMAT_RGB, formats::BGR888 },
{ GST_VIDEO_FORMAT_BGR, formats::RGB888 },
+
+ /* RGB32 */
+ { GST_VIDEO_FORMAT_BGRx, formats::XRGB8888 },
+ { GST_VIDEO_FORMAT_RGBx, formats::XBGR8888 },
+ { GST_VIDEO_FORMAT_xBGR, formats::RGBX8888 },
+ { GST_VIDEO_FORMAT_xRGB, formats::BGRX8888 },
+ { GST_VIDEO_FORMAT_BGRA, formats::ARGB8888 },
+ { GST_VIDEO_FORMAT_RGBA, formats::ABGR8888 },
+ { GST_VIDEO_FORMAT_ABGR, formats::RGBA8888 },
{ GST_VIDEO_FORMAT_ARGB, formats::BGRA8888 },
/* YUV Semiplanar */
@@ -45,6 +84,154 @@ static struct {
/* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
};
+static GstVideoColorimetry
+colorimetry_from_colorspace(const ColorSpace &colorSpace)
+{
+ GstVideoColorimetry colorimetry;
+
+ switch (colorSpace.primaries) {
+ case ColorSpace::Primaries::Raw:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
+ break;
+ case ColorSpace::Primaries::Smpte170m:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
+ break;
+ case ColorSpace::Primaries::Rec709:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
+ break;
+ case ColorSpace::Primaries::Rec2020:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
+ break;
+ }
+
+ switch (colorSpace.transferFunction) {
+ case ColorSpace::TransferFunction::Linear:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
+ break;
+ case ColorSpace::TransferFunction::Srgb:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
+ break;
+ case ColorSpace::TransferFunction::Rec709:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
+ break;
+ }
+
+ switch (colorSpace.ycbcrEncoding) {
+ case ColorSpace::YcbcrEncoding::None:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
+ break;
+ case ColorSpace::YcbcrEncoding::Rec601:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
+ break;
+ case ColorSpace::YcbcrEncoding::Rec709:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
+ break;
+ case ColorSpace::YcbcrEncoding::Rec2020:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
+ break;
+ }
+
+ switch (colorSpace.range) {
+ case ColorSpace::Range::Full:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
+ break;
+ case ColorSpace::Range::Limited:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
+ break;
+ }
+
+ return colorimetry;
+}
+
+static std::optional<ColorSpace>
+colorspace_from_colorimetry(const GstVideoColorimetry &colorimetry)
+{
+ std::optional<ColorSpace> colorspace = ColorSpace::Raw;
+
+ switch (colorimetry.primaries) {
+ case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
+ /* Unknown primaries map to raw colorspace in gstreamer */
+ return ColorSpace::Raw;
+ case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
+ colorspace->primaries = ColorSpace::Primaries::Smpte170m;
+ break;
+ case GST_VIDEO_COLOR_PRIMARIES_BT709:
+ colorspace->primaries = ColorSpace::Primaries::Rec709;
+ break;
+ case GST_VIDEO_COLOR_PRIMARIES_BT2020:
+ colorspace->primaries = ColorSpace::Primaries::Rec2020;
+ break;
+ default:
+ GST_WARNING("Colorimetry primaries %d not mapped in gstlibcamera",
+ colorimetry.primaries);
+ return std::nullopt;
+ }
+
+ switch (colorimetry.transfer) {
+ /* Transfer function mappings inspired from v4l2src plugin */
+ case GST_VIDEO_TRANSFER_GAMMA18:
+ case GST_VIDEO_TRANSFER_GAMMA20:
+ case GST_VIDEO_TRANSFER_GAMMA22:
+ case GST_VIDEO_TRANSFER_GAMMA28:
+ GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
+ [[fallthrough]];
+ case GST_VIDEO_TRANSFER_GAMMA10:
+ colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
+ break;
+ case GST_VIDEO_TRANSFER_SRGB:
+ colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
+ break;
+#if GST_CHECK_VERSION(1, 18, 0)
+ case GST_VIDEO_TRANSFER_BT601:
+ case GST_VIDEO_TRANSFER_BT2020_10:
+#endif
+ case GST_VIDEO_TRANSFER_BT2020_12:
+ case GST_VIDEO_TRANSFER_BT709:
+ colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
+ break;
+ default:
+ GST_WARNING("Colorimetry transfer function %d not mapped in gstlibcamera",
+ colorimetry.transfer);
+ return std::nullopt;
+ }
+
+ switch (colorimetry.matrix) {
+ case GST_VIDEO_COLOR_MATRIX_RGB:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
+ break;
+ /* FCC is about the same as BT601 with less digit */
+ case GST_VIDEO_COLOR_MATRIX_FCC:
+ case GST_VIDEO_COLOR_MATRIX_BT601:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
+ break;
+ case GST_VIDEO_COLOR_MATRIX_BT709:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
+ break;
+ case GST_VIDEO_COLOR_MATRIX_BT2020:
+ colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
+ break;
+ default:
+ GST_WARNING("Colorimetry matrix %d not mapped in gstlibcamera",
+ colorimetry.matrix);
+ return std::nullopt;
+ }
+
+ switch (colorimetry.range) {
+ case GST_VIDEO_COLOR_RANGE_0_255:
+ colorspace->range = ColorSpace::Range::Full;
+ break;
+ case GST_VIDEO_COLOR_RANGE_16_235:
+ colorspace->range = ColorSpace::Range::Limited;
+ break;
+ default:
+ GST_WARNING("Colorimetry range %d not mapped in gstlibcamera",
+ colorimetry.range);
+ return std::nullopt;
+ }
+
+ return colorspace;
+}
+
static GstVideoFormat
pixel_format_to_gst_format(const PixelFormat &format)
{
@@ -67,6 +254,54 @@ gst_format_to_pixel_format(GstVideoFormat gst_format)
return PixelFormat{};
}
+static const gchar *
+bayer_format_to_string(int format)
+{
+ switch (format) {
+ case formats::SBGGR8:
+ return "bggr";
+ case formats::SGBRG8:
+ return "gbrg";
+ case formats::SGRBG8:
+ return "grbg";
+ case formats::SRGGB8:
+ return "rggb";
+ case formats::SBGGR10:
+ return "bggr10le";
+ case formats::SGBRG10:
+ return "gbrg10le";
+ case formats::SGRBG10:
+ return "grbg10le";
+ case formats::SRGGB10:
+ return "rggb10le";
+ case formats::SBGGR12:
+ return "bggr12le";
+ case formats::SGBRG12:
+ return "gbrg12le";
+ case formats::SGRBG12:
+ return "grbg12le";
+ case formats::SRGGB12:
+ return "rggb12le";
+ case formats::SBGGR14:
+ return "bggr14le";
+ case formats::SGBRG14:
+ return "gbrg14le";
+ case formats::SGRBG14:
+ return "grbg14le";
+ case formats::SRGGB14:
+ return "rggb14le";
+ case formats::SBGGR16:
+ return "bggr16le";
+ case formats::SGBRG16:
+ return "gbrg16le";
+ case formats::SGRBG16:
+ return "grbg16le";
+ case formats::SRGGB16:
+ return "rggb16le";
+ }
+ return NULL;
+}
+
static GstStructure *
bare_structure_from_format(const PixelFormat &format)
{
@@ -82,6 +317,14 @@ bare_structure_from_format(const PixelFormat &format)
switch (format) {
case formats::MJPEG:
return gst_structure_new_empty("image/jpeg");
+
+ case formats::SBGGR8:
+ case formats::SGBRG8:
+ case formats::SGRBG8:
+ case formats::SRGGB8:
+ return gst_structure_new("video/x-bayer", "format", G_TYPE_STRING,
+ bayer_format_to_string(format), nullptr);
+
default:
return nullptr;
}
@@ -139,6 +382,18 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
"width", G_TYPE_INT, stream_cfg.size.width,
"height", G_TYPE_INT, stream_cfg.size.height,
nullptr);
+
+ if (stream_cfg.colorSpace) {
+ GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
+ gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
+
+ if (colorimetry_str)
+ gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
+ else
+ g_error("Got invalid colorimetry from ColorSpace: %s",
+ ColorSpace::toString(stream_cfg.colorSpace).c_str());
+ }
+
gst_caps_append_structure(caps, s);
return caps;
@@ -222,18 +477,110 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
gst_structure_get_int(s, "height", &height);
stream_cfg.size.width = width;
stream_cfg.size.height = height;
+
+ /* Configure colorimetry */
+ if (gst_structure_has_field(s, "colorimetry")) {
+ const gchar *colorimetry_str = gst_structure_get_string(s, "colorimetry");
+ GstVideoColorimetry colorimetry;
+
+ if (!gst_video_colorimetry_from_string(&colorimetry, colorimetry_str))
+ g_critical("Invalid colorimetry %s", colorimetry_str);
+
+ stream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry);
+ }
}
-void
-gst_libcamera_resume_task(GstTask *task)
+void gst_libcamera_get_framerate_from_caps(GstCaps *caps,
+ GstStructure *element_caps)
+{
+ GstStructure *s = gst_caps_get_structure(caps, 0);
+ /*
+ * Default to 30 fps. If the "framerate" fraction is invalid below,
+ * libcamerasrc will set 30fps as the framerate.
+ */
+ gint fps_n = 30, fps_d = 1;
+
+ if (gst_structure_has_field_typed(s, "framerate", GST_TYPE_FRACTION)) {
+ if (!gst_structure_get_fraction(s, "framerate", &fps_n, &fps_d))
+ GST_WARNING("Invalid framerate in the caps");
+ }
+
+ gst_structure_set(element_caps, "framerate", GST_TYPE_FRACTION,
+ fps_n, fps_d, nullptr);
+}
+
+void gst_libcamera_clamp_and_set_frameduration(ControlList &initCtrls,
+ const ControlInfoMap &cam_ctrls,
+ GstStructure *element_caps)
+{
+ gint fps_caps_n, fps_caps_d;
+
+ if (!gst_structure_has_field_typed(element_caps, "framerate", GST_TYPE_FRACTION))
+ return;
+
+ auto iterFrameDuration = cam_ctrls.find(&controls::FrameDurationLimits);
+ if (iterFrameDuration == cam_ctrls.end()) {
+ GST_WARNING("FrameDurationLimits not found in camera controls.");
+ return;
+ }
+
+ const GValue *framerate = gst_structure_get_value(element_caps, "framerate");
+
+ fps_caps_n = gst_value_get_fraction_numerator(framerate);
+ fps_caps_d = gst_value_get_fraction_denominator(framerate);
+
+ int64_t target_duration = (fps_caps_d * 1000000.0) / fps_caps_n;
+ int64_t min_frame_duration = iterFrameDuration->second.min().get<int64_t>();
+ int64_t max_frame_duration = iterFrameDuration->second.max().get<int64_t>();
+
+ int64_t frame_duration = std::clamp(target_duration,
+ min_frame_duration,
+ max_frame_duration);
+
+ if (frame_duration != target_duration) {
+ gint framerate_clamped = 1000000 / frame_duration;
+
+ /*
+ * Update the clamped framerate which then will be exposed in
+ * downstream caps.
+ */
+ gst_structure_set(element_caps, "framerate", GST_TYPE_FRACTION,
+ framerate_clamped, 1, nullptr);
+ }
+
+ initCtrls.set(controls::FrameDurationLimits,
+ { frame_duration, frame_duration });
+}
+
+void gst_libcamera_framerate_to_caps(GstCaps *caps, const GstStructure *element_caps)
+{
+ const GValue *framerate = gst_structure_get_value(element_caps, "framerate");
+ if (!GST_VALUE_HOLDS_FRACTION(framerate))
+ return;
+
+ GstStructure *s = gst_caps_get_structure(caps, 0);
+ gint fps_caps_n, fps_caps_d;
+
+ fps_caps_n = gst_value_get_fraction_numerator(framerate);
+ fps_caps_d = gst_value_get_fraction_denominator(framerate);
+
+ gst_structure_set(s, "framerate", GST_TYPE_FRACTION, fps_caps_n, fps_caps_d, nullptr);
+}
+
+#if !GST_CHECK_VERSION(1, 17, 1)
+gboolean
+gst_task_resume(GstTask *task)
{
/* We only want to resume the task if it's paused. */
GLibLocker lock(GST_OBJECT(task));
- if (GST_TASK_STATE(task) == GST_TASK_PAUSED) {
- GST_TASK_STATE(task) = GST_TASK_STARTED;
- GST_TASK_SIGNAL(task);
- }
+ if (GST_TASK_STATE(task) != GST_TASK_PAUSED)
+ return FALSE;
+
+ GST_TASK_STATE(task) = GST_TASK_STARTED;
+ GST_TASK_SIGNAL(task);
+ return TRUE;
}
+#endif
G_LOCK_DEFINE_STATIC(cm_singleton_lock);
static std::weak_ptr<CameraManager> cm_singleton_ptr;
diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h
index d54f1588..6adeab0e 100644
--- a/src/gstreamer/gstlibcamera-utils.h
+++ b/src/gstreamer/gstlibcamera-utils.h
@@ -9,6 +9,7 @@
#pragma once
#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
#include <libcamera/stream.h>
#include <gst/gst.h>
@@ -18,7 +19,22 @@ GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &fo
GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg);
void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,
GstCaps *caps);
-void gst_libcamera_resume_task(GstTask *task);
+void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *element_caps);
+void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls,
+ const libcamera::ControlInfoMap &camera_controls,
+ GstStructure *element_caps);
+void gst_libcamera_framerate_to_caps(GstCaps *caps, const GstStructure *element_caps);
+
+#if !GST_CHECK_VERSION(1, 16, 0)
+static inline void gst_clear_event(GstEvent **event_ptr)
+{
+ g_clear_pointer(event_ptr, gst_mini_object_unref);
+}
+#endif
+
+#if !GST_CHECK_VERSION(1, 17, 1)
+gboolean gst_task_resume(GstTask *task);
+#endif
std::shared_ptr<libcamera::CameraManager> gst_libcamera_get_camera_manager(int &ret);
/**
diff --git a/src/gstreamer/gstlibcamerapad.cpp b/src/gstreamer/gstlibcamerapad.cpp
index c00e81c8..9e710a47 100644
--- a/src/gstreamer/gstlibcamerapad.cpp
+++ b/src/gstreamer/gstlibcamerapad.cpp
@@ -18,7 +18,6 @@ struct _GstLibcameraPad {
GstPad parent;
StreamRole role;
GstLibcameraPool *pool;
- GQueue pending_buffers;
GstClockTime latency;
};
@@ -55,7 +54,7 @@ gst_libcamera_pad_get_property(GObject *object, guint prop_id, GValue *value,
switch (prop_id) {
case PROP_STREAM_ROLE:
- g_value_set_enum(value, self->role);
+ g_value_set_enum(value, static_cast<gint>(self->role));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -88,9 +87,19 @@ gst_libcamera_stream_role_get_type()
{
static GType type = 0;
static const GEnumValue values[] = {
- { StillCapture, "libcamera::StillCapture", "still-capture" },
- { VideoRecording, "libcamera::VideoRecording", "video-recording" },
- { Viewfinder, "libcamera::Viewfinder", "view-finder" },
+ {
+ static_cast<gint>(StreamRole::StillCapture),
+ "libcamera::StillCapture",
+ "still-capture",
+ }, {
+ static_cast<gint>(StreamRole::VideoRecording),
+ "libcamera::VideoRecording",
+ "video-recording",
+ }, {
+ static_cast<gint>(StreamRole::Viewfinder),
+ "libcamera::Viewfinder",
+ "view-finder",
+ },
{ 0, NULL, NULL }
};
@@ -111,7 +120,7 @@ gst_libcamera_pad_class_init(GstLibcameraPadClass *klass)
auto *spec = g_param_spec_enum("stream-role", "Stream Role",
"The selected stream role",
gst_libcamera_stream_role_get_type(),
- VideoRecording,
+ static_cast<gint>(StreamRole::VideoRecording),
(GParamFlags)(GST_PARAM_MUTABLE_READY
| G_PARAM_CONSTRUCT
| G_PARAM_READWRITE
@@ -156,40 +165,6 @@ gst_libcamera_pad_get_stream(GstPad *pad)
}
void
-gst_libcamera_pad_queue_buffer(GstPad *pad, GstBuffer *buffer)
-{
- auto *self = GST_LIBCAMERA_PAD(pad);
- GLibLocker lock(GST_OBJECT(self));
-
- g_queue_push_head(&self->pending_buffers, buffer);
-}
-
-GstFlowReturn
-gst_libcamera_pad_push_pending(GstPad *pad)
-{
- auto *self = GST_LIBCAMERA_PAD(pad);
- GstBuffer *buffer;
-
- {
- GLibLocker lock(GST_OBJECT(self));
- buffer = GST_BUFFER(g_queue_pop_tail(&self->pending_buffers));
- }
-
- if (!buffer)
- return GST_FLOW_OK;
-
- return gst_pad_push(pad, buffer);
-}
-
-bool
-gst_libcamera_pad_has_pending(GstPad *pad)
-{
- auto *self = GST_LIBCAMERA_PAD(pad);
- GLibLocker lock(GST_OBJECT(self));
- return self->pending_buffers.length > 0;
-}
-
-void
gst_libcamera_pad_set_latency(GstPad *pad, GstClockTime latency)
{
auto *self = GST_LIBCAMERA_PAD(pad);
diff --git a/src/gstreamer/gstlibcamerapad.h b/src/gstreamer/gstlibcamerapad.h
index 20769517..103ee57a 100644
--- a/src/gstreamer/gstlibcamerapad.h
+++ b/src/gstreamer/gstlibcamerapad.h
@@ -25,10 +25,4 @@ void gst_libcamera_pad_set_pool(GstPad *pad, GstLibcameraPool *pool);
libcamera::Stream *gst_libcamera_pad_get_stream(GstPad *pad);
-void gst_libcamera_pad_queue_buffer(GstPad *pad, GstBuffer *buffer);
-
-GstFlowReturn gst_libcamera_pad_push_pending(GstPad *pad);
-
-bool gst_libcamera_pad_has_pending(GstPad *pad);
-
void gst_libcamera_pad_set_latency(GstPad *pad, GstClockTime latency);
diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp
index 1fde4213..0c2be43c 100644
--- a/src/gstreamer/gstlibcamerapool.cpp
+++ b/src/gstreamer/gstlibcamerapool.cpp
@@ -134,13 +134,6 @@ gst_libcamera_pool_get_stream(GstLibcameraPool *self)
return self->stream;
}
-Stream *
-gst_libcamera_buffer_get_stream(GstBuffer *buffer)
-{
- auto *self = (GstLibcameraPool *)buffer->pool;
- return self->stream;
-}
-
FrameBuffer *
gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer)
{
diff --git a/src/gstreamer/gstlibcamerapool.h b/src/gstreamer/gstlibcamerapool.h
index 05795d21..ce3bf60b 100644
--- a/src/gstreamer/gstlibcamerapool.h
+++ b/src/gstreamer/gstlibcamerapool.h
@@ -25,6 +25,4 @@ GstLibcameraPool *gst_libcamera_pool_new(GstLibcameraAllocator *allocator,
libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool *self);
-libcamera::Stream *gst_libcamera_buffer_get_stream(GstBuffer *buffer);
-
libcamera::FrameBuffer *gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer);
diff --git a/src/gstreamer/gstlibcameraprovider.cpp b/src/gstreamer/gstlibcameraprovider.cpp
index 6eb0a0eb..ce3e0a08 100644
--- a/src/gstreamer/gstlibcameraprovider.cpp
+++ b/src/gstreamer/gstlibcameraprovider.cpp
@@ -6,6 +6,8 @@
* gstlibcameraprovider.c - GStreamer Device Provider
*/
+#include <array>
+
#include "gstlibcameraprovider.h"
#include <libcamera/camera.h>
@@ -31,6 +33,7 @@ GST_DEBUG_CATEGORY_STATIC(provider_debug);
enum {
PROP_DEVICE_NAME = 1,
+ PROP_AUTO_FOCUS_MODE = 2,
};
#define GST_TYPE_LIBCAMERA_DEVICE gst_libcamera_device_get_type()
@@ -40,6 +43,7 @@ G_DECLARE_FINAL_TYPE(GstLibcameraDevice, gst_libcamera_device,
struct _GstLibcameraDevice {
GstDevice parent;
gchar *name;
+ controls::AfModeEnum auto_focus_mode = controls::AfModeManual;
};
G_DEFINE_TYPE(GstLibcameraDevice, gst_libcamera_device, GST_TYPE_DEVICE)
@@ -56,6 +60,7 @@ gst_libcamera_device_create_element(GstDevice *device, const gchar *name)
g_assert(source);
g_object_set(source, "camera-name", GST_LIBCAMERA_DEVICE(device)->name, nullptr);
+ g_object_set(source, "auto-focus-mode", GST_LIBCAMERA_DEVICE(device)->auto_focus_mode, nullptr);
return source;
}
@@ -82,6 +87,9 @@ gst_libcamera_device_set_property(GObject *object, guint prop_id,
case PROP_DEVICE_NAME:
device->name = g_value_dup_string(value);
break;
+ case PROP_AUTO_FOCUS_MODE:
+ device->auto_focus_mode = static_cast<controls::AfModeEnum>(g_value_get_enum(value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -121,16 +129,24 @@ gst_libcamera_device_class_init(GstLibcameraDeviceClass *klass)
(GParamFlags)(G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property(object_class, PROP_DEVICE_NAME, pspec);
+
+ pspec = g_param_spec_enum("auto-focus-mode",
+ "Set auto-focus mode",
+ "Available options: AfModeManual, "
+ "AfModeAuto or AfModeContinuous.",
+ gst_libcamera_auto_focus_get_type(),
+ static_cast<gint>(controls::AfModeManual),
+ G_PARAM_WRITABLE);
+ g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, pspec);
}
static GstDevice *
gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)
{
+ static const std::array roles{ StreamRole::VideoRecording };
g_autoptr(GstCaps) caps = gst_caps_new_empty();
const gchar *name = camera->id().c_str();
- StreamRoles roles;
- roles.push_back(StreamRole::VideoRecording);
std::unique_ptr<CameraConfiguration> config = camera->generateConfiguration(roles);
if (!config || config->size() != roles.size()) {
GST_ERROR("Failed to generate a default configuration for %s", name);
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 46fd02d2..f015c6d2 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -9,10 +9,8 @@
/**
* \todo The following is a list of items that needs implementation in the GStreamer plugin
* - Implement GstElement::send_event
- * + Allowing application to send EOS
* + Allowing application to use FLUSH/FLUSH_STOP
* + Prevent the main thread from accessing streaming thread
- * - Implement renegotiation (even if slow)
* - Implement GstElement::request-new-pad (multi stream)
* + Evaluate if a single streaming thread is fine
* - Add application driven request (snapshot)
@@ -29,11 +27,13 @@
#include "gstlibcamerasrc.h"
+#include <atomic>
#include <queue>
#include <vector>
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
+#include <libcamera/control_ids.h>
#include <gst/base/base.h>
@@ -51,15 +51,18 @@ struct RequestWrap {
RequestWrap(std::unique_ptr<Request> request);
~RequestWrap();
- void attachBuffer(GstBuffer *buffer);
+ void attachBuffer(Stream *stream, GstBuffer *buffer);
GstBuffer *detachBuffer(Stream *stream);
std::unique_ptr<Request> request_;
std::map<Stream *, GstBuffer *> buffers_;
+
+ GstClockTime latency_;
+ GstClockTime pts_;
};
RequestWrap::RequestWrap(std::unique_ptr<Request> request)
- : request_(std::move(request))
+ : request_(std::move(request)), latency_(0), pts_(GST_CLOCK_TIME_NONE)
{
}
@@ -71,10 +74,9 @@ RequestWrap::~RequestWrap()
}
}
-void RequestWrap::attachBuffer(GstBuffer *buffer)
+void RequestWrap::attachBuffer(Stream *stream, GstBuffer *buffer)
{
FrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);
- Stream *stream = gst_libcamera_buffer_get_stream(buffer);
request_->addBuffer(stream, fb);
@@ -107,11 +109,30 @@ struct GstLibcameraSrcState {
std::shared_ptr<CameraManager> cm_;
std::shared_ptr<Camera> cam_;
std::unique_ptr<CameraConfiguration> config_;
- std::vector<GstPad *> srcpads_;
- std::queue<std::unique_ptr<RequestWrap>> requests_;
+
+ std::vector<GstPad *> srcpads_; /* Protected by stream_lock */
+
+ /*
+ * Contention on this lock_ must be minimized, as it has to be taken in
+ * the realtime-sensitive requestCompleted() handler to protect
+ * queuedRequests_ and completedRequests_.
+ *
+ * stream_lock must be taken before lock_ in contexts where both locks
+ * need to be taken. In particular, this means that the lock_ must not
+ * be held while calling into other graph elements (e.g. when calling
+ * gst_pad_query()).
+ */
+ GMutex lock_;
+ std::queue<std::unique_ptr<RequestWrap>> queuedRequests_;
+ std::queue<std::unique_ptr<RequestWrap>> completedRequests_;
+
+ ControlList initControls_;
guint group_id_;
+ int queueRequest();
void requestCompleted(Request *request);
+ int processRequest();
+ void clearRequests();
};
struct _GstLibcameraSrc {
@@ -121,6 +142,9 @@ struct _GstLibcameraSrc {
GstTask *task;
gchar *camera_name;
+ controls::AfModeEnum auto_focus_mode = controls::AfModeManual;
+
+ std::atomic<GstEvent *> pending_eos;
GstLibcameraSrcState *state;
GstLibcameraAllocator *allocator;
@@ -129,14 +153,15 @@ struct _GstLibcameraSrc {
enum {
PROP_0,
- PROP_CAMERA_NAME
+ PROP_CAMERA_NAME,
+ PROP_AUTO_FOCUS_MODE,
};
G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT,
GST_DEBUG_CATEGORY_INIT(source_debug, "libcamerasrc", 0,
"libcamera Source"))
-#define TEMPLATE_CAPS GST_STATIC_CAPS("video/x-raw; image/jpeg")
+#define TEMPLATE_CAPS GST_STATIC_CAPS("video/x-raw; image/jpeg; video/x-bayer")
/* For the simple case, we have a src pad that is always present. */
GstStaticPadTemplate src_template = {
@@ -148,15 +173,59 @@ GstStaticPadTemplate request_src_template = {
"src_%u", GST_PAD_SRC, GST_PAD_REQUEST, TEMPLATE_CAPS
};
+/* Must be called with stream_lock held. */
+int GstLibcameraSrcState::queueRequest()
+{
+ std::unique_ptr<Request> request = cam_->createRequest();
+ if (!request)
+ return -ENOMEM;
+
+ std::unique_ptr<RequestWrap> wrap =
+ std::make_unique<RequestWrap>(std::move(request));
+
+ for (GstPad *srcpad : srcpads_) {
+ Stream *stream = gst_libcamera_pad_get_stream(srcpad);
+ GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
+ GstBuffer *buffer;
+ GstFlowReturn ret;
+
+ ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
+ &buffer, nullptr);
+ if (ret != GST_FLOW_OK) {
+ /*
+ * RequestWrap has ownership of the request, and we
+ * won't be queueing this one due to lack of buffers.
+ */
+ return -ENOBUFS;
+ }
+
+ wrap->attachBuffer(stream, buffer);
+ }
+
+ GST_TRACE_OBJECT(src_, "Requesting buffers");
+ cam_->queueRequest(wrap->request_.get());
+
+ {
+ GLibLocker locker(&lock_);
+ queuedRequests_.push(std::move(wrap));
+ }
+
+ /* The RequestWrap will be deleted in the completion handler. */
+ return 0;
+}
+
void
GstLibcameraSrcState::requestCompleted(Request *request)
{
- GLibLocker lock(GST_OBJECT(src_));
-
GST_DEBUG_OBJECT(src_, "buffers are ready");
- std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
- requests_.pop();
+ std::unique_ptr<RequestWrap> wrap;
+
+ {
+ GLibLocker locker(&lock_);
+ wrap = std::move(queuedRequests_.front());
+ queuedRequests_.pop();
+ }
g_return_if_fail(wrap->request_.get() == request);
@@ -165,23 +234,61 @@ GstLibcameraSrcState::requestCompleted(Request *request)
return;
}
- GstBuffer *buffer;
+ if (GST_ELEMENT_CLOCK(src_)) {
+ int64_t timestamp = request->metadata().get(controls::SensorTimestamp).value_or(0);
+
+ GstClockTime gst_base_time = GST_ELEMENT(src_)->base_time;
+ GstClockTime gst_now = gst_clock_get_time(GST_ELEMENT_CLOCK(src_));
+ /* \todo Need to expose which reference clock the timestamp relates to. */
+ GstClockTime sys_now = g_get_monotonic_time() * 1000;
+
+ /* Deduced from: sys_now - sys_base_time == gst_now - gst_base_time */
+ GstClockTime sys_base_time = sys_now - (gst_now - gst_base_time);
+ wrap->pts_ = timestamp - sys_base_time;
+ wrap->latency_ = sys_now - timestamp;
+ }
+
+ {
+ GLibLocker locker(&lock_);
+ completedRequests_.push(std::move(wrap));
+ }
+
+ gst_task_resume(src_->task);
+}
+
+/* Must be called with stream_lock held. */
+int GstLibcameraSrcState::processRequest()
+{
+ std::unique_ptr<RequestWrap> wrap;
+ int err = 0;
+
+ {
+ GLibLocker locker(&lock_);
+
+ if (!completedRequests_.empty()) {
+ wrap = std::move(completedRequests_.front());
+ completedRequests_.pop();
+ }
+
+ if (completedRequests_.empty())
+ err = -ENOBUFS;
+ }
+
+ if (!wrap)
+ return -ENOBUFS;
+
+ GstFlowReturn ret = GST_FLOW_OK;
+ gst_flow_combiner_reset(src_->flow_combiner);
+
for (GstPad *srcpad : srcpads_) {
Stream *stream = gst_libcamera_pad_get_stream(srcpad);
- buffer = wrap->detachBuffer(stream);
+ GstBuffer *buffer = wrap->detachBuffer(stream);
FrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);
- if (GST_ELEMENT_CLOCK(src_)) {
- GstClockTime gst_base_time = GST_ELEMENT(src_)->base_time;
- GstClockTime gst_now = gst_clock_get_time(GST_ELEMENT_CLOCK(src_));
- /* \todo Need to expose which reference clock the timestamp relates to. */
- GstClockTime sys_now = g_get_monotonic_time() * 1000;
-
- /* Deduced from: sys_now - sys_base_time == gst_now - gst_base_time */
- GstClockTime sys_base_time = sys_now - (gst_now - gst_base_time);
- GST_BUFFER_PTS(buffer) = fb->metadata().timestamp - sys_base_time;
- gst_libcamera_pad_set_latency(srcpad, sys_now - fb->metadata().timestamp);
+ if (GST_CLOCK_TIME_IS_VALID(wrap->pts_)) {
+ GST_BUFFER_PTS(buffer) = wrap->pts_;
+ gst_libcamera_pad_set_latency(srcpad, wrap->latency_);
} else {
GST_BUFFER_PTS(buffer) = 0;
}
@@ -189,10 +296,60 @@ GstLibcameraSrcState::requestCompleted(Request *request)
GST_BUFFER_OFFSET(buffer) = fb->metadata().sequence;
GST_BUFFER_OFFSET_END(buffer) = fb->metadata().sequence;
- gst_libcamera_pad_queue_buffer(srcpad, buffer);
+ ret = gst_pad_push(srcpad, buffer);
+ ret = gst_flow_combiner_update_pad_flow(src_->flow_combiner,
+ srcpad, ret);
}
- gst_libcamera_resume_task(this->src_->task);
+ switch (ret) {
+ case GST_FLOW_OK:
+ break;
+
+ case GST_FLOW_NOT_NEGOTIATED: {
+ bool reconfigure = false;
+ for (GstPad *srcpad : srcpads_) {
+ if (gst_pad_needs_reconfigure(srcpad)) {
+ reconfigure = true;
+ break;
+ }
+ }
+
+ /* If no pads need a reconfiguration something went wrong. */
+ if (!reconfigure)
+ err = -EPIPE;
+
+ break;
+ }
+
+ case GST_FLOW_EOS: {
+ g_autoptr(GstEvent) eos = gst_event_new_eos();
+ guint32 seqnum = gst_util_seqnum_next();
+ gst_event_set_seqnum(eos, seqnum);
+ for (GstPad *srcpad : srcpads_)
+ gst_pad_push_event(srcpad, gst_event_ref(eos));
+
+ err = -EPIPE;
+ break;
+ }
+
+ case GST_FLOW_FLUSHING:
+ err = -EPIPE;
+ break;
+
+ default:
+ GST_ELEMENT_FLOW_ERROR(src_, ret);
+
+ err = -EPIPE;
+ break;
+ }
+
+ return err;
+}
+
+void GstLibcameraSrcState::clearRequests()
+{
+ GLibLocker locker(&lock_);
+ completedRequests_ = {};
}
static bool
@@ -256,93 +413,195 @@ gst_libcamera_src_open(GstLibcameraSrc *self)
return true;
}
-static void
-gst_libcamera_src_task_run(gpointer user_data)
+/* Must be called with stream_lock held. */
+static bool
+gst_libcamera_src_negotiate(GstLibcameraSrc *self)
{
- GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
GstLibcameraSrcState *state = self->state;
- std::unique_ptr<Request> request = state->cam_->createRequest();
- if (!request) {
+ g_autoptr(GstStructure) element_caps = gst_structure_new_empty("caps");
+
+ for (gsize i = 0; i < state->srcpads_.size(); i++) {
+ GstPad *srcpad = state->srcpads_[i];
+ StreamConfiguration &stream_cfg = state->config_->at(i);
+
+ /* Retrieve the supported caps. */
+ g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
+ g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter);
+ if (gst_caps_is_empty(caps))
+ return false;
+
+ /* Fixate caps and configure the stream. */
+ caps = gst_caps_make_writable(caps);
+ gst_libcamera_configure_stream_from_caps(stream_cfg, caps);
+ gst_libcamera_get_framerate_from_caps(caps, element_caps);
+ }
+
+ /* Validate the configuration. */
+ if (state->config_->validate() == CameraConfiguration::Invalid)
+ return false;
+
+ int ret = state->cam_->configure(state->config_.get());
+ if (ret) {
+ GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
+ ("Failed to configure camera: %s", g_strerror(-ret)),
+ ("Camera::configure() failed with error code %i", ret));
+ return false;
+ }
+
+ /* Check frame duration bounds within controls::FrameDurationLimits */
+ gst_libcamera_clamp_and_set_frameduration(state->initControls_,
+ state->cam_->controls(), element_caps);
+
+ /*
+ * Regardless if it has been modified, create clean caps and push the
+ * caps event. Downstream will decide if the caps are acceptable.
+ */
+ for (gsize i = 0; i < state->srcpads_.size(); i++) {
+ GstPad *srcpad = state->srcpads_[i];
+ const StreamConfiguration &stream_cfg = state->config_->at(i);
+
+ g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg);
+ gst_libcamera_framerate_to_caps(caps, element_caps);
+
+ if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps)))
+ return false;
+ }
+
+ if (self->allocator)
+ g_clear_object(&self->allocator);
+
+ self->allocator = gst_libcamera_allocator_new(state->cam_, state->config_.get());
+ if (!self->allocator) {
GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
- ("Failed to allocate request for camera '%s'.",
- state->cam_->id().c_str()),
- ("libcamera::Camera::createRequest() failed"));
- gst_task_stop(self->task);
- return;
+ ("Failed to allocate memory"),
+ ("gst_libcamera_allocator_new() failed."));
+ return false;
}
- std::unique_ptr<RequestWrap> wrap =
- std::make_unique<RequestWrap>(std::move(request));
+ for (gsize i = 0; i < state->srcpads_.size(); i++) {
+ GstPad *srcpad = state->srcpads_[i];
+ const StreamConfiguration &stream_cfg = state->config_->at(i);
- for (GstPad *srcpad : state->srcpads_) {
- GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
- GstBuffer *buffer;
- GstFlowReturn ret;
+ GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,
+ stream_cfg.stream());
+ g_signal_connect_swapped(pool, "buffer-notify",
+ G_CALLBACK(gst_task_resume), self->task);
- ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
- &buffer, nullptr);
- if (ret != GST_FLOW_OK) {
- /*
- * RequestWrap has ownership of the request, and we
- * won't be queueing this one due to lack of buffers.
- */
- wrap.release();
- break;
- }
+ gst_libcamera_pad_set_pool(srcpad, pool);
- wrap->attachBuffer(buffer);
+ /* Clear all reconfigure flags. */
+ gst_pad_check_reconfigure(srcpad);
}
- if (wrap) {
- GLibLocker lock(GST_OBJECT(self));
- GST_TRACE_OBJECT(self, "Requesting buffers");
- state->cam_->queueRequest(wrap->request_.get());
- state->requests_.push(std::move(wrap));
+ return true;
+}
+
+static void
+gst_libcamera_src_task_run(gpointer user_data)
+{
+ GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
+ GstLibcameraSrcState *state = self->state;
+
+ /*
+ * Start by pausing the task. The task may also get resumed by the
+ * buffer-notify signal when new buffers are queued back to the pool,
+ * or by the request completion handler when a new request has
+ * completed. Both will resume the task after adding the buffers or
+ * request to their respective lists, which are checked below to decide
+ * if the task needs to be resumed for another iteration. This is thus
+ * guaranteed to be race-free, the lock taken by gst_task_pause() and
+ * gst_task_resume() serves as a memory barrier.
+ */
+ gst_task_pause(self->task);
+
+ bool doResume = false;
- /* The RequestWrap will be deleted in the completion handler. */
+ g_autoptr(GstEvent) event = self->pending_eos.exchange(nullptr);
+ if (event) {
+ for (GstPad *srcpad : state->srcpads_)
+ gst_pad_push_event(srcpad, gst_event_ref(event));
+
+ return;
}
- GstFlowReturn ret = GST_FLOW_OK;
- gst_flow_combiner_reset(self->flow_combiner);
+ /* Check if a srcpad requested a renegotiation. */
+ bool reconfigure = false;
for (GstPad *srcpad : state->srcpads_) {
- ret = gst_libcamera_pad_push_pending(srcpad);
- ret = gst_flow_combiner_update_pad_flow(self->flow_combiner,
- srcpad, ret);
+ if (gst_pad_check_reconfigure(srcpad)) {
+ /* Check if the caps even need changing. */
+ g_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad);
+ if (!gst_pad_peer_query_accept_caps(srcpad, caps)) {
+ reconfigure = true;
+ break;
+ }
+ }
}
- {
- if (ret != GST_FLOW_OK) {
- if (ret == GST_FLOW_EOS) {
- g_autoptr(GstEvent) eos = gst_event_new_eos();
- guint32 seqnum = gst_util_seqnum_next();
- gst_event_set_seqnum(eos, seqnum);
- for (GstPad *srcpad : state->srcpads_)
- gst_pad_push_event(srcpad, gst_event_ref(eos));
- } else if (ret != GST_FLOW_FLUSHING) {
- GST_ELEMENT_FLOW_ERROR(self, ret);
- }
+ if (reconfigure) {
+ state->cam_->stop();
+ state->clearRequests();
+
+ if (!gst_libcamera_src_negotiate(self)) {
+ GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
gst_task_stop(self->task);
- return;
}
+ state->cam_->start(&state->initControls_);
+ }
+
+ /*
+ * Create and queue one request. If no buffers are available the
+ * function returns -ENOBUFS, which we ignore here as that's not a
+ * fatal error.
+ */
+ int ret = state->queueRequest();
+ switch (ret) {
+ case 0:
/*
- * Here we need to decide if we want to pause. This needs to
- * happen in lock step with the callback thread which may want
- * to resume the task and might push pending buffers.
+ * The request was successfully queued, there may be enough
+ * buffers to create a new one. Don't pause the task to give it
+ * another try.
*/
- GLibLocker lock(GST_OBJECT(self));
- bool do_pause = true;
- for (GstPad *srcpad : state->srcpads_) {
- if (gst_libcamera_pad_has_pending(srcpad)) {
- do_pause = false;
- break;
- }
- }
+ doResume = true;
+ break;
- if (do_pause)
- gst_task_pause(self->task);
+ case -ENOMEM:
+ GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
+ ("Failed to allocate request for camera '%s'.",
+ state->cam_->id().c_str()),
+ ("libcamera::Camera::createRequest() failed"));
+ gst_task_stop(self->task);
+ return;
+
+ case -ENOBUFS:
+ default:
+ break;
}
+
+ /*
+ * Process one completed request, if available, and record if further
+ * requests are ready for processing.
+ */
+ ret = state->processRequest();
+ switch (ret) {
+ case 0:
+ /* Another completed request is available, resume the task. */
+ doResume = true;
+ break;
+
+ case -EPIPE:
+ gst_task_stop(self->task);
+ return;
+
+ case -ENOBUFS:
+ default:
+ break;
+ }
+
+ /* Resume the task for another iteration if needed. */
+ if (doResume)
+ gst_task_resume(self->task);
}
static void
@@ -352,13 +611,12 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,
GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
GLibRecLocker lock(&self->stream_lock);
GstLibcameraSrcState *state = self->state;
- GstFlowReturn flow_ret = GST_FLOW_OK;
gint ret;
GST_DEBUG_OBJECT(self, "Streaming thread has started");
gint stream_id_num = 0;
- StreamRoles roles;
+ std::vector<StreamRole> roles;
for (GstPad *srcpad : state->srcpads_) {
/* Create stream-id and push stream-start. */
g_autofree gchar *stream_id_intermediate = g_strdup_printf("%i%i", state->group_id_, stream_id_num++);
@@ -382,45 +640,16 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,
}
g_assert(state->config_->size() == state->srcpads_.size());
- for (gsize i = 0; i < state->srcpads_.size(); i++) {
- GstPad *srcpad = state->srcpads_[i];
- StreamConfiguration &stream_cfg = state->config_->at(i);
-
- /* Retrieve the supported caps. */
- g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
- g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter);
- if (gst_caps_is_empty(caps)) {
- flow_ret = GST_FLOW_NOT_NEGOTIATED;
- break;
- }
-
- /* Fixate caps and configure the stream. */
- caps = gst_caps_make_writable(caps);
- gst_libcamera_configure_stream_from_caps(stream_cfg, caps);
- }
-
- if (flow_ret != GST_FLOW_OK)
- goto done;
-
- /* Validate the configuration. */
- if (state->config_->validate() == CameraConfiguration::Invalid) {
- flow_ret = GST_FLOW_NOT_NEGOTIATED;
- goto done;
+ if (!gst_libcamera_src_negotiate(self)) {
+ state->initControls_.clear();
+ GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
+ gst_task_stop(task);
+ return;
}
- /*
- * Regardless if it has been modified, create clean caps and push the
- * caps event. Downstream will decide if the caps are acceptable.
- */
- for (gsize i = 0; i < state->srcpads_.size(); i++) {
- GstPad *srcpad = state->srcpads_[i];
- const StreamConfiguration &stream_cfg = state->config_->at(i);
-
- g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg);
- if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps))) {
- flow_ret = GST_FLOW_NOT_NEGOTIATED;
- break;
- }
+ self->flow_combiner = gst_flow_combiner_new();
+ for (GstPad *srcpad : state->srcpads_) {
+ gst_flow_combiner_add_pad(self->flow_combiner, srcpad);
/* Send an open segment event with time format. */
GstSegment segment;
@@ -428,38 +657,19 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,
gst_pad_push_event(srcpad, gst_event_new_segment(&segment));
}
- ret = state->cam_->configure(state->config_.get());
- if (ret) {
- GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
- ("Failed to configure camera: %s", g_strerror(-ret)),
- ("Camera::configure() failed with error code %i", ret));
- gst_task_stop(task);
- return;
- }
-
- self->allocator = gst_libcamera_allocator_new(state->cam_, state->config_.get());
- if (!self->allocator) {
- GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
- ("Failed to allocate memory"),
- ("gst_libcamera_allocator_new() failed."));
- gst_task_stop(task);
- return;
- }
-
- self->flow_combiner = gst_flow_combiner_new();
- for (gsize i = 0; i < state->srcpads_.size(); i++) {
- GstPad *srcpad = state->srcpads_[i];
- const StreamConfiguration &stream_cfg = state->config_->at(i);
- GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,
- stream_cfg.stream());
- g_signal_connect_swapped(pool, "buffer-notify",
- G_CALLBACK(gst_libcamera_resume_task), task);
-
- gst_libcamera_pad_set_pool(srcpad, pool);
- gst_flow_combiner_add_pad(self->flow_combiner, srcpad);
+ if (self->auto_focus_mode != controls::AfModeManual) {
+ const ControlInfoMap &infoMap = state->cam_->controls();
+ if (infoMap.find(&controls::AfMode) != infoMap.end()) {
+ state->initControls_.set(controls::AfMode, self->auto_focus_mode);
+ } else {
+ GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
+ ("Failed to enable auto focus"),
+ ("AfMode not supported by this camera, "
+ "please retry with 'auto-focus-mode=AfModeManual'"));
+ }
}
- ret = state->cam_->start();
+ ret = state->cam_->start(&state->initControls_);
if (ret) {
GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
("Failed to start the camera: %s", g_strerror(-ret)),
@@ -467,16 +677,6 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,
gst_task_stop(task);
return;
}
-
-done:
- switch (flow_ret) {
- case GST_FLOW_NOT_NEGOTIATED:
- GST_ELEMENT_FLOW_ERROR(self, flow_ret);
- gst_task_stop(task);
- break;
- default:
- break;
- }
}
static void
@@ -490,9 +690,13 @@ gst_libcamera_src_task_leave([[maybe_unused]] GstTask *task,
GST_DEBUG_OBJECT(self, "Streaming thread is about to stop");
state->cam_->stop();
+ state->clearRequests();
- for (GstPad *srcpad : state->srcpads_)
- gst_libcamera_pad_set_pool(srcpad, nullptr);
+ {
+ GLibRecLocker locker(&self->stream_lock);
+ for (GstPad *srcpad : state->srcpads_)
+ gst_libcamera_pad_set_pool(srcpad, nullptr);
+ }
g_clear_object(&self->allocator);
g_clear_pointer(&self->flow_combiner,
@@ -532,6 +736,9 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id,
g_free(self->camera_name);
self->camera_name = g_value_dup_string(value);
break;
+ case PROP_AUTO_FOCUS_MODE:
+ self->auto_focus_mode = static_cast<controls::AfModeEnum>(g_value_get_enum(value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -549,6 +756,9 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value,
case PROP_CAMERA_NAME:
g_value_set_string(value, self->camera_name);
break;
+ case PROP_AUTO_FOCUS_MODE:
+ g_value_set_enum(value, static_cast<gint>(self->auto_focus_mode));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -604,6 +814,27 @@ gst_libcamera_src_change_state(GstElement *element, GstStateChange transition)
return ret;
}
+static gboolean
+gst_libcamera_src_send_event(GstElement *element, GstEvent *event)
+{
+ GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element);
+ gboolean ret = FALSE;
+
+ switch (GST_EVENT_TYPE(event)) {
+ case GST_EVENT_EOS: {
+ GstEvent *oldEvent = self->pending_eos.exchange(event);
+ gst_clear_event(&oldEvent);
+ ret = TRUE;
+ break;
+ }
+ default:
+ gst_event_unref(event);
+ break;
+ }
+
+ return ret;
+}
+
static void
gst_libcamera_src_finalize(GObject *object)
{
@@ -612,6 +843,7 @@ gst_libcamera_src_finalize(GObject *object)
g_rec_mutex_clear(&self->stream_lock);
g_clear_object(&self->task);
+ g_mutex_clear(&self->state->lock_);
g_free(self->camera_name);
delete self->state;
@@ -630,8 +862,12 @@ gst_libcamera_src_init(GstLibcameraSrc *self)
gst_task_set_leave_callback(self->task, gst_libcamera_src_task_leave, self, nullptr);
gst_task_set_lock(self->task, &self->stream_lock);
+ g_mutex_init(&state->lock_);
+
state->srcpads_.push_back(gst_pad_new_from_template(templ, "src"));
- gst_element_add_pad(GST_ELEMENT(self), state->srcpads_[0]);
+ gst_element_add_pad(GST_ELEMENT(self), state->srcpads_.back());
+
+ GST_OBJECT_FLAG_SET(self, GST_ELEMENT_FLAG_SOURCE);
/* C-style friend. */
state->src_ = self;
@@ -651,7 +887,7 @@ gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ,
g_object_ref_sink(pad);
if (gst_element_add_pad(element, pad)) {
- GLibLocker lock(GST_OBJECT(self));
+ GLibRecLocker lock(&self->stream_lock);
self->state->srcpads_.push_back(reinterpret_cast<GstPad *>(g_object_ref(pad)));
} else {
GST_ELEMENT_ERROR(element, STREAM, FAILED,
@@ -671,7 +907,7 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad)
GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad);
{
- GLibLocker lock(GST_OBJECT(self));
+ GLibRecLocker lock(&self->stream_lock);
std::vector<GstPad *> &pads = self->state->srcpads_;
auto begin_iterator = pads.begin();
auto end_iterator = pads.end();
@@ -698,6 +934,7 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
element_class->request_new_pad = gst_libcamera_src_request_new_pad;
element_class->release_pad = gst_libcamera_src_release_pad;
element_class->change_state = gst_libcamera_src_change_state;
+ element_class->send_event = gst_libcamera_src_send_event;
gst_element_class_set_metadata(element_class,
"libcamera Source", "Source/Video",
@@ -717,4 +954,13 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec);
+
+ spec = g_param_spec_enum("auto-focus-mode",
+ "Set auto-focus mode",
+ "Available options: AfModeManual, "
+ "AfModeAuto or AfModeContinuous.",
+ gst_libcamera_auto_focus_get_type(),
+ static_cast<gint>(controls::AfModeManual),
+ G_PARAM_WRITABLE);
+ g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, spec);
}
diff --git a/src/gstreamer/gstlibcamerasrc.h b/src/gstreamer/gstlibcamerasrc.h
index fdea2f10..0a88ba02 100644
--- a/src/gstreamer/gstlibcamerasrc.h
+++ b/src/gstreamer/gstlibcamerasrc.h
@@ -8,6 +8,8 @@
#pragma once
+#include <libcamera/control_ids.h>
+
#include <gst/gst.h>
G_BEGIN_DECLS
@@ -17,3 +19,32 @@ G_DECLARE_FINAL_TYPE(GstLibcameraSrc, gst_libcamera_src,
GST_LIBCAMERA, SRC, GstElement)
G_END_DECLS
+
+inline GType
+gst_libcamera_auto_focus_get_type()
+{
+ static GType type = 0;
+ static const GEnumValue values[] = {
+ {
+ static_cast<gint>(libcamera::controls::AfModeManual),
+ "AfModeManual",
+ "manual-focus",
+ },
+ {
+ static_cast<gint>(libcamera::controls::AfModeAuto),
+ "AfModeAuto",
+ "automatic-auto-focus",
+ },
+ {
+ static_cast<gint>(libcamera::controls::AfModeContinuous),
+ "AfModeContinuous",
+ "continuous-auto-focus",
+ },
+ { 0, NULL, NULL }
+ };
+
+ if (!type)
+ type = g_enum_register_static("GstLibcameraAutoFocus", values);
+
+ return type;
+}
diff --git a/src/gstreamer/meson.build b/src/gstreamer/meson.build
index 77c79140..c2a01e7b 100644
--- a/src/gstreamer/meson.build
+++ b/src/gstreamer/meson.build
@@ -43,6 +43,18 @@ libcamera_gst = shared_library('gstlibcamera',
libcamera_gst_sources,
cpp_args : libcamera_gst_cpp_args,
dependencies : [libcamera_public, gstvideo_dep, gstallocator_dep],
- install: true,
+ install : true,
install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
)
+
+# Make the plugin visible to GStreamer inside meson devenv.
+fs = import('fs')
+gst_plugin_path = fs.parent(libcamera_gst.full_path())
+
+gst_env = environment()
+gst_env.prepend('GST_PLUGIN_PATH', gst_plugin_path)
+
+# Avoid polluting the system registry.
+gst_env.set('GST_REGISTRY', gst_plugin_path / 'registry.data')
+
+meson.add_devenv(gst_env)
diff --git a/src/ipa/ipu3/algorithms/af.cpp b/src/ipa/ipu3/algorithms/af.cpp
index d07521a0..12927eec 100644
--- a/src/ipa/ipu3/algorithms/af.cpp
+++ b/src/ipa/ipu3/algorithms/af.cpp
@@ -114,19 +114,6 @@ Af::Af()
}
/**
- * \copydoc libcamera::ipa::Algorithm::prepare
- */
-void Af::prepare(IPAContext &context, ipu3_uapi_params *params)
-{
- const struct ipu3_uapi_grid_config &grid = context.configuration.af.afGrid;
- params->acc_param.af.grid_cfg = grid;
- params->acc_param.af.filter_config = afFilterConfigDefault;
-
- /* Enable AF processing block */
- params->use.acc_af = 1;
-}
-
-/**
* \brief Configure the Af given a configInfo
* \param[in] context The shared IPA context
* \param[in] configInfo The IPA configuration data
@@ -195,11 +182,27 @@ int Af::configure(IPAContext &context, const IPAConfigInfo &configInfo)
}
/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Af::prepare(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ ipu3_uapi_params *params)
+{
+ const struct ipu3_uapi_grid_config &grid = context.configuration.af.afGrid;
+ params->acc_param.af.grid_cfg = grid;
+ params->acc_param.af.filter_config = afFilterConfigDefault;
+
+ /* Enable AF processing block */
+ params->use.acc_af = 1;
+}
+
+/**
* \brief AF coarse scan
- *
- * Find a near focused image using a coarse step. The step is determined by coarseSearchStep.
- *
* \param[in] context The shared IPA context
+ *
+ * Find a near focused image using a coarse step. The step is determined by
+ * kCoarseSearchStep.
*/
void Af::afCoarseScan(IPAContext &context)
{
@@ -223,10 +226,9 @@ void Af::afCoarseScan(IPAContext &context)
/**
* \brief AF fine scan
+ * \param[in] context The shared IPA context
*
* Find an optimum lens position with moving 1 step for each search.
- *
- * \param[in] context The shared IPA context
*/
void Af::afFineScan(IPAContext &context)
{
@@ -244,10 +246,9 @@ void Af::afFineScan(IPAContext &context)
/**
* \brief AF reset
+ * \param[in] context The shared IPA context
*
* Reset all the parameters to start over the AF process.
- *
- * \param[in] context The shared IPA context
*/
void Af::afReset(IPAContext &context)
{
@@ -266,9 +267,9 @@ void Af::afReset(IPAContext &context)
}
/**
- * \brief AF variance comparison.
+ * \brief AF variance comparison
* \param[in] context The IPA context
- * \param min_step The VCM movement step.
+ * \param[in] min_step The VCM movement step
*
* We always pick the largest variance to replace the previous one. The image
* with a larger variance also indicates it is a clearer image than previous
@@ -321,7 +322,7 @@ bool Af::afScan(IPAContext &context, int min_step)
}
/**
- * \brief Determine the frame to be ignored.
+ * \brief Determine the frame to be ignored
* \return Return True if the frame should be ignored, false otherwise
*/
bool Af::afNeedIgnoreFrame()
@@ -334,7 +335,7 @@ bool Af::afNeedIgnoreFrame()
}
/**
- * \brief Reset frame ignore counter.
+ * \brief Reset frame ignore counter
*/
void Af::afIgnoreFrameReset()
{
@@ -343,9 +344,8 @@ void Af::afIgnoreFrameReset()
/**
* \brief Estimate variance
- * \param y_item The AF filter data set from the IPU3 statistics buffer
- * \param len The quantity of table item entries which are valid to process
- * \param isY1 Selects between filter Y1 or Y2 to calculate the variance
+ * \param[in] y_items The AF filter data set from the IPU3 statistics buffer
+ * \param[in] isY1 Selects between filter Y1 or Y2 to calculate the variance
*
* Calculate the mean of the data set provided by \a y_item, and then calculate
* the variance of that data set from the mean.
@@ -377,16 +377,16 @@ double Af::afEstimateVariance(Span<const y_table_item_t> y_items, bool isY1)
}
/**
- * \brief Determine out-of-focus situation.
- * \param context The IPA context.
+ * \brief Determine out-of-focus situation
+ * \param[in] context The IPA context
*
* Out-of-focus means that the variance change rate for a focused and a new
* variance is greater than a threshold.
*
* \return True if the variance threshold is crossed indicating lost focus,
- * false otherwise.
+ * false otherwise
*/
-bool Af::afIsOutOfFocus(IPAContext context)
+bool Af::afIsOutOfFocus(IPAContext &context)
{
const uint32_t diff_var = std::abs(currentVariance_ -
context.activeState.af.maxVariance);
@@ -404,10 +404,12 @@ bool Af::afIsOutOfFocus(IPAContext context)
}
/**
- * \brief Determine the max contrast image and lens position.
- * \param[in] context The IPA context.
+ * \brief Determine the max contrast image and lens position
+ * \param[in] context The IPA context
+ * \param[in] frame The frame context sequence number
* \param[in] frameContext The current frame context
- * \param[in] stats The statistics buffer of IPU3.
+ * \param[in] stats The statistics buffer of IPU3
+ * \param[out] metadata Metadata for the frame, to be filled by the algorithm
*
* Ideally, a clear image also has a relatively higher contrast. So, every
* image for each focus step should be tested to find an optimal focus step.
@@ -420,8 +422,10 @@ bool Af::afIsOutOfFocus(IPAContext context)
*
* [1] Hill Climbing Algorithm, https://en.wikipedia.org/wiki/Hill_climbing
*/
-void Af::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats)
+void Af::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ [[maybe_unused]] ControlList &metadata)
{
/* Evaluate the AF buffer length */
uint32_t afRawBufferLen = context.configuration.af.afGrid.width *
@@ -450,6 +454,8 @@ void Af::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameCon
}
}
+REGISTER_IPA_ALGORITHM(Af, "Af")
+
} /* namespace ipa::ipu3::algorithms */
} /* namespace libcamera */
diff --git a/src/ipa/ipu3/algorithms/af.h b/src/ipa/ipu3/algorithms/af.h
index ccf015f3..c6168e30 100644
--- a/src/ipa/ipu3/algorithms/af.h
+++ b/src/ipa/ipu3/algorithms/af.h
@@ -30,10 +30,14 @@ public:
Af();
~Af() = default;
- void prepare(IPAContext &context, ipu3_uapi_params *params) override;
int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
- void process(IPAContext &context, IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ ipu3_uapi_params *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ ControlList &metadata) override;
private:
void afCoarseScan(IPAContext &context);
@@ -44,7 +48,7 @@ private:
void afIgnoreFrameReset();
double afEstimateVariance(Span<const y_table_item_t> y_items, bool isY1);
- bool afIsOutOfFocus(IPAContext context);
+ bool afIsOutOfFocus(IPAContext &context);
/* VCM step configuration. It is the current setting of the VCM step. */
uint32_t focus_;
diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index f16be534..606a237a 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -14,6 +14,7 @@
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include <libcamera/control_ids.h>
#include <libcamera/ipa/core_ipa_interface.h>
#include "libipa/histogram.h"
@@ -46,9 +47,8 @@ namespace ipa::ipu3::algorithms {
LOG_DEFINE_CATEGORY(IPU3Agc)
-/* Limits for analogue gain values */
+/* Minimum limit for analogue gain value */
static constexpr double kMinAnalogueGain = 1.0;
-static constexpr double kMaxAnalogueGain = 8.0;
/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */
static constexpr utils::Duration kMaxShutterSpeed = 60ms;
@@ -96,10 +96,10 @@ int Agc::configure(IPAContext &context,
kMaxShutterSpeed);
minAnalogueGain_ = std::max(configuration.agc.minAnalogueGain, kMinAnalogueGain);
- maxAnalogueGain_ = std::min(configuration.agc.maxAnalogueGain, kMaxAnalogueGain);
+ maxAnalogueGain_ = configuration.agc.maxAnalogueGain;
/* Configure the default exposure and gain. */
- activeState.agc.gain = std::max(minAnalogueGain_, kMinAnalogueGain);
+ activeState.agc.gain = minAnalogueGain_;
activeState.agc.exposure = 10ms / configuration.sensor.lineDuration;
frameCount_ = 0;
@@ -183,13 +183,13 @@ utils::Duration Agc::filterExposure(utils::Duration exposureValue)
* \param[in] yGain The gain calculated based on the relative luminance target
* \param[in] iqMeanGain The gain calculated based on the relative luminance target
*/
-void Agc::computeExposure(IPAContext &context, IPAFrameContext *frameContext,
+void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext,
double yGain, double iqMeanGain)
{
const IPASessionConfiguration &configuration = context.configuration;
/* Get the effective exposure and gain applied on the sensor. */
- uint32_t exposure = frameContext->sensor.exposure;
- double analogueGain = frameContext->sensor.gain;
+ uint32_t exposure = frameContext.sensor.exposure;
+ double analogueGain = frameContext.sensor.gain;
/* Use the highest of the two gain estimates. */
double evGain = std::max(yGain, iqMeanGain);
@@ -229,7 +229,7 @@ void Agc::computeExposure(IPAContext &context, IPAFrameContext *frameContext,
/*
* Filter the exposure.
- * \todo: estimate if we need to desaturate
+ * \todo estimate if we need to desaturate
*/
exposureValue = filterExposure(exposureValue);
@@ -317,14 +317,18 @@ double Agc::estimateLuminance(IPAActiveState &activeState,
/**
* \brief Process IPU3 statistics, and run AGC operations
* \param[in] context The shared IPA context
+ * \param[in] frame The current frame sequence number
* \param[in] frameContext The current frame context
* \param[in] stats The IPU3 statistics and ISP results
+ * \param[out] metadata Metadata for the frame, to be filled by the algorithm
*
* Identify the current image brightness, and use that to estimate the optimal
* new exposure and gain for the scene.
*/
-void Agc::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats)
+void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ ControlList &metadata)
{
/*
* Estimate the gain needed to have the proportion of pixels in a given
@@ -361,8 +365,23 @@ void Agc::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameCo
computeExposure(context, frameContext, yGain, iqMeanGain);
frameCount_++;
+
+ utils::Duration exposureTime = context.configuration.sensor.lineDuration
+ * frameContext.sensor.exposure;
+ metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
+ metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
+
+ /* \todo Use VBlank value calculated from each frame exposure. */
+ uint32_t vTotal = context.configuration.sensor.size.height
+ + context.configuration.sensor.defVBlank;
+ utils::Duration frameDuration = context.configuration.sensor.lineDuration
+ * vTotal;
+ metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
+
}
+REGISTER_IPA_ALGORITHM(Agc, "Agc")
+
} /* namespace ipa::ipu3::algorithms */
} /* namespace libcamera */
diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h
index 105ae0f2..9d6e3ff1 100644
--- a/src/ipa/ipu3/algorithms/agc.h
+++ b/src/ipa/ipu3/algorithms/agc.h
@@ -28,14 +28,16 @@ public:
~Agc() = default;
int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
- void process(IPAContext &context, IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ ControlList &metadata) override;
private:
double measureBrightness(const ipu3_uapi_stats_3a *stats,
const ipu3_uapi_grid_config &grid) const;
utils::Duration filterExposure(utils::Duration currentExposure);
- void computeExposure(IPAContext &context, IPAFrameContext *frameContext,
+ void computeExposure(IPAContext &context, IPAFrameContext &frameContext,
double yGain, double iqMeanGain);
double estimateLuminance(IPAActiveState &activeState,
const ipu3_uapi_grid_config &grid,
diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp
index 70426722..5abd4621 100644
--- a/src/ipa/ipu3/algorithms/awb.cpp
+++ b/src/ipa/ipu3/algorithms/awb.cpp
@@ -11,6 +11,8 @@
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
/**
* \file awb.h
*/
@@ -216,6 +218,89 @@ int Awb::configure(IPAContext &context,
return 0;
}
+constexpr uint16_t Awb::threshold(float value)
+{
+ /* AWB thresholds are in the range [0, 8191] */
+ return value * 8191;
+}
+
+constexpr uint16_t Awb::gainValue(double gain)
+{
+ /*
+ * The colour gains applied by the BNR for the four channels (Gr, R, B
+ * and Gb) are expressed in the parameters structure as 16-bit integers
+ * that store a fixed-point U3.13 value in the range [0, 8[.
+ *
+ * The real gain value is equal to the gain parameter plus one, i.e.
+ *
+ * Pout = Pin * (1 + gain / 8192)
+ *
+ * where 'Pin' is the input pixel value, 'Pout' the output pixel value,
+ * and 'gain' the gain in the parameters structure as a 16-bit integer.
+ */
+ return std::clamp((gain - 1.0) * 8192, 0.0, 65535.0);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Awb::prepare(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ ipu3_uapi_params *params)
+{
+ /*
+ * Green saturation thresholds are reduced because we are using the
+ * green channel only in the exposure computation.
+ */
+ params->acc_param.awb.config.rgbs_thr_r = threshold(1.0);
+ params->acc_param.awb.config.rgbs_thr_gr = threshold(0.9);
+ params->acc_param.awb.config.rgbs_thr_gb = threshold(0.9);
+ params->acc_param.awb.config.rgbs_thr_b = threshold(1.0);
+
+ /*
+ * Enable saturation inclusion on thr_b for ImgU to update the
+ * ipu3_uapi_awb_set_item->sat_ratio field.
+ */
+ params->acc_param.awb.config.rgbs_thr_b |= IPU3_UAPI_AWB_RGBS_THR_B_INCL_SAT |
+ IPU3_UAPI_AWB_RGBS_THR_B_EN;
+
+ const ipu3_uapi_grid_config &grid = context.configuration.grid.bdsGrid;
+
+ params->acc_param.awb.config.grid = context.configuration.grid.bdsGrid;
+
+ /*
+ * Optical center is column start (respectively row start) of the
+ * cell of interest minus its X center (respectively Y center).
+ *
+ * For the moment use BDS as a first approximation, but it should
+ * be calculated based on Shading (SHD) parameters.
+ */
+ params->acc_param.bnr = imguCssBnrDefaults;
+ Size &bdsOutputSize = context.configuration.grid.bdsOutputSize;
+ params->acc_param.bnr.column_size = bdsOutputSize.width;
+ params->acc_param.bnr.opt_center.x_reset = grid.x_start - (bdsOutputSize.width / 2);
+ params->acc_param.bnr.opt_center.y_reset = grid.y_start - (bdsOutputSize.height / 2);
+ params->acc_param.bnr.opt_center_sqr.x_sqr_reset = params->acc_param.bnr.opt_center.x_reset
+ * params->acc_param.bnr.opt_center.x_reset;
+ params->acc_param.bnr.opt_center_sqr.y_sqr_reset = params->acc_param.bnr.opt_center.y_reset
+ * params->acc_param.bnr.opt_center.y_reset;
+
+ params->acc_param.bnr.wb_gains.gr = gainValue(context.activeState.awb.gains.green);
+ params->acc_param.bnr.wb_gains.r = gainValue(context.activeState.awb.gains.red);
+ params->acc_param.bnr.wb_gains.b = gainValue(context.activeState.awb.gains.blue);
+ params->acc_param.bnr.wb_gains.gb = gainValue(context.activeState.awb.gains.green);
+
+ LOG(IPU3Awb, Debug) << "Color temperature estimated: " << asyncResults_.temperatureK;
+
+ /* The CCM matrix may change when color temperature will be used */
+ params->acc_param.ccm = imguCssCcmDefault;
+
+ params->use.acc_awb = 1;
+ params->use.acc_bnr = 1;
+ params->use.acc_ccm = 1;
+}
+
/**
* The function estimates the correlated color temperature using
* from RGB color space input.
@@ -387,8 +472,10 @@ void Awb::calculateWBGains(const ipu3_uapi_stats_3a *stats)
/**
* \copydoc libcamera::ipa::Algorithm::process
*/
-void Awb::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats)
+void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ [[maybe_unused]] ControlList &metadata)
{
calculateWBGains(stats);
@@ -401,87 +488,17 @@ void Awb::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameCo
context.activeState.awb.gains.green = asyncResults_.greenGain;
context.activeState.awb.gains.red = asyncResults_.redGain;
context.activeState.awb.temperatureK = asyncResults_.temperatureK;
-}
-constexpr uint16_t Awb::threshold(float value)
-{
- /* AWB thresholds are in the range [0, 8191] */
- return value * 8191;
+ metadata.set(controls::AwbEnable, true);
+ metadata.set(controls::ColourGains, {
+ static_cast<float>(context.activeState.awb.gains.red),
+ static_cast<float>(context.activeState.awb.gains.blue)
+ });
+ metadata.set(controls::ColourTemperature,
+ context.activeState.awb.temperatureK);
}
-constexpr uint16_t Awb::gainValue(double gain)
-{
- /*
- * The colour gains applied by the BNR for the four channels (Gr, R, B
- * and Gb) are expressed in the parameters structure as 16-bit integers
- * that store a fixed-point U3.13 value in the range [0, 8[.
- *
- * The real gain value is equal to the gain parameter plus one, i.e.
- *
- * Pout = Pin * (1 + gain / 8192)
- *
- * where 'Pin' is the input pixel value, 'Pout' the output pixel value,
- * and 'gain' the gain in the parameters structure as a 16-bit integer.
- */
- return std::clamp((gain - 1.0) * 8192, 0.0, 65535.0);
-}
-
-/**
- * \copydoc libcamera::ipa::Algorithm::prepare
- */
-void Awb::prepare(IPAContext &context, ipu3_uapi_params *params)
-{
- /*
- * Green saturation thresholds are reduced because we are using the
- * green channel only in the exposure computation.
- */
- params->acc_param.awb.config.rgbs_thr_r = threshold(1.0);
- params->acc_param.awb.config.rgbs_thr_gr = threshold(0.9);
- params->acc_param.awb.config.rgbs_thr_gb = threshold(0.9);
- params->acc_param.awb.config.rgbs_thr_b = threshold(1.0);
-
- /*
- * Enable saturation inclusion on thr_b for ImgU to update the
- * ipu3_uapi_awb_set_item->sat_ratio field.
- */
- params->acc_param.awb.config.rgbs_thr_b |= IPU3_UAPI_AWB_RGBS_THR_B_INCL_SAT |
- IPU3_UAPI_AWB_RGBS_THR_B_EN;
-
- const ipu3_uapi_grid_config &grid = context.configuration.grid.bdsGrid;
-
- params->acc_param.awb.config.grid = context.configuration.grid.bdsGrid;
-
- /*
- * Optical center is column start (respectively row start) of the
- * cell of interest minus its X center (respectively Y center).
- *
- * For the moment use BDS as a first approximation, but it should
- * be calculated based on Shading (SHD) parameters.
- */
- params->acc_param.bnr = imguCssBnrDefaults;
- Size &bdsOutputSize = context.configuration.grid.bdsOutputSize;
- params->acc_param.bnr.column_size = bdsOutputSize.width;
- params->acc_param.bnr.opt_center.x_reset = grid.x_start - (bdsOutputSize.width / 2);
- params->acc_param.bnr.opt_center.y_reset = grid.y_start - (bdsOutputSize.height / 2);
- params->acc_param.bnr.opt_center_sqr.x_sqr_reset = params->acc_param.bnr.opt_center.x_reset
- * params->acc_param.bnr.opt_center.x_reset;
- params->acc_param.bnr.opt_center_sqr.y_sqr_reset = params->acc_param.bnr.opt_center.y_reset
- * params->acc_param.bnr.opt_center.y_reset;
-
- params->acc_param.bnr.wb_gains.gr = gainValue(context.activeState.awb.gains.green);
- params->acc_param.bnr.wb_gains.r = gainValue(context.activeState.awb.gains.red);
- params->acc_param.bnr.wb_gains.b = gainValue(context.activeState.awb.gains.blue);
- params->acc_param.bnr.wb_gains.gb = gainValue(context.activeState.awb.gains.green);
-
- LOG(IPU3Awb, Debug) << "Color temperature estimated: " << asyncResults_.temperatureK;
-
- /* The CCM matrix may change when color temperature will be used */
- params->acc_param.ccm = imguCssCcmDefault;
-
- params->use.acc_awb = 1;
- params->use.acc_bnr = 1;
- params->use.acc_ccm = 1;
-}
+REGISTER_IPA_ALGORITHM(Awb, "Awb")
} /* namespace ipa::ipu3::algorithms */
diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h
index 0acd2148..7a70854e 100644
--- a/src/ipa/ipu3/algorithms/awb.h
+++ b/src/ipa/ipu3/algorithms/awb.h
@@ -39,9 +39,13 @@ public:
~Awb();
int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
- void prepare(IPAContext &context, ipu3_uapi_params *params) override;
- void process(IPAContext &context, IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ ipu3_uapi_params *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ ControlList &metadata) override;
private:
/* \todo Make these structs available to all the ISPs ? */
diff --git a/src/ipa/ipu3/algorithms/blc.cpp b/src/ipa/ipu3/algorithms/blc.cpp
index 78ab7bff..e838072a 100644
--- a/src/ipa/ipu3/algorithms/blc.cpp
+++ b/src/ipa/ipu3/algorithms/blc.cpp
@@ -38,14 +38,18 @@ BlackLevelCorrection::BlackLevelCorrection()
/**
* \brief Fill in the parameter structure, and enable black level correction
- * \param context The shared IPA context
- * \param params The IPU3 parameters
+ * \param[in] context The shared IPA context
+ * \param[in] frame The frame context sequence number
+ * \param[in] frameContext The FrameContext for this frame
+ * \param[out] params The IPU3 parameters
*
* Populate the IPU3 parameter structure with the correction values for each
* channel and enable the corresponding ImgU block processing.
*/
void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
- ipu3_uapi_params *params)
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ ipu3_uapi_params *params)
{
/*
* The Optical Black Level correction values
@@ -62,6 +66,8 @@ void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
params->use.obgrid_param = 1;
}
+REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection")
+
} /* namespace ipa::ipu3::algorithms */
} /* namespace libcamera */
diff --git a/src/ipa/ipu3/algorithms/blc.h b/src/ipa/ipu3/algorithms/blc.h
index d8da1748..292bf67b 100644
--- a/src/ipa/ipu3/algorithms/blc.h
+++ b/src/ipa/ipu3/algorithms/blc.h
@@ -18,7 +18,9 @@ class BlackLevelCorrection : public Algorithm
public:
BlackLevelCorrection();
- void prepare(IPAContext &context, ipu3_uapi_params *params) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ ipu3_uapi_params *params) override;
};
} /* namespace ipa::ipu3::algorithms */
diff --git a/src/ipa/ipu3/algorithms/tone_mapping.cpp b/src/ipa/ipu3/algorithms/tone_mapping.cpp
index f86e79b2..a169894c 100644
--- a/src/ipa/ipu3/algorithms/tone_mapping.cpp
+++ b/src/ipa/ipu3/algorithms/tone_mapping.cpp
@@ -49,13 +49,17 @@ int ToneMapping::configure(IPAContext &context,
/**
* \brief Fill in the parameter structure, and enable gamma control
- * \param context The shared IPA context
- * \param params The IPU3 parameters
+ * \param[in] context The shared IPA context
+ * \param[in] frame The frame context sequence number
+ * \param[in] frameContext The FrameContext for this frame
+ * \param[out] params The IPU3 parameters
*
* Populate the IPU3 parameter structure with our tone mapping look up table and
* enable the gamma control module in the processing blocks.
*/
void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
ipu3_uapi_params *params)
{
/* Copy the calculated LUT into the parameters buffer. */
@@ -71,15 +75,19 @@ void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
/**
* \brief Calculate the tone mapping look up table
- * \param context The shared IPA context
- * \param frameContext The current frame context
- * \param stats The IPU3 statistics and ISP results
+ * \param[in] context The shared IPA context
+ * \param[in] frame The current frame sequence number
+ * \param[in] frameContext The current frame context
+ * \param[in] stats The IPU3 statistics and ISP results
+ * \param[out] metadata Metadata for the frame, to be filled by the algorithm
*
* The tone mapping look up table is generated as an inverse power curve from
* our gamma setting.
*/
-void ToneMapping::process(IPAContext &context, [[maybe_unused]] IPAFrameContext *frameContext,
- [[maybe_unused]] const ipu3_uapi_stats_3a *stats)
+void ToneMapping::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
+ [[maybe_unused]] ControlList &metadata)
{
/*
* Hardcode gamma to 1.1 as a default for now.
@@ -105,6 +113,8 @@ void ToneMapping::process(IPAContext &context, [[maybe_unused]] IPAFrameContext
context.activeState.toneMapping.gamma = gamma_;
}
+REGISTER_IPA_ALGORITHM(ToneMapping, "ToneMapping")
+
} /* namespace ipa::ipu3::algorithms */
} /* namespace libcamera */
diff --git a/src/ipa/ipu3/algorithms/tone_mapping.h b/src/ipa/ipu3/algorithms/tone_mapping.h
index d7d48006..5ae35da5 100644
--- a/src/ipa/ipu3/algorithms/tone_mapping.h
+++ b/src/ipa/ipu3/algorithms/tone_mapping.h
@@ -19,9 +19,12 @@ public:
ToneMapping();
int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
- void prepare(IPAContext &context, ipu3_uapi_params *params) override;
- void process(IPAContext &context, IPAFrameContext *frameContext,
- const ipu3_uapi_stats_3a *stats) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext, ipu3_uapi_params *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ipu3_uapi_stats_3a *stats,
+ ControlList &metadata) override;
private:
double gamma_;
diff --git a/src/ipa/ipu3/data/meson.build b/src/ipa/ipu3/data/meson.build
new file mode 100644
index 00000000..0f7cd5c6
--- /dev/null
+++ b/src/ipa/ipu3/data/meson.build
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: CC0-1.0
+
+conf_files = files([
+ 'uncalibrated.yaml',
+])
+
+install_data(conf_files,
+ install_dir : ipa_data_dir / 'ipu3',
+ install_tag : 'runtime')
diff --git a/src/ipa/ipu3/data/uncalibrated.yaml b/src/ipa/ipu3/data/uncalibrated.yaml
new file mode 100644
index 00000000..794ab3ed
--- /dev/null
+++ b/src/ipa/ipu3/data/uncalibrated.yaml
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+ - Af:
+ - Agc:
+ - Awb:
+ - BlackLevelCorrection:
+ - ToneMapping:
+...
diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
index 13cdb835..959f314f 100644
--- a/src/ipa/ipu3/ipa_context.cpp
+++ b/src/ipa/ipu3/ipa_context.cpp
@@ -36,22 +36,6 @@ namespace libcamera::ipa::ipu3 {
*/
/**
- * \struct IPAFrameContext
- * \brief Context for a frame
- *
- * The frame context stores data specific to a single frame processed by the
- * IPA. Each frame processed by the IPA has a context associated with it,
- * accessible through the IPAContext structure.
- *
- * Fields in the frame context should reflect values and controls
- * associated with the specific frame as requested by the application, and
- * as configured by the hardware. Fields can be read by algorithms to
- * determine if they should update any specific action for this frame, and
- * finally to update the metadata control lists when the frame is fully
- * completed.
- */
-
-/**
* \struct IPAContext
* \brief Global IPA context data shared between all algorithms
*
@@ -84,22 +68,21 @@ namespace libcamera::ipa::ipu3 {
* \brief AF grid configuration of the IPA
*
* \var IPASessionConfiguration::af.afGrid
- * \brief AF scene grid configuration.
+ * \brief AF scene grid configuration
*/
/**
* \var IPAActiveState::af
* \brief Context for the Automatic Focus algorithm
*
- * \struct IPAActiveState::af
* \var IPAActiveState::af.focus
* \brief Current position of the lens
*
* \var IPAActiveState::af.maxVariance
- * \brief The maximum variance of the current image.
+ * \brief The maximum variance of the current image
*
* \var IPAActiveState::af.stable
- * \brief It is set to true, if the best focus is found.
+ * \brief It is set to true, if the best focus is found
*/
/**
@@ -128,6 +111,9 @@ namespace libcamera::ipa::ipu3 {
*
* \var IPASessionConfiguration::sensor.defVBlank
* \brief The default vblank value of the sensor
+ *
+ * \var IPASessionConfiguration::sensor.size
+ * \brief Sensor output resolution
*/
/**
@@ -150,7 +136,7 @@ namespace libcamera::ipa::ipu3 {
* \var IPAActiveState::awb
* \brief Context for the Automatic White Balance algorithm
*
- * \struct IPAActiveState::awb.gains
+ * \var IPAActiveState::awb.gains
* \brief White balance gains
*
* \var IPAActiveState::awb.gains.red
@@ -181,25 +167,8 @@ namespace libcamera::ipa::ipu3 {
*/
/**
- * \brief Default constructor for IPAFrameContext
- */
-IPAFrameContext::IPAFrameContext() = default;
-
-/**
- * \brief Construct a IPAFrameContext instance
- */
-IPAFrameContext::IPAFrameContext(uint32_t id, const ControlList &reqControls)
- : frame(id), frameControls(reqControls)
-{
- sensor = {};
-}
-
-/**
- * \var IPAFrameContext::frame
- * \brief The frame number
- *
- * \var IPAFrameContext::frameControls
- * \brief Controls sent in by the application while queuing the request
+ * \struct IPAFrameContext
+ * \brief IPU3-specific FrameContext
*
* \var IPAFrameContext::sensor
* \brief Effective sensor values that were applied for the frame
diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
index 42e11141..e9a3863b 100644
--- a/src/ipa/ipu3/ipa_context.h
+++ b/src/ipa/ipu3/ipa_context.h
@@ -8,22 +8,18 @@
#pragma once
-#include <array>
-
#include <linux/intel-ipu3.h>
#include <libcamera/base/utils.h>
-#include <libcamera/controls.h>
#include <libcamera/geometry.h>
+#include <libipa/fc_queue.h>
+
namespace libcamera {
namespace ipa::ipu3 {
-/* Maximum number of frame contexts to be held */
-static constexpr uint32_t kMaxFrameContexts = 16;
-
struct IPASessionConfiguration {
struct {
ipu3_uapi_grid_config bdsGrid;
@@ -45,6 +41,7 @@ struct IPASessionConfiguration {
struct {
int32_t defVBlank;
utils::Duration lineDuration;
+ Size size;
} sensor;
};
@@ -76,24 +73,18 @@ struct IPAActiveState {
} toneMapping;
};
-struct IPAFrameContext {
- IPAFrameContext();
- IPAFrameContext(uint32_t id, const ControlList &reqControls);
-
+struct IPAFrameContext : public FrameContext {
struct {
uint32_t exposure;
double gain;
} sensor;
-
- uint32_t frame;
- ControlList frameControls;
};
struct IPAContext {
IPASessionConfiguration configuration;
IPAActiveState activeState;
- std::array<IPAFrameContext, kMaxFrameContexts> frameContexts;
+ FCQueue<IPAFrameContext> frameContexts;
};
} /* namespace ipa::ipu3 */
diff --git a/src/ipa/ipu3/ipu3-ipa-design-guide.rst b/src/ipa/ipu3/ipu3-ipa-design-guide.rst
index e724fdda..72506397 100644
--- a/src/ipa/ipu3/ipu3-ipa-design-guide.rst
+++ b/src/ipa/ipu3/ipu3-ipa-design-guide.rst
@@ -1,3 +1,5 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
IPU3 IPA Architecture Design and Overview
=========================================
diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 2f6bb672..08ee6eb3 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -18,6 +18,7 @@
#include <linux/intel-ipu3.h>
#include <linux/v4l2-controls.h>
+#include <libcamera/base/file.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -29,6 +30,7 @@
#include <libcamera/request.h>
#include "libcamera/internal/mapped_framebuffer.h"
+#include "libcamera/internal/yaml_parser.h"
#include "algorithms/af.h"
#include "algorithms/agc.h"
@@ -38,6 +40,8 @@
#include "algorithms/tone_mapping.h"
#include "libipa/camera_sensor_helper.h"
+#include "ipa_context.h"
+
/* Minimum grid width, expressed as a number of cells */
static constexpr uint32_t kMinGridWidth = 16;
/* Maximum grid width, expressed as a number of cells */
@@ -51,6 +55,9 @@ static constexpr uint32_t kMinCellSizeLog2 = 3;
/* log2 of the maximum grid cell width and height, in pixels */
static constexpr uint32_t kMaxCellSizeLog2 = 6;
+/* Maximum number of frame contexts to be held */
+static constexpr uint32_t kMaxFrameContexts = 16;
+
namespace libcamera {
LOG_DEFINE_CATEGORY(IPAIPU3)
@@ -71,7 +78,7 @@ namespace ipa::ipu3 {
*
* At initialisation time, a CameraSensorHelper is instantiated to support
* camera-specific calculations, while the default controls are computed, and
- * the algorithms are constructed and placed in an ordered list.
+ * the algorithms are instantiated from the tuning data file.
*
* The IPU3 ImgU operates with a grid layout to divide the overall frame into
* rectangular cells of pixels. When the IPA is configured, we determine the
@@ -92,12 +99,14 @@ namespace ipa::ipu3 {
* fillParamsBuffer() call.
*
* The individual algorithms are split into modular components that are called
- * iteratively to allow them to process statistics from the ImgU in a defined
- * order.
+ * iteratively to allow them to process statistics from the ImgU in the order
+ * defined in the tuning data file.
*
- * The current implementation supports three core algorithms:
- * - Automatic white balance (AWB)
+ * The current implementation supports five core algorithms:
+ *
+ * - Auto focus (AF)
* - Automatic gain and exposure control (AGC)
+ * - Automatic white balance (AWB)
* - Black level correction (BLC)
* - Tone mapping (Gamma)
*
@@ -128,9 +137,11 @@ namespace ipa::ipu3 {
* sensor-specific tuning to adapt for Black Level compensation (BLC), Lens
* shading correction (SHD) and Color correction (CCM).
*/
-class IPAIPU3 : public IPAIPU3Interface
+class IPAIPU3 : public IPAIPU3Interface, public Module
{
public:
+ IPAIPU3();
+
int init(const IPASettings &settings,
const IPACameraSensorInfo &sensorInfo,
const ControlInfoMap &sensorControls,
@@ -150,14 +161,16 @@ public:
void processStatsBuffer(const uint32_t frame, const int64_t frameTimestamp,
const uint32_t bufferId,
const ControlList &sensorControls) override;
+
+protected:
+ std::string logPrefix() const override;
+
private:
void updateControls(const IPACameraSensorInfo &sensorInfo,
const ControlInfoMap &sensorControls,
ControlInfoMap *ipaControls);
void updateSessionConfiguration(const ControlInfoMap &sensorControls);
- bool validateSensorControls();
-
void setControls(unsigned int frame);
void calculateBdsGrid(const Size &bdsOutputSize);
@@ -171,13 +184,20 @@ private:
/* Interface to the Camera Helper */
std::unique_ptr<CameraSensorHelper> camHelper_;
- /* Maintain the algorithms used by the IPA */
- std::list<std::unique_ptr<ipa::ipu3::Algorithm>> algorithms_;
-
/* Local parameter storage */
struct IPAContext context_;
};
+IPAIPU3::IPAIPU3()
+ : context_({ {}, {}, { kMaxFrameContexts } })
+{
+}
+
+std::string IPAIPU3::logPrefix() const
+{
+ return "ipu3";
+}
+
/**
* \brief Compute IPASessionConfiguration using the sensor information and the
* sensor V4L2 controls
@@ -271,28 +291,6 @@ void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo,
}
/**
- * \brief Validate that the sensor controls mandatory for the IPA exists
- */
-bool IPAIPU3::validateSensorControls()
-{
- static const uint32_t ctrls[] = {
- V4L2_CID_ANALOGUE_GAIN,
- V4L2_CID_EXPOSURE,
- V4L2_CID_VBLANK,
- };
-
- for (auto c : ctrls) {
- if (sensorCtrls_.find(c) == sensorCtrls_.end()) {
- LOG(IPAIPU3, Error) << "Unable to find sensor control "
- << utils::hex(c);
- return false;
- }
- }
-
- return true;
-}
-
-/**
* \brief Initialize the IPA module and its controls
*
* This function receives the camera sensor information from the pipeline
@@ -304,7 +302,7 @@ int IPAIPU3::init(const IPASettings &settings,
const ControlInfoMap &sensorControls,
ControlInfoMap *ipaControls)
{
- camHelper_ = CameraSensorHelperFactory::create(settings.sensorModel);
+ camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
if (camHelper_ == nullptr) {
LOG(IPAIPU3, Error)
<< "Failed to create camera sensor helper for "
@@ -314,14 +312,39 @@ int IPAIPU3::init(const IPASettings &settings,
/* Clean context */
context_.configuration = {};
- context_.configuration.sensor.lineDuration = sensorInfo.lineLength * 1.0s / sensorInfo.pixelRate;
+ context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
+ * 1.0s / sensorInfo.pixelRate;
- /* Construct our Algorithms */
- algorithms_.push_back(std::make_unique<algorithms::Af>());
- algorithms_.push_back(std::make_unique<algorithms::Agc>());
- algorithms_.push_back(std::make_unique<algorithms::Awb>());
- algorithms_.push_back(std::make_unique<algorithms::BlackLevelCorrection>());
- algorithms_.push_back(std::make_unique<algorithms::ToneMapping>());
+ /* Load the tuning data file. */
+ File file(settings.configurationFile);
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ int ret = file.error();
+ LOG(IPAIPU3, Error)
+ << "Failed to open configuration file "
+ << settings.configurationFile << ": " << strerror(-ret);
+ return ret;
+ }
+
+ std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+ if (!data)
+ return -EINVAL;
+
+ unsigned int version = (*data)["version"].get<uint32_t>(0);
+ if (version != 1) {
+ LOG(IPAIPU3, Error)
+ << "Invalid tuning file version " << version;
+ return -EINVAL;
+ }
+
+ if (!data->contains("algorithms")) {
+ LOG(IPAIPU3, Error)
+ << "Tuning file doesn't contain any algorithm";
+ return -EINVAL;
+ }
+
+ int ret = createAlgorithms(context_, (*data)["algorithms"]);
+ if (ret)
+ return ret;
/* Initialize controls. */
updateControls(sensorInfo, sensorControls, ipaControls);
@@ -348,6 +371,7 @@ int IPAIPU3::start()
*/
void IPAIPU3::stop()
{
+ context_.frameContexts.clear();
}
/**
@@ -446,6 +470,16 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo,
lensCtrls_ = configInfo.lensControls;
+ /* Clear the IPA context for the new streaming session. */
+ context_.activeState = {};
+ context_.configuration = {};
+ context_.frameContexts.clear();
+
+ /* Initialise the sensor configuration. */
+ context_.configuration.sensor.lineDuration = sensorInfo_.minLineLength
+ * 1.0s / sensorInfo_.pixelRate;
+ context_.configuration.sensor.size = sensorInfo_.outputSize;
+
/*
* Compute the sensor V4L2 controls to be used by the algorithms and
* to be set on the sensor.
@@ -454,23 +488,13 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo,
calculateBdsGrid(configInfo.bdsOutputSize);
- /* Clean IPAActiveState at each reconfiguration. */
- context_.activeState = {};
- IPAFrameContext initFrameContext;
- context_.frameContexts.fill(initFrameContext);
-
- if (!validateSensorControls()) {
- LOG(IPAIPU3, Error) << "Sensor control validation failed.";
- return -EINVAL;
- }
-
/* Update the camera controls using the new sensor settings. */
updateControls(sensorInfo_, sensorCtrls_, ipaControls);
/* Update the IPASessionConfiguration using the sensor settings. */
updateSessionConfiguration(sensorCtrls_);
- for (auto const &algo : algorithms_) {
+ for (auto const &algo : algorithms()) {
int ret = algo->configure(context_, configInfo);
if (ret)
return ret;
@@ -538,8 +562,10 @@ void IPAIPU3::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId)
*/
params->use = {};
- for (auto const &algo : algorithms_)
- algo->prepare(context_, params);
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+
+ for (auto const &algo : algorithms())
+ algo->prepare(context_, frame, frameContext, params);
paramsBufferReady.emit(frame);
}
@@ -569,33 +595,18 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
const ipu3_uapi_stats_3a *stats =
reinterpret_cast<ipu3_uapi_stats_3a *>(mem.data());
- IPAFrameContext &frameContext = context_.frameContexts[frame % kMaxFrameContexts];
-
- if (frameContext.frame != frame)
- LOG(IPAIPU3, Warning) << "Frame " << frame << " does not match its frame context";
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
frameContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
- double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
- int32_t vBlank = context_.configuration.sensor.defVBlank;
- ControlList ctrls(controls::controls);
+ ControlList metadata(controls::controls);
- for (auto const &algo : algorithms_)
- algo->process(context_, &frameContext, stats);
+ for (auto const &algo : algorithms())
+ algo->process(context_, frame, frameContext, stats, metadata);
setControls(frame);
- /* \todo Use VBlank value calculated from each frame exposure. */
- int64_t frameDuration = (vBlank + sensorInfo_.outputSize.height) * lineDuration;
- ctrls.set(controls::FrameDuration, frameDuration);
-
- ctrls.set(controls::AnalogueGain, frameContext.sensor.gain);
-
- ctrls.set(controls::ColourTemperature, context_.activeState.awb.temperatureK);
-
- ctrls.set(controls::ExposureTime, frameContext.sensor.exposure * lineDuration);
-
/*
* \todo The Metadata provides a path to getting extended data
* out to the application. Further data such as a simplifed Histogram
@@ -604,7 +615,7 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
* likely want to avoid putting platform specific metadata in.
*/
- metadataReady.emit(frame, ctrls);
+ metadataReady.emit(frame, metadata);
}
/**
@@ -617,8 +628,10 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
*/
void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls)
{
- /* \todo Start processing for 'frame' based on 'controls'. */
- context_.frameContexts[frame % kMaxFrameContexts] = { frame, controls };
+ IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
+
+ for (auto const &algo : algorithms())
+ algo->queueRequest(context_, frame, frameContext, controls);
}
/**
diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build
index 3194111a..66c39843 100644
--- a/src/ipa/ipu3/meson.build
+++ b/src/ipa/ipu3/meson.build
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: CC0-1.0
subdir('algorithms')
+subdir('data')
ipa_name = 'ipa_ipu3'
@@ -28,3 +29,5 @@ if ipa_sign_module
install : false,
build_by_default : true)
endif
+
+ipa_names += ipa_name
diff --git a/src/ipa/libipa/algorithm.cpp b/src/ipa/libipa/algorithm.cpp
index 8549fe3f..bc1c29a6 100644
--- a/src/ipa/libipa/algorithm.cpp
+++ b/src/ipa/libipa/algorithm.cpp
@@ -67,10 +67,29 @@ namespace ipa {
*/
/**
+ * \fn Algorithm::queueRequest()
+ * \brief Provide control values to the algorithm
+ * \param[in] context The shared IPA context
+ * \param[in] frame The frame number to apply the control values
+ * \param[in] frameContext The current frame's context
+ * \param[in] controls The list of user controls
+ *
+ * This function is called for each request queued to the camera. It provides
+ * the controls stored in the request to the algorithm. The \a frame number
+ * is the Request sequence number and identifies the desired corresponding
+ * frame to target for the controls to take effect.
+ *
+ * Algorithms shall read the applicable controls and store their value for later
+ * use during frame processing.
+ */
+
+/**
* \fn Algorithm::prepare()
* \brief Fill the \a params buffer with ISP processing parameters for a frame
* \param[in] context The shared IPA context
- * \param[out] params The ISP specific parameters.
+ * \param[in] frame The frame context sequence number
+ * \param[in] frameContext The FrameContext for this frame
+ * \param[out] params The ISP specific parameters
*
* This function is called for every frame when the camera is running before it
* is processed by the ISP to prepare the ISP processing parameters for that
@@ -85,13 +104,15 @@ namespace ipa {
* \fn Algorithm::process()
* \brief Process ISP statistics, and run algorithm operations
* \param[in] context The shared IPA context
+ * \param[in] frame The frame context sequence number
* \param[in] frameContext The current frame's context
* \param[in] stats The IPA statistics and ISP results
+ * \param[out] metadata Metadata for the frame, to be filled by the algorithm
*
* This function is called while camera is running for every frame processed by
* the ISP, to process statistics generated from that frame by the ISP.
- * Algorithms shall use this data to run calculations and update their state
- * accordingly.
+ * Algorithms shall use this data to run calculations, update their state
+ * accordingly, and fill the frame metadata.
*
* Processing shall not take an undue amount of time, and any extended or
* computationally expensive calculations or operations must be handled
diff --git a/src/ipa/libipa/algorithm.h b/src/ipa/libipa/algorithm.h
index 2a8871d8..987e3e4c 100644
--- a/src/ipa/libipa/algorithm.h
+++ b/src/ipa/libipa/algorithm.h
@@ -7,8 +7,11 @@
#pragma once
#include <memory>
+#include <stdint.h>
#include <string>
+#include <libcamera/controls.h>
+
namespace libcamera {
class YamlObject;
@@ -35,14 +38,25 @@ public:
return 0;
}
+ virtual void queueRequest([[maybe_unused]] typename Module::Context &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] typename Module::FrameContext &frameContext,
+ [[maybe_unused]] const ControlList &controls)
+ {
+ }
+
virtual void prepare([[maybe_unused]] typename Module::Context &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] typename Module::FrameContext &frameContext,
[[maybe_unused]] typename Module::Params *params)
{
}
virtual void process([[maybe_unused]] typename Module::Context &context,
- [[maybe_unused]] typename Module::FrameContext *frameContext,
- [[maybe_unused]] const typename Module::Stats *stats)
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] typename Module::FrameContext &frameContext,
+ [[maybe_unused]] const typename Module::Stats *stats,
+ [[maybe_unused]] ControlList &metadata)
{
}
};
diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
index d4dba497..ce29f423 100644
--- a/src/ipa/libipa/camera_sensor_helper.cpp
+++ b/src/ipa/libipa/camera_sensor_helper.cpp
@@ -43,7 +43,8 @@ namespace ipa {
* \brief Construct a CameraSensorHelper instance
*
* CameraSensorHelper derived class instances shall never be constructed
- * manually but always through the CameraSensorHelperFactory::create() function.
+ * manually but always through the CameraSensorHelperFactoryBase::create()
+ * function.
*/
/**
@@ -217,27 +218,25 @@ double CameraSensorHelper::gain(uint32_t gainCode) const
*/
/**
- * \class CameraSensorHelperFactory
- * \brief Registration of CameraSensorHelperFactory classes and creation of instances
+ * \class CameraSensorHelperFactoryBase
+ * \brief Base class for camera sensor helper factories
*
- * To facilitate discovery and instantiation of CameraSensorHelper classes, the
- * CameraSensorHelperFactory class maintains a registry of camera sensor helper
- * sub-classes. Each CameraSensorHelper subclass shall register itself using the
- * REGISTER_CAMERA_SENSOR_HELPER() macro, which will create a corresponding
- * instance of a CameraSensorHelperFactory subclass and register it with the
- * static list of factories.
+ * The CameraSensorHelperFactoryBase class is the base of all specializations of
+ * the CameraSensorHelperFactory class template. It implements the factory
+ * registration, maintains a registry of factories, and provides access to the
+ * registered factories.
*/
/**
- * \brief Construct a camera sensor helper factory
+ * \brief Construct a camera sensor helper factory base
* \param[in] name Name of the camera sensor helper class
*
- * Creating an instance of the factory registers it with the global list of
+ * Creating an instance of the factory base registers it with the global list of
* factories, accessible through the factories() function.
*
- * The factory \a name is used for debug purpose and shall be unique.
+ * The factory \a name is used to look up factories and shall be unique.
*/
-CameraSensorHelperFactory::CameraSensorHelperFactory(const std::string name)
+CameraSensorHelperFactoryBase::CameraSensorHelperFactoryBase(const std::string name)
: name_(name)
{
registerType(this);
@@ -252,17 +251,16 @@ CameraSensorHelperFactory::CameraSensorHelperFactory(const std::string name)
* corresponding to the named factory or a null pointer if no such factory
* exists
*/
-std::unique_ptr<CameraSensorHelper> CameraSensorHelperFactory::create(const std::string &name)
+std::unique_ptr<CameraSensorHelper> CameraSensorHelperFactoryBase::create(const std::string &name)
{
- std::vector<CameraSensorHelperFactory *> &factories =
- CameraSensorHelperFactory::factories();
+ const std::vector<CameraSensorHelperFactoryBase *> &factories =
+ CameraSensorHelperFactoryBase::factories();
- for (CameraSensorHelperFactory *factory : factories) {
+ for (const CameraSensorHelperFactoryBase *factory : factories) {
if (name != factory->name_)
continue;
- CameraSensorHelper *helper = factory->createInstance();
- return std::unique_ptr<CameraSensorHelper>(helper);
+ return factory->createInstance();
}
return nullptr;
@@ -275,10 +273,10 @@ std::unique_ptr<CameraSensorHelper> CameraSensorHelperFactory::create(const std:
* The caller is responsible to guarantee the uniqueness of the camera sensor
* helper name.
*/
-void CameraSensorHelperFactory::registerType(CameraSensorHelperFactory *factory)
+void CameraSensorHelperFactoryBase::registerType(CameraSensorHelperFactoryBase *factory)
{
- std::vector<CameraSensorHelperFactory *> &factories =
- CameraSensorHelperFactory::factories();
+ std::vector<CameraSensorHelperFactoryBase *> &factories =
+ CameraSensorHelperFactoryBase::factories();
factories.push_back(factory);
}
@@ -287,33 +285,49 @@ void CameraSensorHelperFactory::registerType(CameraSensorHelperFactory *factory)
* \brief Retrieve the list of all camera sensor helper factories
* \return The list of camera sensor helper factories
*/
-std::vector<CameraSensorHelperFactory *> &CameraSensorHelperFactory::factories()
+std::vector<CameraSensorHelperFactoryBase *> &CameraSensorHelperFactoryBase::factories()
{
/*
* The static factories map is defined inside the function to ensure
* it gets initialized on first use, without any dependency on link
* order.
*/
- static std::vector<CameraSensorHelperFactory *> factories;
+ static std::vector<CameraSensorHelperFactoryBase *> factories;
return factories;
}
/**
- * \fn CameraSensorHelperFactory::createInstance()
- * \brief Create an instance of the CameraSensorHelper corresponding to the
- * factory
+ * \class CameraSensorHelperFactory
+ * \brief Registration of CameraSensorHelperFactory classes and creation of instances
+ * \tparam _Helper The camera sensor helper class type for this factory
+ *
+ * To facilitate discovery and instantiation of CameraSensorHelper classes, the
+ * CameraSensorHelperFactory class implements auto-registration of camera sensor
+ * helpers. Each CameraSensorHelper subclass shall register itself using the
+ * REGISTER_CAMERA_SENSOR_HELPER() macro, which will create a corresponding
+ * instance of a CameraSensorHelperFactory subclass and register it with the
+ * static list of factories.
+ */
+
+/**
+ * \fn CameraSensorHelperFactory::CameraSensorHelperFactory(const char *name)
+ * \brief Construct a camera sensor helper factory
+ * \param[in] name Name of the camera sensor helper class
*
- * This virtual function is implemented by the REGISTER_CAMERA_SENSOR_HELPER()
- * macro. It creates a camera sensor helper instance associated with the camera
- * sensor model.
+ * Creating an instance of the factory registers it with the global list of
+ * factories, accessible through the CameraSensorHelperFactoryBase::factories()
+ * function.
*
- * \return A pointer to a newly constructed instance of the CameraSensorHelper
- * subclass corresponding to the factory
+ * The factory \a name is used to look up factories and shall be unique.
*/
/**
- * \var CameraSensorHelperFactory::name_
- * \brief The name of the factory
+ * \fn CameraSensorHelperFactory::createInstance() const
+ * \brief Create an instance of the CameraSensorHelper corresponding to the
+ * factory
+ *
+ * \return A unique pointer to a newly constructed instance of the
+ * CameraSensorHelper subclass corresponding to the factory
*/
/**
@@ -352,6 +366,35 @@ static constexpr double expGainDb(double step)
return log2_10 * step / 20;
}
+class CameraSensorHelperAr0521 : public CameraSensorHelper
+{
+public:
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+
+private:
+ static constexpr double kStep_ = 16;
+};
+
+uint32_t CameraSensorHelperAr0521::gainCode(double gain) const
+{
+ gain = std::clamp(gain, 1.0, 15.5);
+ unsigned int coarse = std::log2(gain);
+ unsigned int fine = (gain / (1 << coarse) - 1) * kStep_;
+
+ return (coarse << 4) | (fine & 0xf);
+}
+
+double CameraSensorHelperAr0521::gain(uint32_t gainCode) const
+{
+ unsigned int coarse = gainCode >> 4;
+ unsigned int fine = gainCode & 0xf;
+
+ return (1 << coarse) * (1 + fine / kStep_);
+}
+
+REGISTER_CAMERA_SENSOR_HELPER("ar0521", CameraSensorHelperAr0521)
+
class CameraSensorHelperImx219 : public CameraSensorHelper
{
public:
@@ -396,6 +439,11 @@ public:
};
REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296)
+class CameraSensorHelperImx327 : public CameraSensorHelperImx290
+{
+};
+REGISTER_CAMERA_SENSOR_HELPER("imx327", CameraSensorHelperImx327)
+
class CameraSensorHelperImx477 : public CameraSensorHelper
{
public:
@@ -407,6 +455,21 @@ public:
};
REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477)
+class CameraSensorHelperOv2685 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperOv2685()
+ {
+ /*
+ * The Sensor Manual doesn't appear to document the gain model.
+ * This has been validated with some empirical testing only.
+ */
+ gainType_ = AnalogueGainLinear;
+ gainConstants_.linear = { 1, 0, 0, 128 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685)
+
class CameraSensorHelperOv2740 : public CameraSensorHelper
{
public:
@@ -418,6 +481,17 @@ public:
};
REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740)
+class CameraSensorHelperOv4689 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperOv4689()
+ {
+ gainType_ = AnalogueGainLinear;
+ gainConstants_.linear = { 1, 0, 0, 128 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689)
+
class CameraSensorHelperOv5640 : public CameraSensorHelper
{
public:
@@ -429,6 +503,17 @@ public:
};
REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640)
+class CameraSensorHelperOv5647 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperOv5647()
+ {
+ gainType_ = AnalogueGainLinear;
+ gainConstants_.linear = { 1, 0, 0, 16 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647)
+
class CameraSensorHelperOv5670 : public CameraSensorHelper
{
public:
@@ -462,6 +547,35 @@ public:
};
REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693)
+class CameraSensorHelperOv64a40 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperOv64a40()
+ {
+ gainType_ = AnalogueGainLinear;
+ gainConstants_.linear = { 1, 0, 0, 128 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40)
+
+class CameraSensorHelperOv8858 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperOv8858()
+ {
+ gainType_ = AnalogueGainLinear;
+
+ /*
+ * \todo Validate the selected 1/128 step value as it differs
+ * from what the sensor manual describes.
+ *
+ * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267
+ */
+ gainConstants_.linear = { 1, 0, 0, 128 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858)
+
class CameraSensorHelperOv8865 : public CameraSensorHelper
{
public:
diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h
index 7351fc7c..1ca9371b 100644
--- a/src/ipa/libipa/camera_sensor_helper.h
+++ b/src/ipa/libipa/camera_sensor_helper.h
@@ -58,39 +58,44 @@ private:
LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelper)
};
-class CameraSensorHelperFactory
+class CameraSensorHelperFactoryBase
{
public:
- CameraSensorHelperFactory(const std::string name);
- virtual ~CameraSensorHelperFactory() = default;
+ CameraSensorHelperFactoryBase(const std::string name);
+ virtual ~CameraSensorHelperFactoryBase() = default;
static std::unique_ptr<CameraSensorHelper> create(const std::string &name);
- static void registerType(CameraSensorHelperFactory *factory);
- static std::vector<CameraSensorHelperFactory *> &factories();
-
-protected:
- virtual CameraSensorHelper *createInstance() = 0;
+ static std::vector<CameraSensorHelperFactoryBase *> &factories();
private:
- LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelperFactory)
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelperFactoryBase)
+
+ static void registerType(CameraSensorHelperFactoryBase *factory);
+
+ virtual std::unique_ptr<CameraSensorHelper> createInstance() const = 0;
std::string name_;
};
-#define REGISTER_CAMERA_SENSOR_HELPER(name, helper) \
-class helper##Factory final : public CameraSensorHelperFactory \
-{ \
-public: \
- helper##Factory() : CameraSensorHelperFactory(name) {} \
- \
-private: \
- CameraSensorHelper *createInstance() \
- { \
- return new helper(); \
- } \
-}; \
-static helper##Factory global_##helper##Factory;
+template<typename _Helper>
+class CameraSensorHelperFactory final : public CameraSensorHelperFactoryBase
+{
+public:
+ CameraSensorHelperFactory(const char *name)
+ : CameraSensorHelperFactoryBase(name)
+ {
+ }
+
+private:
+ std::unique_ptr<CameraSensorHelper> createInstance() const override
+ {
+ return std::make_unique<_Helper>();
+ }
+};
+
+#define REGISTER_CAMERA_SENSOR_HELPER(name, helper) \
+static CameraSensorHelperFactory<helper> global_##helper##Factory(name);
} /* namespace ipa */
diff --git a/src/ipa/libipa/fc_queue.cpp b/src/ipa/libipa/fc_queue.cpp
new file mode 100644
index 00000000..e812faa5
--- /dev/null
+++ b/src/ipa/libipa/fc_queue.cpp
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * fc_queue.cpp - IPA Frame context queue
+ */
+
+#include "fc_queue.h"
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(FCQueue)
+
+namespace ipa {
+
+/**
+ * \file fc_queue.h
+ * \brief Queue of per-frame contexts
+ */
+
+/**
+ * \struct FrameContext
+ * \brief Context for a frame
+ *
+ * The frame context stores data specific to a single frame processed by the
+ * IPA module. Each frame processed by the IPA module has a context associated
+ * with it, accessible through the Frame Context Queue.
+ *
+ * Fields in the frame context should reflect values and controls associated
+ * with the specific frame as requested by the application, and as configured by
+ * the hardware. Fields can be read by algorithms to determine if they should
+ * update any specific action for this frame, and finally to update the metadata
+ * control lists when the frame is fully completed.
+ *
+ * \var FrameContext::frame
+ * \brief The frame number
+ */
+
+/**
+ * \class FCQueue
+ * \brief A support class for managing FrameContext instances in IPA modules
+ * \tparam FrameContext The IPA module-specific FrameContext derived class type
+ *
+ * Along with the Module and Algorithm classes, the frame context queue is a
+ * core component of the libipa infrastructure. It stores per-frame contexts
+ * used by the Algorithm operations. By centralizing the lifetime management of
+ * the contexts and implementing safeguards against underflows and overflows, it
+ * simplifies IPA modules and improves their reliability.
+ *
+ * The queue references frame contexts by a monotonically increasing sequence
+ * number. The FCQueue design assumes that this number matches both the sequence
+ * number of the corresponding frame, as generated by the camera sensor, and the
+ * sequence number of the request. This allows IPA modules to obtain the frame
+ * context from any location where a request or a frame is available.
+ *
+ * A frame context normally begins its lifetime when the corresponding request
+ * is queued, way before the frame is captured by the camera sensor. IPA modules
+ * allocate the context from the queue at that point, calling alloc() using the
+ * request number. The queue initializes the context, and the IPA module then
+ * populates it with data from the request. The context can be later retrieved
+ * with a call to get(), typically when the IPA module is requested to provide
+ * sensor or ISP parameters or receives statistics for a frame. The frame number
+ * is used at that point to identify the context.
+ *
+ * If an application fails to queue requests to the camera fast enough, frames
+ * may be produced by the camera sensor and processed by the IPA module without
+ * a corresponding request having been queued to the IPA module. This creates an
+ * underrun condition, where the IPA module will try to get a frame context that
+ * hasn't been allocated. In this case, the get() function will allocate and
+ * initialize a context for the frame, and log a message. Algorithms will not
+ * apply the controls associated with the late request, but should otherwise
+ * behave correctly.
+ *
+ * \todo Mark the frame context with a per-frame control error flag in case of
+ * underrun, and research how algorithms should handle this.
+ *
+ * At its core, the queue uses a circular buffer to avoid dynamic memory
+ * allocation at runtime. The buffer is pre-allocated with a maximum number of
+ * entries when the FCQueue instance is constructed. Entries are initialized on
+ * first use by alloc() or, in underrun conditions, get(). The queue is not
+ * allowed to overflow, which must be ensured by pipeline handlers never
+ * queuing more in-flight requests to the IPA module than the queue size. If an
+ * overflow condition is detected, the queue will log a fatal error.
+ *
+ * IPA module-specific frame context implementations shall inherit from the
+ * FrameContext base class to support the minimum required features for a
+ * FrameContext.
+ */
+
+/**
+ * \fn FCQueue::FCQueue(unsigned int size)
+ * \brief Construct a frame contexts queue of a specified size
+ * \param[in] size The number of contexts in the queue
+ */
+
+/**
+ * \fn FCQueue::clear()
+ * \brief Clear the contexts queue
+ *
+ * IPA modules must clear the frame context queue at the beginning of a new
+ * streaming session, in IPAModule::start().
+ *
+ * \todo Fix any issue this may cause with requests queued before the camera is
+ * started.
+ */
+
+/**
+ * \fn FCQueue::alloc(uint32_t frame)
+ * \brief Allocate and return a FrameContext for the \a frame
+ * \param[in] frame The frame context sequence number
+ *
+ * The first call to obtain a FrameContext from the FCQueue should be handled
+ * through this function. The FrameContext will be initialised, if not
+ * initialised already, and returned to the caller.
+ *
+ * If the FrameContext was already initialized for this \a frame, a warning will
+ * be reported and the previously initialized FrameContext is returned.
+ *
+ * Frame contexts are expected to be initialised when a Request is first passed
+ * to the IPA module in IPAModule::queueRequest().
+ *
+ * \return A reference to the FrameContext for sequence \a frame
+ */
+
+/**
+ * \fn FCQueue::get(uint32_t frame)
+ * \brief Obtain the FrameContext for the \a frame
+ * \param[in] frame The frame context sequence number
+ *
+ * If the FrameContext is not correctly initialised for the \a frame, it will be
+ * initialised.
+ *
+ * \return A reference to the FrameContext for sequence \a frame
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/fc_queue.h b/src/ipa/libipa/fc_queue.h
new file mode 100644
index 00000000..a589e7e1
--- /dev/null
+++ b/src/ipa/libipa/fc_queue.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * fc_queue.h - IPA Frame context queue
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <vector>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(FCQueue)
+
+namespace ipa {
+
+template<typename FrameContext>
+class FCQueue;
+
+struct FrameContext {
+private:
+ template<typename T> friend class FCQueue;
+ uint32_t frame;
+};
+
+template<typename FrameContext>
+class FCQueue
+{
+public:
+ FCQueue(unsigned int size)
+ : contexts_(size)
+ {
+ }
+
+ void clear()
+ {
+ for (FrameContext &ctx : contexts_)
+ ctx.frame = 0;
+ }
+
+ FrameContext &alloc(const uint32_t frame)
+ {
+ FrameContext &frameContext = contexts_[frame % contexts_.size()];
+
+ /*
+ * Do not re-initialise if a get() call has already fetched this
+ * frame context to preseve the context.
+ *
+ * \todo If the the sequence number of the context to initialise
+ * is smaller than the sequence number of the queue slot to use,
+ * it means that we had a serious request underrun and more
+ * frames than the queue size has been produced since the last
+ * time the application has queued a request. Does this deserve
+ * an error condition ?
+ */
+ if (frame != 0 && frame <= frameContext.frame)
+ LOG(FCQueue, Warning)
+ << "Frame " << frame << " already initialised";
+ else
+ init(frameContext, frame);
+
+ return frameContext;
+ }
+
+ FrameContext &get(uint32_t frame)
+ {
+ FrameContext &frameContext = contexts_[frame % contexts_.size()];
+
+ /*
+ * If the IPA algorithms try to access a frame context slot which
+ * has been already overwritten by a newer context, it means the
+ * frame context queue has overflowed and the desired context
+ * has been forever lost. The pipeline handler shall avoid
+ * queueing more requests to the IPA than the frame context
+ * queue size.
+ */
+ if (frame < frameContext.frame)
+ LOG(FCQueue, Fatal) << "Frame context for " << frame
+ << " has been overwritten by "
+ << frameContext.frame;
+
+ if (frame == frameContext.frame)
+ return frameContext;
+
+ /*
+ * The frame context has been retrieved before it was
+ * initialised through the initialise() call. This indicates an
+ * algorithm attempted to access a Frame context before it was
+ * queued to the IPA. Controls applied for this request may be
+ * left unhandled.
+ *
+ * \todo Set an error flag for per-frame control errors.
+ */
+ LOG(FCQueue, Warning)
+ << "Obtained an uninitialised FrameContext for " << frame;
+
+ init(frameContext, frame);
+
+ return frameContext;
+ }
+
+private:
+ void init(FrameContext &frameContext, const uint32_t frame)
+ {
+ frameContext = {};
+ frameContext.frame = frame;
+ }
+
+ std::vector<FrameContext> contexts_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/histogram.cpp b/src/ipa/libipa/histogram.cpp
index d8ad1c89..6b5cde8e 100644
--- a/src/ipa/libipa/histogram.cpp
+++ b/src/ipa/libipa/histogram.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
* histogram.cpp - histogram calculations
*/
@@ -53,7 +53,7 @@ Histogram::Histogram(Span<const uint32_t> data)
*/
/**
- * \brief Cumulative frequency up to a (fractional) point in a bin.
+ * \brief Cumulative frequency up to a (fractional) point in a bin
* \param[in] bin The bin up to which to cumulate
*
* With F(p) the cumulative frequency of the histogram, the value is 0 at
diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h
index 164d4603..05bb4b80 100644
--- a/src/ipa/libipa/histogram.h
+++ b/src/ipa/libipa/histogram.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
* histogram.h - histogram calculation interface
*/
diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
index fb894bc6..016b8e0e 100644
--- a/src/ipa/libipa/meson.build
+++ b/src/ipa/libipa/meson.build
@@ -3,6 +3,7 @@
libipa_headers = files([
'algorithm.h',
'camera_sensor_helper.h',
+ 'fc_queue.h',
'histogram.h',
'module.h',
])
@@ -10,6 +11,7 @@ libipa_headers = files([
libipa_sources = files([
'algorithm.cpp',
'camera_sensor_helper.cpp',
+ 'fc_queue.cpp',
'histogram.cpp',
'module.cpp',
])
diff --git a/src/ipa/libipa/module.cpp b/src/ipa/libipa/module.cpp
index 77352104..ee01f12a 100644
--- a/src/ipa/libipa/module.cpp
+++ b/src/ipa/libipa/module.cpp
@@ -17,7 +17,7 @@ namespace libcamera {
LOG_DEFINE_CATEGORY(IPAModuleAlgo)
/**
- * \brief The IPA namespace
+ * \brief The IPA (Image Processing Algorithm) namespace
*
* The IPA namespace groups all types specific to IPA modules. It serves as the
* top-level namespace for the IPA library libipa, and also contains
diff --git a/src/ipa/meson.build b/src/ipa/meson.build
index e15a8a06..0ad4631d 100644
--- a/src/ipa/meson.build
+++ b/src/ipa/meson.build
@@ -28,14 +28,45 @@ ipa_names = []
ipa_modules = get_option('ipas')
-# The ipa-sign-install.sh script which uses the ipa_names variable will itself
-# prepend MESON_INSTALL_DESTDIR_PREFIX to each ipa module name, therefore we
-# must not include the prefix string here.
+# Tests require the vimc IPA, similar to vimc pipline-handler for their
+# execution. Include it automatically when tests are enabled.
+if get_option('test') and 'vimc' not in ipa_modules
+ message('Enabling vimc IPA to support tests')
+ ipa_modules += ['vimc']
+endif
+
+enabled_ipa_modules = []
+enabled_ipa_names = []
+ipa_names = []
+
+subdirs = []
foreach pipeline : pipelines
- if ipa_modules.contains(pipeline)
- subdir(pipeline)
- ipa_names += ipa_install_dir / ipa_name + '.so'
+ # The current implementation expects the IPA module name to match the
+ # pipeline name.
+ # \todo Make the IPA naming scheme more flexible.
+ if not ipa_modules.contains(pipeline)
+ continue
+ endif
+ enabled_ipa_names += pipeline
+
+ # Allow multi-level directory structuring for the IPAs if needed.
+ pipeline = pipeline.split('/')[0]
+ if pipeline in subdirs
+ continue
endif
+
+ subdirs += pipeline
+ subdir(pipeline)
+
+ # Don't reuse the pipeline variable below, the subdirectory may have
+ # overwritten it.
+endforeach
+
+# The ipa-sign-install.sh script which uses the enabled_ipa_modules variable
+# will itself prepend MESON_INSTALL_DESTDIR_PREFIX to each ipa module name,
+# therefore we must not include the prefix string here.
+foreach ipa_name : ipa_names
+ enabled_ipa_modules += ipa_install_dir / ipa_name + '.so'
endforeach
if ipa_sign_module
@@ -44,5 +75,6 @@ if ipa_sign_module
# install time, which invalidates the signatures.
meson.add_install_script('ipa-sign-install.sh',
ipa_priv_key.full_path(),
- ipa_names)
+ enabled_ipa_modules,
+ install_tag : 'runtime')
endif
diff --git a/src/ipa/raspberrypi/cam_helper.cpp b/src/ipa/raspberrypi/cam_helper.cpp
deleted file mode 100644
index 3f81d418..00000000
--- a/src/ipa/raspberrypi/cam_helper.cpp
+++ /dev/null
@@ -1,219 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * cam_helper.cpp - helper information for different sensors
- */
-
-#include <linux/videodev2.h>
-
-#include <assert.h>
-#include <map>
-#include <string.h>
-
-#include "libcamera/internal/v4l2_videodevice.h"
-
-#include "cam_helper.hpp"
-#include "md_parser.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-using libcamera::utils::Duration;
-
-namespace libcamera {
-LOG_DECLARE_CATEGORY(IPARPI)
-}
-
-static std::map<std::string, CamHelperCreateFunc> cam_helpers;
-
-CamHelper *CamHelper::Create(std::string const &cam_name)
-{
- /*
- * CamHelpers get registered by static RegisterCamHelper
- * initialisers.
- */
- for (auto &p : cam_helpers) {
- if (cam_name.find(p.first) != std::string::npos)
- return p.second();
- }
-
- return nullptr;
-}
-
-CamHelper::CamHelper(std::unique_ptr<MdParser> parser, unsigned int frameIntegrationDiff)
- : parser_(std::move(parser)), initialized_(false),
- frameIntegrationDiff_(frameIntegrationDiff)
-{
-}
-
-CamHelper::~CamHelper()
-{
-}
-
-void CamHelper::Prepare(Span<const uint8_t> buffer,
- Metadata &metadata)
-{
- parseEmbeddedData(buffer, metadata);
-}
-
-void CamHelper::Process([[maybe_unused]] StatisticsPtr &stats,
- [[maybe_unused]] Metadata &metadata)
-{
-}
-
-uint32_t CamHelper::ExposureLines(const Duration exposure) const
-{
- assert(initialized_);
- return exposure / mode_.line_length;
-}
-
-Duration CamHelper::Exposure(uint32_t exposure_lines) const
-{
- assert(initialized_);
- return exposure_lines * mode_.line_length;
-}
-
-uint32_t CamHelper::GetVBlanking(Duration &exposure,
- Duration minFrameDuration,
- Duration maxFrameDuration) const
-{
- uint32_t frameLengthMin, frameLengthMax, vblank;
- uint32_t exposureLines = ExposureLines(exposure);
-
- assert(initialized_);
-
- /*
- * minFrameDuration and maxFrameDuration are clamped by the caller
- * based on the limits for the active sensor mode.
- */
- frameLengthMin = minFrameDuration / mode_.line_length;
- frameLengthMax = maxFrameDuration / mode_.line_length;
-
- /*
- * Limit the exposure to the maximum frame duration requested, and
- * re-calculate if it has been clipped.
- */
- exposureLines = std::min(frameLengthMax - frameIntegrationDiff_, exposureLines);
- exposure = Exposure(exposureLines);
-
- /* Limit the vblank to the range allowed by the frame length limits. */
- vblank = std::clamp(exposureLines + frameIntegrationDiff_,
- frameLengthMin, frameLengthMax) - mode_.height;
- return vblank;
-}
-
-void CamHelper::SetCameraMode(const CameraMode &mode)
-{
- mode_ = mode;
- if (parser_) {
- parser_->SetBitsPerPixel(mode.bitdepth);
- parser_->SetLineLengthBytes(0); /* We use SetBufferSize. */
- }
- initialized_ = true;
-}
-
-void CamHelper::GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const
-{
- /*
- * These values are correct for many sensors. Other sensors will
- * need to over-ride this function.
- */
- exposure_delay = 2;
- gain_delay = 1;
- vblank_delay = 2;
-}
-
-bool CamHelper::SensorEmbeddedDataPresent() const
-{
- return false;
-}
-
-double CamHelper::GetModeSensitivity([[maybe_unused]] const CameraMode &mode) const
-{
- /*
- * Most sensors have the same sensitivity in every mode, but this
- * function can be overridden for those that do not. Note that it is
- * called before mode_ is set, so it must return the sensitivity
- * of the mode that is passed in.
- */
- return 1.0;
-}
-
-unsigned int CamHelper::HideFramesStartup() const
-{
- /*
- * The number of frames when a camera first starts that shouldn't be
- * displayed as they are invalid in some way.
- */
- return 0;
-}
-
-unsigned int CamHelper::HideFramesModeSwitch() const
-{
- /* After a mode switch, many sensors return valid frames immediately. */
- return 0;
-}
-
-unsigned int CamHelper::MistrustFramesStartup() const
-{
- /* Many sensors return a single bad frame on start-up. */
- return 1;
-}
-
-unsigned int CamHelper::MistrustFramesModeSwitch() const
-{
- /* Many sensors return valid metadata immediately. */
- return 0;
-}
-
-void CamHelper::parseEmbeddedData(Span<const uint8_t> buffer,
- Metadata &metadata)
-{
- MdParser::RegisterMap registers;
- Metadata parsedMetadata;
-
- if (buffer.empty())
- return;
-
- if (parser_->Parse(buffer, registers) != MdParser::Status::OK) {
- LOG(IPARPI, Error) << "Embedded data buffer parsing failed";
- return;
- }
-
- PopulateMetadata(registers, parsedMetadata);
- metadata.Merge(parsedMetadata);
-
- /*
- * Overwrite the exposure/gain, frame length and sensor temperature values
- * in the existing DeviceStatus with values from the parsed embedded buffer.
- * Fetch it first in case any other fields were set meaningfully.
- */
- DeviceStatus deviceStatus, parsedDeviceStatus;
- if (metadata.Get("device.status", deviceStatus) ||
- parsedMetadata.Get("device.status", parsedDeviceStatus)) {
- LOG(IPARPI, Error) << "DeviceStatus not found";
- return;
- }
-
- deviceStatus.shutter_speed = parsedDeviceStatus.shutter_speed;
- deviceStatus.analogue_gain = parsedDeviceStatus.analogue_gain;
- deviceStatus.frame_length = parsedDeviceStatus.frame_length;
- if (parsedDeviceStatus.sensor_temperature)
- deviceStatus.sensor_temperature = parsedDeviceStatus.sensor_temperature;
-
- LOG(IPARPI, Debug) << "Metadata updated - " << deviceStatus;
-
- metadata.Set("device.status", deviceStatus);
-}
-
-void CamHelper::PopulateMetadata([[maybe_unused]] const MdParser::RegisterMap &registers,
- [[maybe_unused]] Metadata &metadata) const
-{
-}
-
-RegisterCamHelper::RegisterCamHelper(char const *cam_name,
- CamHelperCreateFunc create_func)
-{
- cam_helpers[std::string(cam_name)] = create_func;
-}
diff --git a/src/ipa/raspberrypi/cam_helper.hpp b/src/ipa/raspberrypi/cam_helper.hpp
deleted file mode 100644
index 300f8f8a..00000000
--- a/src/ipa/raspberrypi/cam_helper.hpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * cam_helper.hpp - helper class providing camera information
- */
-#pragma once
-
-#include <memory>
-#include <string>
-
-#include <libcamera/base/span.h>
-#include <libcamera/base/utils.h>
-
-#include "camera_mode.h"
-#include "controller/controller.hpp"
-#include "controller/metadata.hpp"
-#include "md_parser.hpp"
-
-#include "libcamera/internal/v4l2_videodevice.h"
-
-namespace RPiController {
-
-// The CamHelper class provides a number of facilities that anyone trying
-// to drive a camera will need to know, but which are not provided by the
-// standard driver framework. Specifically, it provides:
-//
-// A "CameraMode" structure to describe extra information about the chosen
-// mode of the driver. For example, how it is cropped from the full sensor
-// area, how it is scaled, whether pixels are averaged compared to the full
-// resolution.
-//
-// The ability to convert between number of lines of exposure and actual
-// exposure time, and to convert between the sensor's gain codes and actual
-// gains.
-//
-// A function to return the number of frames of delay between updating exposure,
-// analogue gain and vblanking, and for the changes to take effect. For many
-// sensors these take the values 2, 1 and 2 respectively, but sensors that are
-// different will need to over-ride the default function provided.
-//
-// A function to query if the sensor outputs embedded data that can be parsed.
-//
-// A function to return the sensitivity of a given camera mode.
-//
-// A parser to parse the embedded data buffers provided by some sensors (for
-// example, the imx219 does; the ov5647 doesn't). This allows us to know for
-// sure the exposure and gain of the frame we're looking at. CamHelper
-// provides functions for converting analogue gains to and from the sensor's
-// native gain codes.
-//
-// Finally, a set of functions that determine how to handle the vagaries of
-// different camera modules on start-up or when switching modes. Some
-// modules may produce one or more frames that are not yet correctly exposed,
-// or where the metadata may be suspect. We have the following functions:
-// HideFramesStartup(): Tell the pipeline handler not to return this many
-// frames at start-up. This can also be used to hide initial frames
-// while the AGC and other algorithms are sorting themselves out.
-// HideFramesModeSwitch(): Tell the pipeline handler not to return this
-// many frames after a mode switch (other than start-up). Some sensors
-// may produce innvalid frames after a mode switch; others may not.
-// MistrustFramesStartup(): At start-up a sensor may return frames for
-// which we should not run any control algorithms (for example, metadata
-// may be invalid).
-// MistrustFramesModeSwitch(): The number of frames, after a mode switch
-// (other than start-up), for which control algorithms should not run
-// (for example, metadata may be unreliable).
-
-class CamHelper
-{
-public:
- static CamHelper *Create(std::string const &cam_name);
- CamHelper(std::unique_ptr<MdParser> parser, unsigned int frameIntegrationDiff);
- virtual ~CamHelper();
- void SetCameraMode(const CameraMode &mode);
- virtual void Prepare(libcamera::Span<const uint8_t> buffer,
- Metadata &metadata);
- virtual void Process(StatisticsPtr &stats, Metadata &metadata);
- virtual uint32_t ExposureLines(libcamera::utils::Duration exposure) const;
- virtual libcamera::utils::Duration Exposure(uint32_t exposure_lines) const;
- virtual uint32_t GetVBlanking(libcamera::utils::Duration &exposure,
- libcamera::utils::Duration minFrameDuration,
- libcamera::utils::Duration maxFrameDuration) const;
- virtual uint32_t GainCode(double gain) const = 0;
- virtual double Gain(uint32_t gain_code) const = 0;
- virtual void GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const;
- virtual bool SensorEmbeddedDataPresent() const;
- virtual double GetModeSensitivity(const CameraMode &mode) const;
- virtual unsigned int HideFramesStartup() const;
- virtual unsigned int HideFramesModeSwitch() const;
- virtual unsigned int MistrustFramesStartup() const;
- virtual unsigned int MistrustFramesModeSwitch() const;
-
-protected:
- void parseEmbeddedData(libcamera::Span<const uint8_t> buffer,
- Metadata &metadata);
- virtual void PopulateMetadata(const MdParser::RegisterMap &registers,
- Metadata &metadata) const;
-
- std::unique_ptr<MdParser> parser_;
- CameraMode mode_;
-
-private:
- bool initialized_;
- /*
- * Smallest difference between the frame length and integration time,
- * in units of lines.
- */
- unsigned int frameIntegrationDiff_;
-};
-
-// This is for registering camera helpers with the system, so that the
-// CamHelper::Create function picks them up automatically.
-
-typedef CamHelper *(*CamHelperCreateFunc)();
-struct RegisterCamHelper
-{
- RegisterCamHelper(char const *cam_name,
- CamHelperCreateFunc create_func);
-};
-
-} // namespace RPi
diff --git a/src/ipa/raspberrypi/cam_helper_imx290.cpp b/src/ipa/raspberrypi/cam_helper_imx290.cpp
deleted file mode 100644
index 871c1f8e..00000000
--- a/src/ipa/raspberrypi/cam_helper_imx290.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2021, Raspberry Pi (Trading) Limited
- *
- * cam_helper_imx290.cpp - camera helper for imx290 sensor
- */
-
-#include <math.h>
-
-#include "cam_helper.hpp"
-
-using namespace RPiController;
-
-class CamHelperImx290 : public CamHelper
-{
-public:
- CamHelperImx290();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- void GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const override;
- unsigned int HideFramesModeSwitch() const override;
-
-private:
- /*
- * Smallest difference between the frame length and integration time,
- * in units of lines.
- */
- static constexpr int frameIntegrationDiff = 2;
-};
-
-CamHelperImx290::CamHelperImx290()
- : CamHelper({}, frameIntegrationDiff)
-{
-}
-
-uint32_t CamHelperImx290::GainCode(double gain) const
-{
- int code = 66.6667 * log10(gain);
- return std::max(0, std::min(code, 0xf0));
-}
-
-double CamHelperImx290::Gain(uint32_t gain_code) const
-{
- return pow(10, 0.015 * gain_code);
-}
-
-void CamHelperImx290::GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const
-{
- exposure_delay = 2;
- gain_delay = 2;
- vblank_delay = 2;
-}
-
-unsigned int CamHelperImx290::HideFramesModeSwitch() const
-{
- /* After a mode switch, we seem to get 1 bad frame. */
- return 1;
-}
-
-static CamHelper *Create()
-{
- return new CamHelperImx290();
-}
-
-static RegisterCamHelper reg("imx290", &Create);
diff --git a/src/ipa/raspberrypi/cam_helper_imx296.cpp b/src/ipa/raspberrypi/cam_helper_imx296.cpp
deleted file mode 100644
index a1a771cb..00000000
--- a/src/ipa/raspberrypi/cam_helper_imx296.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
- *
- * cam_helper_imx296.cpp - Camera helper for IMX296 sensor
- */
-
-#include <algorithm>
-#include <cmath>
-#include <stddef.h>
-
-#include "cam_helper.hpp"
-
-using namespace RPiController;
-using libcamera::utils::Duration;
-using namespace std::literals::chrono_literals;
-
-class CamHelperImx296 : public CamHelper
-{
-public:
- CamHelperImx296();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- uint32_t ExposureLines(Duration exposure) const override;
- Duration Exposure(uint32_t exposure_lines) const override;
-
-private:
- static constexpr uint32_t maxGainCode = 239;
- static constexpr Duration timePerLine = 550.0 / 37.125e6 * 1.0s;
-
- /*
- * Smallest difference between the frame length and integration time,
- * in units of lines.
- */
- static constexpr int frameIntegrationDiff = 4;
-};
-
-CamHelperImx296::CamHelperImx296()
- : CamHelper(nullptr, frameIntegrationDiff)
-{
-}
-
-uint32_t CamHelperImx296::GainCode(double gain) const
-{
- uint32_t code = 20 * std::log10(gain) * 10;
- return std::min(code, maxGainCode);
-}
-
-double CamHelperImx296::Gain(uint32_t gain_code) const
-{
- return std::pow(10.0, gain_code / 200.0);
-}
-
-uint32_t CamHelperImx296::ExposureLines(Duration exposure) const
-{
- return (exposure - 14.26us) / timePerLine;
-}
-
-Duration CamHelperImx296::Exposure(uint32_t exposure_lines) const
-{
- return exposure_lines * timePerLine + 14.26us;
-}
-
-static CamHelper *Create()
-{
- return new CamHelperImx296();
-}
-
-static RegisterCamHelper reg("imx296", &Create);
diff --git a/src/ipa/raspberrypi/controller/agc_algorithm.hpp b/src/ipa/raspberrypi/controller/agc_algorithm.hpp
deleted file mode 100644
index 61595ea2..00000000
--- a/src/ipa/raspberrypi/controller/agc_algorithm.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * agc_algorithm.hpp - AGC/AEC control algorithm interface
- */
-#pragma once
-
-#include <libcamera/base/utils.h>
-
-#include "algorithm.hpp"
-
-namespace RPiController {
-
-class AgcAlgorithm : public Algorithm
-{
-public:
- AgcAlgorithm(Controller *controller) : Algorithm(controller) {}
- // An AGC algorithm must provide the following:
- virtual unsigned int GetConvergenceFrames() const = 0;
- virtual void SetEv(double ev) = 0;
- virtual void SetFlickerPeriod(libcamera::utils::Duration flicker_period) = 0;
- virtual void SetFixedShutter(libcamera::utils::Duration fixed_shutter) = 0;
- virtual void SetMaxShutter(libcamera::utils::Duration max_shutter) = 0;
- virtual void SetFixedAnalogueGain(double fixed_analogue_gain) = 0;
- virtual void SetMeteringMode(std::string const &metering_mode_name) = 0;
- virtual void SetExposureMode(std::string const &exposure_mode_name) = 0;
- virtual void
- SetConstraintMode(std::string const &contraint_mode_name) = 0;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/agc_status.h b/src/ipa/raspberrypi/controller/agc_status.h
deleted file mode 100644
index 20cb1b62..00000000
--- a/src/ipa/raspberrypi/controller/agc_status.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * agc_status.h - AGC/AEC control algorithm status
- */
-#pragma once
-
-#include <libcamera/base/utils.h>
-
-// The AGC algorithm should post the following structure into the image's
-// "agc.status" metadata.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Note: total_exposure_value will be reported as zero until the algorithm has
-// seen statistics and calculated meaningful values. The contents should be
-// ignored until then.
-
-struct AgcStatus {
- libcamera::utils::Duration total_exposure_value; // value for all exposure and gain for this image
- libcamera::utils::Duration target_exposure_value; // (unfiltered) target total exposure AGC is aiming for
- libcamera::utils::Duration shutter_time;
- double analogue_gain;
- char exposure_mode[32];
- char constraint_mode[32];
- char metering_mode[32];
- double ev;
- libcamera::utils::Duration flicker_period;
- int floating_region_enable;
- libcamera::utils::Duration fixed_shutter;
- double fixed_analogue_gain;
- double digital_gain;
- int locked;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/algorithm.cpp b/src/ipa/raspberrypi/controller/algorithm.cpp
deleted file mode 100644
index 43ad0a2b..00000000
--- a/src/ipa/raspberrypi/controller/algorithm.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * algorithm.cpp - ISP control algorithms
- */
-
-#include "algorithm.hpp"
-
-using namespace RPiController;
-
-void Algorithm::Read([[maybe_unused]] boost::property_tree::ptree const &params)
-{
-}
-
-void Algorithm::Initialise() {}
-
-void Algorithm::SwitchMode([[maybe_unused]] CameraMode const &camera_mode,
- [[maybe_unused]] Metadata *metadata)
-{
-}
-
-void Algorithm::Prepare([[maybe_unused]] Metadata *image_metadata)
-{
-}
-
-void Algorithm::Process([[maybe_unused]] StatisticsPtr &stats,
- [[maybe_unused]] Metadata *image_metadata)
-{
-}
-
-// For registering algorithms with the system:
-
-static std::map<std::string, AlgoCreateFunc> algorithms;
-std::map<std::string, AlgoCreateFunc> const &RPiController::GetAlgorithms()
-{
- return algorithms;
-}
-
-RegisterAlgorithm::RegisterAlgorithm(char const *name,
- AlgoCreateFunc create_func)
-{
- algorithms[std::string(name)] = create_func;
-}
diff --git a/src/ipa/raspberrypi/controller/algorithm.hpp b/src/ipa/raspberrypi/controller/algorithm.hpp
deleted file mode 100644
index 5123c87b..00000000
--- a/src/ipa/raspberrypi/controller/algorithm.hpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * algorithm.hpp - ISP control algorithm interface
- */
-#pragma once
-
-// All algorithms should be derived from this class and made available to the
-// Controller.
-
-#include <string>
-#include <memory>
-#include <map>
-
-#include "controller.hpp"
-
-#include <boost/property_tree/ptree.hpp>
-
-namespace RPiController {
-
-// This defines the basic interface for all control algorithms.
-
-class Algorithm
-{
-public:
- Algorithm(Controller *controller)
- : controller_(controller), paused_(false)
- {
- }
- virtual ~Algorithm() = default;
- virtual char const *Name() const = 0;
- virtual bool IsPaused() const { return paused_; }
- virtual void Pause() { paused_ = true; }
- virtual void Resume() { paused_ = false; }
- virtual void Read(boost::property_tree::ptree const &params);
- virtual void Initialise();
- virtual void SwitchMode(CameraMode const &camera_mode, Metadata *metadata);
- virtual void Prepare(Metadata *image_metadata);
- virtual void Process(StatisticsPtr &stats, Metadata *image_metadata);
- Metadata &GetGlobalMetadata() const
- {
- return controller_->GetGlobalMetadata();
- }
-
-private:
- Controller *controller_;
- bool paused_;
-};
-
-// This code is for automatic registration of Front End algorithms with the
-// system.
-
-typedef Algorithm *(*AlgoCreateFunc)(Controller *controller);
-struct RegisterAlgorithm {
- RegisterAlgorithm(char const *name, AlgoCreateFunc create_func);
-};
-std::map<std::string, AlgoCreateFunc> const &GetAlgorithms();
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/alsc_status.h b/src/ipa/raspberrypi/controller/alsc_status.h
deleted file mode 100644
index d3f57971..00000000
--- a/src/ipa/raspberrypi/controller/alsc_status.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * alsc_status.h - ALSC (auto lens shading correction) control algorithm status
- */
-#pragma once
-
-// The ALSC algorithm should post the following structure into the image's
-// "alsc.status" metadata.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define ALSC_CELLS_X 16
-#define ALSC_CELLS_Y 12
-
-struct AlscStatus {
- double r[ALSC_CELLS_Y][ALSC_CELLS_X];
- double g[ALSC_CELLS_Y][ALSC_CELLS_X];
- double b[ALSC_CELLS_Y][ALSC_CELLS_X];
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/awb_algorithm.hpp b/src/ipa/raspberrypi/controller/awb_algorithm.hpp
deleted file mode 100644
index 96f88afc..00000000
--- a/src/ipa/raspberrypi/controller/awb_algorithm.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * awb_algorithm.hpp - AWB control algorithm interface
- */
-#pragma once
-
-#include "algorithm.hpp"
-
-namespace RPiController {
-
-class AwbAlgorithm : public Algorithm
-{
-public:
- AwbAlgorithm(Controller *controller) : Algorithm(controller) {}
- // An AWB algorithm must provide the following:
- virtual unsigned int GetConvergenceFrames() const = 0;
- virtual void SetMode(std::string const &mode_name) = 0;
- virtual void SetManualGains(double manual_r, double manual_b) = 0;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/awb_status.h b/src/ipa/raspberrypi/controller/awb_status.h
deleted file mode 100644
index 46d7c842..00000000
--- a/src/ipa/raspberrypi/controller/awb_status.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * awb_status.h - AWB control algorithm status
- */
-#pragma once
-
-// The AWB algorithm places its results into both the image and global metadata,
-// under the tag "awb.status".
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct AwbStatus {
- char mode[32];
- double temperature_K;
- double gain_r;
- double gain_g;
- double gain_b;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/black_level_status.h b/src/ipa/raspberrypi/controller/black_level_status.h
deleted file mode 100644
index d085f64b..00000000
--- a/src/ipa/raspberrypi/controller/black_level_status.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * black_level_status.h - black level control algorithm status
- */
-#pragma once
-
-// The "black level" algorithm stores the black levels to use.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct BlackLevelStatus {
- uint16_t black_level_r; // out of 16 bits
- uint16_t black_level_g;
- uint16_t black_level_b;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/camera_mode.h b/src/ipa/raspberrypi/controller/camera_mode.h
deleted file mode 100644
index e2b82828..00000000
--- a/src/ipa/raspberrypi/controller/camera_mode.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2020, Raspberry Pi (Trading) Limited
- *
- * camera_mode.h - description of a particular operating mode of a sensor
- */
-#pragma once
-
-#include <libcamera/transform.h>
-
-#include <libcamera/base/utils.h>
-
-// Description of a "camera mode", holding enough information for control
-// algorithms to adapt their behaviour to the different modes of the camera,
-// including binning, scaling, cropping etc.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define CAMERA_MODE_NAME_LEN 32
-
-struct CameraMode {
- // bit depth of the raw camera output
- uint32_t bitdepth;
- // size in pixels of frames in this mode
- uint16_t width, height;
- // size of full resolution uncropped frame ("sensor frame")
- uint16_t sensor_width, sensor_height;
- // binning factor (1 = no binning, 2 = 2-pixel binning etc.)
- uint8_t bin_x, bin_y;
- // location of top left pixel in the sensor frame
- uint16_t crop_x, crop_y;
- // scaling factor (so if uncropped, width*scale_x is sensor_width)
- double scale_x, scale_y;
- // scaling of the noise compared to the native sensor mode
- double noise_factor;
- // line time
- libcamera::utils::Duration line_length;
- // any camera transform *not* reflected already in the camera tuning
- libcamera::Transform transform;
- // minimum and maximum fame lengths in units of lines
- uint32_t min_frame_length, max_frame_length;
- // sensitivity of this mode
- double sensitivity;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/ccm_algorithm.hpp b/src/ipa/raspberrypi/controller/ccm_algorithm.hpp
deleted file mode 100644
index 33d0e30d..00000000
--- a/src/ipa/raspberrypi/controller/ccm_algorithm.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * ccm_algorithm.hpp - CCM (colour correction matrix) control algorithm interface
- */
-#pragma once
-
-#include "algorithm.hpp"
-
-namespace RPiController {
-
-class CcmAlgorithm : public Algorithm
-{
-public:
- CcmAlgorithm(Controller *controller) : Algorithm(controller) {}
- // A CCM algorithm must provide the following:
- virtual void SetSaturation(double saturation) = 0;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/contrast_algorithm.hpp b/src/ipa/raspberrypi/controller/contrast_algorithm.hpp
deleted file mode 100644
index 7f03bba5..00000000
--- a/src/ipa/raspberrypi/controller/contrast_algorithm.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * contrast_algorithm.hpp - contrast (gamma) control algorithm interface
- */
-#pragma once
-
-#include "algorithm.hpp"
-
-namespace RPiController {
-
-class ContrastAlgorithm : public Algorithm
-{
-public:
- ContrastAlgorithm(Controller *controller) : Algorithm(controller) {}
- // A contrast algorithm must provide the following:
- virtual void SetBrightness(double brightness) = 0;
- virtual void SetContrast(double contrast) = 0;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/contrast_status.h b/src/ipa/raspberrypi/controller/contrast_status.h
deleted file mode 100644
index d7edd4e9..00000000
--- a/src/ipa/raspberrypi/controller/contrast_status.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * contrast_status.h - contrast (gamma) control algorithm status
- */
-#pragma once
-
-// The "contrast" algorithm creates a gamma curve, optionally doing a little bit
-// of contrast stretching based on the AGC histogram.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define CONTRAST_NUM_POINTS 33
-
-struct ContrastPoint {
- uint16_t x;
- uint16_t y;
-};
-
-struct ContrastStatus {
- struct ContrastPoint points[CONTRAST_NUM_POINTS];
- double brightness;
- double contrast;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/controller.cpp b/src/ipa/raspberrypi/controller/controller.cpp
deleted file mode 100644
index d3433ad2..00000000
--- a/src/ipa/raspberrypi/controller/controller.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * controller.cpp - ISP controller
- */
-
-#include <libcamera/base/log.h>
-
-#include "algorithm.hpp"
-#include "controller.hpp"
-
-#include <boost/property_tree/json_parser.hpp>
-#include <boost/property_tree/ptree.hpp>
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiController)
-
-Controller::Controller()
- : switch_mode_called_(false) {}
-
-Controller::Controller(char const *json_filename)
- : switch_mode_called_(false)
-{
- Read(json_filename);
- Initialise();
-}
-
-Controller::~Controller() {}
-
-void Controller::Read(char const *filename)
-{
- boost::property_tree::ptree root;
- boost::property_tree::read_json(filename, root);
- for (auto const &key_and_value : root) {
- Algorithm *algo = CreateAlgorithm(key_and_value.first.c_str());
- if (algo) {
- algo->Read(key_and_value.second);
- algorithms_.push_back(AlgorithmPtr(algo));
- } else
- LOG(RPiController, Warning)
- << "No algorithm found for \"" << key_and_value.first << "\"";
- }
-}
-
-Algorithm *Controller::CreateAlgorithm(char const *name)
-{
- auto it = GetAlgorithms().find(std::string(name));
- return it != GetAlgorithms().end() ? (*it->second)(this) : nullptr;
-}
-
-void Controller::Initialise()
-{
- for (auto &algo : algorithms_)
- algo->Initialise();
-}
-
-void Controller::SwitchMode(CameraMode const &camera_mode, Metadata *metadata)
-{
- for (auto &algo : algorithms_)
- algo->SwitchMode(camera_mode, metadata);
- switch_mode_called_ = true;
-}
-
-void Controller::Prepare(Metadata *image_metadata)
-{
- assert(switch_mode_called_);
- for (auto &algo : algorithms_)
- if (!algo->IsPaused())
- algo->Prepare(image_metadata);
-}
-
-void Controller::Process(StatisticsPtr stats, Metadata *image_metadata)
-{
- assert(switch_mode_called_);
- for (auto &algo : algorithms_)
- if (!algo->IsPaused())
- algo->Process(stats, image_metadata);
-}
-
-Metadata &Controller::GetGlobalMetadata()
-{
- return global_metadata_;
-}
-
-Algorithm *Controller::GetAlgorithm(std::string const &name) const
-{
- // The passed name must be the entire algorithm name, or must match the
- // last part of it with a period (.) just before.
- size_t name_len = name.length();
- for (auto &algo : algorithms_) {
- char const *algo_name = algo->Name();
- size_t algo_name_len = strlen(algo_name);
- if (algo_name_len >= name_len &&
- strcasecmp(name.c_str(),
- algo_name + algo_name_len - name_len) == 0 &&
- (name_len == algo_name_len ||
- algo_name[algo_name_len - name_len - 1] == '.'))
- return algo.get();
- }
- return nullptr;
-}
diff --git a/src/ipa/raspberrypi/controller/controller.hpp b/src/ipa/raspberrypi/controller/controller.hpp
deleted file mode 100644
index 3b50ae77..00000000
--- a/src/ipa/raspberrypi/controller/controller.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * controller.hpp - ISP controller interface
- */
-#pragma once
-
-// The Controller is simply a container for a collecting together a number of
-// "control algorithms" (such as AWB etc.) and for running them all in a
-// convenient manner.
-
-#include <vector>
-#include <string>
-
-#include <linux/bcm2835-isp.h>
-
-#include "camera_mode.h"
-#include "device_status.h"
-#include "metadata.hpp"
-
-namespace RPiController {
-
-class Algorithm;
-typedef std::unique_ptr<Algorithm> AlgorithmPtr;
-typedef std::shared_ptr<bcm2835_isp_stats> StatisticsPtr;
-
-// The Controller holds a pointer to some global_metadata, which is how
-// different controllers and control algorithms within them can exchange
-// information. The Prepare function returns a pointer to metadata for this
-// specific image, and which should be passed on to the Process function.
-
-class Controller
-{
-public:
- Controller();
- Controller(char const *json_filename);
- ~Controller();
- Algorithm *CreateAlgorithm(char const *name);
- void Read(char const *filename);
- void Initialise();
- void SwitchMode(CameraMode const &camera_mode, Metadata *metadata);
- void Prepare(Metadata *image_metadata);
- void Process(StatisticsPtr stats, Metadata *image_metadata);
- Metadata &GetGlobalMetadata();
- Algorithm *GetAlgorithm(std::string const &name) const;
-
-protected:
- Metadata global_metadata_;
- std::vector<AlgorithmPtr> algorithms_;
- bool switch_mode_called_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/denoise_algorithm.hpp b/src/ipa/raspberrypi/controller/denoise_algorithm.hpp
deleted file mode 100644
index 39fcd7e9..00000000
--- a/src/ipa/raspberrypi/controller/denoise_algorithm.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2021, Raspberry Pi (Trading) Limited
- *
- * denoise.hpp - Denoise control algorithm interface
- */
-#pragma once
-
-#include "algorithm.hpp"
-
-namespace RPiController {
-
-enum class DenoiseMode { Off, ColourOff, ColourFast, ColourHighQuality };
-
-class DenoiseAlgorithm : public Algorithm
-{
-public:
- DenoiseAlgorithm(Controller *controller) : Algorithm(controller) {}
- // A Denoise algorithm must provide the following:
- virtual void SetMode(DenoiseMode mode) = 0;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/denoise_status.h b/src/ipa/raspberrypi/controller/denoise_status.h
deleted file mode 100644
index 67a3c361..00000000
--- a/src/ipa/raspberrypi/controller/denoise_status.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Limited
- *
- * denoise_status.h - Denoise control algorithm status
- */
-#pragma once
-
-// This stores the parameters required for Denoise.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct DenoiseStatus {
- double noise_constant;
- double noise_slope;
- double strength;
- unsigned int mode;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/device_status.cpp b/src/ipa/raspberrypi/controller/device_status.cpp
deleted file mode 100644
index a389c40d..00000000
--- a/src/ipa/raspberrypi/controller/device_status.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2021, Raspberry Pi (Trading) Limited
- *
- * device_status.cpp - device (image sensor) status
- */
-#include "device_status.h"
-
-using namespace libcamera; /* for the Duration operator<< overload */
-
-std::ostream &operator<<(std::ostream &out, const DeviceStatus &d)
-{
- out << "Exposure: " << d.shutter_speed
- << " Frame length: " << d.frame_length
- << " Gain: " << d.analogue_gain;
-
- if (d.aperture)
- out << " Aperture: " << *d.aperture;
-
- if (d.lens_position)
- out << " Lens: " << *d.lens_position;
-
- if (d.flash_intensity)
- out << " Flash: " << *d.flash_intensity;
-
- if (d.sensor_temperature)
- out << " Temperature: " << *d.sensor_temperature;
-
- return out;
-}
diff --git a/src/ipa/raspberrypi/controller/dpc_status.h b/src/ipa/raspberrypi/controller/dpc_status.h
deleted file mode 100644
index a3ec2762..00000000
--- a/src/ipa/raspberrypi/controller/dpc_status.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * dpc_status.h - DPC (defective pixel correction) control algorithm status
- */
-#pragma once
-
-// The "DPC" algorithm sets defective pixel correction strength.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct DpcStatus {
- int strength; // 0 = "off", 1 = "normal", 2 = "strong"
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/focus_status.h b/src/ipa/raspberrypi/controller/focus_status.h
deleted file mode 100644
index ace2fe2c..00000000
--- a/src/ipa/raspberrypi/controller/focus_status.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
- *
- * focus_status.h - focus measurement status
- */
-#pragma once
-
-#include <linux/bcm2835-isp.h>
-
-// The focus algorithm should post the following structure into the image's
-// "focus.status" metadata. Recall that it's only reporting focus (contrast)
-// measurements, it's not driving any kind of auto-focus algorithm!
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct FocusStatus {
- unsigned int num;
- uint32_t focus_measures[FOCUS_REGIONS];
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/histogram.cpp b/src/ipa/raspberrypi/controller/histogram.cpp
deleted file mode 100644
index 9916b3ed..00000000
--- a/src/ipa/raspberrypi/controller/histogram.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * histogram.cpp - histogram calculations
- */
-#include <math.h>
-#include <stdio.h>
-
-#include "histogram.hpp"
-
-using namespace RPiController;
-
-uint64_t Histogram::CumulativeFreq(double bin) const
-{
- if (bin <= 0)
- return 0;
- else if (bin >= Bins())
- return Total();
- int b = (int)bin;
- return cumulative_[b] +
- (bin - b) * (cumulative_[b + 1] - cumulative_[b]);
-}
-
-double Histogram::Quantile(double q, int first, int last) const
-{
- if (first == -1)
- first = 0;
- if (last == -1)
- last = cumulative_.size() - 2;
- assert(first <= last);
- uint64_t items = q * Total();
- while (first < last) // binary search to find the right bin
- {
- int middle = (first + last) / 2;
- if (cumulative_[middle + 1] > items)
- last = middle; // between first and middle
- else
- first = middle + 1; // after middle
- }
- assert(items >= cumulative_[first] && items <= cumulative_[last + 1]);
- double frac = cumulative_[first + 1] == cumulative_[first] ? 0
- : (double)(items - cumulative_[first]) /
- (cumulative_[first + 1] - cumulative_[first]);
- return first + frac;
-}
-
-double Histogram::InterQuantileMean(double q_lo, double q_hi) const
-{
- assert(q_hi > q_lo);
- double p_lo = Quantile(q_lo);
- double p_hi = Quantile(q_hi, (int)p_lo);
- double sum_bin_freq = 0, cumul_freq = 0;
- for (double p_next = floor(p_lo) + 1.0; p_next <= ceil(p_hi);
- p_lo = p_next, p_next += 1.0) {
- int bin = floor(p_lo);
- double freq = (cumulative_[bin + 1] - cumulative_[bin]) *
- (std::min(p_next, p_hi) - p_lo);
- sum_bin_freq += bin * freq;
- cumul_freq += freq;
- }
- // add 0.5 to give an average for bin mid-points
- return sum_bin_freq / cumul_freq + 0.5;
-}
diff --git a/src/ipa/raspberrypi/controller/histogram.hpp b/src/ipa/raspberrypi/controller/histogram.hpp
deleted file mode 100644
index 90f5ac78..00000000
--- a/src/ipa/raspberrypi/controller/histogram.hpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * histogram.hpp - histogram calculation interface
- */
-#pragma once
-
-#include <stdint.h>
-#include <vector>
-#include <cassert>
-
-// A simple histogram class, for use in particular to find "quantiles" and
-// averages between "quantiles".
-
-namespace RPiController {
-
-class Histogram
-{
-public:
- template<typename T> Histogram(T *histogram, int num)
- {
- assert(num);
- cumulative_.reserve(num + 1);
- cumulative_.push_back(0);
- for (int i = 0; i < num; i++)
- cumulative_.push_back(cumulative_.back() +
- histogram[i]);
- }
- uint32_t Bins() const { return cumulative_.size() - 1; }
- uint64_t Total() const { return cumulative_[cumulative_.size() - 1]; }
- // Cumulative frequency up to a (fractional) point in a bin.
- uint64_t CumulativeFreq(double bin) const;
- // Return the (fractional) bin of the point q (0 <= q <= 1) through the
- // histogram. Optionally provide limits to help.
- double Quantile(double q, int first = -1, int last = -1) const;
- // Return the average histogram bin value between the two quantiles.
- double InterQuantileMean(double q_lo, double q_hi) const;
-
-private:
- std::vector<uint64_t> cumulative_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/lux_status.h b/src/ipa/raspberrypi/controller/lux_status.h
deleted file mode 100644
index 8ccfd933..00000000
--- a/src/ipa/raspberrypi/controller/lux_status.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * lux_status.h - Lux control algorithm status
- */
-#pragma once
-
-// The "lux" algorithm looks at the (AGC) histogram statistics of the frame and
-// estimates the current lux level of the scene. It does this by a simple ratio
-// calculation comparing to a reference image that was taken in known conditions
-// with known statistics and a properly measured lux level. There is a slight
-// problem with aperture, in that it may be variable without the system knowing
-// or being aware of it. In this case an external application may set a
-// "current_aperture" value if it wishes, which would be used in place of the
-// (presumably meaningless) value in the image metadata.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct LuxStatus {
- double lux;
- double aperture;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/metadata.hpp b/src/ipa/raspberrypi/controller/metadata.hpp
deleted file mode 100644
index 51e576cf..00000000
--- a/src/ipa/raspberrypi/controller/metadata.hpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Limited
- *
- * metadata.hpp - general metadata class
- */
-#pragma once
-
-// A simple class for carrying arbitrary metadata, for example about an image.
-
-#include <any>
-#include <map>
-#include <mutex>
-#include <string>
-
-namespace RPiController {
-
-class Metadata
-{
-public:
- Metadata() = default;
-
- Metadata(Metadata const &other)
- {
- std::scoped_lock other_lock(other.mutex_);
- data_ = other.data_;
- }
-
- Metadata(Metadata &&other)
- {
- std::scoped_lock other_lock(other.mutex_);
- data_ = std::move(other.data_);
- other.data_.clear();
- }
-
- template<typename T>
- void Set(std::string const &tag, T const &value)
- {
- std::scoped_lock lock(mutex_);
- data_[tag] = value;
- }
-
- template<typename T>
- int Get(std::string const &tag, T &value) const
- {
- std::scoped_lock lock(mutex_);
- auto it = data_.find(tag);
- if (it == data_.end())
- return -1;
- value = std::any_cast<T>(it->second);
- return 0;
- }
-
- void Clear()
- {
- std::scoped_lock lock(mutex_);
- data_.clear();
- }
-
- Metadata &operator=(Metadata const &other)
- {
- std::scoped_lock lock(mutex_, other.mutex_);
- data_ = other.data_;
- return *this;
- }
-
- Metadata &operator=(Metadata &&other)
- {
- std::scoped_lock lock(mutex_, other.mutex_);
- data_ = std::move(other.data_);
- other.data_.clear();
- return *this;
- }
-
- void Merge(Metadata &other)
- {
- std::scoped_lock lock(mutex_, other.mutex_);
- data_.merge(other.data_);
- }
-
- template<typename T>
- T *GetLocked(std::string const &tag)
- {
- // This allows in-place access to the Metadata contents,
- // for which you should be holding the lock.
- auto it = data_.find(tag);
- if (it == data_.end())
- return nullptr;
- return std::any_cast<T>(&it->second);
- }
-
- template<typename T>
- void SetLocked(std::string const &tag, T const &value)
- {
- // Use this only if you're holding the lock yourself.
- data_[tag] = value;
- }
-
- // Note: use of (lowercase) lock and unlock means you can create scoped
- // locks with the standard lock classes.
- // e.g. std::lock_guard<RPiController::Metadata> lock(metadata)
- void lock() { mutex_.lock(); }
- void unlock() { mutex_.unlock(); }
-
-private:
- mutable std::mutex mutex_;
- std::map<std::string, std::any> data_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/noise_status.h b/src/ipa/raspberrypi/controller/noise_status.h
deleted file mode 100644
index 8439a402..00000000
--- a/src/ipa/raspberrypi/controller/noise_status.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * noise_status.h - Noise control algorithm status
- */
-#pragma once
-
-// The "noise" algorithm stores an estimate of the noise profile for this image.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct NoiseStatus {
- double noise_constant;
- double noise_slope;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/controller/pwl.cpp b/src/ipa/raspberrypi/controller/pwl.cpp
deleted file mode 100644
index 130c820b..00000000
--- a/src/ipa/raspberrypi/controller/pwl.cpp
+++ /dev/null
@@ -1,246 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * pwl.cpp - piecewise linear functions
- */
-
-#include <cassert>
-#include <stdexcept>
-
-#include "pwl.hpp"
-
-using namespace RPiController;
-
-void Pwl::Read(boost::property_tree::ptree const &params)
-{
- for (auto it = params.begin(); it != params.end(); it++) {
- double x = it->second.get_value<double>();
- assert(it == params.begin() || x > points_.back().x);
- it++;
- double y = it->second.get_value<double>();
- points_.push_back(Point(x, y));
- }
- assert(points_.size() >= 2);
-}
-
-void Pwl::Append(double x, double y, const double eps)
-{
- if (points_.empty() || points_.back().x + eps < x)
- points_.push_back(Point(x, y));
-}
-
-void Pwl::Prepend(double x, double y, const double eps)
-{
- if (points_.empty() || points_.front().x - eps > x)
- points_.insert(points_.begin(), Point(x, y));
-}
-
-Pwl::Interval Pwl::Domain() const
-{
- return Interval(points_[0].x, points_[points_.size() - 1].x);
-}
-
-Pwl::Interval Pwl::Range() const
-{
- double lo = points_[0].y, hi = lo;
- for (auto &p : points_)
- lo = std::min(lo, p.y), hi = std::max(hi, p.y);
- return Interval(lo, hi);
-}
-
-bool Pwl::Empty() const
-{
- return points_.empty();
-}
-
-double Pwl::Eval(double x, int *span_ptr, bool update_span) const
-{
- int span = findSpan(x, span_ptr && *span_ptr != -1
- ? *span_ptr
- : points_.size() / 2 - 1);
- if (span_ptr && update_span)
- *span_ptr = span;
- return points_[span].y +
- (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /
- (points_[span + 1].x - points_[span].x);
-}
-
-int Pwl::findSpan(double x, int span) const
-{
- // Pwls are generally small, so linear search may well be faster than
- // binary, though could review this if large PWls start turning up.
- int last_span = points_.size() - 2;
- // some algorithms may call us with span pointing directly at the last
- // control point
- span = std::max(0, std::min(last_span, span));
- while (span < last_span && x >= points_[span + 1].x)
- span++;
- while (span && x < points_[span].x)
- span--;
- return span;
-}
-
-Pwl::PerpType Pwl::Invert(Point const &xy, Point &perp, int &span,
- const double eps) const
-{
- assert(span >= -1);
- bool prev_off_end = false;
- for (span = span + 1; span < (int)points_.size() - 1; span++) {
- Point span_vec = points_[span + 1] - points_[span];
- double t = ((xy - points_[span]) % span_vec) / span_vec.Len2();
- if (t < -eps) // off the start of this span
- {
- if (span == 0) {
- perp = points_[span];
- return PerpType::Start;
- } else if (prev_off_end) {
- perp = points_[span];
- return PerpType::Vertex;
- }
- } else if (t > 1 + eps) // off the end of this span
- {
- if (span == (int)points_.size() - 2) {
- perp = points_[span + 1];
- return PerpType::End;
- }
- prev_off_end = true;
- } else // a true perpendicular
- {
- perp = points_[span] + span_vec * t;
- return PerpType::Perpendicular;
- }
- }
- return PerpType::None;
-}
-
-Pwl Pwl::Inverse(bool *true_inverse, const double eps) const
-{
- bool appended = false, prepended = false, neither = false;
- Pwl inverse;
-
- for (Point const &p : points_) {
- if (inverse.Empty())
- inverse.Append(p.y, p.x, eps);
- else if (std::abs(inverse.points_.back().x - p.y) <= eps ||
- std::abs(inverse.points_.front().x - p.y) <= eps)
- /* do nothing */;
- else if (p.y > inverse.points_.back().x) {
- inverse.Append(p.y, p.x, eps);
- appended = true;
- } else if (p.y < inverse.points_.front().x) {
- inverse.Prepend(p.y, p.x, eps);
- prepended = true;
- } else
- neither = true;
- }
-
- // This is not a proper inverse if we found ourselves putting points
- // onto both ends of the inverse, or if there were points that couldn't
- // go on either.
- if (true_inverse)
- *true_inverse = !(neither || (appended && prepended));
-
- return inverse;
-}
-
-Pwl Pwl::Compose(Pwl const &other, const double eps) const
-{
- double this_x = points_[0].x, this_y = points_[0].y;
- int this_span = 0, other_span = other.findSpan(this_y, 0);
- Pwl result({ { this_x, other.Eval(this_y, &other_span, false) } });
- while (this_span != (int)points_.size() - 1) {
- double dx = points_[this_span + 1].x - points_[this_span].x,
- dy = points_[this_span + 1].y - points_[this_span].y;
- if (abs(dy) > eps &&
- other_span + 1 < (int)other.points_.size() &&
- points_[this_span + 1].y >=
- other.points_[other_span + 1].x + eps) {
- // next control point in result will be where this
- // function's y reaches the next span in other
- this_x = points_[this_span].x +
- (other.points_[other_span + 1].x -
- points_[this_span].y) * dx / dy;
- this_y = other.points_[++other_span].x;
- } else if (abs(dy) > eps && other_span > 0 &&
- points_[this_span + 1].y <=
- other.points_[other_span - 1].x - eps) {
- // next control point in result will be where this
- // function's y reaches the previous span in other
- this_x = points_[this_span].x +
- (other.points_[other_span + 1].x -
- points_[this_span].y) * dx / dy;
- this_y = other.points_[--other_span].x;
- } else {
- // we stay in the same span in other
- this_span++;
- this_x = points_[this_span].x,
- this_y = points_[this_span].y;
- }
- result.Append(this_x, other.Eval(this_y, &other_span, false),
- eps);
- }
- return result;
-}
-
-void Pwl::Map(std::function<void(double x, double y)> f) const
-{
- for (auto &pt : points_)
- f(pt.x, pt.y);
-}
-
-void Pwl::Map2(Pwl const &pwl0, Pwl const &pwl1,
- std::function<void(double x, double y0, double y1)> f)
-{
- int span0 = 0, span1 = 0;
- double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);
- f(x, pwl0.Eval(x, &span0, false), pwl1.Eval(x, &span1, false));
- while (span0 < (int)pwl0.points_.size() - 1 ||
- span1 < (int)pwl1.points_.size() - 1) {
- if (span0 == (int)pwl0.points_.size() - 1)
- x = pwl1.points_[++span1].x;
- else if (span1 == (int)pwl1.points_.size() - 1)
- x = pwl0.points_[++span0].x;
- else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)
- x = pwl1.points_[++span1].x;
- else
- x = pwl0.points_[++span0].x;
- f(x, pwl0.Eval(x, &span0, false), pwl1.Eval(x, &span1, false));
- }
-}
-
-Pwl Pwl::Combine(Pwl const &pwl0, Pwl const &pwl1,
- std::function<double(double x, double y0, double y1)> f,
- const double eps)
-{
- Pwl result;
- Map2(pwl0, pwl1, [&](double x, double y0, double y1) {
- result.Append(x, f(x, y0, y1), eps);
- });
- return result;
-}
-
-void Pwl::MatchDomain(Interval const &domain, bool clip, const double eps)
-{
- int span = 0;
- Prepend(domain.start, Eval(clip ? points_[0].x : domain.start, &span),
- eps);
- span = points_.size() - 2;
- Append(domain.end, Eval(clip ? points_.back().x : domain.end, &span),
- eps);
-}
-
-Pwl &Pwl::operator*=(double d)
-{
- for (auto &pt : points_)
- pt.y *= d;
- return *this;
-}
-
-void Pwl::Debug(FILE *fp) const
-{
- fprintf(fp, "Pwl {\n");
- for (auto &p : points_)
- fprintf(fp, "\t(%g, %g)\n", p.x, p.y);
- fprintf(fp, "}\n");
-}
diff --git a/src/ipa/raspberrypi/controller/pwl.hpp b/src/ipa/raspberrypi/controller/pwl.hpp
deleted file mode 100644
index 484672f6..00000000
--- a/src/ipa/raspberrypi/controller/pwl.hpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * pwl.hpp - piecewise linear functions interface
- */
-#pragma once
-
-#include <math.h>
-#include <vector>
-
-#include <boost/property_tree/ptree.hpp>
-
-namespace RPiController {
-
-class Pwl
-{
-public:
- struct Interval {
- Interval(double _start, double _end) : start(_start), end(_end)
- {
- }
- double start, end;
- bool Contains(double value)
- {
- return value >= start && value <= end;
- }
- double Clip(double value)
- {
- return value < start ? start
- : (value > end ? end : value);
- }
- double Len() const { return end - start; }
- };
- struct Point {
- Point() : x(0), y(0) {}
- Point(double _x, double _y) : x(_x), y(_y) {}
- double x, y;
- Point operator-(Point const &p) const
- {
- return Point(x - p.x, y - p.y);
- }
- Point operator+(Point const &p) const
- {
- return Point(x + p.x, y + p.y);
- }
- double operator%(Point const &p) const
- {
- return x * p.x + y * p.y;
- }
- Point operator*(double f) const { return Point(x * f, y * f); }
- Point operator/(double f) const { return Point(x / f, y / f); }
- double Len2() const { return x * x + y * y; }
- double Len() const { return sqrt(Len2()); }
- };
- Pwl() {}
- Pwl(std::vector<Point> const &points) : points_(points) {}
- void Read(boost::property_tree::ptree const &params);
- void Append(double x, double y, const double eps = 1e-6);
- void Prepend(double x, double y, const double eps = 1e-6);
- Interval Domain() const;
- Interval Range() const;
- bool Empty() const;
- // Evaluate Pwl, optionally supplying an initial guess for the
- // "span". The "span" may be optionally be updated. If you want to know
- // the "span" value but don't have an initial guess you can set it to
- // -1.
- double Eval(double x, int *span_ptr = nullptr,
- bool update_span = true) const;
- // Find perpendicular closest to xy, starting from span+1 so you can
- // call it repeatedly to check for multiple closest points (set span to
- // -1 on the first call). Also returns "pseudo" perpendiculars; see
- // PerpType enum.
- enum class PerpType {
- None, // no perpendicular found
- Start, // start of Pwl is closest point
- End, // end of Pwl is closest point
- Vertex, // vertex of Pwl is closest point
- Perpendicular // true perpendicular found
- };
- PerpType Invert(Point const &xy, Point &perp, int &span,
- const double eps = 1e-6) const;
- // Compute the inverse function. Indicate if it is a proper (true)
- // inverse, or only a best effort (e.g. input was non-monotonic).
- Pwl Inverse(bool *true_inverse = nullptr, const double eps = 1e-6) const;
- // Compose two Pwls together, doing "this" first and "other" after.
- Pwl Compose(Pwl const &other, const double eps = 1e-6) const;
- // Apply function to (x,y) values at every control point.
- void Map(std::function<void(double x, double y)> f) const;
- // Apply function to (x, y0, y1) values wherever either Pwl has a
- // control point.
- static void Map2(Pwl const &pwl0, Pwl const &pwl1,
- std::function<void(double x, double y0, double y1)> f);
- // Combine two Pwls, meaning we create a new Pwl where the y values are
- // given by running f wherever either has a knot.
- static Pwl
- Combine(Pwl const &pwl0, Pwl const &pwl1,
- std::function<double(double x, double y0, double y1)> f,
- const double eps = 1e-6);
- // Make "this" match (at least) the given domain. Any extension my be
- // clipped or linear.
- void MatchDomain(Interval const &domain, bool clip = true,
- const double eps = 1e-6);
- Pwl &operator*=(double d);
- void Debug(FILE *fp = stdout) const;
-
-private:
- int findSpan(double x, int span) const;
- std::vector<Point> points_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/agc.cpp b/src/ipa/raspberrypi/controller/rpi/agc.cpp
deleted file mode 100644
index f6a9cb0a..00000000
--- a/src/ipa/raspberrypi/controller/rpi/agc.cpp
+++ /dev/null
@@ -1,797 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * agc.cpp - AGC/AEC control algorithm
- */
-
-#include <map>
-
-#include <linux/bcm2835-isp.h>
-
-#include <libcamera/base/log.h>
-
-#include "../awb_status.h"
-#include "../device_status.h"
-#include "../histogram.hpp"
-#include "../lux_status.h"
-#include "../metadata.hpp"
-
-#include "agc.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-using libcamera::utils::Duration;
-using namespace std::literals::chrono_literals;
-
-LOG_DEFINE_CATEGORY(RPiAgc)
-
-#define NAME "rpi.agc"
-
-#define PIPELINE_BITS 13 // seems to be a 13-bit pipeline
-
-void AgcMeteringMode::Read(boost::property_tree::ptree const &params)
-{
- int num = 0;
- for (auto &p : params.get_child("weights")) {
- if (num == AGC_STATS_SIZE)
- throw std::runtime_error("AgcConfig: too many weights");
- weights[num++] = p.second.get_value<double>();
- }
- if (num != AGC_STATS_SIZE)
- throw std::runtime_error("AgcConfig: insufficient weights");
-}
-
-static std::string
-read_metering_modes(std::map<std::string, AgcMeteringMode> &metering_modes,
- boost::property_tree::ptree const &params)
-{
- std::string first;
- for (auto &p : params) {
- AgcMeteringMode metering_mode;
- metering_mode.Read(p.second);
- metering_modes[p.first] = std::move(metering_mode);
- if (first.empty())
- first = p.first;
- }
- return first;
-}
-
-static int read_list(std::vector<double> &list,
- boost::property_tree::ptree const &params)
-{
- for (auto &p : params)
- list.push_back(p.second.get_value<double>());
- return list.size();
-}
-
-static int read_list(std::vector<Duration> &list,
- boost::property_tree::ptree const &params)
-{
- for (auto &p : params)
- list.push_back(p.second.get_value<double>() * 1us);
- return list.size();
-}
-
-void AgcExposureMode::Read(boost::property_tree::ptree const &params)
-{
- int num_shutters = read_list(shutter, params.get_child("shutter"));
- int num_ags = read_list(gain, params.get_child("gain"));
- if (num_shutters < 2 || num_ags < 2)
- throw std::runtime_error(
- "AgcConfig: must have at least two entries in exposure profile");
- if (num_shutters != num_ags)
- throw std::runtime_error(
- "AgcConfig: expect same number of exposure and gain entries in exposure profile");
-}
-
-static std::string
-read_exposure_modes(std::map<std::string, AgcExposureMode> &exposure_modes,
- boost::property_tree::ptree const &params)
-{
- std::string first;
- for (auto &p : params) {
- AgcExposureMode exposure_mode;
- exposure_mode.Read(p.second);
- exposure_modes[p.first] = std::move(exposure_mode);
- if (first.empty())
- first = p.first;
- }
- return first;
-}
-
-void AgcConstraint::Read(boost::property_tree::ptree const &params)
-{
- std::string bound_string = params.get<std::string>("bound", "");
- transform(bound_string.begin(), bound_string.end(),
- bound_string.begin(), ::toupper);
- if (bound_string != "UPPER" && bound_string != "LOWER")
- throw std::runtime_error(
- "AGC constraint type should be UPPER or LOWER");
- bound = bound_string == "UPPER" ? Bound::UPPER : Bound::LOWER;
- q_lo = params.get<double>("q_lo");
- q_hi = params.get<double>("q_hi");
- Y_target.Read(params.get_child("y_target"));
-}
-
-static AgcConstraintMode
-read_constraint_mode(boost::property_tree::ptree const &params)
-{
- AgcConstraintMode mode;
- for (auto &p : params) {
- AgcConstraint constraint;
- constraint.Read(p.second);
- mode.push_back(std::move(constraint));
- }
- return mode;
-}
-
-static std::string read_constraint_modes(
- std::map<std::string, AgcConstraintMode> &constraint_modes,
- boost::property_tree::ptree const &params)
-{
- std::string first;
- for (auto &p : params) {
- constraint_modes[p.first] = read_constraint_mode(p.second);
- if (first.empty())
- first = p.first;
- }
- return first;
-}
-
-void AgcConfig::Read(boost::property_tree::ptree const &params)
-{
- LOG(RPiAgc, Debug) << "AgcConfig";
- default_metering_mode = read_metering_modes(
- metering_modes, params.get_child("metering_modes"));
- default_exposure_mode = read_exposure_modes(
- exposure_modes, params.get_child("exposure_modes"));
- default_constraint_mode = read_constraint_modes(
- constraint_modes, params.get_child("constraint_modes"));
- Y_target.Read(params.get_child("y_target"));
- speed = params.get<double>("speed", 0.2);
- startup_frames = params.get<uint16_t>("startup_frames", 10);
- convergence_frames = params.get<unsigned int>("convergence_frames", 6);
- fast_reduce_threshold =
- params.get<double>("fast_reduce_threshold", 0.4);
- base_ev = params.get<double>("base_ev", 1.0);
- // Start with quite a low value as ramping up is easier than ramping down.
- default_exposure_time = params.get<double>("default_exposure_time", 1000) * 1us;
- default_analogue_gain = params.get<double>("default_analogue_gain", 1.0);
-}
-
-Agc::ExposureValues::ExposureValues()
- : shutter(0s), analogue_gain(0),
- total_exposure(0s), total_exposure_no_dg(0s)
-{
-}
-
-Agc::Agc(Controller *controller)
- : AgcAlgorithm(controller), metering_mode_(nullptr),
- exposure_mode_(nullptr), constraint_mode_(nullptr),
- frame_count_(0), lock_count_(0),
- last_target_exposure_(0s), last_sensitivity_(0.0),
- ev_(1.0), flicker_period_(0s),
- max_shutter_(0s), fixed_shutter_(0s), fixed_analogue_gain_(0.0)
-{
- memset(&awb_, 0, sizeof(awb_));
- // Setting status_.total_exposure_value_ to zero initially tells us
- // it's not been calculated yet (i.e. Process hasn't yet run).
- memset(&status_, 0, sizeof(status_));
- status_.ev = ev_;
-}
-
-char const *Agc::Name() const
-{
- return NAME;
-}
-
-void Agc::Read(boost::property_tree::ptree const &params)
-{
- LOG(RPiAgc, Debug) << "Agc";
- config_.Read(params);
- // Set the config's defaults (which are the first ones it read) as our
- // current modes, until someone changes them. (they're all known to
- // exist at this point)
- metering_mode_name_ = config_.default_metering_mode;
- metering_mode_ = &config_.metering_modes[metering_mode_name_];
- exposure_mode_name_ = config_.default_exposure_mode;
- exposure_mode_ = &config_.exposure_modes[exposure_mode_name_];
- constraint_mode_name_ = config_.default_constraint_mode;
- constraint_mode_ = &config_.constraint_modes[constraint_mode_name_];
- // Set up the "last shutter/gain" values, in case AGC starts "disabled".
- status_.shutter_time = config_.default_exposure_time;
- status_.analogue_gain = config_.default_analogue_gain;
-}
-
-bool Agc::IsPaused() const
-{
- return false;
-}
-
-void Agc::Pause()
-{
- fixed_shutter_ = status_.shutter_time;
- fixed_analogue_gain_ = status_.analogue_gain;
-}
-
-void Agc::Resume()
-{
- fixed_shutter_ = 0s;
- fixed_analogue_gain_ = 0;
-}
-
-unsigned int Agc::GetConvergenceFrames() const
-{
- // If shutter and gain have been explicitly set, there is no
- // convergence to happen, so no need to drop any frames - return zero.
- if (fixed_shutter_ && fixed_analogue_gain_)
- return 0;
- else
- return config_.convergence_frames;
-}
-
-void Agc::SetEv(double ev)
-{
- ev_ = ev;
-}
-
-void Agc::SetFlickerPeriod(Duration flicker_period)
-{
- flicker_period_ = flicker_period;
-}
-
-void Agc::SetMaxShutter(Duration max_shutter)
-{
- max_shutter_ = max_shutter;
-}
-
-void Agc::SetFixedShutter(Duration fixed_shutter)
-{
- fixed_shutter_ = fixed_shutter;
- // Set this in case someone calls Pause() straight after.
- status_.shutter_time = clipShutter(fixed_shutter_);
-}
-
-void Agc::SetFixedAnalogueGain(double fixed_analogue_gain)
-{
- fixed_analogue_gain_ = fixed_analogue_gain;
- // Set this in case someone calls Pause() straight after.
- status_.analogue_gain = fixed_analogue_gain;
-}
-
-void Agc::SetMeteringMode(std::string const &metering_mode_name)
-{
- metering_mode_name_ = metering_mode_name;
-}
-
-void Agc::SetExposureMode(std::string const &exposure_mode_name)
-{
- exposure_mode_name_ = exposure_mode_name;
-}
-
-void Agc::SetConstraintMode(std::string const &constraint_mode_name)
-{
- constraint_mode_name_ = constraint_mode_name;
-}
-
-void Agc::SwitchMode(CameraMode const &camera_mode,
- Metadata *metadata)
-{
- /* AGC expects the mode sensitivity always to be non-zero. */
- ASSERT(camera_mode.sensitivity);
-
- housekeepConfig();
-
- Duration fixed_shutter = clipShutter(fixed_shutter_);
- if (fixed_shutter && fixed_analogue_gain_) {
- // We're going to reset the algorithm here with these fixed values.
-
- fetchAwbStatus(metadata);
- double min_colour_gain = std::min({ awb_.gain_r, awb_.gain_g, awb_.gain_b, 1.0 });
- ASSERT(min_colour_gain != 0.0);
-
- // This is the equivalent of computeTargetExposure and applyDigitalGain.
- target_.total_exposure_no_dg = fixed_shutter * fixed_analogue_gain_;
- target_.total_exposure = target_.total_exposure_no_dg / min_colour_gain;
-
- // Equivalent of filterExposure. This resets any "history".
- filtered_ = target_;
-
- // Equivalent of divideUpExposure.
- filtered_.shutter = fixed_shutter;
- filtered_.analogue_gain = fixed_analogue_gain_;
- } else if (status_.total_exposure_value) {
- // On a mode switch, various things could happen:
- // - the exposure profile might change
- // - a fixed exposure or gain might be set
- // - the new mode's sensitivity might be different
- // We cope with the last of these by scaling the target values. After
- // that we just need to re-divide the exposure/gain according to the
- // current exposure profile, which takes care of everything else.
-
- double ratio = last_sensitivity_ / camera_mode.sensitivity;
- target_.total_exposure_no_dg *= ratio;
- target_.total_exposure *= ratio;
- filtered_.total_exposure_no_dg *= ratio;
- filtered_.total_exposure *= ratio;
-
- divideUpExposure();
- } else {
- // We come through here on startup, when at least one of the shutter
- // or gain has not been fixed. We must still write those values out so
- // that they will be applied immediately. We supply some arbitrary defaults
- // for any that weren't set.
-
- // Equivalent of divideUpExposure.
- filtered_.shutter = fixed_shutter ? fixed_shutter : config_.default_exposure_time;
- filtered_.analogue_gain = fixed_analogue_gain_ ? fixed_analogue_gain_ : config_.default_analogue_gain;
- }
-
- writeAndFinish(metadata, false);
-
- // We must remember the sensitivity of this mode for the next SwitchMode.
- last_sensitivity_ = camera_mode.sensitivity;
-}
-
-void Agc::Prepare(Metadata *image_metadata)
-{
- status_.digital_gain = 1.0;
- fetchAwbStatus(image_metadata); // always fetch it so that Process knows it's been done
-
- if (status_.total_exposure_value) {
- // Process has run, so we have meaningful values.
- DeviceStatus device_status;
- if (image_metadata->Get("device.status", device_status) == 0) {
- Duration actual_exposure = device_status.shutter_speed *
- device_status.analogue_gain;
- if (actual_exposure) {
- status_.digital_gain =
- status_.total_exposure_value /
- actual_exposure;
- LOG(RPiAgc, Debug) << "Want total exposure " << status_.total_exposure_value;
- // Never ask for a gain < 1.0, and also impose
- // some upper limit. Make it customisable?
- status_.digital_gain = std::max(
- 1.0,
- std::min(status_.digital_gain, 4.0));
- LOG(RPiAgc, Debug) << "Actual exposure " << actual_exposure;
- LOG(RPiAgc, Debug) << "Use digital_gain " << status_.digital_gain;
- LOG(RPiAgc, Debug) << "Effective exposure "
- << actual_exposure * status_.digital_gain;
- // Decide whether AEC/AGC has converged.
- updateLockStatus(device_status);
- }
- } else
- LOG(RPiAgc, Warning) << Name() << ": no device metadata";
- image_metadata->Set("agc.status", status_);
- }
-}
-
-void Agc::Process(StatisticsPtr &stats, Metadata *image_metadata)
-{
- frame_count_++;
- // First a little bit of housekeeping, fetching up-to-date settings and
- // configuration, that kind of thing.
- housekeepConfig();
- // Get the current exposure values for the frame that's just arrived.
- fetchCurrentExposure(image_metadata);
- // Compute the total gain we require relative to the current exposure.
- double gain, target_Y;
- computeGain(stats.get(), image_metadata, gain, target_Y);
- // Now compute the target (final) exposure which we think we want.
- computeTargetExposure(gain);
- // Some of the exposure has to be applied as digital gain, so work out
- // what that is. This function also tells us whether it's decided to
- // "desaturate" the image more quickly.
- bool desaturate = applyDigitalGain(gain, target_Y);
- // The results have to be filtered so as not to change too rapidly.
- filterExposure(desaturate);
- // The last thing is to divide up the exposure value into a shutter time
- // and analogue_gain, according to the current exposure mode.
- divideUpExposure();
- // Finally advertise what we've done.
- writeAndFinish(image_metadata, desaturate);
-}
-
-void Agc::updateLockStatus(DeviceStatus const &device_status)
-{
- const double ERROR_FACTOR = 0.10; // make these customisable?
- const int MAX_LOCK_COUNT = 5;
- // Reset "lock count" when we exceed this multiple of ERROR_FACTOR
- const double RESET_MARGIN = 1.5;
-
- // Add 200us to the exposure time error to allow for line quantisation.
- Duration exposure_error = last_device_status_.shutter_speed * ERROR_FACTOR + 200us;
- double gain_error = last_device_status_.analogue_gain * ERROR_FACTOR;
- Duration target_error = last_target_exposure_ * ERROR_FACTOR;
-
- // Note that we don't know the exposure/gain limits of the sensor, so
- // the values we keep requesting may be unachievable. For this reason
- // we only insist that we're close to values in the past few frames.
- if (device_status.shutter_speed > last_device_status_.shutter_speed - exposure_error &&
- device_status.shutter_speed < last_device_status_.shutter_speed + exposure_error &&
- device_status.analogue_gain > last_device_status_.analogue_gain - gain_error &&
- device_status.analogue_gain < last_device_status_.analogue_gain + gain_error &&
- status_.target_exposure_value > last_target_exposure_ - target_error &&
- status_.target_exposure_value < last_target_exposure_ + target_error)
- lock_count_ = std::min(lock_count_ + 1, MAX_LOCK_COUNT);
- else if (device_status.shutter_speed < last_device_status_.shutter_speed - RESET_MARGIN * exposure_error ||
- device_status.shutter_speed > last_device_status_.shutter_speed + RESET_MARGIN * exposure_error ||
- device_status.analogue_gain < last_device_status_.analogue_gain - RESET_MARGIN * gain_error ||
- device_status.analogue_gain > last_device_status_.analogue_gain + RESET_MARGIN * gain_error ||
- status_.target_exposure_value < last_target_exposure_ - RESET_MARGIN * target_error ||
- status_.target_exposure_value > last_target_exposure_ + RESET_MARGIN * target_error)
- lock_count_ = 0;
-
- last_device_status_ = device_status;
- last_target_exposure_ = status_.target_exposure_value;
-
- LOG(RPiAgc, Debug) << "Lock count updated to " << lock_count_;
- status_.locked = lock_count_ == MAX_LOCK_COUNT;
-}
-
-static void copy_string(std::string const &s, char *d, size_t size)
-{
- size_t length = s.copy(d, size - 1);
- d[length] = '\0';
-}
-
-void Agc::housekeepConfig()
-{
- // First fetch all the up-to-date settings, so no one else has to do it.
- status_.ev = ev_;
- status_.fixed_shutter = clipShutter(fixed_shutter_);
- status_.fixed_analogue_gain = fixed_analogue_gain_;
- status_.flicker_period = flicker_period_;
- LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixed_shutter "
- << status_.fixed_shutter << " fixed_analogue_gain "
- << status_.fixed_analogue_gain;
- // Make sure the "mode" pointers point to the up-to-date things, if
- // they've changed.
- if (strcmp(metering_mode_name_.c_str(), status_.metering_mode)) {
- auto it = config_.metering_modes.find(metering_mode_name_);
- if (it == config_.metering_modes.end())
- throw std::runtime_error("Agc: no metering mode " +
- metering_mode_name_);
- metering_mode_ = &it->second;
- copy_string(metering_mode_name_, status_.metering_mode,
- sizeof(status_.metering_mode));
- }
- if (strcmp(exposure_mode_name_.c_str(), status_.exposure_mode)) {
- auto it = config_.exposure_modes.find(exposure_mode_name_);
- if (it == config_.exposure_modes.end())
- throw std::runtime_error("Agc: no exposure profile " +
- exposure_mode_name_);
- exposure_mode_ = &it->second;
- copy_string(exposure_mode_name_, status_.exposure_mode,
- sizeof(status_.exposure_mode));
- }
- if (strcmp(constraint_mode_name_.c_str(), status_.constraint_mode)) {
- auto it =
- config_.constraint_modes.find(constraint_mode_name_);
- if (it == config_.constraint_modes.end())
- throw std::runtime_error("Agc: no constraint list " +
- constraint_mode_name_);
- constraint_mode_ = &it->second;
- copy_string(constraint_mode_name_, status_.constraint_mode,
- sizeof(status_.constraint_mode));
- }
- LOG(RPiAgc, Debug) << "exposure_mode "
- << exposure_mode_name_ << " constraint_mode "
- << constraint_mode_name_ << " metering_mode "
- << metering_mode_name_;
-}
-
-void Agc::fetchCurrentExposure(Metadata *image_metadata)
-{
- std::unique_lock<Metadata> lock(*image_metadata);
- DeviceStatus *device_status =
- image_metadata->GetLocked<DeviceStatus>("device.status");
- if (!device_status)
- throw std::runtime_error("Agc: no device metadata");
- current_.shutter = device_status->shutter_speed;
- current_.analogue_gain = device_status->analogue_gain;
- AgcStatus *agc_status =
- image_metadata->GetLocked<AgcStatus>("agc.status");
- current_.total_exposure = agc_status ? agc_status->total_exposure_value : 0s;
- current_.total_exposure_no_dg = current_.shutter * current_.analogue_gain;
-}
-
-void Agc::fetchAwbStatus(Metadata *image_metadata)
-{
- awb_.gain_r = 1.0; // in case not found in metadata
- awb_.gain_g = 1.0;
- awb_.gain_b = 1.0;
- if (image_metadata->Get("awb.status", awb_) != 0)
- LOG(RPiAgc, Debug) << "Agc: no AWB status found";
-}
-
-static double compute_initial_Y(bcm2835_isp_stats *stats, AwbStatus const &awb,
- double weights[], double gain)
-{
- bcm2835_isp_stats_region *regions = stats->agc_stats;
- // Note how the calculation below means that equal weights give you
- // "average" metering (i.e. all pixels equally important).
- double R_sum = 0, G_sum = 0, B_sum = 0, pixel_sum = 0;
- for (int i = 0; i < AGC_STATS_SIZE; i++) {
- double counted = regions[i].counted;
- double r_sum = std::min(regions[i].r_sum * gain, ((1 << PIPELINE_BITS) - 1) * counted);
- double g_sum = std::min(regions[i].g_sum * gain, ((1 << PIPELINE_BITS) - 1) * counted);
- double b_sum = std::min(regions[i].b_sum * gain, ((1 << PIPELINE_BITS) - 1) * counted);
- R_sum += r_sum * weights[i];
- G_sum += g_sum * weights[i];
- B_sum += b_sum * weights[i];
- pixel_sum += counted * weights[i];
- }
- if (pixel_sum == 0.0) {
- LOG(RPiAgc, Warning) << "compute_initial_Y: pixel_sum is zero";
- return 0;
- }
- double Y_sum = R_sum * awb.gain_r * .299 +
- G_sum * awb.gain_g * .587 +
- B_sum * awb.gain_b * .114;
- return Y_sum / pixel_sum / (1 << PIPELINE_BITS);
-}
-
-// We handle extra gain through EV by adjusting our Y targets. However, you
-// simply can't monitor histograms once they get very close to (or beyond!)
-// saturation, so we clamp the Y targets to this value. It does mean that EV
-// increases don't necessarily do quite what you might expect in certain
-// (contrived) cases.
-
-#define EV_GAIN_Y_TARGET_LIMIT 0.9
-
-static double constraint_compute_gain(AgcConstraint &c, Histogram &h,
- double lux, double ev_gain,
- double &target_Y)
-{
- target_Y = c.Y_target.Eval(c.Y_target.Domain().Clip(lux));
- target_Y = std::min(EV_GAIN_Y_TARGET_LIMIT, target_Y * ev_gain);
- double iqm = h.InterQuantileMean(c.q_lo, c.q_hi);
- return (target_Y * NUM_HISTOGRAM_BINS) / iqm;
-}
-
-void Agc::computeGain(bcm2835_isp_stats *statistics, Metadata *image_metadata,
- double &gain, double &target_Y)
-{
- struct LuxStatus lux = {};
- lux.lux = 400; // default lux level to 400 in case no metadata found
- if (image_metadata->Get("lux.status", lux) != 0)
- LOG(RPiAgc, Warning) << "Agc: no lux level found";
- Histogram h(statistics->hist[0].g_hist, NUM_HISTOGRAM_BINS);
- double ev_gain = status_.ev * config_.base_ev;
- // The initial gain and target_Y come from some of the regions. After
- // that we consider the histogram constraints.
- target_Y =
- config_.Y_target.Eval(config_.Y_target.Domain().Clip(lux.lux));
- target_Y = std::min(EV_GAIN_Y_TARGET_LIMIT, target_Y * ev_gain);
-
- // Do this calculation a few times as brightness increase can be
- // non-linear when there are saturated regions.
- gain = 1.0;
- for (int i = 0; i < 8; i++) {
- double initial_Y = compute_initial_Y(statistics, awb_,
- metering_mode_->weights, gain);
- double extra_gain = std::min(10.0, target_Y / (initial_Y + .001));
- gain *= extra_gain;
- LOG(RPiAgc, Debug) << "Initial Y " << initial_Y << " target " << target_Y
- << " gives gain " << gain;
- if (extra_gain < 1.01) // close enough
- break;
- }
-
- for (auto &c : *constraint_mode_) {
- double new_target_Y;
- double new_gain =
- constraint_compute_gain(c, h, lux.lux, ev_gain,
- new_target_Y);
- LOG(RPiAgc, Debug) << "Constraint has target_Y "
- << new_target_Y << " giving gain " << new_gain;
- if (c.bound == AgcConstraint::Bound::LOWER &&
- new_gain > gain) {
- LOG(RPiAgc, Debug) << "Lower bound constraint adopted";
- gain = new_gain, target_Y = new_target_Y;
- } else if (c.bound == AgcConstraint::Bound::UPPER &&
- new_gain < gain) {
- LOG(RPiAgc, Debug) << "Upper bound constraint adopted";
- gain = new_gain, target_Y = new_target_Y;
- }
- }
- LOG(RPiAgc, Debug) << "Final gain " << gain << " (target_Y " << target_Y << " ev "
- << status_.ev << " base_ev " << config_.base_ev
- << ")";
-}
-
-void Agc::computeTargetExposure(double gain)
-{
- if (status_.fixed_shutter && status_.fixed_analogue_gain) {
- // When ag and shutter are both fixed, we need to drive the
- // total exposure so that we end up with a digital gain of at least
- // 1/min_colour_gain. Otherwise we'd desaturate channels causing
- // white to go cyan or magenta.
- double min_colour_gain = std::min({ awb_.gain_r, awb_.gain_g, awb_.gain_b, 1.0 });
- ASSERT(min_colour_gain != 0.0);
- target_.total_exposure =
- status_.fixed_shutter * status_.fixed_analogue_gain / min_colour_gain;
- } else {
- // The statistics reflect the image without digital gain, so the final
- // total exposure we're aiming for is:
- target_.total_exposure = current_.total_exposure_no_dg * gain;
- // The final target exposure is also limited to what the exposure
- // mode allows.
- Duration max_shutter = status_.fixed_shutter
- ? status_.fixed_shutter
- : exposure_mode_->shutter.back();
- max_shutter = clipShutter(max_shutter);
- Duration max_total_exposure =
- max_shutter *
- (status_.fixed_analogue_gain != 0.0
- ? status_.fixed_analogue_gain
- : exposure_mode_->gain.back());
- target_.total_exposure = std::min(target_.total_exposure,
- max_total_exposure);
- }
- LOG(RPiAgc, Debug) << "Target total_exposure " << target_.total_exposure;
-}
-
-bool Agc::applyDigitalGain(double gain, double target_Y)
-{
- double min_colour_gain = std::min({ awb_.gain_r, awb_.gain_g, awb_.gain_b, 1.0 });
- ASSERT(min_colour_gain != 0.0);
- double dg = 1.0 / min_colour_gain;
- // I think this pipeline subtracts black level and rescales before we
- // get the stats, so no need to worry about it.
- LOG(RPiAgc, Debug) << "after AWB, target dg " << dg << " gain " << gain
- << " target_Y " << target_Y;
- // Finally, if we're trying to reduce exposure but the target_Y is
- // "close" to 1.0, then the gain computed for that constraint will be
- // only slightly less than one, because the measured Y can never be
- // larger than 1.0. When this happens, demand a large digital gain so
- // that the exposure can be reduced, de-saturating the image much more
- // quickly (and we then approach the correct value more quickly from
- // below).
- bool desaturate = target_Y > config_.fast_reduce_threshold &&
- gain < sqrt(target_Y);
- if (desaturate)
- dg /= config_.fast_reduce_threshold;
- LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate;
- target_.total_exposure_no_dg = target_.total_exposure / dg;
- LOG(RPiAgc, Debug) << "Target total_exposure_no_dg " << target_.total_exposure_no_dg;
- return desaturate;
-}
-
-void Agc::filterExposure(bool desaturate)
-{
- double speed = config_.speed;
- // AGC adapts instantly if both shutter and gain are directly specified
- // or we're in the startup phase.
- if ((status_.fixed_shutter && status_.fixed_analogue_gain) ||
- frame_count_ <= config_.startup_frames)
- speed = 1.0;
- if (!filtered_.total_exposure) {
- filtered_.total_exposure = target_.total_exposure;
- filtered_.total_exposure_no_dg = target_.total_exposure_no_dg;
- } else {
- // If close to the result go faster, to save making so many
- // micro-adjustments on the way. (Make this customisable?)
- if (filtered_.total_exposure < 1.2 * target_.total_exposure &&
- filtered_.total_exposure > 0.8 * target_.total_exposure)
- speed = sqrt(speed);
- filtered_.total_exposure = speed * target_.total_exposure +
- filtered_.total_exposure * (1.0 - speed);
- // When desaturing, take a big jump down in exposure_no_dg,
- // which we'll hide with digital gain.
- if (desaturate)
- filtered_.total_exposure_no_dg =
- target_.total_exposure_no_dg;
- else
- filtered_.total_exposure_no_dg =
- speed * target_.total_exposure_no_dg +
- filtered_.total_exposure_no_dg * (1.0 - speed);
- }
- // We can't let the no_dg exposure deviate too far below the
- // total exposure, as there might not be enough digital gain available
- // in the ISP to hide it (which will cause nasty oscillation).
- if (filtered_.total_exposure_no_dg <
- filtered_.total_exposure * config_.fast_reduce_threshold)
- filtered_.total_exposure_no_dg = filtered_.total_exposure *
- config_.fast_reduce_threshold;
- LOG(RPiAgc, Debug) << "After filtering, total_exposure " << filtered_.total_exposure
- << " no dg " << filtered_.total_exposure_no_dg;
-}
-
-void Agc::divideUpExposure()
-{
- // Sending the fixed shutter/gain cases through the same code may seem
- // unnecessary, but it will make more sense when extend this to cover
- // variable aperture.
- Duration exposure_value = filtered_.total_exposure_no_dg;
- Duration shutter_time;
- double analogue_gain;
- shutter_time = status_.fixed_shutter
- ? status_.fixed_shutter
- : exposure_mode_->shutter[0];
- shutter_time = clipShutter(shutter_time);
- analogue_gain = status_.fixed_analogue_gain != 0.0
- ? status_.fixed_analogue_gain
- : exposure_mode_->gain[0];
- if (shutter_time * analogue_gain < exposure_value) {
- for (unsigned int stage = 1;
- stage < exposure_mode_->gain.size(); stage++) {
- if (!status_.fixed_shutter) {
- Duration stage_shutter =
- clipShutter(exposure_mode_->shutter[stage]);
- if (stage_shutter * analogue_gain >=
- exposure_value) {
- shutter_time =
- exposure_value / analogue_gain;
- break;
- }
- shutter_time = stage_shutter;
- }
- if (status_.fixed_analogue_gain == 0.0) {
- if (exposure_mode_->gain[stage] *
- shutter_time >=
- exposure_value) {
- analogue_gain =
- exposure_value / shutter_time;
- break;
- }
- analogue_gain = exposure_mode_->gain[stage];
- }
- }
- }
- LOG(RPiAgc, Debug) << "Divided up shutter and gain are " << shutter_time << " and "
- << analogue_gain;
- // Finally adjust shutter time for flicker avoidance (require both
- // shutter and gain not to be fixed).
- if (!status_.fixed_shutter && !status_.fixed_analogue_gain &&
- status_.flicker_period) {
- int flicker_periods = shutter_time / status_.flicker_period;
- if (flicker_periods) {
- Duration new_shutter_time = flicker_periods * status_.flicker_period;
- analogue_gain *= shutter_time / new_shutter_time;
- // We should still not allow the ag to go over the
- // largest value in the exposure mode. Note that this
- // may force more of the total exposure into the digital
- // gain as a side-effect.
- analogue_gain = std::min(analogue_gain,
- exposure_mode_->gain.back());
- shutter_time = new_shutter_time;
- }
- LOG(RPiAgc, Debug) << "After flicker avoidance, shutter "
- << shutter_time << " gain " << analogue_gain;
- }
- filtered_.shutter = shutter_time;
- filtered_.analogue_gain = analogue_gain;
-}
-
-void Agc::writeAndFinish(Metadata *image_metadata, bool desaturate)
-{
- status_.total_exposure_value = filtered_.total_exposure;
- status_.target_exposure_value = desaturate ? 0s : target_.total_exposure_no_dg;
- status_.shutter_time = filtered_.shutter;
- status_.analogue_gain = filtered_.analogue_gain;
- // Write to metadata as well, in case anyone wants to update the camera
- // immediately.
- image_metadata->Set("agc.status", status_);
- LOG(RPiAgc, Debug) << "Output written, total exposure requested is "
- << filtered_.total_exposure;
- LOG(RPiAgc, Debug) << "Camera exposure update: shutter time " << filtered_.shutter
- << " analogue gain " << filtered_.analogue_gain;
-}
-
-Duration Agc::clipShutter(Duration shutter)
-{
- if (max_shutter_)
- shutter = std::min(shutter, max_shutter_);
- return shutter;
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Agc(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/agc.hpp b/src/ipa/raspberrypi/controller/rpi/agc.hpp
deleted file mode 100644
index c100d312..00000000
--- a/src/ipa/raspberrypi/controller/rpi/agc.hpp
+++ /dev/null
@@ -1,139 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * agc.hpp - AGC/AEC control algorithm
- */
-#pragma once
-
-#include <vector>
-#include <mutex>
-
-#include <libcamera/base/utils.h>
-
-#include "../agc_algorithm.hpp"
-#include "../agc_status.h"
-#include "../pwl.hpp"
-
-// This is our implementation of AGC.
-
-// This is the number actually set up by the firmware, not the maximum possible
-// number (which is 16).
-
-#define AGC_STATS_SIZE 15
-
-namespace RPiController {
-
-struct AgcMeteringMode {
- double weights[AGC_STATS_SIZE];
- void Read(boost::property_tree::ptree const &params);
-};
-
-struct AgcExposureMode {
- std::vector<libcamera::utils::Duration> shutter;
- std::vector<double> gain;
- void Read(boost::property_tree::ptree const &params);
-};
-
-struct AgcConstraint {
- enum class Bound { LOWER = 0, UPPER = 1 };
- Bound bound;
- double q_lo;
- double q_hi;
- Pwl Y_target;
- void Read(boost::property_tree::ptree const &params);
-};
-
-typedef std::vector<AgcConstraint> AgcConstraintMode;
-
-struct AgcConfig {
- void Read(boost::property_tree::ptree const &params);
- std::map<std::string, AgcMeteringMode> metering_modes;
- std::map<std::string, AgcExposureMode> exposure_modes;
- std::map<std::string, AgcConstraintMode> constraint_modes;
- Pwl Y_target;
- double speed;
- uint16_t startup_frames;
- unsigned int convergence_frames;
- double max_change;
- double min_change;
- double fast_reduce_threshold;
- double speed_up_threshold;
- std::string default_metering_mode;
- std::string default_exposure_mode;
- std::string default_constraint_mode;
- double base_ev;
- libcamera::utils::Duration default_exposure_time;
- double default_analogue_gain;
-};
-
-class Agc : public AgcAlgorithm
-{
-public:
- Agc(Controller *controller);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- // AGC handles "pausing" for itself.
- bool IsPaused() const override;
- void Pause() override;
- void Resume() override;
- unsigned int GetConvergenceFrames() const override;
- void SetEv(double ev) override;
- void SetFlickerPeriod(libcamera::utils::Duration flicker_period) override;
- void SetMaxShutter(libcamera::utils::Duration max_shutter) override;
- void SetFixedShutter(libcamera::utils::Duration fixed_shutter) override;
- void SetFixedAnalogueGain(double fixed_analogue_gain) override;
- void SetMeteringMode(std::string const &metering_mode_name) override;
- void SetExposureMode(std::string const &exposure_mode_name) override;
- void SetConstraintMode(std::string const &contraint_mode_name) override;
- void SwitchMode(CameraMode const &camera_mode, Metadata *metadata) override;
- void Prepare(Metadata *image_metadata) override;
- void Process(StatisticsPtr &stats, Metadata *image_metadata) override;
-
-private:
- void updateLockStatus(DeviceStatus const &device_status);
- AgcConfig config_;
- void housekeepConfig();
- void fetchCurrentExposure(Metadata *image_metadata);
- void fetchAwbStatus(Metadata *image_metadata);
- void computeGain(bcm2835_isp_stats *statistics, Metadata *image_metadata,
- double &gain, double &target_Y);
- void computeTargetExposure(double gain);
- bool applyDigitalGain(double gain, double target_Y);
- void filterExposure(bool desaturate);
- void divideUpExposure();
- void writeAndFinish(Metadata *image_metadata, bool desaturate);
- libcamera::utils::Duration clipShutter(libcamera::utils::Duration shutter);
- AgcMeteringMode *metering_mode_;
- AgcExposureMode *exposure_mode_;
- AgcConstraintMode *constraint_mode_;
- uint64_t frame_count_;
- AwbStatus awb_;
- struct ExposureValues {
- ExposureValues();
-
- libcamera::utils::Duration shutter;
- double analogue_gain;
- libcamera::utils::Duration total_exposure;
- libcamera::utils::Duration total_exposure_no_dg; // without digital gain
- };
- ExposureValues current_; // values for the current frame
- ExposureValues target_; // calculate the values we want here
- ExposureValues filtered_; // these values are filtered towards target
- AgcStatus status_;
- int lock_count_;
- DeviceStatus last_device_status_;
- libcamera::utils::Duration last_target_exposure_;
- double last_sensitivity_; // sensitivity of the previous camera mode
- // Below here the "settings" that applications can change.
- std::string metering_mode_name_;
- std::string exposure_mode_name_;
- std::string constraint_mode_name_;
- double ev_;
- libcamera::utils::Duration flicker_period_;
- libcamera::utils::Duration max_shutter_;
- libcamera::utils::Duration fixed_shutter_;
- double fixed_analogue_gain_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp
deleted file mode 100644
index e575c14a..00000000
--- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp
+++ /dev/null
@@ -1,787 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * alsc.cpp - ALSC (auto lens shading correction) control algorithm
- */
-
-#include <math.h>
-#include <numeric>
-
-#include <libcamera/base/log.h>
-#include <libcamera/base/span.h>
-
-#include "../awb_status.h"
-#include "alsc.hpp"
-
-// Raspberry Pi ALSC (Auto Lens Shading Correction) algorithm.
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiAlsc)
-
-#define NAME "rpi.alsc"
-
-static const int X = ALSC_CELLS_X;
-static const int Y = ALSC_CELLS_Y;
-static const int XY = X * Y;
-static const double INSUFFICIENT_DATA = -1.0;
-
-Alsc::Alsc(Controller *controller)
- : Algorithm(controller)
-{
- async_abort_ = async_start_ = async_started_ = async_finished_ = false;
- async_thread_ = std::thread(std::bind(&Alsc::asyncFunc, this));
-}
-
-Alsc::~Alsc()
-{
- {
- std::lock_guard<std::mutex> lock(mutex_);
- async_abort_ = true;
- }
- async_signal_.notify_one();
- async_thread_.join();
-}
-
-char const *Alsc::Name() const
-{
- return NAME;
-}
-
-static void generate_lut(double *lut, boost::property_tree::ptree const &params)
-{
- double cstrength = params.get<double>("corner_strength", 2.0);
- if (cstrength <= 1.0)
- throw std::runtime_error("Alsc: corner_strength must be > 1.0");
- double asymmetry = params.get<double>("asymmetry", 1.0);
- if (asymmetry < 0)
- throw std::runtime_error("Alsc: asymmetry must be >= 0");
- double f1 = cstrength - 1, f2 = 1 + sqrt(cstrength);
- double R2 = X * Y / 4 * (1 + asymmetry * asymmetry);
- int num = 0;
- for (int y = 0; y < Y; y++) {
- for (int x = 0; x < X; x++) {
- double dy = y - Y / 2 + 0.5,
- dx = (x - X / 2 + 0.5) * asymmetry;
- double r2 = (dx * dx + dy * dy) / R2;
- lut[num++] =
- (f1 * r2 + f2) * (f1 * r2 + f2) /
- (f2 * f2); // this reproduces the cos^4 rule
- }
- }
-}
-
-static void read_lut(double *lut, boost::property_tree::ptree const &params)
-{
- int num = 0;
- const int max_num = XY;
- for (auto &p : params) {
- if (num == max_num)
- throw std::runtime_error(
- "Alsc: too many entries in LSC table");
- lut[num++] = p.second.get_value<double>();
- }
- if (num < max_num)
- throw std::runtime_error("Alsc: too few entries in LSC table");
-}
-
-static void read_calibrations(std::vector<AlscCalibration> &calibrations,
- boost::property_tree::ptree const &params,
- std::string const &name)
-{
- if (params.get_child_optional(name)) {
- double last_ct = 0;
- for (auto &p : params.get_child(name)) {
- double ct = p.second.get<double>("ct");
- if (ct <= last_ct)
- throw std::runtime_error(
- "Alsc: entries in " + name +
- " must be in increasing ct order");
- AlscCalibration calibration;
- calibration.ct = last_ct = ct;
- boost::property_tree::ptree const &table =
- p.second.get_child("table");
- int num = 0;
- for (auto it = table.begin(); it != table.end(); it++) {
- if (num == XY)
- throw std::runtime_error(
- "Alsc: too many values for ct " +
- std::to_string(ct) + " in " +
- name);
- calibration.table[num++] =
- it->second.get_value<double>();
- }
- if (num != XY)
- throw std::runtime_error(
- "Alsc: too few values for ct " +
- std::to_string(ct) + " in " + name);
- calibrations.push_back(calibration);
- LOG(RPiAlsc, Debug)
- << "Read " << name << " calibration for ct " << ct;
- }
- }
-}
-
-void Alsc::Read(boost::property_tree::ptree const &params)
-{
- config_.frame_period = params.get<uint16_t>("frame_period", 12);
- config_.startup_frames = params.get<uint16_t>("startup_frames", 10);
- config_.speed = params.get<double>("speed", 0.05);
- double sigma = params.get<double>("sigma", 0.01);
- config_.sigma_Cr = params.get<double>("sigma_Cr", sigma);
- config_.sigma_Cb = params.get<double>("sigma_Cb", sigma);
- config_.min_count = params.get<double>("min_count", 10.0);
- config_.min_G = params.get<uint16_t>("min_G", 50);
- config_.omega = params.get<double>("omega", 1.3);
- config_.n_iter = params.get<uint32_t>("n_iter", X + Y);
- config_.luminance_strength =
- params.get<double>("luminance_strength", 1.0);
- for (int i = 0; i < XY; i++)
- config_.luminance_lut[i] = 1.0;
- if (params.get_child_optional("corner_strength"))
- generate_lut(config_.luminance_lut, params);
- else if (params.get_child_optional("luminance_lut"))
- read_lut(config_.luminance_lut,
- params.get_child("luminance_lut"));
- else
- LOG(RPiAlsc, Warning)
- << "no luminance table - assume unity everywhere";
- read_calibrations(config_.calibrations_Cr, params, "calibrations_Cr");
- read_calibrations(config_.calibrations_Cb, params, "calibrations_Cb");
- config_.default_ct = params.get<double>("default_ct", 4500.0);
- config_.threshold = params.get<double>("threshold", 1e-3);
- config_.lambda_bound = params.get<double>("lambda_bound", 0.05);
-}
-
-static double get_ct(Metadata *metadata, double default_ct);
-static void get_cal_table(double ct,
- std::vector<AlscCalibration> const &calibrations,
- double cal_table[XY]);
-static void resample_cal_table(double const cal_table_in[XY],
- CameraMode const &camera_mode,
- double cal_table_out[XY]);
-static void compensate_lambdas_for_cal(double const cal_table[XY],
- double const old_lambdas[XY],
- double new_lambdas[XY]);
-static void add_luminance_to_tables(double results[3][Y][X],
- double const lambda_r[XY], double lambda_g,
- double const lambda_b[XY],
- double const luminance_lut[XY],
- double luminance_strength);
-
-void Alsc::Initialise()
-{
- frame_count2_ = frame_count_ = frame_phase_ = 0;
- first_time_ = true;
- ct_ = config_.default_ct;
- // The lambdas are initialised in the SwitchMode.
-}
-
-void Alsc::waitForAysncThread()
-{
- if (async_started_) {
- async_started_ = false;
- std::unique_lock<std::mutex> lock(mutex_);
- sync_signal_.wait(lock, [&] {
- return async_finished_;
- });
- async_finished_ = false;
- }
-}
-
-static bool compare_modes(CameraMode const &cm0, CameraMode const &cm1)
-{
- // Return true if the modes crop from the sensor significantly differently,
- // or if the user transform has changed.
- if (cm0.transform != cm1.transform)
- return true;
- int left_diff = abs(cm0.crop_x - cm1.crop_x);
- int top_diff = abs(cm0.crop_y - cm1.crop_y);
- int right_diff = fabs(cm0.crop_x + cm0.scale_x * cm0.width -
- cm1.crop_x - cm1.scale_x * cm1.width);
- int bottom_diff = fabs(cm0.crop_y + cm0.scale_y * cm0.height -
- cm1.crop_y - cm1.scale_y * cm1.height);
- // These thresholds are a rather arbitrary amount chosen to trigger
- // when carrying on with the previously calculated tables might be
- // worse than regenerating them (but without the adaptive algorithm).
- int threshold_x = cm0.sensor_width >> 4;
- int threshold_y = cm0.sensor_height >> 4;
- return left_diff > threshold_x || right_diff > threshold_x ||
- top_diff > threshold_y || bottom_diff > threshold_y;
-}
-
-void Alsc::SwitchMode(CameraMode const &camera_mode,
- [[maybe_unused]] Metadata *metadata)
-{
- // We're going to start over with the tables if there's any "significant"
- // change.
- bool reset_tables = first_time_ || compare_modes(camera_mode_, camera_mode);
-
- // Believe the colour temperature from the AWB, if there is one.
- ct_ = get_ct(metadata, ct_);
-
- // Ensure the other thread isn't running while we do this.
- waitForAysncThread();
-
- camera_mode_ = camera_mode;
-
- // We must resample the luminance table like we do the others, but it's
- // fixed so we can simply do it up front here.
- resample_cal_table(config_.luminance_lut, camera_mode_, luminance_table_);
-
- if (reset_tables) {
- // Upon every "table reset", arrange for something sensible to be
- // generated. Construct the tables for the previous recorded colour
- // temperature. In order to start over from scratch we initialise
- // the lambdas, but the rest of this code then echoes the code in
- // doAlsc, without the adaptive algorithm.
- for (int i = 0; i < XY; i++)
- lambda_r_[i] = lambda_b_[i] = 1.0;
- double cal_table_r[XY], cal_table_b[XY], cal_table_tmp[XY];
- get_cal_table(ct_, config_.calibrations_Cr, cal_table_tmp);
- resample_cal_table(cal_table_tmp, camera_mode_, cal_table_r);
- get_cal_table(ct_, config_.calibrations_Cb, cal_table_tmp);
- resample_cal_table(cal_table_tmp, camera_mode_, cal_table_b);
- compensate_lambdas_for_cal(cal_table_r, lambda_r_,
- async_lambda_r_);
- compensate_lambdas_for_cal(cal_table_b, lambda_b_,
- async_lambda_b_);
- add_luminance_to_tables(sync_results_, async_lambda_r_, 1.0,
- async_lambda_b_, luminance_table_,
- config_.luminance_strength);
- memcpy(prev_sync_results_, sync_results_,
- sizeof(prev_sync_results_));
- frame_phase_ = config_.frame_period; // run the algo again asap
- first_time_ = false;
- }
-}
-
-void Alsc::fetchAsyncResults()
-{
- LOG(RPiAlsc, Debug) << "Fetch ALSC results";
- async_finished_ = false;
- async_started_ = false;
- memcpy(sync_results_, async_results_, sizeof(sync_results_));
-}
-
-double get_ct(Metadata *metadata, double default_ct)
-{
- AwbStatus awb_status;
- awb_status.temperature_K = default_ct; // in case nothing found
- if (metadata->Get("awb.status", awb_status) != 0)
- LOG(RPiAlsc, Debug) << "no AWB results found, using "
- << awb_status.temperature_K;
- else
- LOG(RPiAlsc, Debug) << "AWB results found, using "
- << awb_status.temperature_K;
- return awb_status.temperature_K;
-}
-
-static void copy_stats(bcm2835_isp_stats_region regions[XY], StatisticsPtr &stats,
- AlscStatus const &status)
-{
- bcm2835_isp_stats_region *input_regions = stats->awb_stats;
- double *r_table = (double *)status.r;
- double *g_table = (double *)status.g;
- double *b_table = (double *)status.b;
- for (int i = 0; i < XY; i++) {
- regions[i].r_sum = input_regions[i].r_sum / r_table[i];
- regions[i].g_sum = input_regions[i].g_sum / g_table[i];
- regions[i].b_sum = input_regions[i].b_sum / b_table[i];
- regions[i].counted = input_regions[i].counted;
- // (don't care about the uncounted value)
- }
-}
-
-void Alsc::restartAsync(StatisticsPtr &stats, Metadata *image_metadata)
-{
- LOG(RPiAlsc, Debug) << "Starting ALSC calculation";
- // Get the current colour temperature. It's all we need from the
- // metadata. Default to the last CT value (which could be the default).
- ct_ = get_ct(image_metadata, ct_);
- // We have to copy the statistics here, dividing out our best guess of
- // the LSC table that the pipeline applied to them.
- AlscStatus alsc_status;
- if (image_metadata->Get("alsc.status", alsc_status) != 0) {
- LOG(RPiAlsc, Warning)
- << "No ALSC status found for applied gains!";
- for (int y = 0; y < Y; y++)
- for (int x = 0; x < X; x++) {
- alsc_status.r[y][x] = 1.0;
- alsc_status.g[y][x] = 1.0;
- alsc_status.b[y][x] = 1.0;
- }
- }
- copy_stats(statistics_, stats, alsc_status);
- frame_phase_ = 0;
- async_started_ = true;
- {
- std::lock_guard<std::mutex> lock(mutex_);
- async_start_ = true;
- }
- async_signal_.notify_one();
-}
-
-void Alsc::Prepare(Metadata *image_metadata)
-{
- // Count frames since we started, and since we last poked the async
- // thread.
- if (frame_count_ < (int)config_.startup_frames)
- frame_count_++;
- double speed = frame_count_ < (int)config_.startup_frames
- ? 1.0
- : config_.speed;
- LOG(RPiAlsc, Debug)
- << "frame_count " << frame_count_ << " speed " << speed;
- {
- std::unique_lock<std::mutex> lock(mutex_);
- if (async_started_ && async_finished_)
- fetchAsyncResults();
- }
- // Apply IIR filter to results and program into the pipeline.
- double *ptr = (double *)sync_results_,
- *pptr = (double *)prev_sync_results_;
- for (unsigned int i = 0;
- i < sizeof(sync_results_) / sizeof(double); i++)
- pptr[i] = speed * ptr[i] + (1.0 - speed) * pptr[i];
- // Put output values into status metadata.
- AlscStatus status;
- memcpy(status.r, prev_sync_results_[0], sizeof(status.r));
- memcpy(status.g, prev_sync_results_[1], sizeof(status.g));
- memcpy(status.b, prev_sync_results_[2], sizeof(status.b));
- image_metadata->Set("alsc.status", status);
-}
-
-void Alsc::Process(StatisticsPtr &stats, Metadata *image_metadata)
-{
- // Count frames since we started, and since we last poked the async
- // thread.
- if (frame_phase_ < (int)config_.frame_period)
- frame_phase_++;
- if (frame_count2_ < (int)config_.startup_frames)
- frame_count2_++;
- LOG(RPiAlsc, Debug) << "frame_phase " << frame_phase_;
- if (frame_phase_ >= (int)config_.frame_period ||
- frame_count2_ < (int)config_.startup_frames) {
- if (async_started_ == false)
- restartAsync(stats, image_metadata);
- }
-}
-
-void Alsc::asyncFunc()
-{
- while (true) {
- {
- std::unique_lock<std::mutex> lock(mutex_);
- async_signal_.wait(lock, [&] {
- return async_start_ || async_abort_;
- });
- async_start_ = false;
- if (async_abort_)
- break;
- }
- doAlsc();
- {
- std::lock_guard<std::mutex> lock(mutex_);
- async_finished_ = true;
- }
- sync_signal_.notify_one();
- }
-}
-
-void get_cal_table(double ct, std::vector<AlscCalibration> const &calibrations,
- double cal_table[XY])
-{
- if (calibrations.empty()) {
- for (int i = 0; i < XY; i++)
- cal_table[i] = 1.0;
- LOG(RPiAlsc, Debug) << "no calibrations found";
- } else if (ct <= calibrations.front().ct) {
- memcpy(cal_table, calibrations.front().table,
- XY * sizeof(double));
- LOG(RPiAlsc, Debug) << "using calibration for "
- << calibrations.front().ct;
- } else if (ct >= calibrations.back().ct) {
- memcpy(cal_table, calibrations.back().table,
- XY * sizeof(double));
- LOG(RPiAlsc, Debug) << "using calibration for "
- << calibrations.back().ct;
- } else {
- int idx = 0;
- while (ct > calibrations[idx + 1].ct)
- idx++;
- double ct0 = calibrations[idx].ct,
- ct1 = calibrations[idx + 1].ct;
- LOG(RPiAlsc, Debug)
- << "ct is " << ct << ", interpolating between "
- << ct0 << " and " << ct1;
- for (int i = 0; i < XY; i++)
- cal_table[i] =
- (calibrations[idx].table[i] * (ct1 - ct) +
- calibrations[idx + 1].table[i] * (ct - ct0)) /
- (ct1 - ct0);
- }
-}
-
-void resample_cal_table(double const cal_table_in[XY],
- CameraMode const &camera_mode, double cal_table_out[XY])
-{
- // Precalculate and cache the x sampling locations and phases to save
- // recomputing them on every row.
- int x_lo[X], x_hi[X];
- double xf[X];
- double scale_x = camera_mode.sensor_width /
- (camera_mode.width * camera_mode.scale_x);
- double x_off = camera_mode.crop_x / (double)camera_mode.sensor_width;
- double x = .5 / scale_x + x_off * X - .5;
- double x_inc = 1 / scale_x;
- for (int i = 0; i < X; i++, x += x_inc) {
- x_lo[i] = floor(x);
- xf[i] = x - x_lo[i];
- x_hi[i] = std::min(x_lo[i] + 1, X - 1);
- x_lo[i] = std::max(x_lo[i], 0);
- if (!!(camera_mode.transform & libcamera::Transform::HFlip)) {
- x_lo[i] = X - 1 - x_lo[i];
- x_hi[i] = X - 1 - x_hi[i];
- }
- }
- // Now march over the output table generating the new values.
- double scale_y = camera_mode.sensor_height /
- (camera_mode.height * camera_mode.scale_y);
- double y_off = camera_mode.crop_y / (double)camera_mode.sensor_height;
- double y = .5 / scale_y + y_off * Y - .5;
- double y_inc = 1 / scale_y;
- for (int j = 0; j < Y; j++, y += y_inc) {
- int y_lo = floor(y);
- double yf = y - y_lo;
- int y_hi = std::min(y_lo + 1, Y - 1);
- y_lo = std::max(y_lo, 0);
- if (!!(camera_mode.transform & libcamera::Transform::VFlip)) {
- y_lo = Y - 1 - y_lo;
- y_hi = Y - 1 - y_hi;
- }
- double const *row_above = cal_table_in + X * y_lo;
- double const *row_below = cal_table_in + X * y_hi;
- for (int i = 0; i < X; i++) {
- double above = row_above[x_lo[i]] * (1 - xf[i]) +
- row_above[x_hi[i]] * xf[i];
- double below = row_below[x_lo[i]] * (1 - xf[i]) +
- row_below[x_hi[i]] * xf[i];
- *(cal_table_out++) = above * (1 - yf) + below * yf;
- }
- }
-}
-
-// Calculate chrominance statistics (R/G and B/G) for each region.
-static_assert(XY == AWB_REGIONS, "ALSC/AWB statistics region mismatch");
-static void calculate_Cr_Cb(bcm2835_isp_stats_region *awb_region, double Cr[XY],
- double Cb[XY], uint32_t min_count, uint16_t min_G)
-{
- for (int i = 0; i < XY; i++) {
- bcm2835_isp_stats_region &zone = awb_region[i];
- if (zone.counted <= min_count ||
- zone.g_sum / zone.counted <= min_G) {
- Cr[i] = Cb[i] = INSUFFICIENT_DATA;
- continue;
- }
- Cr[i] = zone.r_sum / (double)zone.g_sum;
- Cb[i] = zone.b_sum / (double)zone.g_sum;
- }
-}
-
-static void apply_cal_table(double const cal_table[XY], double C[XY])
-{
- for (int i = 0; i < XY; i++)
- if (C[i] != INSUFFICIENT_DATA)
- C[i] *= cal_table[i];
-}
-
-void compensate_lambdas_for_cal(double const cal_table[XY],
- double const old_lambdas[XY],
- double new_lambdas[XY])
-{
- double min_new_lambda = std::numeric_limits<double>::max();
- for (int i = 0; i < XY; i++) {
- new_lambdas[i] = old_lambdas[i] * cal_table[i];
- min_new_lambda = std::min(min_new_lambda, new_lambdas[i]);
- }
- for (int i = 0; i < XY; i++)
- new_lambdas[i] /= min_new_lambda;
-}
-
-[[maybe_unused]] static void print_cal_table(double const C[XY])
-{
- printf("table: [\n");
- for (int j = 0; j < Y; j++) {
- for (int i = 0; i < X; i++) {
- printf("%5.3f", 1.0 / C[j * X + i]);
- if (i != X - 1 || j != Y - 1)
- printf(",");
- }
- printf("\n");
- }
- printf("]\n");
-}
-
-// Compute weight out of 1.0 which reflects how similar we wish to make the
-// colours of these two regions.
-static double compute_weight(double C_i, double C_j, double sigma)
-{
- if (C_i == INSUFFICIENT_DATA || C_j == INSUFFICIENT_DATA)
- return 0;
- double diff = (C_i - C_j) / sigma;
- return exp(-diff * diff / 2);
-}
-
-// Compute all weights.
-static void compute_W(double const C[XY], double sigma, double W[XY][4])
-{
- for (int i = 0; i < XY; i++) {
- // Start with neighbour above and go clockwise.
- W[i][0] = i >= X ? compute_weight(C[i], C[i - X], sigma) : 0;
- W[i][1] = i % X < X - 1 ? compute_weight(C[i], C[i + 1], sigma)
- : 0;
- W[i][2] =
- i < XY - X ? compute_weight(C[i], C[i + X], sigma) : 0;
- W[i][3] = i % X ? compute_weight(C[i], C[i - 1], sigma) : 0;
- }
-}
-
-// Compute M, the large but sparse matrix such that M * lambdas = 0.
-static void construct_M(double const C[XY], double const W[XY][4],
- double M[XY][4])
-{
- double epsilon = 0.001;
- for (int i = 0; i < XY; i++) {
- // Note how, if C[i] == INSUFFICIENT_DATA, the weights will all
- // be zero so the equation is still set up correctly.
- int m = !!(i >= X) + !!(i % X < X - 1) + !!(i < XY - X) +
- !!(i % X); // total number of neighbours
- // we'll divide the diagonal out straight away
- double diagonal =
- (epsilon + W[i][0] + W[i][1] + W[i][2] + W[i][3]) *
- C[i];
- M[i][0] = i >= X ? (W[i][0] * C[i - X] + epsilon / m * C[i]) /
- diagonal
- : 0;
- M[i][1] = i % X < X - 1
- ? (W[i][1] * C[i + 1] + epsilon / m * C[i]) /
- diagonal
- : 0;
- M[i][2] = i < XY - X
- ? (W[i][2] * C[i + X] + epsilon / m * C[i]) /
- diagonal
- : 0;
- M[i][3] = i % X ? (W[i][3] * C[i - 1] + epsilon / m * C[i]) /
- diagonal
- : 0;
- }
-}
-
-// In the compute_lambda_ functions, note that the matrix coefficients for the
-// left/right neighbours are zero down the left/right edges, so we don't need
-// need to test the i value to exclude them.
-static double compute_lambda_bottom(int i, double const M[XY][4],
- double lambda[XY])
-{
- return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X] +
- M[i][3] * lambda[i - 1];
-}
-static double compute_lambda_bottom_start(int i, double const M[XY][4],
- double lambda[XY])
-{
- return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X];
-}
-static double compute_lambda_interior(int i, double const M[XY][4],
- double lambda[XY])
-{
- return M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +
- M[i][2] * lambda[i + X] + M[i][3] * lambda[i - 1];
-}
-static double compute_lambda_top(int i, double const M[XY][4],
- double lambda[XY])
-{
- return M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] +
- M[i][3] * lambda[i - 1];
-}
-static double compute_lambda_top_end(int i, double const M[XY][4],
- double lambda[XY])
-{
- return M[i][0] * lambda[i - X] + M[i][3] * lambda[i - 1];
-}
-
-// Gauss-Seidel iteration with over-relaxation.
-static double gauss_seidel2_SOR(double const M[XY][4], double omega,
- double lambda[XY], double lambda_bound)
-{
- const double min = 1 - lambda_bound, max = 1 + lambda_bound;
- double old_lambda[XY];
- int i;
- for (i = 0; i < XY; i++)
- old_lambda[i] = lambda[i];
- lambda[0] = compute_lambda_bottom_start(0, M, lambda);
- lambda[0] = std::clamp(lambda[0], min, max);
- for (i = 1; i < X; i++) {
- lambda[i] = compute_lambda_bottom(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i < XY - X; i++) {
- lambda[i] = compute_lambda_interior(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i < XY - 1; i++) {
- lambda[i] = compute_lambda_top(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- lambda[i] = compute_lambda_top_end(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- // Also solve the system from bottom to top, to help spread the updates
- // better.
- lambda[i] = compute_lambda_top_end(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- for (i = XY - 2; i >= XY - X; i--) {
- lambda[i] = compute_lambda_top(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i >= X; i--) {
- lambda[i] = compute_lambda_interior(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i >= 1; i--) {
- lambda[i] = compute_lambda_bottom(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- lambda[0] = compute_lambda_bottom_start(0, M, lambda);
- lambda[0] = std::clamp(lambda[0], min, max);
- double max_diff = 0;
- for (i = 0; i < XY; i++) {
- lambda[i] = old_lambda[i] + (lambda[i] - old_lambda[i]) * omega;
- if (fabs(lambda[i] - old_lambda[i]) > fabs(max_diff))
- max_diff = lambda[i] - old_lambda[i];
- }
- return max_diff;
-}
-
-// Normalise the values so that the smallest value is 1.
-static void normalise(double *ptr, size_t n)
-{
- double minval = ptr[0];
- for (size_t i = 1; i < n; i++)
- minval = std::min(minval, ptr[i]);
- for (size_t i = 0; i < n; i++)
- ptr[i] /= minval;
-}
-
-// Rescale the values so that the average value is 1.
-static void reaverage(Span<double> data)
-{
- double sum = std::accumulate(data.begin(), data.end(), 0.0);
- double ratio = 1 / (sum / data.size());
- for (double &d : data)
- d *= ratio;
-}
-
-static void run_matrix_iterations(double const C[XY], double lambda[XY],
- double const W[XY][4], double omega,
- int n_iter, double threshold, double lambda_bound)
-{
- double M[XY][4];
- construct_M(C, W, M);
- double last_max_diff = std::numeric_limits<double>::max();
- for (int i = 0; i < n_iter; i++) {
- double max_diff = fabs(gauss_seidel2_SOR(M, omega, lambda, lambda_bound));
- if (max_diff < threshold) {
- LOG(RPiAlsc, Debug)
- << "Stop after " << i + 1 << " iterations";
- break;
- }
- // this happens very occasionally (so make a note), though
- // doesn't seem to matter
- if (max_diff > last_max_diff)
- LOG(RPiAlsc, Debug)
- << "Iteration " << i << ": max_diff gone up "
- << last_max_diff << " to " << max_diff;
- last_max_diff = max_diff;
- }
- // We're going to normalise the lambdas so the total average is 1.
- reaverage({ lambda, XY });
-}
-
-static void add_luminance_rb(double result[XY], double const lambda[XY],
- double const luminance_lut[XY],
- double luminance_strength)
-{
- for (int i = 0; i < XY; i++)
- result[i] = lambda[i] *
- ((luminance_lut[i] - 1) * luminance_strength + 1);
-}
-
-static void add_luminance_g(double result[XY], double lambda,
- double const luminance_lut[XY],
- double luminance_strength)
-{
- for (int i = 0; i < XY; i++)
- result[i] = lambda *
- ((luminance_lut[i] - 1) * luminance_strength + 1);
-}
-
-void add_luminance_to_tables(double results[3][Y][X], double const lambda_r[XY],
- double lambda_g, double const lambda_b[XY],
- double const luminance_lut[XY],
- double luminance_strength)
-{
- add_luminance_rb((double *)results[0], lambda_r, luminance_lut,
- luminance_strength);
- add_luminance_g((double *)results[1], lambda_g, luminance_lut,
- luminance_strength);
- add_luminance_rb((double *)results[2], lambda_b, luminance_lut,
- luminance_strength);
- normalise((double *)results, 3 * XY);
-}
-
-void Alsc::doAlsc()
-{
- double Cr[XY], Cb[XY], Wr[XY][4], Wb[XY][4], cal_table_r[XY],
- cal_table_b[XY], cal_table_tmp[XY];
- // Calculate our R/B ("Cr"/"Cb") colour statistics, and assess which are
- // usable.
- calculate_Cr_Cb(statistics_, Cr, Cb, config_.min_count, config_.min_G);
- // Fetch the new calibrations (if any) for this CT. Resample them in
- // case the camera mode is not full-frame.
- get_cal_table(ct_, config_.calibrations_Cr, cal_table_tmp);
- resample_cal_table(cal_table_tmp, camera_mode_, cal_table_r);
- get_cal_table(ct_, config_.calibrations_Cb, cal_table_tmp);
- resample_cal_table(cal_table_tmp, camera_mode_, cal_table_b);
- // You could print out the cal tables for this image here, if you're
- // tuning the algorithm...
- // Apply any calibration to the statistics, so the adaptive algorithm
- // makes only the extra adjustments.
- apply_cal_table(cal_table_r, Cr);
- apply_cal_table(cal_table_b, Cb);
- // Compute weights between zones.
- compute_W(Cr, config_.sigma_Cr, Wr);
- compute_W(Cb, config_.sigma_Cb, Wb);
- // Run Gauss-Seidel iterations over the resulting matrix, for R and B.
- run_matrix_iterations(Cr, lambda_r_, Wr, config_.omega, config_.n_iter,
- config_.threshold, config_.lambda_bound);
- run_matrix_iterations(Cb, lambda_b_, Wb, config_.omega, config_.n_iter,
- config_.threshold, config_.lambda_bound);
- // Fold the calibrated gains into our final lambda values. (Note that on
- // the next run, we re-start with the lambda values that don't have the
- // calibration gains included.)
- compensate_lambdas_for_cal(cal_table_r, lambda_r_, async_lambda_r_);
- compensate_lambdas_for_cal(cal_table_b, lambda_b_, async_lambda_b_);
- // Fold in the luminance table at the appropriate strength.
- add_luminance_to_tables(async_results_, async_lambda_r_, 1.0,
- async_lambda_b_, luminance_table_,
- config_.luminance_strength);
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Alsc(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.hpp b/src/ipa/raspberrypi/controller/rpi/alsc.hpp
deleted file mode 100644
index d1dbe0d1..00000000
--- a/src/ipa/raspberrypi/controller/rpi/alsc.hpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * alsc.hpp - ALSC (auto lens shading correction) control algorithm
- */
-#pragma once
-
-#include <mutex>
-#include <condition_variable>
-#include <thread>
-
-#include "../algorithm.hpp"
-#include "../alsc_status.h"
-
-namespace RPiController {
-
-// Algorithm to generate automagic LSC (Lens Shading Correction) tables.
-
-struct AlscCalibration {
- double ct;
- double table[ALSC_CELLS_X * ALSC_CELLS_Y];
-};
-
-struct AlscConfig {
- // Only repeat the ALSC calculation every "this many" frames
- uint16_t frame_period;
- // number of initial frames for which speed taken as 1.0 (maximum)
- uint16_t startup_frames;
- // IIR filter speed applied to algorithm results
- double speed;
- double sigma_Cr;
- double sigma_Cb;
- double min_count;
- uint16_t min_G;
- double omega;
- uint32_t n_iter;
- double luminance_lut[ALSC_CELLS_X * ALSC_CELLS_Y];
- double luminance_strength;
- std::vector<AlscCalibration> calibrations_Cr;
- std::vector<AlscCalibration> calibrations_Cb;
- double default_ct; // colour temperature if no metadata found
- double threshold; // iteration termination threshold
- double lambda_bound; // upper/lower bound for lambda from a value of 1
-};
-
-class Alsc : public Algorithm
-{
-public:
- Alsc(Controller *controller = NULL);
- ~Alsc();
- char const *Name() const override;
- void Initialise() override;
- void SwitchMode(CameraMode const &camera_mode, Metadata *metadata) override;
- void Read(boost::property_tree::ptree const &params) override;
- void Prepare(Metadata *image_metadata) override;
- void Process(StatisticsPtr &stats, Metadata *image_metadata) override;
-
-private:
- // configuration is read-only, and available to both threads
- AlscConfig config_;
- bool first_time_;
- CameraMode camera_mode_;
- double luminance_table_[ALSC_CELLS_X * ALSC_CELLS_Y];
- std::thread async_thread_;
- void asyncFunc(); // asynchronous thread function
- std::mutex mutex_;
- // condvar for async thread to wait on
- std::condition_variable async_signal_;
- // condvar for synchronous thread to wait on
- std::condition_variable sync_signal_;
- // for sync thread to check if async thread finished (requires mutex)
- bool async_finished_;
- // for async thread to check if it's been told to run (requires mutex)
- bool async_start_;
- // for async thread to check if it's been told to quit (requires mutex)
- bool async_abort_;
-
- // The following are only for the synchronous thread to use:
- // for sync thread to note its has asked async thread to run
- bool async_started_;
- // counts up to frame_period before restarting the async thread
- int frame_phase_;
- // counts up to startup_frames
- int frame_count_;
- // counts up to startup_frames for Process function
- int frame_count2_;
- double sync_results_[3][ALSC_CELLS_Y][ALSC_CELLS_X];
- double prev_sync_results_[3][ALSC_CELLS_Y][ALSC_CELLS_X];
- void waitForAysncThread();
- // The following are for the asynchronous thread to use, though the main
- // thread can set/reset them if the async thread is known to be idle:
- void restartAsync(StatisticsPtr &stats, Metadata *image_metadata);
- // copy out the results from the async thread so that it can be restarted
- void fetchAsyncResults();
- double ct_;
- bcm2835_isp_stats_region statistics_[ALSC_CELLS_Y * ALSC_CELLS_X];
- double async_results_[3][ALSC_CELLS_Y][ALSC_CELLS_X];
- double async_lambda_r_[ALSC_CELLS_X * ALSC_CELLS_Y];
- double async_lambda_b_[ALSC_CELLS_X * ALSC_CELLS_Y];
- void doAlsc();
- double lambda_r_[ALSC_CELLS_X * ALSC_CELLS_Y];
- double lambda_b_[ALSC_CELLS_X * ALSC_CELLS_Y];
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/awb.cpp b/src/ipa/raspberrypi/controller/rpi/awb.cpp
deleted file mode 100644
index d4c93447..00000000
--- a/src/ipa/raspberrypi/controller/rpi/awb.cpp
+++ /dev/null
@@ -1,667 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * awb.cpp - AWB control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../lux_status.h"
-
-#include "awb.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiAwb)
-
-#define NAME "rpi.awb"
-
-#define AWB_STATS_SIZE_X DEFAULT_AWB_REGIONS_X
-#define AWB_STATS_SIZE_Y DEFAULT_AWB_REGIONS_Y
-
-// todo - the locking in this algorithm needs some tidying up as has been done
-// elsewhere (ALSC and AGC).
-
-void AwbMode::Read(boost::property_tree::ptree const &params)
-{
- ct_lo = params.get<double>("lo");
- ct_hi = params.get<double>("hi");
-}
-
-void AwbPrior::Read(boost::property_tree::ptree const &params)
-{
- lux = params.get<double>("lux");
- prior.Read(params.get_child("prior"));
-}
-
-static void read_ct_curve(Pwl &ct_r, Pwl &ct_b,
- boost::property_tree::ptree const &params)
-{
- int num = 0;
- for (auto it = params.begin(); it != params.end(); it++) {
- double ct = it->second.get_value<double>();
- assert(it == params.begin() || ct != ct_r.Domain().end);
- if (++it == params.end())
- throw std::runtime_error(
- "AwbConfig: incomplete CT curve entry");
- ct_r.Append(ct, it->second.get_value<double>());
- if (++it == params.end())
- throw std::runtime_error(
- "AwbConfig: incomplete CT curve entry");
- ct_b.Append(ct, it->second.get_value<double>());
- num++;
- }
- if (num < 2)
- throw std::runtime_error(
- "AwbConfig: insufficient points in CT curve");
-}
-
-void AwbConfig::Read(boost::property_tree::ptree const &params)
-{
- bayes = params.get<int>("bayes", 1);
- frame_period = params.get<uint16_t>("frame_period", 10);
- startup_frames = params.get<uint16_t>("startup_frames", 10);
- convergence_frames = params.get<unsigned int>("convergence_frames", 3);
- speed = params.get<double>("speed", 0.05);
- if (params.get_child_optional("ct_curve"))
- read_ct_curve(ct_r, ct_b, params.get_child("ct_curve"));
- if (params.get_child_optional("priors")) {
- for (auto &p : params.get_child("priors")) {
- AwbPrior prior;
- prior.Read(p.second);
- if (!priors.empty() && prior.lux <= priors.back().lux)
- throw std::runtime_error(
- "AwbConfig: Prior must be ordered in increasing lux value");
- priors.push_back(prior);
- }
- if (priors.empty())
- throw std::runtime_error(
- "AwbConfig: no AWB priors configured");
- }
- if (params.get_child_optional("modes")) {
- for (auto &p : params.get_child("modes")) {
- modes[p.first].Read(p.second);
- if (default_mode == nullptr)
- default_mode = &modes[p.first];
- }
- if (default_mode == nullptr)
- throw std::runtime_error(
- "AwbConfig: no AWB modes configured");
- }
- min_pixels = params.get<double>("min_pixels", 16.0);
- min_G = params.get<uint16_t>("min_G", 32);
- min_regions = params.get<uint32_t>("min_regions", 10);
- delta_limit = params.get<double>("delta_limit", 0.2);
- coarse_step = params.get<double>("coarse_step", 0.2);
- transverse_pos = params.get<double>("transverse_pos", 0.01);
- transverse_neg = params.get<double>("transverse_neg", 0.01);
- if (transverse_pos <= 0 || transverse_neg <= 0)
- throw std::runtime_error(
- "AwbConfig: transverse_pos/neg must be > 0");
- sensitivity_r = params.get<double>("sensitivity_r", 1.0);
- sensitivity_b = params.get<double>("sensitivity_b", 1.0);
- if (bayes) {
- if (ct_r.Empty() || ct_b.Empty() || priors.empty() ||
- default_mode == nullptr) {
- LOG(RPiAwb, Warning)
- << "Bayesian AWB mis-configured - switch to Grey method";
- bayes = false;
- }
- }
- fast = params.get<int>(
- "fast", bayes); // default to fast for Bayesian, otherwise slow
- whitepoint_r = params.get<double>("whitepoint_r", 0.0);
- whitepoint_b = params.get<double>("whitepoint_b", 0.0);
- if (bayes == false)
- sensitivity_r = sensitivity_b =
- 1.0; // nor do sensitivities make any sense
-}
-
-Awb::Awb(Controller *controller)
- : AwbAlgorithm(controller)
-{
- async_abort_ = async_start_ = async_started_ = async_finished_ = false;
- mode_ = nullptr;
- manual_r_ = manual_b_ = 0.0;
- first_switch_mode_ = true;
- async_thread_ = std::thread(std::bind(&Awb::asyncFunc, this));
-}
-
-Awb::~Awb()
-{
- {
- std::lock_guard<std::mutex> lock(mutex_);
- async_abort_ = true;
- }
- async_signal_.notify_one();
- async_thread_.join();
-}
-
-char const *Awb::Name() const
-{
- return NAME;
-}
-
-void Awb::Read(boost::property_tree::ptree const &params)
-{
- config_.Read(params);
-}
-
-void Awb::Initialise()
-{
- frame_count_ = frame_phase_ = 0;
- // Put something sane into the status that we are filtering towards,
- // just in case the first few frames don't have anything meaningful in
- // them.
- if (!config_.ct_r.Empty() && !config_.ct_b.Empty()) {
- sync_results_.temperature_K = config_.ct_r.Domain().Clip(4000);
- sync_results_.gain_r =
- 1.0 / config_.ct_r.Eval(sync_results_.temperature_K);
- sync_results_.gain_g = 1.0;
- sync_results_.gain_b =
- 1.0 / config_.ct_b.Eval(sync_results_.temperature_K);
- } else {
- // random values just to stop the world blowing up
- sync_results_.temperature_K = 4500;
- sync_results_.gain_r = sync_results_.gain_g =
- sync_results_.gain_b = 1.0;
- }
- prev_sync_results_ = sync_results_;
- async_results_ = sync_results_;
-}
-
-bool Awb::IsPaused() const
-{
- return false;
-}
-
-void Awb::Pause()
-{
- // "Pause" by fixing everything to the most recent values.
- manual_r_ = sync_results_.gain_r = prev_sync_results_.gain_r;
- manual_b_ = sync_results_.gain_b = prev_sync_results_.gain_b;
- sync_results_.gain_g = prev_sync_results_.gain_g;
- sync_results_.temperature_K = prev_sync_results_.temperature_K;
-}
-
-void Awb::Resume()
-{
- manual_r_ = 0.0;
- manual_b_ = 0.0;
-}
-
-unsigned int Awb::GetConvergenceFrames() const
-{
- // If not in auto mode, there is no convergence
- // to happen, so no need to drop any frames - return zero.
- if (!isAutoEnabled())
- return 0;
- else
- return config_.convergence_frames;
-}
-
-void Awb::SetMode(std::string const &mode_name)
-{
- mode_name_ = mode_name;
-}
-
-void Awb::SetManualGains(double manual_r, double manual_b)
-{
- // If any of these are 0.0, we swich back to auto.
- manual_r_ = manual_r;
- manual_b_ = manual_b;
- // If not in auto mode, set these values into the sync_results which
- // means that Prepare() will adopt them immediately.
- if (!isAutoEnabled()) {
- sync_results_.gain_r = prev_sync_results_.gain_r = manual_r_;
- sync_results_.gain_g = prev_sync_results_.gain_g = 1.0;
- sync_results_.gain_b = prev_sync_results_.gain_b = manual_b_;
- }
-}
-
-void Awb::SwitchMode([[maybe_unused]] CameraMode const &camera_mode,
- Metadata *metadata)
-{
- // On the first mode switch we'll have no meaningful colour
- // temperature, so try to dead reckon one if in manual mode.
- if (!isAutoEnabled() && first_switch_mode_ && config_.bayes) {
- Pwl ct_r_inverse = config_.ct_r.Inverse();
- Pwl ct_b_inverse = config_.ct_b.Inverse();
- double ct_r = ct_r_inverse.Eval(ct_r_inverse.Domain().Clip(1 / manual_r_));
- double ct_b = ct_b_inverse.Eval(ct_b_inverse.Domain().Clip(1 / manual_b_));
- prev_sync_results_.temperature_K = (ct_r + ct_b) / 2;
- sync_results_.temperature_K = prev_sync_results_.temperature_K;
- }
- // Let other algorithms know the current white balance values.
- metadata->Set("awb.status", prev_sync_results_);
- first_switch_mode_ = false;
-}
-
-bool Awb::isAutoEnabled() const
-{
- return manual_r_ == 0.0 || manual_b_ == 0.0;
-}
-
-void Awb::fetchAsyncResults()
-{
- LOG(RPiAwb, Debug) << "Fetch AWB results";
- async_finished_ = false;
- async_started_ = false;
- // It's possible manual gains could be set even while the async
- // thread was running, so only copy the results if still in auto mode.
- if (isAutoEnabled())
- sync_results_ = async_results_;
-}
-
-void Awb::restartAsync(StatisticsPtr &stats, double lux)
-{
- LOG(RPiAwb, Debug) << "Starting AWB calculation";
- // this makes a new reference which belongs to the asynchronous thread
- statistics_ = stats;
- // store the mode as it could technically change
- auto m = config_.modes.find(mode_name_);
- mode_ = m != config_.modes.end()
- ? &m->second
- : (mode_ == nullptr ? config_.default_mode : mode_);
- lux_ = lux;
- frame_phase_ = 0;
- async_started_ = true;
- size_t len = mode_name_.copy(async_results_.mode,
- sizeof(async_results_.mode) - 1);
- async_results_.mode[len] = '\0';
- {
- std::lock_guard<std::mutex> lock(mutex_);
- async_start_ = true;
- }
- async_signal_.notify_one();
-}
-
-void Awb::Prepare(Metadata *image_metadata)
-{
- if (frame_count_ < (int)config_.startup_frames)
- frame_count_++;
- double speed = frame_count_ < (int)config_.startup_frames
- ? 1.0
- : config_.speed;
- LOG(RPiAwb, Debug)
- << "frame_count " << frame_count_ << " speed " << speed;
- {
- std::unique_lock<std::mutex> lock(mutex_);
- if (async_started_ && async_finished_)
- fetchAsyncResults();
- }
- // Finally apply IIR filter to results and put into metadata.
- memcpy(prev_sync_results_.mode, sync_results_.mode,
- sizeof(prev_sync_results_.mode));
- prev_sync_results_.temperature_K =
- speed * sync_results_.temperature_K +
- (1.0 - speed) * prev_sync_results_.temperature_K;
- prev_sync_results_.gain_r = speed * sync_results_.gain_r +
- (1.0 - speed) * prev_sync_results_.gain_r;
- prev_sync_results_.gain_g = speed * sync_results_.gain_g +
- (1.0 - speed) * prev_sync_results_.gain_g;
- prev_sync_results_.gain_b = speed * sync_results_.gain_b +
- (1.0 - speed) * prev_sync_results_.gain_b;
- image_metadata->Set("awb.status", prev_sync_results_);
- LOG(RPiAwb, Debug)
- << "Using AWB gains r " << prev_sync_results_.gain_r << " g "
- << prev_sync_results_.gain_g << " b "
- << prev_sync_results_.gain_b;
-}
-
-void Awb::Process(StatisticsPtr &stats, Metadata *image_metadata)
-{
- // Count frames since we last poked the async thread.
- if (frame_phase_ < (int)config_.frame_period)
- frame_phase_++;
- LOG(RPiAwb, Debug) << "frame_phase " << frame_phase_;
- // We do not restart the async thread if we're not in auto mode.
- if (isAutoEnabled() &&
- (frame_phase_ >= (int)config_.frame_period ||
- frame_count_ < (int)config_.startup_frames)) {
- // Update any settings and any image metadata that we need.
- struct LuxStatus lux_status = {};
- lux_status.lux = 400; // in case no metadata
- if (image_metadata->Get("lux.status", lux_status) != 0)
- LOG(RPiAwb, Debug) << "No lux metadata found";
- LOG(RPiAwb, Debug) << "Awb lux value is " << lux_status.lux;
-
- if (async_started_ == false)
- restartAsync(stats, lux_status.lux);
- }
-}
-
-void Awb::asyncFunc()
-{
- while (true) {
- {
- std::unique_lock<std::mutex> lock(mutex_);
- async_signal_.wait(lock, [&] {
- return async_start_ || async_abort_;
- });
- async_start_ = false;
- if (async_abort_)
- break;
- }
- doAwb();
- {
- std::lock_guard<std::mutex> lock(mutex_);
- async_finished_ = true;
- }
- sync_signal_.notify_one();
- }
-}
-
-static void generate_stats(std::vector<Awb::RGB> &zones,
- bcm2835_isp_stats_region *stats, double min_pixels,
- double min_G)
-{
- for (int i = 0; i < AWB_STATS_SIZE_X * AWB_STATS_SIZE_Y; i++) {
- Awb::RGB zone;
- double counted = stats[i].counted;
- if (counted >= min_pixels) {
- zone.G = stats[i].g_sum / counted;
- if (zone.G >= min_G) {
- zone.R = stats[i].r_sum / counted;
- zone.B = stats[i].b_sum / counted;
- zones.push_back(zone);
- }
- }
- }
-}
-
-void Awb::prepareStats()
-{
- zones_.clear();
- // LSC has already been applied to the stats in this pipeline, so stop
- // any LSC compensation. We also ignore config_.fast in this version.
- generate_stats(zones_, statistics_->awb_stats, config_.min_pixels,
- config_.min_G);
- // we're done with these; we may as well relinquish our hold on the
- // pointer.
- statistics_.reset();
- // apply sensitivities, so values appear to come from our "canonical"
- // sensor.
- for (auto &zone : zones_)
- zone.R *= config_.sensitivity_r,
- zone.B *= config_.sensitivity_b;
-}
-
-double Awb::computeDelta2Sum(double gain_r, double gain_b)
-{
- // Compute the sum of the squared colour error (non-greyness) as it
- // appears in the log likelihood equation.
- double delta2_sum = 0;
- for (auto &z : zones_) {
- double delta_r = gain_r * z.R - 1 - config_.whitepoint_r;
- double delta_b = gain_b * z.B - 1 - config_.whitepoint_b;
- double delta2 = delta_r * delta_r + delta_b * delta_b;
- //LOG(RPiAwb, Debug) << "delta_r " << delta_r << " delta_b " << delta_b << " delta2 " << delta2;
- delta2 = std::min(delta2, config_.delta_limit);
- delta2_sum += delta2;
- }
- return delta2_sum;
-}
-
-Pwl Awb::interpolatePrior()
-{
- // Interpolate the prior log likelihood function for our current lux
- // value.
- if (lux_ <= config_.priors.front().lux)
- return config_.priors.front().prior;
- else if (lux_ >= config_.priors.back().lux)
- return config_.priors.back().prior;
- else {
- int idx = 0;
- // find which two we lie between
- while (config_.priors[idx + 1].lux < lux_)
- idx++;
- double lux0 = config_.priors[idx].lux,
- lux1 = config_.priors[idx + 1].lux;
- return Pwl::Combine(config_.priors[idx].prior,
- config_.priors[idx + 1].prior,
- [&](double /*x*/, double y0, double y1) {
- return y0 + (y1 - y0) *
- (lux_ - lux0) / (lux1 - lux0);
- });
- }
-}
-
-static double interpolate_quadatric(Pwl::Point const &A, Pwl::Point const &B,
- Pwl::Point const &C)
-{
- // Given 3 points on a curve, find the extremum of the function in that
- // interval by fitting a quadratic.
- const double eps = 1e-3;
- Pwl::Point CA = C - A, BA = B - A;
- double denominator = 2 * (BA.y * CA.x - CA.y * BA.x);
- if (abs(denominator) > eps) {
- double numerator = BA.y * CA.x * CA.x - CA.y * BA.x * BA.x;
- double result = numerator / denominator + A.x;
- return std::max(A.x, std::min(C.x, result));
- }
- // has degenerated to straight line segment
- return A.y < C.y - eps ? A.x : (C.y < A.y - eps ? C.x : B.x);
-}
-
-double Awb::coarseSearch(Pwl const &prior)
-{
- points_.clear(); // assume doesn't deallocate memory
- size_t best_point = 0;
- double t = mode_->ct_lo;
- int span_r = 0, span_b = 0;
- // Step down the CT curve evaluating log likelihood.
- while (true) {
- double r = config_.ct_r.Eval(t, &span_r);
- double b = config_.ct_b.Eval(t, &span_b);
- double gain_r = 1 / r, gain_b = 1 / b;
- double delta2_sum = computeDelta2Sum(gain_r, gain_b);
- double prior_log_likelihood =
- prior.Eval(prior.Domain().Clip(t));
- double final_log_likelihood = delta2_sum - prior_log_likelihood;
- LOG(RPiAwb, Debug)
- << "t: " << t << " gain_r " << gain_r << " gain_b "
- << gain_b << " delta2_sum " << delta2_sum
- << " prior " << prior_log_likelihood << " final "
- << final_log_likelihood;
- points_.push_back(Pwl::Point(t, final_log_likelihood));
- if (points_.back().y < points_[best_point].y)
- best_point = points_.size() - 1;
- if (t == mode_->ct_hi)
- break;
- // for even steps along the r/b curve scale them by the current t
- t = std::min(t + t / 10 * config_.coarse_step,
- mode_->ct_hi);
- }
- t = points_[best_point].x;
- LOG(RPiAwb, Debug) << "Coarse search found CT " << t;
- // We have the best point of the search, but refine it with a quadratic
- // interpolation around its neighbours.
- if (points_.size() > 2) {
- unsigned long bp = std::min(best_point, points_.size() - 2);
- best_point = std::max(1UL, bp);
- t = interpolate_quadatric(points_[best_point - 1],
- points_[best_point],
- points_[best_point + 1]);
- LOG(RPiAwb, Debug)
- << "After quadratic refinement, coarse search has CT "
- << t;
- }
- return t;
-}
-
-void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
-{
- int span_r = -1, span_b = -1;
- config_.ct_r.Eval(t, &span_r);
- config_.ct_b.Eval(t, &span_b);
- double step = t / 10 * config_.coarse_step * 0.1;
- int nsteps = 5;
- double r_diff = config_.ct_r.Eval(t + nsteps * step, &span_r) -
- config_.ct_r.Eval(t - nsteps * step, &span_r);
- double b_diff = config_.ct_b.Eval(t + nsteps * step, &span_b) -
- config_.ct_b.Eval(t - nsteps * step, &span_b);
- Pwl::Point transverse(b_diff, -r_diff);
- if (transverse.Len2() < 1e-6)
- return;
- // unit vector orthogonal to the b vs. r function (pointing outwards
- // with r and b increasing)
- transverse = transverse / transverse.Len();
- double best_log_likelihood = 0, best_t = 0, best_r = 0, best_b = 0;
- double transverse_range =
- config_.transverse_neg + config_.transverse_pos;
- const int MAX_NUM_DELTAS = 12;
- // a transverse step approximately every 0.01 r/b units
- int num_deltas = floor(transverse_range * 100 + 0.5) + 1;
- num_deltas = num_deltas < 3 ? 3 :
- (num_deltas > MAX_NUM_DELTAS ? MAX_NUM_DELTAS : num_deltas);
- // Step down CT curve. March a bit further if the transverse range is
- // large.
- nsteps += num_deltas;
- for (int i = -nsteps; i <= nsteps; i++) {
- double t_test = t + i * step;
- double prior_log_likelihood =
- prior.Eval(prior.Domain().Clip(t_test));
- double r_curve = config_.ct_r.Eval(t_test, &span_r);
- double b_curve = config_.ct_b.Eval(t_test, &span_b);
- // x will be distance off the curve, y the log likelihood there
- Pwl::Point points[MAX_NUM_DELTAS];
- int best_point = 0;
- // Take some measurements transversely *off* the CT curve.
- for (int j = 0; j < num_deltas; j++) {
- points[j].x = -config_.transverse_neg +
- (transverse_range * j) / (num_deltas - 1);
- Pwl::Point rb_test = Pwl::Point(r_curve, b_curve) +
- transverse * points[j].x;
- double r_test = rb_test.x, b_test = rb_test.y;
- double gain_r = 1 / r_test, gain_b = 1 / b_test;
- double delta2_sum = computeDelta2Sum(gain_r, gain_b);
- points[j].y = delta2_sum - prior_log_likelihood;
- LOG(RPiAwb, Debug)
- << "At t " << t_test << " r " << r_test << " b "
- << b_test << ": " << points[j].y;
- if (points[j].y < points[best_point].y)
- best_point = j;
- }
- // We have NUM_DELTAS points transversely across the CT curve,
- // now let's do a quadratic interpolation for the best result.
- best_point = std::max(1, std::min(best_point, num_deltas - 2));
- Pwl::Point rb_test =
- Pwl::Point(r_curve, b_curve) +
- transverse *
- interpolate_quadatric(points[best_point - 1],
- points[best_point],
- points[best_point + 1]);
- double r_test = rb_test.x, b_test = rb_test.y;
- double gain_r = 1 / r_test, gain_b = 1 / b_test;
- double delta2_sum = computeDelta2Sum(gain_r, gain_b);
- double final_log_likelihood = delta2_sum - prior_log_likelihood;
- LOG(RPiAwb, Debug)
- << "Finally "
- << t_test << " r " << r_test << " b " << b_test << ": "
- << final_log_likelihood
- << (final_log_likelihood < best_log_likelihood ? " BEST" : "");
- if (best_t == 0 || final_log_likelihood < best_log_likelihood)
- best_log_likelihood = final_log_likelihood,
- best_t = t_test, best_r = r_test, best_b = b_test;
- }
- t = best_t, r = best_r, b = best_b;
- LOG(RPiAwb, Debug)
- << "Fine search found t " << t << " r " << r << " b " << b;
-}
-
-void Awb::awbBayes()
-{
- // May as well divide out G to save computeDelta2Sum from doing it over
- // and over.
- for (auto &z : zones_)
- z.R = z.R / (z.G + 1), z.B = z.B / (z.G + 1);
- // Get the current prior, and scale according to how many zones are
- // valid... not entirely sure about this.
- Pwl prior = interpolatePrior();
- prior *= zones_.size() / (double)(AWB_STATS_SIZE_X * AWB_STATS_SIZE_Y);
- prior.Map([](double x, double y) {
- LOG(RPiAwb, Debug) << "(" << x << "," << y << ")";
- });
- double t = coarseSearch(prior);
- double r = config_.ct_r.Eval(t);
- double b = config_.ct_b.Eval(t);
- LOG(RPiAwb, Debug)
- << "After coarse search: r " << r << " b " << b << " (gains r "
- << 1 / r << " b " << 1 / b << ")";
- // Not entirely sure how to handle the fine search yet. Mostly the
- // estimated CT is already good enough, but the fine search allows us to
- // wander transverely off the CT curve. Under some illuminants, where
- // there may be more or less green light, this may prove beneficial,
- // though I probably need more real datasets before deciding exactly how
- // this should be controlled and tuned.
- fineSearch(t, r, b, prior);
- LOG(RPiAwb, Debug)
- << "After fine search: r " << r << " b " << b << " (gains r "
- << 1 / r << " b " << 1 / b << ")";
- // Write results out for the main thread to pick up. Remember to adjust
- // the gains from the ones that the "canonical sensor" would require to
- // the ones needed by *this* sensor.
- async_results_.temperature_K = t;
- async_results_.gain_r = 1.0 / r * config_.sensitivity_r;
- async_results_.gain_g = 1.0;
- async_results_.gain_b = 1.0 / b * config_.sensitivity_b;
-}
-
-void Awb::awbGrey()
-{
- LOG(RPiAwb, Debug) << "Grey world AWB";
- // Make a separate list of the derivatives for each of red and blue, so
- // that we can sort them to exclude the extreme gains. We could
- // consider some variations, such as normalising all the zones first, or
- // doing an L2 average etc.
- std::vector<RGB> &derivs_R(zones_);
- std::vector<RGB> derivs_B(derivs_R);
- std::sort(derivs_R.begin(), derivs_R.end(),
- [](RGB const &a, RGB const &b) {
- return a.G * b.R < b.G * a.R;
- });
- std::sort(derivs_B.begin(), derivs_B.end(),
- [](RGB const &a, RGB const &b) {
- return a.G * b.B < b.G * a.B;
- });
- // Average the middle half of the values.
- int discard = derivs_R.size() / 4;
- RGB sum_R(0, 0, 0), sum_B(0, 0, 0);
- for (auto ri = derivs_R.begin() + discard,
- bi = derivs_B.begin() + discard;
- ri != derivs_R.end() - discard; ri++, bi++)
- sum_R += *ri, sum_B += *bi;
- double gain_r = sum_R.G / (sum_R.R + 1),
- gain_b = sum_B.G / (sum_B.B + 1);
- async_results_.temperature_K = 4500; // don't know what it is
- async_results_.gain_r = gain_r;
- async_results_.gain_g = 1.0;
- async_results_.gain_b = gain_b;
-}
-
-void Awb::doAwb()
-{
- prepareStats();
- LOG(RPiAwb, Debug) << "Valid zones: " << zones_.size();
- if (zones_.size() > config_.min_regions) {
- if (config_.bayes)
- awbBayes();
- else
- awbGrey();
- LOG(RPiAwb, Debug)
- << "CT found is "
- << async_results_.temperature_K
- << " with gains r " << async_results_.gain_r
- << " and b " << async_results_.gain_b;
- }
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Awb(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/awb.hpp b/src/ipa/raspberrypi/controller/rpi/awb.hpp
deleted file mode 100644
index ac3dca6f..00000000
--- a/src/ipa/raspberrypi/controller/rpi/awb.hpp
+++ /dev/null
@@ -1,179 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * awb.hpp - AWB control algorithm
- */
-#pragma once
-
-#include <mutex>
-#include <condition_variable>
-#include <thread>
-
-#include "../awb_algorithm.hpp"
-#include "../pwl.hpp"
-#include "../awb_status.h"
-
-namespace RPiController {
-
-// Control algorithm to perform AWB calculations.
-
-struct AwbMode {
- void Read(boost::property_tree::ptree const &params);
- double ct_lo; // low CT value for search
- double ct_hi; // high CT value for search
-};
-
-struct AwbPrior {
- void Read(boost::property_tree::ptree const &params);
- double lux; // lux level
- Pwl prior; // maps CT to prior log likelihood for this lux level
-};
-
-struct AwbConfig {
- AwbConfig() : default_mode(nullptr) {}
- void Read(boost::property_tree::ptree const &params);
- // Only repeat the AWB calculation every "this many" frames
- uint16_t frame_period;
- // number of initial frames for which speed taken as 1.0 (maximum)
- uint16_t startup_frames;
- unsigned int convergence_frames; // approx number of frames to converge
- double speed; // IIR filter speed applied to algorithm results
- bool fast; // "fast" mode uses a 16x16 rather than 32x32 grid
- Pwl ct_r; // function maps CT to r (= R/G)
- Pwl ct_b; // function maps CT to b (= B/G)
- // table of illuminant priors at different lux levels
- std::vector<AwbPrior> priors;
- // AWB "modes" (determines the search range)
- std::map<std::string, AwbMode> modes;
- AwbMode *default_mode; // mode used if no mode selected
- // minimum proportion of pixels counted within AWB region for it to be
- // "useful"
- double min_pixels;
- // minimum G value of those pixels, to be regarded a "useful"
- uint16_t min_G;
- // number of AWB regions that must be "useful" in order to do the AWB
- // calculation
- uint32_t min_regions;
- // clamp on colour error term (so as not to penalise non-grey excessively)
- double delta_limit;
- // step size control in coarse search
- double coarse_step;
- // how far to wander off CT curve towards "more purple"
- double transverse_pos;
- // how far to wander off CT curve towards "more green"
- double transverse_neg;
- // red sensitivity ratio (set to canonical sensor's R/G divided by this
- // sensor's R/G)
- double sensitivity_r;
- // blue sensitivity ratio (set to canonical sensor's B/G divided by this
- // sensor's B/G)
- double sensitivity_b;
- // The whitepoint (which we normally "aim" for) can be moved.
- double whitepoint_r;
- double whitepoint_b;
- bool bayes; // use Bayesian algorithm
-};
-
-class Awb : public AwbAlgorithm
-{
-public:
- Awb(Controller *controller = NULL);
- ~Awb();
- char const *Name() const override;
- void Initialise() override;
- void Read(boost::property_tree::ptree const &params) override;
- // AWB handles "pausing" for itself.
- bool IsPaused() const override;
- void Pause() override;
- void Resume() override;
- unsigned int GetConvergenceFrames() const override;
- void SetMode(std::string const &name) override;
- void SetManualGains(double manual_r, double manual_b) override;
- void SwitchMode(CameraMode const &camera_mode, Metadata *metadata) override;
- void Prepare(Metadata *image_metadata) override;
- void Process(StatisticsPtr &stats, Metadata *image_metadata) override;
- struct RGB {
- RGB(double _R = 0, double _G = 0, double _B = 0)
- : R(_R), G(_G), B(_B)
- {
- }
- double R, G, B;
- RGB &operator+=(RGB const &other)
- {
- R += other.R, G += other.G, B += other.B;
- return *this;
- }
- };
-
-private:
- bool isAutoEnabled() const;
- // configuration is read-only, and available to both threads
- AwbConfig config_;
- std::thread async_thread_;
- void asyncFunc(); // asynchronous thread function
- std::mutex mutex_;
- // condvar for async thread to wait on
- std::condition_variable async_signal_;
- // condvar for synchronous thread to wait on
- std::condition_variable sync_signal_;
- // for sync thread to check if async thread finished (requires mutex)
- bool async_finished_;
- // for async thread to check if it's been told to run (requires mutex)
- bool async_start_;
- // for async thread to check if it's been told to quit (requires mutex)
- bool async_abort_;
-
- // The following are only for the synchronous thread to use:
- // for sync thread to note its has asked async thread to run
- bool async_started_;
- // counts up to frame_period before restarting the async thread
- int frame_phase_;
- int frame_count_; // counts up to startup_frames
- AwbStatus sync_results_;
- AwbStatus prev_sync_results_;
- std::string mode_name_;
- // The following are for the asynchronous thread to use, though the main
- // thread can set/reset them if the async thread is known to be idle:
- void restartAsync(StatisticsPtr &stats, double lux);
- // copy out the results from the async thread so that it can be restarted
- void fetchAsyncResults();
- StatisticsPtr statistics_;
- AwbMode *mode_;
- double lux_;
- AwbStatus async_results_;
- void doAwb();
- void awbBayes();
- void awbGrey();
- void prepareStats();
- double computeDelta2Sum(double gain_r, double gain_b);
- Pwl interpolatePrior();
- double coarseSearch(Pwl const &prior);
- void fineSearch(double &t, double &r, double &b, Pwl const &prior);
- std::vector<RGB> zones_;
- std::vector<Pwl::Point> points_;
- // manual r setting
- double manual_r_;
- // manual b setting
- double manual_b_;
- bool first_switch_mode_; // is this the first call to SwitchMode?
-};
-
-static inline Awb::RGB operator+(Awb::RGB const &a, Awb::RGB const &b)
-{
- return Awb::RGB(a.R + b.R, a.G + b.G, a.B + b.B);
-}
-static inline Awb::RGB operator-(Awb::RGB const &a, Awb::RGB const &b)
-{
- return Awb::RGB(a.R - b.R, a.G - b.G, a.B - b.B);
-}
-static inline Awb::RGB operator*(double d, Awb::RGB const &rgb)
-{
- return Awb::RGB(d * rgb.R, d * rgb.G, d * rgb.B);
-}
-static inline Awb::RGB operator*(Awb::RGB const &rgb, double d)
-{
- return d * rgb;
-}
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/black_level.cpp b/src/ipa/raspberrypi/controller/rpi/black_level.cpp
deleted file mode 100644
index 6b3497f1..00000000
--- a/src/ipa/raspberrypi/controller/rpi/black_level.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * black_level.cpp - black level control algorithm
- */
-
-#include <math.h>
-#include <stdint.h>
-
-#include <libcamera/base/log.h>
-
-#include "../black_level_status.h"
-
-#include "black_level.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiBlackLevel)
-
-#define NAME "rpi.black_level"
-
-BlackLevel::BlackLevel(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *BlackLevel::Name() const
-{
- return NAME;
-}
-
-void BlackLevel::Read(boost::property_tree::ptree const &params)
-{
- uint16_t black_level = params.get<uint16_t>(
- "black_level", 4096); // 64 in 10 bits scaled to 16 bits
- black_level_r_ = params.get<uint16_t>("black_level_r", black_level);
- black_level_g_ = params.get<uint16_t>("black_level_g", black_level);
- black_level_b_ = params.get<uint16_t>("black_level_b", black_level);
- LOG(RPiBlackLevel, Debug)
- << " Read black levels red " << black_level_r_
- << " green " << black_level_g_
- << " blue " << black_level_b_;
-}
-
-void BlackLevel::Prepare(Metadata *image_metadata)
-{
- // Possibly we should think about doing this in a switch_mode or
- // something?
- struct BlackLevelStatus status;
- status.black_level_r = black_level_r_;
- status.black_level_g = black_level_g_;
- status.black_level_b = black_level_b_;
- image_metadata->Set("black_level.status", status);
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return new BlackLevel(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/black_level.hpp b/src/ipa/raspberrypi/controller/rpi/black_level.hpp
deleted file mode 100644
index 65ec4d0e..00000000
--- a/src/ipa/raspberrypi/controller/rpi/black_level.hpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * black_level.hpp - black level control algorithm
- */
-#pragma once
-
-#include "../algorithm.hpp"
-#include "../black_level_status.h"
-
-// This is our implementation of the "black level algorithm".
-
-namespace RPiController {
-
-class BlackLevel : public Algorithm
-{
-public:
- BlackLevel(Controller *controller);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void Prepare(Metadata *image_metadata) override;
-
-private:
- double black_level_r_;
- double black_level_g_;
- double black_level_b_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/ccm.cpp b/src/ipa/raspberrypi/controller/rpi/ccm.cpp
deleted file mode 100644
index 821a4c7c..00000000
--- a/src/ipa/raspberrypi/controller/rpi/ccm.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * ccm.cpp - CCM (colour correction matrix) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../awb_status.h"
-#include "../ccm_status.h"
-#include "../lux_status.h"
-#include "../metadata.hpp"
-
-#include "ccm.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiCcm)
-
-// This algorithm selects a CCM (Colour Correction Matrix) according to the
-// colour temperature estimated by AWB (interpolating between known matricies as
-// necessary). Additionally the amount of colour saturation can be controlled
-// both according to the current estimated lux level and according to a
-// saturation setting that is exposed to applications.
-
-#define NAME "rpi.ccm"
-
-Matrix::Matrix()
-{
- memset(m, 0, sizeof(m));
-}
-Matrix::Matrix(double m0, double m1, double m2, double m3, double m4, double m5,
- double m6, double m7, double m8)
-{
- m[0][0] = m0, m[0][1] = m1, m[0][2] = m2, m[1][0] = m3, m[1][1] = m4,
- m[1][2] = m5, m[2][0] = m6, m[2][1] = m7, m[2][2] = m8;
-}
-void Matrix::Read(boost::property_tree::ptree const &params)
-{
- double *ptr = (double *)m;
- int n = 0;
- for (auto it = params.begin(); it != params.end(); it++) {
- if (n++ == 9)
- throw std::runtime_error("Ccm: too many values in CCM");
- *ptr++ = it->second.get_value<double>();
- }
- if (n < 9)
- throw std::runtime_error("Ccm: too few values in CCM");
-}
-
-Ccm::Ccm(Controller *controller)
- : CcmAlgorithm(controller), saturation_(1.0) {}
-
-char const *Ccm::Name() const
-{
- return NAME;
-}
-
-void Ccm::Read(boost::property_tree::ptree const &params)
-{
- if (params.get_child_optional("saturation"))
- config_.saturation.Read(params.get_child("saturation"));
- for (auto &p : params.get_child("ccms")) {
- CtCcm ct_ccm;
- ct_ccm.ct = p.second.get<double>("ct");
- ct_ccm.ccm.Read(p.second.get_child("ccm"));
- if (!config_.ccms.empty() &&
- ct_ccm.ct <= config_.ccms.back().ct)
- throw std::runtime_error(
- "Ccm: CCM not in increasing colour temperature order");
- config_.ccms.push_back(std::move(ct_ccm));
- }
- if (config_.ccms.empty())
- throw std::runtime_error("Ccm: no CCMs specified");
-}
-
-void Ccm::SetSaturation(double saturation)
-{
- saturation_ = saturation;
-}
-
-void Ccm::Initialise() {}
-
-template<typename T>
-static bool get_locked(Metadata *metadata, std::string const &tag, T &value)
-{
- T *ptr = metadata->GetLocked<T>(tag);
- if (ptr == nullptr)
- return false;
- value = *ptr;
- return true;
-}
-
-Matrix calculate_ccm(std::vector<CtCcm> const &ccms, double ct)
-{
- if (ct <= ccms.front().ct)
- return ccms.front().ccm;
- else if (ct >= ccms.back().ct)
- return ccms.back().ccm;
- else {
- int i = 0;
- for (; ct > ccms[i].ct; i++)
- ;
- double lambda =
- (ct - ccms[i - 1].ct) / (ccms[i].ct - ccms[i - 1].ct);
- return lambda * ccms[i].ccm + (1.0 - lambda) * ccms[i - 1].ccm;
- }
-}
-
-Matrix apply_saturation(Matrix const &ccm, double saturation)
-{
- Matrix RGB2Y(0.299, 0.587, 0.114, -0.169, -0.331, 0.500, 0.500, -0.419,
- -0.081);
- Matrix Y2RGB(1.000, 0.000, 1.402, 1.000, -0.345, -0.714, 1.000, 1.771,
- 0.000);
- Matrix S(1, 0, 0, 0, saturation, 0, 0, 0, saturation);
- return Y2RGB * S * RGB2Y * ccm;
-}
-
-void Ccm::Prepare(Metadata *image_metadata)
-{
- bool awb_ok = false, lux_ok = false;
- struct AwbStatus awb = {};
- awb.temperature_K = 4000; // in case no metadata
- struct LuxStatus lux = {};
- lux.lux = 400; // in case no metadata
- {
- // grab mutex just once to get everything
- std::lock_guard<Metadata> lock(*image_metadata);
- awb_ok = get_locked(image_metadata, "awb.status", awb);
- lux_ok = get_locked(image_metadata, "lux.status", lux);
- }
- if (!awb_ok)
- LOG(RPiCcm, Warning) << "no colour temperature found";
- if (!lux_ok)
- LOG(RPiCcm, Warning) << "no lux value found";
- Matrix ccm = calculate_ccm(config_.ccms, awb.temperature_K);
- double saturation = saturation_;
- struct CcmStatus ccm_status;
- ccm_status.saturation = saturation;
- if (!config_.saturation.Empty())
- saturation *= config_.saturation.Eval(
- config_.saturation.Domain().Clip(lux.lux));
- ccm = apply_saturation(ccm, saturation);
- for (int j = 0; j < 3; j++)
- for (int i = 0; i < 3; i++)
- ccm_status.matrix[j * 3 + i] =
- std::max(-8.0, std::min(7.9999, ccm.m[j][i]));
- LOG(RPiCcm, Debug)
- << "colour temperature " << awb.temperature_K << "K";
- LOG(RPiCcm, Debug)
- << "CCM: " << ccm_status.matrix[0] << " " << ccm_status.matrix[1]
- << " " << ccm_status.matrix[2] << " "
- << ccm_status.matrix[3] << " " << ccm_status.matrix[4]
- << " " << ccm_status.matrix[5] << " "
- << ccm_status.matrix[6] << " " << ccm_status.matrix[7]
- << " " << ccm_status.matrix[8];
- image_metadata->Set("ccm.status", ccm_status);
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Ccm(controller);
- ;
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/contrast.cpp b/src/ipa/raspberrypi/controller/rpi/contrast.cpp
deleted file mode 100644
index ae55aad5..00000000
--- a/src/ipa/raspberrypi/controller/rpi/contrast.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * contrast.cpp - contrast (gamma) control algorithm
- */
-#include <stdint.h>
-
-#include <libcamera/base/log.h>
-
-#include "../contrast_status.h"
-#include "../histogram.hpp"
-
-#include "contrast.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiContrast)
-
-// This is a very simple control algorithm which simply retrieves the results of
-// AGC and AWB via their "status" metadata, and applies digital gain to the
-// colour channels in accordance with those instructions. We take care never to
-// apply less than unity gains, as that would cause fully saturated pixels to go
-// off-white.
-
-#define NAME "rpi.contrast"
-
-Contrast::Contrast(Controller *controller)
- : ContrastAlgorithm(controller), brightness_(0.0), contrast_(1.0)
-{
-}
-
-char const *Contrast::Name() const
-{
- return NAME;
-}
-
-void Contrast::Read(boost::property_tree::ptree const &params)
-{
- // enable adaptive enhancement by default
- config_.ce_enable = params.get<int>("ce_enable", 1);
- // the point near the bottom of the histogram to move
- config_.lo_histogram = params.get<double>("lo_histogram", 0.01);
- // where in the range to try and move it to
- config_.lo_level = params.get<double>("lo_level", 0.015);
- // but don't move by more than this
- config_.lo_max = params.get<double>("lo_max", 500);
- // equivalent values for the top of the histogram...
- config_.hi_histogram = params.get<double>("hi_histogram", 0.95);
- config_.hi_level = params.get<double>("hi_level", 0.95);
- config_.hi_max = params.get<double>("hi_max", 2000);
- config_.gamma_curve.Read(params.get_child("gamma_curve"));
-}
-
-void Contrast::SetBrightness(double brightness)
-{
- brightness_ = brightness;
-}
-
-void Contrast::SetContrast(double contrast)
-{
- contrast_ = contrast;
-}
-
-static void fill_in_status(ContrastStatus &status, double brightness,
- double contrast, Pwl &gamma_curve)
-{
- status.brightness = brightness;
- status.contrast = contrast;
- for (int i = 0; i < CONTRAST_NUM_POINTS - 1; i++) {
- int x = i < 16 ? i * 1024
- : (i < 24 ? (i - 16) * 2048 + 16384
- : (i - 24) * 4096 + 32768);
- status.points[i].x = x;
- status.points[i].y = std::min(65535.0, gamma_curve.Eval(x));
- }
- status.points[CONTRAST_NUM_POINTS - 1].x = 65535;
- status.points[CONTRAST_NUM_POINTS - 1].y = 65535;
-}
-
-void Contrast::Initialise()
-{
- // Fill in some default values as Prepare will run before Process gets
- // called.
- fill_in_status(status_, brightness_, contrast_, config_.gamma_curve);
-}
-
-void Contrast::Prepare(Metadata *image_metadata)
-{
- std::unique_lock<std::mutex> lock(mutex_);
- image_metadata->Set("contrast.status", status_);
-}
-
-Pwl compute_stretch_curve(Histogram const &histogram,
- ContrastConfig const &config)
-{
- Pwl enhance;
- enhance.Append(0, 0);
- // If the start of the histogram is rather empty, try to pull it down a
- // bit.
- double hist_lo = histogram.Quantile(config.lo_histogram) *
- (65536 / NUM_HISTOGRAM_BINS);
- double level_lo = config.lo_level * 65536;
- LOG(RPiContrast, Debug)
- << "Move histogram point " << hist_lo << " to " << level_lo;
- hist_lo = std::max(
- level_lo,
- std::min(65535.0, std::min(hist_lo, level_lo + config.lo_max)));
- LOG(RPiContrast, Debug)
- << "Final values " << hist_lo << " -> " << level_lo;
- enhance.Append(hist_lo, level_lo);
- // Keep the mid-point (median) in the same place, though, to limit the
- // apparent amount of global brightness shift.
- double mid = histogram.Quantile(0.5) * (65536 / NUM_HISTOGRAM_BINS);
- enhance.Append(mid, mid);
-
- // If the top to the histogram is empty, try to pull the pixel values
- // there up.
- double hist_hi = histogram.Quantile(config.hi_histogram) *
- (65536 / NUM_HISTOGRAM_BINS);
- double level_hi = config.hi_level * 65536;
- LOG(RPiContrast, Debug)
- << "Move histogram point " << hist_hi << " to " << level_hi;
- hist_hi = std::min(
- level_hi,
- std::max(0.0, std::max(hist_hi, level_hi - config.hi_max)));
- LOG(RPiContrast, Debug)
- << "Final values " << hist_hi << " -> " << level_hi;
- enhance.Append(hist_hi, level_hi);
- enhance.Append(65535, 65535);
- return enhance;
-}
-
-Pwl apply_manual_contrast(Pwl const &gamma_curve, double brightness,
- double contrast)
-{
- Pwl new_gamma_curve;
- LOG(RPiContrast, Debug)
- << "Manual brightness " << brightness << " contrast " << contrast;
- gamma_curve.Map([&](double x, double y) {
- new_gamma_curve.Append(
- x, std::max(0.0, std::min(65535.0,
- (y - 32768) * contrast +
- 32768 + brightness)));
- });
- return new_gamma_curve;
-}
-
-void Contrast::Process(StatisticsPtr &stats,
- [[maybe_unused]] Metadata *image_metadata)
-{
- Histogram histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS);
- // We look at the histogram and adjust the gamma curve in the following
- // ways: 1. Adjust the gamma curve so as to pull the start of the
- // histogram down, and possibly push the end up.
- Pwl gamma_curve = config_.gamma_curve;
- if (config_.ce_enable) {
- if (config_.lo_max != 0 || config_.hi_max != 0)
- gamma_curve = compute_stretch_curve(histogram, config_)
- .Compose(gamma_curve);
- // We could apply other adjustments (e.g. partial equalisation)
- // based on the histogram...?
- }
- // 2. Finally apply any manually selected brightness/contrast
- // adjustment.
- if (brightness_ != 0 || contrast_ != 1.0)
- gamma_curve = apply_manual_contrast(gamma_curve, brightness_,
- contrast_);
- // And fill in the status for output. Use more points towards the bottom
- // of the curve.
- ContrastStatus status;
- fill_in_status(status, brightness_, contrast_, gamma_curve);
- {
- std::unique_lock<std::mutex> lock(mutex_);
- status_ = status;
- }
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Contrast(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/contrast.hpp b/src/ipa/raspberrypi/controller/rpi/contrast.hpp
deleted file mode 100644
index 85624539..00000000
--- a/src/ipa/raspberrypi/controller/rpi/contrast.hpp
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * contrast.hpp - contrast (gamma) control algorithm
- */
-#pragma once
-
-#include <mutex>
-
-#include "../contrast_algorithm.hpp"
-#include "../pwl.hpp"
-
-namespace RPiController {
-
-// Back End algorithm to appaly correct digital gain. Should be placed after
-// Back End AWB.
-
-struct ContrastConfig {
- bool ce_enable;
- double lo_histogram;
- double lo_level;
- double lo_max;
- double hi_histogram;
- double hi_level;
- double hi_max;
- Pwl gamma_curve;
-};
-
-class Contrast : public ContrastAlgorithm
-{
-public:
- Contrast(Controller *controller = NULL);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void SetBrightness(double brightness) override;
- void SetContrast(double contrast) override;
- void Initialise() override;
- void Prepare(Metadata *image_metadata) override;
- void Process(StatisticsPtr &stats, Metadata *image_metadata) override;
-
-private:
- ContrastConfig config_;
- double brightness_;
- double contrast_;
- ContrastStatus status_;
- std::mutex mutex_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/dpc.cpp b/src/ipa/raspberrypi/controller/rpi/dpc.cpp
deleted file mode 100644
index 110f5056..00000000
--- a/src/ipa/raspberrypi/controller/rpi/dpc.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * dpc.cpp - DPC (defective pixel correction) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "dpc.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiDpc)
-
-// We use the lux status so that we can apply stronger settings in darkness (if
-// necessary).
-
-#define NAME "rpi.dpc"
-
-Dpc::Dpc(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *Dpc::Name() const
-{
- return NAME;
-}
-
-void Dpc::Read(boost::property_tree::ptree const &params)
-{
- config_.strength = params.get<int>("strength", 1);
- if (config_.strength < 0 || config_.strength > 2)
- throw std::runtime_error("Dpc: bad strength value");
-}
-
-void Dpc::Prepare(Metadata *image_metadata)
-{
- DpcStatus dpc_status = {};
- // Should we vary this with lux level or analogue gain? TBD.
- dpc_status.strength = config_.strength;
- LOG(RPiDpc, Debug) << "strength " << dpc_status.strength;
- image_metadata->Set("dpc.status", dpc_status);
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Dpc(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/dpc.hpp b/src/ipa/raspberrypi/controller/rpi/dpc.hpp
deleted file mode 100644
index d90285c4..00000000
--- a/src/ipa/raspberrypi/controller/rpi/dpc.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * dpc.hpp - DPC (defective pixel correction) control algorithm
- */
-#pragma once
-
-#include "../algorithm.hpp"
-#include "../dpc_status.h"
-
-namespace RPiController {
-
-// Back End algorithm to apply appropriate GEQ settings.
-
-struct DpcConfig {
- int strength;
-};
-
-class Dpc : public Algorithm
-{
-public:
- Dpc(Controller *controller);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void Prepare(Metadata *image_metadata) override;
-
-private:
- DpcConfig config_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/focus.cpp b/src/ipa/raspberrypi/controller/rpi/focus.cpp
deleted file mode 100644
index a87ec802..00000000
--- a/src/ipa/raspberrypi/controller/rpi/focus.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
- *
- * focus.cpp - focus algorithm
- */
-#include <stdint.h>
-
-#include <libcamera/base/log.h>
-
-#include "../focus_status.h"
-#include "focus.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiFocus)
-
-#define NAME "rpi.focus"
-
-Focus::Focus(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *Focus::Name() const
-{
- return NAME;
-}
-
-void Focus::Process(StatisticsPtr &stats, Metadata *image_metadata)
-{
- FocusStatus status;
- unsigned int i;
- for (i = 0; i < FOCUS_REGIONS; i++)
- status.focus_measures[i] = stats->focus_stats[i].contrast_val[1][1] / 1000;
- status.num = i;
- image_metadata->Set("focus.status", status);
-
- LOG(RPiFocus, Debug)
- << "Focus contrast measure: "
- << (status.focus_measures[5] + status.focus_measures[6]) / 10;
-}
-
-/* Register algorithm with the system. */
-static Algorithm *Create(Controller *controller)
-{
- return new Focus(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/geq.cpp b/src/ipa/raspberrypi/controller/rpi/geq.cpp
deleted file mode 100644
index 4530cb75..00000000
--- a/src/ipa/raspberrypi/controller/rpi/geq.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * geq.cpp - GEQ (green equalisation) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../device_status.h"
-#include "../lux_status.h"
-#include "../pwl.hpp"
-
-#include "geq.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiGeq)
-
-// We use the lux status so that we can apply stronger settings in darkness (if
-// necessary).
-
-#define NAME "rpi.geq"
-
-Geq::Geq(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *Geq::Name() const
-{
- return NAME;
-}
-
-void Geq::Read(boost::property_tree::ptree const &params)
-{
- config_.offset = params.get<uint16_t>("offset", 0);
- config_.slope = params.get<double>("slope", 0.0);
- if (config_.slope < 0.0 || config_.slope >= 1.0)
- throw std::runtime_error("Geq: bad slope value");
- if (params.get_child_optional("strength"))
- config_.strength.Read(params.get_child("strength"));
-}
-
-void Geq::Prepare(Metadata *image_metadata)
-{
- LuxStatus lux_status = {};
- lux_status.lux = 400;
- if (image_metadata->Get("lux.status", lux_status))
- LOG(RPiGeq, Warning) << "no lux data found";
- DeviceStatus device_status;
- device_status.analogue_gain = 1.0; // in case not found
- if (image_metadata->Get("device.status", device_status))
- LOG(RPiGeq, Warning)
- << "no device metadata - use analogue gain of 1x";
- GeqStatus geq_status = {};
- double strength =
- config_.strength.Empty()
- ? 1.0
- : config_.strength.Eval(config_.strength.Domain().Clip(
- lux_status.lux));
- strength *= device_status.analogue_gain;
- double offset = config_.offset * strength;
- double slope = config_.slope * strength;
- geq_status.offset = std::min(65535.0, std::max(0.0, offset));
- geq_status.slope = std::min(.99999, std::max(0.0, slope));
- LOG(RPiGeq, Debug)
- << "offset " << geq_status.offset << " slope "
- << geq_status.slope << " (analogue gain "
- << device_status.analogue_gain << " lux "
- << lux_status.lux << ")";
- image_metadata->Set("geq.status", geq_status);
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Geq(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/geq.hpp b/src/ipa/raspberrypi/controller/rpi/geq.hpp
deleted file mode 100644
index 8ba3046b..00000000
--- a/src/ipa/raspberrypi/controller/rpi/geq.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * geq.hpp - GEQ (green equalisation) control algorithm
- */
-#pragma once
-
-#include "../algorithm.hpp"
-#include "../geq_status.h"
-
-namespace RPiController {
-
-// Back End algorithm to apply appropriate GEQ settings.
-
-struct GeqConfig {
- uint16_t offset;
- double slope;
- Pwl strength; // lux to strength factor
-};
-
-class Geq : public Algorithm
-{
-public:
- Geq(Controller *controller);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void Prepare(Metadata *image_metadata) override;
-
-private:
- GeqConfig config_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/lux.cpp b/src/ipa/raspberrypi/controller/rpi/lux.cpp
deleted file mode 100644
index 4d145b6f..00000000
--- a/src/ipa/raspberrypi/controller/rpi/lux.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * lux.cpp - Lux control algorithm
- */
-#include <math.h>
-
-#include <linux/bcm2835-isp.h>
-
-#include <libcamera/base/log.h>
-
-#include "../device_status.h"
-
-#include "lux.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-using namespace std::literals::chrono_literals;
-
-LOG_DEFINE_CATEGORY(RPiLux)
-
-#define NAME "rpi.lux"
-
-Lux::Lux(Controller *controller)
- : Algorithm(controller)
-{
- // Put in some defaults as there will be no meaningful values until
- // Process has run.
- status_.aperture = 1.0;
- status_.lux = 400;
-}
-
-char const *Lux::Name() const
-{
- return NAME;
-}
-
-void Lux::Read(boost::property_tree::ptree const &params)
-{
- reference_shutter_speed_ =
- params.get<double>("reference_shutter_speed") * 1.0us;
- reference_gain_ = params.get<double>("reference_gain");
- reference_aperture_ = params.get<double>("reference_aperture", 1.0);
- reference_Y_ = params.get<double>("reference_Y");
- reference_lux_ = params.get<double>("reference_lux");
- current_aperture_ = reference_aperture_;
-}
-
-void Lux::SetCurrentAperture(double aperture)
-{
- current_aperture_ = aperture;
-}
-
-void Lux::Prepare(Metadata *image_metadata)
-{
- std::unique_lock<std::mutex> lock(mutex_);
- image_metadata->Set("lux.status", status_);
-}
-
-void Lux::Process(StatisticsPtr &stats, Metadata *image_metadata)
-{
- DeviceStatus device_status;
- if (image_metadata->Get("device.status", device_status) == 0) {
- double current_gain = device_status.analogue_gain;
- double current_aperture = device_status.aperture.value_or(current_aperture_);
- uint64_t sum = 0;
- uint32_t num = 0;
- uint32_t *bin = stats->hist[0].g_hist;
- const int num_bins = sizeof(stats->hist[0].g_hist) /
- sizeof(stats->hist[0].g_hist[0]);
- for (int i = 0; i < num_bins; i++)
- sum += bin[i] * (uint64_t)i, num += bin[i];
- // add .5 to reflect the mid-points of bins
- double current_Y = sum / (double)num + .5;
- double gain_ratio = reference_gain_ / current_gain;
- double shutter_speed_ratio =
- reference_shutter_speed_ / device_status.shutter_speed;
- double aperture_ratio = reference_aperture_ / current_aperture;
- double Y_ratio = current_Y * (65536 / num_bins) / reference_Y_;
- double estimated_lux = shutter_speed_ratio * gain_ratio *
- aperture_ratio * aperture_ratio *
- Y_ratio * reference_lux_;
- LuxStatus status;
- status.lux = estimated_lux;
- status.aperture = current_aperture;
- LOG(RPiLux, Debug) << ": estimated lux " << estimated_lux;
- {
- std::unique_lock<std::mutex> lock(mutex_);
- status_ = status;
- }
- // Overwrite the metadata here as well, so that downstream
- // algorithms get the latest value.
- image_metadata->Set("lux.status", status);
- } else
- LOG(RPiLux, Warning) << ": no device metadata";
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Lux(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/lux.hpp b/src/ipa/raspberrypi/controller/rpi/lux.hpp
deleted file mode 100644
index 3ebd35d1..00000000
--- a/src/ipa/raspberrypi/controller/rpi/lux.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * lux.hpp - Lux control algorithm
- */
-#pragma once
-
-#include <mutex>
-
-#include <libcamera/base/utils.h>
-
-#include "../lux_status.h"
-#include "../algorithm.hpp"
-
-// This is our implementation of the "lux control algorithm".
-
-namespace RPiController {
-
-class Lux : public Algorithm
-{
-public:
- Lux(Controller *controller);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void Prepare(Metadata *image_metadata) override;
- void Process(StatisticsPtr &stats, Metadata *image_metadata) override;
- void SetCurrentAperture(double aperture);
-
-private:
- // These values define the conditions of the reference image, against
- // which we compare the new image.
- libcamera::utils::Duration reference_shutter_speed_;
- double reference_gain_;
- double reference_aperture_; // units of 1/f
- double reference_Y_; // out of 65536
- double reference_lux_;
- double current_aperture_;
- LuxStatus status_;
- std::mutex mutex_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/noise.cpp b/src/ipa/raspberrypi/controller/rpi/noise.cpp
deleted file mode 100644
index 63cad639..00000000
--- a/src/ipa/raspberrypi/controller/rpi/noise.cpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * noise.cpp - Noise control algorithm
- */
-
-#include <math.h>
-
-#include <libcamera/base/log.h>
-
-#include "../device_status.h"
-#include "../noise_status.h"
-
-#include "noise.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiNoise)
-
-#define NAME "rpi.noise"
-
-Noise::Noise(Controller *controller)
- : Algorithm(controller), mode_factor_(1.0)
-{
-}
-
-char const *Noise::Name() const
-{
- return NAME;
-}
-
-void Noise::SwitchMode(CameraMode const &camera_mode,
- [[maybe_unused]] Metadata *metadata)
-{
- // For example, we would expect a 2x2 binned mode to have a "noise
- // factor" of sqrt(2x2) = 2. (can't be less than one, right?)
- mode_factor_ = std::max(1.0, camera_mode.noise_factor);
-}
-
-void Noise::Read(boost::property_tree::ptree const &params)
-{
- reference_constant_ = params.get<double>("reference_constant");
- reference_slope_ = params.get<double>("reference_slope");
-}
-
-void Noise::Prepare(Metadata *image_metadata)
-{
- struct DeviceStatus device_status;
- device_status.analogue_gain = 1.0; // keep compiler calm
- if (image_metadata->Get("device.status", device_status) == 0) {
- // There is a slight question as to exactly how the noise
- // profile, specifically the constant part of it, scales. For
- // now we assume it all scales the same, and we'll revisit this
- // if it proves substantially wrong. NOTE: we may also want to
- // make some adjustments based on the camera mode (such as
- // binning), if we knew how to discover it...
- double factor = sqrt(device_status.analogue_gain) / mode_factor_;
- struct NoiseStatus status;
- status.noise_constant = reference_constant_ * factor;
- status.noise_slope = reference_slope_ * factor;
- image_metadata->Set("noise.status", status);
- LOG(RPiNoise, Debug)
- << "constant " << status.noise_constant
- << " slope " << status.noise_slope;
- } else
- LOG(RPiNoise, Warning) << " no metadata";
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return new Noise(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/noise.hpp b/src/ipa/raspberrypi/controller/rpi/noise.hpp
deleted file mode 100644
index 1c9de5c8..00000000
--- a/src/ipa/raspberrypi/controller/rpi/noise.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * noise.hpp - Noise control algorithm
- */
-#pragma once
-
-#include "../algorithm.hpp"
-#include "../noise_status.h"
-
-// This is our implementation of the "noise algorithm".
-
-namespace RPiController {
-
-class Noise : public Algorithm
-{
-public:
- Noise(Controller *controller);
- char const *Name() const override;
- void SwitchMode(CameraMode const &camera_mode, Metadata *metadata) override;
- void Read(boost::property_tree::ptree const &params) override;
- void Prepare(Metadata *image_metadata) override;
-
-private:
- // the noise profile for analogue gain of 1.0
- double reference_constant_;
- double reference_slope_;
- double mode_factor_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/sdn.cpp b/src/ipa/raspberrypi/controller/rpi/sdn.cpp
deleted file mode 100644
index 93845509..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sdn.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Limited
- *
- * sdn.cpp - SDN (spatial denoise) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../denoise_status.h"
-#include "../noise_status.h"
-
-#include "sdn.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiSdn)
-
-// Calculate settings for the spatial denoise block using the noise profile in
-// the image metadata.
-
-#define NAME "rpi.sdn"
-
-Sdn::Sdn(Controller *controller)
- : DenoiseAlgorithm(controller), mode_(DenoiseMode::ColourOff)
-{
-}
-
-char const *Sdn::Name() const
-{
- return NAME;
-}
-
-void Sdn::Read(boost::property_tree::ptree const &params)
-{
- deviation_ = params.get<double>("deviation", 3.2);
- strength_ = params.get<double>("strength", 0.75);
-}
-
-void Sdn::Initialise() {}
-
-void Sdn::Prepare(Metadata *image_metadata)
-{
- struct NoiseStatus noise_status = {};
- noise_status.noise_slope = 3.0; // in case no metadata
- if (image_metadata->Get("noise.status", noise_status) != 0)
- LOG(RPiSdn, Warning) << "no noise profile found";
- LOG(RPiSdn, Debug)
- << "Noise profile: constant " << noise_status.noise_constant
- << " slope " << noise_status.noise_slope;
- struct DenoiseStatus status;
- status.noise_constant = noise_status.noise_constant * deviation_;
- status.noise_slope = noise_status.noise_slope * deviation_;
- status.strength = strength_;
- status.mode = static_cast<std::underlying_type_t<DenoiseMode>>(mode_);
- image_metadata->Set("denoise.status", status);
- LOG(RPiSdn, Debug)
- << "programmed constant " << status.noise_constant
- << " slope " << status.noise_slope
- << " strength " << status.strength;
-}
-
-void Sdn::SetMode(DenoiseMode mode)
-{
- // We only distinguish between off and all other modes.
- mode_ = mode;
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return (Algorithm *)new Sdn(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/sdn.hpp b/src/ipa/raspberrypi/controller/rpi/sdn.hpp
deleted file mode 100644
index 2371ce04..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sdn.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * sdn.hpp - SDN (spatial denoise) control algorithm
- */
-#pragma once
-
-#include "../algorithm.hpp"
-#include "../denoise_algorithm.hpp"
-
-namespace RPiController {
-
-// Algorithm to calculate correct spatial denoise (SDN) settings.
-
-class Sdn : public DenoiseAlgorithm
-{
-public:
- Sdn(Controller *controller = NULL);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void Initialise() override;
- void Prepare(Metadata *image_metadata) override;
- void SetMode(DenoiseMode mode) override;
-
-private:
- double deviation_;
- double strength_;
- DenoiseMode mode_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/sharpen.cpp b/src/ipa/raspberrypi/controller/rpi/sharpen.cpp
deleted file mode 100644
index 18825a43..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sharpen.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * sharpen.cpp - sharpening control algorithm
- */
-
-#include <math.h>
-
-#include <libcamera/base/log.h>
-
-#include "../sharpen_status.h"
-
-#include "sharpen.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiSharpen)
-
-#define NAME "rpi.sharpen"
-
-Sharpen::Sharpen(Controller *controller)
- : SharpenAlgorithm(controller), user_strength_(1.0)
-{
-}
-
-char const *Sharpen::Name() const
-{
- return NAME;
-}
-
-void Sharpen::SwitchMode(CameraMode const &camera_mode,
- [[maybe_unused]] Metadata *metadata)
-{
- // can't be less than one, right?
- mode_factor_ = std::max(1.0, camera_mode.noise_factor);
-}
-
-void Sharpen::Read(boost::property_tree::ptree const &params)
-{
- threshold_ = params.get<double>("threshold", 1.0);
- strength_ = params.get<double>("strength", 1.0);
- limit_ = params.get<double>("limit", 1.0);
- LOG(RPiSharpen, Debug)
- << "Read threshold " << threshold_
- << " strength " << strength_
- << " limit " << limit_;
-}
-
-void Sharpen::SetStrength(double strength)
-{
- // Note that this function is how an application sets the overall
- // sharpening "strength". We call this the "user strength" field
- // as there already is a strength_ field - being an internal gain
- // parameter that gets passed to the ISP control code. Negative
- // values are not allowed - coerce them to zero (no sharpening).
- user_strength_ = std::max(0.0, strength);
-}
-
-void Sharpen::Prepare(Metadata *image_metadata)
-{
- // The user_strength_ affects the algorithm's internal gain directly, but
- // we adjust the limit and threshold less aggressively. Using a sqrt
- // function is an arbitrary but gentle way of accomplishing this.
- double user_strength_sqrt = sqrt(user_strength_);
- struct SharpenStatus status;
- // Binned modes seem to need the sharpening toned down with this
- // pipeline, thus we use the mode_factor here. Also avoid
- // divide-by-zero with the user_strength_sqrt.
- status.threshold = threshold_ * mode_factor_ /
- std::max(0.01, user_strength_sqrt);
- status.strength = strength_ / mode_factor_ * user_strength_;
- status.limit = limit_ / mode_factor_ * user_strength_sqrt;
- // Finally, report any application-supplied parameters that were used.
- status.user_strength = user_strength_;
- image_metadata->Set("sharpen.status", status);
-}
-
-// Register algorithm with the system.
-static Algorithm *Create(Controller *controller)
-{
- return new Sharpen(controller);
-}
-static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/raspberrypi/controller/rpi/sharpen.hpp b/src/ipa/raspberrypi/controller/rpi/sharpen.hpp
deleted file mode 100644
index 13a076a8..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sharpen.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * sharpen.hpp - sharpening control algorithm
- */
-#pragma once
-
-#include "../sharpen_algorithm.hpp"
-#include "../sharpen_status.h"
-
-// This is our implementation of the "sharpen algorithm".
-
-namespace RPiController {
-
-class Sharpen : public SharpenAlgorithm
-{
-public:
- Sharpen(Controller *controller);
- char const *Name() const override;
- void SwitchMode(CameraMode const &camera_mode, Metadata *metadata) override;
- void Read(boost::property_tree::ptree const &params) override;
- void SetStrength(double strength) override;
- void Prepare(Metadata *image_metadata) override;
-
-private:
- double threshold_;
- double strength_;
- double limit_;
- double mode_factor_;
- double user_strength_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/sharpen_algorithm.hpp b/src/ipa/raspberrypi/controller/sharpen_algorithm.hpp
deleted file mode 100644
index ca800308..00000000
--- a/src/ipa/raspberrypi/controller/sharpen_algorithm.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
- *
- * sharpen_algorithm.hpp - sharpness control algorithm interface
- */
-#pragma once
-
-#include "algorithm.hpp"
-
-namespace RPiController {
-
-class SharpenAlgorithm : public Algorithm
-{
-public:
- SharpenAlgorithm(Controller *controller) : Algorithm(controller) {}
- // A sharpness control algorithm must provide the following:
- virtual void SetStrength(double strength) = 0;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/sharpen_status.h b/src/ipa/raspberrypi/controller/sharpen_status.h
deleted file mode 100644
index 7501b191..00000000
--- a/src/ipa/raspberrypi/controller/sharpen_status.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
- *
- * sharpen_status.h - Sharpen control algorithm status
- */
-#pragma once
-
-// The "sharpen" algorithm stores the strength to use.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct SharpenStatus {
- // controls the smallest level of detail (or noise!) that sharpening will pick up
- double threshold;
- // the rate at which the sharpening response ramps once above the threshold
- double strength;
- // upper limit of the allowed sharpening response
- double limit;
- // The sharpening strength requested by the user or application.
- double user_strength;
-};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/raspberrypi/data/imx219.json b/src/ipa/raspberrypi/data/imx219.json
deleted file mode 100644
index de59d936..00000000
--- a/src/ipa/raspberrypi/data/imx219.json
+++ /dev/null
@@ -1,412 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
-
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 27685,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 998,
- "reference_Y": 12744
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 3.67
- },
- "rpi.geq":
- {
- "offset": 204,
- "slope": 0.01633
- },
- "rpi.sdn":
- {
-
- },
- "rpi.awb":
- {
- "priors":
- [
- {
- "lux": 0, "prior":
- [
- 2000, 1.0, 3000, 0.0, 13000, 0.0
- ]
- },
- {
- "lux": 800, "prior":
- [
- 2000, 0.0, 6000, 2.0, 13000, 2.0
- ]
- },
- {
- "lux": 1500, "prior":
- [
- 2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0
- ]
- }
- ],
- "modes":
- {
- "auto":
- {
- "lo": 2500,
- "hi": 8000
- },
- "incandescent":
- {
- "lo": 2500,
- "hi": 3000
- },
- "tungsten":
- {
- "lo": 3000,
- "hi": 3500
- },
- "fluorescent":
- {
- "lo": 4000,
- "hi": 4700
- },
- "indoor":
- {
- "lo": 3000,
- "hi": 5000
- },
- "daylight":
- {
- "lo": 5500,
- "hi": 6500
- },
- "cloudy":
- {
- "lo": 7000,
- "hi": 8600
- }
- },
- "bayes": 1,
- "ct_curve":
- [
- 2498.0, 0.9309, 0.3599, 2911.0, 0.8682, 0.4283, 2919.0, 0.8358, 0.4621, 3627.0, 0.7646, 0.5327, 4600.0, 0.6079, 0.6721, 5716.0,
- 0.5712, 0.7017, 8575.0, 0.4331, 0.8037
- ],
- "sensitivity_r": 1.05,
- "sensitivity_b": 1.05,
- "transverse_pos": 0.04791,
- "transverse_neg": 0.04881
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 66666
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 33333
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "long":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 12.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ],
- "shadows":
- [
- {
- "bound": "LOWER", "q_lo": 0.0, "q_hi": 0.5, "y_target":
- [
- 0, 0.17, 1000, 0.17
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.7,
- "calibrations_Cr":
- [
- {
- "ct": 3000, "table":
- [
- 1.487, 1.481, 1.481, 1.445, 1.389, 1.327, 1.307, 1.307, 1.307, 1.309, 1.341, 1.405, 1.458, 1.494, 1.494, 1.497,
- 1.491, 1.481, 1.448, 1.397, 1.331, 1.275, 1.243, 1.229, 1.229, 1.249, 1.287, 1.349, 1.409, 1.463, 1.494, 1.497,
- 1.491, 1.469, 1.405, 1.331, 1.275, 1.217, 1.183, 1.172, 1.172, 1.191, 1.231, 1.287, 1.349, 1.424, 1.484, 1.499,
- 1.487, 1.444, 1.363, 1.283, 1.217, 1.183, 1.148, 1.138, 1.138, 1.159, 1.191, 1.231, 1.302, 1.385, 1.461, 1.492,
- 1.481, 1.423, 1.334, 1.253, 1.189, 1.148, 1.135, 1.119, 1.123, 1.137, 1.159, 1.203, 1.272, 1.358, 1.442, 1.488,
- 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.118, 1.114, 1.116, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
- 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.116, 1.114, 1.115, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
- 1.479, 1.425, 1.336, 1.251, 1.189, 1.149, 1.136, 1.118, 1.121, 1.138, 1.158, 1.206, 1.275, 1.358, 1.443, 1.488,
- 1.488, 1.448, 1.368, 1.285, 1.219, 1.189, 1.149, 1.139, 1.139, 1.158, 1.195, 1.235, 1.307, 1.387, 1.462, 1.493,
- 1.496, 1.475, 1.411, 1.337, 1.284, 1.219, 1.189, 1.176, 1.176, 1.195, 1.235, 1.296, 1.356, 1.429, 1.487, 1.501,
- 1.495, 1.489, 1.458, 1.407, 1.337, 1.287, 1.253, 1.239, 1.239, 1.259, 1.296, 1.356, 1.419, 1.472, 1.499, 1.499,
- 1.494, 1.489, 1.489, 1.453, 1.398, 1.336, 1.317, 1.317, 1.317, 1.321, 1.351, 1.416, 1.467, 1.501, 1.501, 1.499
- ]
- },
- {
- "ct": 3850, "table":
- [
- 1.694, 1.688, 1.688, 1.649, 1.588, 1.518, 1.495, 1.495, 1.495, 1.497, 1.532, 1.602, 1.659, 1.698, 1.698, 1.703,
- 1.698, 1.688, 1.653, 1.597, 1.525, 1.464, 1.429, 1.413, 1.413, 1.437, 1.476, 1.542, 1.606, 1.665, 1.698, 1.703,
- 1.697, 1.673, 1.605, 1.525, 1.464, 1.401, 1.369, 1.354, 1.354, 1.377, 1.417, 1.476, 1.542, 1.623, 1.687, 1.705,
- 1.692, 1.646, 1.561, 1.472, 1.401, 1.368, 1.337, 1.323, 1.324, 1.348, 1.377, 1.417, 1.492, 1.583, 1.661, 1.697,
- 1.686, 1.625, 1.528, 1.439, 1.372, 1.337, 1.321, 1.311, 1.316, 1.324, 1.348, 1.389, 1.461, 1.553, 1.642, 1.694,
- 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.306, 1.306, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
- 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.305, 1.305, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
- 1.685, 1.624, 1.529, 1.438, 1.372, 1.336, 1.324, 1.309, 1.314, 1.323, 1.348, 1.392, 1.462, 1.555, 1.646, 1.694,
- 1.692, 1.648, 1.561, 1.473, 1.403, 1.372, 1.336, 1.324, 1.324, 1.348, 1.378, 1.423, 1.495, 1.585, 1.667, 1.701,
- 1.701, 1.677, 1.608, 1.527, 1.471, 1.403, 1.375, 1.359, 1.359, 1.378, 1.423, 1.488, 1.549, 1.631, 1.694, 1.709,
- 1.702, 1.694, 1.656, 1.601, 1.527, 1.473, 1.441, 1.424, 1.424, 1.443, 1.488, 1.549, 1.621, 1.678, 1.706, 1.707,
- 1.699, 1.694, 1.694, 1.654, 1.593, 1.525, 1.508, 1.508, 1.508, 1.509, 1.546, 1.614, 1.674, 1.708, 1.708, 1.707
- ]
- },
- {
- "ct": 6000, "table":
- [
- 2.179, 2.176, 2.176, 2.125, 2.048, 1.975, 1.955, 1.954, 1.954, 1.956, 1.993, 2.071, 2.141, 2.184, 2.185, 2.188,
- 2.189, 2.176, 2.128, 2.063, 1.973, 1.908, 1.872, 1.856, 1.856, 1.876, 1.922, 1.999, 2.081, 2.144, 2.184, 2.192,
- 2.187, 2.152, 2.068, 1.973, 1.907, 1.831, 1.797, 1.786, 1.786, 1.804, 1.853, 1.922, 1.999, 2.089, 2.166, 2.191,
- 2.173, 2.117, 2.013, 1.908, 1.831, 1.791, 1.755, 1.749, 1.749, 1.767, 1.804, 1.853, 1.939, 2.041, 2.135, 2.181,
- 2.166, 2.089, 1.975, 1.869, 1.792, 1.755, 1.741, 1.731, 1.734, 1.749, 1.767, 1.818, 1.903, 2.005, 2.111, 2.173,
- 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.729, 1.725, 1.729, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
- 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.727, 1.724, 1.725, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
- 2.166, 2.085, 1.975, 1.869, 1.791, 1.755, 1.741, 1.729, 1.733, 1.749, 1.769, 1.819, 1.904, 2.009, 2.114, 2.174,
- 2.174, 2.118, 2.015, 1.913, 1.831, 1.791, 1.755, 1.749, 1.749, 1.769, 1.811, 1.855, 1.943, 2.047, 2.139, 2.183,
- 2.187, 2.151, 2.072, 1.979, 1.911, 1.831, 1.801, 1.791, 1.791, 1.811, 1.855, 1.933, 2.006, 2.101, 2.173, 2.197,
- 2.189, 2.178, 2.132, 2.069, 1.979, 1.913, 1.879, 1.867, 1.867, 1.891, 1.933, 2.006, 2.091, 2.156, 2.195, 2.197,
- 2.181, 2.179, 2.178, 2.131, 2.057, 1.981, 1.965, 1.965, 1.965, 1.969, 1.999, 2.083, 2.153, 2.197, 2.197, 2.196
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 3000, "table":
- [
- 1.967, 1.961, 1.955, 1.953, 1.954, 1.957, 1.961, 1.963, 1.963, 1.961, 1.959, 1.957, 1.954, 1.951, 1.951, 1.955,
- 1.961, 1.959, 1.957, 1.956, 1.962, 1.967, 1.975, 1.979, 1.979, 1.975, 1.971, 1.967, 1.957, 1.952, 1.951, 1.951,
- 1.959, 1.959, 1.959, 1.966, 1.976, 1.989, 1.999, 2.004, 2.003, 1.997, 1.991, 1.981, 1.967, 1.956, 1.951, 1.951,
- 1.959, 1.962, 1.967, 1.978, 1.993, 2.009, 2.021, 2.028, 2.026, 2.021, 2.011, 1.995, 1.981, 1.964, 1.953, 1.951,
- 1.961, 1.965, 1.977, 1.993, 2.009, 2.023, 2.041, 2.047, 2.047, 2.037, 2.024, 2.011, 1.995, 1.975, 1.958, 1.953,
- 1.963, 1.968, 1.981, 2.001, 2.019, 2.039, 2.046, 2.052, 2.052, 2.051, 2.035, 2.021, 2.001, 1.978, 1.959, 1.955,
- 1.961, 1.966, 1.981, 2.001, 2.019, 2.038, 2.043, 2.051, 2.052, 2.042, 2.034, 2.019, 2.001, 1.978, 1.959, 1.954,
- 1.957, 1.961, 1.972, 1.989, 2.003, 2.021, 2.038, 2.039, 2.039, 2.034, 2.019, 2.004, 1.988, 1.971, 1.954, 1.949,
- 1.952, 1.953, 1.959, 1.972, 1.989, 2.003, 2.016, 2.019, 2.019, 2.014, 2.003, 1.988, 1.971, 1.955, 1.948, 1.947,
- 1.949, 1.948, 1.949, 1.957, 1.971, 1.978, 1.991, 1.994, 1.994, 1.989, 1.979, 1.967, 1.954, 1.946, 1.947, 1.947,
- 1.949, 1.946, 1.944, 1.946, 1.949, 1.954, 1.962, 1.967, 1.967, 1.963, 1.956, 1.948, 1.943, 1.943, 1.946, 1.949,
- 1.951, 1.946, 1.944, 1.942, 1.943, 1.943, 1.947, 1.948, 1.949, 1.947, 1.945, 1.941, 1.938, 1.939, 1.948, 1.952
- ]
- },
- {
- "ct": 3850, "table":
- [
- 1.726, 1.724, 1.722, 1.723, 1.731, 1.735, 1.743, 1.746, 1.746, 1.741, 1.735, 1.729, 1.725, 1.721, 1.721, 1.721,
- 1.724, 1.723, 1.723, 1.727, 1.735, 1.744, 1.749, 1.756, 1.756, 1.749, 1.744, 1.735, 1.727, 1.719, 1.719, 1.719,
- 1.723, 1.723, 1.724, 1.735, 1.746, 1.759, 1.767, 1.775, 1.775, 1.766, 1.758, 1.746, 1.735, 1.723, 1.718, 1.716,
- 1.723, 1.725, 1.732, 1.746, 1.759, 1.775, 1.782, 1.792, 1.792, 1.782, 1.772, 1.759, 1.745, 1.729, 1.718, 1.716,
- 1.725, 1.729, 1.738, 1.756, 1.775, 1.785, 1.796, 1.803, 1.804, 1.794, 1.783, 1.772, 1.757, 1.736, 1.722, 1.718,
- 1.728, 1.731, 1.741, 1.759, 1.781, 1.795, 1.803, 1.806, 1.808, 1.805, 1.791, 1.779, 1.762, 1.739, 1.722, 1.721,
- 1.727, 1.731, 1.741, 1.759, 1.781, 1.791, 1.799, 1.804, 1.806, 1.801, 1.791, 1.779, 1.762, 1.739, 1.722, 1.717,
- 1.722, 1.724, 1.733, 1.751, 1.768, 1.781, 1.791, 1.796, 1.799, 1.791, 1.781, 1.766, 1.754, 1.731, 1.717, 1.714,
- 1.718, 1.718, 1.724, 1.737, 1.752, 1.768, 1.776, 1.782, 1.784, 1.781, 1.766, 1.754, 1.737, 1.724, 1.713, 1.709,
- 1.716, 1.715, 1.716, 1.725, 1.737, 1.749, 1.756, 1.763, 1.764, 1.762, 1.749, 1.737, 1.724, 1.717, 1.709, 1.708,
- 1.715, 1.714, 1.712, 1.715, 1.722, 1.729, 1.736, 1.741, 1.742, 1.739, 1.731, 1.723, 1.717, 1.712, 1.711, 1.709,
- 1.716, 1.714, 1.711, 1.712, 1.715, 1.719, 1.723, 1.728, 1.731, 1.729, 1.723, 1.718, 1.711, 1.711, 1.713, 1.713
- ]
- },
- {
- "ct": 6000, "table":
- [
- 1.374, 1.372, 1.373, 1.374, 1.375, 1.378, 1.378, 1.381, 1.382, 1.382, 1.378, 1.373, 1.372, 1.369, 1.365, 1.365,
- 1.371, 1.371, 1.372, 1.374, 1.378, 1.381, 1.384, 1.386, 1.388, 1.387, 1.384, 1.377, 1.372, 1.368, 1.364, 1.362,
- 1.369, 1.371, 1.372, 1.377, 1.383, 1.391, 1.394, 1.396, 1.397, 1.395, 1.391, 1.382, 1.374, 1.369, 1.362, 1.361,
- 1.369, 1.371, 1.375, 1.383, 1.391, 1.399, 1.402, 1.404, 1.405, 1.403, 1.398, 1.391, 1.379, 1.371, 1.363, 1.361,
- 1.371, 1.373, 1.378, 1.388, 1.399, 1.407, 1.411, 1.413, 1.413, 1.411, 1.405, 1.397, 1.385, 1.374, 1.366, 1.362,
- 1.371, 1.374, 1.379, 1.389, 1.405, 1.411, 1.414, 1.414, 1.415, 1.415, 1.411, 1.401, 1.388, 1.376, 1.367, 1.363,
- 1.371, 1.373, 1.379, 1.389, 1.405, 1.408, 1.413, 1.414, 1.414, 1.413, 1.409, 1.401, 1.388, 1.376, 1.367, 1.362,
- 1.366, 1.369, 1.374, 1.384, 1.396, 1.404, 1.407, 1.408, 1.408, 1.408, 1.401, 1.395, 1.382, 1.371, 1.363, 1.359,
- 1.364, 1.365, 1.368, 1.375, 1.386, 1.396, 1.399, 1.401, 1.399, 1.399, 1.395, 1.385, 1.374, 1.365, 1.359, 1.357,
- 1.361, 1.363, 1.365, 1.368, 1.377, 1.384, 1.388, 1.391, 1.391, 1.388, 1.385, 1.375, 1.366, 1.361, 1.358, 1.356,
- 1.361, 1.362, 1.362, 1.364, 1.367, 1.373, 1.376, 1.377, 1.377, 1.375, 1.373, 1.366, 1.362, 1.358, 1.358, 1.358,
- 1.361, 1.362, 1.362, 1.362, 1.363, 1.367, 1.369, 1.368, 1.367, 1.367, 1.367, 1.364, 1.358, 1.357, 1.358, 1.359
- ]
- }
- ],
- "luminance_lut":
- [
- 2.716, 2.568, 2.299, 2.065, 1.845, 1.693, 1.605, 1.597, 1.596, 1.634, 1.738, 1.914, 2.145, 2.394, 2.719, 2.901,
- 2.593, 2.357, 2.093, 1.876, 1.672, 1.528, 1.438, 1.393, 1.394, 1.459, 1.569, 1.731, 1.948, 2.169, 2.481, 2.756,
- 2.439, 2.197, 1.922, 1.691, 1.521, 1.365, 1.266, 1.222, 1.224, 1.286, 1.395, 1.573, 1.747, 1.988, 2.299, 2.563,
- 2.363, 2.081, 1.797, 1.563, 1.376, 1.244, 1.152, 1.099, 1.101, 1.158, 1.276, 1.421, 1.607, 1.851, 2.163, 2.455,
- 2.342, 2.003, 1.715, 1.477, 1.282, 1.152, 1.074, 1.033, 1.035, 1.083, 1.163, 1.319, 1.516, 1.759, 2.064, 2.398,
- 2.342, 1.985, 1.691, 1.446, 1.249, 1.111, 1.034, 1.004, 1.004, 1.028, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
- 2.342, 1.991, 1.691, 1.446, 1.249, 1.112, 1.034, 1.011, 1.005, 1.035, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
- 2.365, 2.052, 1.751, 1.499, 1.299, 1.171, 1.089, 1.039, 1.042, 1.084, 1.162, 1.312, 1.516, 1.761, 2.059, 2.393,
- 2.434, 2.159, 1.856, 1.601, 1.403, 1.278, 1.166, 1.114, 1.114, 1.162, 1.266, 1.402, 1.608, 1.847, 2.146, 2.435,
- 2.554, 2.306, 2.002, 1.748, 1.563, 1.396, 1.299, 1.247, 1.243, 1.279, 1.386, 1.551, 1.746, 1.977, 2.272, 2.518,
- 2.756, 2.493, 2.195, 1.947, 1.739, 1.574, 1.481, 1.429, 1.421, 1.457, 1.559, 1.704, 1.929, 2.159, 2.442, 2.681,
- 2.935, 2.739, 2.411, 2.151, 1.922, 1.749, 1.663, 1.628, 1.625, 1.635, 1.716, 1.872, 2.113, 2.368, 2.663, 2.824
- ],
- "sigma": 0.00381,
- "sigma_Cb": 0.00216
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2498, "ccm":
- [
- 1.58731, -0.18011, -0.40721, -0.60639, 2.03422, -0.42782, -0.19612, -1.69203, 2.88815
- ]
- },
- {
- "ct": 2811, "ccm":
- [
- 1.61593, -0.33164, -0.28429, -0.55048, 1.97779, -0.42731, -0.12042, -1.42847, 2.54889
- ]
- },
- {
- "ct": 2911, "ccm":
- [
- 1.62771, -0.41282, -0.21489, -0.57991, 2.04176, -0.46186, -0.07613, -1.13359, 2.20972
- ]
- },
- {
- "ct": 2919, "ccm":
- [
- 1.62661, -0.37736, -0.24925, -0.52519, 1.95233, -0.42714, -0.10842, -1.34929, 2.45771
- ]
- },
- {
- "ct": 3627, "ccm":
- [
- 1.70385, -0.57231, -0.13154, -0.47763, 1.85998, -0.38235, -0.07467, -0.82678, 1.90145
- ]
- },
- {
- "ct": 4600, "ccm":
- [
- 1.68486, -0.61085, -0.07402, -0.41927, 2.04016, -0.62089, -0.08633, -0.67672, 1.76305
- ]
- },
- {
- "ct": 5716, "ccm":
- [
- 1.80439, -0.73699, -0.06739, -0.36073, 1.83327, -0.47255, -0.08378, -0.56403, 1.64781
- ]
- },
- {
- "ct": 8575, "ccm":
- [
- 1.89357, -0.76427, -0.12931, -0.27399, 2.15605, -0.88206, -0.12035, -0.68256, 1.80292
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
-
- },
- "rpi.dpc":
- {
-
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx219_noir.json b/src/ipa/raspberrypi/data/imx219_noir.json
deleted file mode 100644
index 9a3f03ec..00000000
--- a/src/ipa/raspberrypi/data/imx219_noir.json
+++ /dev/null
@@ -1,344 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
-
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 27685,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 998,
- "reference_Y": 12744
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 3.67
- },
- "rpi.geq":
- {
- "offset": 204,
- "slope": 0.01633
- },
- "rpi.sdn":
- {
-
- },
- "rpi.awb":
- {
- "bayes": 0
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 66666
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 33333
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "long":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 12.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ],
- "shadows":
- [
- {
- "bound": "LOWER", "q_lo": 0.0, "q_hi": 0.5, "y_target":
- [
- 0, 0.17, 1000, 0.17
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.7,
- "calibrations_Cr":
- [
- {
- "ct": 3000, "table":
- [
- 1.487, 1.481, 1.481, 1.445, 1.389, 1.327, 1.307, 1.307, 1.307, 1.309, 1.341, 1.405, 1.458, 1.494, 1.494, 1.497,
- 1.491, 1.481, 1.448, 1.397, 1.331, 1.275, 1.243, 1.229, 1.229, 1.249, 1.287, 1.349, 1.409, 1.463, 1.494, 1.497,
- 1.491, 1.469, 1.405, 1.331, 1.275, 1.217, 1.183, 1.172, 1.172, 1.191, 1.231, 1.287, 1.349, 1.424, 1.484, 1.499,
- 1.487, 1.444, 1.363, 1.283, 1.217, 1.183, 1.148, 1.138, 1.138, 1.159, 1.191, 1.231, 1.302, 1.385, 1.461, 1.492,
- 1.481, 1.423, 1.334, 1.253, 1.189, 1.148, 1.135, 1.119, 1.123, 1.137, 1.159, 1.203, 1.272, 1.358, 1.442, 1.488,
- 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.118, 1.114, 1.116, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
- 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.116, 1.114, 1.115, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
- 1.479, 1.425, 1.336, 1.251, 1.189, 1.149, 1.136, 1.118, 1.121, 1.138, 1.158, 1.206, 1.275, 1.358, 1.443, 1.488,
- 1.488, 1.448, 1.368, 1.285, 1.219, 1.189, 1.149, 1.139, 1.139, 1.158, 1.195, 1.235, 1.307, 1.387, 1.462, 1.493,
- 1.496, 1.475, 1.411, 1.337, 1.284, 1.219, 1.189, 1.176, 1.176, 1.195, 1.235, 1.296, 1.356, 1.429, 1.487, 1.501,
- 1.495, 1.489, 1.458, 1.407, 1.337, 1.287, 1.253, 1.239, 1.239, 1.259, 1.296, 1.356, 1.419, 1.472, 1.499, 1.499,
- 1.494, 1.489, 1.489, 1.453, 1.398, 1.336, 1.317, 1.317, 1.317, 1.321, 1.351, 1.416, 1.467, 1.501, 1.501, 1.499
- ]
- },
- {
- "ct": 3850, "table":
- [
- 1.694, 1.688, 1.688, 1.649, 1.588, 1.518, 1.495, 1.495, 1.495, 1.497, 1.532, 1.602, 1.659, 1.698, 1.698, 1.703,
- 1.698, 1.688, 1.653, 1.597, 1.525, 1.464, 1.429, 1.413, 1.413, 1.437, 1.476, 1.542, 1.606, 1.665, 1.698, 1.703,
- 1.697, 1.673, 1.605, 1.525, 1.464, 1.401, 1.369, 1.354, 1.354, 1.377, 1.417, 1.476, 1.542, 1.623, 1.687, 1.705,
- 1.692, 1.646, 1.561, 1.472, 1.401, 1.368, 1.337, 1.323, 1.324, 1.348, 1.377, 1.417, 1.492, 1.583, 1.661, 1.697,
- 1.686, 1.625, 1.528, 1.439, 1.372, 1.337, 1.321, 1.311, 1.316, 1.324, 1.348, 1.389, 1.461, 1.553, 1.642, 1.694,
- 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.306, 1.306, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
- 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.305, 1.305, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
- 1.685, 1.624, 1.529, 1.438, 1.372, 1.336, 1.324, 1.309, 1.314, 1.323, 1.348, 1.392, 1.462, 1.555, 1.646, 1.694,
- 1.692, 1.648, 1.561, 1.473, 1.403, 1.372, 1.336, 1.324, 1.324, 1.348, 1.378, 1.423, 1.495, 1.585, 1.667, 1.701,
- 1.701, 1.677, 1.608, 1.527, 1.471, 1.403, 1.375, 1.359, 1.359, 1.378, 1.423, 1.488, 1.549, 1.631, 1.694, 1.709,
- 1.702, 1.694, 1.656, 1.601, 1.527, 1.473, 1.441, 1.424, 1.424, 1.443, 1.488, 1.549, 1.621, 1.678, 1.706, 1.707,
- 1.699, 1.694, 1.694, 1.654, 1.593, 1.525, 1.508, 1.508, 1.508, 1.509, 1.546, 1.614, 1.674, 1.708, 1.708, 1.707
- ]
- },
- {
- "ct": 6000, "table":
- [
- 2.179, 2.176, 2.176, 2.125, 2.048, 1.975, 1.955, 1.954, 1.954, 1.956, 1.993, 2.071, 2.141, 2.184, 2.185, 2.188,
- 2.189, 2.176, 2.128, 2.063, 1.973, 1.908, 1.872, 1.856, 1.856, 1.876, 1.922, 1.999, 2.081, 2.144, 2.184, 2.192,
- 2.187, 2.152, 2.068, 1.973, 1.907, 1.831, 1.797, 1.786, 1.786, 1.804, 1.853, 1.922, 1.999, 2.089, 2.166, 2.191,
- 2.173, 2.117, 2.013, 1.908, 1.831, 1.791, 1.755, 1.749, 1.749, 1.767, 1.804, 1.853, 1.939, 2.041, 2.135, 2.181,
- 2.166, 2.089, 1.975, 1.869, 1.792, 1.755, 1.741, 1.731, 1.734, 1.749, 1.767, 1.818, 1.903, 2.005, 2.111, 2.173,
- 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.729, 1.725, 1.729, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
- 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.727, 1.724, 1.725, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
- 2.166, 2.085, 1.975, 1.869, 1.791, 1.755, 1.741, 1.729, 1.733, 1.749, 1.769, 1.819, 1.904, 2.009, 2.114, 2.174,
- 2.174, 2.118, 2.015, 1.913, 1.831, 1.791, 1.755, 1.749, 1.749, 1.769, 1.811, 1.855, 1.943, 2.047, 2.139, 2.183,
- 2.187, 2.151, 2.072, 1.979, 1.911, 1.831, 1.801, 1.791, 1.791, 1.811, 1.855, 1.933, 2.006, 2.101, 2.173, 2.197,
- 2.189, 2.178, 2.132, 2.069, 1.979, 1.913, 1.879, 1.867, 1.867, 1.891, 1.933, 2.006, 2.091, 2.156, 2.195, 2.197,
- 2.181, 2.179, 2.178, 2.131, 2.057, 1.981, 1.965, 1.965, 1.965, 1.969, 1.999, 2.083, 2.153, 2.197, 2.197, 2.196
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 3000, "table":
- [
- 1.967, 1.961, 1.955, 1.953, 1.954, 1.957, 1.961, 1.963, 1.963, 1.961, 1.959, 1.957, 1.954, 1.951, 1.951, 1.955,
- 1.961, 1.959, 1.957, 1.956, 1.962, 1.967, 1.975, 1.979, 1.979, 1.975, 1.971, 1.967, 1.957, 1.952, 1.951, 1.951,
- 1.959, 1.959, 1.959, 1.966, 1.976, 1.989, 1.999, 2.004, 2.003, 1.997, 1.991, 1.981, 1.967, 1.956, 1.951, 1.951,
- 1.959, 1.962, 1.967, 1.978, 1.993, 2.009, 2.021, 2.028, 2.026, 2.021, 2.011, 1.995, 1.981, 1.964, 1.953, 1.951,
- 1.961, 1.965, 1.977, 1.993, 2.009, 2.023, 2.041, 2.047, 2.047, 2.037, 2.024, 2.011, 1.995, 1.975, 1.958, 1.953,
- 1.963, 1.968, 1.981, 2.001, 2.019, 2.039, 2.046, 2.052, 2.052, 2.051, 2.035, 2.021, 2.001, 1.978, 1.959, 1.955,
- 1.961, 1.966, 1.981, 2.001, 2.019, 2.038, 2.043, 2.051, 2.052, 2.042, 2.034, 2.019, 2.001, 1.978, 1.959, 1.954,
- 1.957, 1.961, 1.972, 1.989, 2.003, 2.021, 2.038, 2.039, 2.039, 2.034, 2.019, 2.004, 1.988, 1.971, 1.954, 1.949,
- 1.952, 1.953, 1.959, 1.972, 1.989, 2.003, 2.016, 2.019, 2.019, 2.014, 2.003, 1.988, 1.971, 1.955, 1.948, 1.947,
- 1.949, 1.948, 1.949, 1.957, 1.971, 1.978, 1.991, 1.994, 1.994, 1.989, 1.979, 1.967, 1.954, 1.946, 1.947, 1.947,
- 1.949, 1.946, 1.944, 1.946, 1.949, 1.954, 1.962, 1.967, 1.967, 1.963, 1.956, 1.948, 1.943, 1.943, 1.946, 1.949,
- 1.951, 1.946, 1.944, 1.942, 1.943, 1.943, 1.947, 1.948, 1.949, 1.947, 1.945, 1.941, 1.938, 1.939, 1.948, 1.952
- ]
- },
- {
- "ct": 3850, "table":
- [
- 1.726, 1.724, 1.722, 1.723, 1.731, 1.735, 1.743, 1.746, 1.746, 1.741, 1.735, 1.729, 1.725, 1.721, 1.721, 1.721,
- 1.724, 1.723, 1.723, 1.727, 1.735, 1.744, 1.749, 1.756, 1.756, 1.749, 1.744, 1.735, 1.727, 1.719, 1.719, 1.719,
- 1.723, 1.723, 1.724, 1.735, 1.746, 1.759, 1.767, 1.775, 1.775, 1.766, 1.758, 1.746, 1.735, 1.723, 1.718, 1.716,
- 1.723, 1.725, 1.732, 1.746, 1.759, 1.775, 1.782, 1.792, 1.792, 1.782, 1.772, 1.759, 1.745, 1.729, 1.718, 1.716,
- 1.725, 1.729, 1.738, 1.756, 1.775, 1.785, 1.796, 1.803, 1.804, 1.794, 1.783, 1.772, 1.757, 1.736, 1.722, 1.718,
- 1.728, 1.731, 1.741, 1.759, 1.781, 1.795, 1.803, 1.806, 1.808, 1.805, 1.791, 1.779, 1.762, 1.739, 1.722, 1.721,
- 1.727, 1.731, 1.741, 1.759, 1.781, 1.791, 1.799, 1.804, 1.806, 1.801, 1.791, 1.779, 1.762, 1.739, 1.722, 1.717,
- 1.722, 1.724, 1.733, 1.751, 1.768, 1.781, 1.791, 1.796, 1.799, 1.791, 1.781, 1.766, 1.754, 1.731, 1.717, 1.714,
- 1.718, 1.718, 1.724, 1.737, 1.752, 1.768, 1.776, 1.782, 1.784, 1.781, 1.766, 1.754, 1.737, 1.724, 1.713, 1.709,
- 1.716, 1.715, 1.716, 1.725, 1.737, 1.749, 1.756, 1.763, 1.764, 1.762, 1.749, 1.737, 1.724, 1.717, 1.709, 1.708,
- 1.715, 1.714, 1.712, 1.715, 1.722, 1.729, 1.736, 1.741, 1.742, 1.739, 1.731, 1.723, 1.717, 1.712, 1.711, 1.709,
- 1.716, 1.714, 1.711, 1.712, 1.715, 1.719, 1.723, 1.728, 1.731, 1.729, 1.723, 1.718, 1.711, 1.711, 1.713, 1.713
- ]
- },
- {
- "ct": 6000, "table":
- [
- 1.374, 1.372, 1.373, 1.374, 1.375, 1.378, 1.378, 1.381, 1.382, 1.382, 1.378, 1.373, 1.372, 1.369, 1.365, 1.365,
- 1.371, 1.371, 1.372, 1.374, 1.378, 1.381, 1.384, 1.386, 1.388, 1.387, 1.384, 1.377, 1.372, 1.368, 1.364, 1.362,
- 1.369, 1.371, 1.372, 1.377, 1.383, 1.391, 1.394, 1.396, 1.397, 1.395, 1.391, 1.382, 1.374, 1.369, 1.362, 1.361,
- 1.369, 1.371, 1.375, 1.383, 1.391, 1.399, 1.402, 1.404, 1.405, 1.403, 1.398, 1.391, 1.379, 1.371, 1.363, 1.361,
- 1.371, 1.373, 1.378, 1.388, 1.399, 1.407, 1.411, 1.413, 1.413, 1.411, 1.405, 1.397, 1.385, 1.374, 1.366, 1.362,
- 1.371, 1.374, 1.379, 1.389, 1.405, 1.411, 1.414, 1.414, 1.415, 1.415, 1.411, 1.401, 1.388, 1.376, 1.367, 1.363,
- 1.371, 1.373, 1.379, 1.389, 1.405, 1.408, 1.413, 1.414, 1.414, 1.413, 1.409, 1.401, 1.388, 1.376, 1.367, 1.362,
- 1.366, 1.369, 1.374, 1.384, 1.396, 1.404, 1.407, 1.408, 1.408, 1.408, 1.401, 1.395, 1.382, 1.371, 1.363, 1.359,
- 1.364, 1.365, 1.368, 1.375, 1.386, 1.396, 1.399, 1.401, 1.399, 1.399, 1.395, 1.385, 1.374, 1.365, 1.359, 1.357,
- 1.361, 1.363, 1.365, 1.368, 1.377, 1.384, 1.388, 1.391, 1.391, 1.388, 1.385, 1.375, 1.366, 1.361, 1.358, 1.356,
- 1.361, 1.362, 1.362, 1.364, 1.367, 1.373, 1.376, 1.377, 1.377, 1.375, 1.373, 1.366, 1.362, 1.358, 1.358, 1.358,
- 1.361, 1.362, 1.362, 1.362, 1.363, 1.367, 1.369, 1.368, 1.367, 1.367, 1.367, 1.364, 1.358, 1.357, 1.358, 1.359
- ]
- }
- ],
- "luminance_lut":
- [
- 2.716, 2.568, 2.299, 2.065, 1.845, 1.693, 1.605, 1.597, 1.596, 1.634, 1.738, 1.914, 2.145, 2.394, 2.719, 2.901,
- 2.593, 2.357, 2.093, 1.876, 1.672, 1.528, 1.438, 1.393, 1.394, 1.459, 1.569, 1.731, 1.948, 2.169, 2.481, 2.756,
- 2.439, 2.197, 1.922, 1.691, 1.521, 1.365, 1.266, 1.222, 1.224, 1.286, 1.395, 1.573, 1.747, 1.988, 2.299, 2.563,
- 2.363, 2.081, 1.797, 1.563, 1.376, 1.244, 1.152, 1.099, 1.101, 1.158, 1.276, 1.421, 1.607, 1.851, 2.163, 2.455,
- 2.342, 2.003, 1.715, 1.477, 1.282, 1.152, 1.074, 1.033, 1.035, 1.083, 1.163, 1.319, 1.516, 1.759, 2.064, 2.398,
- 2.342, 1.985, 1.691, 1.446, 1.249, 1.111, 1.034, 1.004, 1.004, 1.028, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
- 2.342, 1.991, 1.691, 1.446, 1.249, 1.112, 1.034, 1.011, 1.005, 1.035, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
- 2.365, 2.052, 1.751, 1.499, 1.299, 1.171, 1.089, 1.039, 1.042, 1.084, 1.162, 1.312, 1.516, 1.761, 2.059, 2.393,
- 2.434, 2.159, 1.856, 1.601, 1.403, 1.278, 1.166, 1.114, 1.114, 1.162, 1.266, 1.402, 1.608, 1.847, 2.146, 2.435,
- 2.554, 2.306, 2.002, 1.748, 1.563, 1.396, 1.299, 1.247, 1.243, 1.279, 1.386, 1.551, 1.746, 1.977, 2.272, 2.518,
- 2.756, 2.493, 2.195, 1.947, 1.739, 1.574, 1.481, 1.429, 1.421, 1.457, 1.559, 1.704, 1.929, 2.159, 2.442, 2.681,
- 2.935, 2.739, 2.411, 2.151, 1.922, 1.749, 1.663, 1.628, 1.625, 1.635, 1.716, 1.872, 2.113, 2.368, 2.663, 2.824
- ],
- "sigma": 0.00381,
- "sigma_Cb": 0.00216
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2498, "ccm":
- [
- 1.58731, -0.18011, -0.40721, -0.60639, 2.03422, -0.42782, -0.19612, -1.69203, 2.88815
- ]
- },
- {
- "ct": 2811, "ccm":
- [
- 1.61593, -0.33164, -0.28429, -0.55048, 1.97779, -0.42731, -0.12042, -1.42847, 2.54889
- ]
- },
- {
- "ct": 2911, "ccm":
- [
- 1.62771, -0.41282, -0.21489, -0.57991, 2.04176, -0.46186, -0.07613, -1.13359, 2.20972
- ]
- },
- {
- "ct": 2919, "ccm":
- [
- 1.62661, -0.37736, -0.24925, -0.52519, 1.95233, -0.42714, -0.10842, -1.34929, 2.45771
- ]
- },
- {
- "ct": 3627, "ccm":
- [
- 1.70385, -0.57231, -0.13154, -0.47763, 1.85998, -0.38235, -0.07467, -0.82678, 1.90145
- ]
- },
- {
- "ct": 4600, "ccm":
- [
- 1.68486, -0.61085, -0.07402, -0.41927, 2.04016, -0.62089, -0.08633, -0.67672, 1.76305
- ]
- },
- {
- "ct": 5716, "ccm":
- [
- 1.80439, -0.73699, -0.06739, -0.36073, 1.83327, -0.47255, -0.08378, -0.56403, 1.64781
- ]
- },
- {
- "ct": 8575, "ccm":
- [
- 1.89357, -0.76427, -0.12931, -0.27399, 2.15605, -0.88206, -0.12035, -0.68256, 1.80292
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
-
- },
- "rpi.dpc":
- {
-
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx290.json b/src/ipa/raspberrypi/data/imx290.json
deleted file mode 100644
index 20b45c16..00000000
--- a/src/ipa/raspberrypi/data/imx290.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 3840
- },
- "rpi.dpc":
- {
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 6813,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 890,
- "reference_Y": 12900
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.67
- },
- "rpi.geq":
- {
- "offset": 187,
- "slope": 0.00842
- },
- "rpi.sdn":
- {
- },
- "rpi.awb":
- {
- "bayes": 0
- },
- "rpi.agc":
- {
- "speed": 0.2,
- "metering_modes":
- {
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- },
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 10, 30000, 60000
- ],
- "gain":
- [
- 1.0, 2.0, 8.0
- ]
- },
- "sport":
- {
- "shutter":
- [
- 10, 5000, 10000, 20000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.16, 10000, 0.16
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.7,
- "luminance_lut":
- [
- 2.844, 2.349, 2.018, 1.775, 1.599, 1.466, 1.371, 1.321, 1.306, 1.316, 1.357, 1.439, 1.552, 1.705, 1.915, 2.221,
- 2.576, 2.151, 1.851, 1.639, 1.478, 1.358, 1.272, 1.231, 1.218, 1.226, 1.262, 1.335, 1.438, 1.571, 1.766, 2.067,
- 2.381, 2.005, 1.739, 1.545, 1.389, 1.278, 1.204, 1.166, 1.153, 1.161, 1.194, 1.263, 1.356, 1.489, 1.671, 1.943,
- 2.242, 1.899, 1.658, 1.481, 1.329, 1.225, 1.156, 1.113, 1.096, 1.107, 1.143, 1.201, 1.289, 1.423, 1.607, 1.861,
- 2.152, 1.831, 1.602, 1.436, 1.291, 1.193, 1.121, 1.069, 1.047, 1.062, 1.107, 1.166, 1.249, 1.384, 1.562, 1.801,
- 2.104, 1.795, 1.572, 1.407, 1.269, 1.174, 1.099, 1.041, 1.008, 1.029, 1.083, 1.146, 1.232, 1.364, 1.547, 1.766,
- 2.104, 1.796, 1.572, 1.403, 1.264, 1.171, 1.097, 1.036, 1.001, 1.025, 1.077, 1.142, 1.231, 1.363, 1.549, 1.766,
- 2.148, 1.827, 1.594, 1.413, 1.276, 1.184, 1.114, 1.062, 1.033, 1.049, 1.092, 1.153, 1.242, 1.383, 1.577, 1.795,
- 2.211, 1.881, 1.636, 1.455, 1.309, 1.214, 1.149, 1.104, 1.081, 1.089, 1.125, 1.184, 1.273, 1.423, 1.622, 1.846,
- 2.319, 1.958, 1.698, 1.516, 1.362, 1.262, 1.203, 1.156, 1.137, 1.142, 1.171, 1.229, 1.331, 1.484, 1.682, 1.933,
- 2.459, 2.072, 1.789, 1.594, 1.441, 1.331, 1.261, 1.219, 1.199, 1.205, 1.232, 1.301, 1.414, 1.571, 1.773, 2.052,
- 2.645, 2.206, 1.928, 1.728, 1.559, 1.451, 1.352, 1.301, 1.282, 1.289, 1.319, 1.395, 1.519, 1.685, 1.904, 2.227
- ],
- "sigma": 0.005,
- "sigma_Cb": 0.005
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.sharpen":
- {
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 3900, "ccm":
- [
- 1.54659, -0.17707, -0.36953, -0.51471, 1.72733, -0.21262, 0.06667, -0.92279, 1.85612
- ]
- }
- ]
- },
- "rpi.focus":
- {
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx296.json b/src/ipa/raspberrypi/data/imx296.json
deleted file mode 100644
index 837feff5..00000000
--- a/src/ipa/raspberrypi/data/imx296.json
+++ /dev/null
@@ -1,191 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 19184,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 432,
- "reference_Y": 13773
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.957
- },
- "rpi.geq":
- {
- "offset": 185,
- "slope": 0.0105
- },
- "rpi.sdn":
- {
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 6.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 6.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 0,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 4000, "table":
- [
- 2.554, 2.554, 2.541, 2.534, 2.495, 2.506, 2.516, 2.517, 2.518, 2.515, 2.513, 2.495, 2.481, 2.533, 2.533, 2.521,
- 2.522, 2.534, 2.539, 2.531, 2.531, 2.506, 2.506, 2.513, 2.513, 2.509, 2.498, 2.496, 2.508, 2.517, 2.521, 2.521,
- 2.509, 2.517, 2.534, 2.529, 2.531, 2.521, 2.517, 2.517, 2.515, 2.514, 2.506, 2.499, 2.508, 2.508, 2.521, 2.537,
- 2.507, 2.508, 2.517, 2.516, 2.495, 2.487, 2.519, 2.534, 2.535, 2.531, 2.499, 2.494, 2.501, 2.511, 2.526, 2.526,
- 2.509, 2.517, 2.507, 2.501, 2.494, 2.519, 2.539, 2.539, 2.537, 2.537, 2.533, 2.499, 2.503, 2.511, 2.529, 2.525,
- 2.521, 2.522, 2.476, 2.501, 2.501, 2.539, 2.546, 2.538, 2.531, 2.538, 2.541, 2.531, 2.529, 2.526, 2.529, 2.525,
- 2.516, 2.519, 2.469, 2.499, 2.499, 2.543, 2.543, 2.531, 2.528, 2.534, 2.541, 2.535, 2.531, 2.526, 2.531, 2.528,
- 2.509, 2.515, 2.465, 2.487, 2.487, 2.539, 2.543, 2.539, 2.533, 2.549, 2.542, 2.531, 2.529, 2.524, 2.532, 2.533,
- 2.499, 2.499, 2.475, 2.482, 2.471, 2.509, 2.539, 2.544, 2.543, 2.545, 2.533, 2.498, 2.521, 2.521, 2.537, 2.536,
- 2.499, 2.488, 2.488, 2.488, 2.471, 2.462, 2.509, 2.539, 2.539, 2.532, 2.498, 2.498, 2.518, 2.518, 2.539, 2.539,
- 2.483, 2.484, 2.488, 2.488, 2.502, 2.496, 2.508, 2.514, 2.518, 2.517, 2.521, 2.518, 2.518, 2.518, 2.525, 2.539,
- 2.483, 2.487, 2.478, 2.478, 2.507, 2.509, 2.514, 2.513, 2.514, 2.517, 2.536, 2.559, 2.501, 2.501, 2.503, 2.525
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 4000, "table":
- [
- 2.619, 2.603, 2.599, 2.597, 2.595, 2.594, 2.589, 2.587, 2.586, 2.589, 2.592, 2.597, 2.601, 2.608, 2.621, 2.621,
- 2.619, 2.615, 2.603, 2.601, 2.596, 2.595, 2.591, 2.589, 2.589, 2.592, 2.599, 2.593, 2.601, 2.613, 2.622, 2.631,
- 2.617, 2.617, 2.612, 2.611, 2.604, 2.598, 2.593, 2.591, 2.592, 2.591, 2.593, 2.595, 2.599, 2.614, 2.623, 2.631,
- 2.624, 2.619, 2.615, 2.612, 2.605, 2.602, 2.597, 2.596, 2.592, 2.592, 2.595, 2.599, 2.602, 2.606, 2.619, 2.624,
- 2.629, 2.627, 2.627, 2.617, 2.609, 2.598, 2.612, 2.623, 2.615, 2.604, 2.589, 2.595, 2.599, 2.608, 2.611, 2.614,
- 2.629, 2.632, 2.637, 2.627, 2.612, 2.612, 2.629, 2.631, 2.628, 2.621, 2.604, 2.597, 2.598, 2.604, 2.609, 2.609,
- 2.635, 2.636, 2.642, 2.628, 2.623, 2.623, 2.636, 2.636, 2.634, 2.628, 2.616, 2.599, 2.597, 2.601, 2.603, 2.601,
- 2.641, 2.639, 2.646, 2.632, 2.627, 2.625, 2.632, 2.635, 2.634, 2.627, 2.614, 2.596, 2.595, 2.599, 2.599, 2.598,
- 2.643, 2.644, 2.651, 2.649, 2.629, 2.617, 2.624, 2.629, 2.625, 2.614, 2.586, 2.599, 2.595, 2.597, 2.592, 2.595,
- 2.645, 2.646, 2.649, 2.649, 2.638, 2.624, 2.616, 2.617, 2.609, 2.604, 2.603, 2.603, 2.595, 2.589, 2.587, 2.592,
- 2.641, 2.643, 2.649, 2.647, 2.638, 2.618, 2.615, 2.608, 2.602, 2.595, 2.596, 2.595, 2.593, 2.584, 2.581, 2.583,
- 2.638, 2.637, 2.647, 2.634, 2.634, 2.618, 2.621, 2.621, 2.611, 2.602, 2.596, 2.583, 2.581, 2.581, 2.576, 2.574
- ]
- }
- ],
- "luminance_lut":
- [
- 1.308, 1.293, 1.228, 1.175, 1.139, 1.108, 1.092, 1.082, 1.082, 1.086, 1.097, 1.114, 1.149, 1.199, 1.279, 1.303,
- 1.293, 1.249, 1.199, 1.162, 1.136, 1.109, 1.087, 1.077, 1.072, 1.081, 1.095, 1.103, 1.133, 1.172, 1.225, 1.282,
- 1.251, 1.212, 1.186, 1.159, 1.129, 1.114, 1.102, 1.088, 1.088, 1.088, 1.095, 1.117, 1.123, 1.158, 1.198, 1.249,
- 1.223, 1.192, 1.177, 1.163, 1.147, 1.139, 1.132, 1.112, 1.111, 1.107, 1.113, 1.118, 1.139, 1.155, 1.186, 1.232,
- 1.207, 1.186, 1.171, 1.162, 1.168, 1.163, 1.153, 1.138, 1.129, 1.128, 1.132, 1.136, 1.149, 1.167, 1.189, 1.216,
- 1.198, 1.186, 1.176, 1.176, 1.177, 1.185, 1.171, 1.157, 1.146, 1.144, 1.146, 1.149, 1.161, 1.181, 1.201, 1.221,
- 1.203, 1.181, 1.176, 1.178, 1.191, 1.189, 1.188, 1.174, 1.159, 1.153, 1.158, 1.161, 1.169, 1.185, 1.211, 1.227,
- 1.211, 1.179, 1.177, 1.187, 1.194, 1.196, 1.194, 1.187, 1.176, 1.169, 1.171, 1.171, 1.175, 1.189, 1.214, 1.226,
- 1.219, 1.182, 1.184, 1.191, 1.195, 1.199, 1.197, 1.194, 1.188, 1.185, 1.179, 1.179, 1.182, 1.194, 1.212, 1.227,
- 1.237, 1.192, 1.194, 1.194, 1.198, 1.199, 1.198, 1.197, 1.196, 1.193, 1.189, 1.189, 1.192, 1.203, 1.214, 1.231,
- 1.282, 1.199, 1.199, 1.197, 1.199, 1.199, 1.192, 1.193, 1.193, 1.194, 1.196, 1.197, 1.206, 1.216, 1.228, 1.244,
- 1.309, 1.236, 1.204, 1.203, 1.202, 1.194, 1.194, 1.188, 1.192, 1.192, 1.199, 1.201, 1.212, 1.221, 1.235, 1.247
- ],
- "sigma": 0.005,
- "sigma_Cb": 0.005
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.sharpen":
- {
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx378.json b/src/ipa/raspberrypi/data/imx378.json
deleted file mode 100644
index 66200345..00000000
--- a/src/ipa/raspberrypi/data/imx378.json
+++ /dev/null
@@ -1,338 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 9999,
- "reference_gain": 1.95,
- "reference_aperture": 1.0,
- "reference_lux": 1000,
- "reference_Y": 12996
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.641
- },
- "rpi.geq":
- {
- "offset": 235,
- "slope": 0.00902
- },
- "rpi.sdn":
- {
- },
- "rpi.awb":
- {
- "priors":
- [
- {
- "lux": 0, "prior":
- [
- 2000, 1.0, 3000, 0.0, 13000, 0.0
- ]
- },
- {
- "lux": 800, "prior":
- [
- 2000, 0.0, 6000, 2.0, 13000, 2.0
- ]
- },
- {
- "lux": 1500, "prior":
- [
- 2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0
- ]
- }
- ],
- "modes":
- {
- "auto":
- {
- "lo": 2500,
- "hi": 8000
- },
- "incandescent":
- {
- "lo": 2500,
- "hi": 3000
- },
- "tungsten":
- {
- "lo": 3000,
- "hi": 3500
- },
- "fluorescent":
- {
- "lo": 4000,
- "hi": 4700
- },
- "indoor":
- {
- "lo": 3000,
- "hi": 5000
- },
- "daylight":
- {
- "lo": 5500,
- "hi": 6500
- },
- "cloudy":
- {
- "lo": 7000,
- "hi": 8100
- }
- },
- "bayes": 1,
- "ct_curve":
- [
- 2850.0, 0.6361, 0.3911, 3550.0, 0.5386, 0.5077, 4500.0, 0.4472, 0.6171, 5600.0, 0.3906, 0.6848, 8000.0, 0.3412, 0.7441
- ],
- "sensitivity_r": 1.0,
- "sensitivity_b": 1.0,
- "transverse_pos": 0.01667,
- "transverse_neg": 0.01195
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 2800, "table":
- [
- 1.604, 1.601, 1.593, 1.581, 1.568, 1.561, 1.561, 1.561, 1.561, 1.567, 1.582, 1.596, 1.609, 1.622, 1.632, 1.636,
- 1.601, 1.594, 1.586, 1.571, 1.555, 1.546, 1.543, 1.543, 1.547, 1.555, 1.572, 1.584, 1.599, 1.614, 1.625, 1.632,
- 1.599, 1.586, 1.571, 1.555, 1.542, 1.528, 1.518, 1.518, 1.523, 1.537, 1.555, 1.572, 1.589, 1.607, 1.622, 1.629,
- 1.597, 1.579, 1.561, 1.542, 1.528, 1.512, 1.493, 1.493, 1.499, 1.523, 1.537, 1.563, 1.582, 1.601, 1.619, 1.629,
- 1.597, 1.577, 1.557, 1.535, 1.512, 1.493, 1.481, 1.479, 1.492, 1.499, 1.524, 1.555, 1.578, 1.599, 1.619, 1.629,
- 1.597, 1.577, 1.557, 1.534, 1.508, 1.483, 1.476, 1.476, 1.481, 1.496, 1.522, 1.554, 1.578, 1.599, 1.619, 1.629,
- 1.597, 1.578, 1.557, 1.534, 1.508, 1.483, 1.481, 1.479, 1.481, 1.496, 1.522, 1.554, 1.579, 1.601, 1.619, 1.631,
- 1.597, 1.581, 1.562, 1.539, 1.517, 1.504, 1.483, 1.481, 1.496, 1.511, 1.531, 1.561, 1.585, 1.607, 1.623, 1.632,
- 1.601, 1.589, 1.569, 1.554, 1.539, 1.517, 1.504, 1.504, 1.511, 1.531, 1.553, 1.573, 1.596, 1.614, 1.629, 1.636,
- 1.609, 1.601, 1.586, 1.569, 1.554, 1.542, 1.535, 1.535, 1.541, 1.553, 1.573, 1.592, 1.608, 1.625, 1.637, 1.645,
- 1.617, 1.611, 1.601, 1.586, 1.574, 1.565, 1.564, 1.564, 1.571, 1.579, 1.592, 1.608, 1.622, 1.637, 1.646, 1.654,
- 1.619, 1.617, 1.611, 1.601, 1.588, 1.585, 1.585, 1.585, 1.588, 1.592, 1.607, 1.622, 1.637, 1.645, 1.654, 1.655
- ]
- },
- {
- "ct": 5500, "table":
- [
- 2.664, 2.658, 2.645, 2.629, 2.602, 2.602, 2.602, 2.606, 2.617, 2.628, 2.649, 2.677, 2.699, 2.722, 2.736, 2.747,
- 2.658, 2.653, 2.629, 2.605, 2.576, 2.575, 2.577, 2.592, 2.606, 2.618, 2.629, 2.651, 2.678, 2.707, 2.727, 2.741,
- 2.649, 2.631, 2.605, 2.576, 2.563, 2.552, 2.552, 2.557, 2.577, 2.604, 2.619, 2.641, 2.669, 2.698, 2.721, 2.741,
- 2.643, 2.613, 2.583, 2.563, 2.552, 2.531, 2.527, 2.527, 2.551, 2.577, 2.604, 2.638, 2.665, 2.694, 2.721, 2.741,
- 2.643, 2.606, 2.575, 2.558, 2.531, 2.516, 2.504, 2.516, 2.527, 2.551, 2.596, 2.635, 2.665, 2.694, 2.721, 2.741,
- 2.643, 2.606, 2.575, 2.558, 2.531, 2.503, 2.501, 2.502, 2.522, 2.551, 2.592, 2.635, 2.669, 2.696, 2.727, 2.744,
- 2.648, 2.611, 2.579, 2.558, 2.532, 2.511, 2.502, 2.511, 2.522, 2.552, 2.592, 2.642, 2.673, 2.702, 2.731, 2.752,
- 2.648, 2.619, 2.589, 2.571, 2.556, 2.532, 2.519, 2.522, 2.552, 2.568, 2.605, 2.648, 2.683, 2.715, 2.743, 2.758,
- 2.659, 2.637, 2.613, 2.589, 2.571, 2.556, 2.555, 2.555, 2.568, 2.605, 2.641, 2.671, 2.699, 2.729, 2.758, 2.776,
- 2.679, 2.665, 2.637, 2.613, 2.602, 2.599, 2.599, 2.606, 2.619, 2.641, 2.671, 2.698, 2.723, 2.754, 2.776, 2.787,
- 2.695, 2.684, 2.671, 2.646, 2.636, 2.636, 2.641, 2.648, 2.661, 2.681, 2.698, 2.723, 2.751, 2.776, 2.788, 2.803,
- 2.702, 2.699, 2.684, 2.671, 2.664, 2.664, 2.664, 2.668, 2.681, 2.698, 2.723, 2.751, 2.773, 2.788, 2.803, 2.805
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 2800, "table":
- [
- 2.876, 2.868, 2.863, 2.851, 2.846, 2.846, 2.847, 2.851, 2.851, 2.857, 2.867, 2.875, 2.889, 2.899, 2.913, 2.926,
- 2.863, 2.861, 2.856, 2.846, 2.846, 2.847, 2.848, 2.851, 2.857, 2.859, 2.875, 2.882, 2.886, 2.896, 2.909, 2.917,
- 2.861, 2.856, 2.846, 2.841, 2.841, 2.855, 2.867, 2.875, 2.888, 2.888, 2.885, 2.883, 2.886, 2.889, 2.901, 2.913,
- 2.858, 2.851, 2.846, 2.846, 2.855, 2.867, 2.884, 2.895, 2.902, 2.902, 2.901, 2.891, 2.891, 2.894, 2.901, 2.909,
- 2.858, 2.851, 2.846, 2.846, 2.867, 2.884, 2.895, 2.902, 2.909, 2.915, 2.911, 2.901, 2.895, 2.898, 2.904, 2.909,
- 2.858, 2.851, 2.849, 2.853, 2.874, 2.888, 2.901, 2.909, 2.917, 2.922, 2.917, 2.911, 2.901, 2.899, 2.905, 2.908,
- 2.861, 2.855, 2.853, 2.855, 2.874, 2.888, 2.901, 2.913, 2.918, 2.922, 2.921, 2.911, 2.901, 2.901, 2.907, 2.908,
- 2.862, 2.859, 2.855, 2.856, 2.872, 2.885, 2.899, 2.906, 2.915, 2.917, 2.911, 2.907, 2.907, 2.907, 2.908, 2.909,
- 2.863, 2.863, 2.859, 2.864, 2.871, 2.881, 2.885, 2.899, 2.905, 2.905, 2.904, 2.904, 2.907, 2.909, 2.913, 2.913,
- 2.866, 2.865, 2.865, 2.867, 2.868, 2.872, 2.881, 2.885, 2.889, 2.894, 2.895, 2.902, 2.906, 2.913, 2.914, 2.917,
- 2.875, 2.875, 2.871, 2.871, 2.871, 2.871, 2.869, 2.869, 2.878, 2.889, 2.894, 2.895, 2.906, 2.914, 2.917, 2.921,
- 2.882, 2.879, 2.876, 2.874, 2.871, 2.871, 2.869, 2.869, 2.869, 2.878, 2.891, 2.894, 2.905, 2.914, 2.919, 2.921
- ]
- },
- {
- "ct": 5500, "table":
- [
- 1.488, 1.488, 1.488, 1.488, 1.491, 1.492, 1.492, 1.491, 1.491, 1.491, 1.492, 1.495, 1.497, 1.499, 1.499, 1.503,
- 1.482, 1.485, 1.485, 1.487, 1.489, 1.492, 1.492, 1.492, 1.492, 1.492, 1.494, 1.494, 1.492, 1.491, 1.493, 1.494,
- 1.482, 1.482, 1.484, 1.485, 1.487, 1.492, 1.496, 1.498, 1.499, 1.498, 1.494, 1.492, 1.491, 1.491, 1.491, 1.491,
- 1.481, 1.481, 1.482, 1.485, 1.491, 1.496, 1.498, 1.499, 1.501, 1.499, 1.498, 1.493, 1.491, 1.488, 1.488, 1.488,
- 1.481, 1.481, 1.481, 1.483, 1.491, 1.497, 1.498, 1.499, 1.501, 1.499, 1.498, 1.492, 1.488, 1.485, 1.483, 1.483,
- 1.479, 1.479, 1.481, 1.482, 1.489, 1.495, 1.497, 1.498, 1.499, 1.499, 1.495, 1.492, 1.485, 1.482, 1.482, 1.481,
- 1.479, 1.479, 1.479, 1.481, 1.489, 1.494, 1.496, 1.497, 1.497, 1.496, 1.495, 1.489, 1.482, 1.481, 1.479, 1.477,
- 1.478, 1.478, 1.479, 1.481, 1.487, 1.491, 1.494, 1.496, 1.496, 1.495, 1.492, 1.487, 1.482, 1.479, 1.478, 1.476,
- 1.478, 1.478, 1.479, 1.482, 1.486, 1.488, 1.491, 1.493, 1.493, 1.492, 1.487, 1.484, 1.481, 1.479, 1.476, 1.476,
- 1.477, 1.479, 1.481, 1.483, 1.485, 1.486, 1.488, 1.488, 1.487, 1.487, 1.484, 1.483, 1.481, 1.479, 1.476, 1.476,
- 1.477, 1.479, 1.482, 1.483, 1.484, 1.485, 1.484, 1.482, 1.482, 1.484, 1.483, 1.482, 1.481, 1.479, 1.477, 1.476,
- 1.477, 1.479, 1.482, 1.483, 1.484, 1.484, 1.482, 1.482, 1.482, 1.482, 1.482, 1.481, 1.479, 1.479, 1.479, 1.479
- ]
- }
- ],
- "luminance_lut":
- [
- 2.764, 2.654, 2.321, 2.043, 1.768, 1.594, 1.558, 1.558, 1.558, 1.568, 1.661, 1.904, 2.193, 2.497, 2.888, 3.043,
- 2.654, 2.373, 2.049, 1.819, 1.569, 1.446, 1.381, 1.356, 1.356, 1.403, 1.501, 1.679, 1.939, 2.218, 2.586, 2.888,
- 2.376, 2.154, 1.819, 1.569, 1.438, 1.301, 1.246, 1.224, 1.224, 1.263, 1.349, 1.501, 1.679, 1.985, 2.359, 2.609,
- 2.267, 1.987, 1.662, 1.438, 1.301, 1.235, 1.132, 1.105, 1.105, 1.164, 1.263, 1.349, 1.528, 1.808, 2.184, 2.491,
- 2.218, 1.876, 1.568, 1.367, 1.235, 1.132, 1.087, 1.022, 1.023, 1.104, 1.164, 1.278, 1.439, 1.695, 2.066, 2.429,
- 2.218, 1.832, 1.533, 1.341, 1.206, 1.089, 1.013, 1.002, 1.013, 1.026, 1.122, 1.246, 1.399, 1.642, 2.004, 2.426,
- 2.218, 1.832, 1.533, 1.341, 1.206, 1.089, 1.011, 1.001, 1.009, 1.026, 1.122, 1.246, 1.399, 1.642, 2.004, 2.426,
- 2.224, 1.896, 1.584, 1.382, 1.248, 1.147, 1.088, 1.016, 1.026, 1.118, 1.168, 1.283, 1.444, 1.697, 2.066, 2.428,
- 2.292, 2.019, 1.689, 1.462, 1.322, 1.247, 1.147, 1.118, 1.118, 1.168, 1.275, 1.358, 1.532, 1.809, 2.189, 2.491,
- 2.444, 2.204, 1.856, 1.606, 1.462, 1.322, 1.257, 1.234, 1.234, 1.275, 1.358, 1.516, 1.686, 1.993, 2.371, 2.622,
- 2.748, 2.444, 2.108, 1.856, 1.606, 1.476, 1.399, 1.376, 1.376, 1.422, 1.516, 1.686, 1.968, 2.238, 2.611, 2.935,
- 2.862, 2.748, 2.395, 2.099, 1.811, 1.621, 1.582, 1.582, 1.582, 1.592, 1.677, 1.919, 2.223, 2.534, 2.935, 3.078
- ],
- "sigma": 0.00428,
- "sigma_Cb": 0.00363
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2850, "ccm":
- [
- 1.42601, -0.20537, -0.22063, -0.47682, 1.81987, -0.34305, 0.01854, -0.86036, 1.84181
- ]
- },
- {
- "ct": 2900, "ccm":
- [
- 1.29755, 0.04602, -0.34356, -0.41491, 1.73477, -0.31987, -0.01345, -0.97115, 1.98459
- ]
- },
- {
- "ct": 3550, "ccm":
- [
- 1.49811, -0.33412, -0.16398, -0.40869, 1.72995, -0.32127, -0.01924, -0.62181, 1.64105
- ]
- },
- {
- "ct": 4500, "ccm":
- [
- 1.47015, -0.29229, -0.17786, -0.36561, 1.88919, -0.52358, -0.03552, -0.56717, 1.60269
- ]
- },
- {
- "ct": 5600, "ccm":
- [
- 1.60962, -0.47434, -0.13528, -0.32701, 1.73797, -0.41096, -0.07626, -0.40171, 1.47796
- ]
- },
- {
- "ct": 8000, "ccm":
- [
- 1.54642, -0.20396, -0.34246, -0.31748, 2.22559, -0.90811, -0.10035, -0.65877, 1.75912
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx477.json b/src/ipa/raspberrypi/data/imx477.json
deleted file mode 100644
index d07febd2..00000000
--- a/src/ipa/raspberrypi/data/imx477.json
+++ /dev/null
@@ -1,430 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
-
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 27242,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 830,
- "reference_Y": 17755
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.767
- },
- "rpi.geq":
- {
- "offset": 204,
- "slope": 0.01078
- },
- "rpi.sdn":
- {
-
- },
- "rpi.awb":
- {
- "priors":
- [
- {
- "lux": 0, "prior":
- [
- 2000, 1.0, 3000, 0.0, 13000, 0.0
- ]
- },
- {
- "lux": 800, "prior":
- [
- 2000, 0.0, 6000, 2.0, 13000, 2.0
- ]
- },
- {
- "lux": 1500, "prior":
- [
- 2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0
- ]
- }
- ],
- "modes":
- {
- "auto":
- {
- "lo": 2500,
- "hi": 8000
- },
- "incandescent":
- {
- "lo": 2500,
- "hi": 3000
- },
- "tungsten":
- {
- "lo": 3000,
- "hi": 3500
- },
- "fluorescent":
- {
- "lo": 4000,
- "hi": 4700
- },
- "indoor":
- {
- "lo": 3000,
- "hi": 5000
- },
- "daylight":
- {
- "lo": 5500,
- "hi": 6500
- },
- "cloudy":
- {
- "lo": 7000,
- "hi": 8600
- }
- },
- "bayes": 1,
- "ct_curve":
- [
- 2360.0, 0.6009, 0.3093, 2870.0, 0.5047, 0.3936, 2970.0, 0.4782, 0.4221, 3700.0, 0.4212, 0.4923, 3870.0, 0.4037, 0.5166, 4000.0,
- 0.3965, 0.5271, 4400.0, 0.3703, 0.5666, 4715.0, 0.3411, 0.6147, 5920.0, 0.3108, 0.6687, 9050.0, 0.2524, 0.7856
- ],
- "sensitivity_r": 1.05,
- "sensitivity_b": 1.05,
- "transverse_pos": 0.0238,
- "transverse_neg": 0.04429
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 66666
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 33333
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "long":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 12.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.3, 1000, 0.3
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.3, 1000, 0.3
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ],
- "shadows":
- [
- {
- "bound": "LOWER", "q_lo": 0.0, "q_hi": 0.5, "y_target":
- [
- 0, 0.17, 1000, 0.17
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 2960, "table":
- [
- 2.088, 2.086, 2.082, 2.081, 2.077, 2.071, 2.068, 2.068, 2.072, 2.073, 2.075, 2.078, 2.084, 2.092, 2.095, 2.098,
- 2.086, 2.084, 2.079, 2.078, 2.075, 2.068, 2.064, 2.063, 2.068, 2.071, 2.072, 2.075, 2.081, 2.089, 2.092, 2.094,
- 2.083, 2.081, 2.077, 2.072, 2.069, 2.062, 2.059, 2.059, 2.063, 2.067, 2.069, 2.072, 2.079, 2.088, 2.089, 2.089,
- 2.081, 2.077, 2.072, 2.068, 2.065, 2.058, 2.055, 2.054, 2.057, 2.062, 2.066, 2.069, 2.077, 2.084, 2.086, 2.086,
- 2.078, 2.075, 2.069, 2.065, 2.061, 2.055, 2.052, 2.049, 2.051, 2.056, 2.062, 2.065, 2.072, 2.079, 2.081, 2.079,
- 2.079, 2.075, 2.069, 2.064, 2.061, 2.053, 2.049, 2.046, 2.049, 2.051, 2.057, 2.062, 2.069, 2.075, 2.077, 2.075,
- 2.082, 2.079, 2.072, 2.065, 2.061, 2.054, 2.049, 2.047, 2.049, 2.051, 2.056, 2.061, 2.066, 2.073, 2.073, 2.069,
- 2.086, 2.082, 2.075, 2.068, 2.062, 2.054, 2.051, 2.049, 2.051, 2.052, 2.056, 2.061, 2.066, 2.073, 2.073, 2.072,
- 2.088, 2.086, 2.079, 2.074, 2.066, 2.057, 2.051, 2.051, 2.054, 2.055, 2.056, 2.061, 2.067, 2.072, 2.073, 2.072,
- 2.091, 2.087, 2.079, 2.075, 2.068, 2.057, 2.052, 2.052, 2.056, 2.055, 2.055, 2.059, 2.066, 2.072, 2.072, 2.072,
- 2.093, 2.088, 2.081, 2.077, 2.069, 2.059, 2.054, 2.054, 2.057, 2.056, 2.056, 2.058, 2.066, 2.072, 2.073, 2.073,
- 2.095, 2.091, 2.084, 2.078, 2.075, 2.067, 2.057, 2.057, 2.059, 2.059, 2.058, 2.059, 2.068, 2.073, 2.075, 2.078
- ]
- },
- {
- "ct": 4850, "table":
- [
- 2.973, 2.968, 2.956, 2.943, 2.941, 2.932, 2.923, 2.921, 2.924, 2.929, 2.931, 2.939, 2.953, 2.965, 2.966, 2.976,
- 2.969, 2.962, 2.951, 2.941, 2.934, 2.928, 2.919, 2.918, 2.919, 2.923, 2.927, 2.933, 2.945, 2.957, 2.962, 2.962,
- 2.964, 2.956, 2.944, 2.932, 2.929, 2.924, 2.915, 2.914, 2.915, 2.919, 2.924, 2.928, 2.941, 2.952, 2.958, 2.959,
- 2.957, 2.951, 2.939, 2.928, 2.924, 2.919, 2.913, 2.911, 2.911, 2.915, 2.919, 2.925, 2.936, 2.947, 2.952, 2.953,
- 2.954, 2.947, 2.935, 2.924, 2.919, 2.915, 2.908, 2.906, 2.906, 2.907, 2.914, 2.921, 2.932, 2.941, 2.943, 2.942,
- 2.953, 2.946, 2.932, 2.921, 2.916, 2.911, 2.904, 2.902, 2.901, 2.904, 2.909, 2.919, 2.926, 2.937, 2.939, 2.939,
- 2.953, 2.947, 2.932, 2.918, 2.915, 2.909, 2.903, 2.901, 2.901, 2.906, 2.911, 2.918, 2.924, 2.936, 2.936, 2.932,
- 2.956, 2.948, 2.934, 2.919, 2.916, 2.908, 2.903, 2.901, 2.902, 2.907, 2.909, 2.917, 2.926, 2.936, 2.939, 2.939,
- 2.957, 2.951, 2.936, 2.923, 2.917, 2.907, 2.904, 2.901, 2.902, 2.908, 2.911, 2.919, 2.929, 2.939, 2.942, 2.942,
- 2.961, 2.951, 2.936, 2.922, 2.918, 2.906, 2.904, 2.901, 2.901, 2.907, 2.911, 2.921, 2.931, 2.941, 2.942, 2.944,
- 2.964, 2.954, 2.936, 2.924, 2.918, 2.909, 2.905, 2.905, 2.905, 2.907, 2.912, 2.923, 2.933, 2.942, 2.944, 2.944,
- 2.964, 2.958, 2.943, 2.927, 2.921, 2.914, 2.909, 2.907, 2.907, 2.912, 2.916, 2.928, 2.936, 2.944, 2.947, 2.952
- ]
- },
- {
- "ct": 5930, "table":
- [
- 3.312, 3.308, 3.301, 3.294, 3.288, 3.277, 3.268, 3.261, 3.259, 3.261, 3.267, 3.273, 3.285, 3.301, 3.303, 3.312,
- 3.308, 3.304, 3.294, 3.291, 3.283, 3.271, 3.263, 3.259, 3.257, 3.258, 3.261, 3.268, 3.278, 3.293, 3.299, 3.299,
- 3.302, 3.296, 3.288, 3.282, 3.276, 3.267, 3.259, 3.254, 3.252, 3.253, 3.256, 3.261, 3.273, 3.289, 3.292, 3.292,
- 3.296, 3.289, 3.282, 3.276, 3.269, 3.263, 3.256, 3.251, 3.248, 3.249, 3.251, 3.257, 3.268, 3.279, 3.284, 3.284,
- 3.292, 3.285, 3.279, 3.271, 3.264, 3.257, 3.249, 3.243, 3.241, 3.241, 3.246, 3.252, 3.261, 3.274, 3.275, 3.273,
- 3.291, 3.285, 3.276, 3.268, 3.259, 3.251, 3.242, 3.239, 3.236, 3.238, 3.244, 3.248, 3.258, 3.268, 3.269, 3.265,
- 3.294, 3.288, 3.275, 3.266, 3.257, 3.248, 3.239, 3.238, 3.237, 3.238, 3.243, 3.246, 3.255, 3.264, 3.264, 3.257,
- 3.297, 3.293, 3.279, 3.268, 3.258, 3.249, 3.238, 3.237, 3.239, 3.239, 3.243, 3.245, 3.255, 3.264, 3.264, 3.263,
- 3.301, 3.295, 3.281, 3.271, 3.259, 3.248, 3.237, 3.237, 3.239, 3.241, 3.243, 3.246, 3.257, 3.265, 3.266, 3.264,
- 3.306, 3.295, 3.279, 3.271, 3.261, 3.247, 3.235, 3.234, 3.239, 3.239, 3.243, 3.247, 3.258, 3.265, 3.265, 3.264,
- 3.308, 3.297, 3.279, 3.272, 3.261, 3.249, 3.239, 3.239, 3.241, 3.243, 3.245, 3.248, 3.261, 3.265, 3.266, 3.265,
- 3.309, 3.301, 3.286, 3.276, 3.267, 3.256, 3.246, 3.242, 3.244, 3.244, 3.249, 3.253, 3.263, 3.267, 3.271, 3.274
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 2960, "table":
- [
- 2.133, 2.134, 2.139, 2.143, 2.148, 2.155, 2.158, 2.158, 2.158, 2.161, 2.161, 2.162, 2.159, 2.156, 2.152, 2.151,
- 2.132, 2.133, 2.135, 2.142, 2.147, 2.153, 2.158, 2.158, 2.158, 2.158, 2.159, 2.159, 2.157, 2.154, 2.151, 2.148,
- 2.133, 2.133, 2.135, 2.142, 2.149, 2.154, 2.158, 2.158, 2.157, 2.156, 2.158, 2.157, 2.155, 2.153, 2.148, 2.146,
- 2.133, 2.133, 2.138, 2.145, 2.149, 2.154, 2.158, 2.159, 2.158, 2.155, 2.157, 2.156, 2.153, 2.149, 2.146, 2.144,
- 2.133, 2.134, 2.139, 2.146, 2.149, 2.154, 2.158, 2.159, 2.159, 2.156, 2.154, 2.154, 2.149, 2.145, 2.143, 2.139,
- 2.135, 2.135, 2.139, 2.146, 2.151, 2.155, 2.158, 2.159, 2.158, 2.156, 2.153, 2.151, 2.146, 2.143, 2.139, 2.136,
- 2.135, 2.135, 2.138, 2.145, 2.151, 2.154, 2.157, 2.158, 2.157, 2.156, 2.153, 2.151, 2.147, 2.143, 2.141, 2.137,
- 2.135, 2.134, 2.135, 2.141, 2.149, 2.154, 2.157, 2.157, 2.157, 2.157, 2.157, 2.153, 2.149, 2.146, 2.142, 2.139,
- 2.132, 2.133, 2.135, 2.139, 2.148, 2.153, 2.158, 2.159, 2.159, 2.161, 2.161, 2.157, 2.154, 2.149, 2.144, 2.141,
- 2.132, 2.133, 2.135, 2.141, 2.149, 2.155, 2.161, 2.161, 2.162, 2.162, 2.163, 2.159, 2.154, 2.149, 2.144, 2.138,
- 2.136, 2.136, 2.137, 2.143, 2.149, 2.156, 2.162, 2.163, 2.162, 2.163, 2.164, 2.161, 2.157, 2.152, 2.146, 2.138,
- 2.137, 2.137, 2.141, 2.147, 2.152, 2.157, 2.162, 2.162, 2.159, 2.161, 2.162, 2.162, 2.157, 2.152, 2.148, 2.148
- ]
- },
- {
- "ct": 4850, "table":
- [
- 1.463, 1.464, 1.471, 1.478, 1.479, 1.483, 1.484, 1.486, 1.486, 1.484, 1.483, 1.481, 1.478, 1.475, 1.471, 1.468,
- 1.463, 1.463, 1.468, 1.476, 1.479, 1.482, 1.484, 1.487, 1.486, 1.484, 1.483, 1.482, 1.478, 1.473, 1.469, 1.468,
- 1.463, 1.464, 1.468, 1.476, 1.479, 1.483, 1.484, 1.486, 1.486, 1.485, 1.484, 1.482, 1.477, 1.473, 1.469, 1.468,
- 1.463, 1.464, 1.469, 1.477, 1.481, 1.483, 1.485, 1.487, 1.487, 1.485, 1.485, 1.482, 1.478, 1.474, 1.469, 1.468,
- 1.465, 1.465, 1.471, 1.478, 1.481, 1.484, 1.486, 1.488, 1.488, 1.487, 1.485, 1.482, 1.477, 1.472, 1.468, 1.467,
- 1.465, 1.466, 1.472, 1.479, 1.482, 1.485, 1.486, 1.488, 1.488, 1.486, 1.484, 1.479, 1.475, 1.472, 1.468, 1.466,
- 1.466, 1.466, 1.472, 1.478, 1.482, 1.484, 1.485, 1.488, 1.487, 1.485, 1.483, 1.479, 1.475, 1.472, 1.469, 1.468,
- 1.465, 1.466, 1.469, 1.476, 1.481, 1.485, 1.485, 1.486, 1.486, 1.485, 1.483, 1.479, 1.477, 1.474, 1.471, 1.469,
- 1.464, 1.465, 1.469, 1.476, 1.481, 1.484, 1.485, 1.487, 1.487, 1.486, 1.485, 1.481, 1.478, 1.475, 1.471, 1.469,
- 1.463, 1.464, 1.469, 1.477, 1.481, 1.485, 1.485, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.471, 1.468,
- 1.464, 1.465, 1.471, 1.478, 1.482, 1.486, 1.486, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.472, 1.468,
- 1.465, 1.466, 1.472, 1.481, 1.483, 1.487, 1.487, 1.488, 1.488, 1.486, 1.485, 1.481, 1.479, 1.476, 1.473, 1.472
- ]
- },
- {
- "ct": 5930, "table":
- [
- 1.443, 1.444, 1.448, 1.453, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.466, 1.462, 1.457, 1.454, 1.451,
- 1.443, 1.444, 1.445, 1.451, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.451,
- 1.444, 1.444, 1.445, 1.451, 1.459, 1.463, 1.466, 1.468, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.449,
- 1.444, 1.444, 1.447, 1.452, 1.459, 1.464, 1.467, 1.469, 1.471, 1.469, 1.467, 1.466, 1.461, 1.456, 1.452, 1.449,
- 1.444, 1.445, 1.448, 1.452, 1.459, 1.465, 1.469, 1.471, 1.471, 1.471, 1.468, 1.465, 1.461, 1.455, 1.451, 1.449,
- 1.445, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.471, 1.472, 1.469, 1.467, 1.465, 1.459, 1.455, 1.451, 1.447,
- 1.446, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.459, 1.455, 1.452, 1.449,
- 1.446, 1.446, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.461, 1.457, 1.454, 1.451,
- 1.444, 1.444, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.471, 1.471, 1.468, 1.466, 1.462, 1.458, 1.454, 1.452,
- 1.444, 1.444, 1.448, 1.453, 1.459, 1.466, 1.469, 1.471, 1.472, 1.472, 1.468, 1.466, 1.462, 1.458, 1.454, 1.449,
- 1.446, 1.447, 1.449, 1.454, 1.461, 1.466, 1.471, 1.471, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.455, 1.449,
- 1.447, 1.447, 1.452, 1.457, 1.462, 1.468, 1.472, 1.472, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.456, 1.455
- ]
- }
- ],
- "luminance_lut":
- [
- 1.548, 1.499, 1.387, 1.289, 1.223, 1.183, 1.164, 1.154, 1.153, 1.169, 1.211, 1.265, 1.345, 1.448, 1.581, 1.619,
- 1.513, 1.412, 1.307, 1.228, 1.169, 1.129, 1.105, 1.098, 1.103, 1.127, 1.157, 1.209, 1.272, 1.361, 1.481, 1.583,
- 1.449, 1.365, 1.257, 1.175, 1.124, 1.085, 1.062, 1.054, 1.059, 1.079, 1.113, 1.151, 1.211, 1.293, 1.407, 1.488,
- 1.424, 1.324, 1.222, 1.139, 1.089, 1.056, 1.034, 1.031, 1.034, 1.049, 1.075, 1.115, 1.164, 1.241, 1.351, 1.446,
- 1.412, 1.297, 1.203, 1.119, 1.069, 1.039, 1.021, 1.016, 1.022, 1.032, 1.052, 1.086, 1.135, 1.212, 1.321, 1.439,
- 1.406, 1.287, 1.195, 1.115, 1.059, 1.028, 1.014, 1.012, 1.015, 1.026, 1.041, 1.074, 1.125, 1.201, 1.302, 1.425,
- 1.406, 1.294, 1.205, 1.126, 1.062, 1.031, 1.013, 1.009, 1.011, 1.019, 1.042, 1.079, 1.129, 1.203, 1.302, 1.435,
- 1.415, 1.318, 1.229, 1.146, 1.076, 1.039, 1.019, 1.014, 1.017, 1.031, 1.053, 1.093, 1.144, 1.219, 1.314, 1.436,
- 1.435, 1.348, 1.246, 1.164, 1.094, 1.059, 1.036, 1.032, 1.037, 1.049, 1.072, 1.114, 1.167, 1.257, 1.343, 1.462,
- 1.471, 1.385, 1.278, 1.189, 1.124, 1.084, 1.064, 1.061, 1.069, 1.078, 1.101, 1.146, 1.207, 1.298, 1.415, 1.496,
- 1.522, 1.436, 1.323, 1.228, 1.169, 1.118, 1.101, 1.094, 1.099, 1.113, 1.146, 1.194, 1.265, 1.353, 1.474, 1.571,
- 1.578, 1.506, 1.378, 1.281, 1.211, 1.156, 1.135, 1.134, 1.139, 1.158, 1.194, 1.251, 1.327, 1.427, 1.559, 1.611
- ],
- "sigma": 0.00121,
- "sigma_Cb": 0.00115
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2360, "ccm":
- [
- 1.66078, -0.23588, -0.42491, -0.47456, 1.82763, -0.35307, -0.00545, -1.44729, 2.45273
- ]
- },
- {
- "ct": 2870, "ccm":
- [
- 1.78373, -0.55344, -0.23029, -0.39951, 1.69701, -0.29751, 0.01986, -1.06525, 2.04539
- ]
- },
- {
- "ct": 2970, "ccm":
- [
- 1.73511, -0.56973, -0.16537, -0.36338, 1.69878, -0.33539, -0.02354, -0.76813, 1.79168
- ]
- },
- {
- "ct": 3000, "ccm":
- [
- 2.06374, -0.92218, -0.14156, -0.41721, 1.69289, -0.27568, -0.00554, -0.92741, 1.93295
- ]
- },
- {
- "ct": 3700, "ccm":
- [
- 2.13792, -1.08136, -0.05655, -0.34739, 1.58989, -0.24249, -0.00349, -0.76789, 1.77138
- ]
- },
- {
- "ct": 3870, "ccm":
- [
- 1.83834, -0.70528, -0.13307, -0.30499, 1.60523, -0.30024, -0.05701, -0.58313, 1.64014
- ]
- },
- {
- "ct": 4000, "ccm":
- [
- 2.15741, -1.10295, -0.05447, -0.34631, 1.61158, -0.26528, -0.02723, -0.70288, 1.73011
- ]
- },
- {
- "ct": 4400, "ccm":
- [
- 2.05729, -0.95007, -0.10723, -0.41712, 1.78606, -0.36894, -0.11899, -0.55727, 1.67626
- ]
- },
-
- {
- "ct": 4715, "ccm":
- [
- 1.90255, -0.77478, -0.12777, -0.31338, 1.88197, -0.56858, -0.06001, -0.61785, 1.67786
- ]
- },
- {
- "ct": 5920, "ccm":
- [
- 1.98691, -0.84671, -0.14019, -0.26581, 1.70615, -0.44035, -0.09532, -0.47332, 1.56864
- ]
- },
- {
- "ct": 9050, "ccm":
- [
- 2.09255, -0.76541, -0.32714, -0.28973, 2.27462, -0.98489, -0.17299, -0.61275, 1.78574
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
-
- },
- "rpi.focus":
- {
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx477_noir.json b/src/ipa/raspberrypi/data/imx477_noir.json
deleted file mode 100644
index 7d4fc7da..00000000
--- a/src/ipa/raspberrypi/data/imx477_noir.json
+++ /dev/null
@@ -1,362 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
-
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 27242,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 830,
- "reference_Y": 17755
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.767
- },
- "rpi.geq":
- {
- "offset": 204,
- "slope": 0.01078
- },
- "rpi.sdn":
- {
-
- },
- "rpi.awb":
- {
- "bayes": 0
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 66666
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 33333
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "long":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 12.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.3, 1000, 0.3
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.3, 1000, 0.3
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ],
- "shadows":
- [
- {
- "bound": "LOWER", "q_lo": 0.0, "q_hi": 0.5, "y_target":
- [
- 0, 0.17, 1000, 0.17
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 2960, "table":
- [
- 2.088, 2.086, 2.082, 2.081, 2.077, 2.071, 2.068, 2.068, 2.072, 2.073, 2.075, 2.078, 2.084, 2.092, 2.095, 2.098,
- 2.086, 2.084, 2.079, 2.078, 2.075, 2.068, 2.064, 2.063, 2.068, 2.071, 2.072, 2.075, 2.081, 2.089, 2.092, 2.094,
- 2.083, 2.081, 2.077, 2.072, 2.069, 2.062, 2.059, 2.059, 2.063, 2.067, 2.069, 2.072, 2.079, 2.088, 2.089, 2.089,
- 2.081, 2.077, 2.072, 2.068, 2.065, 2.058, 2.055, 2.054, 2.057, 2.062, 2.066, 2.069, 2.077, 2.084, 2.086, 2.086,
- 2.078, 2.075, 2.069, 2.065, 2.061, 2.055, 2.052, 2.049, 2.051, 2.056, 2.062, 2.065, 2.072, 2.079, 2.081, 2.079,
- 2.079, 2.075, 2.069, 2.064, 2.061, 2.053, 2.049, 2.046, 2.049, 2.051, 2.057, 2.062, 2.069, 2.075, 2.077, 2.075,
- 2.082, 2.079, 2.072, 2.065, 2.061, 2.054, 2.049, 2.047, 2.049, 2.051, 2.056, 2.061, 2.066, 2.073, 2.073, 2.069,
- 2.086, 2.082, 2.075, 2.068, 2.062, 2.054, 2.051, 2.049, 2.051, 2.052, 2.056, 2.061, 2.066, 2.073, 2.073, 2.072,
- 2.088, 2.086, 2.079, 2.074, 2.066, 2.057, 2.051, 2.051, 2.054, 2.055, 2.056, 2.061, 2.067, 2.072, 2.073, 2.072,
- 2.091, 2.087, 2.079, 2.075, 2.068, 2.057, 2.052, 2.052, 2.056, 2.055, 2.055, 2.059, 2.066, 2.072, 2.072, 2.072,
- 2.093, 2.088, 2.081, 2.077, 2.069, 2.059, 2.054, 2.054, 2.057, 2.056, 2.056, 2.058, 2.066, 2.072, 2.073, 2.073,
- 2.095, 2.091, 2.084, 2.078, 2.075, 2.067, 2.057, 2.057, 2.059, 2.059, 2.058, 2.059, 2.068, 2.073, 2.075, 2.078
- ]
- },
- {
- "ct": 4850, "table":
- [
- 2.973, 2.968, 2.956, 2.943, 2.941, 2.932, 2.923, 2.921, 2.924, 2.929, 2.931, 2.939, 2.953, 2.965, 2.966, 2.976,
- 2.969, 2.962, 2.951, 2.941, 2.934, 2.928, 2.919, 2.918, 2.919, 2.923, 2.927, 2.933, 2.945, 2.957, 2.962, 2.962,
- 2.964, 2.956, 2.944, 2.932, 2.929, 2.924, 2.915, 2.914, 2.915, 2.919, 2.924, 2.928, 2.941, 2.952, 2.958, 2.959,
- 2.957, 2.951, 2.939, 2.928, 2.924, 2.919, 2.913, 2.911, 2.911, 2.915, 2.919, 2.925, 2.936, 2.947, 2.952, 2.953,
- 2.954, 2.947, 2.935, 2.924, 2.919, 2.915, 2.908, 2.906, 2.906, 2.907, 2.914, 2.921, 2.932, 2.941, 2.943, 2.942,
- 2.953, 2.946, 2.932, 2.921, 2.916, 2.911, 2.904, 2.902, 2.901, 2.904, 2.909, 2.919, 2.926, 2.937, 2.939, 2.939,
- 2.953, 2.947, 2.932, 2.918, 2.915, 2.909, 2.903, 2.901, 2.901, 2.906, 2.911, 2.918, 2.924, 2.936, 2.936, 2.932,
- 2.956, 2.948, 2.934, 2.919, 2.916, 2.908, 2.903, 2.901, 2.902, 2.907, 2.909, 2.917, 2.926, 2.936, 2.939, 2.939,
- 2.957, 2.951, 2.936, 2.923, 2.917, 2.907, 2.904, 2.901, 2.902, 2.908, 2.911, 2.919, 2.929, 2.939, 2.942, 2.942,
- 2.961, 2.951, 2.936, 2.922, 2.918, 2.906, 2.904, 2.901, 2.901, 2.907, 2.911, 2.921, 2.931, 2.941, 2.942, 2.944,
- 2.964, 2.954, 2.936, 2.924, 2.918, 2.909, 2.905, 2.905, 2.905, 2.907, 2.912, 2.923, 2.933, 2.942, 2.944, 2.944,
- 2.964, 2.958, 2.943, 2.927, 2.921, 2.914, 2.909, 2.907, 2.907, 2.912, 2.916, 2.928, 2.936, 2.944, 2.947, 2.952
- ]
- },
- {
- "ct": 5930, "table":
- [
- 3.312, 3.308, 3.301, 3.294, 3.288, 3.277, 3.268, 3.261, 3.259, 3.261, 3.267, 3.273, 3.285, 3.301, 3.303, 3.312,
- 3.308, 3.304, 3.294, 3.291, 3.283, 3.271, 3.263, 3.259, 3.257, 3.258, 3.261, 3.268, 3.278, 3.293, 3.299, 3.299,
- 3.302, 3.296, 3.288, 3.282, 3.276, 3.267, 3.259, 3.254, 3.252, 3.253, 3.256, 3.261, 3.273, 3.289, 3.292, 3.292,
- 3.296, 3.289, 3.282, 3.276, 3.269, 3.263, 3.256, 3.251, 3.248, 3.249, 3.251, 3.257, 3.268, 3.279, 3.284, 3.284,
- 3.292, 3.285, 3.279, 3.271, 3.264, 3.257, 3.249, 3.243, 3.241, 3.241, 3.246, 3.252, 3.261, 3.274, 3.275, 3.273,
- 3.291, 3.285, 3.276, 3.268, 3.259, 3.251, 3.242, 3.239, 3.236, 3.238, 3.244, 3.248, 3.258, 3.268, 3.269, 3.265,
- 3.294, 3.288, 3.275, 3.266, 3.257, 3.248, 3.239, 3.238, 3.237, 3.238, 3.243, 3.246, 3.255, 3.264, 3.264, 3.257,
- 3.297, 3.293, 3.279, 3.268, 3.258, 3.249, 3.238, 3.237, 3.239, 3.239, 3.243, 3.245, 3.255, 3.264, 3.264, 3.263,
- 3.301, 3.295, 3.281, 3.271, 3.259, 3.248, 3.237, 3.237, 3.239, 3.241, 3.243, 3.246, 3.257, 3.265, 3.266, 3.264,
- 3.306, 3.295, 3.279, 3.271, 3.261, 3.247, 3.235, 3.234, 3.239, 3.239, 3.243, 3.247, 3.258, 3.265, 3.265, 3.264,
- 3.308, 3.297, 3.279, 3.272, 3.261, 3.249, 3.239, 3.239, 3.241, 3.243, 3.245, 3.248, 3.261, 3.265, 3.266, 3.265,
- 3.309, 3.301, 3.286, 3.276, 3.267, 3.256, 3.246, 3.242, 3.244, 3.244, 3.249, 3.253, 3.263, 3.267, 3.271, 3.274
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 2960, "table":
- [
- 2.133, 2.134, 2.139, 2.143, 2.148, 2.155, 2.158, 2.158, 2.158, 2.161, 2.161, 2.162, 2.159, 2.156, 2.152, 2.151,
- 2.132, 2.133, 2.135, 2.142, 2.147, 2.153, 2.158, 2.158, 2.158, 2.158, 2.159, 2.159, 2.157, 2.154, 2.151, 2.148,
- 2.133, 2.133, 2.135, 2.142, 2.149, 2.154, 2.158, 2.158, 2.157, 2.156, 2.158, 2.157, 2.155, 2.153, 2.148, 2.146,
- 2.133, 2.133, 2.138, 2.145, 2.149, 2.154, 2.158, 2.159, 2.158, 2.155, 2.157, 2.156, 2.153, 2.149, 2.146, 2.144,
- 2.133, 2.134, 2.139, 2.146, 2.149, 2.154, 2.158, 2.159, 2.159, 2.156, 2.154, 2.154, 2.149, 2.145, 2.143, 2.139,
- 2.135, 2.135, 2.139, 2.146, 2.151, 2.155, 2.158, 2.159, 2.158, 2.156, 2.153, 2.151, 2.146, 2.143, 2.139, 2.136,
- 2.135, 2.135, 2.138, 2.145, 2.151, 2.154, 2.157, 2.158, 2.157, 2.156, 2.153, 2.151, 2.147, 2.143, 2.141, 2.137,
- 2.135, 2.134, 2.135, 2.141, 2.149, 2.154, 2.157, 2.157, 2.157, 2.157, 2.157, 2.153, 2.149, 2.146, 2.142, 2.139,
- 2.132, 2.133, 2.135, 2.139, 2.148, 2.153, 2.158, 2.159, 2.159, 2.161, 2.161, 2.157, 2.154, 2.149, 2.144, 2.141,
- 2.132, 2.133, 2.135, 2.141, 2.149, 2.155, 2.161, 2.161, 2.162, 2.162, 2.163, 2.159, 2.154, 2.149, 2.144, 2.138,
- 2.136, 2.136, 2.137, 2.143, 2.149, 2.156, 2.162, 2.163, 2.162, 2.163, 2.164, 2.161, 2.157, 2.152, 2.146, 2.138,
- 2.137, 2.137, 2.141, 2.147, 2.152, 2.157, 2.162, 2.162, 2.159, 2.161, 2.162, 2.162, 2.157, 2.152, 2.148, 2.148
- ]
- },
- {
- "ct": 4850, "table":
- [
- 1.463, 1.464, 1.471, 1.478, 1.479, 1.483, 1.484, 1.486, 1.486, 1.484, 1.483, 1.481, 1.478, 1.475, 1.471, 1.468,
- 1.463, 1.463, 1.468, 1.476, 1.479, 1.482, 1.484, 1.487, 1.486, 1.484, 1.483, 1.482, 1.478, 1.473, 1.469, 1.468,
- 1.463, 1.464, 1.468, 1.476, 1.479, 1.483, 1.484, 1.486, 1.486, 1.485, 1.484, 1.482, 1.477, 1.473, 1.469, 1.468,
- 1.463, 1.464, 1.469, 1.477, 1.481, 1.483, 1.485, 1.487, 1.487, 1.485, 1.485, 1.482, 1.478, 1.474, 1.469, 1.468,
- 1.465, 1.465, 1.471, 1.478, 1.481, 1.484, 1.486, 1.488, 1.488, 1.487, 1.485, 1.482, 1.477, 1.472, 1.468, 1.467,
- 1.465, 1.466, 1.472, 1.479, 1.482, 1.485, 1.486, 1.488, 1.488, 1.486, 1.484, 1.479, 1.475, 1.472, 1.468, 1.466,
- 1.466, 1.466, 1.472, 1.478, 1.482, 1.484, 1.485, 1.488, 1.487, 1.485, 1.483, 1.479, 1.475, 1.472, 1.469, 1.468,
- 1.465, 1.466, 1.469, 1.476, 1.481, 1.485, 1.485, 1.486, 1.486, 1.485, 1.483, 1.479, 1.477, 1.474, 1.471, 1.469,
- 1.464, 1.465, 1.469, 1.476, 1.481, 1.484, 1.485, 1.487, 1.487, 1.486, 1.485, 1.481, 1.478, 1.475, 1.471, 1.469,
- 1.463, 1.464, 1.469, 1.477, 1.481, 1.485, 1.485, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.471, 1.468,
- 1.464, 1.465, 1.471, 1.478, 1.482, 1.486, 1.486, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.472, 1.468,
- 1.465, 1.466, 1.472, 1.481, 1.483, 1.487, 1.487, 1.488, 1.488, 1.486, 1.485, 1.481, 1.479, 1.476, 1.473, 1.472
- ]
- },
- {
- "ct": 5930, "table":
- [
- 1.443, 1.444, 1.448, 1.453, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.466, 1.462, 1.457, 1.454, 1.451,
- 1.443, 1.444, 1.445, 1.451, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.451,
- 1.444, 1.444, 1.445, 1.451, 1.459, 1.463, 1.466, 1.468, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.449,
- 1.444, 1.444, 1.447, 1.452, 1.459, 1.464, 1.467, 1.469, 1.471, 1.469, 1.467, 1.466, 1.461, 1.456, 1.452, 1.449,
- 1.444, 1.445, 1.448, 1.452, 1.459, 1.465, 1.469, 1.471, 1.471, 1.471, 1.468, 1.465, 1.461, 1.455, 1.451, 1.449,
- 1.445, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.471, 1.472, 1.469, 1.467, 1.465, 1.459, 1.455, 1.451, 1.447,
- 1.446, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.459, 1.455, 1.452, 1.449,
- 1.446, 1.446, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.461, 1.457, 1.454, 1.451,
- 1.444, 1.444, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.471, 1.471, 1.468, 1.466, 1.462, 1.458, 1.454, 1.452,
- 1.444, 1.444, 1.448, 1.453, 1.459, 1.466, 1.469, 1.471, 1.472, 1.472, 1.468, 1.466, 1.462, 1.458, 1.454, 1.449,
- 1.446, 1.447, 1.449, 1.454, 1.461, 1.466, 1.471, 1.471, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.455, 1.449,
- 1.447, 1.447, 1.452, 1.457, 1.462, 1.468, 1.472, 1.472, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.456, 1.455
- ]
- }
- ],
- "luminance_lut":
- [
- 1.548, 1.499, 1.387, 1.289, 1.223, 1.183, 1.164, 1.154, 1.153, 1.169, 1.211, 1.265, 1.345, 1.448, 1.581, 1.619,
- 1.513, 1.412, 1.307, 1.228, 1.169, 1.129, 1.105, 1.098, 1.103, 1.127, 1.157, 1.209, 1.272, 1.361, 1.481, 1.583,
- 1.449, 1.365, 1.257, 1.175, 1.124, 1.085, 1.062, 1.054, 1.059, 1.079, 1.113, 1.151, 1.211, 1.293, 1.407, 1.488,
- 1.424, 1.324, 1.222, 1.139, 1.089, 1.056, 1.034, 1.031, 1.034, 1.049, 1.075, 1.115, 1.164, 1.241, 1.351, 1.446,
- 1.412, 1.297, 1.203, 1.119, 1.069, 1.039, 1.021, 1.016, 1.022, 1.032, 1.052, 1.086, 1.135, 1.212, 1.321, 1.439,
- 1.406, 1.287, 1.195, 1.115, 1.059, 1.028, 1.014, 1.012, 1.015, 1.026, 1.041, 1.074, 1.125, 1.201, 1.302, 1.425,
- 1.406, 1.294, 1.205, 1.126, 1.062, 1.031, 1.013, 1.009, 1.011, 1.019, 1.042, 1.079, 1.129, 1.203, 1.302, 1.435,
- 1.415, 1.318, 1.229, 1.146, 1.076, 1.039, 1.019, 1.014, 1.017, 1.031, 1.053, 1.093, 1.144, 1.219, 1.314, 1.436,
- 1.435, 1.348, 1.246, 1.164, 1.094, 1.059, 1.036, 1.032, 1.037, 1.049, 1.072, 1.114, 1.167, 1.257, 1.343, 1.462,
- 1.471, 1.385, 1.278, 1.189, 1.124, 1.084, 1.064, 1.061, 1.069, 1.078, 1.101, 1.146, 1.207, 1.298, 1.415, 1.496,
- 1.522, 1.436, 1.323, 1.228, 1.169, 1.118, 1.101, 1.094, 1.099, 1.113, 1.146, 1.194, 1.265, 1.353, 1.474, 1.571,
- 1.578, 1.506, 1.378, 1.281, 1.211, 1.156, 1.135, 1.134, 1.139, 1.158, 1.194, 1.251, 1.327, 1.427, 1.559, 1.611
- ],
- "sigma": 0.00121,
- "sigma_Cb": 0.00115
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2360, "ccm":
- [
- 1.66078, -0.23588, -0.42491, -0.47456, 1.82763, -0.35307, -0.00545, -1.44729, 2.45273
- ]
- },
- {
- "ct": 2870, "ccm":
- [
- 1.78373, -0.55344, -0.23029, -0.39951, 1.69701, -0.29751, 0.01986, -1.06525, 2.04539
- ]
- },
- {
- "ct": 2970, "ccm":
- [
- 1.73511, -0.56973, -0.16537, -0.36338, 1.69878, -0.33539, -0.02354, -0.76813, 1.79168
- ]
- },
- {
- "ct": 3000, "ccm":
- [
- 2.06374, -0.92218, -0.14156, -0.41721, 1.69289, -0.27568, -0.00554, -0.92741, 1.93295
- ]
- },
- {
- "ct": 3700, "ccm":
- [
- 2.13792, -1.08136, -0.05655, -0.34739, 1.58989, -0.24249, -0.00349, -0.76789, 1.77138
- ]
- },
- {
- "ct": 3870, "ccm":
- [
- 1.83834, -0.70528, -0.13307, -0.30499, 1.60523, -0.30024, -0.05701, -0.58313, 1.64014
- ]
- },
- {
- "ct": 4000, "ccm":
- [
- 2.15741, -1.10295, -0.05447, -0.34631, 1.61158, -0.26528, -0.02723, -0.70288, 1.73011
- ]
- },
- {
- "ct": 4400, "ccm":
- [
- 2.05729, -0.95007, -0.10723, -0.41712, 1.78606, -0.36894, -0.11899, -0.55727, 1.67626
- ]
- },
-
- {
- "ct": 4715, "ccm":
- [
- 1.90255, -0.77478, -0.12777, -0.31338, 1.88197, -0.56858, -0.06001, -0.61785, 1.67786
- ]
- },
- {
- "ct": 5920, "ccm":
- [
- 1.98691, -0.84671, -0.14019, -0.26581, 1.70615, -0.44035, -0.09532, -0.47332, 1.56864
- ]
- },
- {
- "ct": 9050, "ccm":
- [
- 2.09255, -0.76541, -0.32714, -0.28973, 2.27462, -0.98489, -0.17299, -0.61275, 1.78574
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
-
- },
- "rpi.focus":
- {
- }
-}
diff --git a/src/ipa/raspberrypi/data/imx519.json b/src/ipa/raspberrypi/data/imx519.json
deleted file mode 100644
index 2ce6a08c..00000000
--- a/src/ipa/raspberrypi/data/imx519.json
+++ /dev/null
@@ -1,338 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.dpc":
- {
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 13841,
- "reference_gain": 2.0,
- "reference_aperture": 1.0,
- "reference_lux": 900,
- "reference_Y": 12064
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.776
- },
- "rpi.geq":
- {
- "offset": 189,
- "slope": 0.01495
- },
- "rpi.sdn":
- {
- },
- "rpi.awb":
- {
- "priors":
- [
- {
- "lux": 0, "prior":
- [
- 2000, 1.0, 3000, 0.0, 13000, 0.0
- ]
- },
- {
- "lux": 800, "prior":
- [
- 2000, 0.0, 6000, 2.0, 13000, 2.0
- ]
- },
- {
- "lux": 1500, "prior":
- [
- 2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0
- ]
- }
- ],
- "modes":
- {
- "auto":
- {
- "lo": 2500,
- "hi": 7900
- },
- "incandescent":
- {
- "lo": 2500,
- "hi": 3000
- },
- "tungsten":
- {
- "lo": 3000,
- "hi": 3500
- },
- "fluorescent":
- {
- "lo": 4000,
- "hi": 4700
- },
- "indoor":
- {
- "lo": 3000,
- "hi": 5000
- },
- "daylight":
- {
- "lo": 5500,
- "hi": 6500
- },
- "cloudy":
- {
- "lo": 7000,
- "hi": 8000
- }
- },
- "bayes": 1,
- "ct_curve":
- [
- 2890.0, 0.7328, 0.3734, 3550.0, 0.6228, 0.4763, 4500.0, 0.5208, 0.5825, 5700.0, 0.4467, 0.6671, 7900.0, 0.3858, 0.7411
- ],
- "sensitivity_r": 1.0,
- "sensitivity_b": 1.0,
- "transverse_pos": 0.02027,
- "transverse_neg": 0.01935
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 3000, "table":
- [
- 1.527, 1.521, 1.508, 1.493, 1.476, 1.455, 1.442, 1.441, 1.441, 1.441, 1.448, 1.467, 1.483, 1.494, 1.503, 1.504,
- 1.525, 1.513, 1.496, 1.477, 1.461, 1.434, 1.418, 1.409, 1.409, 1.416, 1.429, 1.449, 1.469, 1.485, 1.495, 1.503,
- 1.517, 1.506, 1.485, 1.461, 1.434, 1.412, 1.388, 1.376, 1.376, 1.386, 1.405, 1.429, 1.449, 1.471, 1.488, 1.495,
- 1.512, 1.496, 1.471, 1.442, 1.412, 1.388, 1.361, 1.344, 1.344, 1.358, 1.384, 1.405, 1.431, 1.456, 1.479, 1.489,
- 1.508, 1.488, 1.458, 1.425, 1.393, 1.361, 1.343, 1.322, 1.321, 1.342, 1.358, 1.385, 1.416, 1.445, 1.471, 1.484,
- 1.507, 1.482, 1.453, 1.418, 1.382, 1.349, 1.322, 1.318, 1.318, 1.321, 1.345, 1.373, 1.405, 1.437, 1.465, 1.483,
- 1.507, 1.482, 1.453, 1.418, 1.382, 1.349, 1.322, 1.313, 1.313, 1.321, 1.345, 1.373, 1.405, 1.437, 1.465, 1.483,
- 1.507, 1.485, 1.455, 1.422, 1.387, 1.355, 1.333, 1.319, 1.321, 1.333, 1.351, 1.381, 1.411, 1.441, 1.467, 1.483,
- 1.508, 1.489, 1.463, 1.432, 1.401, 1.372, 1.355, 1.333, 1.333, 1.351, 1.369, 1.393, 1.422, 1.448, 1.471, 1.484,
- 1.511, 1.494, 1.472, 1.444, 1.416, 1.398, 1.372, 1.361, 1.361, 1.369, 1.393, 1.411, 1.436, 1.458, 1.477, 1.487,
- 1.511, 1.496, 1.478, 1.455, 1.436, 1.416, 1.399, 1.391, 1.391, 1.397, 1.411, 1.429, 1.451, 1.466, 1.479, 1.487,
- 1.511, 1.495, 1.478, 1.462, 1.448, 1.432, 1.419, 1.419, 1.419, 1.419, 1.429, 1.445, 1.459, 1.471, 1.482, 1.487
- ]
- },
- {
- "ct": 6000, "table":
- [
- 2.581, 2.573, 2.558, 2.539, 2.514, 2.487, 2.473, 2.471, 2.471, 2.471, 2.479, 2.499, 2.517, 2.532, 2.543, 2.544,
- 2.575, 2.559, 2.539, 2.521, 2.491, 2.458, 2.435, 2.421, 2.421, 2.429, 2.449, 2.477, 2.499, 2.519, 2.534, 2.543,
- 2.561, 2.549, 2.521, 2.491, 2.457, 2.423, 2.393, 2.375, 2.375, 2.387, 2.412, 2.444, 2.475, 2.499, 2.519, 2.532,
- 2.552, 2.531, 2.498, 2.459, 2.423, 2.391, 2.349, 2.325, 2.325, 2.344, 2.374, 2.412, 2.444, 2.476, 2.505, 2.519,
- 2.543, 2.518, 2.479, 2.435, 2.392, 2.349, 2.324, 2.285, 2.283, 2.313, 2.344, 2.374, 2.417, 2.457, 2.489, 2.506,
- 2.541, 2.511, 2.469, 2.421, 2.372, 2.326, 2.284, 2.277, 2.279, 2.283, 2.313, 2.357, 2.401, 2.443, 2.479, 2.504,
- 2.541, 2.511, 2.469, 2.421, 2.372, 2.326, 2.284, 2.267, 2.267, 2.281, 2.313, 2.357, 2.401, 2.443, 2.479, 2.504,
- 2.541, 2.512, 2.472, 2.425, 2.381, 2.338, 2.302, 2.278, 2.279, 2.301, 2.324, 2.364, 2.407, 2.447, 2.481, 2.504,
- 2.544, 2.519, 2.483, 2.441, 2.401, 2.363, 2.338, 2.302, 2.302, 2.324, 2.355, 2.385, 2.423, 2.459, 2.488, 2.506,
- 2.549, 2.527, 2.497, 2.463, 2.427, 2.401, 2.363, 2.345, 2.345, 2.355, 2.385, 2.412, 2.444, 2.473, 2.497, 2.509,
- 2.552, 2.532, 2.507, 2.481, 2.459, 2.427, 2.402, 2.389, 2.389, 2.394, 2.412, 2.444, 2.465, 2.481, 2.499, 2.511,
- 2.553, 2.533, 2.508, 2.489, 2.475, 2.454, 2.429, 2.429, 2.429, 2.429, 2.439, 2.463, 2.481, 2.492, 2.504, 2.511
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 3000, "table":
- [
- 3.132, 3.126, 3.116, 3.103, 3.097, 3.091, 3.087, 3.086, 3.088, 3.091, 3.092, 3.102, 3.113, 3.121, 3.141, 3.144,
- 3.149, 3.132, 3.123, 3.108, 3.101, 3.096, 3.091, 3.089, 3.091, 3.092, 3.101, 3.107, 3.116, 3.129, 3.144, 3.153,
- 3.161, 3.149, 3.129, 3.121, 3.108, 3.103, 3.101, 3.101, 3.101, 3.103, 3.107, 3.116, 3.125, 3.134, 3.153, 3.159,
- 3.176, 3.161, 3.144, 3.129, 3.124, 3.121, 3.117, 3.118, 3.118, 3.119, 3.122, 3.125, 3.134, 3.146, 3.159, 3.171,
- 3.183, 3.176, 3.157, 3.144, 3.143, 3.143, 3.139, 3.141, 3.141, 3.141, 3.141, 3.141, 3.146, 3.161, 3.171, 3.179,
- 3.189, 3.183, 3.165, 3.157, 3.156, 3.157, 3.159, 3.163, 3.163, 3.163, 3.163, 3.161, 3.163, 3.169, 3.179, 3.187,
- 3.199, 3.189, 3.171, 3.165, 3.164, 3.167, 3.171, 3.173, 3.173, 3.172, 3.171, 3.169, 3.169, 3.175, 3.187, 3.189,
- 3.206, 3.196, 3.177, 3.171, 3.165, 3.167, 3.171, 3.173, 3.173, 3.172, 3.171, 3.171, 3.173, 3.177, 3.192, 3.194,
- 3.209, 3.197, 3.178, 3.171, 3.164, 3.161, 3.159, 3.161, 3.162, 3.164, 3.167, 3.171, 3.173, 3.181, 3.193, 3.198,
- 3.204, 3.194, 3.176, 3.165, 3.161, 3.156, 3.154, 3.154, 3.159, 3.161, 3.164, 3.168, 3.173, 3.182, 3.198, 3.199,
- 3.199, 3.191, 3.176, 3.169, 3.161, 3.157, 3.153, 3.153, 3.156, 3.161, 3.164, 3.168, 3.173, 3.186, 3.196, 3.199,
- 3.199, 3.188, 3.179, 3.173, 3.165, 3.157, 3.153, 3.154, 3.156, 3.159, 3.167, 3.171, 3.176, 3.185, 3.193, 3.198
- ]
- },
- {
- "ct": 6000, "table":
- [
- 1.579, 1.579, 1.577, 1.574, 1.573, 1.571, 1.571, 1.571, 1.571, 1.569, 1.569, 1.571, 1.572, 1.574, 1.577, 1.578,
- 1.584, 1.579, 1.578, 1.575, 1.573, 1.572, 1.571, 1.572, 1.572, 1.571, 1.571, 1.572, 1.573, 1.576, 1.578, 1.579,
- 1.587, 1.584, 1.579, 1.578, 1.575, 1.573, 1.573, 1.575, 1.575, 1.574, 1.573, 1.574, 1.576, 1.578, 1.581, 1.581,
- 1.591, 1.587, 1.584, 1.579, 1.578, 1.579, 1.579, 1.581, 1.581, 1.581, 1.578, 1.577, 1.578, 1.581, 1.585, 1.586,
- 1.595, 1.591, 1.587, 1.585, 1.585, 1.586, 1.587, 1.587, 1.588, 1.588, 1.585, 1.584, 1.584, 1.586, 1.589, 1.589,
- 1.597, 1.595, 1.591, 1.589, 1.591, 1.593, 1.595, 1.596, 1.597, 1.597, 1.595, 1.594, 1.592, 1.592, 1.593, 1.593,
- 1.601, 1.597, 1.593, 1.592, 1.593, 1.595, 1.598, 1.599, 1.602, 1.601, 1.598, 1.596, 1.595, 1.596, 1.595, 1.595,
- 1.601, 1.599, 1.594, 1.593, 1.593, 1.595, 1.598, 1.599, 1.602, 1.601, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597,
- 1.602, 1.599, 1.594, 1.593, 1.592, 1.593, 1.595, 1.597, 1.597, 1.598, 1.598, 1.597, 1.597, 1.597, 1.598, 1.598,
- 1.599, 1.598, 1.594, 1.592, 1.591, 1.591, 1.592, 1.595, 1.596, 1.597, 1.597, 1.597, 1.597, 1.599, 1.599, 1.599,
- 1.598, 1.596, 1.594, 1.593, 1.592, 1.592, 1.592, 1.594, 1.595, 1.597, 1.597, 1.597, 1.598, 1.599, 1.599, 1.599,
- 1.597, 1.595, 1.594, 1.594, 1.593, 1.592, 1.593, 1.595, 1.595, 1.597, 1.598, 1.598, 1.598, 1.599, 1.599, 1.599
- ]
- }
- ],
- "luminance_lut":
- [
- 2.887, 2.754, 2.381, 2.105, 1.859, 1.678, 1.625, 1.623, 1.623, 1.624, 1.669, 1.849, 2.092, 2.362, 2.723, 2.838,
- 2.754, 2.443, 2.111, 1.905, 1.678, 1.542, 1.455, 1.412, 1.412, 1.452, 1.535, 1.665, 1.893, 2.096, 2.413, 2.723,
- 2.443, 2.216, 1.911, 1.678, 1.537, 1.372, 1.288, 1.245, 1.245, 1.283, 1.363, 1.527, 1.665, 1.895, 2.193, 2.413,
- 2.318, 2.057, 1.764, 1.541, 1.372, 1.282, 1.159, 1.113, 1.113, 1.151, 1.269, 1.363, 1.527, 1.749, 2.034, 2.278,
- 2.259, 1.953, 1.671, 1.452, 1.283, 1.159, 1.107, 1.018, 1.017, 1.097, 1.151, 1.269, 1.437, 1.655, 1.931, 2.222,
- 2.257, 1.902, 1.624, 1.408, 1.239, 1.111, 1.019, 1.011, 1.005, 1.014, 1.098, 1.227, 1.395, 1.608, 1.883, 2.222,
- 2.257, 1.902, 1.624, 1.408, 1.239, 1.111, 1.016, 1.001, 1.001, 1.007, 1.098, 1.227, 1.395, 1.608, 1.883, 2.222,
- 2.257, 1.946, 1.666, 1.448, 1.281, 1.153, 1.093, 1.013, 1.008, 1.089, 1.143, 1.269, 1.437, 1.654, 1.934, 2.226,
- 2.309, 2.044, 1.756, 1.532, 1.363, 1.259, 1.153, 1.093, 1.093, 1.143, 1.264, 1.354, 1.524, 1.746, 2.035, 2.284,
- 2.425, 2.201, 1.896, 1.662, 1.519, 1.363, 1.259, 1.214, 1.214, 1.264, 1.354, 1.519, 1.655, 1.888, 2.191, 2.413,
- 2.724, 2.417, 2.091, 1.888, 1.662, 1.519, 1.419, 1.373, 1.373, 1.425, 1.521, 1.655, 1.885, 2.089, 2.409, 2.722,
- 2.858, 2.724, 2.356, 2.085, 1.842, 1.658, 1.581, 1.577, 1.577, 1.579, 1.653, 1.838, 2.084, 2.359, 2.722, 2.842
- ],
- "sigma": 0.00372,
- "sigma_Cb": 0.00244
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2890, "ccm":
- [
- 1.36754, -0.18448, -0.18306, -0.32356, 1.44826, -0.12471, -0.00412, -0.69936, 1.70348
- ]
- },
- {
- "ct": 2920, "ccm":
- [
- 1.26704, 0.01624, -0.28328, -0.28516, 1.38934, -0.10419, -0.04854, -0.82211, 1.87066
- ]
- },
- {
- "ct": 3550, "ccm":
- [
- 1.42836, -0.27235, -0.15601, -0.28751, 1.41075, -0.12325, -0.01812, -0.54849, 1.56661
- ]
- },
- {
- "ct": 4500, "ccm":
- [
- 1.36328, -0.19569, -0.16759, -0.25254, 1.52248, -0.26994, -0.01575, -0.53155, 1.54729
- ]
- },
- {
- "ct": 5700, "ccm":
- [
- 1.49207, -0.37245, -0.11963, -0.21493, 1.40005, -0.18512, -0.03781, -0.38779, 1.42561
- ]
- },
- {
- "ct": 7900, "ccm":
- [
- 1.34849, -0.05425, -0.29424, -0.22182, 1.77684, -0.55502, -0.07403, -0.55336, 1.62739
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
- }
-}
diff --git a/src/ipa/raspberrypi/data/ov5647.json b/src/ipa/raspberrypi/data/ov5647.json
deleted file mode 100644
index 24bc06fb..00000000
--- a/src/ipa/raspberrypi/data/ov5647.json
+++ /dev/null
@@ -1,409 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 1024
- },
- "rpi.dpc":
- {
-
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 21663,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 987,
- "reference_Y": 8961
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 4.25
- },
- "rpi.geq":
- {
- "offset": 401,
- "slope": 0.05619
- },
- "rpi.sdn":
- {
-
- },
- "rpi.awb":
- {
- "priors":
- [
- {
- "lux": 0, "prior":
- [
- 2000, 1.0, 3000, 0.0, 13000, 0.0
- ]
- },
- {
- "lux": 800, "prior":
- [
- 2000, 0.0, 6000, 2.0, 13000, 2.0
- ]
- },
- {
- "lux": 1500, "prior":
- [
- 2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0
- ]
- }
- ],
- "modes":
- {
- "auto":
- {
- "lo": 2500,
- "hi": 8000
- },
- "incandescent":
- {
- "lo": 2500,
- "hi": 3000
- },
- "tungsten":
- {
- "lo": 3000,
- "hi": 3500
- },
- "fluorescent":
- {
- "lo": 4000,
- "hi": 4700
- },
- "indoor":
- {
- "lo": 3000,
- "hi": 5000
- },
- "daylight":
- {
- "lo": 5500,
- "hi": 6500
- },
- "cloudy":
- {
- "lo": 7000,
- "hi": 8600
- }
- },
- "bayes": 1,
- "ct_curve":
- [
- 2500.0, 1.0289, 0.4503, 2803.0, 0.9428, 0.5108, 2914.0, 0.9406, 0.5127, 3605.0, 0.8261, 0.6249, 4540.0, 0.7331, 0.7533, 5699.0,
- 0.6715, 0.8627, 8625.0, 0.6081, 1.0012
- ],
- "sensitivity_r": 1.05,
- "sensitivity_b": 1.05,
- "transverse_pos": 0.0321,
- "transverse_neg": 0.04313
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 66666
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 33333
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "long":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 12.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ],
- "shadows":
- [
- {
- "bound": "LOWER", "q_lo": 0.0, "q_hi": 0.5, "y_target":
- [
- 0, 0.17, 1000, 0.17
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ],
- "base_ev": 1.25
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 3000, "table":
- [
- 1.105, 1.103, 1.093, 1.083, 1.071, 1.065, 1.065, 1.065, 1.066, 1.069, 1.072, 1.077, 1.084, 1.089, 1.093, 1.093,
- 1.103, 1.096, 1.084, 1.072, 1.059, 1.051, 1.047, 1.047, 1.051, 1.053, 1.059, 1.067, 1.075, 1.082, 1.085, 1.086,
- 1.096, 1.084, 1.072, 1.059, 1.051, 1.045, 1.039, 1.038, 1.039, 1.045, 1.049, 1.057, 1.063, 1.072, 1.081, 1.082,
- 1.092, 1.075, 1.061, 1.052, 1.045, 1.039, 1.036, 1.035, 1.035, 1.039, 1.044, 1.049, 1.056, 1.063, 1.072, 1.081,
- 1.092, 1.073, 1.058, 1.048, 1.043, 1.038, 1.035, 1.033, 1.033, 1.035, 1.039, 1.044, 1.051, 1.057, 1.069, 1.078,
- 1.091, 1.068, 1.054, 1.045, 1.041, 1.038, 1.035, 1.032, 1.032, 1.032, 1.036, 1.041, 1.045, 1.055, 1.069, 1.078,
- 1.091, 1.068, 1.052, 1.043, 1.041, 1.038, 1.035, 1.032, 1.031, 1.032, 1.034, 1.036, 1.043, 1.055, 1.069, 1.078,
- 1.092, 1.068, 1.052, 1.047, 1.042, 1.041, 1.038, 1.035, 1.032, 1.032, 1.035, 1.039, 1.043, 1.055, 1.071, 1.079,
- 1.092, 1.073, 1.057, 1.051, 1.047, 1.047, 1.044, 1.041, 1.038, 1.038, 1.039, 1.043, 1.051, 1.059, 1.076, 1.083,
- 1.092, 1.081, 1.068, 1.058, 1.056, 1.056, 1.053, 1.052, 1.049, 1.048, 1.048, 1.051, 1.059, 1.066, 1.083, 1.085,
- 1.091, 1.087, 1.081, 1.068, 1.065, 1.064, 1.062, 1.062, 1.061, 1.056, 1.056, 1.056, 1.064, 1.069, 1.084, 1.089,
- 1.091, 1.089, 1.085, 1.079, 1.069, 1.068, 1.067, 1.067, 1.067, 1.063, 1.061, 1.063, 1.068, 1.069, 1.081, 1.092
- ]
- },
- {
- "ct": 5000, "table":
- [
- 1.486, 1.484, 1.468, 1.449, 1.427, 1.403, 1.399, 1.399, 1.399, 1.404, 1.413, 1.433, 1.454, 1.473, 1.482, 1.488,
- 1.484, 1.472, 1.454, 1.431, 1.405, 1.381, 1.365, 1.365, 1.367, 1.373, 1.392, 1.411, 1.438, 1.458, 1.476, 1.481,
- 1.476, 1.458, 1.433, 1.405, 1.381, 1.361, 1.339, 1.334, 1.334, 1.346, 1.362, 1.391, 1.411, 1.438, 1.462, 1.474,
- 1.471, 1.443, 1.417, 1.388, 1.361, 1.339, 1.321, 1.313, 1.313, 1.327, 1.346, 1.362, 1.391, 1.422, 1.453, 1.473,
- 1.469, 1.439, 1.408, 1.377, 1.349, 1.321, 1.312, 1.299, 1.299, 1.311, 1.327, 1.348, 1.378, 1.415, 1.446, 1.468,
- 1.468, 1.434, 1.402, 1.371, 1.341, 1.316, 1.299, 1.296, 1.295, 1.299, 1.314, 1.338, 1.371, 1.408, 1.441, 1.466,
- 1.468, 1.434, 1.401, 1.371, 1.341, 1.316, 1.301, 1.296, 1.295, 1.297, 1.314, 1.338, 1.369, 1.408, 1.441, 1.465,
- 1.469, 1.436, 1.401, 1.374, 1.348, 1.332, 1.315, 1.301, 1.301, 1.313, 1.324, 1.342, 1.372, 1.409, 1.442, 1.465,
- 1.471, 1.444, 1.413, 1.388, 1.371, 1.348, 1.332, 1.323, 1.323, 1.324, 1.342, 1.362, 1.386, 1.418, 1.449, 1.467,
- 1.473, 1.454, 1.431, 1.407, 1.388, 1.371, 1.359, 1.352, 1.351, 1.351, 1.362, 1.383, 1.404, 1.433, 1.462, 1.472,
- 1.474, 1.461, 1.447, 1.424, 1.407, 1.394, 1.385, 1.381, 1.379, 1.381, 1.383, 1.401, 1.419, 1.444, 1.466, 1.481,
- 1.474, 1.464, 1.455, 1.442, 1.421, 1.408, 1.403, 1.403, 1.403, 1.399, 1.402, 1.415, 1.432, 1.446, 1.467, 1.483
- ]
- },
- {
- "ct": 6500, "table":
- [
- 1.567, 1.565, 1.555, 1.541, 1.525, 1.518, 1.518, 1.518, 1.521, 1.527, 1.532, 1.541, 1.551, 1.559, 1.567, 1.569,
- 1.565, 1.557, 1.542, 1.527, 1.519, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.533, 1.542, 1.553, 1.559, 1.562,
- 1.561, 1.546, 1.532, 1.521, 1.518, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.529, 1.533, 1.542, 1.554, 1.559,
- 1.561, 1.539, 1.526, 1.524, 1.521, 1.521, 1.522, 1.524, 1.525, 1.531, 1.529, 1.529, 1.531, 1.538, 1.549, 1.558,
- 1.559, 1.538, 1.526, 1.525, 1.524, 1.528, 1.534, 1.536, 1.536, 1.536, 1.532, 1.529, 1.531, 1.537, 1.548, 1.556,
- 1.561, 1.537, 1.525, 1.524, 1.526, 1.532, 1.537, 1.539, 1.538, 1.537, 1.532, 1.529, 1.529, 1.537, 1.546, 1.556,
- 1.561, 1.536, 1.524, 1.522, 1.525, 1.532, 1.538, 1.538, 1.537, 1.533, 1.528, 1.526, 1.527, 1.536, 1.546, 1.555,
- 1.561, 1.537, 1.522, 1.521, 1.524, 1.531, 1.536, 1.537, 1.534, 1.529, 1.526, 1.522, 1.523, 1.534, 1.547, 1.555,
- 1.561, 1.538, 1.524, 1.522, 1.526, 1.531, 1.535, 1.535, 1.534, 1.527, 1.524, 1.522, 1.522, 1.535, 1.549, 1.556,
- 1.558, 1.543, 1.532, 1.526, 1.526, 1.529, 1.534, 1.535, 1.533, 1.526, 1.523, 1.522, 1.524, 1.537, 1.552, 1.557,
- 1.555, 1.546, 1.541, 1.528, 1.527, 1.528, 1.531, 1.533, 1.531, 1.527, 1.522, 1.522, 1.526, 1.536, 1.552, 1.561,
- 1.555, 1.547, 1.542, 1.538, 1.526, 1.526, 1.529, 1.531, 1.529, 1.528, 1.519, 1.519, 1.527, 1.531, 1.543, 1.561
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 3000, "table":
- [
- 1.684, 1.688, 1.691, 1.697, 1.709, 1.722, 1.735, 1.745, 1.747, 1.745, 1.731, 1.719, 1.709, 1.705, 1.699, 1.699,
- 1.684, 1.689, 1.694, 1.708, 1.721, 1.735, 1.747, 1.762, 1.762, 1.758, 1.745, 1.727, 1.716, 1.707, 1.701, 1.699,
- 1.684, 1.691, 1.704, 1.719, 1.734, 1.755, 1.772, 1.786, 1.789, 1.788, 1.762, 1.745, 1.724, 1.709, 1.702, 1.698,
- 1.682, 1.694, 1.709, 1.729, 1.755, 1.773, 1.798, 1.815, 1.817, 1.808, 1.788, 1.762, 1.733, 1.714, 1.704, 1.699,
- 1.682, 1.693, 1.713, 1.742, 1.772, 1.798, 1.815, 1.829, 1.831, 1.821, 1.807, 1.773, 1.742, 1.716, 1.703, 1.699,
- 1.681, 1.693, 1.713, 1.742, 1.772, 1.799, 1.828, 1.839, 1.839, 1.828, 1.807, 1.774, 1.742, 1.715, 1.699, 1.695,
- 1.679, 1.691, 1.712, 1.739, 1.771, 1.798, 1.825, 1.829, 1.831, 1.818, 1.801, 1.774, 1.738, 1.712, 1.695, 1.691,
- 1.676, 1.685, 1.703, 1.727, 1.761, 1.784, 1.801, 1.817, 1.817, 1.801, 1.779, 1.761, 1.729, 1.706, 1.691, 1.684,
- 1.669, 1.678, 1.692, 1.714, 1.741, 1.764, 1.784, 1.795, 1.795, 1.779, 1.761, 1.738, 1.713, 1.696, 1.683, 1.679,
- 1.664, 1.671, 1.679, 1.693, 1.716, 1.741, 1.762, 1.769, 1.769, 1.753, 1.738, 1.713, 1.701, 1.687, 1.681, 1.676,
- 1.661, 1.664, 1.671, 1.679, 1.693, 1.714, 1.732, 1.739, 1.739, 1.729, 1.708, 1.701, 1.685, 1.679, 1.676, 1.677,
- 1.659, 1.661, 1.664, 1.671, 1.679, 1.693, 1.712, 1.714, 1.714, 1.708, 1.701, 1.687, 1.679, 1.672, 1.673, 1.677
- ]
- },
- {
- "ct": 5000, "table":
- [
- 1.177, 1.183, 1.187, 1.191, 1.197, 1.206, 1.213, 1.215, 1.215, 1.215, 1.211, 1.204, 1.196, 1.191, 1.183, 1.182,
- 1.179, 1.185, 1.191, 1.196, 1.206, 1.217, 1.224, 1.229, 1.229, 1.226, 1.221, 1.212, 1.202, 1.195, 1.188, 1.182,
- 1.183, 1.191, 1.196, 1.206, 1.217, 1.229, 1.239, 1.245, 1.245, 1.245, 1.233, 1.221, 1.212, 1.199, 1.193, 1.187,
- 1.183, 1.192, 1.201, 1.212, 1.229, 1.241, 1.252, 1.259, 1.259, 1.257, 1.245, 1.233, 1.217, 1.201, 1.194, 1.192,
- 1.183, 1.192, 1.202, 1.219, 1.238, 1.252, 1.261, 1.269, 1.268, 1.261, 1.257, 1.241, 1.223, 1.204, 1.194, 1.191,
- 1.182, 1.192, 1.202, 1.219, 1.239, 1.255, 1.266, 1.271, 1.271, 1.265, 1.258, 1.242, 1.223, 1.205, 1.192, 1.191,
- 1.181, 1.189, 1.199, 1.218, 1.239, 1.254, 1.262, 1.268, 1.268, 1.258, 1.253, 1.241, 1.221, 1.204, 1.191, 1.187,
- 1.179, 1.184, 1.193, 1.211, 1.232, 1.243, 1.254, 1.257, 1.256, 1.253, 1.242, 1.232, 1.216, 1.199, 1.187, 1.183,
- 1.174, 1.179, 1.187, 1.202, 1.218, 1.232, 1.243, 1.246, 1.246, 1.239, 1.232, 1.218, 1.207, 1.191, 1.183, 1.179,
- 1.169, 1.175, 1.181, 1.189, 1.202, 1.218, 1.229, 1.232, 1.232, 1.224, 1.218, 1.207, 1.199, 1.185, 1.181, 1.174,
- 1.164, 1.168, 1.175, 1.179, 1.189, 1.201, 1.209, 1.213, 1.213, 1.209, 1.201, 1.198, 1.186, 1.181, 1.174, 1.173,
- 1.161, 1.166, 1.171, 1.175, 1.179, 1.189, 1.197, 1.198, 1.198, 1.197, 1.196, 1.186, 1.182, 1.175, 1.173, 1.173
- ]
- },
- {
- "ct": 6500, "table":
- [
- 1.166, 1.171, 1.173, 1.178, 1.187, 1.193, 1.201, 1.205, 1.205, 1.205, 1.199, 1.191, 1.184, 1.179, 1.174, 1.171,
- 1.166, 1.172, 1.176, 1.184, 1.195, 1.202, 1.209, 1.216, 1.216, 1.213, 1.208, 1.201, 1.189, 1.182, 1.176, 1.171,
- 1.166, 1.173, 1.183, 1.195, 1.202, 1.214, 1.221, 1.228, 1.229, 1.228, 1.221, 1.209, 1.201, 1.186, 1.179, 1.174,
- 1.165, 1.174, 1.187, 1.201, 1.214, 1.223, 1.235, 1.241, 1.242, 1.241, 1.229, 1.221, 1.205, 1.188, 1.181, 1.177,
- 1.165, 1.174, 1.189, 1.207, 1.223, 1.235, 1.242, 1.253, 1.252, 1.245, 1.241, 1.228, 1.211, 1.189, 1.181, 1.178,
- 1.164, 1.173, 1.189, 1.207, 1.224, 1.238, 1.249, 1.255, 1.255, 1.249, 1.242, 1.228, 1.211, 1.191, 1.179, 1.176,
- 1.163, 1.172, 1.187, 1.207, 1.223, 1.237, 1.245, 1.253, 1.252, 1.243, 1.237, 1.228, 1.207, 1.188, 1.176, 1.173,
- 1.159, 1.167, 1.179, 1.199, 1.217, 1.227, 1.237, 1.241, 1.241, 1.237, 1.228, 1.217, 1.201, 1.184, 1.174, 1.169,
- 1.156, 1.164, 1.172, 1.189, 1.205, 1.217, 1.226, 1.229, 1.229, 1.222, 1.217, 1.204, 1.192, 1.177, 1.171, 1.166,
- 1.154, 1.159, 1.166, 1.177, 1.189, 1.205, 1.213, 1.216, 1.216, 1.209, 1.204, 1.192, 1.183, 1.172, 1.168, 1.162,
- 1.152, 1.155, 1.161, 1.166, 1.177, 1.188, 1.195, 1.198, 1.199, 1.196, 1.187, 1.183, 1.173, 1.168, 1.163, 1.162,
- 1.151, 1.154, 1.158, 1.162, 1.168, 1.177, 1.183, 1.184, 1.184, 1.184, 1.182, 1.172, 1.168, 1.165, 1.162, 1.161
- ]
- }
- ],
- "luminance_lut":
- [
- 2.236, 2.111, 1.912, 1.741, 1.579, 1.451, 1.379, 1.349, 1.349, 1.361, 1.411, 1.505, 1.644, 1.816, 2.034, 2.159,
- 2.139, 1.994, 1.796, 1.625, 1.467, 1.361, 1.285, 1.248, 1.239, 1.265, 1.321, 1.408, 1.536, 1.703, 1.903, 2.087,
- 2.047, 1.898, 1.694, 1.511, 1.373, 1.254, 1.186, 1.152, 1.142, 1.166, 1.226, 1.309, 1.441, 1.598, 1.799, 1.978,
- 1.999, 1.824, 1.615, 1.429, 1.281, 1.179, 1.113, 1.077, 1.071, 1.096, 1.153, 1.239, 1.357, 1.525, 1.726, 1.915,
- 1.976, 1.773, 1.563, 1.374, 1.222, 1.119, 1.064, 1.032, 1.031, 1.049, 1.099, 1.188, 1.309, 1.478, 1.681, 1.893,
- 1.973, 1.756, 1.542, 1.351, 1.196, 1.088, 1.028, 1.011, 1.004, 1.029, 1.077, 1.169, 1.295, 1.459, 1.663, 1.891,
- 1.973, 1.761, 1.541, 1.349, 1.193, 1.087, 1.031, 1.006, 1.006, 1.023, 1.075, 1.169, 1.298, 1.463, 1.667, 1.891,
- 1.982, 1.789, 1.568, 1.373, 1.213, 1.111, 1.051, 1.029, 1.024, 1.053, 1.106, 1.199, 1.329, 1.495, 1.692, 1.903,
- 2.015, 1.838, 1.621, 1.426, 1.268, 1.159, 1.101, 1.066, 1.068, 1.099, 1.166, 1.259, 1.387, 1.553, 1.751, 1.937,
- 2.076, 1.911, 1.692, 1.507, 1.346, 1.236, 1.169, 1.136, 1.139, 1.174, 1.242, 1.349, 1.475, 1.641, 1.833, 2.004,
- 2.193, 2.011, 1.798, 1.604, 1.444, 1.339, 1.265, 1.235, 1.237, 1.273, 1.351, 1.461, 1.598, 1.758, 1.956, 2.125,
- 2.263, 2.154, 1.916, 1.711, 1.549, 1.432, 1.372, 1.356, 1.356, 1.383, 1.455, 1.578, 1.726, 1.914, 2.119, 2.211
- ],
- "sigma": 0.006,
- "sigma_Cb": 0.00208
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2500, "ccm":
- [
- 1.70741, -0.05307, -0.65433, -0.62822, 1.68836, -0.06014, -0.04452, -1.87628, 2.92079
- ]
- },
- {
- "ct": 2803, "ccm":
- [
- 1.74383, -0.18731, -0.55652, -0.56491, 1.67772, -0.11281, -0.01522, -1.60635, 2.62157
- ]
- },
- {
- "ct": 2912, "ccm":
- [
- 1.75215, -0.22221, -0.52995, -0.54568, 1.63522, -0.08954, 0.02633, -1.56997, 2.54364
- ]
- },
- {
- "ct": 2914, "ccm":
- [
- 1.72423, -0.28939, -0.43484, -0.55188, 1.62925, -0.07737, 0.01959, -1.28661, 2.26702
- ]
- },
- {
- "ct": 3605, "ccm":
- [
- 1.80381, -0.43646, -0.36735, -0.46505, 1.56814, -0.10309, 0.00929, -1.00424, 1.99495
- ]
- },
- {
- "ct": 4540, "ccm":
- [
- 1.85263, -0.46545, -0.38719, -0.44136, 1.68443, -0.24307, 0.04108, -0.85599, 1.81491
- ]
- },
- {
- "ct": 5699, "ccm":
- [
- 1.98595, -0.63542, -0.35054, -0.34623, 1.54146, -0.19522, 0.00411, -0.70936, 1.70525
- ]
- },
- {
- "ct": 8625, "ccm":
- [
- 2.21637, -0.56663, -0.64974, -0.41133, 1.96625, -0.55492, -0.02307, -0.83529, 1.85837
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
-
- }
-}
diff --git a/src/ipa/raspberrypi/data/ov5647_noir.json b/src/ipa/raspberrypi/data/ov5647_noir.json
deleted file mode 100644
index 1c628ed1..00000000
--- a/src/ipa/raspberrypi/data/ov5647_noir.json
+++ /dev/null
@@ -1,341 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 1024
- },
- "rpi.dpc":
- {
-
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 21663,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 987,
- "reference_Y": 8961
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 4.25
- },
- "rpi.geq":
- {
- "offset": 401,
- "slope": 0.05619
- },
- "rpi.sdn":
- {
-
- },
- "rpi.awb":
- {
- "bayes": 0
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 66666
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 33333
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "long":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 12.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ],
- "shadows":
- [
- {
- "bound": "LOWER", "q_lo": 0.0, "q_hi": 0.5, "y_target":
- [
- 0, 0.17, 1000, 0.17
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ],
- "base_ev": 1.25
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 3000, "table":
- [
- 1.105, 1.103, 1.093, 1.083, 1.071, 1.065, 1.065, 1.065, 1.066, 1.069, 1.072, 1.077, 1.084, 1.089, 1.093, 1.093,
- 1.103, 1.096, 1.084, 1.072, 1.059, 1.051, 1.047, 1.047, 1.051, 1.053, 1.059, 1.067, 1.075, 1.082, 1.085, 1.086,
- 1.096, 1.084, 1.072, 1.059, 1.051, 1.045, 1.039, 1.038, 1.039, 1.045, 1.049, 1.057, 1.063, 1.072, 1.081, 1.082,
- 1.092, 1.075, 1.061, 1.052, 1.045, 1.039, 1.036, 1.035, 1.035, 1.039, 1.044, 1.049, 1.056, 1.063, 1.072, 1.081,
- 1.092, 1.073, 1.058, 1.048, 1.043, 1.038, 1.035, 1.033, 1.033, 1.035, 1.039, 1.044, 1.051, 1.057, 1.069, 1.078,
- 1.091, 1.068, 1.054, 1.045, 1.041, 1.038, 1.035, 1.032, 1.032, 1.032, 1.036, 1.041, 1.045, 1.055, 1.069, 1.078,
- 1.091, 1.068, 1.052, 1.043, 1.041, 1.038, 1.035, 1.032, 1.031, 1.032, 1.034, 1.036, 1.043, 1.055, 1.069, 1.078,
- 1.092, 1.068, 1.052, 1.047, 1.042, 1.041, 1.038, 1.035, 1.032, 1.032, 1.035, 1.039, 1.043, 1.055, 1.071, 1.079,
- 1.092, 1.073, 1.057, 1.051, 1.047, 1.047, 1.044, 1.041, 1.038, 1.038, 1.039, 1.043, 1.051, 1.059, 1.076, 1.083,
- 1.092, 1.081, 1.068, 1.058, 1.056, 1.056, 1.053, 1.052, 1.049, 1.048, 1.048, 1.051, 1.059, 1.066, 1.083, 1.085,
- 1.091, 1.087, 1.081, 1.068, 1.065, 1.064, 1.062, 1.062, 1.061, 1.056, 1.056, 1.056, 1.064, 1.069, 1.084, 1.089,
- 1.091, 1.089, 1.085, 1.079, 1.069, 1.068, 1.067, 1.067, 1.067, 1.063, 1.061, 1.063, 1.068, 1.069, 1.081, 1.092
- ]
- },
- {
- "ct": 5000, "table":
- [
- 1.486, 1.484, 1.468, 1.449, 1.427, 1.403, 1.399, 1.399, 1.399, 1.404, 1.413, 1.433, 1.454, 1.473, 1.482, 1.488,
- 1.484, 1.472, 1.454, 1.431, 1.405, 1.381, 1.365, 1.365, 1.367, 1.373, 1.392, 1.411, 1.438, 1.458, 1.476, 1.481,
- 1.476, 1.458, 1.433, 1.405, 1.381, 1.361, 1.339, 1.334, 1.334, 1.346, 1.362, 1.391, 1.411, 1.438, 1.462, 1.474,
- 1.471, 1.443, 1.417, 1.388, 1.361, 1.339, 1.321, 1.313, 1.313, 1.327, 1.346, 1.362, 1.391, 1.422, 1.453, 1.473,
- 1.469, 1.439, 1.408, 1.377, 1.349, 1.321, 1.312, 1.299, 1.299, 1.311, 1.327, 1.348, 1.378, 1.415, 1.446, 1.468,
- 1.468, 1.434, 1.402, 1.371, 1.341, 1.316, 1.299, 1.296, 1.295, 1.299, 1.314, 1.338, 1.371, 1.408, 1.441, 1.466,
- 1.468, 1.434, 1.401, 1.371, 1.341, 1.316, 1.301, 1.296, 1.295, 1.297, 1.314, 1.338, 1.369, 1.408, 1.441, 1.465,
- 1.469, 1.436, 1.401, 1.374, 1.348, 1.332, 1.315, 1.301, 1.301, 1.313, 1.324, 1.342, 1.372, 1.409, 1.442, 1.465,
- 1.471, 1.444, 1.413, 1.388, 1.371, 1.348, 1.332, 1.323, 1.323, 1.324, 1.342, 1.362, 1.386, 1.418, 1.449, 1.467,
- 1.473, 1.454, 1.431, 1.407, 1.388, 1.371, 1.359, 1.352, 1.351, 1.351, 1.362, 1.383, 1.404, 1.433, 1.462, 1.472,
- 1.474, 1.461, 1.447, 1.424, 1.407, 1.394, 1.385, 1.381, 1.379, 1.381, 1.383, 1.401, 1.419, 1.444, 1.466, 1.481,
- 1.474, 1.464, 1.455, 1.442, 1.421, 1.408, 1.403, 1.403, 1.403, 1.399, 1.402, 1.415, 1.432, 1.446, 1.467, 1.483
- ]
- },
- {
- "ct": 6500, "table":
- [
- 1.567, 1.565, 1.555, 1.541, 1.525, 1.518, 1.518, 1.518, 1.521, 1.527, 1.532, 1.541, 1.551, 1.559, 1.567, 1.569,
- 1.565, 1.557, 1.542, 1.527, 1.519, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.533, 1.542, 1.553, 1.559, 1.562,
- 1.561, 1.546, 1.532, 1.521, 1.518, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.529, 1.533, 1.542, 1.554, 1.559,
- 1.561, 1.539, 1.526, 1.524, 1.521, 1.521, 1.522, 1.524, 1.525, 1.531, 1.529, 1.529, 1.531, 1.538, 1.549, 1.558,
- 1.559, 1.538, 1.526, 1.525, 1.524, 1.528, 1.534, 1.536, 1.536, 1.536, 1.532, 1.529, 1.531, 1.537, 1.548, 1.556,
- 1.561, 1.537, 1.525, 1.524, 1.526, 1.532, 1.537, 1.539, 1.538, 1.537, 1.532, 1.529, 1.529, 1.537, 1.546, 1.556,
- 1.561, 1.536, 1.524, 1.522, 1.525, 1.532, 1.538, 1.538, 1.537, 1.533, 1.528, 1.526, 1.527, 1.536, 1.546, 1.555,
- 1.561, 1.537, 1.522, 1.521, 1.524, 1.531, 1.536, 1.537, 1.534, 1.529, 1.526, 1.522, 1.523, 1.534, 1.547, 1.555,
- 1.561, 1.538, 1.524, 1.522, 1.526, 1.531, 1.535, 1.535, 1.534, 1.527, 1.524, 1.522, 1.522, 1.535, 1.549, 1.556,
- 1.558, 1.543, 1.532, 1.526, 1.526, 1.529, 1.534, 1.535, 1.533, 1.526, 1.523, 1.522, 1.524, 1.537, 1.552, 1.557,
- 1.555, 1.546, 1.541, 1.528, 1.527, 1.528, 1.531, 1.533, 1.531, 1.527, 1.522, 1.522, 1.526, 1.536, 1.552, 1.561,
- 1.555, 1.547, 1.542, 1.538, 1.526, 1.526, 1.529, 1.531, 1.529, 1.528, 1.519, 1.519, 1.527, 1.531, 1.543, 1.561
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 3000, "table":
- [
- 1.684, 1.688, 1.691, 1.697, 1.709, 1.722, 1.735, 1.745, 1.747, 1.745, 1.731, 1.719, 1.709, 1.705, 1.699, 1.699,
- 1.684, 1.689, 1.694, 1.708, 1.721, 1.735, 1.747, 1.762, 1.762, 1.758, 1.745, 1.727, 1.716, 1.707, 1.701, 1.699,
- 1.684, 1.691, 1.704, 1.719, 1.734, 1.755, 1.772, 1.786, 1.789, 1.788, 1.762, 1.745, 1.724, 1.709, 1.702, 1.698,
- 1.682, 1.694, 1.709, 1.729, 1.755, 1.773, 1.798, 1.815, 1.817, 1.808, 1.788, 1.762, 1.733, 1.714, 1.704, 1.699,
- 1.682, 1.693, 1.713, 1.742, 1.772, 1.798, 1.815, 1.829, 1.831, 1.821, 1.807, 1.773, 1.742, 1.716, 1.703, 1.699,
- 1.681, 1.693, 1.713, 1.742, 1.772, 1.799, 1.828, 1.839, 1.839, 1.828, 1.807, 1.774, 1.742, 1.715, 1.699, 1.695,
- 1.679, 1.691, 1.712, 1.739, 1.771, 1.798, 1.825, 1.829, 1.831, 1.818, 1.801, 1.774, 1.738, 1.712, 1.695, 1.691,
- 1.676, 1.685, 1.703, 1.727, 1.761, 1.784, 1.801, 1.817, 1.817, 1.801, 1.779, 1.761, 1.729, 1.706, 1.691, 1.684,
- 1.669, 1.678, 1.692, 1.714, 1.741, 1.764, 1.784, 1.795, 1.795, 1.779, 1.761, 1.738, 1.713, 1.696, 1.683, 1.679,
- 1.664, 1.671, 1.679, 1.693, 1.716, 1.741, 1.762, 1.769, 1.769, 1.753, 1.738, 1.713, 1.701, 1.687, 1.681, 1.676,
- 1.661, 1.664, 1.671, 1.679, 1.693, 1.714, 1.732, 1.739, 1.739, 1.729, 1.708, 1.701, 1.685, 1.679, 1.676, 1.677,
- 1.659, 1.661, 1.664, 1.671, 1.679, 1.693, 1.712, 1.714, 1.714, 1.708, 1.701, 1.687, 1.679, 1.672, 1.673, 1.677
- ]
- },
- {
- "ct": 5000, "table":
- [
- 1.177, 1.183, 1.187, 1.191, 1.197, 1.206, 1.213, 1.215, 1.215, 1.215, 1.211, 1.204, 1.196, 1.191, 1.183, 1.182,
- 1.179, 1.185, 1.191, 1.196, 1.206, 1.217, 1.224, 1.229, 1.229, 1.226, 1.221, 1.212, 1.202, 1.195, 1.188, 1.182,
- 1.183, 1.191, 1.196, 1.206, 1.217, 1.229, 1.239, 1.245, 1.245, 1.245, 1.233, 1.221, 1.212, 1.199, 1.193, 1.187,
- 1.183, 1.192, 1.201, 1.212, 1.229, 1.241, 1.252, 1.259, 1.259, 1.257, 1.245, 1.233, 1.217, 1.201, 1.194, 1.192,
- 1.183, 1.192, 1.202, 1.219, 1.238, 1.252, 1.261, 1.269, 1.268, 1.261, 1.257, 1.241, 1.223, 1.204, 1.194, 1.191,
- 1.182, 1.192, 1.202, 1.219, 1.239, 1.255, 1.266, 1.271, 1.271, 1.265, 1.258, 1.242, 1.223, 1.205, 1.192, 1.191,
- 1.181, 1.189, 1.199, 1.218, 1.239, 1.254, 1.262, 1.268, 1.268, 1.258, 1.253, 1.241, 1.221, 1.204, 1.191, 1.187,
- 1.179, 1.184, 1.193, 1.211, 1.232, 1.243, 1.254, 1.257, 1.256, 1.253, 1.242, 1.232, 1.216, 1.199, 1.187, 1.183,
- 1.174, 1.179, 1.187, 1.202, 1.218, 1.232, 1.243, 1.246, 1.246, 1.239, 1.232, 1.218, 1.207, 1.191, 1.183, 1.179,
- 1.169, 1.175, 1.181, 1.189, 1.202, 1.218, 1.229, 1.232, 1.232, 1.224, 1.218, 1.207, 1.199, 1.185, 1.181, 1.174,
- 1.164, 1.168, 1.175, 1.179, 1.189, 1.201, 1.209, 1.213, 1.213, 1.209, 1.201, 1.198, 1.186, 1.181, 1.174, 1.173,
- 1.161, 1.166, 1.171, 1.175, 1.179, 1.189, 1.197, 1.198, 1.198, 1.197, 1.196, 1.186, 1.182, 1.175, 1.173, 1.173
- ]
- },
- {
- "ct": 6500, "table":
- [
- 1.166, 1.171, 1.173, 1.178, 1.187, 1.193, 1.201, 1.205, 1.205, 1.205, 1.199, 1.191, 1.184, 1.179, 1.174, 1.171,
- 1.166, 1.172, 1.176, 1.184, 1.195, 1.202, 1.209, 1.216, 1.216, 1.213, 1.208, 1.201, 1.189, 1.182, 1.176, 1.171,
- 1.166, 1.173, 1.183, 1.195, 1.202, 1.214, 1.221, 1.228, 1.229, 1.228, 1.221, 1.209, 1.201, 1.186, 1.179, 1.174,
- 1.165, 1.174, 1.187, 1.201, 1.214, 1.223, 1.235, 1.241, 1.242, 1.241, 1.229, 1.221, 1.205, 1.188, 1.181, 1.177,
- 1.165, 1.174, 1.189, 1.207, 1.223, 1.235, 1.242, 1.253, 1.252, 1.245, 1.241, 1.228, 1.211, 1.189, 1.181, 1.178,
- 1.164, 1.173, 1.189, 1.207, 1.224, 1.238, 1.249, 1.255, 1.255, 1.249, 1.242, 1.228, 1.211, 1.191, 1.179, 1.176,
- 1.163, 1.172, 1.187, 1.207, 1.223, 1.237, 1.245, 1.253, 1.252, 1.243, 1.237, 1.228, 1.207, 1.188, 1.176, 1.173,
- 1.159, 1.167, 1.179, 1.199, 1.217, 1.227, 1.237, 1.241, 1.241, 1.237, 1.228, 1.217, 1.201, 1.184, 1.174, 1.169,
- 1.156, 1.164, 1.172, 1.189, 1.205, 1.217, 1.226, 1.229, 1.229, 1.222, 1.217, 1.204, 1.192, 1.177, 1.171, 1.166,
- 1.154, 1.159, 1.166, 1.177, 1.189, 1.205, 1.213, 1.216, 1.216, 1.209, 1.204, 1.192, 1.183, 1.172, 1.168, 1.162,
- 1.152, 1.155, 1.161, 1.166, 1.177, 1.188, 1.195, 1.198, 1.199, 1.196, 1.187, 1.183, 1.173, 1.168, 1.163, 1.162,
- 1.151, 1.154, 1.158, 1.162, 1.168, 1.177, 1.183, 1.184, 1.184, 1.184, 1.182, 1.172, 1.168, 1.165, 1.162, 1.161
- ]
- }
- ],
- "luminance_lut":
- [
- 2.236, 2.111, 1.912, 1.741, 1.579, 1.451, 1.379, 1.349, 1.349, 1.361, 1.411, 1.505, 1.644, 1.816, 2.034, 2.159,
- 2.139, 1.994, 1.796, 1.625, 1.467, 1.361, 1.285, 1.248, 1.239, 1.265, 1.321, 1.408, 1.536, 1.703, 1.903, 2.087,
- 2.047, 1.898, 1.694, 1.511, 1.373, 1.254, 1.186, 1.152, 1.142, 1.166, 1.226, 1.309, 1.441, 1.598, 1.799, 1.978,
- 1.999, 1.824, 1.615, 1.429, 1.281, 1.179, 1.113, 1.077, 1.071, 1.096, 1.153, 1.239, 1.357, 1.525, 1.726, 1.915,
- 1.976, 1.773, 1.563, 1.374, 1.222, 1.119, 1.064, 1.032, 1.031, 1.049, 1.099, 1.188, 1.309, 1.478, 1.681, 1.893,
- 1.973, 1.756, 1.542, 1.351, 1.196, 1.088, 1.028, 1.011, 1.004, 1.029, 1.077, 1.169, 1.295, 1.459, 1.663, 1.891,
- 1.973, 1.761, 1.541, 1.349, 1.193, 1.087, 1.031, 1.006, 1.006, 1.023, 1.075, 1.169, 1.298, 1.463, 1.667, 1.891,
- 1.982, 1.789, 1.568, 1.373, 1.213, 1.111, 1.051, 1.029, 1.024, 1.053, 1.106, 1.199, 1.329, 1.495, 1.692, 1.903,
- 2.015, 1.838, 1.621, 1.426, 1.268, 1.159, 1.101, 1.066, 1.068, 1.099, 1.166, 1.259, 1.387, 1.553, 1.751, 1.937,
- 2.076, 1.911, 1.692, 1.507, 1.346, 1.236, 1.169, 1.136, 1.139, 1.174, 1.242, 1.349, 1.475, 1.641, 1.833, 2.004,
- 2.193, 2.011, 1.798, 1.604, 1.444, 1.339, 1.265, 1.235, 1.237, 1.273, 1.351, 1.461, 1.598, 1.758, 1.956, 2.125,
- 2.263, 2.154, 1.916, 1.711, 1.549, 1.432, 1.372, 1.356, 1.356, 1.383, 1.455, 1.578, 1.726, 1.914, 2.119, 2.211
- ],
- "sigma": 0.006,
- "sigma_Cb": 0.00208
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2500, "ccm":
- [
- 1.70741, -0.05307, -0.65433, -0.62822, 1.68836, -0.06014, -0.04452, -1.87628, 2.92079
- ]
- },
- {
- "ct": 2803, "ccm":
- [
- 1.74383, -0.18731, -0.55652, -0.56491, 1.67772, -0.11281, -0.01522, -1.60635, 2.62157
- ]
- },
- {
- "ct": 2912, "ccm":
- [
- 1.75215, -0.22221, -0.52995, -0.54568, 1.63522, -0.08954, 0.02633, -1.56997, 2.54364
- ]
- },
- {
- "ct": 2914, "ccm":
- [
- 1.72423, -0.28939, -0.43484, -0.55188, 1.62925, -0.07737, 0.01959, -1.28661, 2.26702
- ]
- },
- {
- "ct": 3605, "ccm":
- [
- 1.80381, -0.43646, -0.36735, -0.46505, 1.56814, -0.10309, 0.00929, -1.00424, 1.99495
- ]
- },
- {
- "ct": 4540, "ccm":
- [
- 1.85263, -0.46545, -0.38719, -0.44136, 1.68443, -0.24307, 0.04108, -0.85599, 1.81491
- ]
- },
- {
- "ct": 5699, "ccm":
- [
- 1.98595, -0.63542, -0.35054, -0.34623, 1.54146, -0.19522, 0.00411, -0.70936, 1.70525
- ]
- },
- {
- "ct": 8625, "ccm":
- [
- 2.21637, -0.56663, -0.64974, -0.41133, 1.96625, -0.55492, -0.02307, -0.83529, 1.85837
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
-
- }
-}
diff --git a/src/ipa/raspberrypi/data/ov9281.json b/src/ipa/raspberrypi/data/ov9281.json
deleted file mode 100644
index 2319448b..00000000
--- a/src/ipa/raspberrypi/data/ov9281.json
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 2000,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 800,
- "reference_Y": 20000
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 2.5
- },
- "rpi.sdn":
- {
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted": {
- "weights": [4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter": [ 100, 15000, 30000, 60000, 120000 ],
- "gain": [ 1.0, 2.0, 3.0, 4.0, 8.0 ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- { "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [ 0, 0.4, 1000, 0.4 ] }
- ]
- },
- "y_target": [ 0, 0.16, 1000, 0.165, 10000, 0.17 ]
- },
- "rpi.alsc":
- {
- "n_iter": 0,
- "luminance_strength": 1.0,
- "corner_strength": 1.5
- },
- "rpi.contrast":
- {
- "ce_enable": 0,
- "gamma_curve": [
- 0, 0,
- 1024, 5040,
- 2048, 9338,
- 3072, 12356,
- 4096, 15312,
- 5120, 18051,
- 6144, 20790,
- 7168, 23193,
- 8192, 25744,
- 9216, 27942,
- 10240, 30035,
- 11264, 32005,
- 12288, 33975,
- 13312, 35815,
- 14336, 37600,
- 15360, 39168,
- 16384, 40642,
- 18432, 43379,
- 20480, 45749,
- 22528, 47753,
- 24576, 49621,
- 26624, 51253,
- 28672, 52698,
- 30720, 53796,
- 32768, 54876,
- 36864, 57012,
- 40960, 58656,
- 45056, 59954,
- 49152, 61183,
- 53248, 62355,
- 57344, 63419,
- 61440, 64476,
- 65535, 65535
- ]
- }
-}
diff --git a/src/ipa/raspberrypi/data/se327m12.json b/src/ipa/raspberrypi/data/se327m12.json
deleted file mode 100644
index 94af2239..00000000
--- a/src/ipa/raspberrypi/data/se327m12.json
+++ /dev/null
@@ -1,341 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 3840
- },
- "rpi.dpc":
- {
- },
- "rpi.lux":
- {
- "reference_shutter_speed": 6873,
- "reference_gain": 1.0,
- "reference_aperture": 1.0,
- "reference_lux": 800,
- "reference_Y": 12293
- },
- "rpi.noise":
- {
- "reference_constant": 0,
- "reference_slope": 1.986
- },
- "rpi.geq":
- {
- "offset": 207,
- "slope": 0.00539
- },
- "rpi.sdn":
- {
- },
- "rpi.awb":
- {
- "priors":
- [
- {
- "lux": 0, "prior":
- [
- 2000, 1.0, 3000, 0.0, 13000, 0.0
- ]
- },
- {
- "lux": 800, "prior":
- [
- 2000, 0.0, 6000, 2.0, 13000, 2.0
- ]
- },
- {
- "lux": 1500, "prior":
- [
- 2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0
- ]
- }
- ],
- "modes":
- {
- "auto":
- {
- "lo": 2500,
- "hi": 8000
- },
- "incandescent":
- {
- "lo": 2500,
- "hi": 3000
- },
- "tungsten":
- {
- "lo": 3000,
- "hi": 3500
- },
- "fluorescent":
- {
- "lo": 4000,
- "hi": 4700
- },
- "indoor":
- {
- "lo": 3000,
- "hi": 5000
- },
- "daylight":
- {
- "lo": 5500,
- "hi": 6500
- },
- "cloudy":
- {
- "lo": 7000,
- "hi": 8600
- }
- },
- "bayes": 1,
- "ct_curve":
- [
- 2900.0, 0.9217, 0.3657, 3600.0, 0.7876, 0.4651, 4600.0, 0.6807, 0.5684, 5800.0, 0.5937, 0.6724, 8100.0, 0.5447, 0.7403
- ],
- "sensitivity_r": 1.0,
- "sensitivity_b": 1.0,
- "transverse_pos": 0.0162,
- "transverse_neg": 0.0204
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted":
- {
- "weights":
- [
- 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
- ]
- },
- "spot":
- {
- "weights":
- [
- 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]
- },
- "matrix":
- {
- "weights":
- [
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
- ]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter":
- [
- 100, 10000, 30000, 60000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- },
- "short":
- {
- "shutter":
- [
- 100, 5000, 10000, 20000, 120000
- ],
- "gain":
- [
- 1.0, 2.0, 4.0, 6.0, 8.0
- ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- }
- ],
- "highlight":
- [
- {
- "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.5, 1000, 0.5
- ]
- },
- {
- "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
- [
- 0, 0.8, 1000, 0.8
- ]
- }
- ]
- },
- "y_target":
- [
- 0, 0.16, 1000, 0.165, 10000, 0.17
- ]
- },
- "rpi.alsc":
- {
- "omega": 1.3,
- "n_iter": 100,
- "luminance_strength": 0.5,
- "calibrations_Cr":
- [
- {
- "ct": 4000, "table":
- [
- 1.481, 1.471, 1.449, 1.429, 1.416, 1.404, 1.394, 1.389, 1.389, 1.389, 1.392, 1.397, 1.404, 1.416, 1.429, 1.437,
- 1.472, 1.456, 1.436, 1.418, 1.405, 1.394, 1.389, 1.384, 1.382, 1.382, 1.386, 1.388, 1.398, 1.407, 1.422, 1.429,
- 1.465, 1.443, 1.426, 1.411, 1.397, 1.389, 1.383, 1.377, 1.377, 1.377, 1.379, 1.384, 1.388, 1.398, 1.411, 1.422,
- 1.462, 1.441, 1.423, 1.409, 1.395, 1.385, 1.379, 1.376, 1.374, 1.374, 1.375, 1.379, 1.384, 1.394, 1.407, 1.418,
- 1.461, 1.439, 1.421, 1.407, 1.394, 1.385, 1.381, 1.376, 1.373, 1.373, 1.373, 1.376, 1.381, 1.389, 1.403, 1.415,
- 1.461, 1.439, 1.419, 1.404, 1.392, 1.384, 1.379, 1.376, 1.373, 1.372, 1.374, 1.375, 1.379, 1.389, 1.401, 1.413,
- 1.461, 1.438, 1.419, 1.402, 1.389, 1.383, 1.377, 1.375, 1.373, 1.372, 1.372, 1.375, 1.381, 1.388, 1.401, 1.414,
- 1.462, 1.438, 1.419, 1.403, 1.391, 1.381, 1.377, 1.374, 1.373, 1.373, 1.374, 1.376, 1.381, 1.389, 1.401, 1.414,
- 1.462, 1.441, 1.423, 1.405, 1.392, 1.383, 1.377, 1.374, 1.373, 1.372, 1.373, 1.376, 1.382, 1.391, 1.402, 1.414,
- 1.465, 1.444, 1.424, 1.407, 1.393, 1.382, 1.378, 1.373, 1.369, 1.369, 1.372, 1.375, 1.381, 1.389, 1.402, 1.417,
- 1.469, 1.449, 1.427, 1.413, 1.396, 1.384, 1.381, 1.375, 1.371, 1.371, 1.373, 1.377, 1.385, 1.393, 1.407, 1.422,
- 1.474, 1.456, 1.436, 1.419, 1.407, 1.391, 1.383, 1.379, 1.377, 1.377, 1.378, 1.381, 1.391, 1.404, 1.422, 1.426
- ]
- },
- {
- "ct": 5000, "table":
- [
- 1.742, 1.721, 1.689, 1.661, 1.639, 1.623, 1.613, 1.609, 1.607, 1.606, 1.609, 1.617, 1.626, 1.641, 1.665, 1.681,
- 1.728, 1.703, 1.672, 1.645, 1.631, 1.614, 1.602, 1.599, 1.596, 1.597, 1.601, 1.608, 1.618, 1.631, 1.653, 1.671,
- 1.713, 1.691, 1.658, 1.635, 1.618, 1.606, 1.595, 1.591, 1.588, 1.588, 1.591, 1.601, 1.608, 1.624, 1.641, 1.658,
- 1.707, 1.681, 1.651, 1.627, 1.613, 1.599, 1.591, 1.585, 1.583, 1.584, 1.587, 1.591, 1.601, 1.615, 1.633, 1.655,
- 1.699, 1.672, 1.644, 1.622, 1.606, 1.593, 1.586, 1.581, 1.579, 1.581, 1.583, 1.587, 1.597, 1.611, 1.631, 1.652,
- 1.697, 1.665, 1.637, 1.617, 1.601, 1.589, 1.584, 1.579, 1.577, 1.578, 1.581, 1.585, 1.597, 1.607, 1.627, 1.652,
- 1.697, 1.662, 1.634, 1.613, 1.599, 1.591, 1.583, 1.578, 1.576, 1.576, 1.579, 1.586, 1.597, 1.607, 1.628, 1.653,
- 1.697, 1.662, 1.633, 1.613, 1.598, 1.589, 1.582, 1.578, 1.576, 1.577, 1.582, 1.589, 1.598, 1.611, 1.635, 1.655,
- 1.701, 1.666, 1.636, 1.616, 1.602, 1.589, 1.583, 1.578, 1.577, 1.581, 1.583, 1.591, 1.601, 1.617, 1.639, 1.659,
- 1.708, 1.671, 1.641, 1.618, 1.603, 1.591, 1.584, 1.581, 1.578, 1.581, 1.585, 1.594, 1.604, 1.622, 1.646, 1.666,
- 1.714, 1.681, 1.648, 1.622, 1.608, 1.599, 1.591, 1.584, 1.583, 1.584, 1.589, 1.599, 1.614, 1.629, 1.653, 1.673,
- 1.719, 1.691, 1.659, 1.631, 1.618, 1.606, 1.596, 1.591, 1.591, 1.593, 1.599, 1.608, 1.623, 1.642, 1.665, 1.681
- ]
- }
- ],
- "calibrations_Cb":
- [
- {
- "ct": 4000, "table":
- [
- 2.253, 2.267, 2.289, 2.317, 2.342, 2.359, 2.373, 2.381, 2.381, 2.378, 2.368, 2.361, 2.344, 2.337, 2.314, 2.301,
- 2.262, 2.284, 2.314, 2.335, 2.352, 2.371, 2.383, 2.391, 2.393, 2.391, 2.381, 2.368, 2.361, 2.342, 2.322, 2.308,
- 2.277, 2.303, 2.321, 2.346, 2.364, 2.381, 2.391, 2.395, 2.397, 2.397, 2.395, 2.381, 2.367, 2.354, 2.332, 2.321,
- 2.277, 2.304, 2.327, 2.349, 2.369, 2.388, 2.393, 2.396, 2.396, 2.398, 2.396, 2.391, 2.376, 2.359, 2.339, 2.328,
- 2.279, 2.311, 2.327, 2.354, 2.377, 2.389, 2.393, 2.397, 2.397, 2.398, 2.395, 2.393, 2.382, 2.363, 2.344, 2.332,
- 2.282, 2.311, 2.329, 2.354, 2.377, 2.386, 2.396, 2.396, 2.395, 2.396, 2.397, 2.394, 2.383, 2.367, 2.346, 2.333,
- 2.283, 2.314, 2.333, 2.353, 2.375, 2.389, 2.394, 2.395, 2.395, 2.395, 2.396, 2.394, 2.386, 2.368, 2.354, 2.336,
- 2.287, 2.309, 2.331, 2.352, 2.373, 2.386, 2.394, 2.395, 2.395, 2.396, 2.396, 2.394, 2.384, 2.371, 2.354, 2.339,
- 2.289, 2.307, 2.326, 2.347, 2.369, 2.385, 2.392, 2.397, 2.398, 2.398, 2.397, 2.392, 2.383, 2.367, 2.352, 2.337,
- 2.286, 2.303, 2.322, 2.342, 2.361, 2.379, 2.389, 2.394, 2.397, 2.398, 2.396, 2.389, 2.381, 2.366, 2.346, 2.332,
- 2.284, 2.291, 2.312, 2.329, 2.351, 2.372, 2.381, 2.389, 2.393, 2.394, 2.389, 2.385, 2.374, 2.362, 2.338, 2.325,
- 2.283, 2.288, 2.305, 2.319, 2.339, 2.365, 2.374, 2.381, 2.384, 2.386, 2.385, 2.379, 2.368, 2.342, 2.325, 2.318
- ]
- },
- {
- "ct": 5000, "table":
- [
- 1.897, 1.919, 1.941, 1.969, 1.989, 2.003, 2.014, 2.019, 2.019, 2.017, 2.014, 2.008, 1.999, 1.988, 1.968, 1.944,
- 1.914, 1.932, 1.957, 1.982, 1.998, 2.014, 2.023, 2.029, 2.031, 2.029, 2.022, 2.014, 2.006, 1.995, 1.976, 1.955,
- 1.925, 1.951, 1.974, 1.996, 2.013, 2.027, 2.035, 2.039, 2.039, 2.038, 2.035, 2.026, 2.015, 2.002, 1.984, 1.963,
- 1.932, 1.958, 1.986, 2.007, 2.024, 2.034, 2.041, 2.041, 2.045, 2.045, 2.042, 2.033, 2.023, 2.009, 1.995, 1.971,
- 1.942, 1.964, 1.994, 2.012, 2.029, 2.038, 2.043, 2.046, 2.047, 2.046, 2.045, 2.039, 2.029, 2.014, 1.997, 1.977,
- 1.946, 1.974, 1.999, 2.015, 2.031, 2.041, 2.046, 2.047, 2.048, 2.047, 2.044, 2.041, 2.031, 2.019, 1.999, 1.978,
- 1.948, 1.975, 2.002, 2.018, 2.031, 2.041, 2.046, 2.047, 2.048, 2.048, 2.045, 2.041, 2.029, 2.019, 1.998, 1.978,
- 1.948, 1.973, 2.002, 2.018, 2.029, 2.042, 2.045, 2.048, 2.048, 2.048, 2.044, 2.037, 2.027, 2.014, 1.993, 1.978,
- 1.945, 1.969, 1.998, 2.015, 2.028, 2.037, 2.045, 2.046, 2.047, 2.044, 2.039, 2.033, 2.022, 2.008, 1.989, 1.971,
- 1.939, 1.964, 1.991, 2.011, 2.024, 2.032, 2.036, 2.042, 2.042, 2.039, 2.035, 2.024, 2.012, 1.998, 1.977, 1.964,
- 1.932, 1.953, 1.981, 2.006, 2.016, 2.024, 2.028, 2.031, 2.034, 2.031, 2.024, 2.015, 2.005, 1.989, 1.966, 1.955,
- 1.928, 1.944, 1.973, 1.999, 2.007, 2.016, 2.019, 2.025, 2.026, 2.025, 2.017, 2.008, 1.997, 1.975, 1.958, 1.947
- ]
- }
- ],
- "luminance_lut":
- [
- 1.877, 1.597, 1.397, 1.269, 1.191, 1.131, 1.093, 1.078, 1.071, 1.069, 1.086, 1.135, 1.221, 1.331, 1.474, 1.704,
- 1.749, 1.506, 1.334, 1.229, 1.149, 1.088, 1.058, 1.053, 1.051, 1.046, 1.053, 1.091, 1.163, 1.259, 1.387, 1.587,
- 1.661, 1.451, 1.295, 1.195, 1.113, 1.061, 1.049, 1.048, 1.047, 1.049, 1.049, 1.066, 1.124, 1.211, 1.333, 1.511,
- 1.615, 1.411, 1.267, 1.165, 1.086, 1.052, 1.047, 1.047, 1.047, 1.049, 1.052, 1.056, 1.099, 1.181, 1.303, 1.471,
- 1.576, 1.385, 1.252, 1.144, 1.068, 1.049, 1.044, 1.044, 1.045, 1.049, 1.053, 1.054, 1.083, 1.163, 1.283, 1.447,
- 1.561, 1.373, 1.245, 1.135, 1.064, 1.049, 1.044, 1.044, 1.044, 1.046, 1.048, 1.054, 1.073, 1.153, 1.271, 1.432,
- 1.571, 1.377, 1.242, 1.137, 1.066, 1.055, 1.052, 1.051, 1.051, 1.049, 1.047, 1.048, 1.068, 1.148, 1.271, 1.427,
- 1.582, 1.396, 1.259, 1.156, 1.085, 1.068, 1.059, 1.054, 1.049, 1.045, 1.041, 1.043, 1.074, 1.157, 1.284, 1.444,
- 1.623, 1.428, 1.283, 1.178, 1.105, 1.074, 1.069, 1.063, 1.056, 1.048, 1.046, 1.051, 1.094, 1.182, 1.311, 1.473,
- 1.691, 1.471, 1.321, 1.213, 1.135, 1.088, 1.073, 1.069, 1.063, 1.059, 1.053, 1.071, 1.129, 1.222, 1.351, 1.521,
- 1.808, 1.543, 1.371, 1.253, 1.174, 1.118, 1.085, 1.072, 1.067, 1.064, 1.071, 1.106, 1.176, 1.274, 1.398, 1.582,
- 1.969, 1.666, 1.447, 1.316, 1.223, 1.166, 1.123, 1.094, 1.089, 1.097, 1.118, 1.163, 1.239, 1.336, 1.471, 1.681
- ],
- "sigma": 0.00218,
- "sigma_Cb": 0.00194
- },
- "rpi.contrast":
- {
- "ce_enable": 1,
- "gamma_curve":
- [
- 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
- 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
- 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
- 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
- 65535, 65535
- ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- {
- "ct": 2900, "ccm":
- [
- 1.44924, -0.12935, -0.31989, -0.65839, 1.95441, -0.29602, 0.18344, -1.22282, 2.03938
- ]
- },
- {
- "ct": 3000, "ccm":
- [
- 1.38736, 0.07714, -0.46451, -0.59691, 1.84335, -0.24644, 0.10092, -1.30441, 2.20349
- ]
- },
- {
- "ct": 3600, "ccm":
- [
- 1.51261, -0.27921, -0.23339, -0.55129, 1.83241, -0.28111, 0.11649, -0.93195, 1.81546
- ]
- },
- {
- "ct": 4600, "ccm":
- [
- 1.47082, -0.18523, -0.28559, -0.48923, 1.95126, -0.46203, 0.07951, -0.83987, 1.76036
- ]
- },
- {
- "ct": 5800, "ccm":
- [
- 1.57294, -0.36229, -0.21065, -0.42272, 1.80305, -0.38032, 0.03671, -0.66862, 1.63191
- ]
- },
- {
- "ct": 8100, "ccm":
- [
- 1.58803, -0.09912, -0.48891, -0.42594, 2.22303, -0.79709, -0.00621, -0.90516, 1.91137
- ]
- }
- ]
- },
- "rpi.sharpen":
- {
- "threshold": 2.0,
- "strength": 0.5,
- "limit": 0.5
- }
-}
diff --git a/src/ipa/raspberrypi/data/uncalibrated.json b/src/ipa/raspberrypi/data/uncalibrated.json
deleted file mode 100644
index 16a01e94..00000000
--- a/src/ipa/raspberrypi/data/uncalibrated.json
+++ /dev/null
@@ -1,82 +0,0 @@
-{
- "rpi.black_level":
- {
- "black_level": 4096
- },
- "rpi.awb":
- {
- "use_derivatives": 0,
- "bayes": 0
- },
- "rpi.agc":
- {
- "metering_modes":
- {
- "centre-weighted": {
- "weights": [4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0]
- }
- },
- "exposure_modes":
- {
- "normal":
- {
- "shutter": [ 100, 15000, 30000, 60000, 120000 ],
- "gain": [ 1.0, 2.0, 3.0, 4.0, 6.0 ]
- }
- },
- "constraint_modes":
- {
- "normal":
- [
- { "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [ 0, 0.4, 1000, 0.4 ] }
- ]
- },
- "y_target": [ 0, 0.16, 1000, 0.165, 10000, 0.17 ]
- },
- "rpi.ccm":
- {
- "ccms":
- [
- { "ct": 4000, "ccm": [ 2.0, -1.0, 0.0, -0.5, 2.0, -0.5, 0, -1.0, 2.0 ] }
- ]
- },
- "rpi.contrast":
- {
- "ce_enable": 0,
- "gamma_curve": [
- 0, 0,
- 1024, 5040,
- 2048, 9338,
- 3072, 12356,
- 4096, 15312,
- 5120, 18051,
- 6144, 20790,
- 7168, 23193,
- 8192, 25744,
- 9216, 27942,
- 10240, 30035,
- 11264, 32005,
- 12288, 33975,
- 13312, 35815,
- 14336, 37600,
- 15360, 39168,
- 16384, 40642,
- 18432, 43379,
- 20480, 45749,
- 22528, 47753,
- 24576, 49621,
- 26624, 51253,
- 28672, 52698,
- 30720, 53796,
- 32768, 54876,
- 36864, 57012,
- 40960, 58656,
- 45056, 59954,
- 49152, 61183,
- 53248, 62355,
- 57344, 63419,
- 61440, 64476,
- 65535, 65535
- ]
- }
-}
diff --git a/src/ipa/raspberrypi/md_parser_smia.cpp b/src/ipa/raspberrypi/md_parser_smia.cpp
deleted file mode 100644
index ea5eac41..00000000
--- a/src/ipa/raspberrypi/md_parser_smia.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Limited
- *
- * md_parser_smia.cpp - SMIA specification based embedded data parser
- */
-
-#include <libcamera/base/log.h>
-#include "md_parser.hpp"
-
-using namespace RPiController;
-using namespace libcamera;
-
-/*
- * This function goes through the embedded data to find the offsets (not
- * values!), in the data block, where the values of the given registers can
- * subsequently be found.
- *
- * Embedded data tag bytes, from Sony IMX219 datasheet but general to all SMIA
- * sensors, I think.
- */
-
-constexpr unsigned int LINE_START = 0x0a;
-constexpr unsigned int LINE_END_TAG = 0x07;
-constexpr unsigned int REG_HI_BITS = 0xaa;
-constexpr unsigned int REG_LOW_BITS = 0xa5;
-constexpr unsigned int REG_VALUE = 0x5a;
-constexpr unsigned int REG_SKIP = 0x55;
-
-MdParserSmia::MdParserSmia(std::initializer_list<uint32_t> registerList)
-{
- for (auto r : registerList)
- offsets_[r] = {};
-}
-
-MdParser::Status MdParserSmia::Parse(libcamera::Span<const uint8_t> buffer,
- RegisterMap &registers)
-{
- if (reset_) {
- /*
- * Search again through the metadata for all the registers
- * requested.
- */
- ASSERT(bits_per_pixel_);
-
- for (const auto &kv : offsets_)
- offsets_[kv.first] = {};
-
- ParseStatus ret = findRegs(buffer);
- /*
- * > 0 means "worked partially but parse again next time",
- * < 0 means "hard error".
- *
- * In either case, we retry parsing on the next frame.
- */
- if (ret != PARSE_OK)
- return ERROR;
-
- reset_ = false;
- }
-
- /* Populate the register values requested. */
- registers.clear();
- for (const auto &[reg, offset] : offsets_) {
- if (!offset) {
- reset_ = true;
- return NOTFOUND;
- }
- registers[reg] = buffer[offset.value()];
- }
-
- return OK;
-}
-
-MdParserSmia::ParseStatus MdParserSmia::findRegs(libcamera::Span<const uint8_t> buffer)
-{
- ASSERT(offsets_.size());
-
- if (buffer[0] != LINE_START)
- return NO_LINE_START;
-
- unsigned int current_offset = 1; /* after the LINE_START */
- unsigned int current_line_start = 0, current_line = 0;
- unsigned int reg_num = 0, regs_done = 0;
-
- while (1) {
- int tag = buffer[current_offset++];
-
- if ((bits_per_pixel_ == 10 &&
- (current_offset + 1 - current_line_start) % 5 == 0) ||
- (bits_per_pixel_ == 12 &&
- (current_offset + 1 - current_line_start) % 3 == 0)) {
- if (buffer[current_offset++] != REG_SKIP)
- return BAD_DUMMY;
- }
-
- int data_byte = buffer[current_offset++];
-
- if (tag == LINE_END_TAG) {
- if (data_byte != LINE_END_TAG)
- return BAD_LINE_END;
-
- if (num_lines_ && ++current_line == num_lines_)
- return MISSING_REGS;
-
- if (line_length_bytes_) {
- current_offset = current_line_start + line_length_bytes_;
-
- /* Require whole line to be in the buffer (if buffer size set). */
- if (buffer.size() &&
- current_offset + line_length_bytes_ > buffer.size())
- return MISSING_REGS;
-
- if (buffer[current_offset] != LINE_START)
- return NO_LINE_START;
- } else {
- /* allow a zero line length to mean "hunt for the next line" */
- while (current_offset < buffer.size() &&
- buffer[current_offset] != LINE_START)
- current_offset++;
-
- if (current_offset == buffer.size())
- return NO_LINE_START;
- }
-
- /* inc current_offset to after LINE_START */
- current_line_start = current_offset++;
- } else {
- if (tag == REG_HI_BITS)
- reg_num = (reg_num & 0xff) | (data_byte << 8);
- else if (tag == REG_LOW_BITS)
- reg_num = (reg_num & 0xff00) | data_byte;
- else if (tag == REG_SKIP)
- reg_num++;
- else if (tag == REG_VALUE) {
- auto reg = offsets_.find(reg_num);
-
- if (reg != offsets_.end()) {
- offsets_[reg_num] = current_offset - 1;
-
- if (++regs_done == offsets_.size())
- return PARSE_OK;
- }
- reg_num++;
- } else
- return ILLEGAL_TAG;
- }
- }
-}
diff --git a/src/ipa/raspberrypi/meson.build b/src/ipa/raspberrypi/meson.build
deleted file mode 100644
index 32897e07..00000000
--- a/src/ipa/raspberrypi/meson.build
+++ /dev/null
@@ -1,66 +0,0 @@
-# SPDX-License-Identifier: CC0-1.0
-
-ipa_name = 'ipa_rpi'
-
-rpi_ipa_deps = [
- libcamera_private,
- dependency('boost'),
- libatomic,
-]
-
-rpi_ipa_includes = [
- ipa_includes,
- libipa_includes,
- include_directories('controller')
-]
-
-rpi_ipa_sources = files([
- 'raspberrypi.cpp',
- 'md_parser_smia.cpp',
- 'cam_helper.cpp',
- 'cam_helper_ov5647.cpp',
- 'cam_helper_imx219.cpp',
- 'cam_helper_imx290.cpp',
- 'cam_helper_imx296.cpp',
- 'cam_helper_imx477.cpp',
- 'cam_helper_imx519.cpp',
- 'cam_helper_ov9281.cpp',
- 'controller/controller.cpp',
- 'controller/histogram.cpp',
- 'controller/algorithm.cpp',
- 'controller/rpi/alsc.cpp',
- 'controller/rpi/awb.cpp',
- 'controller/rpi/sharpen.cpp',
- 'controller/rpi/black_level.cpp',
- 'controller/rpi/focus.cpp',
- 'controller/rpi/geq.cpp',
- 'controller/rpi/noise.cpp',
- 'controller/rpi/lux.cpp',
- 'controller/rpi/agc.cpp',
- 'controller/rpi/dpc.cpp',
- 'controller/rpi/ccm.cpp',
- 'controller/rpi/contrast.cpp',
- 'controller/rpi/sdn.cpp',
- 'controller/pwl.cpp',
- 'controller/device_status.cpp',
-])
-
-mod = shared_module(ipa_name,
- [rpi_ipa_sources, libcamera_generated_ipa_headers],
- name_prefix : '',
- include_directories : rpi_ipa_includes,
- dependencies : rpi_ipa_deps,
- link_with : libipa,
- install : true,
- install_dir : ipa_install_dir)
-
-if ipa_sign_module
- custom_target(ipa_name + '.so.sign',
- input : mod,
- output : ipa_name + '.so.sign',
- command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
- install : false,
- build_by_default : true)
-endif
-
-subdir('data')
diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
deleted file mode 100644
index f8d37b87..00000000
--- a/src/ipa/raspberrypi/raspberrypi.cpp
+++ /dev/null
@@ -1,1460 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Ltd.
- *
- * rpi.cpp - Raspberry Pi Image Processing Algorithms
- */
-
-#include <algorithm>
-#include <array>
-#include <fcntl.h>
-#include <math.h>
-#include <stdint.h>
-#include <string.h>
-#include <sys/mman.h>
-
-#include <linux/bcm2835-isp.h>
-
-#include <libcamera/base/log.h>
-#include <libcamera/base/shared_fd.h>
-#include <libcamera/base/span.h>
-
-#include <libcamera/control_ids.h>
-#include <libcamera/controls.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/ipa/ipa_interface.h>
-#include <libcamera/ipa/ipa_module_info.h>
-#include <libcamera/ipa/raspberrypi_ipa_interface.h>
-#include <libcamera/request.h>
-
-#include "libcamera/internal/mapped_framebuffer.h"
-
-#include "agc_algorithm.hpp"
-#include "agc_status.h"
-#include "alsc_status.h"
-#include "awb_algorithm.hpp"
-#include "awb_status.h"
-#include "black_level_status.h"
-#include "cam_helper.hpp"
-#include "ccm_algorithm.hpp"
-#include "ccm_status.h"
-#include "contrast_algorithm.hpp"
-#include "contrast_status.h"
-#include "controller.hpp"
-#include "denoise_algorithm.hpp"
-#include "denoise_status.h"
-#include "dpc_status.h"
-#include "focus_status.h"
-#include "geq_status.h"
-#include "lux_status.h"
-#include "metadata.hpp"
-#include "noise_status.h"
-#include "sharpen_algorithm.hpp"
-#include "sharpen_status.h"
-
-namespace libcamera {
-
-using namespace std::literals::chrono_literals;
-using utils::Duration;
-
-/* Configure the sensor with these values initially. */
-constexpr double defaultAnalogueGain = 1.0;
-constexpr Duration defaultExposureTime = 20.0ms;
-constexpr Duration defaultMinFrameDuration = 1.0s / 30.0;
-constexpr Duration defaultMaxFrameDuration = 250.0s;
-
-/*
- * Determine the minimum allowable inter-frame duration to run the controller
- * algorithms. If the pipeline handler provider frames at a rate higher than this,
- * we rate-limit the controller Prepare() and Process() calls to lower than or
- * equal to this rate.
- */
-constexpr Duration controllerMinFrameDuration = 1.0s / 30.0;
-
-/* List of controls handled by the Raspberry Pi IPA */
-static const ControlInfoMap::Map ipaControls{
- { &controls::AeEnable, ControlInfo(false, true) },
- { &controls::ExposureTime, ControlInfo(0, 66666) },
- { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f) },
- { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
- { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
- { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
- { &controls::ExposureValue, ControlInfo(-8.0f, 8.0f, 0.0f) },
- { &controls::AwbEnable, ControlInfo(false, true) },
- { &controls::ColourGains, ControlInfo(0.0f, 32.0f) },
- { &controls::AwbMode, ControlInfo(controls::AwbModeValues) },
- { &controls::Brightness, ControlInfo(-1.0f, 1.0f, 0.0f) },
- { &controls::Contrast, ControlInfo(0.0f, 32.0f, 1.0f) },
- { &controls::Saturation, ControlInfo(0.0f, 32.0f, 1.0f) },
- { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
- { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },
- { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
- { &controls::FrameDurationLimits, ControlInfo(INT64_C(33333), INT64_C(120000)) },
- { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }
-};
-
-LOG_DEFINE_CATEGORY(IPARPI)
-
-namespace ipa::RPi {
-
-class IPARPi : public IPARPiInterface
-{
-public:
- IPARPi()
- : controller_(), frameCount_(0), checkCount_(0), mistrustCount_(0),
- lastRunTimestamp_(0), lsTable_(nullptr), firstStart_(true)
- {
- }
-
- ~IPARPi()
- {
- if (lsTable_)
- munmap(lsTable_, MaxLsGridSize);
- }
-
- int init(const IPASettings &settings, IPAInitResult *result) override;
- void start(const ControlList &controls, StartConfig *startConfig) override;
- void stop() override {}
-
- int configure(const IPACameraSensorInfo &sensorInfo,
- const std::map<unsigned int, IPAStream> &streamConfig,
- const std::map<unsigned int, ControlInfoMap> &entityControls,
- const IPAConfig &data,
- ControlList *controls, IPAConfigResult *result) override;
- void mapBuffers(const std::vector<IPABuffer> &buffers) override;
- void unmapBuffers(const std::vector<unsigned int> &ids) override;
- void signalStatReady(const uint32_t bufferId) override;
- void signalQueueRequest(const ControlList &controls) override;
- void signalIspPrepare(const ISPConfig &data) override;
-
-private:
- void setMode(const IPACameraSensorInfo &sensorInfo);
- bool validateSensorControls();
- bool validateIspControls();
- void queueRequest(const ControlList &controls);
- void returnEmbeddedBuffer(unsigned int bufferId);
- void prepareISP(const ISPConfig &data);
- void reportMetadata();
- void fillDeviceStatus(const ControlList &sensorControls);
- void processStats(unsigned int bufferId);
- void applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration);
- void applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls);
- void applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls);
- void applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls);
- void applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls);
- void applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls);
- void applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls);
- void applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls);
- void applyDenoise(const struct DenoiseStatus *denoiseStatus, ControlList &ctrls);
- void applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls);
- void applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls);
- void applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls);
- void resampleTable(uint16_t dest[], double const src[12][16], int destW, int destH);
-
- std::map<unsigned int, MappedFrameBuffer> buffers_;
-
- ControlInfoMap sensorCtrls_;
- ControlInfoMap ispCtrls_;
- ControlList libcameraMetadata_;
-
- /* Camera sensor params. */
- CameraMode mode_;
-
- /* Raspberry Pi controller specific defines. */
- std::unique_ptr<RPiController::CamHelper> helper_;
- RPiController::Controller controller_;
- RPiController::Metadata rpiMetadata_;
-
- /*
- * We count frames to decide if the frame must be hidden (e.g. from
- * display) or mistrusted (i.e. not given to the control algos).
- */
- uint64_t frameCount_;
-
- /* For checking the sequencing of Prepare/Process calls. */
- uint64_t checkCount_;
-
- /* How many frames we should avoid running control algos on. */
- unsigned int mistrustCount_;
-
- /* Number of frames that need to be dropped on startup. */
- unsigned int dropFrameCount_;
-
- /* Frame timestamp for the last run of the controller. */
- uint64_t lastRunTimestamp_;
-
- /* Do we run a Controller::process() for this frame? */
- bool processPending_;
-
- /* LS table allocation passed in from the pipeline handler. */
- SharedFD lsTableHandle_;
- void *lsTable_;
-
- /* Distinguish the first camera start from others. */
- bool firstStart_;
-
- /* Frame duration (1/fps) limits. */
- Duration minFrameDuration_;
- Duration maxFrameDuration_;
-
- /* Maximum gain code for the sensor. */
- uint32_t maxSensorGainCode_;
-};
-
-int IPARPi::init(const IPASettings &settings, IPAInitResult *result)
-{
- /*
- * Load the "helper" for this sensor. This tells us all the device specific stuff
- * that the kernel driver doesn't. We only do this the first time; we don't need
- * to re-parse the metadata after a simple mode-switch for no reason.
- */
- helper_ = std::unique_ptr<RPiController::CamHelper>(RPiController::CamHelper::Create(settings.sensorModel));
- if (!helper_) {
- LOG(IPARPI, Error) << "Could not create camera helper for "
- << settings.sensorModel;
- return -EINVAL;
- }
-
- /*
- * Pass out the sensor config to the pipeline handler in order
- * to setup the staggered writer class.
- */
- int gainDelay, exposureDelay, vblankDelay, sensorMetadata;
- helper_->GetDelays(exposureDelay, gainDelay, vblankDelay);
- sensorMetadata = helper_->SensorEmbeddedDataPresent();
-
- result->sensorConfig.gainDelay = gainDelay;
- result->sensorConfig.exposureDelay = exposureDelay;
- result->sensorConfig.vblankDelay = vblankDelay;
- result->sensorConfig.sensorMetadata = sensorMetadata;
-
- /* Load the tuning file for this sensor. */
- controller_.Read(settings.configurationFile.c_str());
- controller_.Initialise();
-
- /* Return the controls handled by the IPA */
- ControlInfoMap::Map ctrlMap = ipaControls;
- result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
-
- return 0;
-}
-
-void IPARPi::start(const ControlList &controls, StartConfig *startConfig)
-{
- RPiController::Metadata metadata;
-
- ASSERT(startConfig);
- if (!controls.empty()) {
- /* We have been given some controls to action before start. */
- queueRequest(controls);
- }
-
- controller_.SwitchMode(mode_, &metadata);
-
- /* SwitchMode may supply updated exposure/gain values to use. */
- AgcStatus agcStatus;
- agcStatus.shutter_time = 0.0s;
- agcStatus.analogue_gain = 0.0;
-
- metadata.Get("agc.status", agcStatus);
- if (agcStatus.shutter_time && agcStatus.analogue_gain) {
- ControlList ctrls(sensorCtrls_);
- applyAGC(&agcStatus, ctrls);
- startConfig->controls = std::move(ctrls);
- }
-
- /*
- * Initialise frame counts, and decide how many frames must be hidden or
- * "mistrusted", which depends on whether this is a startup from cold,
- * or merely a mode switch in a running system.
- */
- frameCount_ = 0;
- checkCount_ = 0;
- if (firstStart_) {
- dropFrameCount_ = helper_->HideFramesStartup();
- mistrustCount_ = helper_->MistrustFramesStartup();
-
- /*
- * Query the AGC/AWB for how many frames they may take to
- * converge sufficiently. Where these numbers are non-zero
- * we must allow for the frames with bad statistics
- * (mistrustCount_) that they won't see. But if zero (i.e.
- * no convergence necessary), no frames need to be dropped.
- */
- unsigned int agcConvergenceFrames = 0;
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (agc) {
- agcConvergenceFrames = agc->GetConvergenceFrames();
- if (agcConvergenceFrames)
- agcConvergenceFrames += mistrustCount_;
- }
-
- unsigned int awbConvergenceFrames = 0;
- RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
- controller_.GetAlgorithm("awb"));
- if (awb) {
- awbConvergenceFrames = awb->GetConvergenceFrames();
- if (awbConvergenceFrames)
- awbConvergenceFrames += mistrustCount_;
- }
-
- dropFrameCount_ = std::max({ dropFrameCount_, agcConvergenceFrames, awbConvergenceFrames });
- LOG(IPARPI, Debug) << "Drop " << dropFrameCount_ << " frames on startup";
- } else {
- dropFrameCount_ = helper_->HideFramesModeSwitch();
- mistrustCount_ = helper_->MistrustFramesModeSwitch();
- }
-
- startConfig->dropFrameCount = dropFrameCount_;
- const Duration maxSensorFrameDuration = mode_.max_frame_length * mode_.line_length;
- startConfig->maxSensorFrameLengthMs = maxSensorFrameDuration.get<std::milli>();
-
- firstStart_ = false;
- lastRunTimestamp_ = 0;
-}
-
-void IPARPi::setMode(const IPACameraSensorInfo &sensorInfo)
-{
- mode_.bitdepth = sensorInfo.bitsPerPixel;
- mode_.width = sensorInfo.outputSize.width;
- mode_.height = sensorInfo.outputSize.height;
- mode_.sensor_width = sensorInfo.activeAreaSize.width;
- mode_.sensor_height = sensorInfo.activeAreaSize.height;
- mode_.crop_x = sensorInfo.analogCrop.x;
- mode_.crop_y = sensorInfo.analogCrop.y;
-
- /*
- * Calculate scaling parameters. The scale_[xy] factors are determined
- * by the ratio between the crop rectangle size and the output size.
- */
- mode_.scale_x = sensorInfo.analogCrop.width / sensorInfo.outputSize.width;
- mode_.scale_y = sensorInfo.analogCrop.height / sensorInfo.outputSize.height;
-
- /*
- * We're not told by the pipeline handler how scaling is split between
- * binning and digital scaling. For now, as a heuristic, assume that
- * downscaling up to 2 is achieved through binning, and that any
- * additional scaling is achieved through digital scaling.
- *
- * \todo Get the pipeline handle to provide the full data
- */
- mode_.bin_x = std::min(2, static_cast<int>(mode_.scale_x));
- mode_.bin_y = std::min(2, static_cast<int>(mode_.scale_y));
-
- /* The noise factor is the square root of the total binning factor. */
- mode_.noise_factor = sqrt(mode_.bin_x * mode_.bin_y);
-
- /*
- * Calculate the line length as the ratio between the line length in
- * pixels and the pixel rate.
- */
- mode_.line_length = sensorInfo.lineLength * (1.0s / sensorInfo.pixelRate);
-
- /*
- * Set the frame length limits for the mode to ensure exposure and
- * framerate calculations are clipped appropriately.
- */
- mode_.min_frame_length = sensorInfo.minFrameLength;
- mode_.max_frame_length = sensorInfo.maxFrameLength;
-
- /*
- * Some sensors may have different sensitivities in different modes;
- * the CamHelper will know the correct value.
- */
- mode_.sensitivity = helper_->GetModeSensitivity(mode_);
-}
-
-int IPARPi::configure(const IPACameraSensorInfo &sensorInfo,
- [[maybe_unused]] const std::map<unsigned int, IPAStream> &streamConfig,
- const std::map<unsigned int, ControlInfoMap> &entityControls,
- const IPAConfig &ipaConfig,
- ControlList *controls, IPAConfigResult *result)
-{
- if (entityControls.size() != 2) {
- LOG(IPARPI, Error) << "No ISP or sensor controls found.";
- return -1;
- }
-
- sensorCtrls_ = entityControls.at(0);
- ispCtrls_ = entityControls.at(1);
-
- if (!validateSensorControls()) {
- LOG(IPARPI, Error) << "Sensor control validation failed.";
- return -1;
- }
-
- if (!validateIspControls()) {
- LOG(IPARPI, Error) << "ISP control validation failed.";
- return -1;
- }
-
- maxSensorGainCode_ = sensorCtrls_.at(V4L2_CID_ANALOGUE_GAIN).max().get<int32_t>();
-
- /* Setup a metadata ControlList to output metadata. */
- libcameraMetadata_ = ControlList(controls::controls);
-
- /* Re-assemble camera mode using the sensor info. */
- setMode(sensorInfo);
-
- mode_.transform = static_cast<libcamera::Transform>(ipaConfig.transform);
-
- /* Store the lens shading table pointer and handle if available. */
- if (ipaConfig.lsTableHandle.isValid()) {
- /* Remove any previous table, if there was one. */
- if (lsTable_) {
- munmap(lsTable_, MaxLsGridSize);
- lsTable_ = nullptr;
- }
-
- /* Map the LS table buffer into user space. */
- lsTableHandle_ = std::move(ipaConfig.lsTableHandle);
- if (lsTableHandle_.isValid()) {
- lsTable_ = mmap(nullptr, MaxLsGridSize, PROT_READ | PROT_WRITE,
- MAP_SHARED, lsTableHandle_.get(), 0);
-
- if (lsTable_ == MAP_FAILED) {
- LOG(IPARPI, Error) << "dmaHeap mmap failure for LS table.";
- lsTable_ = nullptr;
- }
- }
- }
-
- /* Pass the camera mode to the CamHelper to setup algorithms. */
- helper_->SetCameraMode(mode_);
-
- /*
- * Initialise this ControlList correctly, even if empty, in case the IPA is
- * running is isolation mode (passing the ControlList through the IPC layer).
- */
- ControlList ctrls(sensorCtrls_);
-
- /* The pipeline handler passes out the mode's sensitivity. */
- result->modeSensitivity = mode_.sensitivity;
-
- if (firstStart_) {
- /* Supply initial values for frame durations. */
- applyFrameDurations(defaultMinFrameDuration, defaultMaxFrameDuration);
-
- /* Supply initial values for gain and exposure. */
- AgcStatus agcStatus;
- agcStatus.shutter_time = defaultExposureTime;
- agcStatus.analogue_gain = defaultAnalogueGain;
- applyAGC(&agcStatus, ctrls);
- }
-
- ASSERT(controls);
- *controls = std::move(ctrls);
-
- /*
- * Apply the correct limits to the exposure, gain and frame duration controls
- * based on the current sensor mode.
- */
- ControlInfoMap::Map ctrlMap = ipaControls;
- const Duration minSensorFrameDuration = mode_.min_frame_length * mode_.line_length;
- const Duration maxSensorFrameDuration = mode_.max_frame_length * mode_.line_length;
- ctrlMap[&controls::FrameDurationLimits] =
- ControlInfo(static_cast<int64_t>(minSensorFrameDuration.get<std::micro>()),
- static_cast<int64_t>(maxSensorFrameDuration.get<std::micro>()));
-
- ctrlMap[&controls::AnalogueGain] =
- ControlInfo(1.0f, static_cast<float>(helper_->Gain(maxSensorGainCode_)));
-
- /*
- * Calculate the max exposure limit from the frame duration limit as V4L2
- * will limit the maximum control value based on the current VBLANK value.
- */
- Duration maxShutter = Duration::max();
- helper_->GetVBlanking(maxShutter, minSensorFrameDuration, maxSensorFrameDuration);
- const uint32_t exposureMin = sensorCtrls_.at(V4L2_CID_EXPOSURE).min().get<int32_t>();
-
- ctrlMap[&controls::ExposureTime] =
- ControlInfo(static_cast<int32_t>(helper_->Exposure(exposureMin).get<std::micro>()),
- static_cast<int32_t>(maxShutter.get<std::micro>()));
-
- result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
- return 0;
-}
-
-void IPARPi::mapBuffers(const std::vector<IPABuffer> &buffers)
-{
- for (const IPABuffer &buffer : buffers) {
- const FrameBuffer fb(buffer.planes);
- buffers_.emplace(buffer.id,
- MappedFrameBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite));
- }
-}
-
-void IPARPi::unmapBuffers(const std::vector<unsigned int> &ids)
-{
- for (unsigned int id : ids) {
- auto it = buffers_.find(id);
- if (it == buffers_.end())
- continue;
-
- buffers_.erase(id);
- }
-}
-
-void IPARPi::signalStatReady(uint32_t bufferId)
-{
- if (++checkCount_ != frameCount_) /* assert here? */
- LOG(IPARPI, Error) << "WARNING: Prepare/Process mismatch!!!";
- if (processPending_ && frameCount_ > mistrustCount_)
- processStats(bufferId);
-
- reportMetadata();
-
- statsMetadataComplete.emit(bufferId & MaskID, libcameraMetadata_);
-}
-
-void IPARPi::signalQueueRequest(const ControlList &controls)
-{
- queueRequest(controls);
-}
-
-void IPARPi::signalIspPrepare(const ISPConfig &data)
-{
- /*
- * At start-up, or after a mode-switch, we may want to
- * avoid running the control algos for a few frames in case
- * they are "unreliable".
- */
- prepareISP(data);
- frameCount_++;
-
- /* Ready to push the input buffer into the ISP. */
- runIsp.emit(data.bayerBufferId & MaskID);
-}
-
-void IPARPi::reportMetadata()
-{
- std::unique_lock<RPiController::Metadata> lock(rpiMetadata_);
-
- /*
- * Certain information about the current frame and how it will be
- * processed can be extracted and placed into the libcamera metadata
- * buffer, where an application could query it.
- */
- DeviceStatus *deviceStatus = rpiMetadata_.GetLocked<DeviceStatus>("device.status");
- if (deviceStatus) {
- libcameraMetadata_.set(controls::ExposureTime,
- deviceStatus->shutter_speed.get<std::micro>());
- libcameraMetadata_.set(controls::AnalogueGain, deviceStatus->analogue_gain);
- libcameraMetadata_.set(controls::FrameDuration,
- helper_->Exposure(deviceStatus->frame_length).get<std::micro>());
- if (deviceStatus->sensor_temperature)
- libcameraMetadata_.set(controls::SensorTemperature, *deviceStatus->sensor_temperature);
- }
-
- AgcStatus *agcStatus = rpiMetadata_.GetLocked<AgcStatus>("agc.status");
- if (agcStatus) {
- libcameraMetadata_.set(controls::AeLocked, agcStatus->locked);
- libcameraMetadata_.set(controls::DigitalGain, agcStatus->digital_gain);
- }
-
- LuxStatus *luxStatus = rpiMetadata_.GetLocked<LuxStatus>("lux.status");
- if (luxStatus)
- libcameraMetadata_.set(controls::Lux, luxStatus->lux);
-
- AwbStatus *awbStatus = rpiMetadata_.GetLocked<AwbStatus>("awb.status");
- if (awbStatus) {
- libcameraMetadata_.set(controls::ColourGains, { static_cast<float>(awbStatus->gain_r),
- static_cast<float>(awbStatus->gain_b) });
- libcameraMetadata_.set(controls::ColourTemperature, awbStatus->temperature_K);
- }
-
- BlackLevelStatus *blackLevelStatus = rpiMetadata_.GetLocked<BlackLevelStatus>("black_level.status");
- if (blackLevelStatus)
- libcameraMetadata_.set(controls::SensorBlackLevels,
- { static_cast<int32_t>(blackLevelStatus->black_level_r),
- static_cast<int32_t>(blackLevelStatus->black_level_g),
- static_cast<int32_t>(blackLevelStatus->black_level_g),
- static_cast<int32_t>(blackLevelStatus->black_level_b) });
-
- FocusStatus *focusStatus = rpiMetadata_.GetLocked<FocusStatus>("focus.status");
- if (focusStatus && focusStatus->num == 12) {
- /*
- * We get a 4x3 grid of regions by default. Calculate the average
- * FoM over the central two positions to give an overall scene FoM.
- * This can change later if it is not deemed suitable.
- */
- int32_t focusFoM = (focusStatus->focus_measures[5] + focusStatus->focus_measures[6]) / 2;
- libcameraMetadata_.set(controls::FocusFoM, focusFoM);
- }
-
- CcmStatus *ccmStatus = rpiMetadata_.GetLocked<CcmStatus>("ccm.status");
- if (ccmStatus) {
- float m[9];
- for (unsigned int i = 0; i < 9; i++)
- m[i] = ccmStatus->matrix[i];
- libcameraMetadata_.set(controls::ColourCorrectionMatrix, m);
- }
-}
-
-bool IPARPi::validateSensorControls()
-{
- static const uint32_t ctrls[] = {
- V4L2_CID_ANALOGUE_GAIN,
- V4L2_CID_EXPOSURE,
- V4L2_CID_VBLANK,
- };
-
- for (auto c : ctrls) {
- if (sensorCtrls_.find(c) == sensorCtrls_.end()) {
- LOG(IPARPI, Error) << "Unable to find sensor control "
- << utils::hex(c);
- return false;
- }
- }
-
- return true;
-}
-
-bool IPARPi::validateIspControls()
-{
- static const uint32_t ctrls[] = {
- V4L2_CID_RED_BALANCE,
- V4L2_CID_BLUE_BALANCE,
- V4L2_CID_DIGITAL_GAIN,
- V4L2_CID_USER_BCM2835_ISP_CC_MATRIX,
- V4L2_CID_USER_BCM2835_ISP_GAMMA,
- V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL,
- V4L2_CID_USER_BCM2835_ISP_GEQ,
- V4L2_CID_USER_BCM2835_ISP_DENOISE,
- V4L2_CID_USER_BCM2835_ISP_SHARPEN,
- V4L2_CID_USER_BCM2835_ISP_DPC,
- V4L2_CID_USER_BCM2835_ISP_LENS_SHADING,
- V4L2_CID_USER_BCM2835_ISP_CDN,
- };
-
- for (auto c : ctrls) {
- if (ispCtrls_.find(c) == ispCtrls_.end()) {
- LOG(IPARPI, Error) << "Unable to find ISP control "
- << utils::hex(c);
- return false;
- }
- }
-
- return true;
-}
-
-/*
- * Converting between enums (used in the libcamera API) and the names that
- * we use to identify different modes. Unfortunately, the conversion tables
- * must be kept up-to-date by hand.
- */
-static const std::map<int32_t, std::string> MeteringModeTable = {
- { controls::MeteringCentreWeighted, "centre-weighted" },
- { controls::MeteringSpot, "spot" },
- { controls::MeteringMatrix, "matrix" },
- { controls::MeteringCustom, "custom" },
-};
-
-static const std::map<int32_t, std::string> ConstraintModeTable = {
- { controls::ConstraintNormal, "normal" },
- { controls::ConstraintHighlight, "highlight" },
- { controls::ConstraintCustom, "custom" },
-};
-
-static const std::map<int32_t, std::string> ExposureModeTable = {
- { controls::ExposureNormal, "normal" },
- { controls::ExposureShort, "short" },
- { controls::ExposureLong, "long" },
- { controls::ExposureCustom, "custom" },
-};
-
-static const std::map<int32_t, std::string> AwbModeTable = {
- { controls::AwbAuto, "auto" },
- { controls::AwbIncandescent, "incandescent" },
- { controls::AwbTungsten, "tungsten" },
- { controls::AwbFluorescent, "fluorescent" },
- { controls::AwbIndoor, "indoor" },
- { controls::AwbDaylight, "daylight" },
- { controls::AwbCloudy, "cloudy" },
- { controls::AwbCustom, "custom" },
-};
-
-static const std::map<int32_t, RPiController::DenoiseMode> DenoiseModeTable = {
- { controls::draft::NoiseReductionModeOff, RPiController::DenoiseMode::Off },
- { controls::draft::NoiseReductionModeFast, RPiController::DenoiseMode::ColourFast },
- { controls::draft::NoiseReductionModeHighQuality, RPiController::DenoiseMode::ColourHighQuality },
- { controls::draft::NoiseReductionModeMinimal, RPiController::DenoiseMode::ColourOff },
- { controls::draft::NoiseReductionModeZSL, RPiController::DenoiseMode::ColourHighQuality },
-};
-
-void IPARPi::queueRequest(const ControlList &controls)
-{
- /* Clear the return metadata buffer. */
- libcameraMetadata_.clear();
-
- for (auto const &ctrl : controls) {
- LOG(IPARPI, Debug) << "Request ctrl: "
- << controls::controls.at(ctrl.first)->name()
- << " = " << ctrl.second.toString();
-
- switch (ctrl.first) {
- case controls::AE_ENABLE: {
- RPiController::Algorithm *agc = controller_.GetAlgorithm("agc");
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set AE_ENABLE - no AGC algorithm";
- break;
- }
-
- if (ctrl.second.get<bool>() == false)
- agc->Pause();
- else
- agc->Resume();
-
- libcameraMetadata_.set(controls::AeEnable, ctrl.second.get<bool>());
- break;
- }
-
- case controls::EXPOSURE_TIME: {
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set EXPOSURE_TIME - no AGC algorithm";
- break;
- }
-
- /* The control provides units of microseconds. */
- agc->SetFixedShutter(ctrl.second.get<int32_t>() * 1.0us);
-
- libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>());
- break;
- }
-
- case controls::ANALOGUE_GAIN: {
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set ANALOGUE_GAIN - no AGC algorithm";
- break;
- }
-
- agc->SetFixedAnalogueGain(ctrl.second.get<float>());
-
- libcameraMetadata_.set(controls::AnalogueGain,
- ctrl.second.get<float>());
- break;
- }
-
- case controls::AE_METERING_MODE: {
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set AE_METERING_MODE - no AGC algorithm";
- break;
- }
-
- int32_t idx = ctrl.second.get<int32_t>();
- if (MeteringModeTable.count(idx)) {
- agc->SetMeteringMode(MeteringModeTable.at(idx));
- libcameraMetadata_.set(controls::AeMeteringMode, idx);
- } else {
- LOG(IPARPI, Error) << "Metering mode " << idx
- << " not recognised";
- }
- break;
- }
-
- case controls::AE_CONSTRAINT_MODE: {
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set AE_CONSTRAINT_MODE - no AGC algorithm";
- break;
- }
-
- int32_t idx = ctrl.second.get<int32_t>();
- if (ConstraintModeTable.count(idx)) {
- agc->SetConstraintMode(ConstraintModeTable.at(idx));
- libcameraMetadata_.set(controls::AeConstraintMode, idx);
- } else {
- LOG(IPARPI, Error) << "Constraint mode " << idx
- << " not recognised";
- }
- break;
- }
-
- case controls::AE_EXPOSURE_MODE: {
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set AE_EXPOSURE_MODE - no AGC algorithm";
- break;
- }
-
- int32_t idx = ctrl.second.get<int32_t>();
- if (ExposureModeTable.count(idx)) {
- agc->SetExposureMode(ExposureModeTable.at(idx));
- libcameraMetadata_.set(controls::AeExposureMode, idx);
- } else {
- LOG(IPARPI, Error) << "Exposure mode " << idx
- << " not recognised";
- }
- break;
- }
-
- case controls::EXPOSURE_VALUE: {
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- if (!agc) {
- LOG(IPARPI, Warning)
- << "Could not set EXPOSURE_VALUE - no AGC algorithm";
- break;
- }
-
- /*
- * The SetEv() function takes in a direct exposure multiplier.
- * So convert to 2^EV
- */
- double ev = pow(2.0, ctrl.second.get<float>());
- agc->SetEv(ev);
- libcameraMetadata_.set(controls::ExposureValue,
- ctrl.second.get<float>());
- break;
- }
-
- case controls::AWB_ENABLE: {
- RPiController::Algorithm *awb = controller_.GetAlgorithm("awb");
- if (!awb) {
- LOG(IPARPI, Warning)
- << "Could not set AWB_ENABLE - no AWB algorithm";
- break;
- }
-
- if (ctrl.second.get<bool>() == false)
- awb->Pause();
- else
- awb->Resume();
-
- libcameraMetadata_.set(controls::AwbEnable,
- ctrl.second.get<bool>());
- break;
- }
-
- case controls::AWB_MODE: {
- RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
- controller_.GetAlgorithm("awb"));
- if (!awb) {
- LOG(IPARPI, Warning)
- << "Could not set AWB_MODE - no AWB algorithm";
- break;
- }
-
- int32_t idx = ctrl.second.get<int32_t>();
- if (AwbModeTable.count(idx)) {
- awb->SetMode(AwbModeTable.at(idx));
- libcameraMetadata_.set(controls::AwbMode, idx);
- } else {
- LOG(IPARPI, Error) << "AWB mode " << idx
- << " not recognised";
- }
- break;
- }
-
- case controls::COLOUR_GAINS: {
- auto gains = ctrl.second.get<Span<const float>>();
- RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
- controller_.GetAlgorithm("awb"));
- if (!awb) {
- LOG(IPARPI, Warning)
- << "Could not set COLOUR_GAINS - no AWB algorithm";
- break;
- }
-
- awb->SetManualGains(gains[0], gains[1]);
- if (gains[0] != 0.0f && gains[1] != 0.0f)
- /* A gain of 0.0f will switch back to auto mode. */
- libcameraMetadata_.set(controls::ColourGains,
- { gains[0], gains[1] });
- break;
- }
-
- case controls::BRIGHTNESS: {
- RPiController::ContrastAlgorithm *contrast = dynamic_cast<RPiController::ContrastAlgorithm *>(
- controller_.GetAlgorithm("contrast"));
- if (!contrast) {
- LOG(IPARPI, Warning)
- << "Could not set BRIGHTNESS - no contrast algorithm";
- break;
- }
-
- contrast->SetBrightness(ctrl.second.get<float>() * 65536);
- libcameraMetadata_.set(controls::Brightness,
- ctrl.second.get<float>());
- break;
- }
-
- case controls::CONTRAST: {
- RPiController::ContrastAlgorithm *contrast = dynamic_cast<RPiController::ContrastAlgorithm *>(
- controller_.GetAlgorithm("contrast"));
- if (!contrast) {
- LOG(IPARPI, Warning)
- << "Could not set CONTRAST - no contrast algorithm";
- break;
- }
-
- contrast->SetContrast(ctrl.second.get<float>());
- libcameraMetadata_.set(controls::Contrast,
- ctrl.second.get<float>());
- break;
- }
-
- case controls::SATURATION: {
- RPiController::CcmAlgorithm *ccm = dynamic_cast<RPiController::CcmAlgorithm *>(
- controller_.GetAlgorithm("ccm"));
- if (!ccm) {
- LOG(IPARPI, Warning)
- << "Could not set SATURATION - no ccm algorithm";
- break;
- }
-
- ccm->SetSaturation(ctrl.second.get<float>());
- libcameraMetadata_.set(controls::Saturation,
- ctrl.second.get<float>());
- break;
- }
-
- case controls::SHARPNESS: {
- RPiController::SharpenAlgorithm *sharpen = dynamic_cast<RPiController::SharpenAlgorithm *>(
- controller_.GetAlgorithm("sharpen"));
- if (!sharpen) {
- LOG(IPARPI, Warning)
- << "Could not set SHARPNESS - no sharpen algorithm";
- break;
- }
-
- sharpen->SetStrength(ctrl.second.get<float>());
- libcameraMetadata_.set(controls::Sharpness,
- ctrl.second.get<float>());
- break;
- }
-
- case controls::SCALER_CROP: {
- /* We do nothing with this, but should avoid the warning below. */
- break;
- }
-
- case controls::FRAME_DURATION_LIMITS: {
- auto frameDurations = ctrl.second.get<Span<const int64_t>>();
- applyFrameDurations(frameDurations[0] * 1.0us, frameDurations[1] * 1.0us);
- break;
- }
-
- case controls::NOISE_REDUCTION_MODE: {
- RPiController::DenoiseAlgorithm *sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>(
- controller_.GetAlgorithm("SDN"));
- if (!sdn) {
- LOG(IPARPI, Warning)
- << "Could not set NOISE_REDUCTION_MODE - no SDN algorithm";
- break;
- }
-
- int32_t idx = ctrl.second.get<int32_t>();
- auto mode = DenoiseModeTable.find(idx);
- if (mode != DenoiseModeTable.end()) {
- sdn->SetMode(mode->second);
-
- /*
- * \todo If the colour denoise is not going to run due to an
- * analysis image resolution or format mismatch, we should
- * report the status correctly in the metadata.
- */
- libcameraMetadata_.set(controls::draft::NoiseReductionMode, idx);
- } else {
- LOG(IPARPI, Error) << "Noise reduction mode " << idx
- << " not recognised";
- }
- break;
- }
-
- default:
- LOG(IPARPI, Warning)
- << "Ctrl " << controls::controls.at(ctrl.first)->name()
- << " is not handled.";
- break;
- }
- }
-}
-
-void IPARPi::returnEmbeddedBuffer(unsigned int bufferId)
-{
- embeddedComplete.emit(bufferId & MaskID);
-}
-
-void IPARPi::prepareISP(const ISPConfig &data)
-{
- int64_t frameTimestamp = data.controls.get(controls::SensorTimestamp);
- RPiController::Metadata lastMetadata;
- Span<uint8_t> embeddedBuffer;
-
- lastMetadata = std::move(rpiMetadata_);
- fillDeviceStatus(data.controls);
-
- if (data.embeddedBufferPresent) {
- /*
- * Pipeline handler has supplied us with an embedded data buffer,
- * we must pass it to the CamHelper for parsing.
- */
- auto it = buffers_.find(data.embeddedBufferId);
- ASSERT(it != buffers_.end());
- embeddedBuffer = it->second.planes()[0];
- }
-
- /*
- * This may overwrite the DeviceStatus using values from the sensor
- * metadata, and may also do additional custom processing.
- */
- helper_->Prepare(embeddedBuffer, rpiMetadata_);
-
- /* Done with embedded data now, return to pipeline handler asap. */
- if (data.embeddedBufferPresent)
- returnEmbeddedBuffer(data.embeddedBufferId);
-
- /* Allow a 10% margin on the comparison below. */
- Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns;
- if (lastRunTimestamp_ && frameCount_ > dropFrameCount_ &&
- delta < controllerMinFrameDuration * 0.9) {
- /*
- * Ensure we merge the previous frame's metadata with the current
- * frame. This will not overwrite exposure/gain values for the
- * current frame, or any other bits of metadata that were added
- * in helper_->Prepare().
- */
- rpiMetadata_.Merge(lastMetadata);
- processPending_ = false;
- return;
- }
-
- lastRunTimestamp_ = frameTimestamp;
- processPending_ = true;
-
- ControlList ctrls(ispCtrls_);
-
- controller_.Prepare(&rpiMetadata_);
-
- /* Lock the metadata buffer to avoid constant locks/unlocks. */
- std::unique_lock<RPiController::Metadata> lock(rpiMetadata_);
-
- AwbStatus *awbStatus = rpiMetadata_.GetLocked<AwbStatus>("awb.status");
- if (awbStatus)
- applyAWB(awbStatus, ctrls);
-
- CcmStatus *ccmStatus = rpiMetadata_.GetLocked<CcmStatus>("ccm.status");
- if (ccmStatus)
- applyCCM(ccmStatus, ctrls);
-
- AgcStatus *dgStatus = rpiMetadata_.GetLocked<AgcStatus>("agc.status");
- if (dgStatus)
- applyDG(dgStatus, ctrls);
-
- AlscStatus *lsStatus = rpiMetadata_.GetLocked<AlscStatus>("alsc.status");
- if (lsStatus)
- applyLS(lsStatus, ctrls);
-
- ContrastStatus *contrastStatus = rpiMetadata_.GetLocked<ContrastStatus>("contrast.status");
- if (contrastStatus)
- applyGamma(contrastStatus, ctrls);
-
- BlackLevelStatus *blackLevelStatus = rpiMetadata_.GetLocked<BlackLevelStatus>("black_level.status");
- if (blackLevelStatus)
- applyBlackLevel(blackLevelStatus, ctrls);
-
- GeqStatus *geqStatus = rpiMetadata_.GetLocked<GeqStatus>("geq.status");
- if (geqStatus)
- applyGEQ(geqStatus, ctrls);
-
- DenoiseStatus *denoiseStatus = rpiMetadata_.GetLocked<DenoiseStatus>("denoise.status");
- if (denoiseStatus)
- applyDenoise(denoiseStatus, ctrls);
-
- SharpenStatus *sharpenStatus = rpiMetadata_.GetLocked<SharpenStatus>("sharpen.status");
- if (sharpenStatus)
- applySharpen(sharpenStatus, ctrls);
-
- DpcStatus *dpcStatus = rpiMetadata_.GetLocked<DpcStatus>("dpc.status");
- if (dpcStatus)
- applyDPC(dpcStatus, ctrls);
-
- if (!ctrls.empty())
- setIspControls.emit(ctrls);
-}
-
-void IPARPi::fillDeviceStatus(const ControlList &sensorControls)
-{
- DeviceStatus deviceStatus = {};
-
- int32_t exposureLines = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
- int32_t gainCode = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
- int32_t vblank = sensorControls.get(V4L2_CID_VBLANK).get<int32_t>();
-
- deviceStatus.shutter_speed = helper_->Exposure(exposureLines);
- deviceStatus.analogue_gain = helper_->Gain(gainCode);
- deviceStatus.frame_length = mode_.height + vblank;
-
- LOG(IPARPI, Debug) << "Metadata - " << deviceStatus;
-
- rpiMetadata_.Set("device.status", deviceStatus);
-}
-
-void IPARPi::processStats(unsigned int bufferId)
-{
- auto it = buffers_.find(bufferId);
- if (it == buffers_.end()) {
- LOG(IPARPI, Error) << "Could not find stats buffer!";
- return;
- }
-
- Span<uint8_t> mem = it->second.planes()[0];
- bcm2835_isp_stats *stats = reinterpret_cast<bcm2835_isp_stats *>(mem.data());
- RPiController::StatisticsPtr statistics = std::make_shared<bcm2835_isp_stats>(*stats);
- helper_->Process(statistics, rpiMetadata_);
- controller_.Process(statistics, &rpiMetadata_);
-
- struct AgcStatus agcStatus;
- if (rpiMetadata_.Get("agc.status", agcStatus) == 0) {
- ControlList ctrls(sensorCtrls_);
- applyAGC(&agcStatus, ctrls);
-
- setDelayedControls.emit(ctrls);
- }
-}
-
-void IPARPi::applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls)
-{
- LOG(IPARPI, Debug) << "Applying WB R: " << awbStatus->gain_r << " B: "
- << awbStatus->gain_b;
-
- ctrls.set(V4L2_CID_RED_BALANCE,
- static_cast<int32_t>(awbStatus->gain_r * 1000));
- ctrls.set(V4L2_CID_BLUE_BALANCE,
- static_cast<int32_t>(awbStatus->gain_b * 1000));
-}
-
-void IPARPi::applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration)
-{
- const Duration minSensorFrameDuration = mode_.min_frame_length * mode_.line_length;
- const Duration maxSensorFrameDuration = mode_.max_frame_length * mode_.line_length;
-
- /*
- * This will only be applied once AGC recalculations occur.
- * The values may be clamped based on the sensor mode capabilities as well.
- */
- minFrameDuration_ = minFrameDuration ? minFrameDuration : defaultMaxFrameDuration;
- maxFrameDuration_ = maxFrameDuration ? maxFrameDuration : defaultMinFrameDuration;
- minFrameDuration_ = std::clamp(minFrameDuration_,
- minSensorFrameDuration, maxSensorFrameDuration);
- maxFrameDuration_ = std::clamp(maxFrameDuration_,
- minSensorFrameDuration, maxSensorFrameDuration);
- maxFrameDuration_ = std::max(maxFrameDuration_, minFrameDuration_);
-
- /* Return the validated limits via metadata. */
- libcameraMetadata_.set(controls::FrameDurationLimits,
- { static_cast<int64_t>(minFrameDuration_.get<std::micro>()),
- static_cast<int64_t>(maxFrameDuration_.get<std::micro>()) });
-
- /*
- * Calculate the maximum exposure time possible for the AGC to use.
- * GetVBlanking() will update maxShutter with the largest exposure
- * value possible.
- */
- Duration maxShutter = Duration::max();
- helper_->GetVBlanking(maxShutter, minFrameDuration_, maxFrameDuration_);
-
- RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
- controller_.GetAlgorithm("agc"));
- agc->SetMaxShutter(maxShutter);
-}
-
-void IPARPi::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls)
-{
- int32_t gainCode = helper_->GainCode(agcStatus->analogue_gain);
-
- /*
- * Ensure anything larger than the max gain code will not be passed to
- * DelayedControls. The AGC will correctly handle a lower gain returned
- * by the sensor, provided it knows the actual gain used.
- */
- gainCode = std::min<int32_t>(gainCode, maxSensorGainCode_);
-
- /* GetVBlanking might clip exposure time to the fps limits. */
- Duration exposure = agcStatus->shutter_time;
- int32_t vblanking = helper_->GetVBlanking(exposure, minFrameDuration_, maxFrameDuration_);
- int32_t exposureLines = helper_->ExposureLines(exposure);
-
- LOG(IPARPI, Debug) << "Applying AGC Exposure: " << exposure
- << " (Shutter lines: " << exposureLines << ", AGC requested "
- << agcStatus->shutter_time << ") Gain: "
- << agcStatus->analogue_gain << " (Gain Code: "
- << gainCode << ")";
-
- /*
- * Due to the behavior of V4L2, the current value of VBLANK could clip the
- * exposure time without us knowing. The next time though this function should
- * clip exposure correctly.
- */
- ctrls.set(V4L2_CID_VBLANK, vblanking);
- ctrls.set(V4L2_CID_EXPOSURE, exposureLines);
- ctrls.set(V4L2_CID_ANALOGUE_GAIN, gainCode);
-}
-
-void IPARPi::applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls)
-{
- ctrls.set(V4L2_CID_DIGITAL_GAIN,
- static_cast<int32_t>(dgStatus->digital_gain * 1000));
-}
-
-void IPARPi::applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls)
-{
- bcm2835_isp_custom_ccm ccm;
-
- for (int i = 0; i < 9; i++) {
- ccm.ccm.ccm[i / 3][i % 3].den = 1000;
- ccm.ccm.ccm[i / 3][i % 3].num = 1000 * ccmStatus->matrix[i];
- }
-
- ccm.enabled = 1;
- ccm.ccm.offsets[0] = ccm.ccm.offsets[1] = ccm.ccm.offsets[2] = 0;
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&ccm),
- sizeof(ccm) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_CC_MATRIX, c);
-}
-
-void IPARPi::applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls)
-{
- struct bcm2835_isp_gamma gamma;
-
- gamma.enabled = 1;
- for (int i = 0; i < CONTRAST_NUM_POINTS; i++) {
- gamma.x[i] = contrastStatus->points[i].x;
- gamma.y[i] = contrastStatus->points[i].y;
- }
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&gamma),
- sizeof(gamma) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_GAMMA, c);
-}
-
-void IPARPi::applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls)
-{
- bcm2835_isp_black_level blackLevel;
-
- blackLevel.enabled = 1;
- blackLevel.black_level_r = blackLevelStatus->black_level_r;
- blackLevel.black_level_g = blackLevelStatus->black_level_g;
- blackLevel.black_level_b = blackLevelStatus->black_level_b;
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&blackLevel),
- sizeof(blackLevel) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL, c);
-}
-
-void IPARPi::applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls)
-{
- bcm2835_isp_geq geq;
-
- geq.enabled = 1;
- geq.offset = geqStatus->offset;
- geq.slope.den = 1000;
- geq.slope.num = 1000 * geqStatus->slope;
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&geq),
- sizeof(geq) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_GEQ, c);
-}
-
-void IPARPi::applyDenoise(const struct DenoiseStatus *denoiseStatus, ControlList &ctrls)
-{
- using RPiController::DenoiseMode;
-
- bcm2835_isp_denoise denoise;
- DenoiseMode mode = static_cast<DenoiseMode>(denoiseStatus->mode);
-
- denoise.enabled = mode != DenoiseMode::Off;
- denoise.constant = denoiseStatus->noise_constant;
- denoise.slope.num = 1000 * denoiseStatus->noise_slope;
- denoise.slope.den = 1000;
- denoise.strength.num = 1000 * denoiseStatus->strength;
- denoise.strength.den = 1000;
-
- /* Set the CDN mode to match the SDN operating mode. */
- bcm2835_isp_cdn cdn;
- switch (mode) {
- case DenoiseMode::ColourFast:
- cdn.enabled = 1;
- cdn.mode = CDN_MODE_FAST;
- break;
- case DenoiseMode::ColourHighQuality:
- cdn.enabled = 1;
- cdn.mode = CDN_MODE_HIGH_QUALITY;
- break;
- default:
- cdn.enabled = 0;
- }
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&denoise),
- sizeof(denoise) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_DENOISE, c);
-
- c = ControlValue(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&cdn),
- sizeof(cdn) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_CDN, c);
-}
-
-void IPARPi::applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls)
-{
- bcm2835_isp_sharpen sharpen;
-
- sharpen.enabled = 1;
- sharpen.threshold.num = 1000 * sharpenStatus->threshold;
- sharpen.threshold.den = 1000;
- sharpen.strength.num = 1000 * sharpenStatus->strength;
- sharpen.strength.den = 1000;
- sharpen.limit.num = 1000 * sharpenStatus->limit;
- sharpen.limit.den = 1000;
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&sharpen),
- sizeof(sharpen) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_SHARPEN, c);
-}
-
-void IPARPi::applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls)
-{
- bcm2835_isp_dpc dpc;
-
- dpc.enabled = 1;
- dpc.strength = dpcStatus->strength;
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&dpc),
- sizeof(dpc) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_DPC, c);
-}
-
-void IPARPi::applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls)
-{
- /*
- * Program lens shading tables into pipeline.
- * Choose smallest cell size that won't exceed 63x48 cells.
- */
- const int cellSizes[] = { 16, 32, 64, 128, 256 };
- unsigned int numCells = std::size(cellSizes);
- unsigned int i, w, h, cellSize;
- for (i = 0; i < numCells; i++) {
- cellSize = cellSizes[i];
- w = (mode_.width + cellSize - 1) / cellSize;
- h = (mode_.height + cellSize - 1) / cellSize;
- if (w < 64 && h <= 48)
- break;
- }
-
- if (i == numCells) {
- LOG(IPARPI, Error) << "Cannot find cell size";
- return;
- }
-
- /* We're going to supply corner sampled tables, 16 bit samples. */
- w++, h++;
- bcm2835_isp_lens_shading ls = {
- .enabled = 1,
- .grid_cell_size = cellSize,
- .grid_width = w,
- .grid_stride = w,
- .grid_height = h,
- /* .dmabuf will be filled in by pipeline handler. */
- .dmabuf = 0,
- .ref_transform = 0,
- .corner_sampled = 1,
- .gain_format = GAIN_FORMAT_U4P10
- };
-
- if (!lsTable_ || w * h * 4 * sizeof(uint16_t) > MaxLsGridSize) {
- LOG(IPARPI, Error) << "Do not have a correctly allocate lens shading table!";
- return;
- }
-
- if (lsStatus) {
- /* Format will be u4.10 */
- uint16_t *grid = static_cast<uint16_t *>(lsTable_);
-
- resampleTable(grid, lsStatus->r, w, h);
- resampleTable(grid + w * h, lsStatus->g, w, h);
- std::memcpy(grid + 2 * w * h, grid + w * h, w * h * sizeof(uint16_t));
- resampleTable(grid + 3 * w * h, lsStatus->b, w, h);
- }
-
- ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&ls),
- sizeof(ls) });
- ctrls.set(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING, c);
-}
-
-/*
- * Resamples a 16x12 table with central sampling to destW x destH with corner
- * sampling.
- */
-void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],
- int destW, int destH)
-{
- /*
- * Precalculate and cache the x sampling locations and phases to
- * save recomputing them on every row.
- */
- assert(destW > 1 && destH > 1 && destW <= 64);
- int xLo[64], xHi[64];
- double xf[64];
- double x = -0.5, xInc = 16.0 / (destW - 1);
- for (int i = 0; i < destW; i++, x += xInc) {
- xLo[i] = floor(x);
- xf[i] = x - xLo[i];
- xHi[i] = xLo[i] < 15 ? xLo[i] + 1 : 15;
- xLo[i] = xLo[i] > 0 ? xLo[i] : 0;
- }
-
- /* Now march over the output table generating the new values. */
- double y = -0.5, yInc = 12.0 / (destH - 1);
- for (int j = 0; j < destH; j++, y += yInc) {
- int yLo = floor(y);
- double yf = y - yLo;
- int yHi = yLo < 11 ? yLo + 1 : 11;
- yLo = yLo > 0 ? yLo : 0;
- double const *rowAbove = src[yLo];
- double const *rowBelow = src[yHi];
- for (int i = 0; i < destW; i++) {
- double above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i];
- double below = rowBelow[xLo[i]] * (1 - xf[i]) + rowBelow[xHi[i]] * xf[i];
- int result = floor(1024 * (above * (1 - yf) + below * yf) + .5);
- *(dest++) = result > 16383 ? 16383 : result; /* want u4.10 */
- }
- }
-}
-
-} /* namespace ipa::RPi */
-
-/*
- * External IPA module interface
- */
-extern "C" {
-const struct IPAModuleInfo ipaModuleInfo = {
- IPA_MODULE_API_VERSION,
- 1,
- "PipelineHandlerRPi",
- "raspberrypi",
-};
-
-IPAInterface *ipaCreate()
-{
- return new ipa::RPi::IPARPi();
-}
-
-} /* extern "C" */
-
-} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index a1bb7d97..47a6f7b2 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -14,6 +14,7 @@
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include <libcamera/control_ids.h>
#include <libcamera/ipa/core_ipa_interface.h>
#include "libipa/histogram.h"
@@ -35,9 +36,8 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Agc)
-/* Limits for analogue gain values */
+/* Minimum limit for analogue gain value */
static constexpr double kMinAnalogueGain = 1.0;
-static constexpr double kMaxAnalogueGain = 8.0;
/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */
static constexpr utils::Duration kMaxShutterSpeed = 60ms;
@@ -59,8 +59,9 @@ static constexpr double kEvGainTarget = 0.5;
static constexpr double kRelativeLuminanceTarget = 0.4;
Agc::Agc()
- : frameCount_(0), numCells_(0), numHistBins_(0), filteredExposure_(0s)
+ : frameCount_(0), filteredExposure_(0s)
{
+ supportsRaw_ = true;
}
/**
@@ -73,21 +74,12 @@ Agc::Agc()
int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
{
/* Configure the default exposure and gain. */
- context.frameContext.agc.gain = std::max(context.configuration.agc.minAnalogueGain, kMinAnalogueGain);
- context.frameContext.agc.exposure = 10ms / context.configuration.sensor.lineDuration;
-
- /*
- * According to the RkISP1 documentation:
- * - versions < V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V10 entries,
- * - versions >= V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V12 entries.
- */
- if (context.configuration.hw.revision < RKISP1_V12) {
- numCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V10;
- numHistBins_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10;
- } else {
- numCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V12;
- numHistBins_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12;
- }
+ context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
+ context.activeState.agc.automatic.exposure =
+ 10ms / context.configuration.sensor.lineDuration;
+ context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
+ context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
+ context.activeState.agc.autoEnabled = !context.configuration.raw;
/*
* Define the measurement window for AGC as a centered rectangle
@@ -98,12 +90,105 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
context.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;
context.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;
- /* \todo Use actual frame index by populating it in the frameContext. */
+ /*
+ * \todo Use the upcoming per-frame context API that will provide a
+ * frame index
+ */
frameCount_ = 0;
return 0;
}
/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Agc::queueRequest(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls)
+{
+ auto &agc = context.activeState.agc;
+
+ if (!context.configuration.raw) {
+ const auto &agcEnable = controls.get(controls::AeEnable);
+ if (agcEnable && *agcEnable != agc.autoEnabled) {
+ agc.autoEnabled = *agcEnable;
+
+ LOG(RkISP1Agc, Debug)
+ << (agc.autoEnabled ? "Enabling" : "Disabling")
+ << " AGC";
+ }
+ }
+
+ const auto &exposure = controls.get(controls::ExposureTime);
+ if (exposure && !agc.autoEnabled) {
+ agc.manual.exposure = *exposure * 1.0us
+ / context.configuration.sensor.lineDuration;
+
+ LOG(RkISP1Agc, Debug)
+ << "Set exposure to " << agc.manual.exposure;
+ }
+
+ const auto &gain = controls.get(controls::AnalogueGain);
+ if (gain && !agc.autoEnabled) {
+ agc.manual.gain = *gain;
+
+ LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain;
+ }
+
+ frameContext.agc.autoEnabled = agc.autoEnabled;
+
+ if (!frameContext.agc.autoEnabled) {
+ frameContext.agc.exposure = agc.manual.exposure;
+ frameContext.agc.gain = agc.manual.gain;
+ }
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Agc::prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+{
+ if (frameContext.agc.autoEnabled) {
+ frameContext.agc.exposure = context.activeState.agc.automatic.exposure;
+ frameContext.agc.gain = context.activeState.agc.automatic.gain;
+ }
+
+ if (frame > 0)
+ return;
+
+ /* Configure the measurement window. */
+ params->meas.aec_config.meas_window = context.configuration.agc.measureWindow;
+ /* Use a continuous method for measure. */
+ params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0;
+ /* Estimate Y as (R + G + B) x (85/256). */
+ params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1;
+
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC;
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC;
+
+ /* Configure histogram. */
+ params->meas.hst_config.meas_window = context.configuration.agc.measureWindow;
+ /* Produce the luminance histogram. */
+ params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
+ /* Set an average weighted histogram. */
+ Span<uint8_t> weights{
+ params->meas.hst_config.hist_weight,
+ context.hw->numHistogramWeights
+ };
+ std::fill(weights.begin(), weights.end(), 1);
+ /* Step size can't be less than 3. */
+ params->meas.hst_config.histogram_predivider = 4;
+
+ /* Update the configuration for histogram. */
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST;
+ /* Enable the histogram measure unit. */
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_HST;
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST;
+}
+
+/**
* \brief Apply a filter on the exposure value to limit the speed of changes
* \param[in] exposureValue The target exposure from the AGC algorithm
*
@@ -140,14 +225,16 @@ utils::Duration Agc::filterExposure(utils::Duration exposureValue)
/**
* \brief Estimate the new exposure and gain values
- * \param[inout] frameContext The shared IPA frame Context
+ * \param[inout] context The shared IPA Context
+ * \param[in] frameContext The FrameContext for this frame
* \param[in] yGain The gain calculated on the current brightness level
* \param[in] iqMeanGain The gain calculated based on the relative luminance target
*/
-void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain)
+void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext,
+ double yGain, double iqMeanGain)
{
IPASessionConfiguration &configuration = context.configuration;
- IPAFrameContext &frameContext = context.frameContext;
+ IPAActiveState &activeState = context.activeState;
/* Get the effective exposure and gain applied on the sensor. */
uint32_t exposure = frameContext.sensor.exposure;
@@ -156,14 +243,13 @@ void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain)
/* Use the highest of the two gain estimates. */
double evGain = std::max(yGain, iqMeanGain);
- utils::Duration minShutterSpeed = configuration.agc.minShutterSpeed;
- utils::Duration maxShutterSpeed = std::min(configuration.agc.maxShutterSpeed,
+ utils::Duration minShutterSpeed = configuration.sensor.minShutterSpeed;
+ utils::Duration maxShutterSpeed = std::min(configuration.sensor.maxShutterSpeed,
kMaxShutterSpeed);
- double minAnalogueGain = std::max(configuration.agc.minAnalogueGain,
+ double minAnalogueGain = std::max(configuration.sensor.minAnalogueGain,
kMinAnalogueGain);
- double maxAnalogueGain = std::min(configuration.agc.maxAnalogueGain,
- kMaxAnalogueGain);
+ double maxAnalogueGain = configuration.sensor.maxAnalogueGain;
/* Consider within 1% of the target as correctly exposed. */
if (utils::abs_diff(evGain, 1.0) < 0.01)
@@ -216,13 +302,13 @@ void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain)
<< stepGain;
/* Update the estimated exposure and gain. */
- frameContext.agc.exposure = shutterTime / configuration.sensor.lineDuration;
- frameContext.agc.gain = stepGain;
+ activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;
+ activeState.agc.automatic.gain = stepGain;
}
/**
* \brief Estimate the relative luminance of the frame with a given gain
- * \param[in] ae The RkISP1 statistics and ISP results
+ * \param[in] expMeans The mean luminance values, from the RkISP1 statistics
* \param[in] gain The gain to apply to the frame
*
* This function estimates the average relative luminance of the frame that
@@ -246,52 +332,86 @@ void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain)
*
* \return The relative luminance
*/
-double Agc::estimateLuminance(const rkisp1_cif_isp_ae_stat *ae,
- double gain)
+double Agc::estimateLuminance(Span<const uint8_t> expMeans, double gain)
{
double ySum = 0.0;
/* Sum the averages, saturated to 255. */
- for (unsigned int aeCell = 0; aeCell < numCells_; aeCell++)
- ySum += std::min(ae->exp_mean[aeCell] * gain, 255.0);
+ for (uint8_t expMean : expMeans)
+ ySum += std::min(expMean * gain, 255.0);
/* \todo Weight with the AWB gains */
- return ySum / numCells_ / 255;
+ return ySum / expMeans.size() / 255;
}
/**
* \brief Estimate the mean value of the top 2% of the histogram
- * \param[in] hist The histogram statistics computed by the ImgU
+ * \param[in] hist The histogram statistics computed by the RkISP1
* \return The mean value of the top 2% of the histogram
*/
-double Agc::measureBrightness(const rkisp1_cif_isp_hist_stat *hist) const
+double Agc::measureBrightness(Span<const uint32_t> hist) const
{
- Histogram histogram{ Span<const uint32_t>(hist->hist_bins, numHistBins_) };
+ Histogram histogram{ hist };
/* Estimate the quantile mean of the top 2% of the histogram. */
return histogram.interQuantileMean(0.98, 1.0);
}
+void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
+ ControlList &metadata)
+{
+ utils::Duration exposureTime = context.configuration.sensor.lineDuration
+ * frameContext.sensor.exposure;
+ metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
+ metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
+
+ /* \todo Use VBlank value calculated from each frame exposure. */
+ uint32_t vTotal = context.configuration.sensor.size.height
+ + context.configuration.sensor.defVBlank;
+ utils::Duration frameDuration = context.configuration.sensor.lineDuration
+ * vTotal;
+ metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
+}
+
/**
* \brief Process RkISP1 statistics, and run AGC operations
* \param[in] context The shared IPA context
+ * \param[in] frame The frame context sequence number
+ * \param[in] frameContext The current frame context
* \param[in] stats The RKISP1 statistics and ISP results
+ * \param[out] metadata Metadata for the frame, to be filled by the algorithm
*
* Identify the current image brightness, and use that to estimate the optimal
* new exposure and gain for the scene.
*/
-void Agc::process(IPAContext &context,
- [[maybe_unused]] IPAFrameContext *frameContext,
- const rkisp1_stat_buffer *stats)
+void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats,
+ ControlList &metadata)
{
+ if (!stats) {
+ fillMetadata(context, frameContext, metadata);
+ return;
+ }
+
+ /*
+ * \todo Verify that the exposure and gain applied by the sensor for
+ * this frame match what has been requested. This isn't a hard
+ * requirement for stability of the AGC (the guarantee we need in
+ * automatic mode is a perfect match between the frame and the values
+ * we receive), but is important in manual mode.
+ */
+
const rkisp1_cif_isp_stat *params = &stats->params;
ASSERT(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP);
- const rkisp1_cif_isp_ae_stat *ae = &params->ae;
- const rkisp1_cif_isp_hist_stat *hist = &params->hist;
+ Span<const uint8_t> ae{ params->ae.exp_mean, context.hw->numAeCells };
+ Span<const uint32_t> hist{
+ params->hist.hist_bins,
+ context.hw->numHistogramBins
+ };
double iqMean = measureBrightness(hist);
- double iqMeanGain = kEvGainTarget * numHistBins_ / iqMean;
+ double iqMeanGain = kEvGainTarget * hist.size() / iqMean;
/*
* Estimate the gain needed to achieve a relative luminance target. To
@@ -315,44 +435,10 @@ void Agc::process(IPAContext &context,
break;
}
- computeExposure(context, yGain, iqMeanGain);
+ computeExposure(context, frameContext, yGain, iqMeanGain);
frameCount_++;
-}
-
-/**
- * \copydoc libcamera::ipa::Algorithm::prepare
- */
-void Agc::prepare(IPAContext &context, rkisp1_params_cfg *params)
-{
- if (context.frameContext.frameCount > 0)
- return;
- /* Configure the measurement window. */
- params->meas.aec_config.meas_window = context.configuration.agc.measureWindow;
- /* Use a continuous method for measure. */
- params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0;
- /* Estimate Y as (R + G + B) x (85/256). */
- params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1;
-
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC;
-
- /* Configure histogram. */
- params->meas.hst_config.meas_window = context.configuration.agc.measureWindow;
- /* Produce the luminance histogram. */
- params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
- /* Set an average weighted histogram. */
- for (unsigned int histBin = 0; histBin < numHistBins_; histBin++)
- params->meas.hst_config.hist_weight[histBin] = 1;
- /* Step size can't be less than 3. */
- params->meas.hst_config.histogram_predivider = 4;
-
- /* Update the configuration for histogram. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST;
- /* Enable the histogram measure unit. */
- params->module_ens |= RKISP1_CIF_ISP_MODULE_HST;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST;
+ fillMetadata(context, frameContext, metadata);
}
REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index 22c02779..fb82a33f 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -9,6 +9,7 @@
#include <linux/rkisp1-config.h>
+#include <libcamera/base/span.h>
#include <libcamera/base/utils.h>
#include <libcamera/geometry.h>
@@ -17,8 +18,6 @@
namespace libcamera {
-struct IPACameraSensorInfo;
-
namespace ipa::rkisp1::algorithms {
class Agc : public Algorithm
@@ -28,21 +27,29 @@ public:
~Agc() = default;
int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
- void prepare(IPAContext &context, rkisp1_params_cfg *params) override;
- void process(IPAContext &context, IPAFrameContext *frameContext,
- const rkisp1_stat_buffer *stats) override;
+ void queueRequest(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata) override;
private:
- void computeExposure(IPAContext &Context, double yGain, double iqMeanGain);
+ void computeExposure(IPAContext &Context, IPAFrameContext &frameContext,
+ double yGain, double iqMeanGain);
utils::Duration filterExposure(utils::Duration exposureValue);
- double estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, double gain);
- double measureBrightness(const rkisp1_cif_isp_hist_stat *hist) const;
+ double estimateLuminance(Span<const uint8_t> expMeans, double gain);
+ double measureBrightness(Span<const uint32_t> hist) const;
+ void fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
+ ControlList &metadata);
uint64_t frameCount_;
- uint32_t numCells_;
- uint32_t numHistBins_;
-
utils::Duration filteredExposure_;
};
diff --git a/src/ipa/rkisp1/algorithms/algorithm.h b/src/ipa/rkisp1/algorithms/algorithm.h
index c3212cff..9454c9a1 100644
--- a/src/ipa/rkisp1/algorithms/algorithm.h
+++ b/src/ipa/rkisp1/algorithms/algorithm.h
@@ -15,7 +15,17 @@ namespace libcamera {
namespace ipa::rkisp1 {
-using Algorithm = libcamera::ipa::Algorithm<Module>;
+class Algorithm : public libcamera::ipa::Algorithm<Module>
+{
+public:
+ Algorithm()
+ : disabled_(false), supportsRaw_(false)
+ {
+ }
+
+ bool disabled_;
+ bool supportsRaw_;
+};
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index 9f00364d..744f4a38 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -9,9 +9,11 @@
#include <algorithm>
#include <cmath>
+#include <iomanip>
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
#include <libcamera/ipa/core_ipa_interface.h>
/**
@@ -29,15 +31,27 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Awb)
+/* Minimum mean value below which AWB can't operate. */
+constexpr double kMeanMinThreshold = 2.0;
+
+Awb::Awb()
+ : rgbMode_(false)
+{
+}
+
/**
* \copydoc libcamera::ipa::Algorithm::configure
*/
int Awb::configure(IPAContext &context,
const IPACameraSensorInfo &configInfo)
{
- context.frameContext.awb.gains.red = 1.0;
- context.frameContext.awb.gains.blue = 1.0;
- context.frameContext.awb.gains.green = 1.0;
+ context.activeState.awb.gains.manual.red = 1.0;
+ context.activeState.awb.gains.manual.blue = 1.0;
+ context.activeState.awb.gains.manual.green = 1.0;
+ context.activeState.awb.gains.automatic.red = 1.0;
+ context.activeState.awb.gains.automatic.blue = 1.0;
+ context.activeState.awb.gains.automatic.green = 1.0;
+ context.activeState.awb.autoEnabled = true;
/*
* Define the measurement window for AWB as a centered rectangle
@@ -48,131 +62,264 @@ int Awb::configure(IPAContext &context,
context.configuration.awb.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;
context.configuration.awb.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;
+ context.configuration.awb.enabled = true;
+
return 0;
}
-uint32_t Awb::estimateCCT(double red, double green, double blue)
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Awb::queueRequest(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls)
{
- /* Convert the RGB values to CIE tristimulus values (XYZ) */
- double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue);
- double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue);
- double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue);
+ auto &awb = context.activeState.awb;
- /* Calculate the normalized chromaticity values */
- double x = X / (X + Y + Z);
- double y = Y / (X + Y + Z);
+ const auto &awbEnable = controls.get(controls::AwbEnable);
+ if (awbEnable && *awbEnable != awb.autoEnabled) {
+ awb.autoEnabled = *awbEnable;
- /* Calculate CCT */
- double n = (x - 0.3320) / (0.1858 - y);
- return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33;
+ LOG(RkISP1Awb, Debug)
+ << (*awbEnable ? "Enabling" : "Disabling") << " AWB";
+ }
+
+ const auto &colourGains = controls.get(controls::ColourGains);
+ if (colourGains && !awb.autoEnabled) {
+ awb.gains.manual.red = (*colourGains)[0];
+ awb.gains.manual.blue = (*colourGains)[1];
+
+ LOG(RkISP1Awb, Debug)
+ << "Set colour gains to red: " << awb.gains.manual.red
+ << ", blue: " << awb.gains.manual.blue;
+ }
+
+ frameContext.awb.autoEnabled = awb.autoEnabled;
+
+ if (!awb.autoEnabled) {
+ frameContext.awb.gains.red = awb.gains.manual.red;
+ frameContext.awb.gains.green = 1.0;
+ frameContext.awb.gains.blue = awb.gains.manual.blue;
+ }
}
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
-void Awb::prepare(IPAContext &context, rkisp1_params_cfg *params)
+void Awb::prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext, rkisp1_params_cfg *params)
{
- params->others.awb_gain_config.gain_green_b = 256 * context.frameContext.awb.gains.green;
- params->others.awb_gain_config.gain_blue = 256 * context.frameContext.awb.gains.blue;
- params->others.awb_gain_config.gain_red = 256 * context.frameContext.awb.gains.red;
- params->others.awb_gain_config.gain_green_r = 256 * context.frameContext.awb.gains.green;
+ /*
+ * This is the latest time we can read the active state. This is the
+ * most up-to-date automatic values we can read.
+ */
+ if (frameContext.awb.autoEnabled) {
+ frameContext.awb.gains.red = context.activeState.awb.gains.automatic.red;
+ frameContext.awb.gains.green = context.activeState.awb.gains.automatic.green;
+ frameContext.awb.gains.blue = context.activeState.awb.gains.automatic.blue;
+ }
+
+ params->others.awb_gain_config.gain_green_b = 256 * frameContext.awb.gains.green;
+ params->others.awb_gain_config.gain_blue = 256 * frameContext.awb.gains.blue;
+ params->others.awb_gain_config.gain_red = 256 * frameContext.awb.gains.red;
+ params->others.awb_gain_config.gain_green_r = 256 * frameContext.awb.gains.green;
/* Update the gains. */
params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
- /* If we already have configured the gains and window, return. */
- if (context.frameContext.frameCount > 0)
+ /* If we have already set the AWB measurement parameters, return. */
+ if (frame > 0)
return;
- /* Configure the gains to apply. */
+ rkisp1_cif_isp_awb_meas_config &awb_config = params->meas.awb_meas_config;
+
+ /* Configure the measure window for AWB. */
+ awb_config.awb_wnd = context.configuration.awb.measureWindow;
+
+ /* Number of frames to use to estimate the means (0 means 1 frame). */
+ awb_config.frames = 0;
+
+ /* Select RGB or YCbCr means measurement. */
+ if (rgbMode_) {
+ awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB;
+
+ /*
+ * For RGB-based measurements, pixels are selected with maximum
+ * red, green and blue thresholds that are set in the
+ * awb_ref_cr, awb_min_y and awb_ref_cb respectively. The other
+ * values are not used, set them to 0.
+ */
+ awb_config.awb_ref_cr = 250;
+ awb_config.min_y = 250;
+ awb_config.awb_ref_cb = 250;
+
+ awb_config.max_y = 0;
+ awb_config.min_c = 0;
+ awb_config.max_csum = 0;
+ } else {
+ awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR;
+
+ /* Set the reference Cr and Cb (AWB target) to white. */
+ awb_config.awb_ref_cb = 128;
+ awb_config.awb_ref_cr = 128;
+
+ /*
+ * Filter out pixels based on luminance and chrominance values.
+ * The acceptable luma values are specified as a [16, 250]
+ * range, while the acceptable chroma values are specified with
+ * a minimum of 16 and a maximum Cb+Cr sum of 250.
+ */
+ awb_config.min_y = 16;
+ awb_config.max_y = 250;
+ awb_config.min_c = 16;
+ awb_config.max_csum = 250;
+ }
+
+ /* Enable the AWB gains. */
params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
- /* Update the ISP to apply the gains configured. */
params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
- /* Configure the measure window for AWB. */
- params->meas.awb_meas_config.awb_wnd = context.configuration.awb.measureWindow;
- /*
- * Measure Y, Cr and Cb means.
- * \todo RGB is not working, the kernel seems to not configure it ?
- */
- params->meas.awb_meas_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR;
- /* Reference Cr and Cb. */
- params->meas.awb_meas_config.awb_ref_cb = 128;
- params->meas.awb_meas_config.awb_ref_cr = 128;
- /* Y values to include are between min_y and max_y only. */
- params->meas.awb_meas_config.min_y = 16;
- params->meas.awb_meas_config.max_y = 250;
- /* Maximum Cr+Cb value to take into account for awb. */
- params->meas.awb_meas_config.max_csum = 250;
- /* Minimum Cr and Cb values to take into account. */
- params->meas.awb_meas_config.min_c = 16;
- /* Number of frames to use to estimate the mean (0 means 1 frame). */
- params->meas.awb_meas_config.frames = 0;
-
- /* Update AWB measurement unit configuration. */
+ /* Update the AWB measurement parameters and enable the AWB module. */
params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB;
- /* Make sure the ISP is measuring the means for the next frame. */
params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB;
params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB;
}
+uint32_t Awb::estimateCCT(double red, double green, double blue)
+{
+ /* Convert the RGB values to CIE tristimulus values (XYZ) */
+ double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue);
+ double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue);
+ double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue);
+
+ /* Calculate the normalized chromaticity values */
+ double x = X / (X + Y + Z);
+ double y = Y / (X + Y + Z);
+
+ /* Calculate CCT */
+ double n = (x - 0.3320) / (0.1858 - y);
+ return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33;
+}
+
/**
* \copydoc libcamera::ipa::Algorithm::process
*/
-void Awb::process([[maybe_unused]] IPAContext &context,
- [[maybe_unused]] IPAFrameContext *frameCtx,
- const rkisp1_stat_buffer *stats)
+void Awb::process(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata)
{
const rkisp1_cif_isp_stat *params = &stats->params;
const rkisp1_cif_isp_awb_stat *awb = &params->awb;
- IPAFrameContext &frameContext = context.frameContext;
-
- /* Get the YCbCr mean values */
- double yMean = awb->awb_mean[0].mean_y_or_g;
- double crMean = awb->awb_mean[0].mean_cr_or_r;
- double cbMean = awb->awb_mean[0].mean_cb_or_b;
+ IPAActiveState &activeState = context.activeState;
+ double greenMean;
+ double redMean;
+ double blueMean;
+
+ if (rgbMode_) {
+ greenMean = awb->awb_mean[0].mean_y_or_g;
+ redMean = awb->awb_mean[0].mean_cr_or_r;
+ blueMean = awb->awb_mean[0].mean_cb_or_b;
+ } else {
+ /* Get the YCbCr mean values */
+ double yMean = awb->awb_mean[0].mean_y_or_g;
+ double cbMean = awb->awb_mean[0].mean_cb_or_b;
+ double crMean = awb->awb_mean[0].mean_cr_or_r;
+
+ /*
+ * Convert from YCbCr to RGB.
+ * The hardware uses the following formulas:
+ * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B
+ * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
+ * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
+ *
+ * The inverse matrix is thus:
+ * [[1,1636, -0,0623, 1,6008]
+ * [1,1636, -0,4045, -0,7949]
+ * [1,1636, 1,9912, -0,0250]]
+ */
+ yMean -= 16;
+ cbMean -= 128;
+ crMean -= 128;
+ redMean = 1.1636 * yMean - 0.0623 * cbMean + 1.6008 * crMean;
+ greenMean = 1.1636 * yMean - 0.4045 * cbMean - 0.7949 * crMean;
+ blueMean = 1.1636 * yMean + 1.9912 * cbMean - 0.0250 * crMean;
+
+ /*
+ * Due to hardware rounding errors in the YCbCr means, the
+ * calculated RGB means may be negative. This would lead to
+ * negative gains, messing up calculation. Prevent this by
+ * clamping the means to positive values.
+ */
+ redMean = std::max(redMean, 0.0);
+ greenMean = std::max(greenMean, 0.0);
+ blueMean = std::max(blueMean, 0.0);
+ }
/*
- * Convert from YCbCr to RGB.
- * The hardware uses the following formulas:
- * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B
- * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
- * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
- *
- * The inverse matrix is thus:
- * [[1,1636, -0,0623, 1,6008]
- * [1,1636, -0,4045, -0,7949]
- * [1,1636, 1,9912, -0,0250]]
+ * The ISP computes the AWB means after applying the colour gains,
+ * divide by the gains that were used to get the raw means from the
+ * sensor.
*/
- yMean -= 16;
- cbMean -= 128;
- crMean -= 128;
- double redMean = 1.1636 * yMean - 0.0623 * cbMean + 1.6008 * crMean;
- double greenMean = 1.1636 * yMean - 0.4045 * cbMean - 0.7949 * crMean;
- double blueMean = 1.1636 * yMean + 1.9912 * cbMean - 0.0250 * crMean;
+ redMean /= frameContext.awb.gains.red;
+ greenMean /= frameContext.awb.gains.green;
+ blueMean /= frameContext.awb.gains.blue;
- /* Estimate the red and blue gains to apply in a grey world. */
- double redGain = greenMean / (redMean + 1);
- double blueGain = greenMean / (blueMean + 1);
+ /*
+ * If the means are too small we don't have enough information to
+ * meaningfully calculate gains. Freeze the algorithm in that case.
+ */
+ if (redMean < kMeanMinThreshold && greenMean < kMeanMinThreshold &&
+ blueMean < kMeanMinThreshold) {
+ frameContext.awb.temperatureK = activeState.awb.temperatureK;
+ return;
+ }
- /* Filter the values to avoid oscillations. */
- double speed = 0.2;
- redGain = speed * redGain + (1 - speed) * frameContext.awb.gains.red;
- blueGain = speed * blueGain + (1 - speed) * frameContext.awb.gains.blue;
+ activeState.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean);
/*
- * Gain values are unsigned integer value, range 0 to 4 with 8 bit
- * fractional part.
+ * Estimate the red and blue gains to apply in a grey world. The green
+ * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
+ * divisor to a minimum value of 1.0.
*/
- frameContext.awb.gains.red = std::clamp(redGain, 0.0, 1023.0 / 256);
- frameContext.awb.gains.blue = std::clamp(blueGain, 0.0, 1023.0 / 256);
- /* Hardcode the green gain to 1.0. */
- frameContext.awb.gains.green = 1.0;
+ double redGain = greenMean / std::max(redMean, 1.0);
+ double blueGain = greenMean / std::max(blueMean, 1.0);
- frameContext.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean);
+ /*
+ * Clamp the gain values to the hardware, which expresses gains as Q2.8
+ * unsigned integer values. Set the minimum just above zero to avoid
+ * divisions by zero when computing the raw means in subsequent
+ * iterations.
+ */
+ redGain = std::clamp(redGain, 1.0 / 256, 1023.0 / 256);
+ blueGain = std::clamp(blueGain, 1.0 / 256, 1023.0 / 256);
- LOG(RkISP1Awb, Debug) << "Gain found for red: " << context.frameContext.awb.gains.red
- << " and for blue: " << context.frameContext.awb.gains.blue;
+ /* Filter the values to avoid oscillations. */
+ double speed = 0.2;
+ redGain = speed * redGain + (1 - speed) * activeState.awb.gains.automatic.red;
+ blueGain = speed * blueGain + (1 - speed) * activeState.awb.gains.automatic.blue;
+
+ activeState.awb.gains.automatic.red = redGain;
+ activeState.awb.gains.automatic.blue = blueGain;
+ activeState.awb.gains.automatic.green = 1.0;
+
+ frameContext.awb.temperatureK = activeState.awb.temperatureK;
+
+ metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
+ metadata.set(controls::ColourGains, {
+ static_cast<float>(frameContext.awb.gains.red),
+ static_cast<float>(frameContext.awb.gains.blue)
+ });
+ metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
+
+ LOG(RkISP1Awb, Debug) << std::showpoint
+ << "Means [" << redMean << ", " << greenMean << ", " << blueMean
+ << "], gains [" << activeState.awb.gains.automatic.red << ", "
+ << activeState.awb.gains.automatic.green << ", "
+ << activeState.awb.gains.automatic.blue << "], temp "
+ << frameContext.awb.temperatureK << "K";
}
REGISTER_IPA_ALGORITHM(Awb, "Awb")
diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
index 7647842f..9d45a442 100644
--- a/src/ipa/rkisp1/algorithms/awb.h
+++ b/src/ipa/rkisp1/algorithms/awb.h
@@ -7,8 +7,6 @@
#pragma once
-#include <linux/rkisp1-config.h>
-
#include "algorithm.h"
namespace libcamera {
@@ -18,16 +16,25 @@ namespace ipa::rkisp1::algorithms {
class Awb : public Algorithm
{
public:
- Awb() = default;
+ Awb();
~Awb() = default;
int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
- void prepare(IPAContext &context, rkisp1_params_cfg *params) override;
- void process(IPAContext &context, IPAFrameContext *frameCtx,
- const rkisp1_stat_buffer *stats) override;
+ void queueRequest(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata) override;
private:
uint32_t estimateCCT(double red, double green, double blue);
+
+ bool rgbMode_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index 3542f61c..15324fb1 100644
--- a/src/ipa/rkisp1/algorithms/blc.cpp
+++ b/src/ipa/rkisp1/algorithms/blc.cpp
@@ -7,6 +7,10 @@
#include "blc.h"
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
/**
* \file blc.h
*/
@@ -29,23 +33,54 @@ namespace ipa::rkisp1::algorithms {
* isn't currently supported.
*/
+LOG_DEFINE_CATEGORY(RkISP1Blc)
+
+BlackLevelCorrection::BlackLevelCorrection()
+ : tuningParameters_(false)
+{
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
+ const YamlObject &tuningData)
+{
+ blackLevelRed_ = tuningData["R"].get<int16_t>(256);
+ blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
+ blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
+ blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
+
+ tuningParameters_ = true;
+
+ LOG(RkISP1Blc, Debug)
+ << "Black levels: red " << blackLevelRed_
+ << ", green (red) " << blackLevelGreenR_
+ << ", green (blue) " << blackLevelGreenB_
+ << ", blue " << blackLevelBlue_;
+
+ return 0;
+}
+
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
-void BlackLevelCorrection::prepare(IPAContext &context,
+void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
+ const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
rkisp1_params_cfg *params)
{
- if (context.frameContext.frameCount > 0)
+ if (frame > 0)
+ return;
+
+ if (!tuningParameters_)
return;
- /*
- * Substract fixed values taken from imx219 tuning file.
- * \todo Use a configuration file for it ?
- */
+
params->others.bls_config.enable_auto = 0;
- params->others.bls_config.fixed_val.r = 256;
- params->others.bls_config.fixed_val.gr = 256;
- params->others.bls_config.fixed_val.gb = 256;
- params->others.bls_config.fixed_val.b = 256;
+ params->others.bls_config.fixed_val.r = blackLevelRed_;
+ params->others.bls_config.fixed_val.gr = blackLevelGreenR_;
+ params->others.bls_config.fixed_val.gb = blackLevelGreenB_;
+ params->others.bls_config.fixed_val.b = blackLevelBlue_;
params->module_en_update |= RKISP1_CIF_ISP_MODULE_BLS;
params->module_ens |= RKISP1_CIF_ISP_MODULE_BLS;
diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h
index 69874d8f..0b1a2d43 100644
--- a/src/ipa/rkisp1/algorithms/blc.h
+++ b/src/ipa/rkisp1/algorithms/blc.h
@@ -7,23 +7,29 @@
#pragma once
-#include <linux/rkisp1-config.h>
-
#include "algorithm.h"
namespace libcamera {
-struct IPACameraSensorInfo;
-
namespace ipa::rkisp1::algorithms {
class BlackLevelCorrection : public Algorithm
{
public:
- BlackLevelCorrection() = default;
+ BlackLevelCorrection();
~BlackLevelCorrection() = default;
- void prepare(IPAContext &context, rkisp1_params_cfg *params) override;
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+
+private:
+ bool tuningParameters_;
+ int16_t blackLevelRed_;
+ int16_t blackLevelGreenR_;
+ int16_t blackLevelGreenB_;
+ int16_t blackLevelBlue_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp
new file mode 100644
index 00000000..eaa56c37
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/cproc.cpp
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * cproc.cpp - RkISP1 Color Processing control
+ */
+
+#include "cproc.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+
+/**
+ * \file cproc.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class ColorProcessing
+ * \brief RkISP1 Color Processing control
+ *
+ * The ColorProcessing algorithm is responsible for applying brightness,
+ * contrast and saturation corrections. The values are directly provided
+ * through requests by the corresponding controls.
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1CProc)
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void ColorProcessing::queueRequest(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls)
+{
+ auto &cproc = context.activeState.cproc;
+ bool update = false;
+
+ const auto &brightness = controls.get(controls::Brightness);
+ if (brightness) {
+ int value = std::clamp<int>(std::lround(*brightness * 128), -128, 127);
+ if (cproc.brightness != value) {
+ cproc.brightness = value;
+ update = true;
+ }
+
+ LOG(RkISP1CProc, Debug) << "Set brightness to " << value;
+ }
+
+ const auto &contrast = controls.get(controls::Contrast);
+ if (contrast) {
+ int value = std::clamp<int>(std::lround(*contrast * 128), 0, 255);
+ if (cproc.contrast != value) {
+ cproc.contrast = value;
+ update = true;
+ }
+
+ LOG(RkISP1CProc, Debug) << "Set contrast to " << value;
+ }
+
+ const auto saturation = controls.get(controls::Saturation);
+ if (saturation) {
+ int value = std::clamp<int>(std::lround(*saturation * 128), 0, 255);
+ if (cproc.saturation != value) {
+ cproc.saturation = value;
+ update = true;
+ }
+
+ LOG(RkISP1CProc, Debug) << "Set saturation to " << value;
+ }
+
+ frameContext.cproc.brightness = cproc.brightness;
+ frameContext.cproc.contrast = cproc.contrast;
+ frameContext.cproc.saturation = cproc.saturation;
+ frameContext.cproc.update = update;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void ColorProcessing::prepare([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params)
+{
+ /* Check if the algorithm configuration has been updated. */
+ if (!frameContext.cproc.update)
+ return;
+
+ params->others.cproc_config.brightness = frameContext.cproc.brightness;
+ params->others.cproc_config.contrast = frameContext.cproc.contrast;
+ params->others.cproc_config.sat = frameContext.cproc.saturation;
+
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_CPROC;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_CPROC;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CPROC;
+}
+
+REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h
new file mode 100644
index 00000000..ba6e901a
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/cproc.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * cproc.h - RkISP1 Color Processing control
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class ColorProcessing : public Algorithm
+{
+public:
+ ColorProcessing() = default;
+ ~ColorProcessing() = default;
+
+ void queueRequest(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp
new file mode 100644
index 00000000..80a1b734
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/dpcc.cpp
@@ -0,0 +1,251 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * dpcc.cpp - RkISP1 Defect Pixel Cluster Correction control
+ */
+
+#include "dpcc.h"
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "linux/rkisp1-config.h"
+
+/**
+ * \file dpcc.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class DefectPixelClusterCorrection
+ * \brief RkISP1 Defect Pixel Cluster Correction control
+ *
+ * Depending of the sensor quality, some pixels can be defective and then
+ * appear significantly brighter or darker than the other pixels.
+ *
+ * The Defect Pixel Cluster Correction algorithms is responsible to minimize
+ * the impact of the pixels. This can be done with algorithms applied at run
+ * time (on-the-fly method) or with a table of defective pixels. Only the first
+ * method is supported for the moment.
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Dpcc)
+
+DefectPixelClusterCorrection::DefectPixelClusterCorrection()
+ : config_({})
+{
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
+ const YamlObject &tuningData)
+{
+ config_.mode = RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE;
+ config_.output_mode = RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER
+ | RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_RB_CENTER;
+
+ config_.set_use = tuningData["fixed-set"].get<bool>(false)
+ ? RKISP1_CIF_ISP_DPCC_SET_USE_STAGE1_USE_FIX_SET : 0;
+
+ /* Get all defined sets to apply (up to 3). */
+ const YamlObject &setsObject = tuningData["sets"];
+ if (!setsObject.isList()) {
+ LOG(RkISP1Dpcc, Error)
+ << "'sets' parameter not found in tuning file";
+ return -EINVAL;
+ }
+
+ if (setsObject.size() > RKISP1_CIF_ISP_DPCC_METHODS_MAX) {
+ LOG(RkISP1Dpcc, Error)
+ << "'sets' size in tuning file (" << setsObject.size()
+ << ") exceeds the maximum hardware capacity (3)";
+ return -EINVAL;
+ }
+
+ for (std::size_t i = 0; i < setsObject.size(); ++i) {
+ struct rkisp1_cif_isp_dpcc_methods_config &method = config_.methods[i];
+ const YamlObject &set = setsObject[i];
+ uint16_t value;
+
+ /* Enable set if described in YAML tuning file. */
+ config_.set_use |= 1 << i;
+
+ /* PG Method */
+ const YamlObject &pgObject = set["pg-factor"];
+
+ if (pgObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_PG_GREEN_ENABLE;
+
+ value = pgObject["green"].get<uint16_t>(0);
+ method.pg_fac |= RKISP1_CIF_ISP_DPCC_PG_FAC_G(value);
+ }
+
+ if (pgObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_PG_RED_BLUE_ENABLE;
+
+ value = pgObject["red-blue"].get<uint16_t>(0);
+ method.pg_fac |= RKISP1_CIF_ISP_DPCC_PG_FAC_RB(value);
+ }
+
+ /* RO Method */
+ const YamlObject &roObject = set["ro-limits"];
+
+ if (roObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RO_GREEN_ENABLE;
+
+ value = roObject["green"].get<uint16_t>(0);
+ config_.ro_limits |=
+ RKISP1_CIF_ISP_DPCC_RO_LIMITS_n_G(i, value);
+ }
+
+ if (roObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RO_RED_BLUE_ENABLE;
+
+ value = roObject["red-blue"].get<uint16_t>(0);
+ config_.ro_limits |=
+ RKISP1_CIF_ISP_DPCC_RO_LIMITS_n_RB(i, value);
+ }
+
+ /* RG Method */
+ const YamlObject &rgObject = set["rg-factor"];
+ method.rg_fac = 0;
+
+ if (rgObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RG_GREEN_ENABLE;
+
+ value = rgObject["green"].get<uint16_t>(0);
+ method.rg_fac |= RKISP1_CIF_ISP_DPCC_RG_FAC_G(value);
+ }
+
+ if (rgObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RG_RED_BLUE_ENABLE;
+
+ value = rgObject["red-blue"].get<uint16_t>(0);
+ method.rg_fac |= RKISP1_CIF_ISP_DPCC_RG_FAC_RB(value);
+ }
+
+ /* RND Method */
+ const YamlObject &rndOffsetsObject = set["rnd-offsets"];
+
+ if (rndOffsetsObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_GREEN_ENABLE;
+
+ value = rndOffsetsObject["green"].get<uint16_t>(0);
+ config_.rnd_offs |=
+ RKISP1_CIF_ISP_DPCC_RND_OFFS_n_G(i, value);
+ }
+
+ if (rndOffsetsObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_RED_BLUE_ENABLE;
+
+ value = rndOffsetsObject["red-blue"].get<uint16_t>(0);
+ config_.rnd_offs |=
+ RKISP1_CIF_ISP_DPCC_RND_OFFS_n_RB(i, value);
+ }
+
+ const YamlObject &rndThresholdObject = set["rnd-threshold"];
+ method.rnd_thresh = 0;
+
+ if (rndThresholdObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_GREEN_ENABLE;
+
+ value = rndThresholdObject["green"].get<uint16_t>(0);
+ method.rnd_thresh |=
+ RKISP1_CIF_ISP_DPCC_RND_THRESH_G(value);
+ }
+
+ if (rndThresholdObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_RED_BLUE_ENABLE;
+
+ value = rndThresholdObject["red-blue"].get<uint16_t>(0);
+ method.rnd_thresh |=
+ RKISP1_CIF_ISP_DPCC_RND_THRESH_RB(value);
+ }
+
+ /* LC Method */
+ const YamlObject &lcThresholdObject = set["line-threshold"];
+ method.line_thresh = 0;
+
+ if (lcThresholdObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_GREEN_ENABLE;
+
+ value = lcThresholdObject["green"].get<uint16_t>(0);
+ method.line_thresh |=
+ RKISP1_CIF_ISP_DPCC_LINE_THRESH_G(value);
+ }
+
+ if (lcThresholdObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_RED_BLUE_ENABLE;
+
+ value = lcThresholdObject["red-blue"].get<uint16_t>(0);
+ method.line_thresh |=
+ RKISP1_CIF_ISP_DPCC_LINE_THRESH_RB(value);
+ }
+
+ const YamlObject &lcTMadFactorObject = set["line-mad-factor"];
+ method.line_mad_fac = 0;
+
+ if (lcTMadFactorObject.contains("green")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_GREEN_ENABLE;
+
+ value = lcTMadFactorObject["green"].get<uint16_t>(0);
+ method.line_mad_fac |=
+ RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_G(value);
+ }
+
+ if (lcTMadFactorObject.contains("red-blue")) {
+ method.method |=
+ RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_RED_BLUE_ENABLE;
+
+ value = lcTMadFactorObject["red-blue"].get<uint16_t>(0);
+ method.line_mad_fac |=
+ RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_RB(value);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void DefectPixelClusterCorrection::prepare([[maybe_unused]] IPAContext &context,
+ const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params)
+{
+ if (frame > 0)
+ return;
+
+ params->others.dpcc_config = config_;
+
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPCC;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_DPCC;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPCC;
+}
+
+REGISTER_IPA_ALGORITHM(DefectPixelClusterCorrection, "DefectPixelClusterCorrection")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h
new file mode 100644
index 00000000..b1fac7d1
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/dpcc.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * dpcc.h - RkISP1 Defect Pixel Cluster Correction control
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class DefectPixelClusterCorrection : public Algorithm
+{
+public:
+ DefectPixelClusterCorrection();
+ ~DefectPixelClusterCorrection() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+
+private:
+ rkisp1_cif_isp_dpcc_config config_;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp
new file mode 100644
index 00000000..5bd7e59f
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/dpf.cpp
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * dpf.cpp - RkISP1 Denoise Pre-Filter control
+ */
+
+#include "dpf.h"
+
+#include <cmath>
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+
+#include "linux/rkisp1-config.h"
+
+/**
+ * \file dpf.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class Dpf
+ * \brief RkISP1 Denoise Pre-Filter control
+ *
+ * The denoise pre-filter algorithm is a bilateral filter which combines a
+ * range filter and a domain filter. The denoise pre-filter is applied before
+ * demosaicing.
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Dpf)
+
+Dpf::Dpf()
+ : config_({}), strengthConfig_({})
+{
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int Dpf::init([[maybe_unused]] IPAContext &context,
+ const YamlObject &tuningData)
+{
+ std::vector<uint8_t> values;
+
+ /*
+ * The domain kernel is configured with a 9x9 kernel for the green
+ * pixels, and a 13x9 or 9x9 kernel for red and blue pixels.
+ */
+ const YamlObject &dFObject = tuningData["DomainFilter"];
+
+ /*
+ * For the green component, we have the 9x9 kernel specified
+ * as 6 coefficients:
+ * Y
+ * ^
+ * 4 | 6 5 4 5 6
+ * 3 | 5 3 3 5
+ * 2 | 5 3 2 3 5
+ * 1 | 3 1 1 3
+ * 0 - 4 2 0 2 4
+ * -1 | 3 1 1 3
+ * -2 | 5 3 2 3 5
+ * -3 | 5 3 3 5
+ * -4 | 6 5 4 5 6
+ * +---------|--------> X
+ * -4....-1 0 1 2 3 4
+ */
+ values = dFObject["g"].getList<uint8_t>().value_or(std::vector<uint8_t>{});
+ if (values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS) {
+ LOG(RkISP1Dpf, Error)
+ << "Invalid 'DomainFilter:g': expected "
+ << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS
+ << " elements, got " << values.size();
+ return -EINVAL;
+ }
+
+ std::copy_n(values.begin(), values.size(),
+ std::begin(config_.g_flt.spatial_coeff));
+
+ config_.g_flt.gr_enable = true;
+ config_.g_flt.gb_enable = true;
+
+ /*
+ * For the red and blue components, we have the 13x9 kernel specified
+ * as 6 coefficients:
+ *
+ * Y
+ * ^
+ * 4 | 6 5 4 3 4 5 6
+ * |
+ * 2 | 5 4 2 1 2 4 5
+ * |
+ * 0 - 5 3 1 0 1 3 5
+ * |
+ * -2 | 5 4 2 1 2 4 5
+ * |
+ * -4 | 6 5 4 3 4 5 6
+ * +-------------|------------> X
+ * -6 -4 -2 0 2 4 6
+ *
+ * For a 9x9 kernel, columns -6 and 6 are dropped, so coefficient
+ * number 6 is not used.
+ */
+ values = dFObject["rb"].getList<uint8_t>().value_or(std::vector<uint8_t>{});
+ if (values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS &&
+ values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS - 1) {
+ LOG(RkISP1Dpf, Error)
+ << "Invalid 'DomainFilter:rb': expected "
+ << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS - 1
+ << " or " << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS
+ << " elements, got " << values.size();
+ return -EINVAL;
+ }
+
+ config_.rb_flt.fltsize = values.size() == RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS
+ ? RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_13x9
+ : RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_9x9;
+
+ std::copy_n(values.begin(), values.size(),
+ std::begin(config_.rb_flt.spatial_coeff));
+
+ config_.rb_flt.r_enable = true;
+ config_.rb_flt.b_enable = true;
+
+ /*
+ * The range kernel is configured with a noise level lookup table (NLL)
+ * which stores a piecewise linear function that characterizes the
+ * sensor noise profile as a noise level function curve (NLF).
+ */
+ const YamlObject &rFObject = tuningData["NoiseLevelFunction"];
+
+ std::vector<uint16_t> nllValues;
+ nllValues = rFObject["coeff"].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (nllValues.size() != RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS) {
+ LOG(RkISP1Dpf, Error)
+ << "Invalid 'RangeFilter:coeff': expected "
+ << RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS
+ << " elements, got " << nllValues.size();
+ return -EINVAL;
+ }
+
+ std::copy_n(nllValues.begin(), nllValues.size(),
+ std::begin(config_.nll.coeff));
+
+ std::string scaleMode = rFObject["scale-mode"].get<std::string>("");
+ if (scaleMode == "linear") {
+ config_.nll.scale_mode = RKISP1_CIF_ISP_NLL_SCALE_LINEAR;
+ } else if (scaleMode == "logarithmic") {
+ config_.nll.scale_mode = RKISP1_CIF_ISP_NLL_SCALE_LOGARITHMIC;
+ } else {
+ LOG(RkISP1Dpf, Error)
+ << "Invalid 'RangeFilter:scale-mode': expected "
+ << "'linear' or 'logarithmic' value, got "
+ << scaleMode;
+ return -EINVAL;
+ }
+
+ const YamlObject &fSObject = tuningData["FilterStrength"];
+
+ strengthConfig_.r = fSObject["r"].get<uint16_t>(64);
+ strengthConfig_.g = fSObject["g"].get<uint16_t>(64);
+ strengthConfig_.b = fSObject["b"].get<uint16_t>(64);
+
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Dpf::queueRequest(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls)
+{
+ auto &dpf = context.activeState.dpf;
+ bool update = false;
+
+ const auto &denoise = controls.get(controls::draft::NoiseReductionMode);
+ if (denoise) {
+ LOG(RkISP1Dpf, Debug) << "Set denoise to " << *denoise;
+
+ switch (*denoise) {
+ case controls::draft::NoiseReductionModeOff:
+ if (dpf.denoise) {
+ dpf.denoise = false;
+ update = true;
+ }
+ break;
+ case controls::draft::NoiseReductionModeMinimal:
+ case controls::draft::NoiseReductionModeHighQuality:
+ case controls::draft::NoiseReductionModeFast:
+ if (!dpf.denoise) {
+ dpf.denoise = true;
+ update = true;
+ }
+ break;
+ default:
+ LOG(RkISP1Dpf, Error)
+ << "Unsupported denoise value "
+ << *denoise;
+ break;
+ }
+ }
+
+ frameContext.dpf.denoise = dpf.denoise;
+ frameContext.dpf.update = update;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Dpf::prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+{
+ if (frame == 0) {
+ params->others.dpf_config = config_;
+ params->others.dpf_strength_config = strengthConfig_;
+
+ const auto &awb = context.configuration.awb;
+ const auto &lsc = context.configuration.lsc;
+ auto &mode = params->others.dpf_config.gain.mode;
+
+ /*
+ * The DPF needs to take into account the total amount of
+ * digital gain, which comes from the AWB and LSC modules. The
+ * DPF hardware can be programmed with a digital gain value
+ * manually, but can also use the gains supplied by the AWB and
+ * LSC modules automatically when they are enabled. Use that
+ * mode of operation as it simplifies control of the DPF.
+ */
+ if (awb.enabled && lsc.enabled)
+ mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_LSC_GAINS;
+ else if (awb.enabled)
+ mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_GAINS;
+ else if (lsc.enabled)
+ mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS;
+ else
+ mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED;
+
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF |
+ RKISP1_CIF_ISP_MODULE_DPF_STRENGTH;
+ }
+
+ if (frameContext.dpf.update) {
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF;
+ if (frameContext.dpf.denoise)
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_DPF;
+ }
+}
+
+REGISTER_IPA_ALGORITHM(Dpf, "Dpf")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h
new file mode 100644
index 00000000..58f29f74
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/dpf.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * dpf.h - RkISP1 Denoise Pre-Filter control
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class Dpf : public Algorithm
+{
+public:
+ Dpf();
+ ~Dpf() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ void queueRequest(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+
+private:
+ struct rkisp1_cif_isp_dpf_config config_;
+ struct rkisp1_cif_isp_dpf_strength_config strengthConfig_;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp
new file mode 100644
index 00000000..4b89c05a
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/filter.cpp
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * filter.cpp - RkISP1 Filter control
+ */
+
+#include "filter.h"
+
+#include <cmath>
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+
+/**
+ * \file filter.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class Filter
+ * \brief RkISP1 Filter control
+ *
+ * Denoise and Sharpness filters will be applied by RkISP1 during the
+ * demosaicing step. The denoise filter is responsible for removing noise from
+ * the image, while the sharpness filter will enhance its acutance.
+ *
+ * \todo In current version the denoise and sharpness control is based on user
+ * controls. In a future version it should be controlled automatically by the
+ * algorithm.
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Filter)
+
+static constexpr uint32_t kFiltLumWeightDefault = 0x00022040;
+static constexpr uint32_t kFiltModeDefault = 0x000004f2;
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Filter::queueRequest(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls)
+{
+ auto &filter = context.activeState.filter;
+ bool update = false;
+
+ const auto &sharpness = controls.get(controls::Sharpness);
+ if (sharpness) {
+ unsigned int value = std::round(std::clamp(*sharpness, 0.0f, 10.0f));
+
+ if (filter.sharpness != value) {
+ filter.sharpness = value;
+ update = true;
+ }
+
+ LOG(RkISP1Filter, Debug) << "Set sharpness to " << *sharpness;
+ }
+
+ const auto &denoise = controls.get(controls::draft::NoiseReductionMode);
+ if (denoise) {
+ LOG(RkISP1Filter, Debug) << "Set denoise to " << *denoise;
+
+ switch (*denoise) {
+ case controls::draft::NoiseReductionModeOff:
+ if (filter.denoise != 0) {
+ filter.denoise = 0;
+ update = true;
+ }
+ break;
+ case controls::draft::NoiseReductionModeMinimal:
+ if (filter.denoise != 1) {
+ filter.denoise = 1;
+ update = true;
+ }
+ break;
+ case controls::draft::NoiseReductionModeHighQuality:
+ case controls::draft::NoiseReductionModeFast:
+ if (filter.denoise != 3) {
+ filter.denoise = 3;
+ update = true;
+ }
+ break;
+ default:
+ LOG(RkISP1Filter, Error)
+ << "Unsupported denoise value "
+ << *denoise;
+ break;
+ }
+ }
+
+ frameContext.filter.denoise = filter.denoise;
+ frameContext.filter.sharpness = filter.sharpness;
+ frameContext.filter.update = update;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Filter::prepare([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+{
+ /* Check if the algorithm configuration has been updated. */
+ if (!frameContext.filter.update)
+ return;
+
+ static constexpr uint16_t filt_fac_sh0[] = {
+ 0x04, 0x07, 0x0a, 0x0c, 0x10, 0x14, 0x1a, 0x1e, 0x24, 0x2a, 0x30
+ };
+
+ static constexpr uint16_t filt_fac_sh1[] = {
+ 0x04, 0x08, 0x0c, 0x10, 0x16, 0x1b, 0x20, 0x26, 0x2c, 0x30, 0x3f
+ };
+
+ static constexpr uint16_t filt_fac_mid[] = {
+ 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x10, 0x13, 0x17, 0x1d, 0x22, 0x28
+ };
+
+ static constexpr uint16_t filt_fac_bl0[] = {
+ 0x02, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x10, 0x15, 0x1a, 0x24
+ };
+
+ static constexpr uint16_t filt_fac_bl1[] = {
+ 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x06, 0x08, 0x0d, 0x14, 0x20
+ };
+
+ static constexpr uint16_t filt_thresh_sh0[] = {
+ 0, 18, 26, 36, 41, 75, 90, 120, 170, 250, 1023
+ };
+
+ static constexpr uint16_t filt_thresh_sh1[] = {
+ 0, 33, 44, 51, 67, 100, 120, 150, 200, 300, 1023
+ };
+
+ static constexpr uint16_t filt_thresh_bl0[] = {
+ 0, 8, 13, 23, 26, 50, 60, 80, 140, 180, 1023
+ };
+
+ static constexpr uint16_t filt_thresh_bl1[] = {
+ 0, 2, 5, 10, 15, 20, 26, 51, 100, 150, 1023
+ };
+
+ static constexpr uint16_t stage1_select[] = {
+ 6, 6, 4, 4, 3, 3, 2, 2, 2, 1, 0
+ };
+
+ static constexpr uint16_t filt_chr_v_mode[] = {
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
+ };
+
+ static constexpr uint16_t filt_chr_h_mode[] = {
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
+ };
+
+ uint8_t denoise = frameContext.filter.denoise;
+ uint8_t sharpness = frameContext.filter.sharpness;
+ auto &flt_config = params->others.flt_config;
+
+ flt_config.fac_sh0 = filt_fac_sh0[sharpness];
+ flt_config.fac_sh1 = filt_fac_sh1[sharpness];
+ flt_config.fac_mid = filt_fac_mid[sharpness];
+ flt_config.fac_bl0 = filt_fac_bl0[sharpness];
+ flt_config.fac_bl1 = filt_fac_bl1[sharpness];
+
+ flt_config.lum_weight = kFiltLumWeightDefault;
+ flt_config.mode = kFiltModeDefault;
+ flt_config.thresh_sh0 = filt_thresh_sh0[denoise];
+ flt_config.thresh_sh1 = filt_thresh_sh1[denoise];
+ flt_config.thresh_bl0 = filt_thresh_bl0[denoise];
+ flt_config.thresh_bl1 = filt_thresh_bl1[denoise];
+ flt_config.grn_stage1 = stage1_select[denoise];
+ flt_config.chr_v_mode = filt_chr_v_mode[denoise];
+ flt_config.chr_h_mode = filt_chr_h_mode[denoise];
+
+ /*
+ * Combined high denoising and high sharpening requires some
+ * adjustments to the configuration of the filters. A first stage
+ * filter with a lower strength must be selected, and the blur factors
+ * must be decreased.
+ */
+ if (denoise == 9) {
+ if (sharpness > 3)
+ flt_config.grn_stage1 = 2;
+ } else if (denoise == 10) {
+ if (sharpness > 5)
+ flt_config.grn_stage1 = 2;
+ else if (sharpness > 3)
+ flt_config.grn_stage1 = 1;
+ }
+
+ if (denoise > 7) {
+ if (sharpness > 7) {
+ flt_config.fac_bl0 /= 2;
+ flt_config.fac_bl1 /= 4;
+ } else if (sharpness > 4) {
+ flt_config.fac_bl0 = flt_config.fac_bl0 * 3 / 4;
+ flt_config.fac_bl1 /= 2;
+ }
+ }
+
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_FLT;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_FLT;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_FLT;
+}
+
+REGISTER_IPA_ALGORITHM(Filter, "Filter")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h
new file mode 100644
index 00000000..3fd882ea
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/filter.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * filter.h - RkISP1 Filter control
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class Filter : public Algorithm
+{
+public:
+ Filter() = default;
+ ~Filter() = default;
+
+ void queueRequest(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp
new file mode 100644
index 00000000..b9f87912
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/gsl.cpp
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * gsl.cpp - RkISP1 Gamma Sensor Linearization control
+ */
+
+#include "gsl.h"
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "linux/rkisp1-config.h"
+
+/**
+ * \file gsl.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class GammaSensorLinearization
+ * \brief RkISP1 Gamma Sensor Linearization control
+ *
+ * This algorithm linearizes the sensor output to compensate the sensor
+ * non-linearities by applying piecewise linear functions to the red, green and
+ * blue channels.
+ *
+ * The curves are specified in the tuning data and defined using 17 points.
+ *
+ * - The X coordinates are expressed using 16 intervals, with the first point
+ * at X coordinate 0. Each interval is expressed as a 2-bit value DX (from
+ * GAMMA_DX_1 to GAMMA_DX_16), stored in the RKISP1_CIF_ISP_GAMMA_DX_LO and
+ * RKISP1_CIF_ISP_GAMMA_DX_HI registers. The real interval is equal to
+ * \f$2^{dx+4}\f$. X coordinates are shared between the red, green and blue
+ * curves.
+ *
+ * - The Y coordinates are specified as 17 values separately for the
+ * red, green and blue channels, with a 12-bit resolution. Each value must be
+ * in the [-2048, 2047] range compared to the previous value.
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Gsl)
+
+static constexpr unsigned int kDegammaXIntervals = 16;
+
+GammaSensorLinearization::GammaSensorLinearization()
+{
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
+ const YamlObject &tuningData)
+{
+ std::vector<uint16_t> xIntervals =
+ tuningData["x-intervals"].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (xIntervals.size() != kDegammaXIntervals) {
+ LOG(RkISP1Gsl, Error)
+ << "Invalid 'x' coordinates: expected "
+ << kDegammaXIntervals << " elements, got "
+ << xIntervals.size();
+
+ return -EINVAL;
+ }
+
+ /* Compute gammaDx_ intervals from xIntervals values */
+ gammaDx_[0] = 0;
+ gammaDx_[1] = 0;
+ for (unsigned int i = 0; i < kDegammaXIntervals; ++i)
+ gammaDx_[i / 8] |= (xIntervals[i] & 0x07) << ((i % 8) * 4);
+
+ const YamlObject &yObject = tuningData["y"];
+ if (!yObject.isDictionary()) {
+ LOG(RkISP1Gsl, Error)
+ << "Issue while parsing 'y' in tuning file: "
+ << "entry must be a dictionary";
+ return -EINVAL;
+ }
+
+ curveYr_ = yObject["red"].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (curveYr_.size() != RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE) {
+ LOG(RkISP1Gsl, Error)
+ << "Invalid 'y:red' coordinates: expected "
+ << RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE
+ << " elements, got " << curveYr_.size();
+ return -EINVAL;
+ }
+
+ curveYg_ = yObject["green"].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (curveYg_.size() != RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE) {
+ LOG(RkISP1Gsl, Error)
+ << "Invalid 'y:green' coordinates: expected "
+ << RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE
+ << " elements, got " << curveYg_.size();
+ return -EINVAL;
+ }
+
+ curveYb_ = yObject["blue"].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (curveYb_.size() != RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE) {
+ LOG(RkISP1Gsl, Error)
+ << "Invalid 'y:blue' coordinates: expected "
+ << RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE
+ << " elements, got " << curveYb_.size();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context,
+ const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params)
+{
+ if (frame > 0)
+ return;
+
+ params->others.sdg_config.xa_pnts.gamma_dx0 = gammaDx_[0];
+ params->others.sdg_config.xa_pnts.gamma_dx1 = gammaDx_[1];
+
+ std::copy(curveYr_.begin(), curveYr_.end(),
+ params->others.sdg_config.curve_r.gamma_y);
+ std::copy(curveYg_.begin(), curveYg_.end(),
+ params->others.sdg_config.curve_g.gamma_y);
+ std::copy(curveYb_.begin(), curveYb_.end(),
+ params->others.sdg_config.curve_b.gamma_y);
+
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_SDG;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_SDG;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_SDG;
+}
+
+REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h
new file mode 100644
index 00000000..0f1116a7
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/gsl.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * gsl.h - RkISP1 Gamma Sensor Linearization control
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class GammaSensorLinearization : public Algorithm
+{
+public:
+ GammaSensorLinearization();
+ ~GammaSensorLinearization() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+
+private:
+ uint32_t gammaDx_[2];
+ std::vector<uint16_t> curveYr_;
+ std::vector<uint16_t> curveYg_;
+ std::vector<uint16_t> curveYb_;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
new file mode 100644
index 00000000..a7ccedb1
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -0,0 +1,342 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * lsc.cpp - RkISP1 Lens Shading Correction control
+ */
+
+#include "lsc.h"
+
+#include <algorithm>
+#include <cmath>
+#include <numeric>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "linux/rkisp1-config.h"
+
+/**
+ * \file lsc.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class LensShadingCorrection
+ * \brief RkISP1 Lens Shading Correction control
+ *
+ * Due to the optical characteristics of the lens, the light intensity received
+ * by the sensor is not uniform.
+ *
+ * The Lens Shading Correction algorithm applies multipliers to all pixels
+ * to compensate for the lens shading effect. The coefficients are
+ * specified in a downscaled table in the YAML tuning file.
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Lsc)
+
+static std::vector<double> parseSizes(const YamlObject &tuningData,
+ const char *prop)
+{
+ std::vector<double> sizes =
+ tuningData[prop].getList<double>().value_or(std::vector<double>{});
+ if (sizes.size() != RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE) {
+ LOG(RkISP1Lsc, Error)
+ << "Invalid '" << prop << "' values: expected "
+ << RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE
+ << " elements, got " << sizes.size();
+ return {};
+ }
+
+ /*
+ * The sum of all elements must be 0.5 to satisfy hardware constraints.
+ * Validate it here, allowing a 1% tolerance as rounding errors may
+ * prevent an exact match (further adjustments will be performed in
+ * LensShadingCorrection::prepare()).
+ */
+ double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0);
+ if (sum < 0.495 || sum > 0.505) {
+ LOG(RkISP1Lsc, Error)
+ << "Invalid '" << prop << "' values: sum of the elements"
+ << " should be 0.5, got " << sum;
+ return {};
+ }
+
+ return sizes;
+}
+
+static std::vector<uint16_t> parseTable(const YamlObject &tuningData,
+ const char *prop)
+{
+ static constexpr unsigned int kLscNumSamples =
+ RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
+
+ std::vector<uint16_t> table =
+ tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (table.size() != kLscNumSamples) {
+ LOG(RkISP1Lsc, Error)
+ << "Invalid '" << prop << "' values: expected "
+ << kLscNumSamples
+ << " elements, got " << table.size();
+ return {};
+ }
+
+ return table;
+}
+
+LensShadingCorrection::LensShadingCorrection()
+ : lastCt_({ 0, 0 })
+{
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
+ const YamlObject &tuningData)
+{
+ xSize_ = parseSizes(tuningData, "x-size");
+ ySize_ = parseSizes(tuningData, "y-size");
+
+ if (xSize_.empty() || ySize_.empty())
+ return -EINVAL;
+
+ /* Get all defined sets to apply. */
+ const YamlObject &yamlSets = tuningData["sets"];
+ if (!yamlSets.isList()) {
+ LOG(RkISP1Lsc, Error)
+ << "'sets' parameter not found in tuning file";
+ return -EINVAL;
+ }
+
+ const auto &sets = yamlSets.asList();
+ for (const auto &yamlSet : sets) {
+ uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
+
+ if (sets_.count(ct)) {
+ LOG(RkISP1Lsc, Error)
+ << "Multiple sets found for color temperature "
+ << ct;
+ return -EINVAL;
+ }
+
+ Components &set = sets_[ct];
+
+ set.ct = ct;
+ set.r = parseTable(yamlSet, "r");
+ set.gr = parseTable(yamlSet, "gr");
+ set.gb = parseTable(yamlSet, "gb");
+ set.b = parseTable(yamlSet, "b");
+
+ if (set.r.empty() || set.gr.empty() ||
+ set.gb.empty() || set.b.empty()) {
+ LOG(RkISP1Lsc, Error)
+ << "Set for color temperature " << ct
+ << " is missing tables";
+ return -EINVAL;
+ }
+ }
+
+ if (sets_.empty()) {
+ LOG(RkISP1Lsc, Error) << "Failed to load any sets";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::configure
+ */
+int LensShadingCorrection::configure(IPAContext &context,
+ [[maybe_unused]] const IPACameraSensorInfo &configInfo)
+{
+ const Size &size = context.configuration.sensor.size;
+ Size totalSize{};
+
+ for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE; ++i) {
+ xSizes_[i] = xSize_[i] * size.width;
+ ySizes_[i] = ySize_[i] * size.height;
+
+ /*
+ * To prevent unexpected behavior of the ISP, the sum of x_size_tbl and
+ * y_size_tbl items shall be equal to respectively size.width/2 and
+ * size.height/2. Enforce it by computing the last tables value to avoid
+ * rounding-induced errors.
+ */
+ if (i == RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE - 1) {
+ xSizes_[i] = size.width / 2 - totalSize.width;
+ ySizes_[i] = size.height / 2 - totalSize.height;
+ }
+
+ totalSize.width += xSizes_[i];
+ totalSize.height += ySizes_[i];
+
+ xGrad_[i] = std::round(32768 / xSizes_[i]);
+ yGrad_[i] = std::round(32768 / ySizes_[i]);
+ }
+
+ context.configuration.lsc.enabled = true;
+ return 0;
+}
+
+void LensShadingCorrection::setParameters(rkisp1_params_cfg *params)
+{
+ struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config;
+
+ memcpy(config.x_grad_tbl, xGrad_, sizeof(config.x_grad_tbl));
+ memcpy(config.y_grad_tbl, yGrad_, sizeof(config.y_grad_tbl));
+ memcpy(config.x_size_tbl, xSizes_, sizeof(config.x_size_tbl));
+ memcpy(config.y_size_tbl, ySizes_, sizeof(config.y_size_tbl));
+
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_LSC;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_LSC;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_LSC;
+}
+
+void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config,
+ const Components &set)
+{
+ std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]);
+ std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]);
+ std::copy(set.gb.begin(), set.gb.end(), &config.gb_data_tbl[0][0]);
+ std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]);
+}
+
+/*
+ * Interpolate LSC parameters based on color temperature value.
+ */
+void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config,
+ const Components &set0,
+ const Components &set1,
+ const uint32_t ct)
+{
+ double coeff0 = (set1.ct - ct) / static_cast<double>(set1.ct - set0.ct);
+ double coeff1 = (ct - set0.ct) / static_cast<double>(set1.ct - set0.ct);
+
+ for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) {
+ for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) {
+ unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j;
+
+ config.r_data_tbl[i][j] =
+ set0.r[sample] * coeff0 +
+ set1.r[sample] * coeff1;
+
+ config.gr_data_tbl[i][j] =
+ set0.gr[sample] * coeff0 +
+ set1.gr[sample] * coeff1;
+
+ config.gb_data_tbl[i][j] =
+ set0.gb[sample] * coeff0 +
+ set1.gb[sample] * coeff1;
+
+ config.b_data_tbl[i][j] =
+ set0.b[sample] * coeff0 +
+ set1.b[sample] * coeff1;
+ }
+ }
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void LensShadingCorrection::prepare(IPAContext &context,
+ const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params)
+{
+ struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config;
+
+ /*
+ * If there is only one set, the configuration has already been done
+ * for first frame.
+ */
+ if (sets_.size() == 1 && frame > 0)
+ return;
+
+ /*
+ * If there is only one set, pick it. We can ignore lastCt_, as it will
+ * never be relevant.
+ */
+ if (sets_.size() == 1) {
+ setParameters(params);
+ copyTable(config, sets_.cbegin()->second);
+ return;
+ }
+
+ uint32_t ct = context.activeState.awb.temperatureK;
+ ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first);
+
+ /*
+ * If the original is the same, then it means the same adjustment would
+ * be made. If the adjusted is the same, then it means that it's the
+ * same as what was actually applied. Thus in these cases we can skip
+ * reprogramming the LSC.
+ *
+ * original == adjusted can only happen if an interpolation
+ * happened, or if original has an exact entry in sets_. This means
+ * that if original != adjusted, then original was adjusted to
+ * the nearest available entry in sets_, resulting in adjusted.
+ * Clearly, any ct value that is in between original and adjusted
+ * will be adjusted to the same adjusted value, so we can skip
+ * reprogramming the LSC table.
+ *
+ * We also skip updating the original value, as the last one had a
+ * larger bound and thus a larger range of ct values that will be
+ * adjusted to the same adjusted.
+ */
+ if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) ||
+ (lastCt_.adjusted <= ct && ct <= lastCt_.original))
+ return;
+
+ setParameters(params);
+
+ /*
+ * The color temperature matches exactly one of the available LSC tables.
+ */
+ if (sets_.count(ct)) {
+ copyTable(config, sets_[ct]);
+ lastCt_ = { ct, ct };
+ return;
+ }
+
+ /* No shortcuts left; we need to round or interpolate */
+ auto iter = sets_.upper_bound(ct);
+ const Components &set1 = iter->second;
+ const Components &set0 = (--iter)->second;
+ uint32_t ct0 = set0.ct;
+ uint32_t ct1 = set1.ct;
+ uint32_t diff0 = ct - ct0;
+ uint32_t diff1 = ct1 - ct;
+ static constexpr double kThreshold = 0.1;
+ float threshold = kThreshold * (ct1 - ct0);
+
+ if (diff0 < threshold || diff1 < threshold) {
+ const Components &set = diff0 < diff1 ? set0 : set1;
+ LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct;
+ copyTable(config, set);
+ lastCt_ = { ct, set.ct };
+ return;
+ }
+
+ /*
+ * ct is not within 10% of the difference between the neighbouring
+ * color temperatures, so we need to interpolate.
+ */
+ LOG(RkISP1Lsc, Debug)
+ << "ct is " << ct << ", interpolating between "
+ << ct0 << " and " << ct1;
+ interpolateTable(config, set0, set1, ct);
+ lastCt_ = { ct, ct };
+}
+
+REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
new file mode 100644
index 00000000..e2a93a56
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021-2022, Ideas On Board
+ *
+ * lsc.h - RkISP1 Lens Shading Correction control
+ */
+
+#pragma once
+
+#include <map>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class LensShadingCorrection : public Algorithm
+{
+public:
+ LensShadingCorrection();
+ ~LensShadingCorrection() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+
+private:
+ struct Components {
+ uint32_t ct;
+ std::vector<uint16_t> r;
+ std::vector<uint16_t> gr;
+ std::vector<uint16_t> gb;
+ std::vector<uint16_t> b;
+ };
+
+ void setParameters(rkisp1_params_cfg *params);
+ void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);
+ void interpolateTable(rkisp1_cif_isp_lsc_config &config,
+ const Components &set0, const Components &set1,
+ const uint32_t ct);
+
+ std::map<uint32_t, Components> sets_;
+ std::vector<double> xSize_;
+ std::vector<double> ySize_;
+ uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
+ uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
+ uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
+ uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
+ struct {
+ uint32_t original;
+ uint32_t adjusted;
+ } lastCt_;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build
index 7ec53d89..93a48329 100644
--- a/src/ipa/rkisp1/algorithms/meson.build
+++ b/src/ipa/rkisp1/algorithms/meson.build
@@ -4,4 +4,10 @@ rkisp1_ipa_algorithms = files([
'agc.cpp',
'awb.cpp',
'blc.cpp',
+ 'cproc.cpp',
+ 'dpcc.cpp',
+ 'dpf.cpp',
+ 'filter.cpp',
+ 'gsl.cpp',
+ 'lsc.cpp',
])
diff --git a/src/ipa/rkisp1/data/imx219.yaml b/src/ipa/rkisp1/data/imx219.yaml
index 232d8ae8..cbcc43b8 100644
--- a/src/ipa/rkisp1/data/imx219.yaml
+++ b/src/ipa/rkisp1/data/imx219.yaml
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-%YAML 1.2
+%YAML 1.1
---
version: 1
algorithms:
@@ -10,4 +10,109 @@ algorithms:
Gr: 256
Gb: 256
B: 256
+ - LensShadingCorrection:
+ x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ sets:
+ - ct: 5800
+ r: [
+ 1501, 1480, 1478, 1362, 1179, 1056, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1030, 1053, 1134, 1185, 1520, 1480, 1463, 1179, 1056, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1027, 1046, 1134, 1533, 1471, 1179, 1056, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1025, 1039, 1471,
+ 1314, 1068, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1025, 1314, 1150, 1028, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1150, 1050, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1076, 1026,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1052, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1050, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1050, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1050, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1025, 1086, 1037, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1025, 1057, 1182, 1071, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1057, 1161,
+ 1345, 1146, 1027, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1036, 1161, 1298, 1612, 1328, 1089, 1025, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1025, 1036, 1161, 1324, 1463, 1884, 1651, 1339, 1103, 1032,
+ 1025, 1024, 1024, 1024, 1024, 1025, 1038, 1101, 1204, 1324, 1463, 1497, 1933,
+ 1884, 1587, 1275, 1079, 1052, 1046, 1046, 1046, 1046, 1055, 1101, 1204, 1336,
+ 1487, 1493, 1476,
+ ]
+ gr: [
+ 1262, 1250, 1094, 1027, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1250, 1095, 1028, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1095, 1030, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1030,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1025, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1041, 1051, 1025, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1051, 1165, 1088,
+ 1051, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1051, 1165, 1261,
+ ]
+ gb: [
+ 1259, 1248, 1092, 1027, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1248, 1092, 1027, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1092, 1029, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1029,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1025, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1041, 1051, 1025, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1052, 1166, 1090,
+ 1051, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1052, 1166, 1266,
+ ]
+ b: [
+ 1380, 1378, 1377, 1247, 1080, 1025, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1030, 1406, 1378, 1284, 1092, 1027, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1406, 1338, 1129, 1029, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1338,
+ 1205, 1043, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1205, 1094, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1116, 1039, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1070, 1025,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1052, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1052, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1052, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1052, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1070, 1025, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1025, 1109, 1036, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1025, 1057,
+ 1175, 1082, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1057, 1176, 1293, 1172, 1036, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1054, 1185, 1334, 1438, 1294, 1099, 1025, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1054, 1185, 1334, 1334, 1462,
+ 1438, 1226, 1059, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1025, 1054, 1185,
+ 1326, 1334, 1334,
+ ]
...
diff --git a/src/ipa/rkisp1/data/imx258.yaml b/src/ipa/rkisp1/data/imx258.yaml
new file mode 100644
index 00000000..43dddf20
--- /dev/null
+++ b/src/ipa/rkisp1/data/imx258.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+ - Agc:
+ - Awb:
+ - LensShadingCorrection:
+ x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ sets:
+ #4208x3120_A_70 - A
+ - ct: 2856
+ resolution: 4208x3120
+ r: [1483, 1423, 1410, 1414, 1417, 1384, 1356, 1348, 1349, 1348, 1393, 1392, 1409, 1444, 1460, 1475, 1568, 1462, 1409, 1398, 1391, 1361, 1343, 1328, 1312, 1316, 1325, 1328, 1372, 1395, 1427, 1410, 1440, 1525, 1441, 1366, 1373, 1364, 1338, 1312, 1287, 1270, 1262, 1267, 1305, 1339, 1380, 1402, 1425, 1424, 1510, 1423, 1376, 1375, 1353, 1309, 1253, 1220, 1201, 1192, 1203, 1243, 1286, 1338, 1375, 1427, 1438, 1499, 1405, 1353, 1354, 1331, 1269, 1207, 1169, 1140, 1137, 1145, 1186, 1246, 1309, 1373, 1399, 1438, 1512, 1391, 1349, 1351, 1306, 1236, 1174, 1121, 1089, 1083, 1098, 1139, 1202, 1276, 1349, 1384, 1428, 1494, 1401, 1337, 1336, 1277, 1211, 1138, 1082, 1057, 1053, 1067, 1110, 1166, 1253, 1331, 1375, 1417, 1485, 1401, 1341, 1316, 1269, 1184, 1115, 1063, 1037, 1029, 1042, 1082, 1144, 1234, 1322, 1368, 1405, 1480, 1387, 1329, 1305, 1257, 1179, 1104, 1049, 1028, 1024, 1037, 1078, 1144, 1231, 1312, 1363, 1404, 1456, 1401, 1341, 1313, 1254, 1177, 1104, 1053, 1041, 1026, 1042, 1082, 1149, 1229, 1322, 1372, 1397, 1457, 1397, 1344, 1312, 1271, 1191, 1122, 1070, 1052, 1044, 1061, 1097, 1166, 1245, 1334, 1382, 1405, 1476, 1400, 1342, 1333, 1293, 1213, 1146, 1099, 1073, 1061, 1081, 1134, 1202, 1273, 1332, 1380, 1411, 1484, 1414, 1350, 1344, 1301, 1251, 1181, 1133, 1109, 1100, 1118, 1164, 1218, 1299, 1338, 1373, 1408, 1459, 1397, 1360, 1342, 1339, 1293, 1231, 1181, 1149, 1155, 1161, 1202, 1256, 1315, 1364, 1383, 1396, 1479, 1382, 1342, 1358, 1346, 1314, 1284, 1231, 1210, 1198, 1224, 1251, 1303, 1338, 1361, 1381, 1394, 1455, 1386, 1338, 1342, 1341, 1326, 1296, 1274, 1254, 1249, 1262, 1280, 1319, 1357, 1367, 1373, 1379, 1462, 1426, 1340, 1356, 1354, 1330, 1344, 1291, 1275, 1255, 1272, 1298, 1333, 1374, 1390, 1393, 1418, 1580, ]
+ gr: [1274, 1203, 1200, 1184, 1165, 1167, 1155, 1160, 1155, 1158, 1164, 1181, 1196, 1223, 1219, 1220, 1369, 1233, 1172, 1161, 1158, 1146, 1149, 1142, 1129, 1133, 1137, 1144, 1155, 1173, 1189, 1204, 1205, 1268, 1215, 1172, 1148, 1137, 1135, 1124, 1123, 1114, 1110, 1116, 1131, 1149, 1161, 1175, 1191, 1220, 1263, 1185, 1153, 1140, 1137, 1119, 1106, 1094, 1088, 1086, 1099, 1107, 1125, 1152, 1154, 1187, 1209, 1255, 1195, 1141, 1133, 1133, 1112, 1083, 1081, 1066, 1057, 1067, 1088, 1103, 1134, 1154, 1172, 1199, 1255, 1186, 1136, 1127, 1121, 1094, 1077, 1055, 1044, 1040, 1048, 1067, 1086, 1121, 1146, 1155, 1185, 1258, 1177, 1127, 1117, 1104, 1082, 1063, 1044, 1038, 1027, 1036, 1057, 1070, 1101, 1138, 1151, 1177, 1245, 1184, 1116, 1119, 1098, 1070, 1045, 1037, 1030, 1027, 1026, 1045, 1062, 1099, 1132, 1149, 1179, 1238, 1172, 1120, 1113, 1100, 1070, 1042, 1029, 1027, 1029, 1027, 1042, 1066, 1088, 1126, 1149, 1174, 1223, 1162, 1118, 1117, 1093, 1065, 1039, 1030, 1028, 1022, 1028, 1045, 1060, 1101, 1134, 1146, 1165, 1246, 1172, 1116, 1119, 1102, 1075, 1046, 1029, 1032, 1030, 1038, 1049, 1073, 1097, 1132, 1146, 1168, 1231, 1178, 1118, 1123, 1111, 1083, 1062, 1041, 1038, 1033, 1041, 1054, 1074, 1109, 1135, 1144, 1175, 1244, 1193, 1136, 1123, 1118, 1100, 1070, 1045, 1036, 1044, 1047, 1067, 1090, 1116, 1135, 1158, 1174, 1232, 1198, 1142, 1127, 1130, 1107, 1085, 1068, 1060, 1057, 1069, 1079, 1102, 1115, 1124, 1154, 1178, 1241, 1192, 1136, 1125, 1113, 1116, 1096, 1081, 1075, 1075, 1088, 1097, 1116, 1124, 1135, 1155, 1177, 1232, 1183, 1142, 1119, 1113, 1099, 1101, 1088, 1084, 1085, 1089, 1103, 1109, 1122, 1133, 1147, 1175, 1258, 1238, 1162, 1161, 1143, 1124, 1131, 1108, 1111, 1107, 1115, 1116, 1138, 1137, 1150, 1163, 1186, 1381, ]
+ gb: [1277, 1217, 1179, 1179, 1163, 1158, 1151, 1150, 1149, 1143, 1151, 1172, 1184, 1207, 1216, 1246, 1375, 1242, 1194, 1166, 1151, 1144, 1145, 1135, 1130, 1129, 1132, 1137, 1154, 1166, 1189, 1207, 1210, 1290, 1229, 1177, 1153, 1144, 1140, 1135, 1124, 1110, 1104, 1115, 1126, 1148, 1162, 1171, 1199, 1220, 1268, 1226, 1163, 1152, 1138, 1130, 1111, 1091, 1088, 1086, 1089, 1097, 1126, 1147, 1164, 1187, 1206, 1273, 1212, 1151, 1141, 1132, 1117, 1093, 1075, 1060, 1059, 1062, 1088, 1108, 1133, 1162, 1168, 1204, 1278, 1207, 1141, 1130, 1126, 1095, 1075, 1063, 1046, 1044, 1054, 1069, 1084, 1120, 1153, 1167, 1195, 1269, 1200, 1141, 1126, 1113, 1092, 1063, 1045, 1033, 1036, 1038, 1055, 1080, 1117, 1139, 1165, 1182, 1262, 1195, 1130, 1128, 1115, 1079, 1052, 1041, 1031, 1024, 1028, 1046, 1072, 1110, 1141, 1160, 1175, 1258, 1189, 1136, 1124, 1105, 1077, 1049, 1029, 1021, 1029, 1033, 1040, 1074, 1108, 1143, 1152, 1173, 1237, 1200, 1130, 1126, 1109, 1080, 1050, 1030, 1031, 1027, 1031, 1043, 1069, 1099, 1141, 1152, 1168, 1249, 1203, 1132, 1124, 1113, 1082, 1058, 1032, 1030, 1024, 1033, 1050, 1083, 1109, 1151, 1156, 1178, 1253, 1204, 1130, 1128, 1112, 1088, 1060, 1045, 1030, 1027, 1036, 1058, 1082, 1120, 1145, 1160, 1176, 1246, 1195, 1137, 1123, 1121, 1102, 1072, 1046, 1037, 1037, 1047, 1072, 1090, 1125, 1140, 1158, 1177, 1252, 1209, 1147, 1128, 1125, 1114, 1088, 1063, 1053, 1051, 1058, 1084, 1101, 1128, 1140, 1159, 1176, 1243, 1195, 1138, 1130, 1127, 1113, 1101, 1076, 1071, 1067, 1082, 1087, 1111, 1125, 1140, 1151, 1183, 1235, 1189, 1137, 1126, 1122, 1112, 1104, 1091, 1089, 1081, 1085, 1103, 1112, 1125, 1140, 1157, 1175, 1242, 1234, 1181, 1161, 1150, 1127, 1117, 1101, 1094, 1094, 1102, 1117, 1130, 1138, 1155, 1171, 1192, 1399, ]
+ b: [1309, 1209, 1169, 1157, 1149, 1136, 1116, 1117, 1126, 1128, 1127, 1141, 1143, 1182, 1196, 1209, 1398, 1231, 1176, 1140, 1123, 1119, 1113, 1111, 1122, 1105, 1117, 1116, 1135, 1130, 1135, 1171, 1169, 1271, 1251, 1154, 1132, 1118, 1104, 1109, 1103, 1094, 1088, 1104, 1093, 1120, 1130, 1135, 1151, 1180, 1267, 1219, 1136, 1111, 1125, 1106, 1107, 1082, 1074, 1077, 1074, 1101, 1112, 1117, 1136, 1139, 1173, 1256, 1205, 1125, 1108, 1118, 1110, 1091, 1081, 1065, 1068, 1065, 1086, 1087, 1105, 1123, 1119, 1156, 1249, 1195, 1106, 1112, 1101, 1085, 1068, 1064, 1053, 1043, 1048, 1068, 1073, 1095, 1117, 1118, 1123, 1251, 1193, 1101, 1091, 1097, 1081, 1052, 1043, 1045, 1041, 1045, 1052, 1065, 1100, 1112, 1112, 1123, 1200, 1180, 1096, 1103, 1083, 1069, 1053, 1045, 1035, 1034, 1035, 1045, 1062, 1087, 1108, 1113, 1113, 1228, 1176, 1093, 1095, 1080, 1062, 1055, 1035, 1033, 1028, 1037, 1039, 1064, 1080, 1115, 1121, 1120, 1202, 1174, 1086, 1087, 1078, 1064, 1049, 1037, 1027, 1022, 1031, 1045, 1058, 1087, 1113, 1108, 1113, 1207, 1200, 1095, 1102, 1092, 1072, 1052, 1043, 1033, 1024, 1033, 1043, 1069, 1095, 1112, 1128, 1123, 1220, 1215, 1101, 1091, 1096, 1080, 1059, 1051, 1040, 1031, 1040, 1064, 1064, 1095, 1111, 1112, 1141, 1222, 1198, 1119, 1108, 1097, 1080, 1059, 1050, 1043, 1034, 1043, 1063, 1073, 1100, 1107, 1114, 1131, 1212, 1197, 1136, 1094, 1109, 1096, 1078, 1054, 1052, 1051, 1060, 1063, 1078, 1101, 1109, 1116, 1142, 1256, 1212, 1112, 1098, 1097, 1094, 1084, 1074, 1061, 1051, 1057, 1064, 1080, 1089, 1102, 1115, 1136, 1227, 1185, 1118, 1081, 1059, 1072, 1068, 1057, 1049, 1048, 1054, 1066, 1058, 1067, 1096, 1109, 1143, 1223, 1291, 1173, 1131, 1113, 1087, 1077, 1090, 1081, 1090, 1086, 1090, 1092, 1103, 1144, 1149, 1216, 1387, ]
+ #4208x3120_D50_70 - D50
+ - ct: 5003
+ resolution: 4208x3120
+ r: [1240, 1212, 1218, 1191, 1191, 1171, 1136, 1144, 1113, 1148, 1182, 1166, 1210, 1211, 1213, 1240, 1336, 1236, 1193, 1176, 1158, 1147, 1126, 1107, 1122, 1107, 1107, 1110, 1146, 1176, 1194, 1195, 1219, 1259, 1210, 1157, 1156, 1153, 1123, 1115, 1094, 1074, 1078, 1081, 1098, 1130, 1163, 1170, 1179, 1220, 1284, 1228, 1146, 1159, 1132, 1101, 1074, 1059, 1053, 1044, 1060, 1072, 1102, 1131, 1156, 1186, 1227, 1272, 1219, 1176, 1150, 1124, 1091, 1043, 1036, 1025, 1025, 1031, 1042, 1076, 1095, 1155, 1188, 1209, 1296, 1206, 1161, 1128, 1101, 1065, 1032, 1019, 1018, 1027, 1018, 1034, 1057, 1102, 1139, 1161, 1211, 1274, 1184, 1133, 1119, 1097, 1042, 1018, 1020, 1027, 1034, 1030, 1032, 1042, 1075, 1119, 1164, 1199, 1270, 1205, 1124, 1114, 1086, 1033, 1015, 1023, 1039, 1039, 1033, 1026, 1041, 1074, 1111, 1142, 1206, 1278, 1193, 1118, 1098, 1084, 1023, 1003, 1016, 1047, 1059, 1038, 1025, 1046, 1063, 1124, 1148, 1190, 1238, 1191, 1124, 1107, 1069, 1027, 1009, 1012, 1036, 1045, 1036, 1020, 1024, 1058, 1118, 1158, 1183, 1262, 1213, 1121, 1112, 1076, 1030, 1012, 1003, 1019, 1028, 1013, 1020, 1036, 1078, 1123, 1155, 1176, 1228, 1221, 1135, 1117, 1105, 1055, 1020, 1005, 1007, 1007, 1004, 1017, 1048, 1088, 1131, 1169, 1183, 1280, 1209, 1141, 1125, 1105, 1074, 1025, 1012, 1008, 1000, 1011, 1024, 1050, 1113, 1128, 1154, 1199, 1290, 1217, 1142, 1134, 1120, 1101, 1054, 1028, 1014, 1006, 1017, 1040, 1078, 1105, 1136, 1164, 1188, 1250, 1195, 1130, 1148, 1120, 1108, 1083, 1053, 1041, 1032, 1061, 1067, 1097, 1127, 1136, 1152, 1181, 1227, 1166, 1145, 1140, 1141, 1119, 1092, 1075, 1072, 1052, 1065, 1089, 1107, 1147, 1154, 1158, 1183, 1230, 1136, 1147, 1150, 1168, 1139, 1113, 1098, 1055, 1048, 1072, 1079, 1129, 1147, 1173, 1188, 1181, 1283, ]
+ gr: [1246, 1183, 1160, 1143, 1145, 1138, 1113, 1111, 1117, 1116, 1132, 1145, 1167, 1167, 1196, 1197, 1335, 1205, 1152, 1123, 1122, 1123, 1103, 1107, 1102, 1097, 1102, 1099, 1128, 1141, 1157, 1152, 1184, 1242, 1204, 1141, 1112, 1106, 1102, 1093, 1096, 1085, 1076, 1085, 1094, 1107, 1123, 1146, 1162, 1178, 1218, 1169, 1130, 1114, 1100, 1096, 1083, 1072, 1059, 1065, 1070, 1087, 1096, 1116, 1134, 1155, 1174, 1238, 1159, 1126, 1105, 1102, 1083, 1062, 1060, 1049, 1047, 1054, 1063, 1084, 1111, 1131, 1140, 1164, 1243, 1167, 1114, 1105, 1088, 1067, 1047, 1034, 1034, 1028, 1042, 1042, 1059, 1096, 1114, 1135, 1170, 1200, 1156, 1101, 1098, 1089, 1068, 1048, 1027, 1034, 1029, 1032, 1047, 1043, 1088, 1111, 1130, 1160, 1201, 1143, 1100, 1086, 1087, 1051, 1034, 1029, 1028, 1030, 1019, 1033, 1044, 1087, 1109, 1124, 1155, 1211, 1148, 1098, 1088, 1077, 1058, 1037, 1026, 1025, 1034, 1033, 1031, 1054, 1074, 1107, 1134, 1159, 1211, 1150, 1090, 1084, 1074, 1056, 1029, 1020, 1028, 1025, 1027, 1031, 1044, 1080, 1109, 1126, 1152, 1208, 1131, 1101, 1088, 1073, 1048, 1035, 1030, 1026, 1024, 1034, 1038, 1053, 1083, 1104, 1124, 1160, 1206, 1147, 1103, 1082, 1082, 1060, 1035, 1026, 1023, 1018, 1031, 1044, 1058, 1096, 1114, 1128, 1153, 1208, 1170, 1112, 1098, 1088, 1070, 1049, 1027, 1027, 1023, 1031, 1046, 1071, 1085, 1106, 1129, 1150, 1228, 1164, 1111, 1101, 1089, 1078, 1058, 1040, 1030, 1032, 1037, 1060, 1073, 1102, 1097, 1125, 1156, 1223, 1181, 1115, 1097, 1093, 1083, 1072, 1056, 1047, 1041, 1057, 1071, 1079, 1081, 1102, 1124, 1141, 1195, 1170, 1109, 1091, 1089, 1061, 1074, 1049, 1054, 1052, 1057, 1067, 1076, 1097, 1106, 1121, 1141, 1211, 1173, 1129, 1108, 1099, 1093, 1092, 1076, 1063, 1057, 1065, 1090, 1107, 1117, 1140, 1123, 1175, 1343, ]
+ gb: [1238, 1183, 1160, 1160, 1134, 1134, 1124, 1108, 1131, 1127, 1124, 1145, 1172, 1188, 1201, 1217, 1349, 1216, 1160, 1128, 1120, 1117, 1110, 1108, 1105, 1102, 1111, 1114, 1125, 1144, 1160, 1162, 1192, 1260, 1212, 1141, 1127, 1118, 1101, 1104, 1103, 1086, 1077, 1086, 1105, 1116, 1126, 1147, 1167, 1191, 1242, 1191, 1130, 1126, 1103, 1093, 1082, 1074, 1070, 1064, 1064, 1079, 1099, 1113, 1132, 1156, 1185, 1247, 1175, 1117, 1114, 1109, 1081, 1067, 1061, 1047, 1044, 1051, 1066, 1083, 1108, 1134, 1141, 1180, 1248, 1187, 1108, 1106, 1095, 1076, 1052, 1044, 1036, 1034, 1042, 1052, 1070, 1105, 1124, 1140, 1161, 1228, 1171, 1091, 1095, 1088, 1069, 1041, 1035, 1034, 1034, 1037, 1048, 1062, 1090, 1120, 1129, 1165, 1223, 1158, 1108, 1093, 1080, 1052, 1030, 1034, 1027, 1030, 1028, 1034, 1054, 1083, 1112, 1133, 1141, 1208, 1158, 1099, 1091, 1075, 1047, 1031, 1017, 1021, 1035, 1027, 1033, 1054, 1088, 1110, 1120, 1146, 1211, 1171, 1099, 1093, 1079, 1056, 1029, 1021, 1030, 1025, 1031, 1037, 1047, 1077, 1116, 1122, 1132, 1203, 1179, 1093, 1087, 1076, 1053, 1038, 1028, 1024, 1024, 1024, 1040, 1058, 1082, 1108, 1114, 1144, 1198, 1167, 1091, 1091, 1087, 1059, 1047, 1029, 1016, 1021, 1036, 1045, 1066, 1093, 1113, 1116, 1144, 1205, 1159, 1113, 1099, 1091, 1069, 1047, 1029, 1029, 1024, 1037, 1054, 1072, 1088, 1109, 1125, 1150, 1200, 1186, 1114, 1097, 1098, 1087, 1065, 1035, 1033, 1043, 1042, 1054, 1076, 1089, 1111, 1126, 1130, 1214, 1153, 1106, 1100, 1090, 1086, 1082, 1057, 1059, 1053, 1059, 1066, 1077, 1088, 1113, 1117, 1144, 1203, 1147, 1107, 1110, 1090, 1088, 1072, 1070, 1060, 1062, 1058, 1074, 1087, 1096, 1109, 1126, 1150, 1216, 1170, 1145, 1128, 1108, 1088, 1110, 1085, 1070, 1064, 1078, 1077, 1101, 1107, 1136, 1148, 1163, 1345, ]
+ b: [1252, 1185, 1146, 1139, 1147, 1130, 1114, 1111, 1122, 1111, 1121, 1123, 1144, 1150, 1171, 1167, 1303, 1187, 1152, 1125, 1101, 1104, 1096, 1101, 1099, 1093, 1096, 1098, 1103, 1118, 1141, 1160, 1156, 1226, 1222, 1125, 1112, 1118, 1104, 1094, 1083, 1073, 1073, 1094, 1099, 1103, 1114, 1133, 1146, 1174, 1212, 1162, 1123, 1104, 1110, 1100, 1081, 1066, 1065, 1057, 1053, 1072, 1094, 1107, 1117, 1136, 1162, 1226, 1197, 1124, 1088, 1092, 1084, 1066, 1055, 1051, 1044, 1049, 1061, 1081, 1096, 1102, 1134, 1143, 1234, 1171, 1110, 1099, 1075, 1070, 1051, 1052, 1030, 1030, 1035, 1055, 1071, 1092, 1100, 1113, 1128, 1214, 1174, 1099, 1080, 1069, 1054, 1047, 1032, 1031, 1027, 1034, 1042, 1061, 1086, 1091, 1113, 1139, 1222, 1156, 1088, 1089, 1072, 1051, 1036, 1032, 1026, 1030, 1024, 1040, 1047, 1074, 1091, 1109, 1131, 1198, 1158, 1090, 1079, 1071, 1047, 1038, 1031, 1028, 1027, 1028, 1029, 1046, 1068, 1087, 1105, 1122, 1196, 1173, 1098, 1080, 1060, 1040, 1036, 1022, 1019, 1022, 1029, 1029, 1045, 1077, 1094, 1103, 1109, 1189, 1170, 1096, 1070, 1063, 1048, 1033, 1026, 1023, 1016, 1021, 1037, 1053, 1068, 1098, 1107, 1128, 1195, 1166, 1099, 1086, 1066, 1061, 1040, 1022, 1022, 1028, 1027, 1041, 1057, 1086, 1094, 1103, 1124, 1188, 1202, 1113, 1081, 1083, 1071, 1040, 1025, 1024, 1025, 1019, 1055, 1055, 1081, 1099, 1112, 1128, 1202, 1171, 1108, 1083, 1084, 1078, 1051, 1043, 1020, 1037, 1037, 1049, 1072, 1069, 1100, 1107, 1115, 1176, 1180, 1106, 1094, 1077, 1068, 1053, 1050, 1035, 1041, 1038, 1062, 1068, 1068, 1084, 1098, 1125, 1184, 1164, 1104, 1077, 1057, 1064, 1049, 1039, 1041, 1036, 1041, 1042, 1058, 1064, 1087, 1099, 1111, 1173, 1209, 1137, 1099, 1083, 1076, 1072, 1077, 1065, 1066, 1065, 1061, 1081, 1096, 1135, 1126, 1150, 1333, ]
+ #4208x3120_D65_70 - D65
+ - ct: 6504
+ resolution: 4208x3120
+ r: [1359, 1336, 1313, 1273, 1274, 1250, 1250, 1218, 1222, 1223, 1240, 1266, 1308, 1327, 1333, 1336, 1456, 1359, 1286, 1256, 1249, 1235, 1235, 1216, 1219, 1187, 1205, 1216, 1240, 1267, 1277, 1303, 1311, 1420, 1326, 1254, 1250, 1239, 1212, 1207, 1191, 1181, 1176, 1181, 1187, 1226, 1241, 1281, 1295, 1326, 1391, 1304, 1253, 1234, 1234, 1209, 1174, 1156, 1147, 1131, 1139, 1168, 1196, 1227, 1265, 1282, 1293, 1385, 1302, 1242, 1224, 1216, 1171, 1140, 1112, 1098, 1087, 1098, 1124, 1177, 1206, 1245, 1266, 1310, 1389, 1327, 1227, 1231, 1195, 1156, 1116, 1094, 1070, 1067, 1073, 1101, 1151, 1190, 1223, 1251, 1281, 1402, 1285, 1229, 1203, 1184, 1135, 1093, 1063, 1047, 1041, 1050, 1083, 1119, 1176, 1211, 1248, 1288, 1388, 1269, 1210, 1215, 1173, 1118, 1078, 1046, 1028, 1025, 1037, 1059, 1103, 1170, 1213, 1230, 1268, 1355, 1295, 1208, 1203, 1171, 1124, 1070, 1041, 1024, 1027, 1030, 1057, 1094, 1168, 1206, 1252, 1270, 1364, 1293, 1196, 1187, 1156, 1110, 1075, 1039, 1022, 1022, 1028, 1065, 1096, 1166, 1213, 1245, 1273, 1349, 1291, 1213, 1203, 1162, 1131, 1079, 1053, 1038, 1029, 1044, 1080, 1119, 1176, 1225, 1243, 1271, 1354, 1284, 1222, 1202, 1186, 1136, 1097, 1063, 1054, 1041, 1054, 1083, 1131, 1186, 1232, 1256, 1276, 1360, 1290, 1237, 1210, 1207, 1166, 1116, 1076, 1066, 1070, 1080, 1109, 1152, 1188, 1230, 1240, 1293, 1341, 1304, 1231, 1229, 1210, 1177, 1153, 1128, 1097, 1105, 1108, 1140, 1170, 1213, 1224, 1260, 1282, 1357, 1299, 1237, 1218, 1218, 1202, 1171, 1144, 1135, 1131, 1143, 1161, 1189, 1221, 1233, 1261, 1271, 1346, 1262, 1216, 1229, 1218, 1191, 1187, 1162, 1161, 1148, 1153, 1180, 1201, 1220, 1234, 1251, 1250, 1352, 1294, 1234, 1242, 1240, 1246, 1200, 1178, 1172, 1137, 1154, 1187, 1214, 1252, 1251, 1247, 1296, 1456, ]
+ gr: [1240, 1187, 1158, 1152, 1144, 1129, 1130, 1118, 1115, 1113, 1119, 1141, 1156, 1172, 1180, 1199, 1330, 1223, 1153, 1127, 1123, 1115, 1104, 1104, 1095, 1100, 1107, 1110, 1121, 1137, 1156, 1169, 1179, 1261, 1205, 1138, 1122, 1108, 1101, 1104, 1098, 1088, 1083, 1090, 1106, 1119, 1125, 1144, 1163, 1186, 1236, 1170, 1122, 1112, 1101, 1091, 1089, 1076, 1068, 1061, 1072, 1084, 1101, 1118, 1134, 1156, 1179, 1243, 1162, 1120, 1105, 1105, 1088, 1067, 1061, 1050, 1050, 1057, 1070, 1088, 1112, 1127, 1145, 1166, 1232, 1163, 1108, 1111, 1099, 1079, 1054, 1046, 1041, 1030, 1040, 1053, 1074, 1098, 1120, 1140, 1170, 1226, 1158, 1105, 1094, 1099, 1064, 1048, 1034, 1036, 1028, 1029, 1049, 1055, 1089, 1116, 1135, 1166, 1218, 1142, 1107, 1094, 1092, 1061, 1041, 1030, 1024, 1025, 1028, 1036, 1053, 1087, 1110, 1128, 1153, 1223, 1142, 1098, 1092, 1084, 1056, 1036, 1025, 1024, 1027, 1024, 1038, 1055, 1082, 1108, 1132, 1153, 1203, 1155, 1098, 1094, 1080, 1056, 1034, 1023, 1025, 1022, 1025, 1036, 1053, 1078, 1112, 1126, 1144, 1212, 1163, 1096, 1092, 1083, 1059, 1039, 1027, 1023, 1028, 1026, 1044, 1056, 1091, 1114, 1130, 1149, 1204, 1152, 1103, 1090, 1089, 1065, 1045, 1031, 1028, 1025, 1035, 1048, 1064, 1092, 1116, 1131, 1157, 1203, 1162, 1100, 1098, 1093, 1076, 1049, 1033, 1030, 1030, 1040, 1050, 1067, 1094, 1103, 1127, 1154, 1221, 1162, 1112, 1099, 1095, 1079, 1064, 1042, 1033, 1034, 1048, 1061, 1077, 1091, 1108, 1126, 1148, 1213, 1154, 1112, 1106, 1095, 1081, 1065, 1056, 1052, 1050, 1059, 1071, 1082, 1091, 1102, 1129, 1149, 1211, 1157, 1106, 1092, 1081, 1066, 1072, 1064, 1048, 1056, 1061, 1066, 1076, 1091, 1107, 1122, 1145, 1207, 1204, 1127, 1117, 1106, 1098, 1081, 1073, 1068, 1062, 1068, 1081, 1107, 1102, 1127, 1148, 1170, 1353, ]
+ gb: [1240, 1177, 1157, 1143, 1129, 1130, 1118, 1112, 1123, 1123, 1123, 1137, 1159, 1181, 1197, 1206, 1354, 1217, 1153, 1130, 1124, 1109, 1114, 1105, 1108, 1116, 1110, 1114, 1131, 1145, 1145, 1163, 1183, 1249, 1197, 1134, 1124, 1107, 1115, 1104, 1100, 1085, 1091, 1097, 1102, 1110, 1133, 1145, 1155, 1190, 1227, 1191, 1125, 1107, 1105, 1093, 1084, 1072, 1066, 1071, 1072, 1081, 1106, 1124, 1129, 1153, 1178, 1238, 1193, 1108, 1104, 1098, 1085, 1072, 1059, 1052, 1048, 1059, 1075, 1089, 1105, 1126, 1146, 1162, 1233, 1166, 1098, 1099, 1091, 1078, 1053, 1043, 1036, 1035, 1045, 1058, 1070, 1100, 1113, 1128, 1156, 1230, 1173, 1100, 1087, 1087, 1064, 1046, 1037, 1031, 1031, 1034, 1047, 1063, 1092, 1107, 1112, 1153, 1228, 1169, 1089, 1089, 1079, 1057, 1043, 1030, 1030, 1027, 1027, 1035, 1057, 1087, 1111, 1125, 1136, 1218, 1166, 1097, 1087, 1079, 1056, 1035, 1022, 1021, 1027, 1022, 1035, 1053, 1083, 1109, 1118, 1138, 1198, 1151, 1100, 1087, 1077, 1057, 1034, 1023, 1024, 1027, 1025, 1036, 1051, 1083, 1109, 1116, 1129, 1215, 1159, 1096, 1091, 1079, 1053, 1037, 1026, 1021, 1020, 1020, 1039, 1063, 1086, 1113, 1116, 1134, 1214, 1158, 1096, 1091, 1087, 1065, 1043, 1034, 1025, 1020, 1028, 1046, 1059, 1088, 1109, 1119, 1130, 1202, 1168, 1101, 1091, 1084, 1074, 1050, 1029, 1028, 1026, 1035, 1055, 1072, 1099, 1105, 1121, 1138, 1204, 1160, 1104, 1093, 1094, 1079, 1067, 1043, 1036, 1036, 1048, 1057, 1081, 1089, 1107, 1118, 1140, 1222, 1158, 1101, 1096, 1090, 1082, 1076, 1059, 1052, 1053, 1063, 1071, 1086, 1094, 1103, 1119, 1134, 1206, 1150, 1105, 1098, 1093, 1082, 1077, 1067, 1063, 1065, 1069, 1081, 1081, 1088, 1108, 1123, 1138, 1211, 1198, 1133, 1114, 1117, 1097, 1093, 1076, 1073, 1067, 1077, 1076, 1089, 1101, 1119, 1154, 1163, 1346, ]
+ b: [1241, 1188, 1165, 1151, 1131, 1127, 1134, 1115, 1122, 1127, 1131, 1136, 1154, 1165, 1173, 1161, 1319, 1210, 1153, 1138, 1120, 1111, 1114, 1118, 1124, 1108, 1118, 1121, 1123, 1132, 1151, 1161, 1150, 1244, 1224, 1149, 1118, 1108, 1107, 1107, 1103, 1098, 1091, 1103, 1103, 1121, 1124, 1135, 1167, 1177, 1224, 1195, 1130, 1099, 1108, 1101, 1083, 1081, 1078, 1074, 1084, 1086, 1097, 1115, 1128, 1145, 1181, 1211, 1191, 1111, 1109, 1098, 1087, 1081, 1071, 1059, 1053, 1064, 1078, 1091, 1109, 1127, 1139, 1167, 1226, 1192, 1111, 1097, 1098, 1072, 1064, 1050, 1042, 1040, 1046, 1053, 1077, 1099, 1113, 1130, 1152, 1215, 1179, 1106, 1093, 1084, 1070, 1055, 1039, 1037, 1034, 1033, 1046, 1067, 1088, 1112, 1120, 1150, 1220, 1178, 1092, 1097, 1085, 1066, 1049, 1033, 1032, 1026, 1028, 1038, 1058, 1081, 1112, 1120, 1137, 1208, 1170, 1103, 1096, 1082, 1063, 1038, 1035, 1025, 1026, 1027, 1035, 1060, 1075, 1109, 1122, 1133, 1214, 1175, 1095, 1097, 1074, 1061, 1039, 1029, 1028, 1022, 1025, 1033, 1049, 1083, 1107, 1117, 1125, 1212, 1179, 1097, 1091, 1076, 1062, 1045, 1030, 1031, 1027, 1031, 1039, 1055, 1082, 1109, 1114, 1144, 1204, 1178, 1102, 1080, 1087, 1060, 1052, 1027, 1028, 1025, 1028, 1043, 1067, 1093, 1113, 1121, 1123, 1189, 1191, 1117, 1100, 1092, 1079, 1058, 1037, 1037, 1020, 1037, 1058, 1065, 1092, 1101, 1115, 1140, 1194, 1173, 1120, 1096, 1085, 1085, 1065, 1048, 1039, 1036, 1046, 1053, 1076, 1096, 1099, 1114, 1140, 1195, 1180, 1105, 1090, 1079, 1073, 1066, 1056, 1049, 1043, 1057, 1061, 1077, 1081, 1090, 1115, 1131, 1180, 1154, 1095, 1084, 1061, 1055, 1056, 1045, 1043, 1039, 1041, 1051, 1067, 1077, 1092, 1108, 1122, 1197, 1210, 1139, 1117, 1112, 1088, 1097, 1084, 1073, 1074, 1065, 1079, 1091, 1103, 1131, 1144, 1154, 1356, ]
+ #4208x3120_D75_70 - D75
+ - ct: 7504
+ resolution: 4208x3120
+ r: [2718, 2443, 2251, 2101, 1949, 1828, 1725, 1659, 1637, 1656, 1692, 1787, 1913, 2038, 2175, 2358, 2612, 2566, 2301, 2129, 1946, 1798, 1654, 1562, 1501, 1474, 1484, 1541, 1628, 1753, 1900, 2056, 2216, 2458, 2439, 2204, 2002, 1839, 1664, 1534, 1419, 1372, 1340, 1357, 1403, 1489, 1621, 1784, 1950, 2114, 2358, 2344, 2108, 1932, 1723, 1559, 1413, 1321, 1258, 1239, 1246, 1293, 1388, 1512, 1675, 1846, 2036, 2269, 2294, 2047, 1842, 1635, 1464, 1328, 1231, 1178, 1144, 1167, 1208, 1298, 1419, 1582, 1769, 1962, 2198, 2234, 1977, 1769, 1556, 1393, 1262, 1164, 1108, 1086, 1096, 1146, 1232, 1350, 1513, 1700, 1913, 2137, 2206, 1942, 1733, 1515, 1345, 1216, 1120, 1066, 1045, 1060, 1099, 1182, 1316, 1462, 1656, 1868, 2131, 2182, 1922, 1685, 1495, 1315, 1188, 1092, 1045, 1025, 1037, 1080, 1160, 1283, 1442, 1624, 1853, 2102, 2193, 1910, 1702, 1477, 1310, 1179, 1087, 1034, 1024, 1029, 1069, 1163, 1278, 1441, 1624, 1846, 2081, 2191, 1936, 1698, 1495, 1325, 1192, 1100, 1052, 1033, 1042, 1082, 1166, 1291, 1448, 1634, 1852, 2118, 2209, 1957, 1732, 1534, 1357, 1223, 1125, 1078, 1062, 1066, 1113, 1204, 1324, 1486, 1665, 1895, 2127, 2267, 2018, 1789, 1577, 1407, 1280, 1181, 1124, 1105, 1113, 1166, 1252, 1388, 1539, 1724, 1936, 2180, 2319, 2074, 1867, 1659, 1491, 1354, 1248, 1192, 1175, 1191, 1236, 1333, 1441, 1618, 1798, 2005, 2249, 2399, 2148, 1955, 1752, 1578, 1442, 1351, 1293, 1272, 1286, 1334, 1418, 1547, 1709, 1872, 2085, 2297, 2497, 2217, 2069, 1857, 1694, 1560, 1458, 1403, 1384, 1400, 1443, 1537, 1670, 1815, 1991, 2157, 2412, 2594, 2341, 2147, 2004, 1827, 1693, 1600, 1537, 1521, 1524, 1576, 1665, 1788, 1941, 2083, 2257, 2529, 2745, 2483, 2315, 2146, 2006, 1868, 1779, 1701, 1679, 1704, 1744, 1845, 1954, 2087, 2219, 2407, 2701, ]
+ gr: [2344, 2089, 1940, 1831, 1739, 1672, 1602, 1564, 1546, 1553, 1585, 1636, 1713, 1798, 1899, 2031, 2234, 2182, 1973, 1842, 1732, 1637, 1548, 1485, 1448, 1422, 1438, 1466, 1527, 1594, 1695, 1784, 1902, 2122, 2082, 1884, 1773, 1653, 1549, 1465, 1398, 1351, 1329, 1338, 1376, 1435, 1516, 1611, 1725, 1828, 2008, 1997, 1821, 1706, 1585, 1480, 1382, 1319, 1261, 1244, 1253, 1291, 1352, 1439, 1540, 1647, 1772, 1932, 1947, 1773, 1655, 1522, 1409, 1310, 1239, 1184, 1161, 1174, 1213, 1284, 1368, 1480, 1601, 1717, 1882, 1904, 1739, 1605, 1470, 1360, 1257, 1173, 1124, 1094, 1111, 1149, 1221, 1320, 1433, 1550, 1678, 1844, 1878, 1711, 1571, 1443, 1317, 1213, 1126, 1077, 1057, 1066, 1105, 1180, 1279, 1400, 1515, 1652, 1819, 1862, 1687, 1556, 1420, 1299, 1183, 1102, 1048, 1029, 1041, 1081, 1155, 1258, 1374, 1495, 1634, 1800, 1856, 1692, 1556, 1415, 1289, 1176, 1095, 1044, 1024, 1033, 1073, 1145, 1247, 1370, 1492, 1626, 1800, 1869, 1697, 1555, 1419, 1303, 1190, 1104, 1054, 1040, 1045, 1085, 1154, 1260, 1373, 1511, 1632, 1804, 1887, 1717, 1571, 1440, 1323, 1216, 1128, 1077, 1066, 1069, 1109, 1182, 1284, 1398, 1520, 1656, 1831, 1910, 1751, 1607, 1480, 1360, 1261, 1173, 1123, 1100, 1114, 1154, 1226, 1326, 1444, 1555, 1689, 1856, 1962, 1793, 1656, 1522, 1416, 1315, 1237, 1180, 1166, 1176, 1214, 1288, 1375, 1486, 1603, 1722, 1910, 2020, 1845, 1710, 1586, 1477, 1387, 1307, 1266, 1241, 1257, 1292, 1347, 1446, 1548, 1657, 1785, 1964, 2118, 1888, 1794, 1658, 1552, 1462, 1394, 1349, 1332, 1342, 1378, 1436, 1525, 1617, 1736, 1848, 2048, 2195, 1989, 1855, 1742, 1633, 1555, 1487, 1437, 1427, 1429, 1471, 1521, 1603, 1699, 1804, 1921, 2149, 2334, 2103, 1971, 1863, 1757, 1666, 1598, 1565, 1537, 1554, 1579, 1640, 1716, 1810, 1923, 2044, 2308, ]
+ gb: [2383, 2122, 1974, 1866, 1767, 1684, 1620, 1581, 1559, 1575, 1592, 1654, 1726, 1816, 1917, 2071, 2294, 2242, 2002, 1872, 1752, 1650, 1564, 1499, 1455, 1438, 1442, 1485, 1537, 1614, 1715, 1814, 1935, 2155, 2114, 1929, 1797, 1674, 1568, 1477, 1406, 1358, 1340, 1348, 1386, 1447, 1534, 1631, 1754, 1861, 2057, 2044, 1859, 1737, 1606, 1493, 1396, 1322, 1270, 1247, 1259, 1305, 1370, 1455, 1566, 1679, 1808, 1979, 1981, 1812, 1674, 1549, 1424, 1325, 1246, 1191, 1168, 1179, 1222, 1294, 1383, 1498, 1623, 1748, 1932, 1939, 1777, 1626, 1500, 1376, 1265, 1179, 1128, 1104, 1119, 1160, 1235, 1331, 1447, 1577, 1708, 1885, 1922, 1735, 1602, 1464, 1333, 1226, 1134, 1083, 1061, 1071, 1113, 1191, 1296, 1412, 1543, 1677, 1849, 1885, 1723, 1574, 1437, 1310, 1191, 1105, 1055, 1035, 1048, 1088, 1164, 1272, 1388, 1516, 1660, 1847, 1891, 1714, 1568, 1431, 1300, 1185, 1099, 1047, 1024, 1038, 1075, 1155, 1259, 1386, 1512, 1649, 1832, 1901, 1722, 1575, 1434, 1309, 1196, 1109, 1054, 1041, 1047, 1087, 1162, 1267, 1385, 1526, 1650, 1833, 1912, 1740, 1588, 1456, 1329, 1220, 1133, 1080, 1065, 1072, 1113, 1189, 1289, 1410, 1538, 1672, 1862, 1949, 1767, 1632, 1487, 1367, 1261, 1175, 1123, 1100, 1114, 1158, 1224, 1331, 1450, 1571, 1705, 1880, 1990, 1811, 1670, 1531, 1420, 1315, 1227, 1180, 1158, 1172, 1212, 1285, 1375, 1490, 1611, 1744, 1925, 2033, 1864, 1715, 1588, 1477, 1377, 1307, 1253, 1232, 1248, 1285, 1344, 1439, 1545, 1661, 1797, 1971, 2126, 1898, 1798, 1658, 1548, 1449, 1381, 1338, 1315, 1329, 1366, 1428, 1512, 1617, 1730, 1853, 2058, 2203, 1998, 1856, 1734, 1624, 1539, 1467, 1424, 1409, 1409, 1448, 1505, 1584, 1689, 1796, 1923, 2148, 2342, 2110, 1959, 1848, 1740, 1635, 1572, 1533, 1519, 1527, 1561, 1610, 1693, 1786, 1900, 2039, 2306, ]
+ b: [2199, 1976, 1828, 1725, 1640, 1549, 1510, 1473, 1457, 1462, 1485, 1529, 1603, 1690, 1796, 1922, 2111, 2048, 1861, 1735, 1618, 1532, 1462, 1400, 1360, 1346, 1355, 1384, 1433, 1501, 1589, 1680, 1793, 1982, 1975, 1801, 1672, 1564, 1465, 1387, 1326, 1294, 1272, 1284, 1310, 1363, 1440, 1518, 1627, 1730, 1888, 1903, 1736, 1617, 1500, 1405, 1325, 1260, 1219, 1198, 1208, 1239, 1296, 1365, 1465, 1557, 1664, 1833, 1837, 1684, 1556, 1449, 1345, 1261, 1200, 1151, 1132, 1137, 1175, 1238, 1307, 1402, 1517, 1627, 1775, 1806, 1650, 1518, 1407, 1306, 1216, 1144, 1099, 1078, 1092, 1120, 1185, 1270, 1360, 1472, 1596, 1740, 1778, 1621, 1499, 1381, 1270, 1180, 1110, 1066, 1046, 1057, 1087, 1150, 1236, 1335, 1447, 1560, 1703, 1764, 1612, 1479, 1367, 1255, 1158, 1089, 1045, 1031, 1038, 1071, 1128, 1218, 1312, 1430, 1544, 1702, 1773, 1604, 1480, 1359, 1252, 1148, 1082, 1041, 1024, 1036, 1061, 1124, 1210, 1314, 1432, 1542, 1693, 1782, 1617, 1485, 1366, 1253, 1162, 1092, 1046, 1038, 1043, 1068, 1130, 1215, 1322, 1431, 1549, 1700, 1786, 1634, 1499, 1378, 1276, 1184, 1108, 1067, 1060, 1062, 1094, 1153, 1235, 1346, 1450, 1556, 1722, 1813, 1667, 1535, 1411, 1306, 1220, 1148, 1103, 1089, 1091, 1132, 1189, 1277, 1372, 1474, 1593, 1740, 1852, 1712, 1569, 1449, 1354, 1263, 1195, 1156, 1137, 1149, 1180, 1239, 1319, 1413, 1516, 1627, 1798, 1910, 1741, 1617, 1509, 1403, 1324, 1267, 1221, 1205, 1213, 1244, 1296, 1377, 1459, 1565, 1679, 1826, 1984, 1788, 1696, 1556, 1473, 1386, 1333, 1296, 1280, 1282, 1316, 1361, 1442, 1519, 1624, 1732, 1905, 2059, 1881, 1746, 1642, 1533, 1467, 1400, 1370, 1354, 1357, 1389, 1438, 1500, 1587, 1688, 1800, 1995, 2190, 1971, 1845, 1743, 1643, 1562, 1515, 1468, 1453, 1454, 1501, 1532, 1608, 1692, 1782, 1904, 2117, ]
+ #4208x3120_F11_TL84_70 - F11_TL84
+ - ct: 4000
+ resolution: 4208x3120
+ r: [1286, 1278, 1265, 1240, 1240, 1217, 1199, 1205, 1185, 1191, 1213, 1243, 1251, 1276, 1282, 1297, 1358, 1273, 1227, 1225, 1219, 1199, 1190, 1164, 1151, 1137, 1151, 1174, 1213, 1238, 1237, 1261, 1274, 1331, 1273, 1220, 1214, 1199, 1174, 1154, 1126, 1115, 1105, 1106, 1132, 1183, 1215, 1238, 1260, 1277, 1310, 1254, 1204, 1204, 1193, 1151, 1097, 1081, 1066, 1057, 1066, 1094, 1133, 1183, 1228, 1240, 1275, 1341, 1239, 1196, 1193, 1167, 1112, 1071, 1046, 1035, 1034, 1045, 1056, 1097, 1153, 1210, 1232, 1257, 1313, 1240, 1187, 1195, 1142, 1080, 1048, 1031, 1023, 1025, 1026, 1034, 1065, 1115, 1186, 1223, 1254, 1322, 1241, 1178, 1166, 1121, 1060, 1031, 1014, 1029, 1039, 1026, 1032, 1057, 1101, 1162, 1210, 1247, 1295, 1224, 1178, 1157, 1104, 1049, 1021, 1015, 1036, 1044, 1036, 1024, 1049, 1097, 1144, 1206, 1235, 1312, 1215, 1170, 1153, 1098, 1046, 1020, 1017, 1043, 1046, 1036, 1028, 1039, 1086, 1144, 1202, 1234, 1280, 1224, 1178, 1148, 1093, 1049, 1010, 1011, 1032, 1038, 1030, 1024, 1042, 1094, 1153, 1213, 1231, 1294, 1237, 1185, 1157, 1104, 1050, 1017, 1005, 1029, 1030, 1022, 1027, 1048, 1098, 1172, 1213, 1243, 1300, 1244, 1173, 1168, 1122, 1073, 1021, 1011, 1004, 1007, 1015, 1029, 1062, 1115, 1176, 1219, 1227, 1304, 1243, 1192, 1182, 1148, 1093, 1048, 1014, 1004, 1007, 1019, 1039, 1068, 1132, 1187, 1214, 1237, 1290, 1233, 1197, 1186, 1170, 1130, 1068, 1043, 1021, 1024, 1035, 1063, 1100, 1148, 1200, 1218, 1239, 1280, 1225, 1193, 1182, 1178, 1152, 1113, 1082, 1057, 1055, 1069, 1098, 1133, 1184, 1199, 1214, 1224, 1291, 1224, 1180, 1184, 1176, 1165, 1145, 1105, 1093, 1081, 1091, 1128, 1167, 1185, 1197, 1202, 1207, 1268, 1216, 1185, 1208, 1194, 1182, 1156, 1131, 1104, 1097, 1110, 1150, 1176, 1214, 1220, 1219, 1234, 1375, ]
+ gr: [1267, 1211, 1186, 1180, 1181, 1169, 1162, 1152, 1144, 1152, 1159, 1184, 1192, 1196, 1221, 1236, 1372, 1236, 1175, 1159, 1149, 1143, 1142, 1134, 1123, 1120, 1130, 1134, 1154, 1170, 1190, 1202, 1212, 1256, 1214, 1170, 1139, 1139, 1125, 1116, 1120, 1100, 1097, 1106, 1111, 1131, 1160, 1173, 1191, 1203, 1266, 1206, 1150, 1137, 1128, 1111, 1095, 1087, 1073, 1069, 1077, 1097, 1116, 1137, 1160, 1182, 1204, 1252, 1187, 1142, 1137, 1122, 1098, 1068, 1065, 1046, 1052, 1054, 1069, 1093, 1121, 1147, 1174, 1200, 1253, 1176, 1136, 1125, 1111, 1080, 1061, 1044, 1042, 1032, 1041, 1055, 1072, 1106, 1139, 1157, 1186, 1246, 1182, 1120, 1109, 1092, 1067, 1042, 1037, 1033, 1028, 1031, 1043, 1058, 1094, 1130, 1156, 1179, 1240, 1162, 1120, 1110, 1088, 1054, 1032, 1030, 1027, 1027, 1025, 1035, 1050, 1091, 1121, 1149, 1186, 1226, 1152, 1122, 1108, 1092, 1054, 1031, 1024, 1026, 1029, 1021, 1037, 1055, 1085, 1113, 1144, 1178, 1217, 1168, 1113, 1102, 1084, 1053, 1032, 1025, 1024, 1027, 1027, 1032, 1048, 1083, 1123, 1142, 1168, 1226, 1163, 1116, 1111, 1086, 1060, 1033, 1023, 1023, 1025, 1028, 1035, 1062, 1090, 1124, 1140, 1164, 1216, 1179, 1124, 1107, 1100, 1072, 1043, 1024, 1024, 1020, 1029, 1044, 1067, 1106, 1128, 1143, 1163, 1219, 1179, 1127, 1117, 1105, 1086, 1053, 1034, 1029, 1029, 1034, 1054, 1076, 1102, 1125, 1157, 1179, 1231, 1165, 1137, 1120, 1112, 1100, 1069, 1051, 1038, 1038, 1052, 1068, 1097, 1109, 1132, 1146, 1166, 1233, 1187, 1128, 1122, 1111, 1107, 1083, 1073, 1057, 1060, 1076, 1083, 1105, 1114, 1134, 1139, 1170, 1243, 1174, 1126, 1115, 1111, 1097, 1093, 1072, 1073, 1067, 1077, 1095, 1104, 1120, 1139, 1135, 1169, 1256, 1232, 1141, 1148, 1125, 1122, 1123, 1104, 1096, 1093, 1094, 1117, 1137, 1146, 1153, 1158, 1160, 1389, ]
+ gb: [1264, 1211, 1190, 1175, 1162, 1153, 1144, 1142, 1132, 1132, 1149, 1168, 1193, 1211, 1221, 1230, 1377, 1240, 1176, 1162, 1152, 1140, 1139, 1131, 1120, 1120, 1122, 1142, 1155, 1163, 1191, 1203, 1210, 1274, 1240, 1171, 1153, 1142, 1131, 1118, 1104, 1091, 1099, 1099, 1111, 1133, 1156, 1172, 1192, 1213, 1273, 1222, 1157, 1140, 1134, 1117, 1092, 1075, 1069, 1067, 1080, 1091, 1115, 1136, 1167, 1180, 1211, 1272, 1226, 1153, 1134, 1124, 1102, 1079, 1063, 1048, 1050, 1055, 1072, 1097, 1123, 1158, 1180, 1201, 1273, 1199, 1142, 1131, 1117, 1088, 1059, 1042, 1035, 1034, 1037, 1057, 1078, 1116, 1145, 1161, 1193, 1256, 1211, 1141, 1116, 1106, 1074, 1049, 1035, 1031, 1033, 1033, 1045, 1073, 1104, 1136, 1153, 1188, 1250, 1196, 1128, 1114, 1100, 1060, 1039, 1030, 1034, 1032, 1030, 1030, 1057, 1094, 1125, 1155, 1169, 1257, 1204, 1126, 1114, 1100, 1063, 1037, 1022, 1024, 1032, 1034, 1036, 1060, 1094, 1125, 1148, 1172, 1242, 1188, 1123, 1116, 1093, 1060, 1035, 1025, 1024, 1027, 1027, 1034, 1057, 1090, 1134, 1146, 1172, 1239, 1192, 1122, 1119, 1095, 1069, 1040, 1021, 1026, 1016, 1030, 1038, 1065, 1094, 1136, 1148, 1173, 1244, 1202, 1132, 1117, 1104, 1068, 1043, 1034, 1020, 1019, 1025, 1042, 1072, 1102, 1136, 1152, 1167, 1237, 1191, 1136, 1120, 1108, 1087, 1053, 1034, 1025, 1020, 1032, 1050, 1073, 1110, 1130, 1148, 1182, 1238, 1201, 1133, 1117, 1120, 1100, 1071, 1049, 1038, 1032, 1048, 1064, 1090, 1117, 1134, 1152, 1170, 1237, 1188, 1128, 1128, 1115, 1106, 1090, 1067, 1058, 1058, 1066, 1082, 1107, 1115, 1135, 1148, 1171, 1250, 1187, 1138, 1126, 1119, 1108, 1095, 1078, 1075, 1066, 1079, 1090, 1099, 1121, 1143, 1149, 1165, 1237, 1229, 1158, 1157, 1139, 1119, 1118, 1101, 1078, 1084, 1091, 1103, 1125, 1130, 1149, 1173, 1184, 1398, ]
+ b: [1291, 1208, 1168, 1145, 1132, 1140, 1122, 1134, 1138, 1129, 1131, 1140, 1161, 1197, 1196, 1179, 1329, 1235, 1176, 1150, 1125, 1118, 1113, 1115, 1113, 1108, 1113, 1115, 1131, 1136, 1149, 1181, 1176, 1255, 1237, 1147, 1129, 1116, 1119, 1106, 1104, 1091, 1086, 1099, 1104, 1119, 1137, 1134, 1164, 1179, 1231, 1204, 1137, 1111, 1113, 1103, 1096, 1079, 1070, 1070, 1074, 1090, 1104, 1120, 1126, 1149, 1183, 1234, 1208, 1123, 1112, 1118, 1097, 1075, 1066, 1055, 1051, 1059, 1066, 1090, 1114, 1127, 1135, 1157, 1226, 1197, 1110, 1109, 1095, 1083, 1055, 1047, 1044, 1040, 1044, 1051, 1063, 1095, 1112, 1132, 1148, 1232, 1198, 1107, 1098, 1081, 1063, 1051, 1043, 1036, 1033, 1033, 1043, 1061, 1082, 1109, 1116, 1144, 1209, 1161, 1095, 1096, 1091, 1054, 1042, 1039, 1035, 1035, 1022, 1042, 1053, 1080, 1107, 1122, 1132, 1216, 1169, 1097, 1094, 1081, 1048, 1041, 1024, 1034, 1034, 1031, 1034, 1058, 1074, 1105, 1124, 1124, 1218, 1188, 1095, 1092, 1079, 1054, 1042, 1032, 1035, 1022, 1025, 1035, 1053, 1080, 1107, 1118, 1132, 1228, 1181, 1093, 1094, 1077, 1059, 1043, 1030, 1030, 1023, 1033, 1036, 1058, 1090, 1109, 1111, 1135, 1209, 1191, 1105, 1096, 1087, 1060, 1044, 1034, 1034, 1020, 1034, 1037, 1063, 1087, 1112, 1123, 1138, 1226, 1203, 1118, 1090, 1097, 1081, 1052, 1041, 1027, 1030, 1034, 1048, 1067, 1093, 1110, 1121, 1142, 1220, 1210, 1127, 1102, 1091, 1087, 1061, 1052, 1024, 1044, 1041, 1056, 1076, 1091, 1113, 1125, 1152, 1216, 1194, 1107, 1106, 1077, 1085, 1074, 1060, 1048, 1041, 1048, 1060, 1082, 1085, 1085, 1125, 1132, 1218, 1190, 1112, 1074, 1071, 1066, 1067, 1050, 1045, 1045, 1045, 1061, 1075, 1070, 1088, 1106, 1128, 1222, 1234, 1145, 1131, 1120, 1099, 1095, 1079, 1078, 1073, 1078, 1083, 1086, 1108, 1125, 1141, 1156, 1386, ]
+ #4208x3120_F2_CWF_70 - F2_CWF
+ - ct: 4230
+ resolution: 4208x3120
+ r: [1140, 1119, 1106, 1105, 1086, 1079, 1072, 1070, 1070, 1079, 1084, 1102, 1114, 1131, 1157, 1152, 1232, 1131, 1103, 1088, 1084, 1071, 1074, 1077, 1066, 1064, 1063, 1080, 1094, 1101, 1112, 1113, 1134, 1194, 1143, 1073, 1077, 1078, 1069, 1067, 1058, 1060, 1046, 1048, 1067, 1085, 1095, 1101, 1127, 1144, 1169, 1132, 1072, 1074, 1078, 1055, 1045, 1037, 1033, 1039, 1036, 1045, 1068, 1085, 1098, 1122, 1115, 1183, 1106, 1064, 1069, 1068, 1049, 1026, 1030, 1019, 1025, 1026, 1038, 1051, 1070, 1100, 1102, 1120, 1174, 1103, 1043, 1052, 1055, 1024, 1023, 1017, 1019, 1025, 1024, 1032, 1037, 1063, 1085, 1094, 1110, 1195, 1095, 1047, 1062, 1041, 1025, 1017, 1011, 1031, 1027, 1023, 1023, 1030, 1050, 1071, 1084, 1110, 1190, 1073, 1034, 1056, 1042, 1015, 1010, 1016, 1032, 1027, 1024, 1024, 1036, 1039, 1074, 1087, 1109, 1168, 1079, 1042, 1055, 1032, 1019, 1007, 1013, 1026, 1027, 1026, 1021, 1032, 1044, 1082, 1093, 1098, 1158, 1091, 1046, 1053, 1028, 1020, 1007, 1011, 1026, 1022, 1019, 1021, 1020, 1045, 1071, 1084, 1096, 1159, 1114, 1047, 1047, 1030, 1017, 997, 1008, 1016, 1019, 1021, 1016, 1028, 1053, 1080, 1094, 1103, 1157, 1088, 1049, 1052, 1040, 1024, 1003, 1001, 1004, 1010, 1006, 1019, 1037, 1057, 1085, 1084, 1099, 1161, 1106, 1057, 1063, 1056, 1032, 1010, 993, 998, 999, 1006, 1016, 1031, 1052, 1071, 1089, 1106, 1174, 1112, 1055, 1054, 1062, 1043, 1022, 1002, 1004, 1008, 1007, 1015, 1045, 1064, 1085, 1087, 1097, 1157, 1102, 1059, 1064, 1059, 1054, 1035, 1018, 1002, 1005, 1012, 1035, 1052, 1057, 1068, 1071, 1098, 1156, 1098, 1045, 1044, 1042, 1046, 1041, 1024, 1009, 1004, 1017, 1035, 1062, 1062, 1064, 1064, 1088, 1140, 1088, 1043, 1070, 1066, 1041, 1047, 1026, 1014, 1009, 1022, 1032, 1060, 1073, 1077, 1087, 1107, 1237, ]
+ gr: [1219, 1156, 1145, 1130, 1128, 1112, 1116, 1104, 1112, 1106, 1118, 1128, 1154, 1165, 1161, 1170, 1306, 1183, 1124, 1113, 1099, 1100, 1099, 1091, 1084, 1095, 1090, 1099, 1116, 1126, 1140, 1142, 1158, 1213, 1174, 1112, 1103, 1094, 1084, 1087, 1090, 1075, 1075, 1077, 1088, 1101, 1119, 1133, 1149, 1162, 1193, 1149, 1106, 1091, 1086, 1076, 1071, 1066, 1057, 1064, 1064, 1074, 1082, 1109, 1117, 1140, 1151, 1204, 1155, 1094, 1089, 1088, 1075, 1059, 1052, 1046, 1043, 1048, 1061, 1074, 1101, 1113, 1123, 1154, 1198, 1137, 1093, 1082, 1078, 1059, 1048, 1041, 1033, 1030, 1038, 1048, 1059, 1078, 1109, 1116, 1143, 1198, 1119, 1082, 1074, 1071, 1051, 1040, 1036, 1032, 1031, 1031, 1042, 1047, 1077, 1097, 1112, 1133, 1185, 1126, 1082, 1077, 1058, 1039, 1029, 1025, 1024, 1024, 1022, 1033, 1044, 1068, 1095, 1099, 1131, 1187, 1123, 1078, 1071, 1060, 1043, 1028, 1025, 1027, 1027, 1021, 1033, 1045, 1066, 1087, 1105, 1121, 1173, 1121, 1070, 1067, 1058, 1039, 1024, 1020, 1024, 1024, 1022, 1030, 1043, 1064, 1093, 1099, 1121, 1182, 1112, 1076, 1072, 1065, 1044, 1029, 1021, 1023, 1021, 1026, 1032, 1047, 1066, 1091, 1105, 1131, 1180, 1132, 1076, 1066, 1067, 1052, 1031, 1021, 1021, 1020, 1028, 1039, 1044, 1076, 1098, 1107, 1127, 1179, 1124, 1087, 1076, 1076, 1064, 1036, 1018, 1018, 1020, 1028, 1041, 1056, 1085, 1086, 1106, 1128, 1187, 1126, 1099, 1082, 1072, 1065, 1043, 1031, 1024, 1029, 1034, 1052, 1065, 1074, 1094, 1111, 1127, 1181, 1128, 1086, 1076, 1073, 1072, 1058, 1050, 1046, 1039, 1048, 1059, 1074, 1070, 1096, 1112, 1124, 1174, 1140, 1078, 1077, 1067, 1057, 1055, 1043, 1040, 1042, 1042, 1054, 1069, 1075, 1088, 1099, 1112, 1189, 1182, 1099, 1096, 1093, 1082, 1080, 1072, 1055, 1059, 1061, 1076, 1095, 1090, 1112, 1113, 1140, 1321, ]
+ gb: [1236, 1163, 1136, 1120, 1113, 1111, 1109, 1101, 1104, 1099, 1102, 1140, 1141, 1158, 1170, 1194, 1332, 1195, 1138, 1114, 1109, 1097, 1098, 1092, 1089, 1085, 1089, 1098, 1117, 1125, 1141, 1155, 1156, 1232, 1186, 1125, 1108, 1095, 1099, 1081, 1078, 1075, 1073, 1073, 1083, 1097, 1118, 1128, 1148, 1166, 1218, 1171, 1107, 1099, 1091, 1086, 1069, 1059, 1051, 1049, 1064, 1071, 1088, 1110, 1118, 1137, 1162, 1225, 1171, 1099, 1092, 1085, 1069, 1057, 1051, 1041, 1036, 1050, 1055, 1077, 1092, 1118, 1133, 1151, 1227, 1158, 1099, 1090, 1086, 1061, 1043, 1039, 1028, 1036, 1039, 1048, 1060, 1091, 1110, 1117, 1147, 1216, 1152, 1086, 1082, 1073, 1054, 1040, 1026, 1028, 1029, 1032, 1040, 1051, 1076, 1104, 1115, 1139, 1222, 1141, 1088, 1078, 1073, 1048, 1034, 1026, 1025, 1025, 1022, 1033, 1051, 1077, 1104, 1115, 1129, 1202, 1154, 1081, 1080, 1069, 1050, 1029, 1023, 1022, 1029, 1027, 1031, 1050, 1070, 1098, 1107, 1127, 1188, 1146, 1090, 1078, 1065, 1044, 1029, 1015, 1022, 1024, 1025, 1035, 1053, 1071, 1104, 1102, 1136, 1207, 1152, 1083, 1078, 1073, 1042, 1027, 1024, 1024, 1016, 1024, 1037, 1056, 1076, 1106, 1111, 1130, 1197, 1146, 1086, 1076, 1074, 1046, 1031, 1023, 1018, 1021, 1026, 1043, 1051, 1081, 1102, 1111, 1126, 1191, 1134, 1090, 1084, 1079, 1067, 1038, 1019, 1018, 1021, 1033, 1041, 1055, 1081, 1099, 1107, 1131, 1199, 1147, 1091, 1082, 1083, 1072, 1050, 1031, 1024, 1027, 1032, 1053, 1063, 1082, 1099, 1107, 1130, 1191, 1139, 1087, 1078, 1077, 1073, 1058, 1048, 1037, 1037, 1046, 1062, 1073, 1079, 1099, 1099, 1130, 1177, 1147, 1082, 1087, 1074, 1061, 1062, 1052, 1042, 1036, 1045, 1063, 1068, 1079, 1094, 1103, 1120, 1189, 1176, 1105, 1102, 1092, 1081, 1073, 1064, 1053, 1053, 1066, 1067, 1084, 1087, 1103, 1134, 1146, 1336, ]
+ b: [1203, 1195, 1154, 1123, 1104, 1106, 1116, 1099, 1099, 1099, 1102, 1106, 1123, 1155, 1149, 1168, 1283, 1196, 1141, 1119, 1102, 1098, 1088, 1088, 1095, 1086, 1095, 1097, 1101, 1117, 1121, 1156, 1135, 1209, 1211, 1127, 1102, 1082, 1089, 1088, 1072, 1075, 1083, 1083, 1085, 1106, 1107, 1120, 1142, 1149, 1224, 1163, 1121, 1087, 1078, 1085, 1077, 1062, 1065, 1056, 1057, 1082, 1093, 1094, 1096, 1111, 1147, 1193, 1179, 1105, 1083, 1088, 1070, 1074, 1060, 1048, 1055, 1044, 1068, 1082, 1091, 1097, 1102, 1141, 1209, 1178, 1091, 1076, 1077, 1063, 1060, 1043, 1043, 1035, 1046, 1059, 1064, 1084, 1103, 1107, 1125, 1196, 1156, 1088, 1068, 1070, 1057, 1043, 1046, 1041, 1038, 1038, 1046, 1059, 1073, 1083, 1086, 1111, 1178, 1146, 1067, 1083, 1068, 1044, 1042, 1033, 1044, 1033, 1026, 1037, 1045, 1067, 1089, 1092, 1108, 1203, 1148, 1082, 1072, 1066, 1050, 1044, 1035, 1035, 1031, 1028, 1035, 1055, 1069, 1082, 1094, 1101, 1188, 1163, 1067, 1074, 1056, 1040, 1034, 1037, 1026, 1022, 1033, 1037, 1049, 1067, 1084, 1092, 1103, 1185, 1156, 1074, 1073, 1066, 1042, 1036, 1028, 1031, 1030, 1034, 1042, 1051, 1073, 1091, 1090, 1102, 1196, 1172, 1086, 1071, 1077, 1055, 1041, 1036, 1025, 1024, 1028, 1032, 1053, 1076, 1094, 1089, 1101, 1178, 1179, 1095, 1079, 1075, 1070, 1043, 1026, 1022, 1022, 1029, 1045, 1054, 1078, 1075, 1092, 1120, 1179, 1193, 1091, 1074, 1061, 1064, 1056, 1043, 1034, 1026, 1027, 1039, 1060, 1081, 1070, 1078, 1115, 1205, 1172, 1096, 1069, 1060, 1071, 1055, 1044, 1035, 1027, 1043, 1048, 1063, 1054, 1065, 1083, 1122, 1186, 1158, 1088, 1060, 1043, 1037, 1037, 1031, 1033, 1025, 1029, 1035, 1041, 1041, 1060, 1084, 1114, 1202, 1217, 1122, 1101, 1079, 1058, 1061, 1049, 1056, 1051, 1036, 1062, 1061, 1076, 1094, 1116, 1139, 1331, ]
+
diff --git a/src/ipa/rkisp1/data/meson.build b/src/ipa/rkisp1/data/meson.build
index c3b4e388..7150e155 100644
--- a/src/ipa/rkisp1/data/meson.build
+++ b/src/ipa/rkisp1/data/meson.build
@@ -2,9 +2,11 @@
conf_files = files([
'imx219.yaml',
+ 'ov4689.yaml',
'ov5640.yaml',
'uncalibrated.yaml',
])
install_data(conf_files,
- install_dir : ipa_data_dir / 'rkisp1')
+ install_dir : ipa_data_dir / 'rkisp1',
+ install_tag : 'runtime')
diff --git a/src/ipa/rkisp1/data/ov2685.yaml b/src/ipa/rkisp1/data/ov2685.yaml
new file mode 100644
index 00000000..fdfc98d3
--- /dev/null
+++ b/src/ipa/rkisp1/data/ov2685.yaml
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+ - Agc:
+ - Awb:
+ - LensShadingCorrection:
+ x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ sets:
+ #800x600_A_70 - A
+ - ct: 2856
+ resolution: 800x600
+ r: [2451, 2258, 2111, 2039, 1982, 1925, 1860, 1818, 1802, 1815, 1859, 1936, 1997, 2056, 2129, 2298, 2486, 2351, 2157, 2066, 1991, 1912, 1809, 1720, 1677, 1653, 1671, 1739, 1843, 1932, 2009, 2071, 2182, 2392, 2253, 2105, 2018, 1929, 1802, 1670, 1566, 1503, 1475, 1508, 1590, 1705, 1848, 1947, 2026, 2118, 2281, 2174, 2065, 1975, 1854, 1687, 1529, 1412, 1345, 1327, 1358, 1445, 1572, 1733, 1870, 1992, 2075, 2202, 2125, 2033, 1929, 1765, 1574, 1407, 1286, 1220, 1204, 1237, 1318, 1447, 1632, 1801, 1951, 2048, 2142, 2092, 2010, 1877, 1688, 1471, 1304, 1187, 1127, 1118, 1149, 1221, 1348, 1533, 1738, 1918, 2021, 2105, 2088, 1982, 1836, 1628, 1398, 1239, 1128, 1073, 1060, 1086, 1163, 1280, 1466, 1688, 1886, 2001, 2092, 2067, 1965, 1809, 1584, 1358, 1200, 1094, 1044, 1030, 1056, 1123, 1240, 1424, 1649, 1860, 1989, 2082, 2057, 1960, 1795, 1569, 1345, 1187, 1083, 1034, 1024, 1046, 1111, 1229, 1408, 1637, 1850, 1989, 2085, 2053, 1967, 1802, 1578, 1358, 1199, 1095, 1046, 1031, 1058, 1122, 1245, 1423, 1651, 1867, 1989, 2084, 2059, 1970, 1823, 1615, 1399, 1235, 1129, 1074, 1061, 1090, 1161, 1281, 1461, 1689, 1878, 2006, 2096, 2086, 1989, 1866, 1670, 1471, 1302, 1188, 1134, 1117, 1150, 1223, 1352, 1537, 1745, 1909, 2028, 2114, 2101, 2006, 1916, 1749, 1567, 1399, 1278, 1218, 1206, 1237, 1317, 1456, 1633, 1813, 1954, 2053, 2142, 2171, 2023, 1954, 1843, 1680, 1526, 1403, 1339, 1323, 1357, 1440, 1575, 1733, 1885, 1996, 2069, 2212, 2231, 2074, 1990, 1916, 1792, 1656, 1554, 1489, 1473, 1513, 1588, 1702, 1840, 1946, 2011, 2124, 2283, 2343, 2146, 2036, 1973, 1890, 1789, 1700, 1653, 1645, 1678, 1733, 1828, 1922, 1978, 2065, 2181, 2405, 2420, 2246, 2092, 2015, 1954, 1885, 1816, 1776, 1777, 1791, 1847, 1904, 1941, 2016, 2105, 2284, 2463, ]
+ gr: [1790, 1645, 1522, 1469, 1433, 1419, 1390, 1381, 1374, 1381, 1401, 1428, 1460, 1494, 1552, 1693, 1839, 1687, 1555, 1471, 1433, 1408, 1362, 1335, 1319, 1308, 1318, 1344, 1393, 1430, 1456, 1497, 1591, 1752, 1612, 1503, 1447, 1417, 1365, 1315, 1276, 1248, 1237, 1252, 1290, 1339, 1404, 1435, 1469, 1539, 1661, 1547, 1470, 1424, 1389, 1321, 1260, 1205, 1173, 1165, 1181, 1221, 1286, 1358, 1409, 1452, 1503, 1603, 1504, 1451, 1411, 1358, 1276, 1198, 1148, 1114, 1110, 1124, 1164, 1228, 1320, 1388, 1435, 1479, 1552, 1475, 1437, 1392, 1325, 1231, 1153, 1094, 1069, 1068, 1084, 1119, 1182, 1278, 1365, 1429, 1469, 1529, 1464, 1430, 1375, 1301, 1196, 1118, 1067, 1043, 1039, 1051, 1089, 1150, 1245, 1342, 1417, 1453, 1512, 1461, 1418, 1369, 1281, 1177, 1099, 1051, 1028, 1029, 1037, 1069, 1129, 1224, 1328, 1404, 1449, 1503, 1455, 1422, 1366, 1276, 1170, 1094, 1046, 1026, 1024, 1033, 1063, 1125, 1216, 1322, 1400, 1448, 1508, 1459, 1426, 1368, 1280, 1179, 1102, 1051, 1030, 1029, 1039, 1071, 1132, 1222, 1327, 1406, 1448, 1502, 1473, 1433, 1380, 1302, 1201, 1125, 1069, 1046, 1043, 1055, 1091, 1153, 1245, 1343, 1412, 1461, 1523, 1488, 1445, 1397, 1328, 1242, 1157, 1104, 1079, 1073, 1088, 1127, 1193, 1284, 1373, 1424, 1473, 1543, 1521, 1461, 1424, 1361, 1289, 1210, 1152, 1124, 1118, 1134, 1174, 1242, 1330, 1396, 1439, 1494, 1572, 1573, 1475, 1434, 1397, 1336, 1270, 1213, 1182, 1176, 1194, 1239, 1301, 1366, 1420, 1464, 1510, 1624, 1628, 1510, 1449, 1424, 1378, 1326, 1281, 1252, 1243, 1264, 1304, 1352, 1406, 1443, 1456, 1554, 1692, 1727, 1578, 1482, 1448, 1415, 1374, 1337, 1318, 1317, 1338, 1356, 1398, 1429, 1443, 1501, 1603, 1783, 1776, 1643, 1510, 1448, 1415, 1387, 1353, 1344, 1343, 1348, 1368, 1396, 1407, 1442, 1515, 1674, 1832, ]
+ gb: [1805, 1650, 1529, 1468, 1430, 1412, 1378, 1371, 1363, 1371, 1393, 1430, 1465, 1501, 1567, 1713, 1864, 1700, 1564, 1476, 1434, 1404, 1359, 1323, 1306, 1294, 1306, 1338, 1388, 1432, 1462, 1509, 1605, 1780, 1627, 1520, 1457, 1423, 1370, 1311, 1267, 1238, 1226, 1245, 1286, 1344, 1414, 1448, 1489, 1563, 1697, 1568, 1487, 1436, 1398, 1325, 1257, 1200, 1163, 1156, 1175, 1221, 1291, 1372, 1427, 1476, 1528, 1636, 1527, 1474, 1431, 1371, 1285, 1201, 1144, 1109, 1104, 1121, 1165, 1239, 1335, 1411, 1461, 1509, 1588, 1498, 1463, 1413, 1343, 1242, 1159, 1094, 1066, 1064, 1083, 1124, 1195, 1299, 1391, 1455, 1499, 1561, 1492, 1454, 1401, 1319, 1209, 1124, 1068, 1042, 1039, 1053, 1096, 1164, 1268, 1370, 1446, 1486, 1547, 1486, 1446, 1392, 1302, 1190, 1108, 1053, 1028, 1029, 1040, 1078, 1146, 1245, 1355, 1437, 1600, 1546, 1600, 1449, 1389, 1294, 1184, 1101, 1047, 1024, 1024, 1035, 1073, 1136, 1240, 1348, 1431, 1483, 1537, 1485, 1450, 1390, 1298, 1188, 1109, 1051, 1030, 1026, 1038, 1077, 1143, 1243, 1354, 1436, 1482, 1547, 1494, 1454, 1400, 1317, 1211, 1125, 1067, 1041, 1038, 1053, 1094, 1165, 1264, 1368, 1440, 1489, 1557, 1513, 1464, 1414, 1340, 1245, 1156, 1097, 1071, 1063, 1081, 1126, 1197, 1298, 1394, 1446, 1502, 1573, 1541, 1477, 1438, 1370, 1292, 1204, 1142, 1111, 1106, 1121, 1169, 1245, 1338, 1411, 1462, 1519, 1599, 1590, 1485, 1447, 1403, 1334, 1263, 1199, 1164, 1158, 1179, 1230, 1299, 1373, 1433, 1477, 1528, 1649, 1643, 1520, 1454, 1426, 1375, 1315, 1266, 1235, 1224, 1247, 1291, 1345, 1408, 1449, 1468, 1572, 1711, 1738, 1579, 1482, 1443, 1406, 1359, 1318, 1294, 1294, 1312, 1338, 1385, 1427, 1441, 1507, 1614, 1799, 1786, 1653, 1516, 1452, 1414, 1383, 1348, 1331, 1328, 1336, 1362, 1391, 1408, 1448, 1529, 1684, 1858, ]
+ b: [1807, 1633, 1496, 1427, 1395, 1372, 1357, 1340, 1339, 1335, 1356, 1382, 1410, 1454, 1541, 1690, 1860, 1657, 1503, 1411, 1364, 1342, 1312, 1286, 1274, 1262, 1270, 1287, 1326, 1355, 1387, 1447, 1550, 1726, 1556, 1438, 1374, 1340, 1305, 1267, 1236, 1213, 1199, 1211, 1246, 1280, 1324, 1355, 1397, 1475, 1620, 1473, 1407, 1350, 1317, 1270, 1223, 1173, 1144, 1135, 1151, 1185, 1237, 1292, 1326, 1368, 1422, 1544, 1430, 1375, 1331, 1293, 1238, 1166, 1120, 1096, 1091, 1104, 1133, 1188, 1261, 1310, 1351, 1388, 1487, 1383, 1362, 1316, 1269, 1194, 1128, 1076, 1054, 1057, 1070, 1101, 1146, 1229, 1294, 1329, 1368, 1459, 1368, 1347, 1301, 1250, 1162, 1099, 1057, 1039, 1035, 1041, 1076, 1119, 1199, 1271, 1321, 1349, 1440, 1360, 1338, 1299, 1234, 1145, 1086, 1042, 1029, 1026, 1034, 1059, 1104, 1176, 1260, 1307, 1344, 1439, 1347, 1342, 1293, 1226, 1139, 1077, 1040, 1024, 1025, 1030, 1051, 1099, 1170, 1249, 1301, 1335, 1432, 1346, 1342, 1295, 1227, 1145, 1083, 1040, 1025, 1024, 1031, 1059, 1096, 1170, 1247, 1297, 1338, 1436, 1362, 1344, 1299, 1245, 1161, 1095, 1055, 1034, 1031, 1041, 1069, 1115, 1185, 1252, 1299, 1347, 1453, 1378, 1353, 1311, 1261, 1191, 1117, 1077, 1058, 1045, 1063, 1092, 1141, 1210, 1274, 1302, 1358, 1461, 1405, 1364, 1329, 1281, 1229, 1159, 1106, 1084, 1080, 1093, 1124, 1180, 1244, 1285, 1317, 1380, 1496, 1467, 1379, 1343, 1304, 1260, 1208, 1154, 1127, 1117, 1138, 1172, 1225, 1266, 1297, 1340, 1397, 1556, 1532, 1428, 1354, 1325, 1290, 1248, 1211, 1181, 1178, 1197, 1227, 1261, 1293, 1321, 1342, 1450, 1624, 1634, 1502, 1394, 1347, 1316, 1283, 1251, 1239, 1241, 1254, 1266, 1297, 1312, 1328, 1396, 1509, 1739, 1685, 1572, 1426, 1351, 1313, 1285, 1257, 1254, 1249, 1259, 1266, 1287, 1292, 1336, 1429, 1593, 1816, ]
+ #800x600_D65_70 - D65
+ - ct: 6504
+ resolution: 800x600
+ r: [2310, 2164, 1991, 1936, 1850, 1817, 1755, 1703, 1707, 1707, 1757, 1836, 1862, 1962, 2029, 2221, 2360, 2246, 2047, 1960, 1865, 1809, 1707, 1633, 1600, 1571, 1595, 1646, 1733, 1829, 1886, 1973, 2107, 2297, 2150, 1988, 1897, 1818, 1703, 1592, 1504, 1453, 1424, 1452, 1527, 1625, 1753, 1828, 1929, 2014, 2213, 2056, 1960, 1846, 1757, 1608, 1475, 1376, 1315, 1297, 1330, 1399, 1512, 1645, 1782, 1879, 1981, 2117, 2007, 1925, 1817, 1678, 1513, 1371, 1268, 1205, 1188, 1221, 800, 1406, 1563, 1712, 1840, 1954, 2039, 1988, 1883, 1780, 1612, 1425, 1282, 1180, 1125, 1111, 1140, 1208, 1324, 1484, 1660, 1821, 1914, 2015, 1973, 1864, 1740, 1553, 1366, 1220, 1124, 1069, 1057, 1083, 1154, 1264, 1423, 1615, 1794, 1891, 2000, 1955, 1842, 1717, 1524, 1332, 1187, 1094, 1042, 1028, 1053, 1117, 1229, 1387, 1582, 1767, 1877, 1991, 1942, 1849, 1704, 1509, 1320, 1177, 1081, 1031, 1024, 1042, 1108, 1216, 1376, 1569, 1767, 1877, 1998, 1946, 1853, 1710, 1515, 1335, 1186, 1092, 1041, 1030, 1055, 1118, 1233, 1390, 1584, 1773, 1885, 1985, 1958, 1852, 1737, 1550, 1370, 1224, 1125, 1073, 1058, 1089, 1155, 1265, 1419, 1614, 1788, 1894, 2007, 1973, 1875, 1768, 1604, 1426, 1282, 1181, 1128, 1112, 1145, 1214, 1330, 1491, 1667, 1810, 1926, 2015, 1995, 1902, 1815, 1667, 1513, 1371, 1262, 1207, 1194, 1224, 1299, 1418, 1569, 1723, 1848, 1961, 2038, 2051, 1925, 1837, 1758, 1606, 1473, 1373, 1313, 1302, 1335, 1405, 1521, 1650, 1793, 1893, 1977, 2116, 2136, 1971, 1882, 1815, 1703, 1587, 1492, 1445, 1432, 1461, 1529, 1624, 1754, 1841, 1907, 2032, 2215, 2244, 2038, 1200, 1860, 1800, 1696, 1625, 1583, 1577, 1610, 1653, 1734, 1822, 1865, 1980, 2109, 2298, 2286, 2159, 1971, 1909, 1828, 1794, 1703, 1686, 1686, 1689, 1740, 1810, 1830, 1925, 1999, 2201, 2357, ]
+ gr: [1785, 1800, 1516, 1458, 1422, 1403, 1374, 1363, 1359, 1363, 1385, 1417, 1447, 1486, 1547, 1693, 1834, 1675, 1547, 1462, 1418, 1393, 1346, 1319, 1304, 1289, 1302, 1330, 1382, 1417, 1451, 1492, 1592, 1743, 1607, 1498, 1437, 1404, 1353, 1301, 1264, 1238, 1226, 1240, 1281, 1325, 1398, 1426, 1468, 1541, 1668, 1547, 1466, 1413, 1382, 1311, 1251, 1202, 1168, 1161, 1176, 1218, 1275, 1351, 1408, 1449, 1498, 1606, 1499, 1447, 1404, 1349, 1269, 1199, 1147, 1113, 1106, 1123, 1163, 1225, 1313, 1384, 1435, 1485, 1551, 1467, 1437, 1388, 1318, 1228, 1154, 1099, 1070, 1066, 1081, 1120, 1185, 1278, 1362, 1430, 1468, 1530, 1460, 1422, 1370, 1293, 1199, 1121, 1068, 1044, 1035, 1052, 1090, 1155, 1244, 1344, 1420, 1457, 1507, 1460, 1416, 1363, 1278, 1179, 1105, 1054, 1028, 1028, 1036, 1073, 1134, 1230, 1323, 1413, 1452, 1509, 1454, 1421, 1361, 1272, 1174, 1097, 1046, 1025, 1024, 1033, 1068, 1130, 1222, 1320, 1408, 1450, 1503, 1456, 1423, 1366, 1275, 1184, 1105, 1053, 1030, 1027, 1040, 1073, 1136, 1228, 1324, 1411, 1457, 1508, 1472, 1429, 1376, 1294, 1205, 1126, 1072, 1046, 1044, 1058, 1095, 1159, 1246, 1345, 1419, 1464, 1530, 1481, 1443, 1396, 1322, 1239, 1161, 1104, 1078, 1070, 1088, 1128, 1196, 1283, 1371, 1428, 1600, 1551, 1521, 1457, 1421, 1355, 1282, 1209, 1152, 1125, 1116, 1134, 1176, 1243, 1324, 1398, 1446, 1497, 1581, 1571, 1471, 1430, 1392, 1328, 1262, 1210, 1179, 1172, 1191, 1236, 1295, 1363, 1424, 1465, 1511, 1636, 1636, 1509, 1448, 1415, 1368, 1316, 1271, 1243, 1234, 1258, 800, 1340, 1407, 1439, 1459, 1561, 1699, 1720, 1577, 1479, 1444, 1408, 1362, 1325, 1304, 1305, 1325, 1348, 1394, 1426, 1439, 1503, 1609, 1788, 1770, 1642, 1502, 1444, 1400, 1384, 1338, 1334, 1329, 1339, 1357, 1389, 1396, 1443, 1514, 1670, 1822, ]
+ gb: [1791, 1649, 1516, 1459, 1422, 1404, 1373, 1360, 1353, 1358, 1386, 1424, 1451, 1492, 1563, 1710, 1854, 1687, 1553, 1463, 1420, 1393, 1347, 1313, 800, 1284, 1295, 1324, 1376, 1417, 1455, 1493, 1609, 1768, 1617, 1511, 1444, 1409, 1359, 1299, 1260, 1234, 1219, 1237, 1276, 1328, 1403, 1431, 1479, 1557, 1696, 1555, 1477, 1422, 1388, 1311, 1250, 1200, 1165, 1158, 1174, 1217, 1281, 1358, 1416, 1463, 1520, 1629, 1520, 1458, 1415, 1355, 1272, 1203, 1144, 1111, 1105, 1122, 1165, 1231, 1322, 1394, 1447, 1497, 1577, 1481, 1452, 1399, 1330, 1234, 1160, 1101, 1070, 1065, 1082, 1124, 1192, 1288, 1373, 1443, 1485, 1556, 1476, 1437, 1384, 1304, 1207, 1124, 1070, 1045, 1039, 1055, 1092, 1163, 1256, 1357, 1429, 1475, 1539, 1470, 1430, 1373, 1288, 1186, 1108, 1056, 1029, 1027, 1040, 1078, 1142, 1240, 1336, 1424, 1469, 1529, 1465, 1433, 1370, 1281, 1179, 1102, 1049, 1025, 1024, 1035, 1070, 1134, 1230, 1332, 1420, 1464, 1536, 1469, 1434, 1372, 1283, 1186, 1108, 1055, 1029, 1027, 1037, 1076, 1145, 1236, 1337, 1421, 1468, 1535, 1478, 1438, 1382, 1303, 1210, 1128, 1070, 1044, 1040, 1056, 1096, 1164, 1255, 1355, 1427, 1478, 1551, 1489, 1454, 1401, 1329, 1239, 1160, 1102, 1075, 1067, 1084, 1128, 1196, 1288, 1380, 1435, 1492, 1573, 1528, 1464, 1426, 1358, 1283, 1206, 1146, 1116, 1110, 1129, 1172, 1242, 1327, 1402, 1451, 1508, 1597, 1574, 1476, 1433, 1395, 1326, 1254, 1202, 1170, 1165, 1182, 1230, 1292, 1361, 1425, 1471, 1526, 1657, 1638, 1512, 1449, 1418, 1366, 1308, 1259, 1230, 1223, 1246, 1285, 1334, 1402, 1439, 1465, 1574, 1712, 1723, 1575, 1474, 1440, 1400, 1353, 1312, 1289, 1287, 1305, 1332, 1381, 1417, 1440, 1504, 1616, 1806, 1780, 1652, 1506, 1448, 1403, 1380, 1340, 1327, 1325, 1335, 1350, 1390, 1402, 1448, 1532, 1693, 1848, ]
+ b: [1834, 1686, 1532, 1462, 1420, 1404, 1369, 1360, 1354, 1357, 1375, 1415, 1442, 1496, 1568, 1741, 1872, 1706, 1543, 1441, 1391, 1366, 1321, 1295, 1281, 1270, 1276, 1305, 1345, 1389, 1418, 1477, 1588, 1752, 1594, 1473, 1400, 1363, 1317, 1269, 1238, 1216, 1206, 1214, 1250, 800, 1353, 1389, 1434, 1503, 1664, 1514, 1437, 1372, 1334, 1278, 1228, 1180, 1151, 1143, 1159, 1196, 1246, 1313, 1359, 1405, 1453, 1587, 1465, 1401, 1351, 1308, 1236, 1177, 1127, 1101, 1093, 1109, 1141, 1200, 1274, 1335, 1384, 1427, 1522, 1423, 1386, 1335, 1275, 1199, 1133, 1087, 1063, 1059, 1069, 1104, 1159, 1240, 1316, 1369, 1402, 1493, 1407, 1375, 1318, 1256, 1172, 1107, 1060, 1041, 1035, 1048, 1077, 1135, 1211, 1291, 1354, 1391, 1478, 1390, 1365, 1313, 1239, 1153, 1089, 1047, 1029, 1028, 1033, 1065, 1116, 1193, 1278, 1342, 1382, 1475, 1384, 1364, 1308, 1231, 1146, 1082, 1040, 1025, 1024, 1030, 1057, 1110, 1183, 1269, 1337, 1379, 1475, 1384, 1372, 1309, 1233, 1152, 1086, 1046, 1024, 1024, 1032, 1061, 1113, 1187, 1268, 1337, 1379, 1479, 1395, 1370, 1317, 1249, 1171, 1102, 1058, 1035, 1029, 1047, 1073, 1130, 1200, 1278, 1341, 1388, 1491, 1420, 1383, 1336, 1265, 1195, 1129, 1078, 1059, 1053, 1065, 1102, 1155, 1227, 1301, 1348, 1405, 1505, 1452, 1396, 1356, 1295, 1234, 1166, 1116, 1092, 1084, 1103, 1139, 1195, 1262, 1321, 1364, 1420, 1547, 1517, 1414, 1375, 1324, 1269, 1214, 1165, 1138, 1132, 1148, 1188, 1239, 1291, 1336, 1387, 1446, 1604, 1587, 1471, 1383, 1354, 1309, 1257, 1216, 1192, 1187, 1209, 1241, 1277, 1330, 1366, 1384, 1498, 1682, 1689, 1543, 1427, 1381, 1344, 1303, 1265, 1250, 1251, 1266, 1284, 1326, 1353, 1369, 1447, 1566, 1790, 1754, 1632, 1469, 1391, 1353, 1317, 1292, 1282, 1278, 1294, 1306, 1321, 1347, 1382, 1477, 1650, 1854, ]
+ #800x600_F2_CWF_70 - F2_CWF
+ - ct: 4230
+ resolution: 800x600
+ r: [2065, 1886, 1745, 1661, 1619, 1574, 1532, 1504, 1498, 1499, 1533, 1586, 1628, 1689, 1770, 1942, 2140, 1978, 1796, 1688, 1627, 1565, 1501, 1446, 1424, 1407, 1419, 1460, 1525, 1583, 1642, 1712, 1829, 2032, 1880, 1732, 1643, 1579, 1499, 1418, 1356, 1319, 1300, 1320, 1372, 1443, 1536, 1598, 1661, 1763, 1923, 1812, 1689, 1608, 1535, 1429, 1335, 1267, 1223, 1210, 1234, 1284, 1362, 1461, 1547, 1634, 1715, 1848, 1755, 1664, 1579, 1600, 1362, 1262, 1188, 1145, 1132, 1156, 1211, 1289, 1403, 1504, 1604, 1688, 1791, 1726, 1635, 1548, 1433, 1298, 1199, 1126, 1084, 1080, 1101, 1147, 1226, 1340, 1468, 1586, 1659, 1752, 1707, 1624, 1522, 1393, 1256, 1155, 1085, 1054, 1043, 1059, 1111, 1187, 1302, 1435, 1566, 1645, 1732, 1695, 1605, 1508, 1367, 1230, 1132, 1066, 1034, 1028, 1042, 1084, 1160, 1275, 1418, 1549, 1634, 1722, 1681, 1604, 1498, 1360, 1222, 1121, 1058, 1027, 1024, 1034, 1075, 1151, 1264, 1407, 1543, 1633, 1723, 1691, 1609, 1498, 1361, 1231, 1130, 1064, 1037, 1027, 1043, 1083, 1162, 1275, 1413, 1545, 1638, 1714, 1692, 1612, 1515, 1385, 1258, 1153, 1087, 1051, 1045, 1064, 1109, 1185, 1295, 1437, 1560, 1645, 1741, 1712, 1627, 1538, 1417, 1298, 1199, 1124, 1087, 1075, 1101, 1146, 1231, 1342, 1472, 1574, 1665, 1754, 1743, 1637, 1572, 1466, 1357, 1253, 1181, 1142, 1131, 1154, 1207, 1295, 1401, 1515, 1601, 1687, 1789, 1807, 1661, 1597, 1525, 1425, 1328, 1257, 1215, 1208, 1230, 1282, 1363, 1459, 1555, 1800, 1714, 1857, 1871, 1711, 1631, 1573, 1491, 1407, 1343, 1307, 1298, 1323, 1368, 1440, 1528, 1601, 1649, 1767, 1932, 1982, 1788, 1675, 1617, 1559, 1489, 1433, 1406, 1405, 1425, 1457, 1516, 1581, 1623, 1713, 1836, 2044, 2041, 1885, 1730, 1646, 1589, 1547, 1498, 1476, 1474, 1488, 1518, 1569, 1594, 1656, 1757, 1921, 2111, ]
+ gr: [1765, 1633, 1502, 1441, 1411, 1389, 1365, 1356, 1350, 1358, 1375, 1408, 1434, 1476, 1534, 1678, 1820, 1671, 1535, 1450, 1410, 1381, 1341, 1311, 1297, 1288, 1295, 1323, 1368, 1407, 1437, 1600, 1580, 1736, 1595, 1488, 1424, 1388, 1342, 1293, 1255, 1230, 1219, 1235, 1270, 1319, 1384, 1413, 1452, 1524, 1657, 1534, 1452, 1399, 1367, 1300, 1238, 1194, 1162, 1155, 1171, 1209, 1267, 1336, 1393, 1435, 1486, 1591, 1491, 1429, 1389, 1335, 1255, 1189, 1139, 1108, 1104, 1118, 1156, 1218, 1302, 1369, 1422, 1470, 1540, 1456, 1416, 1370, 1305, 1216, 1146, 1093, 1068, 1064, 1078, 1116, 1176, 1268, 1345, 1415, 1451, 1510, 1445, 1409, 1352, 1280, 1185, 1113, 1065, 1041, 1039, 1051, 1085, 1147, 1235, 1330, 1402, 1440, 1499, 1444, 1399, 1349, 1261, 1171, 1096, 1050, 1029, 1030, 1037, 1070, 1127, 1217, 1314, 1395, 1437, 1490, 1437, 1401, 1346, 1256, 1161, 1091, 1043, 1026, 1024, 1034, 1064, 1123, 1210, 1308, 1390, 1436, 1490, 1441, 1409, 1346, 1262, 1170, 1097, 1049, 1030, 1029, 1040, 1069, 1129, 1216, 1315, 1393, 1439, 1490, 1458, 1413, 1357, 1280, 1194, 1118, 1065, 1044, 1043, 1055, 1088, 1151, 1235, 1331, 1404, 1448, 1513, 1475, 1426, 1378, 1304, 1225, 1149, 1098, 1074, 1067, 1083, 1122, 1187, 1268, 1356, 1411, 1465, 1530, 1505, 1439, 1402, 1339, 1268, 1197, 1144, 1119, 1110, 1129, 1167, 1232, 1313, 1383, 1428, 1481, 1563, 1564, 1455, 1415, 1373, 1313, 1249, 1203, 1173, 1167, 1184, 1227, 1284, 1349, 1404, 1449, 1499, 1617, 1620, 1493, 1428, 1402, 1354, 1303, 1261, 1236, 1228, 1250, 1285, 1333, 1389, 1428, 1444, 1544, 1684, 1710, 1568, 1462, 1428, 1394, 1354, 1315, 800, 1298, 1317, 1337, 1381, 1411, 1428, 1491, 1594, 1774, 1755, 1632, 1496, 1430, 1395, 1370, 1330, 1328, 1322, 1331, 1348, 1378, 1392, 1426, 1503, 1657, 1810, ]
+ gb: [1773, 1627, 1500, 1438, 1403, 1382, 1352, 1341, 1336, 1344, 1365, 1404, 1435, 1476, 1545, 1692, 1839, 1672, 1540, 1450, 1406, 1376, 1332, 1298, 1282, 1274, 1284, 1312, 1363, 1405, 1440, 1483, 1594, 1751, 1608, 1494, 1426, 1391, 1341, 1284, 1247, 1219, 1207, 1224, 1263, 1318, 1388, 1423, 1460, 1542, 1678, 1545, 1463, 1407, 1368, 1298, 1235, 1188, 1153, 1148, 1163, 1207, 1268, 1345, 1402, 1450, 1506, 1613, 1499, 1442, 1399, 1342, 1259, 1187, 1135, 1103, 1096, 1116, 1157, 1222, 1310, 1382, 1436, 1489, 1564, 1475, 1434, 1382, 1315, 1221, 1145, 1093, 1065, 1061, 1076, 1115, 1182, 1278, 1364, 1431, 1474, 1541, 1461, 1425, 1368, 1290, 1193, 1118, 1064, 1041, 1037, 1050, 1090, 1154, 1246, 1346, 1420, 1466, 1525, 1463, 1416, 1363, 1273, 1178, 1097, 1051, 1030, 1029, 1039, 1073, 1136, 1232, 1332, 1414, 1460, 1519, 1452, 1420, 1357, 1268, 1172, 1094, 1045, 1026, 1024, 1034, 1067, 1131, 1223, 1324, 1409, 1458, 1521, 1460, 1420, 1359, 1271, 1175, 1099, 1048, 1029, 1027, 1038, 1072, 1136, 1227, 1330, 1412, 1458, 1524, 1467, 1424, 1368, 1289, 1197, 1117, 1063, 1040, 1038, 1053, 1089, 1156, 1246, 1345, 1415, 1470, 1538, 1486, 1437, 1384, 1309, 1224, 1146, 1091, 1067, 1063, 1077, 1118, 1187, 1278, 1367, 1425, 1600, 1553, 1519, 1445, 1408, 1342, 1266, 1192, 1136, 1106, 1102, 1119, 1161, 1230, 1316, 1389, 1438, 1495, 1583, 1567, 1460, 1420, 1374, 1310, 1241, 1189, 1158, 1152, 1173, 1214, 1278, 1348, 1410, 1456, 1511, 1634, 1624, 1498, 1427, 1400, 1346, 1294, 1244, 1219, 1210, 1232, 1271, 1321, 1384, 1430, 1448, 1557, 1697, 1719, 1560, 1458, 1421, 1381, 1338, 1298, 1274, 1275, 1292, 1318, 1365, 1404, 1424, 1489, 1601, 1785, 1751, 1637, 1497, 1429, 1389, 1361, 1323, 1311, 1309, 1318, 1339, 1374, 1388, 1429, 1513, 1674, 1829, ]
+ b: [1800, 1643, 1486, 1416, 1376, 1354, 1329, 1318, 1309, 1310, 1331, 1359, 1390, 1444, 1533, 1708, 1846, 1664, 1510, 1400, 1351, 1324, 1286, 1260, 1246, 1235, 1244, 1266, 1306, 1341, 1373, 1441, 1556, 1734, 1557, 1441, 1360, 1322, 1282, 1242, 1211, 1188, 1180, 1186, 1220, 1258, 1309, 1346, 1391, 1475, 1626, 1484, 1400, 1331, 1300, 1247, 1202, 1163, 1135, 1127, 1143, 1170, 1215, 1274, 1315, 1365, 1417, 1555, 1422, 1368, 1316, 1270, 1209, 1158, 1117, 1088, 1084, 1094, 1130, 1174, 1240, 800, 1343, 1389, 1497, 1383, 1351, 1299, 1247, 1177, 1122, 1081, 1057, 1051, 1067, 1094, 1142, 1209, 1274, 1329, 1362, 1461, 1367, 1333, 1284, 1224, 1153, 1098, 1056, 1040, 1035, 1042, 1070, 1118, 1186, 1255, 1314, 1349, 1441, 1355, 1327, 1275, 1209, 1137, 1082, 1044, 1029, 1026, 1034, 1056, 1100, 1166, 1241, 1302, 1341, 1439, 1343, 1325, 1270, 1201, 1130, 1075, 1037, 1024, 1026, 1030, 1050, 1094, 1160, 1231, 1295, 1334, 1434, 1347, 1330, 1274, 1203, 1135, 1079, 1040, 1026, 1024, 1031, 1054, 1097, 1161, 1231, 1292, 1338, 1433, 1358, 1330, 1280, 1219, 1152, 1093, 1051, 1032, 1030, 1043, 1067, 1115, 1173, 1237, 1298, 1348, 1447, 1382, 1342, 1298, 1236, 1174, 1115, 1071, 1051, 1044, 1060, 1088, 1138, 1197, 1259, 1301, 1365, 1464, 1410, 1360, 1314, 1259, 1205, 1149, 1104, 1079, 1075, 1090, 1123, 1171, 1227, 1277, 1315, 1387, 1508, 1476, 1376, 1330, 1287, 1238, 1188, 1144, 1122, 1115, 1132, 1165, 1206, 1249, 1294, 1344, 1402, 1567, 1548, 1431, 1348, 1314, 1271, 1224, 1190, 1168, 1163, 1182, 1210, 1246, 1286, 1318, 1344, 1462, 1650, 1658, 1510, 1386, 1342, 1305, 1268, 1232, 1220, 1221, 1236, 1250, 1283, 1311, 1328, 1406, 1530, 1755, 1698, 1587, 1431, 1350, 1304, 1274, 1244, 1238, 1239, 1245, 1262, 1283, 1293, 1339, 1439, 1608, 1825, ]
+ #800x600_D50_70 - D50
+ - ct: 5003
+ resolution: 800x600
+ r: [2543, 2578, 2509, 2438, 2318, 2233, 2133, 2085, 2088, 2130, 2245, 2390, 2533, 2674, 2811, 2910, 2790, 2536, 2518, 2407, 2309, 2153, 2048, 1910, 1861, 1865, 1921, 2013, 2160, 2340, 2523, 2664, 2836, 2882, 2501, 2408, 2276, 2127, 1951, 1804, 1701, 1655, 1635, 1674, 1771, 1939, 2141, 2356, 2565, 2701, 2839, 2403, 2314, 2154, 1963, 1779, 1618, 1511, 1447, 1433, 1470, 1554, 1714, 1920, 2196, 2430, 2589, 2694, 2352, 2232, 2049, 1828, 1635, 1472, 1357, 1295, 1274, 1317, 1399, 1543, 1785, 2021, 2302, 2494, 2688, 2254, 2143, 1936, 1720, 1509, 1345, 1237, 1168, 1158, 1188, 1271, 1420, 1614, 1894, 2190, 2443, 2592, 2210, 2085, 1870, 1630, 1432, 1264, 1161, 1090, 1079, 1102, 1184, 1329, 1525, 1797, 2112, 2377, 2587, 2224, 2063, 1822, 1598, 1381, 1217, 1121, 1045, 1031, 1063, 1129, 1270, 1481, 1749, 2059, 2344, 2559, 2234, 2083, 1812, 1592, 1381, 1215, 1102, 1046, 1024, 1053, 1122, 1257, 1466, 1734, 2045, 2338, 2530, 2224, 2063, 1856, 1610, 1407, 1237, 1126, 1063, 1044, 1072, 1145, 1288, 1485, 1764, 2059, 2344, 2539, 2273, 2135, 1906, 1675, 1470, 1299, 1187, 1112, 1094, 1120, 1208, 1348, 1546, 1828, 2124, 2377, 2566, 2321, 2197, 1986, 1779, 1563, 1402, 1271, 1209, 1192, 1221, 1313, 1461, 1664, 1929, 2203, 2460, 2659, 2371, 2292, 2119, 1906, 1700, 1538, 1407, 1335, 1321, 1366, 1447, 1593, 1800, 2062, 2331, 2570, 2737, 2485, 2382, 2262, 2078, 1876, 1721, 1587, 1525, 1504, 1545, 1633, 1785, 1985, 2246, 2464, 2631, 2799, 2621, 2465, 2387, 2243, 2063, 1912, 1801, 1734, 1705, 1755, 1848, 2005, 2213, 2417, 2584, 2773, 2900, 2757, 2632, 2519, 2419, 2283, 2160, 2044, 1976, 1979, 2024, 2107, 2272, 2430, 2578, 2731, 2921, 2984, 2724, 2762, 2663, 2570, 2413, 2331, 2245, 2227, 2242, 2278, 2369, 2486, 2647, 2763, 2864, 3041, 2860, ]
+ gr: [2123, 2151, 2065, 2008, 1917, 1836, 1766, 1738, 1740, 1752, 1817, 1882, 1943, 2023, 2110, 2206, 2123, 2143, 2093, 2006, 1915, 1810, 1724, 1632, 1597, 1588, 1608, 1665, 1733, 1827, 1928, 2014, 2122, 2189, 2104, 2052, 1936, 1805, 1686, 1575, 1502, 1464, 1446, 1461, 1512, 1597, 1705, 1827, 1949, 2027, 2124, 2066, 1962, 1856, 1704, 1563, 1450, 1376, 1323, 1310, 1323, 1371, 1466, 1570, 1714, 1868, 1954, 2066, 1997, 1917, 1771, 1622, 1466, 1351, 1258, 1217, 1199, 1211, 1265, 1351, 1469, 1622, 1781, 1891, 1989, 1958, 1863, 1700, 1537, 1382, 1265, 1182, 1133, 1118, 1128, 1178, 1254, 1385, 1537, 1695, 1838, 1943, 1935, 1829, 1642, 1480, 1319, 1202, 1122, 1078, 1061, 1073, 1114, 1196, 1316, 1477, 1655, 1806, 1913, 1953, 1794, 1639, 1442, 1288, 1171, 1089, 1047, 1031, 1044, 1083, 1153, 1279, 1436, 1623, 1783, 1924, 1940, 1807, 1621, 1442, 1283, 1166, 1083, 1041, 1024, 1034, 1073, 1147, 1270, 1436, 1608, 1768, 1897, 1968, 1828, 1639, 1470, 1297, 1182, 1096, 1055, 1038, 1050, 1090, 1168, 1290, 1442, 1627, 1783, 1917, 1942, 1841, 1682, 1510, 1349, 1222, 1132, 1088, 1067, 1081, 1127, 1206, 1326, 1486, 1651, 1811, 1942, 2005, 1901, 1743, 1578, 1422, 1303, 1209, 1152, 1135, 1148, 1191, 1280, 1399, 1548, 1719, 1845, 1974, 2057, 1952, 1830, 1685, 1512, 1393, 1305, 1245, 1221, 1233, 1289, 1372, 1489, 1634, 1776, 1904, 2031, 2113, 2007, 1918, 1777, 1640, 1511, 1423, 1360, 1344, 1360, 1400, 1494, 1608, 1742, 1862, 1976, 2123, 2199, 2104, 2006, 1879, 1756, 1649, 1553, 1502, 1480, 1495, 1546, 1633, 1732, 1839, 1956, 2052, 2210, 2300, 2191, 2104, 2010, 1907, 1802, 1717, 1669, 1655, 1673, 1717, 1792, 1878, 1955, 2054, 2222, 2274, 2310, 2336, 2195, 2103, 2012, 1925, 1861, 1823, 1814, 1844, 1889, 1931, 2004, 2079, 2166, 2287, 2213, ]
+ gb: [2166, 2183, 2106, 2056, 1961, 1889, 1800, 1772, 1760, 1791, 1821, 1907, 1948, 2040, 2115, 2205, 2191, 2197, 2125, 2062, 1973, 1862, 1758, 1680, 1620, 1612, 1636, 1693, 1758, 1851, 1953, 2031, 2125, 2174, 2125, 2067, 1974, 1852, 1719, 1621, 1532, 1477, 1465, 1480, 1535, 1605, 1724, 1852, 1967, 2050, 2156, 2107, 2015, 1893, 1738, 1608, 1485, 1406, 1337, 1319, 1337, 1382, 1476, 1589, 1733, 1869, 1985, 2070, 2037, 1948, 1806, 1641, 1501, 1377, 1287, 1227, 1215, 1227, 1274, 1364, 1485, 1645, 1806, 1928, 2028, 1981, 1887, 1728, 1564, 1409, 1285, 1199, 1145, 1125, 1135, 1183, 1270, 1395, 1560, 1733, 1868, 1974, 1965, 1841, 1670, 1509, 1349, 1221, 1138, 1084, 1065, 1073, 1121, 1208, 1332, 1496, 1670, 1835, 1958, 1948, 1818, 1642, 1467, 1315, 1185, 1099, 1052, 1035, 1042, 1084, 1163, 1292, 1458, 1638, 1812, 1948, 1942, 1809, 1635, 1467, 1296, 1178, 1094, 1039, 1024, 1038, 1073, 1157, 1285, 1451, 1640, 1803, 1935, 1948, 1812, 1646, 1483, 1317, 1196, 1107, 1057, 1043, 1053, 1090, 1183, 1296, 1464, 1650, 1818, 1941, 1965, 1841, 1687, 1519, 1362, 1243, 1145, 1094, 1075, 1088, 1137, 1225, 1339, 1512, 1692, 1835, 1988, 1981, 1893, 1738, 1586, 1435, 1314, 1218, 1160, 1143, 1158, 1212, 1294, 1418, 1578, 1742, 1887, 2005, 2037, 1948, 1838, 1674, 1527, 1398, 1309, 1251, 1236, 1253, 1305, 1385, 1514, 1674, 1816, 1934, 2062, 2098, 2015, 1899, 1791, 1656, 1530, 1430, 1379, 1360, 1379, 1428, 1517, 1639, 1781, 1893, 2015, 2117, 2199, 2075, 1988, 1910, 1776, 1664, 1583, 1518, 1502, 1525, 1576, 1668, 1776, 1898, 1981, 2084, 2221, 2269, 2204, 2103, 2021, 1921, 1827, 1751, 1676, 1671, 1693, 1755, 1843, 1927, 2007, 2095, 2224, 2294, 2285, 2285, 2190, 2112, 2009, 1956, 1909, 1853, 1845, 1864, 1921, 1995, 2058, 2137, 2199, 2308, 2231, ]
+ b: [2007, 2014, 1951, 1922, 1856, 1794, 1746, 1720, 1718, 1747, 1818, 1865, 1956, 2026, 2146, 2219, 2251, 2020, 1954, 1914, 1840, 1745, 1673, 1626, 1592, 1586, 1613, 1674, 1732, 1851, 1938, 2030, 2131, 2207, 1927, 1878, 1807, 1732, 1628, 1548, 1486, 1461, 1440, 1465, 1519, 1601, 1715, 1846, 1943, 2018, 2141, 1863, 1826, 1730, 1633, 1515, 1436, 1369, 1326, 1318, 1337, 1399, 1479, 1598, 1729, 1865, 1962, 2051, 1840, 1751, 1653, 1541, 1426, 1333, 1265, 1217, 1214, 1223, 1281, 1373, 1493, 1641, 1794, 1908, 2015, 1803, 1695, 1587, 1462, 1347, 1245, 1173, 1139, 1122, 1139, 1197, 1288, 1404, 1555, 1712, 1845, 1987, 1781, 1659, 1544, 1402, 1284, 1186, 1117, 1075, 1065, 1088, 1131, 1214, 1342, 1504, 1667, 1808, 1945, 1753, 1639, 1509, 1376, 1253, 1152, 1083, 1045, 1040, 1051, 1094, 1177, 1307, 1464, 1630, 1782, 1939, 1752, 1626, 1510, 1370, 1248, 1141, 1076, 1037, 1024, 1043, 1087, 1163, 1299, 1452, 1631, 1789, 1927, 1761, 1639, 1509, 1384, 1259, 1157, 1088, 1049, 1036, 1061, 1103, 1190, 1321, 1469, 1648, 1806, 1939, 1772, 1673, 1550, 1423, 1304, 1194, 1124, 1088, 1073, 1094, 1143, 1231, 1353, 1508, 1673, 1816, 1955, 1794, 1709, 1599, 1495, 1373, 1269, 1191, 1149, 1129, 1159, 1210, 1298, 1429, 1571, 1726, 1854, 2010, 1840, 1759, 1679, 1567, 1448, 1358, 1284, 1234, 1228, 1249, 1306, 1392, 1507, 1647, 1794, 1917, 2076, 1929, 1835, 1760, 1670, 1565, 1470, 1388, 1351, 1335, 1362, 1423, 1511, 1609, 1743, 1865, 1983, 2145, 2028, 1898, 1841, 1761, 1670, 1590, 1519, 1483, 1475, 1505, 1563, 1640, 1749, 1862, 1943, 2078, 2218, 2109, 2014, 1944, 1883, 1812, 1745, 1674, 1630, 1635, 1665, 1717, 1801, 1884, 1967, 2064, 2188, 2295, 2157, 2126, 2020, 1952, 1891, 1833, 1781, 1761, 1773, 1803, 1857, 1943, 2005, 2026, 2159, 2268, 2251, ]
+
+...
diff --git a/src/ipa/rkisp1/data/ov4689.yaml b/src/ipa/rkisp1/data/ov4689.yaml
new file mode 100644
index 00000000..2068684c
--- /dev/null
+++ b/src/ipa/rkisp1/data/ov4689.yaml
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+ - Agc:
+ - Awb:
+ - BlackLevelCorrection:
+ R: 66
+ Gr: 66
+ Gb: 66
+ B: 66
+...
diff --git a/src/ipa/rkisp1/data/ov5640.yaml b/src/ipa/rkisp1/data/ov5640.yaml
index 232d8ae8..897b83cb 100644
--- a/src/ipa/rkisp1/data/ov5640.yaml
+++ b/src/ipa/rkisp1/data/ov5640.yaml
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-%YAML 1.2
+%YAML 1.1
---
version: 1
algorithms:
@@ -10,4 +10,245 @@ algorithms:
Gr: 256
Gb: 256
B: 256
+ - ColorProcessing:
+ - GammaSensorLinearization:
+ x-intervals: [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ]
+ y:
+ red: [ 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840, 4095 ]
+ green: [ 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840, 4095 ]
+ blue: [ 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840, 4095 ]
+ - LensShadingCorrection:
+ x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ sets:
+ - ct: 3000
+ r: [
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ ]
+ gr: [
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ ]
+ gb: [
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ ]
+ b: [
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
+ ]
+ - ct: 7000
+ r: [
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ ]
+ gr: [
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ ]
+ gb: [
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ ]
+ b: [
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536,
+ ]
+ - DefectPixelClusterCorrection:
+ fixed-set: false
+ sets:
+ # PG, LC, RO, RND, RG
+ - line-threshold:
+ green: 8
+ red-blue: 8
+ line-mad-factor:
+ green: 4
+ red-blue: 4
+ pg-factor:
+ green: 8
+ red-blue: 8
+ rnd-threshold:
+ green: 10
+ red-blue: 10
+ rg-factor:
+ green: 32
+ red-blue: 32
+ ro-limits:
+ green: 1
+ red-blue: 1
+ rnd-offsets:
+ green: 2
+ red-blue: 2
+ # PG, LC, RO
+ - line-threshold:
+ green: 24
+ red-blue: 32
+ line-mad-factor:
+ green: 16
+ red-blue: 24
+ pg-factor:
+ green: 6
+ red-blue: 8
+ ro-limits:
+ green: 2
+ red-blue: 2
+ # PG, LC, RO, RND, RG
+ - line-threshold:
+ green: 32
+ red-blue: 32
+ line-mad-factor:
+ green: 4
+ red-blue: 4
+ pg-factor:
+ green: 10
+ red-blue: 10
+ rnd-threshold:
+ green: 6
+ red-blue: 8
+ rg-factor:
+ green: 4
+ red-blue: 4
+ ro-limits:
+ green: 1
+ red-blue: 2
+ rnd-offsets:
+ green: 2
+ red-blue: 2
+ - Dpf:
+ DomainFilter:
+ g: [ 16, 16, 16, 16, 16, 16 ]
+ rb: [ 16, 16, 16, 16, 16, 16 ]
+ NoiseLevelFunction:
+ coeff: [
+ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1023
+ ]
+ scale-mode: "linear"
+ FilterStrength:
+ r: 64
+ g: 64
+ b: 64
+ - Filter:
...
diff --git a/src/ipa/rkisp1/data/ov5695.yaml b/src/ipa/rkisp1/data/ov5695.yaml
new file mode 100644
index 00000000..2e39e3a5
--- /dev/null
+++ b/src/ipa/rkisp1/data/ov5695.yaml
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+ - Agc:
+ - Awb:
+ - LensShadingCorrection:
+ x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ sets:
+ #2592x1944_A_70 - A
+ - ct: 2856
+ resolution: 2592x1944
+ r: [2312, 2874, 2965, 2789, 2603, 2424, 2288, 2176, 2151, 2176, 2240, 2345, 2520, 2736, 2856, 2825, 2272, 2675, 3026, 2925, 2693, 2443, 2247, 2074, 1992, 1947, 1972, 2066, 2211, 2386, 2618, 2847, 2953, 2698, 2927, 3008, 2846, 2541, 2272, 2037, 1867, 1782, 1740, 1762, 1855, 1981, 2198, 2454, 2711, 2963, 2927, 2974, 2920, 2664, 2337, 2061, 1822, 1648, 1550, 1503, 1550, 1648, 1794, 1982, 2257, 2565, 2805, 2880, 2933, 2799, 2472, 2161, 1880, 1631, 1457, 1361, 1328, 1364, 1448, 1602, 1817, 2087, 2390, 2698, 2911, 2947, 2734, 2404, 2061, 1759, 1525, 1340, 1244, 1209, 1240, 1343, 1473, 1701, 1975, 2278, 2641, 2823, 2948, 2680, 2342, 1979, 1667, 1425, 1259, 1159, 1125, 1159, 1238, 1407, 1633, 1914, 2235, 2592, 2866, 2936, 2661, 2276, 1908, 1624, 1368, 1190, 1097, 1058, 1086, 1178, 1341, 1556, 1848, 2175, 2509, 2763, 2873, 2603, 2230, 1868, 1578, 1320, 1157, 1058, 1024, 1053, 1142, 1302, 1521, 1789, 2125, 2471, 2760, 2896, 2661, 2276, 1914, 1591, 1349, 1176, 1083, 1044, 1080, 1166, 1327, 1544, 1814, 2141, 2509, 2763, 2969, 2710, 2342, 1985, 1676, 1431, 1250, 1146, 1105, 1140, 1234, 1392, 1616, 1895, 2235, 2578, 2847, 3060, 2800, 2426, 2076, 1764, 1518, 1335, 1227, 1197, 1227, 1314, 1486, 1696, 1989, 2298, 2641, 2863, 2978, 2853, 2496, 2169, 1880, 1631, 1457, 1345, 1304, 1334, 1429, 1586, 1811, 2064, 2378, 2698, 2867, 3024, 2960, 2664, 2327, 2054, 1811, 1626, 1517, 1490, 1514, 1597, 1763, 1962, 2229, 2538, 2768, 2926, 3032, 3077, 2864, 2554, 2272, 2052, 1861, 1747, 1716, 1742, 1816, 1995, 2190, 2454, 2727, 2920, 2927, 2849, 3155, 3008, 2772, 2490, 2276, 2121, 2006, 1954, 1978, 2066, 2202, 2408, 2648, 2847, 2977, 2797, 2440, 3116, 3132, 2900, 2738, 2509, 2329, 2239, 2194, 2230, 2298, 2436, 2617, 2825, 2965, 2899, 2312, ]
+ gr: [1557, 1922, 2004, 1947, 1841, 1757, 1689, 1651, 1631, 1647, 1680, 1737, 1835, 1911, 1995, 1941, 1613, 1820, 2038, 1996, 1900, 1779, 1692, 1617, 1565, 1549, 1554, 1594, 1670, 1753, 1875, 1957, 2029, 1848, 2009, 2064, 1956, 1834, 1715, 1601, 1518, 1474, 1446, 1459, 1505, 1582, 1666, 1796, 1935, 2029, 2009, 2013, 2006, 1874, 1731, 1602, 1493, 1409, 1346, 1332, 1348, 1395, 1474, 1576, 1689, 1843, 1944, 2003, 1982, 1931, 1783, 1637, 1496, 1386, 1297, 1238, 1219, 1239, 1284, 1370, 1474, 1601, 1747, 1897, 2000, 1998, 1920, 1755, 1587, 1455, 1325, 1228, 1171, 1159, 1176, 1223, 1311, 1418, 1565, 1707, 1855, 1990, 2007, 1897, 1733, 1574, 1423, 1296, 1183, 1121, 1101, 1132, 1182, 1277, 1396, 1539, 1696, 1866, 1990, 2000, 1870, 1692, 1529, 1377, 1239, 1141, 1077, 1057, 1079, 1141, 1230, 1350, 1493, 1640, 1810, 1961, 1957, 1849, 1669, 1496, 1356, 1212, 1112, 1053, 1024, 1049, 1106, 1203, 1322, 1465, 1615, 1780, 1919, 1969, 1870, 1675, 1515, 1365, 1232, 1128, 1063, 1042, 1068, 1123, 1220, 1345, 1483, 1628, 1788, 1945, 2007, 1917, 1728, 1574, 1420, 1285, 1173, 1115, 1088, 1109, 1170, 1268, 1388, 1532, 1678, 1835, 1999, 2033, 1927, 1760, 1613, 1461, 1334, 1234, 1175, 1145, 1168, 1225, 1311, 1423, 1557, 1726, 1874, 2015, 2000, 1960, 1810, 1641, 1515, 1391, 1292, 1228, 1212, 1232, 1275, 1358, 1462, 1601, 1737, 1883, 1974, 2032, 2006, 1874, 1712, 1594, 1477, 1395, 1329, 1316, 1327, 1375, 1453, 1547, 1671, 1808, 1937, 1994, 2039, 2064, 1971, 1829, 1701, 1608, 1521, 1465, 1441, 1462, 1498, 1571, 1666, 1785, 1921, 2003, 2039, 1886, 2087, 2062, 1926, 1817, 1706, 1637, 1572, 1560, 1572, 1613, 1688, 1774, 1868, 1973, 2029, 1886, 1692, 2020, 2067, 2008, 1897, 1822, 1741, 1704, 1683, 1695, 1727, 1783, 1872, 1977, 2022, 1989, 1639, ]
+ gb: [1553, 1926, 1992, 1930, 1852, 1746, 1675, 1630, 1611, 1622, 1671, 1726, 1804, 1915, 1992, 1955, 1584, 1852, 2043, 2001, 1879, 1773, 1674, 1602, 1548, 1532, 1541, 1583, 1661, 1752, 1867, 1986, 2034, 1881, 1993, 2060, 1976, 1811, 1697, 1590, 1505, 1459, 1439, 1453, 1496, 1579, 1674, 1795, 1940, 2051, 2034, 2018, 2003, 1866, 1735, 1594, 1478, 1396, 1339, 1326, 1339, 1388, 1463, 1579, 1707, 1842, 1980, 2037, 2014, 1950, 1793, 1641, 1509, 1384, 1291, 1229, 1209, 1231, 1283, 1369, 1481, 1625, 1751, 1901, 2023, 2029, 1925, 1750, 1602, 1458, 1330, 1228, 1162, 1144, 1166, 1218, 1308, 1433, 1572, 1730, 1872, 2029, 2020, 1934, 1752, 1578, 1429, 1288, 1181, 1116, 1102, 1130, 1184, 1278, 1400, 1546, 1700, 1870, 2020, 2030, 1899, 1706, 1536, 1388, 1239, 1137, 1074, 1053, 1078, 1134, 1235, 1358, 1509, 1661, 1838, 1989, 1985, 1853, 1682, 1522, 1356, 1209, 1114, 1050, 1024, 1046, 1106, 1206, 1335, 1478, 1623, 1801, 1954, 2005, 1887, 1706, 1536, 1383, 1235, 1131, 1063, 1045, 1059, 1120, 1225, 1356, 1493, 1666, 1815, 1981, 2063, 1948, 1767, 1589, 1438, 1293, 1183, 1116, 1093, 1115, 1174, 1272, 1400, 1546, 1695, 1877, 2012, 2055, 1952, 1795, 1633, 1476, 1347, 1235, 1167, 1146, 1160, 1230, 1323, 1435, 1579, 1730, 1898, 2046, 2059, 1972, 1843, 1666, 1519, 1402, 1291, 1231, 1209, 1233, 1283, 1366, 1481, 1613, 1767, 1922, 2023, 2066, 2036, 1903, 1740, 1609, 1484, 1399, 1337, 1317, 1330, 1378, 1451, 1572, 1689, 1830, 1964, 2037, 2034, 2097, 2005, 1856, 1724, 1608, 1521, 1471, 1450, 1456, 1505, 1593, 1688, 1805, 1940, 2051, 2045, 1974, 2123, 2067, 1958, 1827, 1719, 1633, 1580, 1563, 1576, 1609, 1688, 1783, 1892, 2009, 2053, 1911, 1652, 2078, 2101, 2021, 1915, 1837, 1731, 1682, 1661, 1686, 1717, 1782, 1864, 1982, 2036, 2005, 1669, ]
+ b: [1439, 1756, 1796, 1808, 1716, 1631, 1568, 1537, 1530, 1546, 1578, 1608, 1676, 1744, 1796, 1756, 1456, 1685, 1858, 1830, 1764, 1687, 1603, 1529, 1486, 1489, 1486, 1493, 1552, 1628, 1721, 1812, 1858, 1727, 1837, 1888, 1825, 1726, 1628, 1548, 1478, 1449, 1423, 1434, 1462, 1521, 1566, 1688, 1809, 1888, 1837, 1889, 1857, 1775, 1680, 1576, 1467, 1403, 1336, 1309, 1329, 1369, 1429, 1529, 1623, 1733, 1822, 1868, 1852, 1828, 1704, 1585, 1486, 1377, 1285, 1237, 1216, 1232, 1268, 1344, 1438, 1536, 1667, 1764, 1813, 1853, 1815, 1675, 1576, 1436, 1333, 1226, 1158, 1145, 1158, 1216, 1298, 1407, 1503, 1640, 1754, 1816, 1908, 1800, 1691, 1536, 1422, 1296, 1188, 1114, 1095, 1114, 1174, 1268, 1388, 1485, 1623, 1742, 1851, 1865, 1783, 1646, 1513, 1378, 1236, 1124, 1071, 1050, 1074, 1132, 1211, 1333, 1463, 1603, 1713, 1829, 1822, 1736, 1621, 1486, 1358, 1211, 1109, 1040, 1024, 1037, 1101, 1197, 1314, 1423, 1559, 1683, 1788, 1829, 1769, 1635, 1513, 1371, 1231, 1128, 1057, 1033, 1057, 1112, 1202, 1327, 1455, 1572, 1700, 1794, 1870, 1831, 1679, 1554, 1430, 1290, 1170, 1103, 1091, 1107, 1165, 1263, 1374, 1501, 1623, 1742, 1833, 1911, 1863, 1724, 1586, 1459, 1352, 1236, 1171, 1153, 1171, 1221, 1315, 1414, 1520, 1663, 1799, 1872, 1913, 1861, 1730, 1626, 1511, 1397, 1296, 1242, 1221, 1227, 1279, 1350, 1446, 1555, 1691, 1779, 1852, 1934, 1893, 1804, 1703, 1576, 1475, 1396, 1329, 1309, 1336, 1363, 1437, 1538, 1634, 1747, 1839, 1868, 1955, 1991, 1910, 1808, 1696, 1596, 1537, 1472, 1445, 1457, 1494, 1539, 1617, 1739, 1825, 1928, 1860, 1818, 2015, 1981, 1906, 1778, 1680, 1627, 1585, 1551, 1566, 1596, 1646, 1725, 1824, 1902, 1945, 1794, 1571, 1937, 1977, 1932, 1866, 1784, 1714, 1674, 1642, 1662, 1678, 1730, 1788, 1859, 1913, 1912, 1592, ]
+ #2592x1944_D65_70 - D65
+ - ct: 6504
+ resolution: 2592x1944
+ r: [2457, 2985, 2981, 2763, 2587, 2383, 2222, 2123, 2089, 2123, 2167, 2270, 2466, 2638, 2823, 2805, 2457, 2770, 3097, 2893, 2640, 2410, 2169, 2039, 1933, 1908, 1914, 1973, 2117, 2295, 2514, 2728, 2953, 2735, 3009, 2991, 2771, 2467, 2201, 1985, 1825, 1726, 1679, 1703, 1791, 1924, 2085, 2345, 2583, 2806, 2898, 3015, 2906, 2586, 2267, 2005, 1790, 1629, 1527, 1488, 1505, 1597, 1734, 1923, 2169, 2447, 2714, 2876, 2953, 2756, 2435, 2120, 1832, 1617, 1462, 1359, 1326, 1351, 1423, 1573, 1774, 2014, 2285, 2612, 2857, 2963, 2676, 2324, 2016, 1735, 1499, 1334, 1234, 1201, 1227, 1313, 1452, 1649, 1893, 2177, 2503, 2754, 2883, 2582, 2252, 1912, 1634, 1401, 1236, 1144, 1106, 1135, 1215, 1365, 1570, 1804, 2091, 2443, 2715, 2839, 2555, 2196, 1860, 1576, 1346, 1180, 1084, 1046, 1077, 1161, 1305, 1501, 1767, 2056, 2384, 2678, 2797, 2546, 2165, 1832, 1546, 1314, 1150, 1060, 1024, 1046, 1133, 1275, 1474, 1726, 2030, 2378, 2667, 2811, 2555, 2169, 1843, 1564, 1321, 1161, 1069, 1032, 1057, 1146, 1289, 1496, 1751, 2021, 2350, 2653, 2883, 2603, 2195, 1884, 1614, 1388, 1219, 1116, 1077, 1107, 1196, 1335, 1529, 1787, 2079, 2406, 2689, 2900, 2630, 2293, 1963, 1677, 1462, 1294, 1194, 1157, 1181, 1274, 1403, 1622, 1847, 2163, 2464, 2727, 2920, 2731, 2400, 2071, 1798, 1567, 1404, 1301, 1264, 1293, 1376, 1514, 1711, 1949, 2224, 2568, 2767, 3015, 2820, 2545, 2196, 1933, 1719, 1554, 1452, 1422, 1442, 1525, 1661, 1847, 2078, 2358, 2639, 2780, 2971, 2927, 2674, 2396, 2110, 1904, 1767, 1654, 1611, 1627, 1720, 1848, 2026, 2250, 2540, 2722, 2863, 2842, 3023, 2864, 2576, 2311, 2105, 1952, 1857, 1808, 1830, 1912, 2033, 2205, 2417, 2652, 2822, 2667, 2489, 3024, 2981, 2737, 2546, 2317, 2180, 2086, 2041, 2050, 2140, 2255, 2391, 2615, 2735, 2840, 2366, ]
+ gr: [1766, 2092, 2109, 2006, 1875, 1775, 1707, 1659, 1633, 1646, 1679, 1754, 1844, 1954, 2045, 2041, 1740, 1981, 2142, 2048, 1911, 1779, 1678, 1597, 1549, 1529, 1539, 1570, 1630, 1728, 1848, 1970, 2064, 1971, 2109, 2107, 1982, 1820, 1673, 1563, 1494, 1442, 1423, 1433, 1472, 1538, 1630, 1751, 1899, 2019, 2058, 2121, 2066, 1892, 1719, 1584, 1472, 1386, 1331, 1311, 1326, 1370, 1441, 1533, 1673, 1820, 1956, 2062, 2080, 1982, 1807, 1636, 1493, 1379, 1293, 1236, 1213, 1230, 1280, 1353, 1458, 1580, 1729, 1885, 2017, 2074, 1934, 1756, 1584, 1435, 1318, 1220, 1163, 1142, 1154, 1207, 1280, 1393, 1522, 1666, 1844, 1990, 2041, 1886, 1711, 1535, 1392, 1269, 1165, 1106, 1086, 1103, 1151, 1240, 1356, 1479, 1635, 1802, 1969, 2006, 1856, 1673, 1506, 1359, 1220, 1131, 1067, 1041, 1056, 1113, 1201, 1312, 1446, 1594, 1771, 1937, 2000, 1841, 1654, 1489, 1334, 1201, 1105, 1046, 1024, 1038, 1096, 1183, 1299, 1428, 1577, 1746, 1925, 2006, 1850, 1656, 1490, 1339, 1210, 1112, 1054, 1028, 1044, 1098, 1188, 1296, 1431, 1574, 1754, 1923, 2033, 1868, 1692, 1518, 1366, 1242, 1143, 1085, 1060, 1074, 1133, 1214, 1329, 1460, 1602, 1780, 1938, 2040, 1900, 1722, 1547, 1409, 1291, 1192, 1131, 1107, 1125, 1174, 1258, 1363, 1488, 1644, 1813, 1958, 2052, 1939, 1770, 1592, 1461, 1346, 1254, 1192, 1174, 1186, 1236, 1312, 1410, 1535, 1690, 1846, 1975, 2071, 1986, 1843, 1664, 1533, 1424, 1338, 1280, 1256, 1269, 1309, 1387, 1475, 1596, 1753, 1898, 2006, 2058, 2045, 1906, 1756, 1622, 1517, 1432, 1380, 1363, 1372, 1412, 1480, 1566, 1691, 1835, 1955, 2008, 1971, 2083, 2008, 1842, 1718, 1606, 1530, 1488, 1463, 1468, 1506, 1574, 1675, 1772, 1904, 1992, 1922, 1748, 2103, 2063, 1961, 1838, 1724, 1648, 1600, 1596, 1592, 1627, 1690, 1780, 1890, 1969, 1992, 1713, ]
+ gb: [1749, 2093, 2072, 1983, 1869, 1765, 1684, 1638, 1621, 1629, 1666, 1734, 1838, 1925, 2019, 2021, 1722, 1981, 2142, 2048, 1904, 1774, 1660, 1582, 1535, 1512, 1528, 1563, 1626, 1728, 1854, 1970, 2064, 1961, 2088, 2107, 1975, 1809, 1668, 1556, 1481, 1424, 1406, 1421, 1456, 1528, 1626, 1761, 1886, 2028, 2068, 2111, 2049, 1873, 1715, 1569, 1465, 1376, 1323, 1300, 1321, 1363, 1432, 1536, 1660, 1808, 1956, 2062, 2089, 1975, 1797, 1632, 1493, 1374, 1284, 1228, 1205, 1226, 1273, 1351, 1449, 1577, 1729, 1898, 2035, 2083, 1934, 1751, 1584, 1441, 1307, 1214, 1156, 1134, 1153, 1203, 1280, 1393, 1526, 1675, 1844, 1998, 2049, 1905, 1702, 1535, 1390, 1265, 1160, 1103, 1078, 1100, 1150, 1238, 1351, 1485, 1631, 1814, 1984, 2014, 1868, 1678, 1506, 1356, 1218, 1123, 1065, 1039, 1055, 1112, 1201, 1317, 1446, 1602, 1782, 1952, 2008, 1853, 1658, 1496, 1344, 1203, 1110, 1046, 1024, 1037, 1091, 1179, 1292, 1428, 1588, 1757, 1947, 2030, 1856, 1660, 1493, 1346, 1212, 1116, 1049, 1024, 1040, 1093, 1190, 1303, 1440, 1590, 1760, 1937, 2041, 1886, 1688, 1522, 1376, 1240, 1146, 1083, 1057, 1074, 1131, 1218, 1331, 1466, 1614, 1785, 1953, 2066, 1920, 1737, 1558, 1415, 1289, 1186, 1130, 1110, 1123, 1172, 1254, 1368, 1492, 1644, 1814, 1974, 2080, 1953, 1775, 1612, 1461, 1343, 1254, 1194, 1174, 1186, 1236, 1309, 1413, 1528, 1695, 1852, 1983, 2081, 2009, 1837, 1678, 1543, 1424, 1338, 1278, 1254, 1273, 1306, 1390, 1485, 1604, 1758, 1905, 2016, 2078, 2062, 1926, 1777, 1626, 1517, 1441, 1388, 1363, 1367, 1412, 1487, 1574, 1686, 1835, 1962, 2018, 1981, 2112, 2016, 1848, 1733, 1614, 1541, 1488, 1469, 1468, 1520, 1570, 1666, 1789, 1911, 1992, 1913, 1776, 2082, 2072, 1968, 1856, 1739, 1657, 1600, 1577, 1592, 1627, 1695, 1786, 1883, 1977, 2002, 1722, ]
+ b: [1681, 1945, 1998, 1882, 1777, 1699, 1617, 1588, 1571, 1554, 1581, 1644, 1729, 1797, 1905, 1919, 1646, 1868, 2012, 1964, 1828, 1711, 1617, 1535, 1492, 1479, 1478, 1509, 1559, 1636, 1737, 1860, 1925, 1830, 1961, 2001, 1890, 1754, 1638, 1529, 1463, 1407, 1389, 1407, 1432, 1485, 1574, 1668, 1790, 1898, 1922, 1995, 1962, 1813, 1680, 1557, 1453, 1378, 1319, 1297, 1302, 1348, 1418, 1505, 1605, 1726, 1868, 1944, 2004, 1901, 1765, 1611, 1482, 1375, 1287, 1230, 1207, 1224, 1259, 1338, 1420, 1528, 1664, 1807, 1921, 1969, 1858, 1708, 1557, 1434, 1317, 1217, 1161, 1142, 1156, 1206, 1275, 1369, 1481, 1598, 1764, 1880, 1973, 1821, 1664, 1516, 1392, 1270, 1165, 1106, 1085, 1095, 1152, 1231, 1336, 1445, 1567, 1725, 1856, 1947, 1804, 1647, 1495, 1359, 1230, 1136, 1067, 1043, 1060, 1115, 1197, 1299, 1419, 1548, 1695, 1834, 1924, 1787, 1623, 1478, 1346, 1212, 1114, 1052, 1024, 1044, 1094, 1172, 1287, 1408, 1532, 1681, 1853, 1925, 1804, 1641, 1481, 1351, 1225, 1124, 1056, 1032, 1046, 1099, 1181, 1296, 1410, 1531, 1688, 1806, 1951, 1821, 1664, 1516, 1377, 1255, 1150, 1089, 1066, 1082, 1128, 1214, 1315, 1432, 1562, 1709, 1856, 1957, 1840, 1688, 1546, 1413, 1297, 1190, 1139, 1116, 1130, 1179, 1259, 1347, 1462, 1592, 1740, 1859, 1968, 1881, 1728, 1588, 1460, 1345, 1265, 1199, 1180, 1191, 1241, 1307, 1391, 1498, 1644, 1773, 1876, 2008, 1940, 1789, 1654, 1531, 1427, 1341, 1286, 1265, 1273, 1316, 1370, 1471, 1569, 1696, 1830, 1896, 2002, 1977, 1871, 1732, 1620, 1519, 1432, 1387, 1362, 1364, 1402, 1466, 1535, 1654, 1782, 1877, 1896, 1895, 2025, 1975, 1828, 1704, 1599, 1540, 1478, 1456, 1459, 1499, 1548, 1636, 1737, 1841, 1925, 1830, 1705, 2013, 2036, 1912, 1785, 1720, 1636, 1588, 1565, 1576, 1599, 1664, 1722, 1815, 1905, 1945, 1681, ]
+ #2592x1944_F2_CWF_70 - F2_CWF
+ - ct: 4230
+ resolution: 2592x1944
+ r: [2512, 2860, 2753, 2554, 2376, 2198, 2033, 1949, 1924, 1921, 2012, 2100, 2257, 2461, 2682, 2775, 2436, 2753, 2915, 2713, 2415, 2193, 2004, 1869, 1790, 1755, 1774, 1844, 1945, 2108, 2306, 2547, 2755, 2697, 2849, 2810, 2526, 2247, 2018, 1821, 1692, 1608, 1577, 1591, 1653, 1775, 1921, 2132, 2371, 2625, 2765, 2881, 2679, 2376, 2077, 1853, 1677, 1542, 1449, 1412, 1430, 1511, 1615, 1781, 1983, 2258, 2517, 2722, 2832, 2589, 2237, 1977, 1718, 1527, 1403, 1319, 1290, 1307, 1370, 1491, 1658, 1850, 2112, 2408, 2708, 2718, 2474, 2154, 1861, 1616, 1439, 1293, 1211, 1176, 1205, 1275, 1390, 1553, 1773, 2008, 2313, 2607, 2661, 2388, 2066, 1781, 1535, 1359, 1207, 1130, 1098, 1117, 1192, 1313, 1474, 1688, 1934, 2240, 2537, 2672, 2353, 2024, 1733, 1494, 1296, 1162, 1075, 1045, 1064, 1146, 1261, 1422, 1640, 1889, 2197, 2528, 2599, 2332, 1991, 1718, 1484, 1276, 1139, 1051, 1024, 1051, 1117, 1245, 1409, 1620, 1861, 2179, 2481, 2651, 2338, 2004, 1719, 1479, 1289, 1146, 1066, 1034, 1055, 1127, 1248, 1413, 1633, 1872, 2184, 2471, 2640, 2372, 2045, 1751, 1514, 1324, 1189, 1107, 1064, 1097, 1163, 1280, 1455, 1661, 1915, 2226, 2498, 2672, 2457, 2107, 1820, 1587, 1390, 1248, 1170, 1132, 1155, 1235, 1353, 1510, 1729, 1967, 2268, 2544, 2781, 2532, 2198, 1920, 1678, 1486, 1349, 1251, 1225, 1251, 1326, 1438, 1602, 1800, 2043, 2343, 2616, 2826, 2637, 2330, 2024, 1796, 1609, 1480, 1391, 1365, 1370, 1442, 1556, 1714, 1915, 2190, 2461, 2673, 2820, 2738, 2472, 2182, 1949, 1760, 1640, 1545, 1517, 1524, 1591, 1716, 1867, 2073, 2308, 2561, 2686, 2782, 2806, 2648, 2352, 2132, 1926, 1819, 1716, 1678, 1702, 1757, 1872, 2029, 2234, 2434, 2611, 2617, 2538, 2919, 2777, 2554, 2345, 2148, 2012, 1940, 1896, 1930, 1961, 2065, 2243, 2426, 2592, 2669, 2461, ]
+ gr: [2065, 2350, 2320, 2148, 2002, 1877, 1794, 1730, 1709, 1712, 1754, 1837, 1948, 2082, 2217, 2291, 2054, 2263, 2359, 2204, 2022, 1860, 1735, 1639, 1583, 1560, 1576, 1619, 1694, 1805, 1967, 2126, 2281, 2228, 2353, 2294, 2112, 1897, 1724, 1615, 1525, 1460, 1441, 1448, 1499, 1581, 1684, 1829, 2000, 2187, 2305, 2354, 2194, 1994, 1785, 1626, 1493, 1406, 1349, 1323, 1342, 1384, 1468, 1576, 1722, 1909, 2100, 2265, 2281, 2126, 1894, 1708, 1539, 1409, 1310, 1253, 1225, 1240, 1291, 1377, 1486, 1639, 1821, 2019, 2220, 2257, 2059, 1819, 1622, 1464, 1337, 1233, 1168, 1144, 1161, 1219, 1302, 1420, 1576, 1733, 1934, 2180, 2189, 1991, 1759, 1578, 1407, 1280, 1164, 1107, 1085, 1100, 1157, 1242, 1359, 1514, 1685, 1894, 2110, 2153, 1954, 1726, 1537, 1365, 1229, 1129, 1066, 1039, 1057, 1114, 1202, 1327, 1471, 1638, 1850, 2094, 2153, 1948, 1718, 1522, 1352, 1217, 1114, 1047, 1024, 1038, 1100, 1187, 1310, 1467, 1627, 1851, 2078, 2162, 1947, 1716, 1527, 1367, 1225, 1125, 1054, 1031, 1045, 1106, 1198, 1320, 1465, 1638, 1861, 2094, 2180, 1964, 1731, 1545, 1383, 1252, 1145, 1085, 1057, 1070, 1131, 1223, 1341, 1488, 1658, 1852, 2077, 2199, 2002, 1787, 1584, 1429, 1297, 1194, 1131, 1109, 1124, 1181, 1266, 1384, 1523, 1695, 1908, 2118, 2260, 2071, 1843, 1651, 1502, 1364, 1265, 1203, 1181, 1197, 1244, 1331, 1451, 1579, 1763, 1969, 2153, 2276, 2150, 1922, 1736, 1573, 1453, 1355, 1296, 1275, 1285, 1335, 1417, 1526, 1663, 1849, 2052, 2203, 2294, 2205, 2029, 1834, 1666, 1548, 1461, 1399, 1372, 1390, 1431, 1513, 1620, 1760, 1931, 2115, 2237, 2228, 2271, 2126, 1934, 1784, 1650, 1577, 1512, 1485, 1506, 1547, 1625, 1729, 1872, 2029, 2189, 2160, 2033, 2326, 2227, 2106, 1935, 1815, 1721, 1671, 1627, 1654, 1688, 1768, 1885, 2021, 2160, 2245, 2022, ]
+ gb: [2062, 2335, 2286, 2148, 1975, 1850, 1776, 1709, 1688, 1709, 1761, 1822, 1943, 2082, 2226, 2300, 2062, 2272, 2345, 2186, 2016, 1856, 1728, 1637, 1579, 1556, 1564, 1610, 1691, 1807, 1961, 2126, 2280, 2237, 2338, 2293, 2081, 1893, 1731, 1594, 1501, 1444, 1424, 1441, 1485, 1572, 1677, 1830, 2022, 2195, 2303, 2352, 2212, 1988, 1782, 1625, 1499, 1400, 1342, 1318, 1335, 1379, 1468, 1579, 1728, 1898, 2116, 2274, 2311, 2127, 1896, 1701, 1538, 1404, 1308, 1249, 1218, 1243, 1290, 1382, 1491, 1641, 1828, 2041, 2249, 2256, 2060, 1820, 1637, 1476, 1335, 1234, 1166, 1147, 1159, 1220, 1302, 1428, 1586, 1754, 1968, 2198, 2225, 2013, 1781, 1584, 1421, 1281, 1166, 1101, 1082, 1105, 1158, 1246, 1372, 1524, 1696, 1914, 2144, 2179, 1961, 1742, 1546, 1378, 1232, 1136, 1064, 1042, 1061, 1118, 1208, 1335, 1489, 1661, 1875, 2110, 2179, 1962, 1734, 1538, 1367, 1224, 1117, 1051, 1024, 1046, 1106, 1195, 1322, 1479, 1658, 1876, 2094, 2179, 1988, 1742, 1543, 1375, 1232, 1128, 1060, 1030, 1050, 1110, 1208, 1330, 1486, 1652, 1881, 2127, 2197, 2006, 1761, 1562, 1396, 1255, 1152, 1086, 1063, 1077, 1137, 1232, 1354, 1504, 1682, 1902, 2135, 2236, 2031, 1810, 1605, 1449, 1311, 1200, 1137, 1110, 1130, 1185, 1275, 1389, 1539, 1720, 1922, 2161, 2290, 2103, 1873, 1675, 1504, 1379, 1276, 1211, 1184, 1202, 1251, 1339, 1460, 1593, 1785, 1983, 2180, 2329, 2176, 1961, 1752, 1598, 1471, 1366, 1308, 1279, 1292, 1348, 1432, 1535, 1682, 1874, 2068, 2222, 2338, 2253, 2059, 1852, 1686, 1565, 1473, 1410, 1385, 1393, 1445, 1522, 1639, 1782, 1959, 2132, 2257, 2272, 2312, 2160, 1961, 1802, 1674, 1587, 1525, 1497, 1508, 1557, 1644, 1741, 1897, 2045, 2197, 2202, 2095, 2335, 2276, 2098, 1969, 1828, 1732, 1669, 1641, 1656, 1699, 1785, 1886, 2036, 2188, 2254, 2030, ]
+ b: [1957, 2184, 2113, 2000, 1876, 1757, 1686, 1620, 1614, 1596, 1649, 1687, 1805, 1914, 2027, 2082, 1880, 2101, 2170, 2056, 1894, 1763, 1659, 1571, 1527, 1501, 1506, 1541, 1608, 1694, 1809, 1964, 2094, 2040, 2156, 2121, 1964, 1796, 1654, 1563, 1485, 1419, 1399, 1407, 1447, 1499, 1587, 1724, 1859, 2019, 2076, 2184, 2063, 1888, 1705, 1586, 1470, 1383, 1330, 1299, 1315, 1352, 1421, 1513, 1633, 1794, 1956, 2125, 2153, 2012, 1821, 1660, 1511, 1395, 1302, 1241, 1219, 1232, 1275, 1352, 1453, 1570, 1726, 1914, 2080, 2106, 1953, 1751, 1601, 1462, 1333, 1235, 1171, 1142, 1156, 1207, 1285, 1403, 1520, 1656, 1838, 2038, 2081, 1885, 1704, 1553, 1398, 1266, 1166, 1101, 1079, 1097, 1151, 1240, 1340, 1471, 1616, 1780, 1970, 2041, 1882, 1686, 1513, 1364, 1235, 1125, 1065, 1037, 1054, 1108, 1196, 1299, 1429, 1576, 1756, 1935, 2049, 1853, 1665, 1504, 1363, 1227, 1118, 1049, 1024, 1035, 1099, 1188, 1298, 1434, 1582, 1752, 1929, 2073, 1870, 1677, 1520, 1364, 1240, 1131, 1057, 1037, 1048, 1102, 1188, 1308, 1442, 1600, 1756, 1921, 2048, 1885, 1695, 1525, 1387, 1248, 1148, 1085, 1064, 1076, 1131, 1215, 1325, 1458, 1591, 1780, 1926, 2089, 1926, 1731, 1563, 1432, 1304, 1191, 1132, 1112, 1129, 1172, 1258, 1359, 1492, 1647, 1814, 1975, 2115, 1983, 1799, 1626, 1491, 1368, 1270, 1212, 1188, 1204, 1249, 1322, 1416, 1548, 1697, 1874, 2045, 2164, 2047, 1888, 1705, 1571, 1451, 1357, 1296, 1276, 1291, 1336, 1404, 1499, 1616, 1772, 1956, 2069, 2177, 2139, 1964, 1785, 1654, 1549, 1459, 1402, 1376, 1385, 1423, 1493, 1587, 1704, 1847, 2003, 2057, 2144, 2190, 2056, 1906, 1753, 1642, 1556, 1506, 1488, 1485, 1534, 1592, 1684, 1809, 1935, 2076, 2081, 1997, 2228, 2150, 2030, 1888, 1799, 1704, 1637, 1631, 1629, 1667, 1716, 1816, 1914, 2043, 2122, 1917, ]
+ #2592x1944_D50_70 - D50
+ - ct: 5003
+ resolution: 2592x1944
+ r: [2445, 2929, 2967, 2734, 2576, 2380, 2211, 2113, 2074, 2072, 2166, 2255, 2383, 2626, 2861, 2812, 2411, 2795, 3067, 2915, 2660, 2369, 2162, 2038, 1940, 1900, 1919, 1978, 2106, 2281, 2519, 2702, 2875, 2718, 2953, 3006, 2761, 2452, 2197, 1964, 1815, 1720, 1676, 1712, 1769, 1899, 2070, 2268, 2581, 2739, 2798, 3022, 2895, 2570, 2275, 2011, 1793, 1619, 1512, 1486, 1506, 1577, 1740, 1898, 2123, 2420, 2659, 2869, 2939, 2776, 2457, 2132, 1863, 1619, 1479, 1366, 1332, 1356, 1435, 1571, 1769, 1978, 2272, 2543, 2736, 2905, 2703, 2360, 2023, 1747, 1516, 1355, 1247, 1214, 1243, 1332, 1457, 1651, 1898, 2194, 2488, 2714, 2945, 2615, 2257, 1937, 1653, 1419, 1242, 1151, 1117, 1138, 1219, 1374, 1575, 1795, 2080, 2417, 2695, 2795, 2558, 2207, 1875, 1586, 1350, 1182, 1089, 1046, 1084, 1158, 1305, 1497, 1736, 2027, 2351, 2624, 2840, 2547, 2201, 1863, 1566, 1323, 1172, 1068, 1024, 1057, 1142, 1288, 1484, 1725, 2010, 2343, 2584, 2857, 2580, 2222, 1875, 1573, 1355, 1182, 1086, 1046, 1072, 1151, 1301, 1509, 1762, 2052, 2371, 2707, 2912, 2615, 2257, 1904, 1631, 1389, 1227, 1129, 1090, 1122, 1197, 1331, 1529, 1777, 2040, 2397, 2639, 2905, 2628, 2290, 1987, 1698, 1457, 1296, 1202, 1154, 1181, 1259, 1398, 1607, 1826, 2119, 2466, 2684, 2939, 2748, 2399, 2078, 1796, 1584, 1424, 1310, 1276, 1297, 1377, 1519, 1708, 1943, 2222, 2543, 2736, 2982, 2863, 2570, 2243, 1964, 1740, 1570, 1470, 1435, 1448, 1537, 1683, 1856, 2094, 2342, 2632, 2798, 3037, 2970, 2681, 2413, 2111, 1920, 1769, 1672, 1616, 1634, 1709, 1847, 2019, 2234, 2488, 2709, 2835, 2836, 3026, 2851, 2611, 2315, 2106, 1932, 1836, 1801, 1807, 1899, 2027, 2199, 2392, 2620, 2805, 2644, 2515, 3013, 2967, 2792, 2553, 2343, 2181, 2046, 2035, 2033, 2108, 2239, 2444, 2575, 2731, 2812, 2411, ]
+ gr: [1764, 2120, 2133, 2015, 1886, 1783, 1704, 1644, 1626, 1631, 1666, 1739, 1792, 1938, 2020, 2014, 1727, 1988, 2163, 2079, 1945, 1797, 1681, 1595, 1551, 1526, 1533, 1567, 1619, 1707, 1833, 1963, 2052, 1936, 2115, 2119, 1964, 1824, 1676, 1555, 1486, 1428, 1406, 1425, 1447, 1526, 1623, 1720, 1866, 2001, 2030, 2142, 2062, 1902, 1716, 1580, 1465, 1376, 1321, 1301, 1314, 1355, 1428, 1513, 1645, 1791, 1941, 2022, 2104, 1988, 1816, 1663, 1515, 1388, 1294, 1235, 1215, 1225, 1271, 1350, 1449, 1571, 1719, 1880, 2028, 2113, 1963, 1766, 1588, 1445, 1325, 1231, 1168, 1142, 1155, 1213, 1284, 1392, 1517, 1662, 1835, 1980, 2065, 1897, 1712, 1544, 1394, 1268, 1163, 1105, 1080, 1097, 1147, 1225, 1348, 1464, 1603, 1780, 1948, 2044, 1877, 1672, 1512, 1355, 1223, 1127, 1057, 1038, 1052, 1107, 1193, 1312, 1437, 1593, 1741, 1931, 2004, 1873, 1674, 1501, 1350, 1211, 1113, 1048, 1024, 1038, 1095, 1180, 1301, 1424, 1571, 1738, 1895, 2027, 1871, 1681, 1506, 1361, 1227, 1123, 1064, 1035, 1057, 1104, 1189, 1310, 1440, 1573, 1758, 1916, 2048, 1884, 1707, 1526, 1374, 1248, 1154, 1087, 1069, 1073, 1128, 1205, 1317, 1455, 1590, 1757, 1925, 2031, 1907, 1720, 1557, 1406, 1289, 1193, 1129, 1104, 1116, 1170, 1244, 1348, 1478, 1621, 1792, 1947, 2075, 1973, 1777, 1615, 1465, 1355, 1269, 1195, 1176, 1184, 1234, 1302, 1412, 1532, 1669, 1826, 1975, 2100, 2028, 1870, 1687, 1542, 1443, 1352, 1294, 1264, 1278, 1324, 1393, 1492, 1602, 1757, 1911, 2031, 2093, 2054, 1935, 1763, 1631, 1529, 1441, 1393, 1361, 1371, 1419, 1480, 1569, 1690, 1827, 1960, 2020, 1957, 2091, 1979, 1864, 1722, 1619, 1529, 1484, 1458, 1471, 1497, 1557, 1654, 1761, 1918, 2005, 1907, 1783, 2076, 2094, 1938, 1829, 1729, 1657, 1592, 1571, 1572, 1616, 1664, 1769, 1880, 1968, 1994, 1718, ]
+ gb: [1771, 2117, 2122, 1999, 1887, 1768, 1691, 1633, 1619, 1633, 1668, 1736, 1836, 1923, 2010, 2002, 1734, 2040, 2161, 2070, 1925, 1777, 1678, 1601, 1532, 1528, 1518, 1562, 1625, 1724, 1840, 1956, 2079, 1954, 2091, 2109, 1965, 1826, 1669, 1561, 1472, 1419, 1400, 1422, 1450, 1521, 1608, 1732, 1867, 2001, 2028, 2151, 2053, 1877, 1718, 1579, 1465, 1379, 1319, 1296, 1309, 1350, 1428, 1530, 1647, 1792, 1934, 2030, 2112, 2003, 1824, 1656, 1511, 1388, 1296, 1240, 1206, 1228, 1271, 1347, 1458, 1577, 1725, 1894, 2018, 2112, 1978, 1778, 1602, 1451, 1325, 1231, 1165, 1141, 1154, 1207, 1292, 1397, 1530, 1687, 1849, 2030, 2056, 1911, 1723, 1554, 1396, 1271, 1165, 1103, 1077, 1100, 1148, 1236, 1343, 1477, 1626, 1798, 1972, 2027, 1885, 1692, 1522, 1358, 1225, 1126, 1068, 1038, 1055, 1105, 1194, 1313, 1443, 1583, 1771, 1931, 2037, 1868, 1690, 1514, 1355, 1216, 1116, 1053, 1024, 1046, 1096, 1191, 1306, 1433, 1586, 1762, 1925, 2061, 1891, 1688, 1522, 1363, 1236, 1128, 1067, 1037, 1059, 1110, 1196, 1318, 1439, 1596, 1765, 1977, 2056, 1898, 1709, 1535, 1391, 1264, 1157, 1089, 1069, 1076, 1131, 1216, 1335, 1467, 1596, 1775, 1948, 2048, 1929, 1737, 1567, 1427, 1294, 1198, 1130, 1106, 1120, 1168, 1260, 1353, 1491, 1641, 1811, 1963, 2112, 1988, 1795, 1626, 1484, 1374, 1274, 1198, 1174, 1190, 1237, 1317, 1427, 1538, 1695, 1840, 2000, 2140, 2045, 1877, 1708, 1567, 1443, 1360, 1304, 1267, 1288, 1337, 1398, 1491, 1621, 1781, 1919, 2039, 2112, 2109, 1936, 1792, 1633, 1539, 1450, 1396, 1377, 1376, 1422, 1496, 1579, 1697, 1835, 1976, 2028, 2029, 2089, 2028, 1884, 1734, 1638, 1543, 1490, 1460, 1466, 1514, 1579, 1670, 1774, 1910, 2013, 1904, 1790, 2117, 2065, 1961, 1854, 1752, 1672, 1616, 1590, 1599, 1623, 1700, 1782, 1867, 1984, 2022, 1698, ]
+ b: [1676, 1930, 1956, 1924, 1811, 1685, 1640, 1571, 1556, 1544, 1569, 1639, 1710, 1802, 1890, 1881, 1642, 1930, 2013, 1952, 1827, 1711, 1616, 1538, 1488, 1472, 1470, 1494, 1560, 1632, 1724, 1825, 1906, 1803, 1985, 2007, 1894, 1759, 1625, 1524, 1440, 1401, 1380, 1385, 1411, 1463, 1537, 1649, 1765, 1876, 1884, 1996, 1961, 1831, 1676, 1555, 1444, 1367, 1301, 1282, 1295, 1328, 1383, 1468, 1580, 1708, 1833, 1900, 2020, 1914, 1777, 1618, 1508, 1382, 1284, 1227, 1197, 1216, 1251, 1325, 1408, 1511, 1639, 1796, 1915, 1998, 1901, 1716, 1581, 1447, 1327, 1226, 1169, 1134, 1155, 1199, 1269, 1368, 1486, 1608, 1741, 1879, 1959, 1838, 1674, 1531, 1387, 1269, 1158, 1094, 1072, 1082, 1132, 1217, 1323, 1431, 1568, 1706, 1847, 1956, 1806, 1645, 1497, 1352, 1222, 1124, 1059, 1031, 1049, 1093, 1177, 1292, 1398, 1528, 1686, 1800, 1945, 1806, 1634, 1494, 1357, 1211, 1110, 1049, 1024, 1034, 1080, 1174, 1277, 1388, 1519, 1673, 1809, 1989, 1822, 1664, 1497, 1366, 1239, 1115, 1065, 1033, 1049, 1095, 1183, 1295, 1406, 1544, 1679, 1855, 1981, 1838, 1674, 1512, 1384, 1260, 1151, 1086, 1062, 1069, 1121, 1198, 1303, 1423, 1540, 1691, 1847, 1964, 1856, 1683, 1550, 1422, 1294, 1189, 1122, 1103, 1113, 1164, 1237, 1332, 1446, 1574, 1741, 1859, 2008, 1885, 1755, 1606, 1471, 1371, 1263, 1197, 1169, 1182, 1228, 1298, 1392, 1501, 1620, 1763, 1883, 2034, 1950, 1823, 1676, 1540, 1439, 1353, 1298, 1269, 1276, 1325, 1383, 1468, 1575, 1700, 1833, 1923, 2012, 1995, 1894, 1744, 1625, 1519, 1440, 1389, 1361, 1370, 1403, 1467, 1558, 1642, 1773, 1876, 1908, 1903, 2038, 1942, 1844, 1704, 1599, 1528, 1484, 1445, 1457, 1494, 1544, 1602, 1724, 1843, 1906, 1827, 1724, 2051, 2027, 1914, 1827, 1698, 1640, 1577, 1566, 1588, 1604, 1633, 1717, 1811, 1901, 1930, 1665, ]
+
+...
diff --git a/src/ipa/rkisp1/data/ov8858.yaml b/src/ipa/rkisp1/data/ov8858.yaml
new file mode 100644
index 00000000..f297b0e0
--- /dev/null
+++ b/src/ipa/rkisp1/data/ov8858.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+ - Agc:
+ - Awb:
+ - LensShadingCorrection:
+ x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
+ sets:
+ #3264x2448_A_70 - A
+ - ct: 2856
+ resolution: 3264x2448
+ r: [4095, 3932, 3584, 3324, 3113, 2934, 2747, 2619, 2566, 2579, 2671, 2816, 3009, 3217, 3444, 3843, 4095, 4095, 3658, 3343, 3088, 2867, 2620, 2404, 2271, 2207, 2229, 2315, 2485, 2727, 2965, 3232, 3500, 4057, 3926, 3482, 3187, 2914, 2612, 2330, 2112, 1976, 1917, 1931, 2028, 2198, 2456, 2762, 3042, 3335, 3770, 3739, 3331, 3029, 2720, 2364, 2070, 1852, 1718, 1655, 1669, 1765, 1940, 2207, 2538, 2878, 3183, 3565, 3590, 3209, 2910, 2524, 2156, 1860, 1642, 1493, 1431, 1446, 1551, 1734, 1986, 2338, 2721, 3075, 3405, 3484, 3116, 2778, 2373, 1997, 1698, 1466, 1315, 1254, 1272, 1374, 1562, 1825, 2169, 2587, 2946, 3317, 3415, 3044, 2682, 2252, 1873, 1574, 1336, 1192, 1126, 1146, 1249, 1437, 1712, 2050, 2462, 2877, 3238, 3355, 3002, 2619, 2171, 1800, 1490, 1259, 1112, 1051, 1073, 1173, 1359, 1635, 1977, 2388, 2813, 3182, 3348, 2969, 2587, 2138, 1768, 1457, 1228, 1085, 1024, 1043, 1144, 1326, 1603, 1950, 2364, 2783, 3170, 3344, 2984, 2594, 2152, 1776, 1468, 1239, 1098, 1041, 1061, 1161, 1342, 1617, 1962, 2373, 2798, 3177, 3388, 3011, 2637, 2207, 1829, 1528, 1298, 1158, 1100, 1120, 1217, 1408, 1677, 2018, 2429, 2841, 3192, 3442, 3064, 2718, 2301, 1929, 1633, 1405, 1263, 1205, 1224, 1326, 1513, 1777, 2119, 2525, 2903, 3274, 3557, 3138, 2822, 2435, 2066, 1775, 1558, 1414, 1355, 1378, 1478, 1663, 1927, 2255, 2657, 2987, 3369, 3682, 3256, 2940, 2604, 2252, 1958, 1748, 1609, 1557, 1576, 1677, 1857, 2106, 2445, 2793, 3096, 3526, 3874, 3380, 3075, 2783, 2472, 2189, 1974, 1846, 1790, 1811, 1909, 2086, 2342, 2643, 2934, 3247, 3743, 4095, 3583, 3218, 2950, 2708, 2456, 2257, 2114, 2064, 2083, 2185, 2364, 2598, 2856, 3111, 3444, 4045, 4095, 3842, 3474, 3155, 2950, 2731, 2575, 2440, 2388, 2413, 2499, 2659, 2846, 3056, 3334, 3796, 4095, ]
+ gr: [3246, 2753, 2547, 2359, 2249, 2148, 2052, 1977, 1938, 1947, 1995, 2082, 2183, 2277, 2411, 2655, 2957, 2906, 2568, 2361, 2223, 2092, 1964, 1850, 1767, 1735, 1740, 1790, 1881, 2002, 2124, 2265, 2437, 2751, 2740, 2449, 2261, 2106, 1950, 1798, 1681, 1604, 1570, 1577, 1626, 1714, 1846, 2012, 2149, 2322, 2581, 2628, 2348, 2169, 2000, 1808, 1654, 1539, 1460, 1419, 1429, 1483, 1576, 1710, 1881, 2062, 2231, 2443, 2541, 2279, 2102, 1891, 1687, 1536, 1420, 1330, 1289, 1298, 1362, 1459, 1589, 1773, 1967, 2168, 2352, 2459, 2226, 2027, 1797, 1599, 1442, 1313, 1221, 1179, 1190, 1253, 1359, 1497, 1675, 1898, 2100, 2286, 2406, 2180, 1976, 1732, 1531, 1369, 1231, 1140, 1096, 1109, 1174, 1284, 1431, 1608, 1824, 2055, 2245, 2374, 2148, 1928, 1684, 1484, 1317, 1178, 1084, 1043, 1058, 1122, 1234, 1387, 1562, 1785, 2020, 2218, 2363, 2140, 1910, 1663, 1464, 1292, 1156, 1063, 1024, 1036, 1102, 1214, 1363, 1547, 1762, 2004, 2194, 2366, 2136, 1917, 1670, 1469, 1302, 1163, 1073, 1032, 1047, 1111, 1223, 1373, 1552, 1775, 2009, 2206, 2383, 2158, 1940, 1703, 1506, 1339, 1201, 1112, 1072, 1087, 1150, 1265, 1408, 1584, 1805, 2030, 2228, 2434, 2189, 1994, 1757, 1557, 1400, 1270, 1181, 1142, 1154, 1218, 1328, 1468, 1640, 1860, 2068, 2267, 2497, 2235, 2043, 1837, 1630, 1477, 1360, 1273, 1238, 1249, 1310, 1412, 1544, 1725, 1924, 2124, 2329, 2592, 2305, 2109, 1925, 1731, 1576, 1460, 1384, 1350, 1364, 1422, 1513, 1648, 1818, 2009, 2174, 2427, 2699, 2379, 2188, 2022, 1860, 1696, 1588, 1510, 1480, 1489, 1543, 1637, 1771, 1937, 2072, 2269, 2546, 2862, 2514, 2276, 2120, 1983, 1850, 1737, 1664, 1628, 1642, 1695, 1787, 1914, 2043, 2182, 2390, 2734, 3175, 2661, 2434, 2232, 2119, 2004, 1921, 1849, 1813, 1816, 1874, 1959, 2049, 2159, 2317, 2604, 2891, ]
+ gb: [3248, 2762, 2549, 2352, 2241, 2135, 2024, 1949, 1910, 1923, 1970, 2058, 2167, 2278, 2427, 2679, 3003, 2939, 2581, 2369, 2212, 2084, 1945, 1829, 1743, 1710, 1713, 1773, 1861, 1999, 2127, 2278, 2456, 2799, 2766, 2468, 2268, 2114, 1949, 1788, 1666, 1587, 1550, 1557, 1612, 1711, 1849, 2022, 2168, 2354, 2627, 2659, 2372, 2185, 2003, 1808, 1646, 1531, 1447, 1404, 1415, 1474, 1573, 1711, 1896, 2082, 2269, 2494, 2572, 2297, 2122, 1903, 1694, 1534, 1411, 1322, 1278, 1294, 1356, 1459, 1599, 1796, 2003, 2204, 2415, 2494, 2259, 2053, 1813, 1609, 1442, 1310, 1216, 1174, 1186, 1254, 1368, 1512, 1699, 1934, 2147, 2352, 2450, 2219, 2006, 1751, 1543, 1372, 1233, 1134, 1096, 1108, 1175, 1292, 1449, 1639, 1865, 2103, 2311, 2424, 2182, 1960, 1705, 1498, 1324, 1181, 1086, 1041, 1059, 1127, 1245, 1404, 1594, 1828, 2078, 2281, 2405, 2182, 1937, 1687, 1480, 1301, 1161, 1062, 1024, 1038, 1107, 1224, 1384, 1581, 1812, 2057, 2272, 2417, 2181, 1951, 1695, 1487, 1312, 1167, 1074, 1032, 1050, 1118, 1235, 1397, 1586, 1820, 2069, 2278, 2450, 2196, 1974, 1724, 1522, 1348, 1205, 1113, 1075, 1089, 1153, 1276, 1430, 1619, 1849, 2095, 2291, 2483, 2229, 2022, 1779, 1573, 1408, 1272, 1181, 1142, 1156, 1223, 1339, 1488, 1673, 1905, 2123, 2343, 2541, 2277, 2079, 1856, 1643, 1485, 1361, 1270, 1235, 1248, 1313, 1421, 1566, 1751, 1971, 2173, 2399, 2635, 2339, 2138, 1944, 1745, 1580, 1458, 1380, 1344, 1359, 1418, 1519, 1661, 1849, 2048, 2222, 2487, 2743, 2413, 2216, 2037, 1864, 1702, 1579, 1500, 1467, 1479, 1537, 1642, 1777, 1958, 2108, 2315, 2617, 2890, 2544, 2293, 2131, 1988, 1842, 1726, 1651, 1612, 1628, 1684, 1783, 1920, 2060, 2213, 2432, 2804, 3189, 2693, 2445, 2245, 2116, 2000, 1902, 1826, 1789, 1798, 1857, 1950, 2045, 2170, 2337, 2642, 2952, ]
+ b: [3058, 2592, 2385, 2213, 2113, 2016, 1936, 1869, 1845, 1844, 1887, 1965, 2056, 2162, 2288, 2535, 2815, 2739, 2411, 2208, 2067, 1959, 1848, 1747, 1681, 1655, 1659, 1709, 1788, 1909, 2024, 2149, 2317, 2640, 2595, 2298, 2119, 1981, 1836, 1704, 1608, 1543, 1517, 1519, 1561, 1646, 1774, 1925, 2042, 2217, 2463, 2469, 2218, 2033, 1880, 1710, 1575, 1479, 1419, 1384, 1398, 1439, 1527, 1647, 1810, 1968, 2125, 2330, 2404, 2138, 1979, 1785, 1611, 1474, 1374, 1303, 1271, 1280, 1336, 1421, 1545, 1706, 1895, 2058, 2261, 2341, 2104, 1920, 1713, 1535, 1397, 1284, 1203, 1168, 1181, 1237, 1339, 1462, 1631, 1822, 2012, 2194, 2293, 2063, 1882, 1662, 1480, 1336, 1206, 1128, 1092, 1106, 1165, 1270, 1407, 1565, 1767, 1965, 2158, 2262, 2048, 1845, 1625, 1450, 1289, 1165, 1079, 1041, 1057, 1122, 1223, 1370, 1534, 1725, 1940, 2129, 2258, 2046, 1834, 1605, 1433, 1273, 1147, 1058, 1024, 1037, 1102, 1209, 1352, 1519, 1711, 1928, 2110, 2261, 2041, 1847, 1615, 1442, 1282, 1151, 1069, 1028, 1048, 1109, 1218, 1359, 1523, 1716, 1927, 2124, 2282, 2064, 1864, 1645, 1461, 1316, 1184, 1103, 1070, 1083, 1143, 1249, 1389, 1552, 1745, 1948, 2141, 2326, 2090, 1907, 1695, 1505, 1362, 1247, 1164, 1133, 1144, 1202, 1307, 1436, 1597, 1794, 1985, 2182, 2380, 2132, 1952, 1758, 1569, 1429, 1323, 1247, 1215, 1229, 1283, 1379, 1506, 1669, 1851, 2025, 2222, 2458, 2187, 2000, 1835, 1653, 1511, 1407, 1344, 1314, 1326, 1374, 1461, 1583, 1749, 1916, 2069, 2319, 2559, 2255, 2066, 1910, 1757, 1616, 1512, 1450, 1427, 1431, 1481, 1565, 1688, 1850, 1970, 2151, 2432, 2700, 2384, 2151, 1995, 1874, 1747, 1637, 1577, 1552, 1563, 1610, 1689, 1817, 1934, 2064, 2254, 2607, 3019, 2498, 2301, 2107, 1991, 1888, 1808, 1742, 1716, 1716, 1775, 1847, 1930, 2044, 2200, 2494, 2763, ]
+ #3264x2448_D50_70 - D50
+ - ct: 5003
+ resolution: 3264x2448
+ r: [4095, 3613, 3287, 3049, 2867, 2696, 2545, 2427, 2374, 2387, 2473, 2592, 2779, 2948, 3156, 3544, 3984, 3842, 3341, 3076, 2850, 2650, 2438, 2245, 2123, 2065, 2085, 2164, 2316, 2531, 2745, 2979, 3232, 3738, 3605, 3194, 2924, 2694, 2430, 2182, 1986, 1867, 1814, 1824, 1909, 2060, 2301, 2567, 2807, 3088, 3473, 3432, 3048, 2806, 2516, 2208, 1953, 1758, 1638, 1581, 1596, 1679, 1836, 2061, 2367, 2669, 2928, 3285, 3275, 2940, 2676, 2354, 2027, 1763, 1572, 1443, 1385, 1398, 1496, 1648, 1878, 2184, 2527, 2813, 3150, 3181, 2855, 2566, 2201, 1877, 1622, 1413, 1284, 1226, 1243, 1333, 1502, 1732, 2033, 2391, 2731, 3021, 3116, 2786, 2474, 2100, 1773, 1510, 1304, 1171, 1114, 1131, 1224, 1389, 1630, 1925, 2296, 2638, 2973, 3060, 2752, 2410, 2024, 1710, 1437, 1231, 1101, 1044, 1063, 1152, 1318, 1559, 1865, 2228, 2600, 2919, 3044, 2730, 2388, 2001, 1677, 1403, 1204, 1073, 1024, 1036, 1128, 1289, 1534, 1839, 2198, 2569, 2903, 3039, 2734, 2392, 2004, 1684, 1417, 1210, 1086, 1031, 1050, 1138, 1306, 1544, 1845, 2204, 2576, 2916, 3099, 2751, 2432, 2050, 1732, 1469, 1264, 1136, 1085, 1101, 1194, 1358, 1596, 1891, 2264, 2612, 2929, 3131, 2808, 2499, 2142, 1811, 1556, 1354, 1230, 1178, 1195, 1286, 1451, 1683, 1986, 2341, 2678, 2991, 3235, 2875, 2592, 2258, 1936, 1679, 1491, 1363, 1310, 1332, 1421, 1582, 1813, 2113, 2455, 2737, 3096, 3357, 2965, 2692, 2412, 2094, 1840, 1650, 1533, 1485, 1501, 1591, 1747, 1979, 2275, 2582, 2840, 3239, 3543, 3094, 2808, 2555, 2298, 2043, 1851, 1737, 1685, 1703, 1791, 1955, 2178, 2459, 2700, 2992, 3425, 3749, 3286, 2950, 2712, 2495, 2282, 2093, 1972, 1919, 1950, 2033, 2186, 2412, 2625, 2856, 3165, 3713, 4095, 3514, 3156, 2880, 2701, 2511, 2370, 2249, 2203, 2222, 2309, 2454, 2607, 2813, 3060, 3476, 3973, ]
+ gr: [3126, 2654, 2449, 2277, 2167, 2065, 1967, 1898, 1859, 1866, 1917, 2000, 2085, 2198, 2323, 2565, 2866, 2805, 2487, 2288, 2151, 2020, 1894, 1781, 1706, 1672, 1681, 1731, 1812, 1937, 2057, 2191, 2358, 2670, 2662, 2378, 2191, 2044, 1889, 1739, 1629, 1554, 1520, 1528, 1576, 1662, 1791, 1947, 2083, 2253, 2496, 2545, 2278, 2108, 1939, 1753, 1606, 1498, 1421, 1385, 1393, 1444, 1533, 1656, 1830, 2001, 2166, 2370, 2460, 2205, 2037, 1834, 1644, 1494, 1384, 1301, 1264, 1275, 1328, 1422, 1547, 1723, 1914, 2100, 2284, 2377, 2164, 1972, 1748, 1557, 1410, 1287, 1200, 1162, 1174, 1231, 1334, 1463, 1632, 1846, 2043, 2218, 2335, 2117, 1922, 1686, 1494, 1339, 1213, 1125, 1090, 1100, 1157, 1263, 1401, 1569, 1778, 1995, 2176, 2311, 2081, 1879, 1641, 1452, 1292, 1163, 1078, 1038, 1055, 1111, 1217, 1356, 1527, 1740, 1960, 2152, 2296, 2074, 1861, 1621, 1434, 1273, 1142, 1058, 1024, 1032, 1093, 1197, 1338, 1508, 1718, 1949, 2134, 2292, 2079, 1863, 1628, 1441, 1280, 1149, 1065, 1029, 1042, 1100, 1207, 1347, 1519, 1728, 1951, 2144, 2319, 2089, 1890, 1658, 1470, 1312, 1185, 1101, 1065, 1077, 1138, 1242, 1378, 1549, 1757, 1976, 2157, 2353, 2128, 1936, 1706, 1519, 1366, 1249, 1162, 1129, 1142, 1198, 1303, 1434, 1600, 1808, 2011, 2202, 2417, 2165, 1985, 1785, 1586, 1443, 1327, 1249, 1217, 1226, 1283, 1378, 1506, 1675, 1874, 2060, 2255, 2508, 2231, 2044, 1867, 1681, 1530, 1425, 1348, 1320, 1331, 1386, 1476, 1601, 1770, 1955, 2110, 2345, 2616, 2306, 2124, 1958, 1799, 1648, 1536, 1466, 1437, 1448, 1497, 1589, 1716, 1880, 2017, 2199, 2467, 2754, 2434, 2202, 2053, 1920, 1788, 1681, 1608, 1574, 1588, 1641, 1726, 1853, 1980, 2112, 2304, 2656, 3054, 2562, 2347, 2155, 2038, 1931, 1843, 1778, 1742, 1748, 1803, 1887, 1976, 2089, 2229, 2513, 2806, ]
+ gb: [3110, 2650, 2442, 2268, 2159, 2061, 1963, 1887, 1855, 1860, 1910, 1995, 2091, 2202, 2330, 2589, 2876, 2817, 2480, 2285, 2141, 2019, 1890, 1777, 1697, 1664, 1670, 1725, 1811, 1936, 2060, 2200, 2370, 2701, 2645, 2378, 2188, 2041, 1882, 1735, 1623, 1548, 1513, 1524, 1567, 1660, 1798, 1959, 2096, 2272, 2534, 2550, 2276, 2104, 1935, 1753, 1601, 1494, 1417, 1377, 1388, 1441, 1533, 1660, 1839, 2014, 2181, 2402, 2452, 2209, 2036, 1834, 1641, 1493, 1377, 1298, 1257, 1272, 1328, 1426, 1554, 1732, 1932, 2122, 2315, 2387, 2165, 1969, 1749, 1559, 1407, 1285, 1197, 1159, 1171, 1233, 1337, 1472, 1649, 1862, 2070, 2256, 2336, 2119, 1926, 1684, 1495, 1340, 1210, 1124, 1087, 1100, 1159, 1269, 1411, 1582, 1801, 2019, 2219, 2312, 2092, 1885, 1644, 1453, 1295, 1164, 1077, 1036, 1054, 1115, 1221, 1370, 1544, 1763, 1995, 2189, 2297, 2086, 1862, 1629, 1435, 1275, 1145, 1058, 1024, 1036, 1097, 1205, 1352, 1529, 1746, 1980, 2180, 2305, 2091, 1869, 1634, 1444, 1283, 1151, 1066, 1030, 1045, 1106, 1215, 1360, 1538, 1754, 1987, 2182, 2329, 2104, 1896, 1662, 1476, 1315, 1187, 1101, 1066, 1081, 1142, 1249, 1395, 1566, 1785, 2007, 2205, 2369, 2133, 1942, 1715, 1523, 1370, 1247, 1163, 1128, 1141, 1203, 1309, 1447, 1618, 1834, 2043, 2240, 2430, 2181, 1995, 1785, 1588, 1444, 1330, 1247, 1216, 1227, 1287, 1387, 1520, 1694, 1902, 2086, 2299, 2513, 2244, 2058, 1879, 1688, 1534, 1424, 1350, 1317, 1331, 1388, 1478, 1613, 1786, 1975, 2139, 2392, 2625, 2320, 2129, 1965, 1806, 1649, 1539, 1465, 1435, 1446, 1500, 1596, 1728, 1895, 2039, 2230, 2517, 2757, 2450, 2210, 2061, 1924, 1795, 1680, 1608, 1572, 1587, 1638, 1732, 1863, 1994, 2136, 2337, 2692, 3076, 2574, 2347, 2163, 2039, 1933, 1842, 1764, 1738, 1749, 1804, 1883, 1981, 2095, 2253, 2542, 2845, ]
+ b: [2915, 2480, 2280, 2121, 2025, 1929, 1854, 1793, 1773, 1769, 1815, 1879, 1970, 2069, 2185, 2406, 2670, 2610, 2321, 2132, 1997, 1889, 1781, 1681, 1616, 1587, 1598, 1642, 1721, 1831, 1945, 2068, 2221, 2492, 2485, 2222, 2043, 1913, 1775, 1639, 1541, 1485, 1457, 1466, 1500, 1579, 1705, 1855, 1972, 2122, 2360, 2380, 2127, 1969, 1815, 1647, 1516, 1427, 1367, 1342, 1342, 1390, 1463, 1577, 1739, 1901, 2041, 2243, 2297, 2061, 1914, 1722, 1549, 1418, 1325, 1261, 1233, 1241, 1287, 1369, 1483, 1638, 1820, 1994, 2158, 2233, 2025, 1852, 1646, 1474, 1347, 1242, 1171, 1142, 1152, 1203, 1293, 1409, 1559, 1758, 1931, 2104, 2198, 1987, 1808, 1594, 1424, 1290, 1178, 1104, 1079, 1088, 1139, 1232, 1358, 1505, 1700, 1893, 2077, 2165, 1972, 1772, 1561, 1393, 1250, 1139, 1065, 1035, 1051, 1101, 1196, 1323, 1473, 1656, 1867, 2046, 2166, 1960, 1769, 1542, 1381, 1234, 1121, 1048, 1024, 1034, 1084, 1178, 1308, 1462, 1651, 1855, 2036, 2166, 1961, 1774, 1548, 1380, 1240, 1126, 1054, 1025, 1041, 1092, 1186, 1315, 1464, 1654, 1862, 2041, 2184, 1975, 1794, 1576, 1408, 1268, 1155, 1082, 1056, 1066, 1118, 1211, 1338, 1492, 1678, 1877, 2063, 2222, 1999, 1826, 1623, 1441, 1314, 1208, 1137, 1109, 1120, 1171, 1261, 1383, 1533, 1724, 1912, 2071, 2265, 2043, 1871, 1684, 1507, 1372, 1276, 1211, 1183, 1193, 1242, 1327, 1447, 1600, 1781, 1941, 2132, 2351, 2095, 1928, 1760, 1588, 1454, 1357, 1297, 1271, 1282, 1326, 1406, 1523, 1684, 1849, 1988, 2215, 2439, 2167, 1992, 1847, 1695, 1551, 1455, 1397, 1372, 1381, 1422, 1507, 1622, 1785, 1897, 2068, 2323, 2564, 2289, 2068, 1923, 1803, 1684, 1581, 1520, 1495, 1504, 1546, 1623, 1752, 1866, 1990, 2170, 2488, 2838, 2390, 2201, 2026, 1908, 1814, 1736, 1669, 1643, 1654, 1700, 1774, 1862, 1964, 2101, 2363, 2613, ]
+ #3264x2448_D65_70 - D65
+ - ct: 6504
+ resolution: 3264x2448
+ r: [4095, 3609, 3293, 3044, 2858, 2708, 2555, 2426, 2383, 2390, 2485, 2610, 2769, 2948, 3150, 3554, 4002, 3858, 3341, 3067, 2851, 2656, 2436, 2251, 2136, 2083, 2092, 2169, 2327, 2531, 2747, 2983, 3227, 3713, 3579, 3194, 2920, 2704, 2441, 2187, 2002, 1873, 1824, 1838, 1920, 2070, 2308, 2573, 2812, 3074, 3487, 3428, 3039, 2791, 2525, 2213, 1962, 1775, 1650, 1593, 1609, 1691, 1852, 2077, 2379, 2680, 2932, 3261, 3283, 2933, 2685, 2353, 2038, 1779, 1582, 1449, 1395, 1407, 1501, 1661, 1893, 2189, 2527, 2825, 3136, 3179, 2846, 2572, 2206, 1894, 1626, 1426, 1292, 1234, 1250, 1343, 1513, 1744, 2046, 2404, 2725, 3037, 3115, 2787, 2479, 2109, 1786, 1520, 1312, 1180, 1120, 1136, 1229, 1399, 1641, 1938, 2296, 2645, 2956, 3052, 2747, 2419, 2039, 1716, 1448, 1238, 1106, 1047, 1068, 1160, 1326, 1572, 1876, 2228, 2597, 2913, 3044, 2732, 2389, 2006, 1687, 1415, 1208, 1079, 1024, 1040, 1132, 1296, 1542, 1843, 2206, 2571, 2901, 3049, 2721, 2397, 2016, 1694, 1426, 1215, 1091, 1035, 1055, 1145, 1312, 1550, 1859, 2211, 2575, 2919, 3078, 2759, 2434, 2063, 1737, 1478, 1271, 1141, 1088, 1106, 1199, 1367, 1603, 1905, 2267, 2616, 2927, 3143, 2793, 2505, 2140, 1828, 1564, 1364, 1237, 1183, 1202, 1290, 1461, 1695, 1996, 2340, 2676, 2993, 3228, 2867, 2595, 2268, 1942, 1689, 1499, 1370, 1316, 1340, 1431, 1593, 1823, 2117, 2461, 2756, 3077, 3371, 2972, 2696, 2408, 2104, 1852, 1661, 1541, 1491, 1505, 1599, 1758, 1987, 2276, 2582, 2849, 3235, 3523, 3088, 2811, 2565, 2302, 2046, 1860, 1745, 1694, 1716, 1800, 1961, 2188, 2460, 2699, 2987, 3420, 3757, 3276, 2947, 2706, 2497, 2283, 2099, 1979, 1929, 1947, 2032, 2199, 2409, 2626, 2852, 3158, 3715, 4095, 3473, 3168, 2886, 2708, 2514, 2365, 2251, 2203, 2229, 2315, 2440, 2623, 2806, 3061, 3472, 3935, ]
+ gr: [3109, 2638, 2434, 2267, 2147, 2051, 1954, 1871, 1847, 1848, 1903, 1981, 2080, 2184, 2312, 2555, 2821, 2799, 2481, 2275, 2132, 2010, 1885, 1775, 1698, 1665, 1670, 1719, 1802, 1926, 2045, 2182, 2346, 2660, 2643, 2361, 2180, 2032, 1880, 1730, 1618, 1547, 1513, 1520, 1566, 1652, 1785, 1940, 2074, 2238, 2491, 2534, 2272, 2096, 1934, 1743, 1597, 1491, 1416, 1379, 1389, 1437, 1526, 1653, 1822, 1991, 2156, 2356, 2445, 2203, 2031, 1828, 1639, 1492, 1376, 1298, 1261, 1270, 1325, 1418, 1540, 1717, 1908, 2093, 2270, 2374, 2153, 1965, 1746, 1552, 1404, 1282, 1198, 1160, 1173, 1228, 1331, 1459, 1629, 1836, 2038, 2206, 2328, 2111, 1916, 1679, 1490, 1336, 1208, 1123, 1087, 1097, 1156, 1260, 1398, 1564, 1772, 1985, 2174, 2292, 2087, 1871, 1639, 1448, 1292, 1161, 1077, 1038, 1051, 1111, 1214, 1355, 1521, 1732, 1955, 2142, 2290, 2067, 1852, 1619, 1430, 1271, 1141, 1055, 1024, 1033, 1091, 1194, 1335, 1507, 1715, 1939, 2133, 2285, 2073, 1861, 1623, 1436, 1278, 1147, 1065, 1028, 1042, 1099, 1204, 1345, 1514, 1723, 1945, 2131, 2312, 2082, 1884, 1653, 1467, 1308, 1181, 1100, 1065, 1076, 1133, 1240, 1377, 1543, 1754, 1968, 2151, 2350, 2114, 1928, 1703, 1515, 1364, 1244, 1161, 1126, 1138, 1197, 1300, 1429, 1595, 1803, 2003, 2192, 2404, 2166, 1977, 1775, 1581, 1435, 1322, 1245, 1213, 1223, 1278, 1375, 1504, 1671, 1872, 2048, 2255, 2499, 2220, 2040, 1859, 1678, 1526, 1416, 1345, 1314, 1327, 1380, 1468, 1596, 1763, 1948, 2105, 2337, 2607, 2299, 2116, 1951, 1792, 1638, 1534, 1458, 1431, 1443, 1492, 1583, 1709, 1873, 2004, 2191, 2463, 2733, 2429, 2197, 2044, 1912, 1782, 1670, 1601, 1568, 1581, 1630, 1719, 1847, 1973, 2107, 2304, 2637, 3045, 2548, 2338, 2143, 2029, 1920, 1832, 1762, 1736, 1737, 1795, 1871, 1961, 2070, 2227, 2493, 2794, ]
+ gb: [3118, 2634, 2434, 2259, 2154, 2052, 1949, 1888, 1844, 1853, 1900, 1987, 2084, 2192, 2325, 2571, 2855, 2786, 2469, 2271, 2125, 2010, 1882, 1775, 1690, 1662, 1669, 1719, 1805, 1928, 2050, 2192, 2362, 2674, 2635, 2358, 2173, 2030, 1872, 1729, 1620, 1547, 1508, 1516, 1565, 1654, 1790, 1947, 2082, 2257, 2516, 2527, 2260, 2094, 1923, 1744, 1598, 1486, 1411, 1374, 1388, 1438, 1525, 1657, 1830, 2001, 2169, 2382, 2431, 2196, 2021, 1824, 1634, 1486, 1376, 1296, 1254, 1269, 1325, 1422, 1547, 1722, 1922, 2106, 2297, 2367, 2146, 1960, 1736, 1550, 1402, 1281, 1196, 1157, 1169, 1230, 1333, 1466, 1640, 1848, 2055, 2232, 2320, 2105, 1909, 1675, 1489, 1335, 1208, 1120, 1083, 1099, 1158, 1265, 1405, 1575, 1794, 2006, 2206, 2295, 2075, 1873, 1634, 1447, 1292, 1162, 1076, 1037, 1052, 1113, 1220, 1363, 1541, 1748, 1982, 2173, 2278, 2071, 1850, 1619, 1430, 1271, 1144, 1056, 1024, 1035, 1096, 1202, 1348, 1521, 1736, 1966, 2162, 2290, 2073, 1856, 1626, 1439, 1279, 1150, 1065, 1029, 1043, 1104, 1211, 1355, 1532, 1744, 1973, 2166, 2302, 2090, 1883, 1651, 1466, 1313, 1184, 1100, 1065, 1078, 1139, 1246, 1388, 1557, 1771, 1995, 2185, 2344, 2122, 1927, 1706, 1513, 1368, 1245, 1163, 1126, 1140, 1200, 1305, 1441, 1612, 1823, 2030, 2225, 2411, 2166, 1983, 1776, 1584, 1439, 1324, 1245, 1213, 1225, 1283, 1383, 1513, 1688, 1887, 2074, 2281, 2493, 2226, 2042, 1867, 1679, 1535, 1418, 1349, 1317, 1329, 1382, 1476, 1607, 1780, 1968, 2128, 2376, 2613, 2305, 2120, 1955, 1797, 1642, 1536, 1460, 1430, 1446, 1496, 1591, 1722, 1887, 2029, 2217, 2500, 2745, 2434, 2202, 2052, 1917, 1784, 1676, 1603, 1572, 1584, 1634, 1731, 1857, 1986, 2128, 2326, 2675, 3059, 2546, 2342, 2153, 2041, 1930, 1833, 1767, 1731, 1739, 1795, 1880, 1970, 2091, 2242, 2528, 2816, ]
+ b: [2873, 2460, 2268, 2104, 2011, 1921, 1837, 1775, 1753, 1759, 1798, 1871, 1956, 2059, 2172, 2375, 2631, 2606, 2309, 2117, 1990, 1879, 1768, 1673, 1606, 1582, 1588, 1633, 1705, 1820, 1931, 2051, 2202, 2475, 2458, 2204, 2033, 1901, 1760, 1630, 1533, 1475, 1452, 1455, 1495, 1572, 1694, 1839, 1962, 2110, 2332, 2361, 2122, 1964, 1800, 1640, 1506, 1417, 1362, 1332, 1340, 1378, 1452, 1573, 1727, 1887, 2031, 2222, 2280, 2053, 1893, 1713, 1542, 1414, 1321, 1257, 1229, 1235, 1282, 1365, 1470, 1633, 1804, 1974, 2144, 2220, 2010, 1846, 1638, 1472, 1340, 1238, 1168, 1141, 1149, 1201, 1288, 1403, 1551, 1742, 1923, 2094, 2180, 1986, 1797, 1591, 1416, 1287, 1176, 1105, 1077, 1088, 1137, 1230, 1350, 1502, 1688, 1885, 2062, 2161, 1955, 1767, 1554, 1387, 1249, 1135, 1064, 1035, 1050, 1097, 1191, 1317, 1471, 1654, 1863, 2027, 2145, 1955, 1757, 1539, 1375, 1233, 1121, 1047, 1024, 1033, 1086, 1175, 1303, 1454, 1640, 1848, 2020, 2154, 1953, 1760, 1542, 1379, 1237, 1124, 1053, 1027, 1038, 1089, 1182, 1310, 1463, 1645, 1848, 2028, 2167, 1965, 1781, 1567, 1400, 1266, 1152, 1083, 1054, 1066, 1117, 1209, 1334, 1483, 1674, 1867, 2043, 2207, 1995, 1816, 1613, 1440, 1311, 1204, 1137, 1109, 1118, 1169, 1258, 1378, 1527, 1713, 1899, 2067, 2247, 2035, 1862, 1676, 1500, 1369, 1274, 1208, 1182, 1190, 1237, 1324, 1439, 1592, 1770, 1930, 2126, 2337, 2085, 1919, 1752, 1585, 1447, 1353, 1294, 1270, 1278, 1325, 1401, 1517, 1672, 1842, 1979, 2199, 2421, 2154, 1984, 1835, 1686, 1549, 1450, 1393, 1369, 1381, 1418, 1500, 1617, 1769, 1886, 2055, 2310, 2539, 2273, 2056, 1921, 1791, 1680, 1576, 1515, 1490, 1499, 1544, 1624, 1737, 1860, 1983, 2162, 2458, 2817, 2386, 2185, 2018, 1904, 1802, 1724, 1668, 1638, 1646, 1685, 1765, 1851, 1953, 2089, 2342, 2607, ]
+ #3264x2448_D75_70 - D75
+ - ct: 7504
+ resolution: 3264x2448
+ r: [4095, 3519, 3218, 2985, 2815, 2645, 2509, 2389, 2327, 2355, 2435, 2555, 2710, 2908, 3107, 3455, 3909, 3739, 3284, 3001, 2795, 2603, 2392, 2213, 2093, 2049, 2058, 2135, 2281, 2493, 2685, 2920, 3163, 3650, 3536, 3113, 2865, 2641, 2393, 2149, 1967, 1852, 1802, 1811, 1894, 2037, 2267, 2525, 2747, 3014, 3388, 3358, 2983, 2730, 2466, 2185, 1933, 1755, 1634, 1579, 1590, 1678, 1826, 2049, 2329, 2621, 2864, 3207, 3196, 2870, 2628, 2311, 2001, 1757, 1569, 1439, 1382, 1396, 1488, 1645, 1865, 2163, 2477, 2773, 3063, 3115, 2785, 2512, 2175, 1859, 1619, 1412, 1285, 1228, 1243, 1335, 1502, 1726, 2015, 2362, 2666, 2951, 3027, 2733, 2430, 2073, 1761, 1507, 1303, 1172, 1116, 1132, 1223, 1388, 1622, 1913, 2253, 2591, 2908, 2995, 2683, 2368, 2007, 1696, 1435, 1234, 1104, 1045, 1068, 1154, 1317, 1561, 1846, 2189, 2547, 2845, 2960, 2670, 2344, 1972, 1667, 1403, 1205, 1074, 1024, 1038, 1128, 1290, 1526, 1816, 2166, 2519, 2841, 2985, 2665, 2355, 1980, 1675, 1416, 1210, 1087, 1032, 1052, 1141, 1300, 1537, 1836, 2171, 2530, 2837, 3017, 2686, 2380, 2030, 1721, 1465, 1264, 1140, 1086, 1104, 1190, 1358, 1586, 1879, 2221, 2556, 2871, 3062, 2738, 2456, 2107, 1796, 1549, 1356, 1232, 1175, 1192, 1285, 1446, 1672, 1961, 2298, 2626, 2926, 3172, 2807, 2533, 2227, 1916, 1670, 1485, 1356, 1308, 1325, 1415, 1577, 1801, 2085, 2411, 2676, 3033, 3272, 2904, 2640, 2360, 2069, 1821, 1639, 1525, 1476, 1492, 1580, 1735, 1951, 2232, 2536, 2784, 3143, 3481, 3014, 2752, 2511, 2256, 2018, 1835, 1719, 1672, 1687, 1777, 1931, 2151, 2414, 2647, 2922, 3369, 3652, 3193, 2877, 2650, 2441, 2239, 2058, 1946, 1895, 1918, 1999, 2153, 2365, 2572, 2794, 3086, 3594, 4095, 3408, 3097, 2824, 2643, 2469, 2323, 2215, 2158, 2187, 2264, 2412, 2554, 2742, 2991, 3425, 3869, ]
+ gr: [3118, 2636, 2433, 2254, 2141, 2035, 1950, 1873, 1840, 1849, 1893, 1975, 2079, 2175, 2303, 2544, 2821, 2787, 2475, 2277, 2131, 2003, 1880, 1767, 1691, 1656, 1665, 1715, 1794, 1921, 2037, 2179, 2343, 2648, 2644, 2359, 2180, 2024, 1877, 1724, 1615, 1543, 1508, 1516, 1561, 1650, 1780, 1935, 2071, 2236, 2483, 2533, 2271, 2094, 1926, 1742, 1593, 1487, 1413, 1377, 1385, 1434, 1520, 1647, 1819, 1984, 2150, 2358, 2451, 2197, 2027, 1823, 1635, 1491, 1375, 1296, 1258, 1268, 1324, 1417, 1538, 1712, 1905, 2087, 2270, 2374, 2145, 1961, 1741, 1549, 1402, 1281, 1196, 1159, 1169, 1227, 1325, 1458, 1624, 1834, 2028, 2212, 2324, 2109, 1912, 1678, 1487, 1335, 1208, 1123, 1087, 1096, 1155, 1260, 1394, 1560, 1769, 1981, 2168, 2302, 2071, 1872, 1633, 1447, 1290, 1159, 1076, 1038, 1052, 1109, 1211, 1356, 1521, 1728, 1954, 2134, 2285, 2065, 1850, 1617, 1427, 1269, 1142, 1054, 1024, 1033, 1090, 1194, 1333, 1502, 1714, 1936, 2128, 2281, 2075, 1855, 1621, 1435, 1277, 1146, 1064, 1030, 1042, 1100, 1203, 1341, 1513, 1721, 1948, 2122, 2312, 2076, 1880, 1647, 1463, 1308, 1180, 1099, 1064, 1075, 1132, 1237, 1375, 1539, 1746, 1961, 2151, 2345, 2115, 1924, 1700, 1514, 1361, 1244, 1160, 1126, 1137, 1194, 1298, 1427, 1592, 1802, 2001, 2181, 2409, 2156, 1978, 1774, 1578, 1435, 1320, 1242, 1211, 1221, 1276, 1372, 1498, 1668, 1864, 2047, 2237, 2494, 2218, 2033, 1858, 1672, 1520, 1415, 1343, 1311, 1324, 1376, 1462, 1590, 1758, 1940, 2097, 2340, 2607, 2290, 2110, 1945, 1786, 1638, 1526, 1455, 1425, 1437, 1485, 1578, 1705, 1868, 1998, 2185, 2460, 2727, 2419, 2192, 2039, 1906, 1775, 1666, 1593, 1565, 1576, 1627, 1711, 1838, 1963, 2101, 2299, 2626, 3040, 2538, 2330, 2138, 2021, 1918, 1827, 1755, 1724, 1732, 1784, 1866, 1954, 2068, 2214, 2496, 2760, ]
+ gb: [3103, 2631, 2429, 2258, 2149, 2044, 1949, 1878, 1843, 1853, 1904, 1985, 2081, 2188, 2320, 2563, 2842, 2787, 2459, 2271, 2124, 2008, 1878, 1772, 1689, 1663, 1666, 1715, 1801, 1924, 2045, 2190, 2357, 2679, 2626, 2355, 2170, 2027, 1869, 1724, 1617, 1543, 1507, 1517, 1566, 1653, 1785, 1945, 2080, 2250, 2509, 2516, 2256, 2083, 1920, 1737, 1595, 1485, 1413, 1376, 1385, 1438, 1526, 1654, 1826, 1997, 2161, 2383, 2426, 2190, 2013, 1820, 1629, 1486, 1374, 1294, 1255, 1266, 1325, 1419, 1543, 1721, 1918, 2103, 2291, 2358, 2142, 1954, 1731, 1545, 1400, 1280, 1194, 1157, 1171, 1227, 1334, 1465, 1633, 1848, 2045, 2227, 2319, 2095, 1902, 1672, 1488, 1334, 1207, 1123, 1085, 1096, 1157, 1261, 1401, 1572, 1784, 2003, 2191, 2286, 2071, 1863, 1631, 1445, 1289, 1160, 1075, 1038, 1053, 1113, 1221, 1363, 1534, 1743, 1971, 2167, 2278, 2059, 1844, 1613, 1427, 1271, 1143, 1057, 1024, 1035, 1096, 1199, 1346, 1518, 1731, 1960, 2153, 2280, 2065, 1853, 1619, 1438, 1278, 1149, 1066, 1029, 1044, 1105, 1210, 1354, 1528, 1735, 1970, 2160, 2302, 2080, 1875, 1649, 1465, 1309, 1183, 1100, 1065, 1079, 1136, 1246, 1384, 1556, 1767, 1987, 2178, 2346, 2109, 1923, 1697, 1514, 1365, 1245, 1160, 1127, 1141, 1199, 1303, 1438, 1608, 1818, 2027, 2215, 2410, 2158, 1976, 1774, 1578, 1437, 1325, 1245, 1212, 1225, 1284, 1379, 1514, 1680, 1883, 2068, 2272, 2489, 2219, 2041, 1862, 1677, 1529, 1417, 1345, 1314, 1327, 1381, 1474, 1600, 1780, 1961, 2120, 2371, 2601, 2306, 2111, 1953, 1795, 1642, 1534, 1459, 1431, 1443, 1496, 1587, 1717, 1881, 2024, 2213, 2482, 2733, 2436, 2194, 2049, 1910, 1784, 1674, 1600, 1567, 1581, 1632, 1728, 1855, 1985, 2122, 2321, 2675, 3032, 2542, 2344, 2151, 2037, 1930, 1834, 1767, 1732, 1747, 1791, 1879, 1968, 2083, 2239, 2522, 2807, ]
+ b: [2879, 2455, 2264, 2106, 2006, 1922, 1836, 1777, 1750, 1753, 1802, 1870, 1949, 2055, 2160, 2385, 2620, 2609, 2309, 2119, 1990, 1882, 1764, 1668, 1603, 1583, 1586, 1625, 1704, 1818, 1933, 2054, 2201, 2478, 2465, 2208, 2038, 1897, 1760, 1627, 1531, 1477, 1450, 1453, 1492, 1569, 1686, 1838, 1960, 2103, 2342, 2362, 2116, 1967, 1802, 1637, 1506, 1416, 1359, 1332, 1340, 1379, 1453, 1574, 1722, 1888, 2030, 2214, 2284, 2053, 1896, 1715, 1540, 1412, 1320, 1257, 1227, 1236, 1282, 1363, 1468, 1629, 1806, 1969, 2149, 2217, 2010, 1841, 1638, 1470, 1340, 1237, 1168, 1140, 1146, 1199, 1286, 1401, 1552, 1740, 1932, 2082, 2182, 1981, 1791, 1589, 1418, 1287, 1175, 1104, 1076, 1087, 1137, 1227, 1352, 1497, 1690, 1883, 2059, 2158, 1964, 1767, 1551, 1387, 1247, 1135, 1065, 1036, 1048, 1100, 1190, 1318, 1466, 1651, 1858, 2037, 2149, 1951, 1756, 1539, 1373, 1233, 1121, 1047, 1024, 1035, 1085, 1174, 1302, 1457, 1637, 1845, 2021, 2153, 1952, 1760, 1542, 1378, 1236, 1126, 1054, 1026, 1040, 1090, 1181, 1308, 1458, 1645, 1852, 2025, 2172, 1964, 1780, 1565, 1398, 1266, 1151, 1085, 1055, 1066, 1116, 1209, 1333, 1484, 1667, 1864, 2036, 2200, 1989, 1822, 1612, 1435, 1311, 1202, 1135, 1108, 1117, 1169, 1259, 1374, 1526, 1714, 1895, 2075, 2259, 2034, 1860, 1674, 1500, 1363, 1275, 1208, 1180, 1192, 1237, 1319, 1437, 1591, 1767, 1932, 2119, 2327, 2081, 1914, 1750, 1580, 1445, 1350, 1292, 1269, 1279, 1320, 1400, 1515, 1671, 1835, 1975, 2198, 2428, 2152, 1983, 1838, 1684, 1546, 1448, 1394, 1367, 1377, 1417, 1501, 1615, 1768, 1890, 2056, 2310, 2536, 2273, 2059, 1919, 1794, 1676, 1576, 1512, 1487, 1499, 1543, 1621, 1741, 1856, 1980, 2155, 2463, 2820, 2387, 2189, 2014, 1906, 1806, 1722, 1672, 1639, 1645, 1687, 1758, 1846, 1950, 2094, 2345, 2609, ]
+ #3264x2448_F11_TL84_70 - F11_TL84
+ - ct: 4000
+ resolution: 3264x2448
+ r: [4002, 3309, 3035, 2794, 2634, 2461, 2319, 2207, 2157, 2168, 2244, 2370, 2537, 2712, 2917, 3269, 3672, 3551, 3103, 2825, 2625, 2420, 2214, 2037, 1922, 1874, 1882, 1956, 2100, 2302, 2511, 2738, 2969, 3444, 3298, 2949, 2692, 2463, 2213, 1969, 1792, 1686, 1640, 1646, 1721, 1857, 2074, 2333, 2576, 2831, 3187, 3157, 2805, 2562, 2298, 1998, 1762, 1596, 1491, 1444, 1454, 1521, 1655, 1863, 2142, 2432, 2691, 3014, 3030, 2709, 2454, 2128, 1831, 1597, 1435, 1335, 1291, 1302, 1366, 1495, 1686, 1971, 2291, 2593, 2883, 2940, 2627, 2345, 1995, 1701, 1475, 1311, 1216, 1176, 1186, 1246, 1372, 1564, 1831, 2173, 2490, 2788, 2868, 2575, 2259, 1900, 1604, 1387, 1231, 1136, 1095, 1105, 1167, 1286, 1475, 1735, 2074, 2418, 2721, 2826, 2533, 2203, 1835, 1548, 1332, 1177, 1084, 1042, 1056, 1116, 1233, 1422, 1676, 2015, 2370, 2679, 2812, 2511, 2176, 1810, 1521, 1303, 1157, 1063, 1024, 1034, 1095, 1216, 1398, 1657, 1989, 2342, 2677, 2816, 2517, 2185, 1816, 1530, 1312, 1161, 1070, 1031, 1041, 1109, 1224, 1410, 1665, 1999, 2359, 2664, 2839, 2531, 2218, 1856, 1571, 1350, 1197, 1106, 1065, 1080, 1142, 1263, 1451, 1708, 2046, 2389, 2703, 2896, 2578, 2281, 1935, 1636, 1421, 1265, 1171, 1135, 1147, 1209, 1335, 1527, 1788, 2123, 2454, 2753, 2994, 2638, 2366, 2046, 1749, 1522, 1365, 1268, 1231, 1245, 1310, 1442, 1638, 1912, 2230, 2518, 2840, 3101, 2741, 2467, 2183, 1895, 1664, 1502, 1402, 1363, 1376, 1451, 1582, 1789, 2057, 2362, 2609, 2977, 3260, 2841, 2581, 2342, 2083, 1842, 1676, 1575, 1534, 1553, 1625, 1769, 1977, 2240, 2474, 2752, 3175, 3489, 3019, 2716, 2496, 2274, 2077, 1899, 1789, 1751, 1769, 1847, 1991, 2189, 2409, 2631, 2927, 3411, 3949, 3229, 2910, 2647, 2477, 2296, 2156, 2049, 2010, 2022, 2104, 2237, 2398, 2579, 2812, 3226, 3666, ]
+ gr: [3132, 2654, 2457, 2283, 2168, 2064, 1974, 1892, 1855, 1864, 1922, 1997, 2100, 2202, 2331, 2576, 2861, 2822, 2487, 2297, 2143, 2021, 1891, 1780, 1697, 1664, 1669, 1720, 1809, 1934, 2058, 2197, 2364, 2674, 2652, 2374, 2189, 2039, 1882, 1732, 1618, 1541, 1502, 1512, 1561, 1654, 1788, 1943, 2081, 2250, 2503, 2542, 2272, 2100, 1925, 1743, 1592, 1482, 1408, 1367, 1378, 1429, 1517, 1644, 1816, 1993, 2163, 2364, 2454, 2203, 2028, 1824, 1624, 1481, 1366, 1286, 1249, 1256, 1312, 1409, 1527, 1709, 1905, 2097, 2279, 2368, 2158, 1956, 1731, 1540, 1390, 1275, 1189, 1153, 1165, 1219, 1318, 1446, 1615, 1833, 2032, 2220, 2332, 2110, 1908, 1667, 1473, 1322, 1200, 1119, 1085, 1095, 1149, 1249, 1383, 1550, 1760, 1983, 2175, 2300, 2074, 1859, 1619, 1428, 1273, 1154, 1072, 1038, 1052, 1105, 1203, 1339, 1506, 1722, 1951, 2146, 2289, 2061, 1844, 1602, 1410, 1256, 1134, 1053, 1024, 1031, 1089, 1183, 1320, 1490, 1702, 1938, 2137, 2282, 2067, 1845, 1605, 1418, 1260, 1141, 1061, 1027, 1041, 1095, 1194, 1328, 1497, 1713, 1942, 2139, 2318, 2083, 1870, 1634, 1448, 1296, 1173, 1096, 1062, 1073, 1129, 1226, 1363, 1528, 1741, 1967, 2157, 2345, 2113, 1918, 1691, 1495, 1351, 1233, 1154, 1119, 1132, 1189, 1286, 1418, 1583, 1795, 2001, 2190, 2416, 2159, 1976, 1767, 1568, 1424, 1311, 1232, 1202, 1211, 1268, 1363, 1490, 1661, 1868, 2047, 2256, 2502, 2222, 2037, 1855, 1670, 1518, 1407, 1333, 1302, 1313, 1369, 1457, 1591, 1756, 1941, 2106, 2352, 2619, 2304, 2118, 1948, 1789, 1638, 1523, 1449, 1418, 1432, 1483, 1578, 1706, 1875, 2011, 2197, 2473, 2758, 2433, 2198, 2052, 1915, 1783, 1674, 1593, 1566, 1576, 1629, 1721, 1852, 1976, 2115, 2312, 2657, 3071, 2569, 2344, 2154, 2039, 1930, 1841, 1773, 1734, 1748, 1795, 1881, 1974, 2089, 2231, 2521, 2802, ]
+ gb: [3133, 2656, 2457, 2275, 2154, 2053, 1951, 1877, 1838, 1848, 1901, 1985, 2088, 2205, 2345, 2598, 2891, 2824, 2492, 2292, 2135, 2015, 1879, 1765, 1681, 1647, 1653, 1708, 1800, 1928, 2056, 2208, 2384, 2708, 2667, 2381, 2198, 2039, 1879, 1723, 1610, 1527, 1492, 1502, 1553, 1645, 1781, 1953, 2093, 2277, 2545, 2558, 2287, 2108, 1931, 1743, 1586, 1472, 1400, 1359, 1367, 1424, 1513, 1652, 1830, 2012, 2188, 2417, 2474, 2212, 2042, 1831, 1630, 1477, 1365, 1283, 1242, 1255, 1313, 1408, 1538, 1723, 1930, 2127, 2323, 2395, 2169, 1970, 1738, 1548, 1392, 1272, 1187, 1151, 1161, 1222, 1322, 1459, 1633, 1861, 2066, 2263, 2356, 2130, 1922, 1679, 1479, 1325, 1200, 1118, 1082, 1094, 1151, 1254, 1396, 1573, 1792, 2024, 2227, 2337, 2095, 1883, 1627, 1438, 1279, 1156, 1074, 1038, 1054, 1110, 1211, 1352, 1530, 1752, 1997, 2195, 2306, 2095, 1861, 1616, 1421, 1258, 1139, 1055, 1024, 1035, 1094, 1193, 1335, 1513, 1741, 1986, 2182, 2315, 2094, 1867, 1622, 1427, 1266, 1143, 1064, 1029, 1044, 1100, 1202, 1344, 1523, 1746, 1989, 2193, 2342, 2108, 1890, 1648, 1458, 1299, 1176, 1096, 1061, 1075, 1132, 1236, 1376, 1557, 1773, 2010, 2203, 2377, 2140, 1939, 1704, 1508, 1353, 1232, 1154, 1120, 1131, 1193, 1292, 1432, 1608, 1828, 2044, 2251, 2443, 2185, 1992, 1782, 1577, 1428, 1315, 1233, 1199, 1214, 1271, 1370, 1504, 1685, 1895, 2093, 2305, 2519, 2249, 2058, 1869, 1675, 1519, 1406, 1331, 1298, 1313, 1371, 1462, 1599, 1781, 1976, 2139, 2405, 2637, 2326, 2130, 1962, 1792, 1637, 1521, 1445, 1412, 1428, 1481, 1578, 1713, 1888, 2035, 2238, 2529, 2777, 2458, 2215, 2053, 1917, 1776, 1662, 1588, 1554, 1568, 1624, 1722, 1851, 1992, 2136, 2351, 2708, 3076, 2575, 2354, 2161, 2036, 1925, 1834, 1757, 1723, 1732, 1779, 1874, 1972, 2093, 2258, 2546, 2857, ]
+ b: [2906, 2483, 2290, 2108, 2020, 1921, 1851, 1778, 1756, 1759, 1799, 1880, 1969, 2074, 2183, 2435, 2664, 2618, 2324, 2122, 1992, 1883, 1772, 1666, 1601, 1578, 1586, 1627, 1712, 1827, 1934, 2072, 2225, 2524, 2483, 2211, 2037, 1900, 1761, 1625, 1532, 1472, 1447, 1449, 1486, 1571, 1692, 1847, 1968, 2118, 2360, 2370, 2126, 1961, 1803, 1638, 1509, 1411, 1355, 1324, 1335, 1376, 1449, 1572, 1729, 1884, 2042, 2233, 2286, 2051, 1902, 1710, 1537, 1407, 1314, 1249, 1222, 1228, 1276, 1356, 1472, 1629, 1815, 1975, 2159, 2238, 2012, 1839, 1636, 1463, 1333, 1232, 1165, 1137, 1144, 1192, 1280, 1394, 1549, 1743, 1922, 2094, 2184, 1979, 1797, 1586, 1413, 1279, 1170, 1102, 1074, 1086, 1134, 1219, 1345, 1492, 1684, 1888, 2067, 2160, 1958, 1765, 1546, 1378, 1240, 1132, 1062, 1035, 1050, 1095, 1184, 1307, 1459, 1646, 1858, 2036, 2151, 1954, 1752, 1531, 1366, 1224, 1115, 1046, 1026, 1033, 1081, 1170, 1293, 1450, 1635, 1845, 2032, 2155, 1948, 1754, 1535, 1373, 1228, 1118, 1053, 1024, 1038, 1088, 1175, 1299, 1452, 1638, 1849, 2027, 2179, 1970, 1780, 1565, 1391, 1259, 1147, 1079, 1053, 1063, 1113, 1203, 1324, 1474, 1668, 1869, 2037, 2214, 1989, 1816, 1610, 1433, 1297, 1194, 1130, 1105, 1112, 1161, 1249, 1367, 1522, 1710, 1892, 2074, 2264, 2034, 1863, 1673, 1491, 1360, 1264, 1199, 1176, 1185, 1230, 1312, 1434, 1590, 1770, 1936, 2127, 2348, 2084, 1916, 1751, 1581, 1437, 1343, 1284, 1254, 1268, 1312, 1395, 1516, 1673, 1837, 1986, 2216, 2445, 2159, 1975, 1832, 1684, 1544, 1441, 1381, 1358, 1367, 1413, 1494, 1612, 1773, 1894, 2067, 2330, 2573, 2285, 2061, 1914, 1791, 1672, 1568, 1507, 1480, 1492, 1529, 1619, 1743, 1862, 1987, 2168, 2475, 2853, 2395, 2197, 2003, 1909, 1798, 1726, 1652, 1638, 1640, 1687, 1762, 1852, 1956, 2101, 2365, 2643, ]
+ #3264x2448_F2_CWF_70 - F2_CWF
+ - ct: 4230
+ resolution: 3264x2448
+ r: [3695, 3077, 2822, 2622, 2472, 2342, 2200, 2111, 2075, 2079, 2145, 2258, 2393, 2547, 2713, 3030, 3396, 3294, 2882, 2641, 2461, 2294, 2117, 1965, 1868, 1822, 1827, 1898, 2020, 2200, 2366, 2557, 2763, 3190, 3081, 2755, 2527, 2334, 2120, 1915, 1760, 1667, 1625, 1635, 1702, 1820, 2002, 2225, 2422, 2641, 2979, 2935, 2624, 2415, 2192, 1939, 1732, 1587, 1496, 1452, 1461, 1526, 1643, 1825, 2064, 2314, 2518, 2804, 2832, 2532, 2323, 2050, 1792, 1591, 1448, 1348, 1301, 1315, 1382, 1504, 1675, 1916, 2190, 2435, 2700, 2735, 2464, 2229, 1935, 1680, 1485, 1327, 1227, 1183, 1194, 1265, 1392, 1567, 1799, 2091, 2351, 2611, 2673, 2415, 2150, 1853, 1597, 1397, 1244, 1144, 1096, 1111, 1182, 1308, 1489, 1715, 2000, 2291, 2552, 2638, 2381, 2104, 1797, 1546, 1342, 1189, 1086, 1042, 1058, 1126, 1255, 1435, 1666, 1950, 2257, 2514, 2621, 2361, 2083, 1766, 1525, 1319, 1164, 1064, 1024, 1037, 1106, 1231, 1415, 1644, 1929, 2233, 2506, 2638, 2364, 2088, 1777, 1528, 1326, 1168, 1073, 1029, 1046, 1115, 1240, 1422, 1654, 1941, 2237, 2511, 2655, 2388, 2121, 1813, 1563, 1366, 1210, 1114, 1070, 1084, 1155, 1283, 1459, 1693, 1981, 2269, 2530, 2712, 2427, 2182, 1884, 1628, 1428, 1281, 1183, 1143, 1158, 1226, 1352, 1531, 1764, 2046, 2317, 2579, 2790, 2485, 2250, 1983, 1722, 1523, 1379, 1284, 1242, 1258, 1327, 1454, 1628, 1862, 2139, 2376, 2667, 2895, 2571, 2344, 2103, 1851, 1644, 1506, 1409, 1371, 1388, 1457, 1578, 1756, 1996, 2250, 2457, 2782, 3048, 2672, 2441, 2229, 2007, 1806, 1658, 1567, 1526, 1541, 1611, 1739, 1916, 2148, 2340, 2583, 2953, 3225, 2827, 2544, 2353, 2172, 1998, 1846, 1755, 1708, 1732, 1794, 1928, 2102, 2282, 2468, 2726, 3175, 3641, 3010, 2734, 2492, 2341, 2192, 2069, 1968, 1937, 1948, 2023, 2139, 2270, 2437, 2634, 2994, 3392, ]
+ gr: [3050, 2599, 2407, 2232, 2134, 2044, 1950, 1879, 1843, 1845, 1897, 1973, 2069, 2164, 2285, 2518, 2788, 2763, 2436, 2247, 2112, 1994, 1867, 1764, 1688, 1655, 1661, 1710, 1788, 1907, 2024, 2157, 2320, 2612, 2604, 2323, 2155, 2009, 1858, 1715, 1606, 1543, 1504, 1512, 1556, 1640, 1766, 1917, 2047, 2211, 2450, 2492, 2232, 2067, 1906, 1727, 1584, 1480, 1411, 1371, 1381, 1428, 1512, 1632, 1799, 1962, 2124, 2327, 2400, 2164, 1999, 1801, 1617, 1475, 1369, 1292, 1252, 1264, 1317, 1408, 1525, 1691, 1879, 2063, 2240, 2326, 2120, 1935, 1721, 1533, 1392, 1278, 1194, 1156, 1167, 1225, 1319, 1443, 1606, 1809, 2003, 2170, 2291, 2075, 1883, 1653, 1470, 1323, 1204, 1122, 1086, 1096, 1153, 1252, 1381, 1540, 1746, 1951, 2139, 2256, 2043, 1839, 1609, 1430, 1278, 1158, 1076, 1038, 1052, 1108, 1206, 1341, 1500, 1702, 1929, 2103, 2242, 2036, 1820, 1596, 1411, 1260, 1138, 1053, 1024, 1032, 1091, 1186, 1322, 1484, 1690, 1909, 2098, 2251, 2034, 1826, 1598, 1416, 1267, 1143, 1065, 1027, 1043, 1097, 1198, 1328, 1493, 1694, 1913, 2096, 2263, 2048, 1852, 1626, 1447, 1298, 1177, 1096, 1063, 1075, 1131, 1230, 1360, 1521, 1723, 1934, 2117, 2316, 2078, 1897, 1680, 1494, 1351, 1238, 1159, 1123, 1135, 1193, 1290, 1416, 1572, 1776, 1974, 2152, 2362, 2122, 1947, 1746, 1562, 1424, 1313, 1238, 1207, 1218, 1272, 1361, 1484, 1647, 1838, 2014, 2215, 2461, 2182, 2007, 1835, 1653, 1510, 1408, 1336, 1305, 1317, 1368, 1456, 1576, 1736, 1919, 2068, 2306, 2560, 2260, 2080, 1920, 1771, 1626, 1516, 1450, 1420, 1432, 1480, 1566, 1687, 1844, 1975, 2157, 2418, 2703, 2387, 2160, 2012, 1888, 1763, 1660, 1588, 1558, 1566, 1617, 1702, 1827, 1943, 2075, 2267, 2603, 2992, 2511, 2296, 2118, 2001, 1898, 1817, 1749, 1719, 1730, 1779, 1859, 1938, 2050, 2187, 2457, 2741, ]
+ gb: [3060, 2612, 2398, 2229, 2123, 2030, 1932, 1857, 1822, 1830, 1874, 1957, 2069, 2163, 2291, 2542, 2825, 2776, 2432, 2251, 2106, 1988, 1856, 1748, 1668, 1636, 1641, 1695, 1784, 1902, 2026, 2170, 2338, 2654, 2609, 2336, 2151, 2005, 1853, 1710, 1597, 1527, 1487, 1500, 1546, 1634, 1768, 1926, 2063, 2235, 2497, 2514, 2248, 2075, 1908, 1727, 1578, 1471, 1396, 1360, 1371, 1422, 1509, 1639, 1810, 1981, 2151, 2365, 2415, 2182, 2010, 1807, 1619, 1474, 1366, 1284, 1247, 1257, 1316, 1409, 1532, 1710, 1906, 2098, 2282, 2358, 2140, 1949, 1725, 1539, 1393, 1276, 1191, 1153, 1166, 1224, 1325, 1455, 1628, 1840, 2045, 2226, 2308, 2101, 1903, 1666, 1479, 1329, 1204, 1121, 1083, 1098, 1154, 1260, 1395, 1565, 1775, 2000, 2191, 2296, 2069, 1863, 1625, 1437, 1285, 1160, 1074, 1038, 1053, 1112, 1214, 1355, 1527, 1746, 1970, 2167, 2280, 2060, 1844, 1609, 1422, 1262, 1140, 1055, 1024, 1034, 1095, 1198, 1337, 1516, 1724, 1962, 2155, 2284, 2063, 1850, 1618, 1429, 1273, 1147, 1064, 1030, 1043, 1104, 1207, 1351, 1519, 1738, 1965, 2159, 2303, 2083, 1878, 1640, 1460, 1304, 1182, 1099, 1065, 1078, 1136, 1244, 1379, 1552, 1764, 1986, 2181, 2341, 2110, 1916, 1698, 1504, 1359, 1238, 1159, 1125, 1136, 1197, 1297, 1431, 1599, 1809, 2018, 2208, 2403, 2156, 1967, 1764, 1570, 1427, 1315, 1237, 1205, 1217, 1274, 1369, 1502, 1673, 1875, 2061, 2278, 2488, 2208, 2025, 1848, 1662, 1513, 1405, 1333, 1304, 1314, 1372, 1460, 1588, 1760, 1946, 2108, 2355, 2596, 2289, 2101, 1934, 1775, 1624, 1516, 1442, 1412, 1425, 1476, 1571, 1700, 1865, 2005, 2195, 2486, 2720, 2411, 2169, 2025, 1895, 1760, 1650, 1578, 1548, 1559, 1612, 1702, 1834, 1960, 2101, 2302, 2647, 3035, 2523, 2314, 2125, 2002, 1897, 1806, 1738, 1705, 1716, 1766, 1855, 1944, 2061, 2204, 2497, 2792, ]
+ b: [2861, 2421, 2239, 2078, 1980, 1893, 1811, 1762, 1723, 1742, 1779, 1851, 1933, 2034, 2151, 2359, 2635, 2562, 2279, 2088, 1949, 1859, 1748, 1650, 1585, 1562, 1570, 1607, 1691, 1798, 1909, 2028, 2181, 2467, 2428, 2166, 2009, 1873, 1736, 1613, 1518, 1461, 1436, 1441, 1480, 1557, 1676, 1814, 1932, 2087, 2311, 2326, 2088, 1923, 1779, 1621, 1492, 1404, 1351, 1322, 1329, 1368, 1445, 1557, 1708, 1863, 2004, 2200, 2250, 2013, 1869, 1687, 1522, 1398, 1309, 1250, 1218, 1231, 1273, 1354, 1457, 1615, 1779, 1941, 2113, 2187, 1979, 1812, 1617, 1454, 1331, 1231, 1163, 1137, 1145, 1195, 1277, 1392, 1537, 1720, 1899, 2061, 2161, 1947, 1769, 1567, 1405, 1273, 1171, 1101, 1078, 1087, 1132, 1222, 1336, 1483, 1665, 1849, 2018, 2122, 1923, 1740, 1530, 1369, 1239, 1131, 1064, 1037, 1049, 1096, 1182, 1306, 1452, 1625, 1829, 1999, 2115, 1919, 1730, 1520, 1360, 1222, 1117, 1046, 1024, 1033, 1086, 1169, 1288, 1439, 1617, 1815, 1991, 2121, 1918, 1736, 1524, 1359, 1227, 1119, 1053, 1025, 1040, 1088, 1173, 1295, 1442, 1624, 1817, 1995, 2136, 1934, 1750, 1546, 1384, 1254, 1147, 1079, 1053, 1063, 1114, 1203, 1321, 1464, 1649, 1837, 2004, 2179, 1955, 1795, 1587, 1423, 1294, 1195, 1131, 1105, 1112, 1161, 1247, 1362, 1506, 1688, 1872, 2037, 2228, 1999, 1833, 1656, 1480, 1353, 1263, 1197, 1172, 1182, 1228, 1311, 1423, 1574, 1751, 1903, 2078, 2309, 2047, 1889, 1724, 1558, 1425, 1336, 1277, 1252, 1263, 1308, 1382, 1500, 1654, 1806, 1954, 2164, 2390, 2114, 1949, 1802, 1660, 1524, 1429, 1373, 1352, 1360, 1401, 1482, 1597, 1748, 1863, 2031, 2287, 2520, 2231, 2019, 1882, 1760, 1651, 1549, 1494, 1466, 1478, 1519, 1597, 1715, 1827, 1947, 2124, 2444, 2788, 2355, 2157, 1974, 1878, 1770, 1701, 1637, 1615, 1612, 1661, 1743, 1824, 1925, 2064, 2315, 2599, ]
+
diff --git a/src/ipa/rkisp1/data/uncalibrated.yaml b/src/ipa/rkisp1/data/uncalibrated.yaml
index bdbd5fda..a7bbd8d8 100644
--- a/src/ipa/rkisp1/data/uncalibrated.yaml
+++ b/src/ipa/rkisp1/data/uncalibrated.yaml
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-%YAML 1.2
+%YAML 1.1
---
version: 1
algorithms:
diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp
index 1559d3ff..070834fa 100644
--- a/src/ipa/rkisp1/ipa_context.cpp
+++ b/src/ipa/rkisp1/ipa_context.cpp
@@ -15,6 +15,25 @@
namespace libcamera::ipa::rkisp1 {
/**
+ * \struct IPAHwSettings
+ * \brief RkISP1 version-specific hardware parameters
+ */
+
+/**
+ * \var IPAHwSettings::numAeCells
+ * \brief Number of cells in the AE exposure means grid
+ *
+ * \var IPAHwSettings::numHistogramBins
+ * \brief Number of bins in the histogram
+ *
+ * \var IPAHwSettings::numHistogramWeights
+ * \brief Number of weights in the histogram grid
+ *
+ * \var IPAHwSettings::numGammaOutSamples
+ * \brief Number of samples in the gamma out table
+ */
+
+/**
* \struct IPASessionConfiguration
* \brief Session configuration for the IPA module
*
@@ -25,84 +44,217 @@ namespace libcamera::ipa::rkisp1 {
*/
/**
- * \struct IPAFrameContext
- * \brief Per-frame context for algorithms
+ * \var IPASessionConfiguration::agc
+ * \brief AGC parameters configuration of the IPA
+ *
+ * \var IPASessionConfiguration::agc.measureWindow
+ * \brief AGC measure window
+ */
+
+/**
+ * \var IPASessionConfiguration::awb
+ * \brief AWB parameters configuration of the IPA
*
- * The frame context stores data specific to a single frame processed by the
- * IPA. Each frame processed by the IPA has a context associated with it,
- * accessible through the IPAContext structure.
+ * \var IPASessionConfiguration::awb.measureWindow
+ * \brief AWB measure window
*
- * \todo Detail how to access contexts for a particular frame
+ * \var IPASessionConfiguration::awb.enabled
+ * \brief Indicates if the AWB hardware is enabled and applies colour gains
*
- * Each of the fields in the frame context belongs to either a specific
- * algorithm, or to the top-level IPA module. A field may be read by any
- * algorithm, but should only be written by its owner.
+ * The AWB module of the ISP applies colour gains and computes statistics. It is
+ * enabled when the AWB algorithm is loaded, regardless of whether the algorithm
+ * operates in manual or automatic mode.
*/
/**
- * \struct IPAContext
- * \brief Global IPA context data shared between all algorithms
+ * \var IPASessionConfiguration::lsc
+ * \brief Lens Shading Correction configuration of the IPA
*
- * \var IPAContext::configuration
- * \brief The IPA session configuration, immutable during the session
+ * \var IPASessionConfiguration::lsc.enabled
+ * \brief Indicates if the LSC hardware is enabled
+ */
+
+/**
+ * \var IPASessionConfiguration::sensor
+ * \brief Sensor-specific configuration of the IPA
+ *
+ * \var IPASessionConfiguration::sensor.minShutterSpeed
+ * \brief Minimum shutter speed supported with the sensor
+ *
+ * \var IPASessionConfiguration::sensor.maxShutterSpeed
+ * \brief Maximum shutter speed supported with the sensor
*
- * \var IPAContext::frameContext
- * \brief The frame context for the frame being processed
+ * \var IPASessionConfiguration::sensor.minAnalogueGain
+ * \brief Minimum analogue gain supported with the sensor
*
- * \todo While the frame context is supposed to be per-frame, this
- * single frame context stores data related to both the current frame
- * and the previous frames, with fields being updated as the algorithms
- * are run. This needs to be turned into real per-frame data storage.
+ * \var IPASessionConfiguration::sensor.maxAnalogueGain
+ * \brief Maximum analogue gain supported with the sensor
+ *
+ * \var IPASessionConfiguration::sensor.defVBlank
+ * \brief The default vblank value of the sensor
+ *
+ * \var IPASessionConfiguration::sensor.lineDuration
+ * \brief Line duration in microseconds
+ *
+ * \var IPASessionConfiguration::sensor.size
+ * \brief Sensor output resolution
*/
/**
- * \var IPASessionConfiguration::agc
- * \brief AGC parameters configuration of the IPA
+ * \var IPASessionConfiguration::raw
+ * \brief Indicates if the camera is configured to capture raw frames
+ */
+
+/**
+ * \struct IPAActiveState
+ * \brief Active state for algorithms
*
- * \var IPASessionConfiguration::agc.minShutterSpeed
- * \brief Minimum shutter speed supported with the configured sensor
+ * The active state contains all algorithm-specific data that needs to be
+ * maintained by algorithms across frames. Unlike the session configuration,
+ * the active state is mutable and constantly updated by algorithms. The active
+ * state is accessible through the IPAContext structure.
*
- * \var IPASessionConfiguration::agc.maxShutterSpeed
- * \brief Maximum shutter speed supported with the configured sensor
+ * The active state stores two distinct categories of information:
*
- * \var IPASessionConfiguration::agc.minAnalogueGain
- * \brief Minimum analogue gain supported with the configured sensor
+ * - The consolidated value of all algorithm controls. Requests passed to
+ * the queueRequest() function store values for controls that the
+ * application wants to modify for that particular frame, and the
+ * queueRequest() function updates the active state with those values.
+ * The active state thus contains a consolidated view of the value of all
+ * controls handled by the algorithm.
*
- * \var IPASessionConfiguration::agc.maxAnalogueGain
- * \brief Maximum analogue gain supported with the configured sensor
+ * - The value of parameters computed by the algorithm when running in auto
+ * mode. Algorithms running in auto mode compute new parameters every
+ * time statistics buffers are received (either synchronously, or
+ * possibly in a background thread). The latest computed value of those
+ * parameters is stored in the active state in the process() function.
*
- * \var IPASessionConfiguration::agc.measureWindow
- * \brief AGC measure window
+ * Each of the members in the active state belongs to a specific algorithm. A
+ * member may be read by any algorithm, but shall only be written by its owner.
+ */
+
+/**
+ * \var IPAActiveState::agc
+ * \brief State for the Automatic Gain Control algorithm
*
- * \var IPASessionConfiguration::hw
- * \brief RkISP1-specific hardware information
+ * The exposure and gain are the latest values computed by the AGC algorithm.
*
- * \var IPASessionConfiguration::hw.revision
- * \brief Hardware revision of the ISP
+ * \var IPAActiveState::agc.exposure
+ * \brief Exposure time expressed as a number of lines
+ *
+ * \var IPAActiveState::agc.gain
+ * \brief Analogue gain multiplier
*/
/**
- * \var IPASessionConfiguration::awb
- * \brief AWB parameters configuration of the IPA
+ * \var IPAActiveState::awb
+ * \brief State for the Automatic White Balance algorithm
*
- * \var IPASessionConfiguration::awb.measureWindow
- * \brief AWB measure window
+ * \struct IPAActiveState::awb.gains
+ * \brief White balance gains
+ *
+ * \struct IPAActiveState::awb.gains.manual
+ * \brief Manual white balance gains (set through requests)
+ *
+ * \var IPAActiveState::awb.gains.manual.red
+ * \brief Manual white balance gain for R channel
+ *
+ * \var IPAActiveState::awb.gains.manual.green
+ * \brief Manual white balance gain for G channel
+ *
+ * \var IPAActiveState::awb.gains.manual.blue
+ * \brief Manual white balance gain for B channel
+ *
+ * \struct IPAActiveState::awb.gains.automatic
+ * \brief Automatic white balance gains (computed by the algorithm)
+ *
+ * \var IPAActiveState::awb.gains.automatic.red
+ * \brief Automatic white balance gain for R channel
+ *
+ * \var IPAActiveState::awb.gains.automatic.green
+ * \brief Automatic white balance gain for G channel
+ *
+ * \var IPAActiveState::awb.gains.automatic.blue
+ * \brief Automatic white balance gain for B channel
+ *
+ * \var IPAActiveState::awb.temperatureK
+ * \brief Estimated color temperature
+ *
+ * \var IPAActiveState::awb.autoEnabled
+ * \brief Whether the Auto White Balance algorithm is enabled
*/
/**
- * \var IPASessionConfiguration::sensor
- * \brief Sensor-specific configuration of the IPA
+ * \var IPAActiveState::cproc
+ * \brief State for the Color Processing algorithm
*
- * \var IPASessionConfiguration::sensor.lineDuration
- * \brief Line duration in microseconds
+ * \struct IPAActiveState::cproc.brightness
+ * \brief Brightness level
+ *
+ * \var IPAActiveState::cproc.contrast
+ * \brief Contrast level
+ *
+ * \var IPAActiveState::cproc.saturation
+ * \brief Saturation level
+ */
+
+/**
+ * \var IPAActiveState::dpf
+ * \brief State for the Denoise Pre-Filter algorithm
+ *
+ * \var IPAActiveState::dpf.denoise
+ * \brief Indicates if denoise is activated
+ */
+
+/**
+ * \var IPAActiveState::filter
+ * \brief State for the Filter algorithm
+ *
+ * \struct IPAActiveState::filter.denoise
+ * \brief Denoising level
+ *
+ * \var IPAActiveState::filter.sharpness
+ * \brief Sharpness level
+ */
+
+/**
+ * \struct IPAFrameContext
+ * \brief Per-frame context for algorithms
+ *
+ * The frame context stores two distinct categories of information:
+ *
+ * - The value of the controls to be applied to the frame. These values are
+ * typically set in the queueRequest() function, from the consolidated
+ * control values stored in the active state. The frame context thus stores
+ * values for all controls related to the algorithm, not limited to the
+ * controls specified in the corresponding request, but consolidated from all
+ * requests that have been queued so far.
+ *
+ * For controls that can be set manually or computed by an algorithm
+ * (depending on the algorithm operation mode), such as for instance the
+ * colour gains for the AWB algorithm, the control value will be stored in
+ * the frame context in the queueRequest() function only when operating in
+ * manual mode. When operating in auto mode, the values are computed by the
+ * algorithm in process(), stored in the active state, and copied to the
+ * frame context in prepare(), just before being stored in the ISP parameters
+ * buffer.
+ *
+ * The queueRequest() function can also store ancillary data in the frame
+ * context, such as flags to indicate if (and what) control values have
+ * changed compared to the previous request.
+ *
+ * - Status information computed by the algorithm for a frame. For instance,
+ * the colour temperature estimated by the AWB algorithm from ISP statistics
+ * calculated on a frame is stored in the frame context for that frame in
+ * the process() function.
*/
/**
* \var IPAFrameContext::agc
- * \brief Context for the Automatic Gain Control algorithm
+ * \brief Automatic Gain Control parameters for this frame
*
- * The exposure and gain determined are expected to be applied to the sensor
- * at the earliest opportunity.
+ * The exposure and gain are provided by the AGC algorithm, and are to be
+ * applied to the sensor in order to take effect for this frame.
*
* \var IPAFrameContext::agc.exposure
* \brief Exposure time expressed as a number of lines
@@ -115,7 +267,7 @@ namespace libcamera::ipa::rkisp1 {
/**
* \var IPAFrameContext::awb
- * \brief Context for the Automatic White Balance algorithm
+ * \brief Automatic White Balance parameters for this frame
*
* \struct IPAFrameContext::awb.gains
* \brief White balance gains
@@ -131,11 +283,59 @@ namespace libcamera::ipa::rkisp1 {
*
* \var IPAFrameContext::awb.temperatureK
* \brief Estimated color temperature
+ *
+ * \var IPAFrameContext::awb.autoEnabled
+ * \brief Whether the Auto White Balance algorithm is enabled
+ */
+
+/**
+ * \var IPAFrameContext::cproc
+ * \brief Color Processing parameters for this frame
+ *
+ * \struct IPAFrameContext::cproc.brightness
+ * \brief Brightness level
+ *
+ * \var IPAFrameContext::cproc.contrast
+ * \brief Contrast level
+ *
+ * \var IPAFrameContext::cproc.saturation
+ * \brief Saturation level
+ *
+ * \var IPAFrameContext::cproc.update
+ * \brief Indicates if the color processing parameters have been updated
+ * compared to the previous frame
+ */
+
+/**
+ * \var IPAFrameContext::dpf
+ * \brief Denoise Pre-Filter parameters for this frame
+ *
+ * \var IPAFrameContext::dpf.denoise
+ * \brief Indicates if denoise is activated
+ *
+ * \var IPAFrameContext::dpf.update
+ * \brief Indicates if the denoise pre-filter parameters have been updated
+ * compared to the previous frame
+ */
+
+/**
+ * \var IPAFrameContext::filter
+ * \brief Filter parameters for this frame
+ *
+ * \struct IPAFrameContext::filter.denoise
+ * \brief Denoising level
+ *
+ * \var IPAFrameContext::filter.sharpness
+ * \brief Sharpness level
+ *
+ * \var IPAFrameContext::filter.updateParams
+ * \brief Indicates if the filter parameters have been updated compared to the
+ * previous frame
*/
/**
* \var IPAFrameContext::sensor
- * \brief Effective sensor values
+ * \brief Sensor configuration that used been used for this frame
*
* \var IPAFrameContext::sensor.exposure
* \brief Exposure time expressed as a number of lines
@@ -145,12 +345,20 @@ namespace libcamera::ipa::rkisp1 {
*/
/**
- * \var IPAFrameContext::frameCount
- * \brief Counter of requests queued to the IPA module
+ * \struct IPAContext
+ * \brief Global IPA context data shared between all algorithms
+ *
+ * \var IPAContext::hw
+ * \brief RkISP1 version-specific hardware parameters
+ *
+ * \var IPAContext::configuration
+ * \brief The IPA session configuration, immutable during the session
+ *
+ * \var IPAContext::activeState
+ * \brief The IPA active state, storing the latest state for all algorithms
*
- * The counter is reset to 0 when the IPA module is configured, and is
- * incremented for each request being queued, after calling the
- * Algorithm::prepare() function of all algorithms.
+ * \var IPAContext::frameContexts
+ * \brief Ring buffer of per-frame contexts
*/
} /* namespace libcamera::ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index f387cace..10d8f38c 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -14,36 +14,100 @@
#include <libcamera/geometry.h>
+#include <libipa/fc_queue.h>
+
namespace libcamera {
namespace ipa::rkisp1 {
+struct IPAHwSettings {
+ unsigned int numAeCells;
+ unsigned int numHistogramBins;
+ unsigned int numHistogramWeights;
+ unsigned int numGammaOutSamples;
+};
+
struct IPASessionConfiguration {
struct {
- utils::Duration minShutterSpeed;
- utils::Duration maxShutterSpeed;
- double minAnalogueGain;
- double maxAnalogueGain;
struct rkisp1_cif_isp_window measureWindow;
} agc;
struct {
struct rkisp1_cif_isp_window measureWindow;
+ bool enabled;
} awb;
struct {
+ bool enabled;
+ } lsc;
+
+ struct {
+ utils::Duration minShutterSpeed;
+ utils::Duration maxShutterSpeed;
+ double minAnalogueGain;
+ double maxAnalogueGain;
+
+ int32_t defVBlank;
utils::Duration lineDuration;
+ Size size;
} sensor;
+ bool raw;
+};
+
+struct IPAActiveState {
+ struct {
+ struct {
+ uint32_t exposure;
+ double gain;
+ } manual;
+ struct {
+ uint32_t exposure;
+ double gain;
+ } automatic;
+
+ bool autoEnabled;
+ } agc;
+
+ struct {
+ struct {
+ struct {
+ double red;
+ double green;
+ double blue;
+ } manual;
+ struct {
+ double red;
+ double green;
+ double blue;
+ } automatic;
+ } gains;
+
+ unsigned int temperatureK;
+ bool autoEnabled;
+ } awb;
+
+ struct {
+ int8_t brightness;
+ uint8_t contrast;
+ uint8_t saturation;
+ } cproc;
+
struct {
- rkisp1_cif_isp_version revision;
- } hw;
+ bool denoise;
+ } dpf;
+
+ struct {
+ uint8_t denoise;
+ uint8_t sharpness;
+ } filter;
};
-struct IPAFrameContext {
+struct IPAFrameContext : public FrameContext {
struct {
uint32_t exposure;
double gain;
+ bool autoEnabled;
} agc;
struct {
@@ -53,20 +117,40 @@ struct IPAFrameContext {
double blue;
} gains;
- double temperatureK;
+ unsigned int temperatureK;
+ bool autoEnabled;
} awb;
struct {
+ int8_t brightness;
+ uint8_t contrast;
+ uint8_t saturation;
+ bool update;
+ } cproc;
+
+ struct {
+ bool denoise;
+ bool update;
+ } dpf;
+
+ struct {
+ uint8_t denoise;
+ uint8_t sharpness;
+ bool update;
+ } filter;
+
+ struct {
uint32_t exposure;
double gain;
} sensor;
-
- unsigned int frameCount;
};
struct IPAContext {
+ const IPAHwSettings *hw;
IPASessionConfiguration configuration;
- IPAFrameContext frameContext;
+ IPAActiveState activeState;
+
+ FCQueue<IPAFrameContext> frameContexts;
};
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build
index ccb84b27..e813da53 100644
--- a/src/ipa/rkisp1/meson.build
+++ b/src/ipa/rkisp1/meson.build
@@ -29,3 +29,5 @@ if ipa_sign_module
install : false,
build_by_default : true)
endif
+
+ipa_names += ipa_name
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 21166b0f..9dc5f53c 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -24,13 +24,11 @@
#include <libcamera/ipa/rkisp1_ipa_interface.h>
#include <libcamera/request.h>
+#include "libcamera/internal/formats.h"
#include "libcamera/internal/mapped_framebuffer.h"
#include "libcamera/internal/yaml_parser.h"
-#include "algorithms/agc.h"
#include "algorithms/algorithm.h"
-#include "algorithms/awb.h"
-#include "algorithms/blc.h"
#include "libipa/camera_sensor_helper.h"
#include "ipa_context.h"
@@ -43,16 +41,24 @@ using namespace std::literals::chrono_literals;
namespace ipa::rkisp1 {
+/* Maximum number of frame contexts to be held */
+static constexpr uint32_t kMaxFrameContexts = 16;
+
class IPARkISP1 : public IPARkISP1Interface, public Module
{
public:
- int init(const IPASettings &settings, unsigned int hwRevision) override;
+ IPARkISP1();
+
+ int init(const IPASettings &settings, unsigned int hwRevision,
+ const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls) override;
int start() override;
- void stop() override {}
+ void stop() override;
- int configure(const IPACameraSensorInfo &info,
+ int configure(const IPAConfigInfo &ipaConfig,
const std::map<uint32_t, IPAStream> &streamConfig,
- const std::map<uint32_t, ControlInfoMap> &entityControls) override;
+ ControlInfoMap *ipaControls) override;
void mapBuffers(const std::vector<IPABuffer> &buffers) override;
void unmapBuffers(const std::vector<unsigned int> &ids) override;
@@ -65,22 +71,15 @@ protected:
std::string logPrefix() const override;
private:
+ void updateControls(const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls);
void setControls(unsigned int frame);
- void prepareMetadata(unsigned int frame, unsigned int aeState);
std::map<unsigned int, FrameBuffer> buffers_;
std::map<unsigned int, MappedFrameBuffer> mappedBuffers_;
- ControlInfoMap ctrls_;
-
- /* Camera sensor controls. */
- bool autoExposure_;
-
- /* revision-specific data */
- rkisp1_cif_isp_version hwRevision_;
- unsigned int hwHistBinNMax_;
- unsigned int hwGammaOutMaxSamples_;
- unsigned int hwHistogramWeightGridsSize_;
+ ControlInfoMap sensorControls_;
/* Interface to the Camera Helper */
std::unique_ptr<CameraSensorHelper> camHelper_;
@@ -89,24 +88,59 @@ private:
struct IPAContext context_;
};
+namespace {
+
+const IPAHwSettings ipaHwSettingsV10{
+ RKISP1_CIF_ISP_AE_MEAN_MAX_V10,
+ RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10,
+ RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10,
+ RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10,
+};
+
+const IPAHwSettings ipaHwSettingsV12{
+ RKISP1_CIF_ISP_AE_MEAN_MAX_V12,
+ RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12,
+ RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12,
+ RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12,
+};
+
+/* List of controls handled by the RkISP1 IPA */
+const ControlInfoMap::Map rkisp1Controls{
+ { &controls::AeEnable, ControlInfo(false, true) },
+ { &controls::AwbEnable, ControlInfo(false, true) },
+ { &controls::ColourGains, ControlInfo(0.0f, 3.996f, 1.0f) },
+ { &controls::Brightness, ControlInfo(-1.0f, 0.993f, 0.0f) },
+ { &controls::Contrast, ControlInfo(0.0f, 1.993f, 1.0f) },
+ { &controls::Saturation, ControlInfo(0.0f, 1.993f, 1.0f) },
+ { &controls::Sharpness, ControlInfo(0.0f, 10.0f, 1.0f) },
+ { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) },
+};
+
+} /* namespace */
+
+IPARkISP1::IPARkISP1()
+ : context_({ {}, {}, {}, { kMaxFrameContexts } })
+{
+}
+
std::string IPARkISP1::logPrefix() const
{
return "rkisp1";
}
-int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision)
+int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
+ const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls)
{
/* \todo Add support for other revisions */
switch (hwRevision) {
case RKISP1_V10:
- hwHistBinNMax_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10;
- hwGammaOutMaxSamples_ = RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10;
- hwHistogramWeightGridsSize_ = RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10;
+ case RKISP1_V_IMX8MP:
+ context_.hw = &ipaHwSettingsV10;
break;
case RKISP1_V12:
- hwHistBinNMax_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12;
- hwGammaOutMaxSamples_ = RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12;
- hwHistogramWeightGridsSize_ = RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12;
+ context_.hw = &ipaHwSettingsV12;
break;
default:
LOG(IPARkISP1, Error)
@@ -117,10 +151,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision)
LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision;
- /* Cache the value to set it in configure. */
- hwRevision_ = static_cast<rkisp1_cif_isp_version>(hwRevision);
-
- camHelper_ = CameraSensorHelperFactory::create(settings.sensorModel);
+ camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
if (!camHelper_) {
LOG(IPARkISP1, Error)
<< "Failed to create camera sensor helper for "
@@ -128,8 +159,11 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision)
return -ENODEV;
}
+ context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
+ * 1.0s / sensorInfo.pixelRate;
+
/* Load the tuning data file. */
- File file(settings.configurationFile.c_str());
+ File file(settings.configurationFile);
if (!file.open(File::OpenModeFlag::ReadOnly)) {
int ret = file.error();
LOG(IPARkISP1, Error)
@@ -155,7 +189,14 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision)
return -EINVAL;
}
- return createAlgorithms(context_, (*data)["algorithms"]);
+ int ret = createAlgorithms(context_, (*data)["algorithms"]);
+ if (ret)
+ return ret;
+
+ /* Initialize controls. */
+ updateControls(sensorInfo, sensorControls, ipaControls);
+
+ return 0;
}
int IPARkISP1::start()
@@ -165,52 +206,42 @@ int IPARkISP1::start()
return 0;
}
-/**
- * \todo The RkISP1 pipeline currently provides an empty IPACameraSensorInfo
- * if the connected sensor does not provide enough information to properly
- * assemble one. Make sure the reported sensor information are relevant
- * before accessing them.
- */
-int IPARkISP1::configure([[maybe_unused]] const IPACameraSensorInfo &info,
- [[maybe_unused]] const std::map<uint32_t, IPAStream> &streamConfig,
- const std::map<uint32_t, ControlInfoMap> &entityControls)
+void IPARkISP1::stop()
{
- if (entityControls.empty())
- return -EINVAL;
-
- ctrls_ = entityControls.at(0);
-
- const auto itExp = ctrls_.find(V4L2_CID_EXPOSURE);
- if (itExp == ctrls_.end()) {
- LOG(IPARkISP1, Error) << "Can't find exposure control";
- return -EINVAL;
- }
-
- const auto itGain = ctrls_.find(V4L2_CID_ANALOGUE_GAIN);
- if (itGain == ctrls_.end()) {
- LOG(IPARkISP1, Error) << "Can't find gain control";
- return -EINVAL;
- }
+ context_.frameContexts.clear();
+}
- autoExposure_ = true;
+int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,
+ const std::map<uint32_t, IPAStream> &streamConfig,
+ ControlInfoMap *ipaControls)
+{
+ sensorControls_ = ipaConfig.sensorControls;
+ const auto itExp = sensorControls_.find(V4L2_CID_EXPOSURE);
int32_t minExposure = itExp->second.min().get<int32_t>();
int32_t maxExposure = itExp->second.max().get<int32_t>();
+ const auto itGain = sensorControls_.find(V4L2_CID_ANALOGUE_GAIN);
int32_t minGain = itGain->second.min().get<int32_t>();
int32_t maxGain = itGain->second.max().get<int32_t>();
- LOG(IPARkISP1, Info)
- << "Exposure: " << minExposure << "-" << maxExposure
- << " Gain: " << minGain << "-" << maxGain;
+ LOG(IPARkISP1, Debug)
+ << "Exposure: [" << minExposure << ", " << maxExposure
+ << "], gain: [" << minGain << ", " << maxGain << "]";
- /* Clean context at configuration */
- context_ = {};
+ /* Clear the IPA context before the streaming session. */
+ context_.configuration = {};
+ context_.activeState = {};
+ context_.frameContexts.clear();
- /* Set the hardware revision for the algorithms. */
- context_.configuration.hw.revision = hwRevision_;
+ const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
+ const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second;
+ context_.configuration.sensor.defVBlank = vBlank.def().get<int32_t>();
+ context_.configuration.sensor.size = info.outputSize;
+ context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate;
- context_.configuration.sensor.lineDuration = info.lineLength * 1.0s / info.pixelRate;
+ /* Update the camera controls using the new sensor settings. */
+ updateControls(info, sensorControls_, ipaControls);
/*
* When the AGC computes the new exposure values for a frame, it needs
@@ -219,14 +250,28 @@ int IPARkISP1::configure([[maybe_unused]] const IPACameraSensorInfo &info,
*
* \todo take VBLANK into account for maximum shutter speed
*/
- context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration;
- context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration;
- context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain);
- context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain);
-
- context_.frameContext.frameCount = 0;
+ context_.configuration.sensor.minShutterSpeed =
+ minExposure * context_.configuration.sensor.lineDuration;
+ context_.configuration.sensor.maxShutterSpeed =
+ maxExposure * context_.configuration.sensor.lineDuration;
+ context_.configuration.sensor.minAnalogueGain = camHelper_->gain(minGain);
+ context_.configuration.sensor.maxAnalogueGain = camHelper_->gain(maxGain);
+
+ context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(),
+ [](auto &cfg) -> bool {
+ PixelFormat pixelFormat{ cfg.second.pixelFormat };
+ const PixelFormatInfo &format = PixelFormatInfo::info(pixelFormat);
+ return format.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
+ });
+
+ for (auto const &a : algorithms()) {
+ Algorithm *algo = static_cast<Algorithm *>(a.get());
+
+ /* Disable algorithms that don't support raw formats. */
+ algo->disabled_ = context_.configuration.raw && !algo->supportsRaw_;
+ if (algo->disabled_)
+ continue;
- for (auto const &algo : algorithms()) {
int ret = algo->configure(context_, info);
if (ret)
return ret;
@@ -265,14 +310,22 @@ void IPARkISP1::unmapBuffers(const std::vector<unsigned int> &ids)
}
}
-void IPARkISP1::queueRequest([[maybe_unused]] const uint32_t frame,
- [[maybe_unused]] const ControlList &controls)
+void IPARkISP1::queueRequest(const uint32_t frame, const ControlList &controls)
{
- /* \todo Start processing for 'frame' based on 'controls'. */
+ IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
+
+ for (auto const &a : algorithms()) {
+ Algorithm *algo = static_cast<Algorithm *>(a.get());
+ if (algo->disabled_)
+ continue;
+ algo->queueRequest(context_, frame, frameContext, controls);
+ }
}
void IPARkISP1::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId)
{
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+
rkisp1_params_cfg *params =
reinterpret_cast<rkisp1_params_cfg *>(
mappedBuffers_.at(bufferId).planes()[0].data());
@@ -281,54 +334,118 @@ void IPARkISP1::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId)
memset(params, 0, sizeof(*params));
for (auto const &algo : algorithms())
- algo->prepare(context_, params);
+ algo->prepare(context_, frame, frameContext, params);
paramsBufferReady.emit(frame);
- context_.frameContext.frameCount++;
}
void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId,
const ControlList &sensorControls)
{
- const rkisp1_stat_buffer *stats =
- reinterpret_cast<rkisp1_stat_buffer *>(
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+
+ /*
+ * In raw capture mode, the ISP is bypassed and no statistics buffer is
+ * provided.
+ */
+ const rkisp1_stat_buffer *stats = nullptr;
+ if (!context_.configuration.raw)
+ stats = reinterpret_cast<rkisp1_stat_buffer *>(
mappedBuffers_.at(bufferId).planes()[0].data());
- context_.frameContext.sensor.exposure =
+ frameContext.sensor.exposure =
sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
- context_.frameContext.sensor.gain =
+ frameContext.sensor.gain =
camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
- unsigned int aeState = 0;
+ ControlList metadata(controls::controls);
- for (auto const &algo : algorithms())
- algo->process(context_, nullptr, stats);
+ for (auto const &a : algorithms()) {
+ Algorithm *algo = static_cast<Algorithm *>(a.get());
+ if (algo->disabled_)
+ continue;
+ algo->process(context_, frame, frameContext, stats, metadata);
+ }
setControls(frame);
- prepareMetadata(frame, aeState);
+ metadataReady.emit(frame, metadata);
}
-void IPARkISP1::setControls(unsigned int frame)
+void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls)
{
- uint32_t exposure = context_.frameContext.agc.exposure;
- uint32_t gain = camHelper_->gainCode(context_.frameContext.agc.gain);
+ ControlInfoMap::Map ctrlMap = rkisp1Controls;
- ControlList ctrls(ctrls_);
- ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
- ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));
+ /*
+ * Compute exposure time limits from the V4L2_CID_EXPOSURE control
+ * limits and the line duration.
+ */
+ double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
+ const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
+ int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration;
+ int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration;
+ int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration;
+ ctrlMap.emplace(std::piecewise_construct,
+ std::forward_as_tuple(&controls::ExposureTime),
+ std::forward_as_tuple(minExposure, maxExposure, defExposure));
+
+ /* Compute the analogue gain limits. */
+ const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
+ float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
+ float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
+ float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
+ ctrlMap.emplace(std::piecewise_construct,
+ std::forward_as_tuple(&controls::AnalogueGain),
+ std::forward_as_tuple(minGain, maxGain, defGain));
- setSensorControls.emit(frame, ctrls);
+ /*
+ * Compute the frame duration limits.
+ *
+ * The frame length is computed assuming a fixed line length combined
+ * with the vertical frame sizes.
+ */
+ const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second;
+ uint32_t hblank = v4l2HBlank.def().get<int32_t>();
+ uint32_t lineLength = sensorInfo.outputSize.width + hblank;
+
+ const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second;
+ std::array<uint32_t, 3> frameHeights{
+ v4l2VBlank.min().get<int32_t>() + sensorInfo.outputSize.height,
+ v4l2VBlank.max().get<int32_t>() + sensorInfo.outputSize.height,
+ v4l2VBlank.def().get<int32_t>() + sensorInfo.outputSize.height,
+ };
+
+ std::array<int64_t, 3> frameDurations;
+ for (unsigned int i = 0; i < frameHeights.size(); ++i) {
+ uint64_t frameSize = lineLength * frameHeights[i];
+ frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U);
+ }
+
+ ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0],
+ frameDurations[1],
+ frameDurations[2]);
+
+ *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
}
-void IPARkISP1::prepareMetadata(unsigned int frame, unsigned int aeState)
+void IPARkISP1::setControls(unsigned int frame)
{
- ControlList ctrls(controls::controls);
+ /*
+ * \todo The frame number is most likely wrong here, we need to take
+ * internal sensor delays and other timing parameters into account.
+ */
- if (aeState)
- ctrls.set(controls::AeLocked, aeState == 2);
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+ uint32_t exposure = frameContext.agc.exposure;
+ uint32_t gain = camHelper_->gainCode(frameContext.agc.gain);
- metadataReady.emit(frame, ctrls);
+ ControlList ctrls(sensorControls_);
+ ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
+ ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));
+
+ setSensorControls.emit(frame, ctrls);
}
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/raspberrypi/README.md b/src/ipa/rpi/README.md
index 94a8ccc8..94a8ccc8 100644
--- a/src/ipa/raspberrypi/README.md
+++ b/src/ipa/rpi/README.md
diff --git a/src/ipa/rpi/cam_helper/cam_helper.cpp b/src/ipa/rpi/cam_helper/cam_helper.cpp
new file mode 100644
index 00000000..ddd5e9a4
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper.cpp
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * cam_helper.cpp - helper information for different sensors
+ */
+
+#include <linux/videodev2.h>
+
+#include <limits>
+#include <map>
+#include <string.h>
+
+#include "libcamera/internal/v4l2_videodevice.h"
+
+#include "cam_helper.h"
+#include "md_parser.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using libcamera::utils::Duration;
+using namespace std::literals::chrono_literals;
+
+namespace libcamera {
+LOG_DECLARE_CATEGORY(IPARPI)
+}
+
+namespace {
+
+std::map<std::string, CamHelperCreateFunc> &camHelpers()
+{
+ static std::map<std::string, CamHelperCreateFunc> helpers;
+ return helpers;
+}
+
+} /* namespace */
+
+CamHelper *CamHelper::create(std::string const &camName)
+{
+ /*
+ * CamHelpers get registered by static RegisterCamHelper
+ * initialisers.
+ */
+ for (auto &p : camHelpers()) {
+ if (camName.find(p.first) != std::string::npos)
+ return p.second();
+ }
+
+ return nullptr;
+}
+
+CamHelper::CamHelper(std::unique_ptr<MdParser> parser, unsigned int frameIntegrationDiff)
+ : parser_(std::move(parser)), frameIntegrationDiff_(frameIntegrationDiff)
+{
+}
+
+CamHelper::~CamHelper()
+{
+}
+
+void CamHelper::prepare(Span<const uint8_t> buffer,
+ Metadata &metadata)
+{
+ parseEmbeddedData(buffer, metadata);
+}
+
+void CamHelper::process([[maybe_unused]] StatisticsPtr &stats,
+ [[maybe_unused]] Metadata &metadata)
+{
+}
+
+uint32_t CamHelper::exposureLines(const Duration exposure, const Duration lineLength) const
+{
+ return exposure / lineLength;
+}
+
+Duration CamHelper::exposure(uint32_t exposureLines, const Duration lineLength) const
+{
+ return exposureLines * lineLength;
+}
+
+std::pair<uint32_t, uint32_t> CamHelper::getBlanking(Duration &exposure,
+ Duration minFrameDuration,
+ Duration maxFrameDuration) const
+{
+ uint32_t frameLengthMin, frameLengthMax, vblank, hblank;
+ Duration lineLength = mode_.minLineLength;
+
+ /*
+ * minFrameDuration and maxFrameDuration are clamped by the caller
+ * based on the limits for the active sensor mode.
+ *
+ * frameLengthMax gets calculated on the smallest line length as we do
+ * not want to extend that unless absolutely necessary.
+ */
+ frameLengthMin = minFrameDuration / mode_.minLineLength;
+ frameLengthMax = maxFrameDuration / mode_.minLineLength;
+
+ /*
+ * Watch out for (exposureLines + frameIntegrationDiff_) overflowing a
+ * uint32_t in the std::clamp() below when the exposure time is
+ * extremely (extremely!) long - as happens when the IPA calculates the
+ * maximum possible exposure time.
+ */
+ uint32_t exposureLines = std::min(CamHelper::exposureLines(exposure, lineLength),
+ std::numeric_limits<uint32_t>::max() - frameIntegrationDiff_);
+ uint32_t frameLengthLines = std::clamp(exposureLines + frameIntegrationDiff_,
+ frameLengthMin, frameLengthMax);
+
+ /*
+ * If our frame length lines is above the maximum allowed, see if we can
+ * extend the line length to accommodate the requested frame length.
+ */
+ if (frameLengthLines > mode_.maxFrameLength) {
+ Duration lineLengthAdjusted = lineLength * frameLengthLines / mode_.maxFrameLength;
+ lineLength = std::min(mode_.maxLineLength, lineLengthAdjusted);
+ frameLengthLines = mode_.maxFrameLength;
+ }
+
+ hblank = lineLengthToHblank(lineLength);
+ vblank = frameLengthLines - mode_.height;
+
+ /*
+ * Limit the exposure to the maximum frame duration requested, and
+ * re-calculate if it has been clipped.
+ */
+ exposureLines = std::min(frameLengthLines - frameIntegrationDiff_,
+ CamHelper::exposureLines(exposure, lineLength));
+ exposure = CamHelper::exposure(exposureLines, lineLength);
+
+ return { vblank, hblank };
+}
+
+Duration CamHelper::hblankToLineLength(uint32_t hblank) const
+{
+ return (mode_.width + hblank) * (1.0s / mode_.pixelRate);
+}
+
+uint32_t CamHelper::lineLengthToHblank(const Duration &lineLength) const
+{
+ return (lineLength * mode_.pixelRate / 1.0s) - mode_.width;
+}
+
+Duration CamHelper::lineLengthPckToDuration(uint32_t lineLengthPck) const
+{
+ return lineLengthPck * (1.0s / mode_.pixelRate);
+}
+
+void CamHelper::setCameraMode(const CameraMode &mode)
+{
+ mode_ = mode;
+ if (parser_) {
+ parser_->reset();
+ parser_->setBitsPerPixel(mode.bitdepth);
+ parser_->setLineLengthBytes(0); /* We use SetBufferSize. */
+ }
+}
+
+void CamHelper::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ /*
+ * These values are correct for many sensors. Other sensors will
+ * need to over-ride this function.
+ */
+ exposureDelay = 2;
+ gainDelay = 1;
+ vblankDelay = 2;
+ hblankDelay = 2;
+}
+
+bool CamHelper::sensorEmbeddedDataPresent() const
+{
+ return false;
+}
+
+double CamHelper::getModeSensitivity([[maybe_unused]] const CameraMode &mode) const
+{
+ /*
+ * Most sensors have the same sensitivity in every mode, but this
+ * function can be overridden for those that do not. Note that it is
+ * called before mode_ is set, so it must return the sensitivity
+ * of the mode that is passed in.
+ */
+ return 1.0;
+}
+
+unsigned int CamHelper::hideFramesStartup() const
+{
+ /*
+ * The number of frames when a camera first starts that shouldn't be
+ * displayed as they are invalid in some way.
+ */
+ return 0;
+}
+
+unsigned int CamHelper::hideFramesModeSwitch() const
+{
+ /* After a mode switch, many sensors return valid frames immediately. */
+ return 0;
+}
+
+unsigned int CamHelper::mistrustFramesStartup() const
+{
+ /* Many sensors return a single bad frame on start-up. */
+ return 1;
+}
+
+unsigned int CamHelper::mistrustFramesModeSwitch() const
+{
+ /* Many sensors return valid metadata immediately. */
+ return 0;
+}
+
+void CamHelper::parseEmbeddedData(Span<const uint8_t> buffer,
+ Metadata &metadata)
+{
+ MdParser::RegisterMap registers;
+ Metadata parsedMetadata;
+
+ if (buffer.empty())
+ return;
+
+ if (parser_->parse(buffer, registers) != MdParser::Status::OK) {
+ LOG(IPARPI, Error) << "Embedded data buffer parsing failed";
+ return;
+ }
+
+ populateMetadata(registers, parsedMetadata);
+ metadata.merge(parsedMetadata);
+
+ /*
+ * Overwrite the exposure/gain, line/frame length and sensor temperature values
+ * in the existing DeviceStatus with values from the parsed embedded buffer.
+ * Fetch it first in case any other fields were set meaningfully.
+ */
+ DeviceStatus deviceStatus, parsedDeviceStatus;
+ if (metadata.get("device.status", deviceStatus) ||
+ parsedMetadata.get("device.status", parsedDeviceStatus)) {
+ LOG(IPARPI, Error) << "DeviceStatus not found";
+ return;
+ }
+
+ deviceStatus.shutterSpeed = parsedDeviceStatus.shutterSpeed;
+ deviceStatus.analogueGain = parsedDeviceStatus.analogueGain;
+ deviceStatus.frameLength = parsedDeviceStatus.frameLength;
+ deviceStatus.lineLength = parsedDeviceStatus.lineLength;
+ if (parsedDeviceStatus.sensorTemperature)
+ deviceStatus.sensorTemperature = parsedDeviceStatus.sensorTemperature;
+
+ LOG(IPARPI, Debug) << "Metadata updated - " << deviceStatus;
+
+ metadata.set("device.status", deviceStatus);
+}
+
+void CamHelper::populateMetadata([[maybe_unused]] const MdParser::RegisterMap &registers,
+ [[maybe_unused]] Metadata &metadata) const
+{
+}
+
+RegisterCamHelper::RegisterCamHelper(char const *camName,
+ CamHelperCreateFunc createFunc)
+{
+ camHelpers()[std::string(camName)] = createFunc;
+}
diff --git a/src/ipa/rpi/cam_helper/cam_helper.h b/src/ipa/rpi/cam_helper/cam_helper.h
new file mode 100644
index 00000000..58a4b202
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper.h
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * cam_helper.h - helper class providing camera information
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <libcamera/base/span.h>
+#include <libcamera/base/utils.h>
+
+#include "controller/camera_mode.h"
+#include "controller/controller.h"
+#include "controller/metadata.h"
+#include "md_parser.h"
+
+#include "libcamera/internal/v4l2_videodevice.h"
+
+namespace RPiController {
+
+/*
+ * The CamHelper class provides a number of facilities that anyone trying
+ * to drive a camera will need to know, but which are not provided by the
+ * standard driver framework. Specifically, it provides:
+ *
+ * A "CameraMode" structure to describe extra information about the chosen
+ * mode of the driver. For example, how it is cropped from the full sensor
+ * area, how it is scaled, whether pixels are averaged compared to the full
+ * resolution.
+ *
+ * The ability to convert between number of lines of exposure and actual
+ * exposure time, and to convert between the sensor's gain codes and actual
+ * gains.
+ *
+ * A function to return the number of frames of delay between updating exposure,
+ * analogue gain and vblanking, and for the changes to take effect. For many
+ * sensors these take the values 2, 1 and 2 respectively, but sensors that are
+ * different will need to over-ride the default function provided.
+ *
+ * A function to query if the sensor outputs embedded data that can be parsed.
+ *
+ * A function to return the sensitivity of a given camera mode.
+ *
+ * A parser to parse the embedded data buffers provided by some sensors (for
+ * example, the imx219 does; the ov5647 doesn't). This allows us to know for
+ * sure the exposure and gain of the frame we're looking at. CamHelper
+ * provides functions for converting analogue gains to and from the sensor's
+ * native gain codes.
+ *
+ * Finally, a set of functions that determine how to handle the vagaries of
+ * different camera modules on start-up or when switching modes. Some
+ * modules may produce one or more frames that are not yet correctly exposed,
+ * or where the metadata may be suspect. We have the following functions:
+ * HideFramesStartup(): Tell the pipeline handler not to return this many
+ * frames at start-up. This can also be used to hide initial frames
+ * while the AGC and other algorithms are sorting themselves out.
+ * HideFramesModeSwitch(): Tell the pipeline handler not to return this
+ * many frames after a mode switch (other than start-up). Some sensors
+ * may produce innvalid frames after a mode switch; others may not.
+ * MistrustFramesStartup(): At start-up a sensor may return frames for
+ * which we should not run any control algorithms (for example, metadata
+ * may be invalid).
+ * MistrustFramesModeSwitch(): The number of frames, after a mode switch
+ * (other than start-up), for which control algorithms should not run
+ * (for example, metadata may be unreliable).
+ */
+
+class CamHelper
+{
+public:
+ static CamHelper *create(std::string const &camName);
+ CamHelper(std::unique_ptr<MdParser> parser, unsigned int frameIntegrationDiff);
+ virtual ~CamHelper();
+ void setCameraMode(const CameraMode &mode);
+ virtual void prepare(libcamera::Span<const uint8_t> buffer,
+ Metadata &metadata);
+ virtual void process(StatisticsPtr &stats, Metadata &metadata);
+ virtual uint32_t exposureLines(const libcamera::utils::Duration exposure,
+ const libcamera::utils::Duration lineLength) const;
+ virtual libcamera::utils::Duration exposure(uint32_t exposureLines,
+ const libcamera::utils::Duration lineLength) const;
+ virtual std::pair<uint32_t, uint32_t> getBlanking(libcamera::utils::Duration &exposure,
+ libcamera::utils::Duration minFrameDuration,
+ libcamera::utils::Duration maxFrameDuration) const;
+ libcamera::utils::Duration hblankToLineLength(uint32_t hblank) const;
+ uint32_t lineLengthToHblank(const libcamera::utils::Duration &duration) const;
+ libcamera::utils::Duration lineLengthPckToDuration(uint32_t lineLengthPck) const;
+ virtual uint32_t gainCode(double gain) const = 0;
+ virtual double gain(uint32_t gainCode) const = 0;
+ virtual void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const;
+ virtual bool sensorEmbeddedDataPresent() const;
+ virtual double getModeSensitivity(const CameraMode &mode) const;
+ virtual unsigned int hideFramesStartup() const;
+ virtual unsigned int hideFramesModeSwitch() const;
+ virtual unsigned int mistrustFramesStartup() const;
+ virtual unsigned int mistrustFramesModeSwitch() const;
+
+protected:
+ void parseEmbeddedData(libcamera::Span<const uint8_t> buffer,
+ Metadata &metadata);
+ virtual void populateMetadata(const MdParser::RegisterMap &registers,
+ Metadata &metadata) const;
+
+ std::unique_ptr<MdParser> parser_;
+ CameraMode mode_;
+
+private:
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ unsigned int frameIntegrationDiff_;
+};
+
+/*
+ * This is for registering camera helpers with the system, so that the
+ * CamHelper::Create function picks them up automatically.
+ */
+
+typedef CamHelper *(*CamHelperCreateFunc)();
+struct RegisterCamHelper
+{
+ RegisterCamHelper(char const *camName,
+ CamHelperCreateFunc createFunc);
+};
+
+} /* namespace RPi */
diff --git a/src/ipa/raspberrypi/cam_helper_imx219.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx219.cpp
index a3caab71..c3337ed0 100644
--- a/src/ipa/raspberrypi/cam_helper_imx219.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx219.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
* cam_helper_imx219.cpp - camera helper for imx219 sensor
*/
@@ -16,9 +16,9 @@
*/
#define ENABLE_EMBEDDED_DATA 0
-#include "cam_helper.hpp"
+#include "cam_helper.h"
#if ENABLE_EMBEDDED_DATA
-#include "md_parser.hpp"
+#include "md_parser.h"
#endif
using namespace RPiController;
@@ -32,17 +32,20 @@ constexpr uint32_t expHiReg = 0x15a;
constexpr uint32_t expLoReg = 0x15b;
constexpr uint32_t frameLengthHiReg = 0x160;
constexpr uint32_t frameLengthLoReg = 0x161;
+constexpr uint32_t lineLengthHiReg = 0x162;
+constexpr uint32_t lineLengthLoReg = 0x163;
constexpr std::initializer_list<uint32_t> registerList [[maybe_unused]]
- = { expHiReg, expLoReg, gainReg, frameLengthHiReg, frameLengthLoReg };
+ = { expHiReg, expLoReg, gainReg, frameLengthHiReg, frameLengthLoReg,
+ lineLengthHiReg, lineLengthLoReg };
class CamHelperImx219 : public CamHelper
{
public:
CamHelperImx219();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- unsigned int MistrustFramesModeSwitch() const override;
- bool SensorEmbeddedDataPresent() const override;
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ unsigned int mistrustFramesModeSwitch() const override;
+ bool sensorEmbeddedDataPresent() const override;
private:
/*
@@ -51,7 +54,7 @@ private:
*/
static constexpr int frameIntegrationDiff = 4;
- void PopulateMetadata(const MdParser::RegisterMap &registers,
+ void populateMetadata(const MdParser::RegisterMap &registers,
Metadata &metadata) const override;
};
@@ -64,17 +67,17 @@ CamHelperImx219::CamHelperImx219()
{
}
-uint32_t CamHelperImx219::GainCode(double gain) const
+uint32_t CamHelperImx219::gainCode(double gain) const
{
return (uint32_t)(256 - 256 / gain);
}
-double CamHelperImx219::Gain(uint32_t gain_code) const
+double CamHelperImx219::gain(uint32_t gainCode) const
{
- return 256.0 / (256 - gain_code);
+ return 256.0 / (256 - gainCode);
}
-unsigned int CamHelperImx219::MistrustFramesModeSwitch() const
+unsigned int CamHelperImx219::mistrustFramesModeSwitch() const
{
/*
* For reasons unknown, we do occasionally get a bogus metadata frame
@@ -84,26 +87,29 @@ unsigned int CamHelperImx219::MistrustFramesModeSwitch() const
return 1;
}
-bool CamHelperImx219::SensorEmbeddedDataPresent() const
+bool CamHelperImx219::sensorEmbeddedDataPresent() const
{
return ENABLE_EMBEDDED_DATA;
}
-void CamHelperImx219::PopulateMetadata(const MdParser::RegisterMap &registers,
+void CamHelperImx219::populateMetadata(const MdParser::RegisterMap &registers,
Metadata &metadata) const
{
DeviceStatus deviceStatus;
- deviceStatus.shutter_speed = Exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg));
- deviceStatus.analogue_gain = Gain(registers.at(gainReg));
- deviceStatus.frame_length = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
+ deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 +
+ registers.at(lineLengthLoReg));
+ deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg),
+ deviceStatus.lineLength);
+ deviceStatus.analogueGain = gain(registers.at(gainReg));
+ deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
- metadata.Set("device.status", deviceStatus);
+ metadata.set("device.status", deviceStatus);
}
-static CamHelper *Create()
+static CamHelper *create()
{
return new CamHelperImx219();
}
-static RegisterCamHelper reg("imx219", &Create);
+static RegisterCamHelper reg("imx219", &create);
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp
new file mode 100644
index 00000000..d98b51cd
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2021, Raspberry Pi Ltd
+ *
+ * cam_helper_imx290.cpp - camera helper for imx290 sensor
+ */
+
+#include <math.h>
+
+#include "cam_helper.h"
+
+using namespace RPiController;
+
+class CamHelperImx290 : public CamHelper
+{
+public:
+ CamHelperImx290();
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ unsigned int hideFramesStartup() const override;
+ unsigned int hideFramesModeSwitch() const override;
+
+private:
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ static constexpr int frameIntegrationDiff = 2;
+};
+
+CamHelperImx290::CamHelperImx290()
+ : CamHelper({}, frameIntegrationDiff)
+{
+}
+
+uint32_t CamHelperImx290::gainCode(double gain) const
+{
+ int code = 66.6667 * log10(gain);
+ return std::max(0, std::min(code, 0xf0));
+}
+
+double CamHelperImx290::gain(uint32_t gainCode) const
+{
+ return pow(10, 0.015 * gainCode);
+}
+
+void CamHelperImx290::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
+}
+
+unsigned int CamHelperImx290::hideFramesStartup() const
+{
+ /* On startup, we seem to get 1 bad frame. */
+ return 1;
+}
+
+unsigned int CamHelperImx290::hideFramesModeSwitch() const
+{
+ /* After a mode switch, we seem to get 1 bad frame. */
+ return 1;
+}
+
+static CamHelper *create()
+{
+ return new CamHelperImx290();
+}
+
+static RegisterCamHelper reg("imx290", &create);
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp
new file mode 100644
index 00000000..ecb845e7
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * cam_helper_imx296.cpp - Camera helper for IMX296 sensor
+ */
+
+#include <algorithm>
+#include <cmath>
+#include <stddef.h>
+
+#include "cam_helper.h"
+
+using namespace RPiController;
+using libcamera::utils::Duration;
+using namespace std::literals::chrono_literals;
+
+class CamHelperImx296 : public CamHelper
+{
+public:
+ CamHelperImx296();
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ uint32_t exposureLines(const Duration exposure, const Duration lineLength) const override;
+ Duration exposure(uint32_t exposureLines, const Duration lineLength) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+
+private:
+ static constexpr uint32_t minExposureLines = 1;
+ static constexpr uint32_t maxGainCode = 239;
+ static constexpr Duration timePerLine = 550.0 / 37.125e6 * 1.0s;
+
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ static constexpr int frameIntegrationDiff = 4;
+};
+
+CamHelperImx296::CamHelperImx296()
+ : CamHelper(nullptr, frameIntegrationDiff)
+{
+}
+
+uint32_t CamHelperImx296::gainCode(double gain) const
+{
+ uint32_t code = 20 * std::log10(gain) * 10;
+ return std::min(code, maxGainCode);
+}
+
+double CamHelperImx296::gain(uint32_t gainCode) const
+{
+ return std::pow(10.0, gainCode / 200.0);
+}
+
+uint32_t CamHelperImx296::exposureLines(const Duration exposure,
+ [[maybe_unused]] const Duration lineLength) const
+{
+ return std::max<uint32_t>(minExposureLines, (exposure - 14.26us) / timePerLine);
+}
+
+Duration CamHelperImx296::exposure(uint32_t exposureLines,
+ [[maybe_unused]] const Duration lineLength) const
+{
+ return std::max<uint32_t>(minExposureLines, exposureLines) * timePerLine + 14.26us;
+}
+
+void CamHelperImx296::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
+}
+
+static CamHelper *create()
+{
+ return new CamHelperImx296();
+}
+
+static RegisterCamHelper reg("imx296", &create);
diff --git a/src/ipa/raspberrypi/cam_helper_imx477.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx477.cpp
index 0e1c0dbd..bc769ca7 100644
--- a/src/ipa/raspberrypi/cam_helper_imx477.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx477.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* cam_helper_imx477.cpp - camera helper for imx477 sensor
*/
@@ -14,8 +14,8 @@
#include <libcamera/base/log.h>
-#include "cam_helper.hpp"
-#include "md_parser.hpp"
+#include "cam_helper.h"
+#include "md_parser.h"
using namespace RPiController;
using namespace libcamera;
@@ -35,22 +35,25 @@ constexpr uint32_t gainHiReg = 0x0204;
constexpr uint32_t gainLoReg = 0x0205;
constexpr uint32_t frameLengthHiReg = 0x0340;
constexpr uint32_t frameLengthLoReg = 0x0341;
+constexpr uint32_t lineLengthHiReg = 0x0342;
+constexpr uint32_t lineLengthLoReg = 0x0343;
constexpr uint32_t temperatureReg = 0x013a;
constexpr std::initializer_list<uint32_t> registerList =
- { expHiReg, expLoReg, gainHiReg, gainLoReg, frameLengthHiReg, frameLengthLoReg, temperatureReg };
+ { expHiReg, expLoReg, gainHiReg, gainLoReg, frameLengthHiReg, frameLengthLoReg,
+ lineLengthHiReg, lineLengthLoReg, temperatureReg };
class CamHelperImx477 : public CamHelper
{
public:
CamHelperImx477();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- void Prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override;
- uint32_t GetVBlanking(Duration &exposure, Duration minFrameDuration,
- Duration maxFrameDuration) const override;
- void GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const override;
- bool SensorEmbeddedDataPresent() const override;
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override;
+ std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration,
+ Duration maxFrameDuration) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ bool sensorEmbeddedDataPresent() const override;
private:
/*
@@ -63,7 +66,7 @@ private:
/* Largest long exposure scale factor given as a left shift on the frame length. */
static constexpr int longExposureShiftMax = 7;
- void PopulateMetadata(const MdParser::RegisterMap &registers,
+ void populateMetadata(const MdParser::RegisterMap &registers,
Metadata &metadata) const override;
};
@@ -72,22 +75,22 @@ CamHelperImx477::CamHelperImx477()
{
}
-uint32_t CamHelperImx477::GainCode(double gain) const
+uint32_t CamHelperImx477::gainCode(double gain) const
{
return static_cast<uint32_t>(1024 - 1024 / gain);
}
-double CamHelperImx477::Gain(uint32_t gain_code) const
+double CamHelperImx477::gain(uint32_t gainCode) const
{
- return 1024.0 / (1024 - gain_code);
+ return 1024.0 / (1024 - gainCode);
}
-void CamHelperImx477::Prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata)
+void CamHelperImx477::prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata)
{
MdParser::RegisterMap registers;
DeviceStatus deviceStatus;
- if (metadata.Get("device.status", deviceStatus)) {
+ if (metadata.get("device.status", deviceStatus)) {
LOG(IPARPI, Error) << "DeviceStatus not found from DelayedControls";
return;
}
@@ -105,28 +108,32 @@ void CamHelperImx477::Prepare(libcamera::Span<const uint8_t> buffer, Metadata &m
* Otherwise, all values are updated with what is reported in the
* embedded data.
*/
- if (deviceStatus.frame_length > frameLengthMax) {
+ if (deviceStatus.frameLength > frameLengthMax) {
DeviceStatus parsedDeviceStatus;
- metadata.Get("device.status", parsedDeviceStatus);
- parsedDeviceStatus.shutter_speed = deviceStatus.shutter_speed;
- parsedDeviceStatus.frame_length = deviceStatus.frame_length;
- metadata.Set("device.status", parsedDeviceStatus);
+ metadata.get("device.status", parsedDeviceStatus);
+ parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed;
+ parsedDeviceStatus.frameLength = deviceStatus.frameLength;
+ metadata.set("device.status", parsedDeviceStatus);
LOG(IPARPI, Debug) << "Metadata updated for long exposure: "
<< parsedDeviceStatus;
}
}
-uint32_t CamHelperImx477::GetVBlanking(Duration &exposure,
- Duration minFrameDuration,
- Duration maxFrameDuration) const
+std::pair<uint32_t, uint32_t> CamHelperImx477::getBlanking(Duration &exposure,
+ Duration minFrameDuration,
+ Duration maxFrameDuration) const
{
uint32_t frameLength, exposureLines;
unsigned int shift = 0;
- frameLength = mode_.height + CamHelper::GetVBlanking(exposure, minFrameDuration,
- maxFrameDuration);
+ auto [vblank, hblank] = CamHelper::getBlanking(exposure, minFrameDuration,
+ maxFrameDuration);
+
+ frameLength = mode_.height + vblank;
+ Duration lineLength = hblankToLineLength(hblank);
+
/*
* Check if the frame length calculated needs to be setup for long
* exposure mode. This will require us to use a long exposure scale
@@ -144,43 +151,47 @@ uint32_t CamHelperImx477::GetVBlanking(Duration &exposure,
if (shift) {
/* Account for any rounding in the scaled frame length value. */
frameLength <<= shift;
- exposureLines = ExposureLines(exposure);
+ exposureLines = CamHelperImx477::exposureLines(exposure, lineLength);
exposureLines = std::min(exposureLines, frameLength - frameIntegrationDiff);
- exposure = Exposure(exposureLines);
+ exposure = CamHelperImx477::exposure(exposureLines, lineLength);
}
- return frameLength - mode_.height;
+ return { frameLength - mode_.height, hblank };
}
-void CamHelperImx477::GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const
+void CamHelperImx477::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
{
- exposure_delay = 2;
- gain_delay = 2;
- vblank_delay = 3;
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 3;
+ hblankDelay = 3;
}
-bool CamHelperImx477::SensorEmbeddedDataPresent() const
+bool CamHelperImx477::sensorEmbeddedDataPresent() const
{
return true;
}
-void CamHelperImx477::PopulateMetadata(const MdParser::RegisterMap &registers,
+void CamHelperImx477::populateMetadata(const MdParser::RegisterMap &registers,
Metadata &metadata) const
{
DeviceStatus deviceStatus;
- deviceStatus.shutter_speed = Exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg));
- deviceStatus.analogue_gain = Gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg));
- deviceStatus.frame_length = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
- deviceStatus.sensor_temperature = std::clamp<int8_t>(registers.at(temperatureReg), -20, 80);
+ deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 +
+ registers.at(lineLengthLoReg));
+ deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg),
+ deviceStatus.lineLength);
+ deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg));
+ deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
+ deviceStatus.sensorTemperature = std::clamp<int8_t>(registers.at(temperatureReg), -20, 80);
- metadata.Set("device.status", deviceStatus);
+ metadata.set("device.status", deviceStatus);
}
-static CamHelper *Create()
+static CamHelper *create()
{
return new CamHelperImx477();
}
-static RegisterCamHelper reg("imx477", &Create);
+static RegisterCamHelper reg("imx477", &create);
diff --git a/src/ipa/raspberrypi/cam_helper_imx519.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx519.cpp
index eaf24982..c7262aa0 100644
--- a/src/ipa/raspberrypi/cam_helper_imx519.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx519.cpp
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Based on cam_helper_imx477.cpp
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* cam_helper_imx519.cpp - camera helper for imx519 sensor
* Copyright (C) 2021, Arducam Technology co., Ltd.
@@ -15,8 +15,8 @@
#include <libcamera/base/log.h>
-#include "cam_helper.hpp"
-#include "md_parser.hpp"
+#include "cam_helper.h"
+#include "md_parser.h"
using namespace RPiController;
using namespace libcamera;
@@ -36,21 +36,24 @@ constexpr uint32_t gainHiReg = 0x0204;
constexpr uint32_t gainLoReg = 0x0205;
constexpr uint32_t frameLengthHiReg = 0x0340;
constexpr uint32_t frameLengthLoReg = 0x0341;
+constexpr uint32_t lineLengthHiReg = 0x0342;
+constexpr uint32_t lineLengthLoReg = 0x0343;
constexpr std::initializer_list<uint32_t> registerList =
- { expHiReg, expLoReg, gainHiReg, gainLoReg, frameLengthHiReg, frameLengthLoReg };
+ { expHiReg, expLoReg, gainHiReg, gainLoReg, frameLengthHiReg, frameLengthLoReg,
+ lineLengthHiReg, lineLengthLoReg };
class CamHelperImx519 : public CamHelper
{
public:
CamHelperImx519();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- void Prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override;
- uint32_t GetVBlanking(Duration &exposure, Duration minFrameDuration,
- Duration maxFrameDuration) const override;
- void GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const override;
- bool SensorEmbeddedDataPresent() const override;
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override;
+ std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration,
+ Duration maxFrameDuration) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ bool sensorEmbeddedDataPresent() const override;
private:
/*
@@ -63,7 +66,7 @@ private:
/* Largest long exposure scale factor given as a left shift on the frame length. */
static constexpr int longExposureShiftMax = 7;
- void PopulateMetadata(const MdParser::RegisterMap &registers,
+ void populateMetadata(const MdParser::RegisterMap &registers,
Metadata &metadata) const override;
};
@@ -72,22 +75,22 @@ CamHelperImx519::CamHelperImx519()
{
}
-uint32_t CamHelperImx519::GainCode(double gain) const
+uint32_t CamHelperImx519::gainCode(double gain) const
{
return static_cast<uint32_t>(1024 - 1024 / gain);
}
-double CamHelperImx519::Gain(uint32_t gain_code) const
+double CamHelperImx519::gain(uint32_t gainCode) const
{
- return 1024.0 / (1024 - gain_code);
+ return 1024.0 / (1024 - gainCode);
}
-void CamHelperImx519::Prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata)
+void CamHelperImx519::prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata)
{
MdParser::RegisterMap registers;
DeviceStatus deviceStatus;
- if (metadata.Get("device.status", deviceStatus)) {
+ if (metadata.get("device.status", deviceStatus)) {
LOG(IPARPI, Error) << "DeviceStatus not found from DelayedControls";
return;
}
@@ -105,28 +108,32 @@ void CamHelperImx519::Prepare(libcamera::Span<const uint8_t> buffer, Metadata &m
* Otherwise, all values are updated with what is reported in the
* embedded data.
*/
- if (deviceStatus.frame_length > frameLengthMax) {
+ if (deviceStatus.frameLength > frameLengthMax) {
DeviceStatus parsedDeviceStatus;
- metadata.Get("device.status", parsedDeviceStatus);
- parsedDeviceStatus.shutter_speed = deviceStatus.shutter_speed;
- parsedDeviceStatus.frame_length = deviceStatus.frame_length;
- metadata.Set("device.status", parsedDeviceStatus);
+ metadata.get("device.status", parsedDeviceStatus);
+ parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed;
+ parsedDeviceStatus.frameLength = deviceStatus.frameLength;
+ metadata.set("device.status", parsedDeviceStatus);
LOG(IPARPI, Debug) << "Metadata updated for long exposure: "
<< parsedDeviceStatus;
}
}
-uint32_t CamHelperImx519::GetVBlanking(Duration &exposure,
- Duration minFrameDuration,
- Duration maxFrameDuration) const
+std::pair<uint32_t, uint32_t> CamHelperImx519::getBlanking(Duration &exposure,
+ Duration minFrameDuration,
+ Duration maxFrameDuration) const
{
uint32_t frameLength, exposureLines;
unsigned int shift = 0;
- frameLength = mode_.height + CamHelper::GetVBlanking(exposure, minFrameDuration,
- maxFrameDuration);
+ auto [vblank, hblank] = CamHelper::getBlanking(exposure, minFrameDuration,
+ maxFrameDuration);
+
+ frameLength = mode_.height + vblank;
+ Duration lineLength = hblankToLineLength(hblank);
+
/*
* Check if the frame length calculated needs to be setup for long
* exposure mode. This will require us to use a long exposure scale
@@ -144,42 +151,46 @@ uint32_t CamHelperImx519::GetVBlanking(Duration &exposure,
if (shift) {
/* Account for any rounding in the scaled frame length value. */
frameLength <<= shift;
- exposureLines = ExposureLines(exposure);
+ exposureLines = CamHelperImx519::exposureLines(exposure, lineLength);
exposureLines = std::min(exposureLines, frameLength - frameIntegrationDiff);
- exposure = Exposure(exposureLines);
+ exposure = CamHelperImx519::exposure(exposureLines, lineLength);
}
- return frameLength - mode_.height;
+ return { frameLength - mode_.height, hblank };
}
-void CamHelperImx519::GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const
+void CamHelperImx519::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
{
- exposure_delay = 2;
- gain_delay = 2;
- vblank_delay = 3;
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 3;
+ hblankDelay = 3;
}
-bool CamHelperImx519::SensorEmbeddedDataPresent() const
+bool CamHelperImx519::sensorEmbeddedDataPresent() const
{
return true;
}
-void CamHelperImx519::PopulateMetadata(const MdParser::RegisterMap &registers,
+void CamHelperImx519::populateMetadata(const MdParser::RegisterMap &registers,
Metadata &metadata) const
{
DeviceStatus deviceStatus;
- deviceStatus.shutter_speed = Exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg));
- deviceStatus.analogue_gain = Gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg));
- deviceStatus.frame_length = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
+ deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 +
+ registers.at(lineLengthLoReg));
+ deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg),
+ deviceStatus.lineLength);
+ deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg));
+ deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
- metadata.Set("device.status", deviceStatus);
+ metadata.set("device.status", deviceStatus);
}
-static CamHelper *Create()
+static CamHelper *create()
{
return new CamHelperImx519();
}
-static RegisterCamHelper reg("imx519", &Create);
+static RegisterCamHelper reg("imx519", &create);
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp
new file mode 100644
index 00000000..906c6fa2
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp
@@ -0,0 +1,382 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * cam_helper_imx708.cpp - camera helper for imx708 sensor
+ */
+
+#include <cmath>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libcamera/base/log.h>
+
+#include "controller/pdaf_data.h"
+
+#include "cam_helper.h"
+#include "md_parser.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using libcamera::utils::Duration;
+
+using namespace std::literals::chrono_literals;
+
+namespace libcamera {
+LOG_DECLARE_CATEGORY(IPARPI)
+}
+
+/*
+ * We care about two gain registers and a pair of exposure registers. Their
+ * I2C addresses from the Sony imx708 datasheet:
+ */
+constexpr uint32_t expHiReg = 0x0202;
+constexpr uint32_t expLoReg = 0x0203;
+constexpr uint32_t gainHiReg = 0x0204;
+constexpr uint32_t gainLoReg = 0x0205;
+constexpr uint32_t frameLengthHiReg = 0x0340;
+constexpr uint32_t frameLengthLoReg = 0x0341;
+constexpr uint32_t lineLengthHiReg = 0x0342;
+constexpr uint32_t lineLengthLoReg = 0x0343;
+constexpr uint32_t temperatureReg = 0x013a;
+constexpr std::initializer_list<uint32_t> registerList =
+ { expHiReg, expLoReg, gainHiReg, gainLoReg, lineLengthHiReg,
+ lineLengthLoReg, frameLengthHiReg, frameLengthLoReg, temperatureReg };
+
+class CamHelperImx708 : public CamHelper
+{
+public:
+ CamHelperImx708();
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gain_code) const override;
+ void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override;
+ void process(StatisticsPtr &stats, Metadata &metadata) override;
+ std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration,
+ Duration maxFrameDuration) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ bool sensorEmbeddedDataPresent() const override;
+ double getModeSensitivity(const CameraMode &mode) const override;
+ unsigned int hideFramesModeSwitch() const override;
+ unsigned int hideFramesStartup() const override;
+
+private:
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ static constexpr int frameIntegrationDiff = 22;
+ /* Maximum frame length allowable for long exposure calculations. */
+ static constexpr int frameLengthMax = 0xffdc;
+ /* Largest long exposure scale factor given as a left shift on the frame length. */
+ static constexpr int longExposureShiftMax = 7;
+
+ static constexpr int pdafStatsRows = 12;
+ static constexpr int pdafStatsCols = 16;
+
+ void populateMetadata(const MdParser::RegisterMap &registers,
+ Metadata &metadata) const override;
+
+ static bool parsePdafData(const uint8_t *ptr, size_t len, unsigned bpp,
+ PdafRegions &pdaf);
+
+ bool parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp);
+ void putAGCStatistics(StatisticsPtr stats);
+
+ Histogram aeHistLinear_;
+ uint32_t aeHistAverage_;
+ bool aeHistValid_;
+};
+
+CamHelperImx708::CamHelperImx708()
+ : CamHelper(std::make_unique<MdParserSmia>(registerList), frameIntegrationDiff),
+ aeHistLinear_{}, aeHistAverage_(0), aeHistValid_(false)
+{
+}
+
+uint32_t CamHelperImx708::gainCode(double gain) const
+{
+ return static_cast<uint32_t>(1024 - 1024 / gain);
+}
+
+double CamHelperImx708::gain(uint32_t gain_code) const
+{
+ return 1024.0 / (1024 - gain_code);
+}
+
+void CamHelperImx708::prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata)
+{
+ MdParser::RegisterMap registers;
+ DeviceStatus deviceStatus;
+
+ LOG(IPARPI, Debug) << "Embedded buffer size: " << buffer.size();
+
+ if (metadata.get("device.status", deviceStatus)) {
+ LOG(IPARPI, Error) << "DeviceStatus not found from DelayedControls";
+ return;
+ }
+
+ parseEmbeddedData(buffer, metadata);
+
+ /*
+ * Parse PDAF data, which we expect to occupy the third scanline
+ * of embedded data. As PDAF is quite sensor-specific, it's parsed here.
+ */
+ size_t bytesPerLine = (mode_.width * mode_.bitdepth) >> 3;
+
+ if (buffer.size() > 2 * bytesPerLine) {
+ PdafRegions pdaf;
+ if (parsePdafData(&buffer[2 * bytesPerLine],
+ buffer.size() - 2 * bytesPerLine,
+ mode_.bitdepth, pdaf))
+ metadata.set("pdaf.regions", pdaf);
+ }
+
+ /* Parse AE-HIST data where present */
+ if (buffer.size() > 3 * bytesPerLine) {
+ aeHistValid_ = parseAEHist(&buffer[3 * bytesPerLine],
+ buffer.size() - 3 * bytesPerLine,
+ mode_.bitdepth);
+ }
+
+ /*
+ * The DeviceStatus struct is first populated with values obtained from
+ * DelayedControls. If this reports frame length is > frameLengthMax,
+ * it means we are using a long exposure mode. Since the long exposure
+ * scale factor is not returned back through embedded data, we must rely
+ * on the existing exposure lines and frame length values returned by
+ * DelayedControls.
+ *
+ * Otherwise, all values are updated with what is reported in the
+ * embedded data.
+ */
+ if (deviceStatus.frameLength > frameLengthMax) {
+ DeviceStatus parsedDeviceStatus;
+
+ metadata.get("device.status", parsedDeviceStatus);
+ parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed;
+ parsedDeviceStatus.frameLength = deviceStatus.frameLength;
+ metadata.set("device.status", parsedDeviceStatus);
+
+ LOG(IPARPI, Debug) << "Metadata updated for long exposure: "
+ << parsedDeviceStatus;
+ }
+}
+
+void CamHelperImx708::process(StatisticsPtr &stats, [[maybe_unused]] Metadata &metadata)
+{
+ if (aeHistValid_)
+ putAGCStatistics(stats);
+}
+
+std::pair<uint32_t, uint32_t> CamHelperImx708::getBlanking(Duration &exposure,
+ Duration minFrameDuration,
+ Duration maxFrameDuration) const
+{
+ uint32_t frameLength, exposureLines;
+ unsigned int shift = 0;
+
+ auto [vblank, hblank] = CamHelper::getBlanking(exposure, minFrameDuration,
+ maxFrameDuration);
+
+ frameLength = mode_.height + vblank;
+ Duration lineLength = hblankToLineLength(hblank);
+
+ /*
+ * Check if the frame length calculated needs to be setup for long
+ * exposure mode. This will require us to use a long exposure scale
+ * factor provided by a shift operation in the sensor.
+ */
+ while (frameLength > frameLengthMax) {
+ if (++shift > longExposureShiftMax) {
+ shift = longExposureShiftMax;
+ frameLength = frameLengthMax;
+ break;
+ }
+ frameLength >>= 1;
+ }
+
+ if (shift) {
+ /* Account for any rounding in the scaled frame length value. */
+ frameLength <<= shift;
+ exposureLines = CamHelper::exposureLines(exposure, lineLength);
+ exposureLines = std::min(exposureLines, frameLength - frameIntegrationDiff);
+ exposure = CamHelper::exposure(exposureLines, lineLength);
+ }
+
+ return { frameLength - mode_.height, hblank };
+}
+
+void CamHelperImx708::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 3;
+ hblankDelay = 3;
+}
+
+bool CamHelperImx708::sensorEmbeddedDataPresent() const
+{
+ return true;
+}
+
+double CamHelperImx708::getModeSensitivity(const CameraMode &mode) const
+{
+ /* In binned modes, sensitivity increases by a factor of 2 */
+ return (mode.width > 2304) ? 1.0 : 2.0;
+}
+
+unsigned int CamHelperImx708::hideFramesModeSwitch() const
+{
+ /*
+ * We need to drop the first startup frame in HDR mode.
+ * Unfortunately the only way to currently determine if the sensor is in
+ * the HDR mode is to match with the resolution and framerate - the HDR
+ * mode only runs upto 30fps.
+ */
+ if (mode_.width == 2304 && mode_.height == 1296 &&
+ mode_.minFrameDuration > 1.0s / 32)
+ return 1;
+ else
+ return 0;
+}
+
+unsigned int CamHelperImx708::hideFramesStartup() const
+{
+ return hideFramesModeSwitch();
+}
+
+void CamHelperImx708::populateMetadata(const MdParser::RegisterMap &registers,
+ Metadata &metadata) const
+{
+ DeviceStatus deviceStatus;
+
+ deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 +
+ registers.at(lineLengthLoReg));
+ deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg),
+ deviceStatus.lineLength);
+ deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg));
+ deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
+ deviceStatus.sensorTemperature = std::clamp<int8_t>(registers.at(temperatureReg), -20, 80);
+
+ metadata.set("device.status", deviceStatus);
+}
+
+bool CamHelperImx708::parsePdafData(const uint8_t *ptr, size_t len,
+ unsigned bpp, PdafRegions &pdaf)
+{
+ size_t step = bpp >> 1; /* bytes per PDAF grid entry */
+
+ if (bpp < 10 || bpp > 14 || len < 194 * step || ptr[0] != 0 || ptr[1] >= 0x40) {
+ LOG(IPARPI, Error) << "PDAF data in unsupported format";
+ return false;
+ }
+
+ pdaf.init({ pdafStatsCols, pdafStatsRows });
+
+ ptr += 2 * step;
+ for (unsigned i = 0; i < pdafStatsRows; ++i) {
+ for (unsigned j = 0; j < pdafStatsCols; ++j) {
+ unsigned c = (ptr[0] << 3) | (ptr[1] >> 5);
+ int p = (((ptr[1] & 0x0F) - (ptr[1] & 0x10)) << 6) | (ptr[2] >> 2);
+ PdafData pdafData;
+ pdafData.conf = c;
+ pdafData.phase = c ? p : 0;
+ pdaf.set(libcamera::Point(j, i), { pdafData, 1, 0 });
+ ptr += step;
+ }
+ }
+
+ return true;
+}
+
+bool CamHelperImx708::parseAEHist(const uint8_t *ptr, size_t len, unsigned bpp)
+{
+ static constexpr unsigned int PipelineBits = Statistics::NormalisationFactorPow2;
+
+ uint64_t count = 0, sum = 0;
+ size_t step = bpp >> 1; /* bytes per histogram bin */
+ uint32_t hist[128];
+
+ if (len < 144 * step)
+ return false;
+
+ /*
+ * Read the 128 bin linear histogram, which by default covers
+ * the full range of the HDR shortest exposure (small values are
+ * expected to dominate, so pixel-value resolution will be poor).
+ */
+ for (unsigned i = 0; i < 128; ++i) {
+ if (ptr[3] != 0x55)
+ return false;
+ uint32_t c = (ptr[0] << 14) + (ptr[1] << 6) + (ptr[2] >> 2);
+ hist[i] = c >> 2; /* pixels to quads */
+ if (i != 0) {
+ count += c;
+ sum += c *
+ (i * (1u << (PipelineBits - 7)) +
+ (1u << (PipelineBits - 8)));
+ }
+ ptr += step;
+ }
+
+ /*
+ * Now use the first 9 bins of the log histogram (these should be
+ * subdivisions of the smallest linear bin), to get a more accurate
+ * average value. Don't assume that AEHIST1_AVERAGE is present.
+ */
+ for (unsigned i = 0; i < 9; ++i) {
+ if (ptr[3] != 0x55)
+ return false;
+ uint32_t c = (ptr[0] << 14) + (ptr[1] << 6) + (ptr[2] >> 2);
+ count += c;
+ sum += c *
+ ((3u << PipelineBits) >> (17 - i));
+ ptr += step;
+ }
+ if ((unsigned)((ptr[0] << 12) + (ptr[1] << 4) + (ptr[2] >> 4)) !=
+ hist[1]) {
+ LOG(IPARPI, Error) << "Lin/Log histogram mismatch";
+ return false;
+ }
+
+ aeHistLinear_ = Histogram(hist, 128);
+ aeHistAverage_ = count ? (sum / count) : 0;
+
+ return count != 0;
+}
+
+void CamHelperImx708::putAGCStatistics(StatisticsPtr stats)
+{
+ /*
+ * For HDR mode, copy sensor's AE/AGC statistics over ISP's, so the
+ * AGC algorithm sees a linear response to exposure and gain changes.
+ *
+ * Histogram: Just copy the "raw" histogram over the tone-mapped one,
+ * although they have different distributions (raw values are lower).
+ * Tuning should either ignore it, or constrain for highlights only.
+ *
+ * Average: Overwrite all regional averages with a global raw average,
+ * scaled by a fiddle-factor so that a conventional (non-HDR) y_target
+ * of e.g. 0.17 will map to a suitable level for HDR.
+ */
+ stats->yHist = aeHistLinear_;
+
+ constexpr unsigned int HdrHeadroomFactor = 4;
+ uint64_t v = HdrHeadroomFactor * aeHistAverage_;
+ for (auto &region : stats->agcRegions) {
+ region.val.rSum = region.val.gSum = region.val.bSum = region.counted * v;
+ }
+}
+
+static CamHelper *create()
+{
+ return new CamHelperImx708();
+}
+
+static RegisterCamHelper reg("imx708", &create);
+static RegisterCamHelper regWide("imx708_wide", &create);
+static RegisterCamHelper regNoIr("imx708_noir", &create);
+static RegisterCamHelper regWideNoIr("imx708_wide_noir", &create);
diff --git a/src/ipa/raspberrypi/cam_helper_ov5647.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp
index 702c2d07..5a99083d 100644
--- a/src/ipa/raspberrypi/cam_helper_ov5647.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp
@@ -1,13 +1,13 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
* cam_helper_ov5647.cpp - camera information for ov5647 sensor
*/
#include <assert.h>
-#include "cam_helper.hpp"
+#include "cam_helper.h"
using namespace RPiController;
@@ -15,14 +15,14 @@ class CamHelperOv5647 : public CamHelper
{
public:
CamHelperOv5647();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- void GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const override;
- unsigned int HideFramesStartup() const override;
- unsigned int HideFramesModeSwitch() const override;
- unsigned int MistrustFramesStartup() const override;
- unsigned int MistrustFramesModeSwitch() const override;
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ unsigned int hideFramesStartup() const override;
+ unsigned int hideFramesModeSwitch() const override;
+ unsigned int mistrustFramesStartup() const override;
+ unsigned int mistrustFramesModeSwitch() const override;
private:
/*
@@ -42,29 +42,30 @@ CamHelperOv5647::CamHelperOv5647()
{
}
-uint32_t CamHelperOv5647::GainCode(double gain) const
+uint32_t CamHelperOv5647::gainCode(double gain) const
{
return static_cast<uint32_t>(gain * 16.0);
}
-double CamHelperOv5647::Gain(uint32_t gain_code) const
+double CamHelperOv5647::gain(uint32_t gainCode) const
{
- return static_cast<double>(gain_code) / 16.0;
+ return static_cast<double>(gainCode) / 16.0;
}
-void CamHelperOv5647::GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const
+void CamHelperOv5647::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
{
/*
* We run this sensor in a mode where the gain delay is bumped up to
* 2. It seems to be the only way to make the delays "predictable".
*/
- exposure_delay = 2;
- gain_delay = 2;
- vblank_delay = 2;
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
}
-unsigned int CamHelperOv5647::HideFramesStartup() const
+unsigned int CamHelperOv5647::hideFramesStartup() const
{
/*
* On startup, we get a couple of under-exposed frames which
@@ -73,7 +74,7 @@ unsigned int CamHelperOv5647::HideFramesStartup() const
return 2;
}
-unsigned int CamHelperOv5647::HideFramesModeSwitch() const
+unsigned int CamHelperOv5647::hideFramesModeSwitch() const
{
/*
* After a mode switch, we get a couple of under-exposed frames which
@@ -82,7 +83,7 @@ unsigned int CamHelperOv5647::HideFramesModeSwitch() const
return 2;
}
-unsigned int CamHelperOv5647::MistrustFramesStartup() const
+unsigned int CamHelperOv5647::mistrustFramesStartup() const
{
/*
* First couple of frames are under-exposed and are no good for control
@@ -91,7 +92,7 @@ unsigned int CamHelperOv5647::MistrustFramesStartup() const
return 2;
}
-unsigned int CamHelperOv5647::MistrustFramesModeSwitch() const
+unsigned int CamHelperOv5647::mistrustFramesModeSwitch() const
{
/*
* First couple of frames are under-exposed even after a simple
@@ -100,9 +101,9 @@ unsigned int CamHelperOv5647::MistrustFramesModeSwitch() const
return 2;
}
-static CamHelper *Create()
+static CamHelper *create()
{
return new CamHelperOv5647();
}
-static RegisterCamHelper reg("ov5647", &Create);
+static RegisterCamHelper reg("ov5647", &create);
diff --git a/src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp
new file mode 100644
index 00000000..27e449b1
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2021, Raspberry Pi Ltd
+ * Copyright (C) 2023, Ideas on Board Oy.
+ *
+ * cam_helper_ov64a40.cpp - camera information for ov64a40 sensor
+ */
+
+#include <assert.h>
+
+#include "cam_helper.h"
+
+using namespace RPiController;
+
+class CamHelperOv64a40 : public CamHelper
+{
+public:
+ CamHelperOv64a40();
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ double getModeSensitivity(const CameraMode &mode) const override;
+
+private:
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ static constexpr int frameIntegrationDiff = 32;
+};
+
+CamHelperOv64a40::CamHelperOv64a40()
+ : CamHelper({}, frameIntegrationDiff)
+{
+}
+
+uint32_t CamHelperOv64a40::gainCode(double gain) const
+{
+ return static_cast<uint32_t>(gain * 128.0);
+}
+
+double CamHelperOv64a40::gain(uint32_t gainCode) const
+{
+ return static_cast<double>(gainCode) / 128.0;
+}
+
+void CamHelperOv64a40::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ /* The driver appears to behave as follows: */
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
+}
+
+double CamHelperOv64a40::getModeSensitivity(const CameraMode &mode) const
+{
+ if (mode.binX >= 2 && mode.scaleX >= 4) {
+ return 4.0;
+ } else if (mode.binX >= 2 && mode.scaleX >= 2) {
+ return 2.0;
+ } else {
+ return 1.0;
+ }
+}
+
+static CamHelper *create()
+{
+ return new CamHelperOv64a40();
+}
+
+static RegisterCamHelper reg("ov64a40", &create);
diff --git a/src/ipa/raspberrypi/cam_helper_ov9281.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp
index 9de868c3..86c5bc4c 100644
--- a/src/ipa/raspberrypi/cam_helper_ov9281.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp
@@ -1,13 +1,13 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2021, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2021, Raspberry Pi Ltd
*
* cam_helper_ov9281.cpp - camera information for ov9281 sensor
*/
#include <assert.h>
-#include "cam_helper.hpp"
+#include "cam_helper.h"
using namespace RPiController;
@@ -15,10 +15,10 @@ class CamHelperOv9281 : public CamHelper
{
public:
CamHelperOv9281();
- uint32_t GainCode(double gain) const override;
- double Gain(uint32_t gain_code) const override;
- void GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const override;
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
private:
/*
@@ -38,28 +38,29 @@ CamHelperOv9281::CamHelperOv9281()
{
}
-uint32_t CamHelperOv9281::GainCode(double gain) const
+uint32_t CamHelperOv9281::gainCode(double gain) const
{
return static_cast<uint32_t>(gain * 16.0);
}
-double CamHelperOv9281::Gain(uint32_t gain_code) const
+double CamHelperOv9281::gain(uint32_t gainCode) const
{
- return static_cast<double>(gain_code) / 16.0;
+ return static_cast<double>(gainCode) / 16.0;
}
-void CamHelperOv9281::GetDelays(int &exposure_delay, int &gain_delay,
- int &vblank_delay) const
+void CamHelperOv9281::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
{
/* The driver appears to behave as follows: */
- exposure_delay = 2;
- gain_delay = 2;
- vblank_delay = 2;
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
}
-static CamHelper *Create()
+static CamHelper *create()
{
return new CamHelperOv9281();
}
-static RegisterCamHelper reg("ov9281", &Create);
+static RegisterCamHelper reg("ov9281", &create);
diff --git a/src/ipa/raspberrypi/md_parser.hpp b/src/ipa/rpi/cam_helper/md_parser.h
index d32d0f54..77d557aa 100644
--- a/src/ipa/raspberrypi/md_parser.hpp
+++ b/src/ipa/rpi/cam_helper/md_parser.h
@@ -1,8 +1,8 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
- * md_parser.hpp - image sensor metadata parser interface
+ * md_parser.h - image sensor metadata parser interface
*/
#pragma once
@@ -75,40 +75,40 @@ public:
};
MdParser()
- : reset_(true), bits_per_pixel_(0), num_lines_(0), line_length_bytes_(0)
+ : reset_(true), bitsPerPixel_(0), numLines_(0), lineLengthBytes_(0)
{
}
virtual ~MdParser() = default;
- void Reset()
+ void reset()
{
reset_ = true;
}
- void SetBitsPerPixel(int bpp)
+ void setBitsPerPixel(int bpp)
{
- bits_per_pixel_ = bpp;
+ bitsPerPixel_ = bpp;
}
- void SetNumLines(unsigned int num_lines)
+ void setNumLines(unsigned int numLines)
{
- num_lines_ = num_lines;
+ numLines_ = numLines;
}
- void SetLineLengthBytes(unsigned int num_bytes)
+ void setLineLengthBytes(unsigned int numBytes)
{
- line_length_bytes_ = num_bytes;
+ lineLengthBytes_ = numBytes;
}
- virtual Status Parse(libcamera::Span<const uint8_t> buffer,
+ virtual Status parse(libcamera::Span<const uint8_t> buffer,
RegisterMap &registers) = 0;
protected:
bool reset_;
- int bits_per_pixel_;
- unsigned int num_lines_;
- unsigned int line_length_bytes_;
+ int bitsPerPixel_;
+ unsigned int numLines_;
+ unsigned int lineLengthBytes_;
};
/*
@@ -123,7 +123,7 @@ class MdParserSmia final : public MdParser
public:
MdParserSmia(std::initializer_list<uint32_t> registerList);
- MdParser::Status Parse(libcamera::Span<const uint8_t> buffer,
+ MdParser::Status parse(libcamera::Span<const uint8_t> buffer,
RegisterMap &registers) override;
private:
@@ -133,18 +133,18 @@ private:
/*
* Note that error codes > 0 are regarded as non-fatal; codes < 0
* indicate a bad data buffer. Status codes are:
- * PARSE_OK - found all registers, much happiness
- * MISSING_REGS - some registers found; should this be a hard error?
+ * ParseOk - found all registers, much happiness
+ * MissingRegs - some registers found; should this be a hard error?
* The remaining codes are all hard errors.
*/
enum ParseStatus {
- PARSE_OK = 0,
- MISSING_REGS = 1,
- NO_LINE_START = -1,
- ILLEGAL_TAG = -2,
- BAD_DUMMY = -3,
- BAD_LINE_END = -4,
- BAD_PADDING = -5
+ ParseOk = 0,
+ MissingRegs = 1,
+ NoLineStart = -1,
+ IllegalTag = -2,
+ BadDummy = -3,
+ BadLineEnd = -4,
+ BadPadding = -5
};
ParseStatus findRegs(libcamera::Span<const uint8_t> buffer);
@@ -152,4 +152,4 @@ private:
OffsetMap offsets_;
};
-} // namespace RPi
+} /* namespace RPi */
diff --git a/src/ipa/rpi/cam_helper/md_parser_smia.cpp b/src/ipa/rpi/cam_helper/md_parser_smia.cpp
new file mode 100644
index 00000000..c5b806d7
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/md_parser_smia.cpp
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2021, Raspberry Pi Ltd
+ *
+ * md_parser_smia.cpp - SMIA specification based embedded data parser
+ */
+
+#include <libcamera/base/log.h>
+#include "md_parser.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+/*
+ * This function goes through the embedded data to find the offsets (not
+ * values!), in the data block, where the values of the given registers can
+ * subsequently be found.
+ *
+ * Embedded data tag bytes, from Sony IMX219 datasheet but general to all SMIA
+ * sensors, I think.
+ */
+
+constexpr unsigned int LineStart = 0x0a;
+constexpr unsigned int LineEndTag = 0x07;
+constexpr unsigned int RegHiBits = 0xaa;
+constexpr unsigned int RegLowBits = 0xa5;
+constexpr unsigned int RegValue = 0x5a;
+constexpr unsigned int RegSkip = 0x55;
+
+MdParserSmia::MdParserSmia(std::initializer_list<uint32_t> registerList)
+{
+ for (auto r : registerList)
+ offsets_[r] = {};
+}
+
+MdParser::Status MdParserSmia::parse(libcamera::Span<const uint8_t> buffer,
+ RegisterMap &registers)
+{
+ if (reset_) {
+ /*
+ * Search again through the metadata for all the registers
+ * requested.
+ */
+ ASSERT(bitsPerPixel_);
+
+ for (const auto &kv : offsets_)
+ offsets_[kv.first] = {};
+
+ ParseStatus ret = findRegs(buffer);
+ /*
+ * > 0 means "worked partially but parse again next time",
+ * < 0 means "hard error".
+ *
+ * In either case, we retry parsing on the next frame.
+ */
+ if (ret != ParseOk)
+ return ERROR;
+
+ reset_ = false;
+ }
+
+ /* Populate the register values requested. */
+ registers.clear();
+ for (const auto &[reg, offset] : offsets_) {
+ if (!offset) {
+ reset_ = true;
+ return NOTFOUND;
+ }
+ registers[reg] = buffer[offset.value()];
+ }
+
+ return OK;
+}
+
+MdParserSmia::ParseStatus MdParserSmia::findRegs(libcamera::Span<const uint8_t> buffer)
+{
+ ASSERT(offsets_.size());
+
+ if (buffer[0] != LineStart)
+ return NoLineStart;
+
+ unsigned int currentOffset = 1; /* after the LineStart */
+ unsigned int currentLineStart = 0, currentLine = 0;
+ unsigned int regNum = 0, regsDone = 0;
+
+ while (1) {
+ int tag = buffer[currentOffset++];
+
+ /* Non-dummy bytes come in even-sized blocks: skip can only ever follow tag */
+ while ((bitsPerPixel_ == 10 &&
+ (currentOffset + 1 - currentLineStart) % 5 == 0) ||
+ (bitsPerPixel_ == 12 &&
+ (currentOffset + 1 - currentLineStart) % 3 == 0) ||
+ (bitsPerPixel_ == 14 &&
+ (currentOffset - currentLineStart) % 7 >= 4)) {
+ if (buffer[currentOffset++] != RegSkip)
+ return BadDummy;
+ }
+
+ int dataByte = buffer[currentOffset++];
+
+ if (tag == LineEndTag) {
+ if (dataByte != LineEndTag)
+ return BadLineEnd;
+
+ if (numLines_ && ++currentLine == numLines_)
+ return MissingRegs;
+
+ if (lineLengthBytes_) {
+ currentOffset = currentLineStart + lineLengthBytes_;
+
+ /* Require whole line to be in the buffer (if buffer size set). */
+ if (buffer.size() &&
+ currentOffset + lineLengthBytes_ > buffer.size())
+ return MissingRegs;
+
+ if (buffer[currentOffset] != LineStart)
+ return NoLineStart;
+ } else {
+ /* allow a zero line length to mean "hunt for the next line" */
+ while (currentOffset < buffer.size() &&
+ buffer[currentOffset] != LineStart)
+ currentOffset++;
+
+ if (currentOffset == buffer.size())
+ return NoLineStart;
+ }
+
+ /* inc currentOffset to after LineStart */
+ currentLineStart = currentOffset++;
+ } else {
+ if (tag == RegHiBits)
+ regNum = (regNum & 0xff) | (dataByte << 8);
+ else if (tag == RegLowBits)
+ regNum = (regNum & 0xff00) | dataByte;
+ else if (tag == RegSkip)
+ regNum++;
+ else if (tag == RegValue) {
+ auto reg = offsets_.find(regNum);
+
+ if (reg != offsets_.end()) {
+ offsets_[regNum] = currentOffset - 1;
+
+ if (++regsDone == offsets_.size())
+ return ParseOk;
+ }
+ regNum++;
+ } else
+ return IllegalTag;
+ }
+ }
+}
diff --git a/src/ipa/rpi/cam_helper/meson.build b/src/ipa/rpi/cam_helper/meson.build
new file mode 100644
index 00000000..72625057
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/meson.build
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: CC0-1.0
+
+rpi_ipa_cam_helper_sources = files([
+ 'cam_helper.cpp',
+ 'cam_helper_ov5647.cpp',
+ 'cam_helper_imx219.cpp',
+ 'cam_helper_imx290.cpp',
+ 'cam_helper_imx296.cpp',
+ 'cam_helper_imx477.cpp',
+ 'cam_helper_imx519.cpp',
+ 'cam_helper_imx708.cpp',
+ 'cam_helper_ov64a40.cpp',
+ 'cam_helper_ov9281.cpp',
+ 'md_parser_smia.cpp',
+])
+
+rpi_ipa_cam_helper_includes = [
+ include_directories('..'),
+]
+
+rpi_ipa_cam_helper_deps = [
+ libcamera_private,
+]
+
+rpi_ipa_cam_helper_lib = static_library('rpi_ipa_cam_helper', rpi_ipa_cam_helper_sources,
+ include_directories : rpi_ipa_cam_helper_includes,
+ dependencies : rpi_ipa_cam_helper_deps)
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
new file mode 100644
index 00000000..3c133c55
--- /dev/null
+++ b/src/ipa/rpi/common/ipa_base.cpp
@@ -0,0 +1,1456 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2023, Raspberry Pi Ltd
+ *
+ * ipa_base.cpp - Raspberry Pi IPA base class
+ */
+
+#include "ipa_base.h"
+
+#include <cmath>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/property_ids.h>
+
+#include "controller/af_algorithm.h"
+#include "controller/af_status.h"
+#include "controller/agc_algorithm.h"
+#include "controller/awb_algorithm.h"
+#include "controller/awb_status.h"
+#include "controller/black_level_status.h"
+#include "controller/ccm_algorithm.h"
+#include "controller/ccm_status.h"
+#include "controller/contrast_algorithm.h"
+#include "controller/denoise_algorithm.h"
+#include "controller/hdr_algorithm.h"
+#include "controller/hdr_status.h"
+#include "controller/lux_status.h"
+#include "controller/sharpen_algorithm.h"
+#include "controller/statistics.h"
+
+namespace libcamera {
+
+using namespace std::literals::chrono_literals;
+using utils::Duration;
+
+namespace {
+
+/* Number of frame length times to hold in the queue. */
+constexpr unsigned int FrameLengthsQueueSize = 10;
+
+/* Configure the sensor with these values initially. */
+constexpr double defaultAnalogueGain = 1.0;
+constexpr Duration defaultExposureTime = 20.0ms;
+constexpr Duration defaultMinFrameDuration = 1.0s / 30.0;
+constexpr Duration defaultMaxFrameDuration = 250.0s;
+
+/*
+ * Determine the minimum allowable inter-frame duration to run the controller
+ * algorithms. If the pipeline handler provider frames at a rate higher than this,
+ * we rate-limit the controller Prepare() and Process() calls to lower than or
+ * equal to this rate.
+ */
+constexpr Duration controllerMinFrameDuration = 1.0s / 30.0;
+
+/* List of controls handled by the Raspberry Pi IPA */
+const ControlInfoMap::Map ipaControls{
+ { &controls::AeEnable, ControlInfo(false, true) },
+ { &controls::ExposureTime, ControlInfo(0, 66666) },
+ { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f) },
+ { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
+ { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
+ { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
+ { &controls::ExposureValue, ControlInfo(-8.0f, 8.0f, 0.0f) },
+ { &controls::AeFlickerMode, ControlInfo(static_cast<int>(controls::FlickerOff),
+ static_cast<int>(controls::FlickerManual),
+ static_cast<int>(controls::FlickerOff)) },
+ { &controls::AeFlickerPeriod, ControlInfo(100, 1000000) },
+ { &controls::Brightness, ControlInfo(-1.0f, 1.0f, 0.0f) },
+ { &controls::Contrast, ControlInfo(0.0f, 32.0f, 1.0f) },
+ { &controls::HdrMode, ControlInfo(controls::HdrModeValues) },
+ { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
+ { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
+ { &controls::FrameDurationLimits, ControlInfo(INT64_C(33333), INT64_C(120000)) },
+ { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) },
+ { &controls::rpi::StatsOutputEnable, ControlInfo(false, true) },
+};
+
+/* IPA controls handled conditionally, if the sensor is not mono */
+const ControlInfoMap::Map ipaColourControls{
+ { &controls::AwbEnable, ControlInfo(false, true) },
+ { &controls::AwbMode, ControlInfo(controls::AwbModeValues) },
+ { &controls::ColourGains, ControlInfo(0.0f, 32.0f) },
+ { &controls::Saturation, ControlInfo(0.0f, 32.0f, 1.0f) },
+};
+
+/* IPA controls handled conditionally, if the lens has a focus control */
+const ControlInfoMap::Map ipaAfControls{
+ { &controls::AfMode, ControlInfo(controls::AfModeValues) },
+ { &controls::AfRange, ControlInfo(controls::AfRangeValues) },
+ { &controls::AfSpeed, ControlInfo(controls::AfSpeedValues) },
+ { &controls::AfMetering, ControlInfo(controls::AfMeteringValues) },
+ { &controls::AfWindows, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
+ { &controls::AfTrigger, ControlInfo(controls::AfTriggerValues) },
+ { &controls::AfPause, ControlInfo(controls::AfPauseValues) },
+ { &controls::LensPosition, ControlInfo(0.0f, 32.0f, 1.0f) }
+};
+
+} /* namespace */
+
+LOG_DEFINE_CATEGORY(IPARPI)
+
+namespace ipa::RPi {
+
+IpaBase::IpaBase()
+ : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), statsMetadataOutput_(false),
+ frameCount_(0), mistrustCount_(0), lastRunTimestamp_(0), firstStart_(true),
+ flickerState_({ 0, 0s })
+{
+}
+
+IpaBase::~IpaBase()
+{
+}
+
+int32_t IpaBase::init(const IPASettings &settings, const InitParams &params, InitResult *result)
+{
+ /*
+ * Load the "helper" for this sensor. This tells us all the device specific stuff
+ * that the kernel driver doesn't. We only do this the first time; we don't need
+ * to re-parse the metadata after a simple mode-switch for no reason.
+ */
+ helper_ = std::unique_ptr<RPiController::CamHelper>(RPiController::CamHelper::create(settings.sensorModel));
+ if (!helper_) {
+ LOG(IPARPI, Error) << "Could not create camera helper for "
+ << settings.sensorModel;
+ return -EINVAL;
+ }
+
+ /*
+ * Pass out the sensor config to the pipeline handler in order
+ * to setup the staggered writer class.
+ */
+ int gainDelay, exposureDelay, vblankDelay, hblankDelay, sensorMetadata;
+ helper_->getDelays(exposureDelay, gainDelay, vblankDelay, hblankDelay);
+ sensorMetadata = helper_->sensorEmbeddedDataPresent();
+
+ result->sensorConfig.gainDelay = gainDelay;
+ result->sensorConfig.exposureDelay = exposureDelay;
+ result->sensorConfig.vblankDelay = vblankDelay;
+ result->sensorConfig.hblankDelay = hblankDelay;
+ result->sensorConfig.sensorMetadata = sensorMetadata;
+
+ /* Load the tuning file for this sensor. */
+ int ret = controller_.read(settings.configurationFile.c_str());
+ if (ret) {
+ LOG(IPARPI, Error)
+ << "Failed to load tuning data file "
+ << settings.configurationFile;
+ return ret;
+ }
+
+ lensPresent_ = params.lensPresent;
+
+ controller_.initialise();
+
+ /* Return the controls handled by the IPA */
+ ControlInfoMap::Map ctrlMap = ipaControls;
+ if (lensPresent_)
+ ctrlMap.merge(ControlInfoMap::Map(ipaAfControls));
+
+ monoSensor_ = params.sensorInfo.cfaPattern == properties::draft::ColorFilterArrangementEnum::MONO;
+ if (!monoSensor_)
+ ctrlMap.merge(ControlInfoMap::Map(ipaColourControls));
+
+ result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
+
+ return platformInit(params, result);
+}
+
+int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigParams &params,
+ ConfigResult *result)
+{
+ sensorCtrls_ = params.sensorControls;
+
+ if (!validateSensorControls()) {
+ LOG(IPARPI, Error) << "Sensor control validation failed.";
+ return -1;
+ }
+
+ if (lensPresent_) {
+ lensCtrls_ = params.lensControls;
+ if (!validateLensControls()) {
+ LOG(IPARPI, Warning) << "Lens validation failed, "
+ << "no lens control will be available.";
+ lensPresent_ = false;
+ }
+ }
+
+ /* Setup a metadata ControlList to output metadata. */
+ libcameraMetadata_ = ControlList(controls::controls);
+
+ /* Re-assemble camera mode using the sensor info. */
+ setMode(sensorInfo);
+
+ mode_.transform = static_cast<libcamera::Transform>(params.transform);
+
+ /* Pass the camera mode to the CamHelper to setup algorithms. */
+ helper_->setCameraMode(mode_);
+
+ /*
+ * Initialise this ControlList correctly, even if empty, in case the IPA is
+ * running is isolation mode (passing the ControlList through the IPC layer).
+ */
+ ControlList ctrls(sensorCtrls_);
+
+ /* The pipeline handler passes out the mode's sensitivity. */
+ result->modeSensitivity = mode_.sensitivity;
+
+ if (firstStart_) {
+ /* Supply initial values for frame durations. */
+ applyFrameDurations(defaultMinFrameDuration, defaultMaxFrameDuration);
+
+ /* Supply initial values for gain and exposure. */
+ AgcStatus agcStatus;
+ agcStatus.shutterTime = defaultExposureTime;
+ agcStatus.analogueGain = defaultAnalogueGain;
+ applyAGC(&agcStatus, ctrls);
+
+ /*
+ * Set the lens to the default (typically hyperfocal) position
+ * on first start.
+ */
+ if (lensPresent_) {
+ RPiController::AfAlgorithm *af =
+ dynamic_cast<RPiController::AfAlgorithm *>(controller_.getAlgorithm("af"));
+
+ if (af) {
+ float defaultPos =
+ ipaAfControls.at(&controls::LensPosition).def().get<float>();
+ ControlList lensCtrl(lensCtrls_);
+ int32_t hwpos;
+
+ af->setLensPosition(defaultPos, &hwpos);
+ lensCtrl.set(V4L2_CID_FOCUS_ABSOLUTE, hwpos);
+ result->lensControls = std::move(lensCtrl);
+ }
+ }
+ }
+
+ result->sensorControls = std::move(ctrls);
+
+ /*
+ * Apply the correct limits to the exposure, gain and frame duration controls
+ * based on the current sensor mode.
+ */
+ ControlInfoMap::Map ctrlMap = ipaControls;
+ ctrlMap[&controls::FrameDurationLimits] =
+ ControlInfo(static_cast<int64_t>(mode_.minFrameDuration.get<std::micro>()),
+ static_cast<int64_t>(mode_.maxFrameDuration.get<std::micro>()));
+
+ ctrlMap[&controls::AnalogueGain] =
+ ControlInfo(static_cast<float>(mode_.minAnalogueGain),
+ static_cast<float>(mode_.maxAnalogueGain));
+
+ ctrlMap[&controls::ExposureTime] =
+ ControlInfo(static_cast<int32_t>(mode_.minShutter.get<std::micro>()),
+ static_cast<int32_t>(mode_.maxShutter.get<std::micro>()));
+
+ /* Declare colour processing related controls for non-mono sensors. */
+ if (!monoSensor_)
+ ctrlMap.merge(ControlInfoMap::Map(ipaColourControls));
+
+ /* Declare Autofocus controls, only if we have a controllable lens */
+ if (lensPresent_)
+ ctrlMap.merge(ControlInfoMap::Map(ipaAfControls));
+
+ result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
+
+ return platformConfigure(params, result);
+}
+
+void IpaBase::start(const ControlList &controls, StartResult *result)
+{
+ RPiController::Metadata metadata;
+
+ if (!controls.empty()) {
+ /* We have been given some controls to action before start. */
+ applyControls(controls);
+ }
+
+ controller_.switchMode(mode_, &metadata);
+
+ /* Reset the frame lengths queue state. */
+ lastTimeout_ = 0s;
+ frameLengths_.clear();
+ frameLengths_.resize(FrameLengthsQueueSize, 0s);
+
+ /* SwitchMode may supply updated exposure/gain values to use. */
+ AgcStatus agcStatus;
+ agcStatus.shutterTime = 0.0s;
+ agcStatus.analogueGain = 0.0;
+
+ metadata.get("agc.status", agcStatus);
+ if (agcStatus.shutterTime && agcStatus.analogueGain) {
+ ControlList ctrls(sensorCtrls_);
+ applyAGC(&agcStatus, ctrls);
+ result->controls = std::move(ctrls);
+ setCameraTimeoutValue();
+ }
+
+ /*
+ * Initialise frame counts, and decide how many frames must be hidden or
+ * "mistrusted", which depends on whether this is a startup from cold,
+ * or merely a mode switch in a running system.
+ */
+ frameCount_ = 0;
+ if (firstStart_) {
+ dropFrameCount_ = helper_->hideFramesStartup();
+ mistrustCount_ = helper_->mistrustFramesStartup();
+
+ /*
+ * Query the AGC/AWB for how many frames they may take to
+ * converge sufficiently. Where these numbers are non-zero
+ * we must allow for the frames with bad statistics
+ * (mistrustCount_) that they won't see. But if zero (i.e.
+ * no convergence necessary), no frames need to be dropped.
+ */
+ unsigned int agcConvergenceFrames = 0;
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (agc) {
+ agcConvergenceFrames = agc->getConvergenceFrames();
+ if (agcConvergenceFrames)
+ agcConvergenceFrames += mistrustCount_;
+ }
+
+ unsigned int awbConvergenceFrames = 0;
+ RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
+ controller_.getAlgorithm("awb"));
+ if (awb) {
+ awbConvergenceFrames = awb->getConvergenceFrames();
+ if (awbConvergenceFrames)
+ awbConvergenceFrames += mistrustCount_;
+ }
+
+ dropFrameCount_ = std::max({ dropFrameCount_, agcConvergenceFrames, awbConvergenceFrames });
+ LOG(IPARPI, Debug) << "Drop " << dropFrameCount_ << " frames on startup";
+ } else {
+ dropFrameCount_ = helper_->hideFramesModeSwitch();
+ mistrustCount_ = helper_->mistrustFramesModeSwitch();
+ }
+
+ result->dropFrameCount = dropFrameCount_;
+
+ firstStart_ = false;
+ lastRunTimestamp_ = 0;
+
+ platformStart(controls, result);
+}
+
+void IpaBase::mapBuffers(const std::vector<IPABuffer> &buffers)
+{
+ for (const IPABuffer &buffer : buffers) {
+ const FrameBuffer fb(buffer.planes);
+ buffers_.emplace(buffer.id,
+ MappedFrameBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite));
+ }
+}
+
+void IpaBase::unmapBuffers(const std::vector<unsigned int> &ids)
+{
+ for (unsigned int id : ids) {
+ auto it = buffers_.find(id);
+ if (it == buffers_.end())
+ continue;
+
+ buffers_.erase(id);
+ }
+}
+
+void IpaBase::prepareIsp(const PrepareParams &params)
+{
+ applyControls(params.requestControls);
+
+ /*
+ * At start-up, or after a mode-switch, we may want to
+ * avoid running the control algos for a few frames in case
+ * they are "unreliable".
+ */
+ int64_t frameTimestamp = params.sensorControls.get(controls::SensorTimestamp).value_or(0);
+ unsigned int ipaContext = params.ipaContext % rpiMetadata_.size();
+ RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];
+ Span<uint8_t> embeddedBuffer;
+
+ rpiMetadata.clear();
+ fillDeviceStatus(params.sensorControls, ipaContext);
+
+ if (params.buffers.embedded) {
+ /*
+ * Pipeline handler has supplied us with an embedded data buffer,
+ * we must pass it to the CamHelper for parsing.
+ */
+ auto it = buffers_.find(params.buffers.embedded);
+ ASSERT(it != buffers_.end());
+ embeddedBuffer = it->second.planes()[0];
+ }
+
+ /*
+ * AGC wants to know the algorithm status from the time it actioned the
+ * sensor exposure/gain changes. So fetch it from the metadata list
+ * indexed by the IPA cookie returned, and put it in the current frame
+ * metadata.
+ */
+ AgcStatus agcStatus;
+ RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext];
+ if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus))
+ rpiMetadata.set("agc.delayed_status", agcStatus);
+
+ /*
+ * This may overwrite the DeviceStatus using values from the sensor
+ * metadata, and may also do additional custom processing.
+ */
+ helper_->prepare(embeddedBuffer, rpiMetadata);
+
+ /* Allow a 10% margin on the comparison below. */
+ Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns;
+ if (lastRunTimestamp_ && frameCount_ > dropFrameCount_ &&
+ delta < controllerMinFrameDuration * 0.9) {
+ /*
+ * Ensure we merge the previous frame's metadata with the current
+ * frame. This will not overwrite exposure/gain values for the
+ * current frame, or any other bits of metadata that were added
+ * in helper_->Prepare().
+ */
+ RPiController::Metadata &lastMetadata =
+ rpiMetadata_[(ipaContext ? ipaContext : rpiMetadata_.size()) - 1];
+ rpiMetadata.mergeCopy(lastMetadata);
+ processPending_ = false;
+ } else {
+ processPending_ = true;
+ lastRunTimestamp_ = frameTimestamp;
+ }
+
+ /*
+ * If the statistics are inline (i.e. already available with the Bayer
+ * frame), call processStats() now before prepare().
+ */
+ if (controller_.getHardwareConfig().statsInline)
+ processStats({ params.buffers, params.ipaContext });
+
+ /* Do we need/want to call prepare? */
+ if (processPending_) {
+ controller_.prepare(&rpiMetadata);
+ /* Actually prepare the ISP parameters for the frame. */
+ platformPrepareIsp(params, rpiMetadata);
+ }
+
+ frameCount_++;
+
+ /* If the statistics are inline the metadata can be returned early. */
+ if (controller_.getHardwareConfig().statsInline)
+ reportMetadata(ipaContext);
+
+ /* Ready to push the input buffer into the ISP. */
+ prepareIspComplete.emit(params.buffers, false);
+}
+
+void IpaBase::processStats(const ProcessParams &params)
+{
+ unsigned int ipaContext = params.ipaContext % rpiMetadata_.size();
+
+ if (processPending_ && frameCount_ >= mistrustCount_) {
+ RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];
+
+ auto it = buffers_.find(params.buffers.stats);
+ if (it == buffers_.end()) {
+ LOG(IPARPI, Error) << "Could not find stats buffer!";
+ return;
+ }
+
+ RPiController::StatisticsPtr statistics = platformProcessStats(it->second.planes()[0]);
+
+ /* reportMetadata() will pick this up and set the FocusFoM metadata */
+ rpiMetadata.set("focus.status", statistics->focusRegions);
+
+ helper_->process(statistics, rpiMetadata);
+ controller_.process(statistics, &rpiMetadata);
+
+ struct AgcStatus agcStatus;
+ if (rpiMetadata.get("agc.status", agcStatus) == 0) {
+ ControlList ctrls(sensorCtrls_);
+ applyAGC(&agcStatus, ctrls);
+ setDelayedControls.emit(ctrls, ipaContext);
+ setCameraTimeoutValue();
+ }
+ }
+
+ /*
+ * If the statistics are not inline the metadata must be returned now,
+ * before the processStatsComplete signal.
+ */
+ if (!controller_.getHardwareConfig().statsInline)
+ reportMetadata(ipaContext);
+
+ processStatsComplete.emit(params.buffers);
+}
+
+void IpaBase::setMode(const IPACameraSensorInfo &sensorInfo)
+{
+ mode_.bitdepth = sensorInfo.bitsPerPixel;
+ mode_.width = sensorInfo.outputSize.width;
+ mode_.height = sensorInfo.outputSize.height;
+ mode_.sensorWidth = sensorInfo.activeAreaSize.width;
+ mode_.sensorHeight = sensorInfo.activeAreaSize.height;
+ mode_.cropX = sensorInfo.analogCrop.x;
+ mode_.cropY = sensorInfo.analogCrop.y;
+ mode_.pixelRate = sensorInfo.pixelRate;
+
+ /*
+ * Calculate scaling parameters. The scale_[xy] factors are determined
+ * by the ratio between the crop rectangle size and the output size.
+ */
+ mode_.scaleX = sensorInfo.analogCrop.width / sensorInfo.outputSize.width;
+ mode_.scaleY = sensorInfo.analogCrop.height / sensorInfo.outputSize.height;
+
+ /*
+ * We're not told by the pipeline handler how scaling is split between
+ * binning and digital scaling. For now, as a heuristic, assume that
+ * downscaling up to 2 is achieved through binning, and that any
+ * additional scaling is achieved through digital scaling.
+ *
+ * \todo Get the pipeline handle to provide the full data
+ */
+ mode_.binX = std::min(2, static_cast<int>(mode_.scaleX));
+ mode_.binY = std::min(2, static_cast<int>(mode_.scaleY));
+
+ /* The noise factor is the square root of the total binning factor. */
+ mode_.noiseFactor = std::sqrt(mode_.binX * mode_.binY);
+
+ /*
+ * Calculate the line length as the ratio between the line length in
+ * pixels and the pixel rate.
+ */
+ mode_.minLineLength = sensorInfo.minLineLength * (1.0s / sensorInfo.pixelRate);
+ mode_.maxLineLength = sensorInfo.maxLineLength * (1.0s / sensorInfo.pixelRate);
+
+ /*
+ * Ensure that the maximum pixel processing rate does not exceed the ISP
+ * hardware capabilities. If it does, try adjusting the minimum line
+ * length to compensate if possible.
+ */
+ Duration minPixelTime = controller_.getHardwareConfig().minPixelProcessingTime;
+ Duration pixelTime = mode_.minLineLength / mode_.width;
+ if (minPixelTime && pixelTime < minPixelTime) {
+ Duration adjustedLineLength = minPixelTime * mode_.width;
+ if (adjustedLineLength <= mode_.maxLineLength) {
+ LOG(IPARPI, Info)
+ << "Adjusting mode minimum line length from " << mode_.minLineLength
+ << " to " << adjustedLineLength << " because of ISP constraints.";
+ mode_.minLineLength = adjustedLineLength;
+ } else {
+ LOG(IPARPI, Error)
+ << "Sensor minimum line length of " << pixelTime * mode_.width
+ << " (" << 1us / pixelTime << " MPix/s)"
+ << " is below the minimum allowable ISP limit of "
+ << adjustedLineLength
+ << " (" << 1us / minPixelTime << " MPix/s) ";
+ LOG(IPARPI, Error)
+ << "THIS WILL CAUSE IMAGE CORRUPTION!!! "
+ << "Please update the camera sensor driver to allow more horizontal blanking control.";
+ }
+ }
+
+ /*
+ * Set the frame length limits for the mode to ensure exposure and
+ * framerate calculations are clipped appropriately.
+ */
+ mode_.minFrameLength = sensorInfo.minFrameLength;
+ mode_.maxFrameLength = sensorInfo.maxFrameLength;
+
+ /* Store these for convenience. */
+ mode_.minFrameDuration = mode_.minFrameLength * mode_.minLineLength;
+ mode_.maxFrameDuration = mode_.maxFrameLength * mode_.maxLineLength;
+
+ /*
+ * Some sensors may have different sensitivities in different modes;
+ * the CamHelper will know the correct value.
+ */
+ mode_.sensitivity = helper_->getModeSensitivity(mode_);
+
+ const ControlInfo &gainCtrl = sensorCtrls_.at(V4L2_CID_ANALOGUE_GAIN);
+ const ControlInfo &shutterCtrl = sensorCtrls_.at(V4L2_CID_EXPOSURE);
+
+ mode_.minAnalogueGain = helper_->gain(gainCtrl.min().get<int32_t>());
+ mode_.maxAnalogueGain = helper_->gain(gainCtrl.max().get<int32_t>());
+
+ /* Shutter speed is calculated based on the limits of the frame durations. */
+ mode_.minShutter = helper_->exposure(shutterCtrl.min().get<int32_t>(), mode_.minLineLength);
+ mode_.maxShutter = Duration::max();
+ helper_->getBlanking(mode_.maxShutter,
+ mode_.minFrameDuration, mode_.maxFrameDuration);
+}
+
+void IpaBase::setCameraTimeoutValue()
+{
+ /*
+ * Take the maximum value of the exposure queue as the camera timeout
+ * value to pass back to the pipeline handler. Only signal if it has changed
+ * from the last set value.
+ */
+ auto max = std::max_element(frameLengths_.begin(), frameLengths_.end());
+
+ if (*max != lastTimeout_) {
+ setCameraTimeout.emit(max->get<std::milli>());
+ lastTimeout_ = *max;
+ }
+}
+
+bool IpaBase::validateSensorControls()
+{
+ static const uint32_t ctrls[] = {
+ V4L2_CID_ANALOGUE_GAIN,
+ V4L2_CID_EXPOSURE,
+ V4L2_CID_VBLANK,
+ V4L2_CID_HBLANK,
+ };
+
+ for (auto c : ctrls) {
+ if (sensorCtrls_.find(c) == sensorCtrls_.end()) {
+ LOG(IPARPI, Error) << "Unable to find sensor control "
+ << utils::hex(c);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IpaBase::validateLensControls()
+{
+ if (lensCtrls_.find(V4L2_CID_FOCUS_ABSOLUTE) == lensCtrls_.end()) {
+ LOG(IPARPI, Error) << "Unable to find Lens control V4L2_CID_FOCUS_ABSOLUTE";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Converting between enums (used in the libcamera API) and the names that
+ * we use to identify different modes. Unfortunately, the conversion tables
+ * must be kept up-to-date by hand.
+ */
+static const std::map<int32_t, std::string> MeteringModeTable = {
+ { controls::MeteringCentreWeighted, "centre-weighted" },
+ { controls::MeteringSpot, "spot" },
+ { controls::MeteringMatrix, "matrix" },
+ { controls::MeteringCustom, "custom" },
+};
+
+static const std::map<int32_t, std::string> ConstraintModeTable = {
+ { controls::ConstraintNormal, "normal" },
+ { controls::ConstraintHighlight, "highlight" },
+ { controls::ConstraintShadows, "shadows" },
+ { controls::ConstraintCustom, "custom" },
+};
+
+static const std::map<int32_t, std::string> ExposureModeTable = {
+ { controls::ExposureNormal, "normal" },
+ { controls::ExposureShort, "short" },
+ { controls::ExposureLong, "long" },
+ { controls::ExposureCustom, "custom" },
+};
+
+static const std::map<int32_t, std::string> AwbModeTable = {
+ { controls::AwbAuto, "auto" },
+ { controls::AwbIncandescent, "incandescent" },
+ { controls::AwbTungsten, "tungsten" },
+ { controls::AwbFluorescent, "fluorescent" },
+ { controls::AwbIndoor, "indoor" },
+ { controls::AwbDaylight, "daylight" },
+ { controls::AwbCloudy, "cloudy" },
+ { controls::AwbCustom, "custom" },
+};
+
+static const std::map<int32_t, RPiController::AfAlgorithm::AfMode> AfModeTable = {
+ { controls::AfModeManual, RPiController::AfAlgorithm::AfModeManual },
+ { controls::AfModeAuto, RPiController::AfAlgorithm::AfModeAuto },
+ { controls::AfModeContinuous, RPiController::AfAlgorithm::AfModeContinuous },
+};
+
+static const std::map<int32_t, RPiController::AfAlgorithm::AfRange> AfRangeTable = {
+ { controls::AfRangeNormal, RPiController::AfAlgorithm::AfRangeNormal },
+ { controls::AfRangeMacro, RPiController::AfAlgorithm::AfRangeMacro },
+ { controls::AfRangeFull, RPiController::AfAlgorithm::AfRangeFull },
+};
+
+static const std::map<int32_t, RPiController::AfAlgorithm::AfPause> AfPauseTable = {
+ { controls::AfPauseImmediate, RPiController::AfAlgorithm::AfPauseImmediate },
+ { controls::AfPauseDeferred, RPiController::AfAlgorithm::AfPauseDeferred },
+ { controls::AfPauseResume, RPiController::AfAlgorithm::AfPauseResume },
+};
+
+static const std::map<int32_t, std::string> HdrModeTable = {
+ { controls::HdrModeOff, "Off" },
+ { controls::HdrModeMultiExposure, "MultiExposure" },
+ { controls::HdrModeSingleExposure, "SingleExposure" },
+};
+
+void IpaBase::applyControls(const ControlList &controls)
+{
+ using RPiController::AgcAlgorithm;
+ using RPiController::AfAlgorithm;
+ using RPiController::HdrAlgorithm;
+
+ /* Clear the return metadata buffer. */
+ libcameraMetadata_.clear();
+
+ /* Because some AF controls are mode-specific, handle AF mode change first. */
+ if (controls.contains(controls::AF_MODE)) {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_MODE - no AF algorithm";
+ }
+
+ int32_t idx = controls.get(controls::AF_MODE).get<int32_t>();
+ auto mode = AfModeTable.find(idx);
+ if (mode == AfModeTable.end()) {
+ LOG(IPARPI, Error) << "AF mode " << idx
+ << " not recognised";
+ } else if (af)
+ af->setMode(mode->second);
+ }
+
+ /* Iterate over controls */
+ for (auto const &ctrl : controls) {
+ LOG(IPARPI, Debug) << "Request ctrl: "
+ << controls::controls.at(ctrl.first)->name()
+ << " = " << ctrl.second.toString();
+
+ switch (ctrl.first) {
+ case controls::AE_ENABLE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AE_ENABLE - no AGC algorithm";
+ break;
+ }
+
+ if (ctrl.second.get<bool>() == false)
+ agc->disableAuto();
+ else
+ agc->enableAuto();
+
+ libcameraMetadata_.set(controls::AeEnable, ctrl.second.get<bool>());
+ break;
+ }
+
+ case controls::EXPOSURE_TIME: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set EXPOSURE_TIME - no AGC algorithm";
+ break;
+ }
+
+ /* The control provides units of microseconds. */
+ agc->setFixedShutter(0, ctrl.second.get<int32_t>() * 1.0us);
+
+ libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>());
+ break;
+ }
+
+ case controls::ANALOGUE_GAIN: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set ANALOGUE_GAIN - no AGC algorithm";
+ break;
+ }
+
+ agc->setFixedAnalogueGain(0, ctrl.second.get<float>());
+
+ libcameraMetadata_.set(controls::AnalogueGain,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::AE_METERING_MODE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AE_METERING_MODE - no AGC algorithm";
+ break;
+ }
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ if (MeteringModeTable.count(idx)) {
+ agc->setMeteringMode(MeteringModeTable.at(idx));
+ libcameraMetadata_.set(controls::AeMeteringMode, idx);
+ } else {
+ LOG(IPARPI, Error) << "Metering mode " << idx
+ << " not recognised";
+ }
+ break;
+ }
+
+ case controls::AE_CONSTRAINT_MODE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AE_CONSTRAINT_MODE - no AGC algorithm";
+ break;
+ }
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ if (ConstraintModeTable.count(idx)) {
+ agc->setConstraintMode(ConstraintModeTable.at(idx));
+ libcameraMetadata_.set(controls::AeConstraintMode, idx);
+ } else {
+ LOG(IPARPI, Error) << "Constraint mode " << idx
+ << " not recognised";
+ }
+ break;
+ }
+
+ case controls::AE_EXPOSURE_MODE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AE_EXPOSURE_MODE - no AGC algorithm";
+ break;
+ }
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ if (ExposureModeTable.count(idx)) {
+ agc->setExposureMode(ExposureModeTable.at(idx));
+ libcameraMetadata_.set(controls::AeExposureMode, idx);
+ } else {
+ LOG(IPARPI, Error) << "Exposure mode " << idx
+ << " not recognised";
+ }
+ break;
+ }
+
+ case controls::EXPOSURE_VALUE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set EXPOSURE_VALUE - no AGC algorithm";
+ break;
+ }
+
+ /*
+ * The SetEv() function takes in a direct exposure multiplier.
+ * So convert to 2^EV
+ */
+ double ev = pow(2.0, ctrl.second.get<float>());
+ agc->setEv(0, ev);
+ libcameraMetadata_.set(controls::ExposureValue,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::AE_FLICKER_MODE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AeFlickerMode - no AGC algorithm";
+ break;
+ }
+
+ int32_t mode = ctrl.second.get<int32_t>();
+ bool modeValid = true;
+
+ switch (mode) {
+ case controls::FlickerOff:
+ agc->setFlickerPeriod(0us);
+
+ break;
+
+ case controls::FlickerManual:
+ agc->setFlickerPeriod(flickerState_.manualPeriod);
+
+ break;
+
+ default:
+ LOG(IPARPI, Error) << "Flicker mode " << mode << " is not supported";
+ modeValid = false;
+
+ break;
+ }
+
+ if (modeValid)
+ flickerState_.mode = mode;
+
+ break;
+ }
+
+ case controls::AE_FLICKER_PERIOD: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AeFlickerPeriod - no AGC algorithm";
+ break;
+ }
+
+ uint32_t manualPeriod = ctrl.second.get<int32_t>();
+ flickerState_.manualPeriod = manualPeriod * 1.0us;
+
+ /*
+ * We note that it makes no difference if the mode gets set to "manual"
+ * first, and the period updated after, or vice versa.
+ */
+ if (flickerState_.mode == controls::FlickerManual)
+ agc->setFlickerPeriod(flickerState_.manualPeriod);
+
+ break;
+ }
+
+ case controls::AWB_ENABLE: {
+ /* Silently ignore this control for a mono sensor. */
+ if (monoSensor_)
+ break;
+
+ RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
+ controller_.getAlgorithm("awb"));
+ if (!awb) {
+ LOG(IPARPI, Warning)
+ << "Could not set AWB_ENABLE - no AWB algorithm";
+ break;
+ }
+
+ if (ctrl.second.get<bool>() == false)
+ awb->disableAuto();
+ else
+ awb->enableAuto();
+
+ libcameraMetadata_.set(controls::AwbEnable,
+ ctrl.second.get<bool>());
+ break;
+ }
+
+ case controls::AWB_MODE: {
+ /* Silently ignore this control for a mono sensor. */
+ if (monoSensor_)
+ break;
+
+ RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
+ controller_.getAlgorithm("awb"));
+ if (!awb) {
+ LOG(IPARPI, Warning)
+ << "Could not set AWB_MODE - no AWB algorithm";
+ break;
+ }
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ if (AwbModeTable.count(idx)) {
+ awb->setMode(AwbModeTable.at(idx));
+ libcameraMetadata_.set(controls::AwbMode, idx);
+ } else {
+ LOG(IPARPI, Error) << "AWB mode " << idx
+ << " not recognised";
+ }
+ break;
+ }
+
+ case controls::COLOUR_GAINS: {
+ /* Silently ignore this control for a mono sensor. */
+ if (monoSensor_)
+ break;
+
+ auto gains = ctrl.second.get<Span<const float>>();
+ RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
+ controller_.getAlgorithm("awb"));
+ if (!awb) {
+ LOG(IPARPI, Warning)
+ << "Could not set COLOUR_GAINS - no AWB algorithm";
+ break;
+ }
+
+ awb->setManualGains(gains[0], gains[1]);
+ if (gains[0] != 0.0f && gains[1] != 0.0f)
+ /* A gain of 0.0f will switch back to auto mode. */
+ libcameraMetadata_.set(controls::ColourGains,
+ { gains[0], gains[1] });
+ break;
+ }
+
+ case controls::BRIGHTNESS: {
+ RPiController::ContrastAlgorithm *contrast = dynamic_cast<RPiController::ContrastAlgorithm *>(
+ controller_.getAlgorithm("contrast"));
+ if (!contrast) {
+ LOG(IPARPI, Warning)
+ << "Could not set BRIGHTNESS - no contrast algorithm";
+ break;
+ }
+
+ contrast->setBrightness(ctrl.second.get<float>() * 65536);
+ libcameraMetadata_.set(controls::Brightness,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::CONTRAST: {
+ RPiController::ContrastAlgorithm *contrast = dynamic_cast<RPiController::ContrastAlgorithm *>(
+ controller_.getAlgorithm("contrast"));
+ if (!contrast) {
+ LOG(IPARPI, Warning)
+ << "Could not set CONTRAST - no contrast algorithm";
+ break;
+ }
+
+ contrast->setContrast(ctrl.second.get<float>());
+ libcameraMetadata_.set(controls::Contrast,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::SATURATION: {
+ /* Silently ignore this control for a mono sensor. */
+ if (monoSensor_)
+ break;
+
+ RPiController::CcmAlgorithm *ccm = dynamic_cast<RPiController::CcmAlgorithm *>(
+ controller_.getAlgorithm("ccm"));
+ if (!ccm) {
+ LOG(IPARPI, Warning)
+ << "Could not set SATURATION - no ccm algorithm";
+ break;
+ }
+
+ ccm->setSaturation(ctrl.second.get<float>());
+ libcameraMetadata_.set(controls::Saturation,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::SHARPNESS: {
+ RPiController::SharpenAlgorithm *sharpen = dynamic_cast<RPiController::SharpenAlgorithm *>(
+ controller_.getAlgorithm("sharpen"));
+ if (!sharpen) {
+ LOG(IPARPI, Warning)
+ << "Could not set SHARPNESS - no sharpen algorithm";
+ break;
+ }
+
+ sharpen->setStrength(ctrl.second.get<float>());
+ libcameraMetadata_.set(controls::Sharpness,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::SCALER_CROP: {
+ /* We do nothing with this, but should avoid the warning below. */
+ break;
+ }
+
+ case controls::FRAME_DURATION_LIMITS: {
+ auto frameDurations = ctrl.second.get<Span<const int64_t>>();
+ applyFrameDurations(frameDurations[0] * 1.0us, frameDurations[1] * 1.0us);
+ break;
+ }
+
+ case controls::draft::NOISE_REDUCTION_MODE:
+ /* Handled below in handleControls() */
+ libcameraMetadata_.set(controls::draft::NoiseReductionMode,
+ ctrl.second.get<int32_t>());
+ break;
+
+ case controls::AF_MODE:
+ break; /* We already handled this one above */
+
+ case controls::AF_RANGE: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_RANGE - no focus algorithm";
+ break;
+ }
+
+ auto range = AfRangeTable.find(ctrl.second.get<int32_t>());
+ if (range == AfRangeTable.end()) {
+ LOG(IPARPI, Error) << "AF range " << ctrl.second.get<int32_t>()
+ << " not recognised";
+ break;
+ }
+ af->setRange(range->second);
+ break;
+ }
+
+ case controls::AF_SPEED: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_SPEED - no focus algorithm";
+ break;
+ }
+
+ AfAlgorithm::AfSpeed speed = ctrl.second.get<int32_t>() == controls::AfSpeedFast ?
+ AfAlgorithm::AfSpeedFast : AfAlgorithm::AfSpeedNormal;
+ af->setSpeed(speed);
+ break;
+ }
+
+ case controls::AF_METERING: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_METERING - no AF algorithm";
+ break;
+ }
+ af->setMetering(ctrl.second.get<int32_t>() == controls::AfMeteringWindows);
+ break;
+ }
+
+ case controls::AF_WINDOWS: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_WINDOWS - no AF algorithm";
+ break;
+ }
+ af->setWindows(ctrl.second.get<Span<const Rectangle>>());
+ break;
+ }
+
+ case controls::AF_PAUSE: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af || af->getMode() != AfAlgorithm::AfModeContinuous) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_PAUSE - no AF algorithm or not Continuous";
+ break;
+ }
+ auto pause = AfPauseTable.find(ctrl.second.get<int32_t>());
+ if (pause == AfPauseTable.end()) {
+ LOG(IPARPI, Error) << "AF pause " << ctrl.second.get<int32_t>()
+ << " not recognised";
+ break;
+ }
+ af->pause(pause->second);
+ break;
+ }
+
+ case controls::AF_TRIGGER: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (!af || af->getMode() != AfAlgorithm::AfModeAuto) {
+ LOG(IPARPI, Warning)
+ << "Could not set AF_TRIGGER - no AF algorithm or not Auto";
+ break;
+ } else {
+ if (ctrl.second.get<int32_t>() == controls::AfTriggerStart)
+ af->triggerScan();
+ else
+ af->cancelScan();
+ }
+ break;
+ }
+
+ case controls::LENS_POSITION: {
+ AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
+ if (af) {
+ int32_t hwpos;
+ if (af->setLensPosition(ctrl.second.get<float>(), &hwpos)) {
+ ControlList lensCtrls(lensCtrls_);
+ lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, hwpos);
+ setLensControls.emit(lensCtrls);
+ }
+ } else {
+ LOG(IPARPI, Warning)
+ << "Could not set LENS_POSITION - no AF algorithm";
+ }
+ break;
+ }
+
+ case controls::HDR_MODE: {
+ HdrAlgorithm *hdr = dynamic_cast<HdrAlgorithm *>(controller_.getAlgorithm("hdr"));
+ if (!hdr) {
+ LOG(IPARPI, Warning) << "No HDR algorithm available";
+ break;
+ }
+
+ auto mode = HdrModeTable.find(ctrl.second.get<int32_t>());
+ if (mode == HdrModeTable.end()) {
+ LOG(IPARPI, Warning) << "Unrecognised HDR mode";
+ break;
+ }
+
+ AgcAlgorithm *agc = dynamic_cast<AgcAlgorithm *>(controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning) << "HDR requires an AGC algorithm";
+ break;
+ }
+
+ if (hdr->setMode(mode->second) == 0)
+ agc->setActiveChannels(hdr->getChannels());
+ else
+ LOG(IPARPI, Warning)
+ << "HDR mode " << mode->second << " not supported";
+
+ break;
+ }
+
+ case controls::rpi::STATS_OUTPUT_ENABLE:
+ statsMetadataOutput_ = ctrl.second.get<bool>();
+ break;
+
+ default:
+ LOG(IPARPI, Warning)
+ << "Ctrl " << controls::controls.at(ctrl.first)->name()
+ << " is not handled.";
+ break;
+ }
+ }
+
+ /* Give derived classes a chance to examine the new controls. */
+ handleControls(controls);
+}
+
+void IpaBase::fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext)
+{
+ DeviceStatus deviceStatus = {};
+
+ int32_t exposureLines = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+ int32_t gainCode = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
+ int32_t vblank = sensorControls.get(V4L2_CID_VBLANK).get<int32_t>();
+ int32_t hblank = sensorControls.get(V4L2_CID_HBLANK).get<int32_t>();
+
+ deviceStatus.lineLength = helper_->hblankToLineLength(hblank);
+ deviceStatus.shutterSpeed = helper_->exposure(exposureLines, deviceStatus.lineLength);
+ deviceStatus.analogueGain = helper_->gain(gainCode);
+ deviceStatus.frameLength = mode_.height + vblank;
+
+ RPiController::AfAlgorithm *af = dynamic_cast<RPiController::AfAlgorithm *>(
+ controller_.getAlgorithm("af"));
+ if (af)
+ deviceStatus.lensPosition = af->getLensPosition();
+
+ LOG(IPARPI, Debug) << "Metadata - " << deviceStatus;
+
+ rpiMetadata_[ipaContext].set("device.status", deviceStatus);
+}
+
+void IpaBase::reportMetadata(unsigned int ipaContext)
+{
+ RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];
+ std::unique_lock<RPiController::Metadata> lock(rpiMetadata);
+
+ /*
+ * Certain information about the current frame and how it will be
+ * processed can be extracted and placed into the libcamera metadata
+ * buffer, where an application could query it.
+ */
+ DeviceStatus *deviceStatus = rpiMetadata.getLocked<DeviceStatus>("device.status");
+ if (deviceStatus) {
+ libcameraMetadata_.set(controls::ExposureTime,
+ deviceStatus->shutterSpeed.get<std::micro>());
+ libcameraMetadata_.set(controls::AnalogueGain, deviceStatus->analogueGain);
+ libcameraMetadata_.set(controls::FrameDuration,
+ helper_->exposure(deviceStatus->frameLength, deviceStatus->lineLength).get<std::micro>());
+ if (deviceStatus->sensorTemperature)
+ libcameraMetadata_.set(controls::SensorTemperature, *deviceStatus->sensorTemperature);
+ if (deviceStatus->lensPosition)
+ libcameraMetadata_.set(controls::LensPosition, *deviceStatus->lensPosition);
+ }
+
+ AgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked<AgcPrepareStatus>("agc.prepare_status");
+ if (agcPrepareStatus) {
+ libcameraMetadata_.set(controls::AeLocked, agcPrepareStatus->locked);
+ libcameraMetadata_.set(controls::DigitalGain, agcPrepareStatus->digitalGain);
+ }
+
+ LuxStatus *luxStatus = rpiMetadata.getLocked<LuxStatus>("lux.status");
+ if (luxStatus)
+ libcameraMetadata_.set(controls::Lux, luxStatus->lux);
+
+ AwbStatus *awbStatus = rpiMetadata.getLocked<AwbStatus>("awb.status");
+ if (awbStatus) {
+ libcameraMetadata_.set(controls::ColourGains, { static_cast<float>(awbStatus->gainR),
+ static_cast<float>(awbStatus->gainB) });
+ libcameraMetadata_.set(controls::ColourTemperature, awbStatus->temperatureK);
+ }
+
+ BlackLevelStatus *blackLevelStatus = rpiMetadata.getLocked<BlackLevelStatus>("black_level.status");
+ if (blackLevelStatus)
+ libcameraMetadata_.set(controls::SensorBlackLevels,
+ { static_cast<int32_t>(blackLevelStatus->blackLevelR),
+ static_cast<int32_t>(blackLevelStatus->blackLevelG),
+ static_cast<int32_t>(blackLevelStatus->blackLevelG),
+ static_cast<int32_t>(blackLevelStatus->blackLevelB) });
+
+ RPiController::FocusRegions *focusStatus =
+ rpiMetadata.getLocked<RPiController::FocusRegions>("focus.status");
+ if (focusStatus) {
+ /*
+ * Calculate the average FoM over the central (symmetric) positions
+ * to give an overall scene FoM. This can change later if it is
+ * not deemed suitable.
+ */
+ libcamera::Size size = focusStatus->size();
+ unsigned rows = size.height;
+ unsigned cols = size.width;
+
+ uint64_t sum = 0;
+ unsigned int numRegions = 0;
+ for (unsigned r = rows / 3; r < rows - rows / 3; ++r) {
+ for (unsigned c = cols / 4; c < cols - cols / 4; ++c) {
+ sum += focusStatus->get({ (int)c, (int)r }).val;
+ numRegions++;
+ }
+ }
+
+ uint32_t focusFoM = sum / numRegions;
+ libcameraMetadata_.set(controls::FocusFoM, focusFoM);
+ }
+
+ CcmStatus *ccmStatus = rpiMetadata.getLocked<CcmStatus>("ccm.status");
+ if (ccmStatus) {
+ float m[9];
+ for (unsigned int i = 0; i < 9; i++)
+ m[i] = ccmStatus->matrix[i];
+ libcameraMetadata_.set(controls::ColourCorrectionMatrix, m);
+ }
+
+ const AfStatus *afStatus = rpiMetadata.getLocked<AfStatus>("af.status");
+ if (afStatus) {
+ int32_t s, p;
+ switch (afStatus->state) {
+ case AfState::Scanning:
+ s = controls::AfStateScanning;
+ break;
+ case AfState::Focused:
+ s = controls::AfStateFocused;
+ break;
+ case AfState::Failed:
+ s = controls::AfStateFailed;
+ break;
+ default:
+ s = controls::AfStateIdle;
+ }
+ switch (afStatus->pauseState) {
+ case AfPauseState::Pausing:
+ p = controls::AfPauseStatePausing;
+ break;
+ case AfPauseState::Paused:
+ p = controls::AfPauseStatePaused;
+ break;
+ default:
+ p = controls::AfPauseStateRunning;
+ }
+ libcameraMetadata_.set(controls::AfState, s);
+ libcameraMetadata_.set(controls::AfPauseState, p);
+ }
+
+ const HdrStatus *hdrStatus = rpiMetadata.getLocked<HdrStatus>("hdr.status");
+ if (hdrStatus) {
+ if (hdrStatus->channel == "short")
+ libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelShort);
+ else if (hdrStatus->channel == "long")
+ libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelLong);
+ else
+ libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelNone);
+ }
+
+ metadataReady.emit(libcameraMetadata_);
+}
+
+void IpaBase::applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration)
+{
+ /*
+ * This will only be applied once AGC recalculations occur.
+ * The values may be clamped based on the sensor mode capabilities as well.
+ */
+ minFrameDuration_ = minFrameDuration ? minFrameDuration : defaultMinFrameDuration;
+ maxFrameDuration_ = maxFrameDuration ? maxFrameDuration : defaultMaxFrameDuration;
+ minFrameDuration_ = std::clamp(minFrameDuration_,
+ mode_.minFrameDuration, mode_.maxFrameDuration);
+ maxFrameDuration_ = std::clamp(maxFrameDuration_,
+ mode_.minFrameDuration, mode_.maxFrameDuration);
+ maxFrameDuration_ = std::max(maxFrameDuration_, minFrameDuration_);
+
+ /* Return the validated limits via metadata. */
+ libcameraMetadata_.set(controls::FrameDurationLimits,
+ { static_cast<int64_t>(minFrameDuration_.get<std::micro>()),
+ static_cast<int64_t>(maxFrameDuration_.get<std::micro>()) });
+
+ /*
+ * Calculate the maximum exposure time possible for the AGC to use.
+ * getBlanking() will update maxShutter with the largest exposure
+ * value possible.
+ */
+ Duration maxShutter = Duration::max();
+ helper_->getBlanking(maxShutter, minFrameDuration_, maxFrameDuration_);
+
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ agc->setMaxShutter(maxShutter);
+}
+
+void IpaBase::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls)
+{
+ const int32_t minGainCode = helper_->gainCode(mode_.minAnalogueGain);
+ const int32_t maxGainCode = helper_->gainCode(mode_.maxAnalogueGain);
+ int32_t gainCode = helper_->gainCode(agcStatus->analogueGain);
+
+ /*
+ * Ensure anything larger than the max gain code will not be passed to
+ * DelayedControls. The AGC will correctly handle a lower gain returned
+ * by the sensor, provided it knows the actual gain used.
+ */
+ gainCode = std::clamp<int32_t>(gainCode, minGainCode, maxGainCode);
+
+ /* getBlanking might clip exposure time to the fps limits. */
+ Duration exposure = agcStatus->shutterTime;
+ auto [vblank, hblank] = helper_->getBlanking(exposure, minFrameDuration_, maxFrameDuration_);
+ int32_t exposureLines = helper_->exposureLines(exposure,
+ helper_->hblankToLineLength(hblank));
+
+ LOG(IPARPI, Debug) << "Applying AGC Exposure: " << exposure
+ << " (Shutter lines: " << exposureLines << ", AGC requested "
+ << agcStatus->shutterTime << ") Gain: "
+ << agcStatus->analogueGain << " (Gain Code: "
+ << gainCode << ")";
+
+ ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
+ ctrls.set(V4L2_CID_EXPOSURE, exposureLines);
+ ctrls.set(V4L2_CID_ANALOGUE_GAIN, gainCode);
+
+ /*
+ * At present, there is no way of knowing if a control is read-only.
+ * As a workaround, assume that if the minimum and maximum values of
+ * the V4L2_CID_HBLANK control are the same, it implies the control
+ * is read-only. This seems to be the case for all the cameras our IPA
+ * works with.
+ *
+ * \todo The control API ought to have a flag to specify if a control
+ * is read-only which could be used below.
+ */
+ if (mode_.minLineLength != mode_.maxLineLength)
+ ctrls.set(V4L2_CID_HBLANK, static_cast<int32_t>(hblank));
+
+ /*
+ * Store the frame length times in a circular queue, up-to FrameLengthsQueueSize
+ * elements. This will be used to advertise a camera timeout value to the
+ * pipeline handler.
+ */
+ frameLengths_.pop_front();
+ frameLengths_.push_back(helper_->exposure(vblank + mode_.height,
+ helper_->hblankToLineLength(hblank)));
+}
+
+} /* namespace ipa::RPi */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rpi/common/ipa_base.h b/src/ipa/rpi/common/ipa_base.h
new file mode 100644
index 00000000..4db4411e
--- /dev/null
+++ b/src/ipa/rpi/common/ipa_base.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * ipa_base.h - Raspberry Pi IPA base class
+ */
+#pragma once
+
+#include <array>
+#include <deque>
+#include <map>
+#include <stdint.h>
+
+#include <libcamera/base/utils.h>
+#include <libcamera/controls.h>
+
+#include <libcamera/ipa/raspberrypi_ipa_interface.h>
+
+#include "libcamera/internal/mapped_framebuffer.h"
+
+#include "cam_helper/cam_helper.h"
+#include "controller/agc_status.h"
+#include "controller/camera_mode.h"
+#include "controller/controller.h"
+#include "controller/metadata.h"
+
+namespace libcamera {
+
+namespace ipa::RPi {
+
+class IpaBase : public IPARPiInterface
+{
+public:
+ IpaBase();
+ ~IpaBase();
+
+ int32_t init(const IPASettings &settings, const InitParams &params, InitResult *result) override;
+ int32_t configure(const IPACameraSensorInfo &sensorInfo, const ConfigParams &params,
+ ConfigResult *result) override;
+
+ void start(const ControlList &controls, StartResult *result) override;
+ void stop() override {}
+
+ void mapBuffers(const std::vector<IPABuffer> &buffers) override;
+ void unmapBuffers(const std::vector<unsigned int> &ids) override;
+
+ void prepareIsp(const PrepareParams &params) override;
+ void processStats(const ProcessParams &params) override;
+
+protected:
+ /* Raspberry Pi controller specific defines. */
+ std::unique_ptr<RPiController::CamHelper> helper_;
+ RPiController::Controller controller_;
+
+ ControlInfoMap sensorCtrls_;
+ ControlInfoMap lensCtrls_;
+
+ /* Camera sensor params. */
+ CameraMode mode_;
+
+ /* Track the frame length times over FrameLengthsQueueSize frames. */
+ std::deque<utils::Duration> frameLengths_;
+ utils::Duration lastTimeout_;
+ ControlList libcameraMetadata_;
+ bool statsMetadataOutput_;
+
+private:
+ /* Number of metadata objects available in the context list. */
+ static constexpr unsigned int numMetadataContexts = 16;
+
+ virtual int32_t platformInit(const InitParams &params, InitResult *result) = 0;
+ virtual int32_t platformStart(const ControlList &controls, StartResult *result) = 0;
+ virtual int32_t platformConfigure(const ConfigParams &params, ConfigResult *result) = 0;
+
+ virtual void platformPrepareIsp(const PrepareParams &params,
+ RPiController::Metadata &rpiMetadata) = 0;
+ virtual RPiController::StatisticsPtr platformProcessStats(Span<uint8_t> mem) = 0;
+
+ void setMode(const IPACameraSensorInfo &sensorInfo);
+ void setCameraTimeoutValue();
+ bool validateSensorControls();
+ bool validateLensControls();
+ void applyControls(const ControlList &controls);
+ virtual void handleControls(const ControlList &controls) = 0;
+ void fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext);
+ void reportMetadata(unsigned int ipaContext);
+ void applyFrameDurations(utils::Duration minFrameDuration, utils::Duration maxFrameDuration);
+ void applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls);
+
+ std::map<unsigned int, MappedFrameBuffer> buffers_;
+
+ bool lensPresent_;
+ bool monoSensor_;
+
+ std::array<RPiController::Metadata, numMetadataContexts> rpiMetadata_;
+
+ /*
+ * We count frames to decide if the frame must be hidden (e.g. from
+ * display) or mistrusted (i.e. not given to the control algos).
+ */
+ uint64_t frameCount_;
+
+ /* How many frames we should avoid running control algos on. */
+ unsigned int mistrustCount_;
+
+ /* Number of frames that need to be dropped on startup. */
+ unsigned int dropFrameCount_;
+
+ /* Frame timestamp for the last run of the controller. */
+ uint64_t lastRunTimestamp_;
+
+ /* Do we run a Controller::process() for this frame? */
+ bool processPending_;
+
+ /* Distinguish the first camera start from others. */
+ bool firstStart_;
+
+ /* Frame duration (1/fps) limits. */
+ utils::Duration minFrameDuration_;
+ utils::Duration maxFrameDuration_;
+
+ /* The current state of flicker avoidance. */
+ struct FlickerState {
+ int32_t mode;
+ utils::Duration manualPeriod;
+ } flickerState_;
+};
+
+} /* namespace ipa::RPi */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rpi/common/meson.build b/src/ipa/rpi/common/meson.build
new file mode 100644
index 00000000..73d2ee73
--- /dev/null
+++ b/src/ipa/rpi/common/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: CC0-1.0
+
+rpi_ipa_common_sources = files([
+ 'ipa_base.cpp',
+])
+
+rpi_ipa_common_includes = [
+ include_directories('..'),
+]
+
+rpi_ipa_common_deps = [
+ libcamera_private,
+]
+
+rpi_ipa_common_lib = static_library('rpi_ipa_common', rpi_ipa_common_sources,
+ include_directories : rpi_ipa_common_includes,
+ dependencies : rpi_ipa_common_deps)
diff --git a/src/ipa/rpi/controller/af_algorithm.h b/src/ipa/rpi/controller/af_algorithm.h
new file mode 100644
index 00000000..ad9b5754
--- /dev/null
+++ b/src/ipa/rpi/controller/af_algorithm.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * af_algorithm.hpp - auto focus algorithm interface
+ */
+#pragma once
+
+#include <optional>
+
+#include <libcamera/base/span.h>
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class AfAlgorithm : public Algorithm
+{
+public:
+ AfAlgorithm(Controller *controller)
+ : Algorithm(controller) {}
+
+ /*
+ * An autofocus algorithm should provide the following calls.
+ *
+ * Where a ControlList combines a change of AfMode with other AF
+ * controls, setMode() should be called first, to ensure the
+ * algorithm will be in the correct state to handle controls.
+ *
+ * setLensPosition() returns true if the mode was AfModeManual and
+ * the lens position has changed, otherwise returns false. When it
+ * returns true, hwpos should be sent immediately to the lens driver.
+ *
+ * getMode() is provided mainly for validating controls.
+ * getLensPosition() is provided for populating DeviceStatus.
+ */
+
+ enum AfRange { AfRangeNormal = 0,
+ AfRangeMacro,
+ AfRangeFull,
+ AfRangeMax };
+
+ enum AfSpeed { AfSpeedNormal = 0,
+ AfSpeedFast,
+ AfSpeedMax };
+
+ enum AfMode { AfModeManual = 0,
+ AfModeAuto,
+ AfModeContinuous };
+
+ enum AfPause { AfPauseImmediate = 0,
+ AfPauseDeferred,
+ AfPauseResume };
+
+ virtual void setRange([[maybe_unused]] AfRange range)
+ {
+ }
+ virtual void setSpeed([[maybe_unused]] AfSpeed speed)
+ {
+ }
+ virtual void setMetering([[maybe_unused]] bool use_windows)
+ {
+ }
+ virtual void setWindows([[maybe_unused]] libcamera::Span<libcamera::Rectangle const> const &wins)
+ {
+ }
+ virtual void setMode(AfMode mode) = 0;
+ virtual AfMode getMode() const = 0;
+ virtual bool setLensPosition(double dioptres, int32_t *hwpos) = 0;
+ virtual std::optional<double> getLensPosition() const = 0;
+ virtual void triggerScan() = 0;
+ virtual void cancelScan() = 0;
+ virtual void pause(AfPause pause) = 0;
+};
+
+} // namespace RPiController
diff --git a/src/ipa/rpi/controller/af_status.h b/src/ipa/rpi/controller/af_status.h
new file mode 100644
index 00000000..92c08812
--- /dev/null
+++ b/src/ipa/rpi/controller/af_status.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * af_status.h - AF control algorithm status
+ */
+#pragma once
+
+#include <optional>
+
+/*
+ * The AF algorithm should post the following structure into the image's
+ * "af.status" metadata. lensSetting should control the lens.
+ */
+
+enum class AfState {
+ Idle = 0,
+ Scanning,
+ Focused,
+ Failed
+};
+
+enum class AfPauseState {
+ Running = 0,
+ Pausing,
+ Paused
+};
+
+struct AfStatus {
+ /* state for reporting */
+ AfState state;
+ AfPauseState pauseState;
+ /* lensSetting should be sent to the lens driver, when valid */
+ std::optional<int> lensSetting;
+};
diff --git a/src/ipa/rpi/controller/agc_algorithm.h b/src/ipa/rpi/controller/agc_algorithm.h
new file mode 100644
index 00000000..534e38e2
--- /dev/null
+++ b/src/ipa/rpi/controller/agc_algorithm.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * agc_algorithm.h - AGC/AEC control algorithm interface
+ */
+#pragma once
+
+#include <vector>
+
+#include <libcamera/base/utils.h>
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class AgcAlgorithm : public Algorithm
+{
+public:
+ AgcAlgorithm(Controller *controller) : Algorithm(controller) {}
+ /* An AGC algorithm must provide the following: */
+ virtual unsigned int getConvergenceFrames() const = 0;
+ virtual std::vector<double> const &getWeights() const = 0;
+ virtual void setEv(unsigned int channel, double ev) = 0;
+ virtual void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) = 0;
+ virtual void setFixedShutter(unsigned int channel,
+ libcamera::utils::Duration fixedShutter) = 0;
+ virtual void setMaxShutter(libcamera::utils::Duration maxShutter) = 0;
+ virtual void setFixedAnalogueGain(unsigned int channel, double fixedAnalogueGain) = 0;
+ virtual void setMeteringMode(std::string const &meteringModeName) = 0;
+ virtual void setExposureMode(std::string const &exposureModeName) = 0;
+ virtual void setConstraintMode(std::string const &contraintModeName) = 0;
+ virtual void enableAuto() = 0;
+ virtual void disableAuto() = 0;
+ virtual void setActiveChannels(const std::vector<unsigned int> &activeChannels) = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h
new file mode 100644
index 00000000..68f89958
--- /dev/null
+++ b/src/ipa/rpi/controller/agc_status.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * agc_status.h - AGC/AEC control algorithm status
+ */
+#pragma once
+
+#include <string>
+
+#include <libcamera/base/utils.h>
+
+#include "hdr_status.h"
+
+/*
+ * The AGC algorithm process method should post an AgcStatus into the image
+ * metadata under the tag "agc.status".
+ * The AGC algorithm prepare method should post an AgcPrepareStatus instead
+ * under "agc.prepare_status".
+ */
+
+/*
+ * Note: total_exposure_value will be reported as zero until the algorithm has
+ * seen statistics and calculated meaningful values. The contents should be
+ * ignored until then.
+ */
+
+struct AgcStatus {
+ libcamera::utils::Duration totalExposureValue; /* value for all exposure and gain for this image */
+ libcamera::utils::Duration targetExposureValue; /* (unfiltered) target total exposure AGC is aiming for */
+ libcamera::utils::Duration shutterTime;
+ double analogueGain;
+ std::string exposureMode;
+ std::string constraintMode;
+ std::string meteringMode;
+ double ev;
+ libcamera::utils::Duration flickerPeriod;
+ int floatingRegionEnable;
+ libcamera::utils::Duration fixedShutter;
+ double fixedAnalogueGain;
+ unsigned int channel;
+ HdrStatus hdr;
+};
+
+struct AgcPrepareStatus {
+ double digitalGain;
+ int locked;
+};
diff --git a/src/ipa/rpi/controller/algorithm.cpp b/src/ipa/rpi/controller/algorithm.cpp
new file mode 100644
index 00000000..a957fde5
--- /dev/null
+++ b/src/ipa/rpi/controller/algorithm.cpp
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * algorithm.cpp - ISP control algorithms
+ */
+
+#include "algorithm.h"
+
+using namespace RPiController;
+
+int Algorithm::read([[maybe_unused]] const libcamera::YamlObject &params)
+{
+ return 0;
+}
+
+void Algorithm::initialise()
+{
+}
+
+void Algorithm::switchMode([[maybe_unused]] CameraMode const &cameraMode,
+ [[maybe_unused]] Metadata *metadata)
+{
+}
+
+void Algorithm::prepare([[maybe_unused]] Metadata *imageMetadata)
+{
+}
+
+void Algorithm::process([[maybe_unused]] StatisticsPtr &stats,
+ [[maybe_unused]] Metadata *imageMetadata)
+{
+}
+
+/* For registering algorithms with the system: */
+
+namespace {
+
+std::map<std::string, AlgoCreateFunc> &algorithms()
+{
+ static std::map<std::string, AlgoCreateFunc> algorithms;
+ return algorithms;
+}
+
+} /* namespace */
+
+std::map<std::string, AlgoCreateFunc> const &RPiController::getAlgorithms()
+{
+ return algorithms();
+}
+
+RegisterAlgorithm::RegisterAlgorithm(char const *name,
+ AlgoCreateFunc createFunc)
+{
+ algorithms()[std::string(name)] = createFunc;
+}
diff --git a/src/ipa/rpi/controller/algorithm.h b/src/ipa/rpi/controller/algorithm.h
new file mode 100644
index 00000000..4aa814eb
--- /dev/null
+++ b/src/ipa/rpi/controller/algorithm.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * algorithm.h - ISP control algorithm interface
+ */
+#pragma once
+
+/*
+ * All algorithms should be derived from this class and made available to the
+ * Controller.
+ */
+
+#include <string>
+#include <memory>
+#include <map>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "controller.h"
+
+namespace RPiController {
+
+/* This defines the basic interface for all control algorithms. */
+
+class Algorithm
+{
+public:
+ Algorithm(Controller *controller)
+ : controller_(controller)
+ {
+ }
+ virtual ~Algorithm() = default;
+ virtual char const *name() const = 0;
+ virtual int read(const libcamera::YamlObject &params);
+ virtual void initialise();
+ virtual void switchMode(CameraMode const &cameraMode, Metadata *metadata);
+ virtual void prepare(Metadata *imageMetadata);
+ virtual void process(StatisticsPtr &stats, Metadata *imageMetadata);
+ Metadata &getGlobalMetadata() const
+ {
+ return controller_->getGlobalMetadata();
+ }
+ const std::string &getTarget() const
+ {
+ return controller_->getTarget();
+ }
+ const Controller::HardwareConfig &getHardwareConfig() const
+ {
+ return controller_->getHardwareConfig();
+ }
+
+private:
+ Controller *controller_;
+};
+
+/*
+ * This code is for automatic registration of Front End algorithms with the
+ * system.
+ */
+
+typedef Algorithm *(*AlgoCreateFunc)(Controller *controller);
+struct RegisterAlgorithm {
+ RegisterAlgorithm(char const *name, AlgoCreateFunc createFunc);
+};
+std::map<std::string, AlgoCreateFunc> const &getAlgorithms();
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/alsc_status.h b/src/ipa/rpi/controller/alsc_status.h
new file mode 100644
index 00000000..49a9f4a0
--- /dev/null
+++ b/src/ipa/rpi/controller/alsc_status.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * alsc_status.h - ALSC (auto lens shading correction) control algorithm status
+ */
+#pragma once
+
+#include <vector>
+
+/*
+ * The ALSC algorithm should post the following structure into the image's
+ * "alsc.status" metadata.
+ */
+
+struct AlscStatus {
+ std::vector<double> r;
+ std::vector<double> g;
+ std::vector<double> b;
+ unsigned int rows;
+ unsigned int cols;
+};
diff --git a/src/ipa/rpi/controller/awb_algorithm.h b/src/ipa/rpi/controller/awb_algorithm.h
new file mode 100644
index 00000000..6009bdac
--- /dev/null
+++ b/src/ipa/rpi/controller/awb_algorithm.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * awb_algorithm.h - AWB control algorithm interface
+ */
+#pragma once
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class AwbAlgorithm : public Algorithm
+{
+public:
+ AwbAlgorithm(Controller *controller) : Algorithm(controller) {}
+ /* An AWB algorithm must provide the following: */
+ virtual unsigned int getConvergenceFrames() const = 0;
+ virtual void initialValues(double &gainR, double &gainB) = 0;
+ virtual void setMode(std::string const &modeName) = 0;
+ virtual void setManualGains(double manualR, double manualB) = 0;
+ virtual void enableAuto() = 0;
+ virtual void disableAuto() = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/awb_status.h b/src/ipa/rpi/controller/awb_status.h
new file mode 100644
index 00000000..dd5a79e3
--- /dev/null
+++ b/src/ipa/rpi/controller/awb_status.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * awb_status.h - AWB control algorithm status
+ */
+#pragma once
+
+/*
+ * The AWB algorithm places its results into both the image and global metadata,
+ * under the tag "awb.status".
+ */
+
+struct AwbStatus {
+ char mode[32];
+ double temperatureK;
+ double gainR;
+ double gainG;
+ double gainB;
+};
diff --git a/src/ipa/rpi/controller/black_level_algorithm.h b/src/ipa/rpi/controller/black_level_algorithm.h
new file mode 100644
index 00000000..c2cff2f5
--- /dev/null
+++ b/src/ipa/rpi/controller/black_level_algorithm.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * black_level_algorithm.h - black level control algorithm interface
+ */
+#pragma once
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class BlackLevelAlgorithm : public Algorithm
+{
+public:
+ BlackLevelAlgorithm(Controller *controller)
+ : Algorithm(controller) {}
+ /* A black level algorithm must provide the following: */
+ virtual void initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG,
+ uint16_t &blackLevelB) = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/black_level_status.h b/src/ipa/rpi/controller/black_level_status.h
new file mode 100644
index 00000000..fd5e4ccb
--- /dev/null
+++ b/src/ipa/rpi/controller/black_level_status.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * black_level_status.h - black level control algorithm status
+ */
+#pragma once
+
+/* The "black level" algorithm stores the black levels to use. */
+
+struct BlackLevelStatus {
+ uint16_t blackLevelR; /* out of 16 bits */
+ uint16_t blackLevelG;
+ uint16_t blackLevelB;
+};
diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h
new file mode 100644
index 00000000..475d4c5c
--- /dev/null
+++ b/src/ipa/rpi/controller/cac_status.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ *
+ * CAC (Chromatic Abberation Correction) algorithm status
+ */
+#pragma once
+
+#include "pwl.h"
+
+struct CacStatus {
+ std::vector<double> lutRx;
+ std::vector<double> lutRy;
+ std::vector<double> lutBx;
+ std::vector<double> lutBy;
+};
diff --git a/src/ipa/rpi/controller/camera_mode.h b/src/ipa/rpi/controller/camera_mode.h
new file mode 100644
index 00000000..63b11778
--- /dev/null
+++ b/src/ipa/rpi/controller/camera_mode.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2020, Raspberry Pi Ltd
+ *
+ * camera_mode.h - description of a particular operating mode of a sensor
+ */
+#pragma once
+
+#include <libcamera/transform.h>
+
+#include <libcamera/base/utils.h>
+
+/*
+ * Description of a "camera mode", holding enough information for control
+ * algorithms to adapt their behaviour to the different modes of the camera,
+ * including binning, scaling, cropping etc.
+ */
+
+struct CameraMode {
+ /* bit depth of the raw camera output */
+ uint32_t bitdepth;
+ /* size in pixels of frames in this mode */
+ uint16_t width;
+ uint16_t height;
+ /* size of full resolution uncropped frame ("sensor frame") */
+ uint16_t sensorWidth;
+ uint16_t sensorHeight;
+ /* binning factor (1 = no binning, 2 = 2-pixel binning etc.) */
+ uint8_t binX;
+ uint8_t binY;
+ /* location of top left pixel in the sensor frame */
+ uint16_t cropX;
+ uint16_t cropY;
+ /* scaling factor (so if uncropped, width*scaleX is sensorWidth) */
+ double scaleX;
+ double scaleY;
+ /* scaling of the noise compared to the native sensor mode */
+ double noiseFactor;
+ /* minimum and maximum line time and frame durations */
+ libcamera::utils::Duration minLineLength;
+ libcamera::utils::Duration maxLineLength;
+ libcamera::utils::Duration minFrameDuration;
+ libcamera::utils::Duration maxFrameDuration;
+ /* any camera transform *not* reflected already in the camera tuning */
+ libcamera::Transform transform;
+ /* minimum and maximum frame lengths in units of lines */
+ uint32_t minFrameLength;
+ uint32_t maxFrameLength;
+ /* sensitivity of this mode */
+ double sensitivity;
+ /* pixel clock rate */
+ uint64_t pixelRate;
+ /* Mode specific shutter speed limits */
+ libcamera::utils::Duration minShutter;
+ libcamera::utils::Duration maxShutter;
+ /* Mode specific analogue gain limits */
+ double minAnalogueGain;
+ double maxAnalogueGain;
+};
diff --git a/src/ipa/rpi/controller/ccm_algorithm.h b/src/ipa/rpi/controller/ccm_algorithm.h
new file mode 100644
index 00000000..e2c4d771
--- /dev/null
+++ b/src/ipa/rpi/controller/ccm_algorithm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * ccm_algorithm.h - CCM (colour correction matrix) control algorithm interface
+ */
+#pragma once
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class CcmAlgorithm : public Algorithm
+{
+public:
+ CcmAlgorithm(Controller *controller) : Algorithm(controller) {}
+ /* A CCM algorithm must provide the following: */
+ virtual void setSaturation(double saturation) = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/ccm_status.h b/src/ipa/rpi/controller/ccm_status.h
index 7e41dd1f..5e28ee7c 100644
--- a/src/ipa/raspberrypi/controller/ccm_status.h
+++ b/src/ipa/rpi/controller/ccm_status.h
@@ -1,22 +1,14 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
* ccm_status.h - CCM (colour correction matrix) control algorithm status
*/
#pragma once
-// The "ccm" algorithm generates an appropriate colour matrix.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
+/* The "ccm" algorithm generates an appropriate colour matrix. */
struct CcmStatus {
double matrix[9];
double saturation;
};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/rpi/controller/contrast_algorithm.h b/src/ipa/rpi/controller/contrast_algorithm.h
new file mode 100644
index 00000000..895b36b0
--- /dev/null
+++ b/src/ipa/rpi/controller/contrast_algorithm.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * contrast_algorithm.h - contrast (gamma) control algorithm interface
+ */
+#pragma once
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class ContrastAlgorithm : public Algorithm
+{
+public:
+ ContrastAlgorithm(Controller *controller) : Algorithm(controller) {}
+ /* A contrast algorithm must provide the following: */
+ virtual void setBrightness(double brightness) = 0;
+ virtual void setContrast(double contrast) = 0;
+ virtual void enableCe(bool enable) = 0;
+ virtual void restoreCe() = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h
new file mode 100644
index 00000000..fb9fe4ba
--- /dev/null
+++ b/src/ipa/rpi/controller/contrast_status.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * contrast_status.h - contrast (gamma) control algorithm status
+ */
+#pragma once
+
+#include "pwl.h"
+
+/*
+ * The "contrast" algorithm creates a gamma curve, optionally doing a little bit
+ * of contrast stretching based on the AGC histogram.
+ */
+
+struct ContrastStatus {
+ RPiController::Pwl gammaCurve;
+ double brightness;
+ double contrast;
+};
diff --git a/src/ipa/rpi/controller/controller.cpp b/src/ipa/rpi/controller/controller.cpp
new file mode 100644
index 00000000..5ca98b98
--- /dev/null
+++ b/src/ipa/rpi/controller/controller.cpp
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * controller.cpp - ISP controller
+ */
+
+#include <assert.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "algorithm.h"
+#include "controller.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using namespace std::literals::chrono_literals;
+
+LOG_DEFINE_CATEGORY(RPiController)
+
+static const std::map<std::string, Controller::HardwareConfig> HardwareConfigMap = {
+ {
+ "bcm2835",
+ {
+ /*
+ * There are only ever 15 AGC regions computed by the firmware
+ * due to zoning, but the HW defines AGC_REGIONS == 16!
+ */
+ .agcRegions = { 15 , 1 },
+ .agcZoneWeights = { 15 , 1 },
+ .awbRegions = { 16, 12 },
+ .cacRegions = { 0, 0 },
+ .focusRegions = { 4, 3 },
+ .numHistogramBins = 128,
+ .numGammaPoints = 33,
+ .pipelineWidth = 13,
+ .statsInline = false,
+ .minPixelProcessingTime = 0s,
+ }
+ },
+ {
+ "pisp",
+ {
+ .agcRegions = { 0, 0 },
+ .agcZoneWeights = { 15, 15 },
+ .awbRegions = { 32, 32 },
+ .cacRegions = { 8, 8 },
+ .focusRegions = { 8, 8 },
+ .numHistogramBins = 1024,
+ .numGammaPoints = 64,
+ .pipelineWidth = 16,
+ .statsInline = true,
+
+ /*
+ * The constraint below is on the rate of pixels going
+ * from CSI2 peripheral to ISP-FE (400Mpix/s, plus tiny
+ * overheads per scanline, for which 380Mpix/s is a
+ * conservative bound).
+ *
+ * There is a 64kbit data FIFO before the bottleneck,
+ * which means that in all reasonable cases the
+ * constraint applies at a timescale >= 1 scanline, so
+ * adding horizontal blanking can prevent loss.
+ *
+ * If the backlog were to grow beyond 64kbit during a
+ * single scanline, there could still be loss. This
+ * could happen using 4 lanes at 1.5Gbps at 10bpp with
+ * frames wider than ~16,000 pixels.
+ */
+ .minPixelProcessingTime = 1.0us / 380,
+ }
+ },
+};
+
+Controller::Controller()
+ : switchModeCalled_(false)
+{
+}
+
+Controller::~Controller() {}
+
+int Controller::read(char const *filename)
+{
+ File file(filename);
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ LOG(RPiController, Warning)
+ << "Failed to open tuning file '" << filename << "'";
+ return -EINVAL;
+ }
+
+ std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+ if (!root)
+ return -EINVAL;
+
+ double version = (*root)["version"].get<double>(1.0);
+ target_ = (*root)["target"].get<std::string>("bcm2835");
+
+ if (version < 2.0) {
+ LOG(RPiController, Warning)
+ << "This format of the tuning file will be deprecated soon!"
+ << " Please use the convert_tuning.py utility to update to version 2.0.";
+
+ for (auto const &[key, value] : root->asDict()) {
+ int ret = createAlgorithm(key, value);
+ if (ret)
+ return ret;
+ }
+ } else if (version < 3.0) {
+ if (!root->contains("algorithms")) {
+ LOG(RPiController, Error)
+ << "Tuning file " << filename
+ << " does not have an \"algorithms\" list!";
+ return -EINVAL;
+ }
+
+ for (auto const &rootAlgo : (*root)["algorithms"].asList())
+ for (auto const &[key, value] : rootAlgo.asDict()) {
+ int ret = createAlgorithm(key, value);
+ if (ret)
+ return ret;
+ }
+ } else {
+ LOG(RPiController, Error)
+ << "Unrecognised version " << version
+ << " for the tuning file " << filename;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int Controller::createAlgorithm(const std::string &name, const YamlObject &params)
+{
+ auto it = getAlgorithms().find(name);
+ if (it == getAlgorithms().end()) {
+ LOG(RPiController, Warning)
+ << "No algorithm found for \"" << name << "\"";
+ return 0;
+ }
+
+ Algorithm *algo = (*it->second)(this);
+ int ret = algo->read(params);
+ if (ret)
+ return ret;
+
+ algorithms_.push_back(AlgorithmPtr(algo));
+ return 0;
+}
+
+void Controller::initialise()
+{
+ for (auto &algo : algorithms_)
+ algo->initialise();
+}
+
+void Controller::switchMode(CameraMode const &cameraMode, Metadata *metadata)
+{
+ for (auto &algo : algorithms_)
+ algo->switchMode(cameraMode, metadata);
+ switchModeCalled_ = true;
+}
+
+void Controller::prepare(Metadata *imageMetadata)
+{
+ assert(switchModeCalled_);
+ for (auto &algo : algorithms_)
+ algo->prepare(imageMetadata);
+}
+
+void Controller::process(StatisticsPtr stats, Metadata *imageMetadata)
+{
+ assert(switchModeCalled_);
+ for (auto &algo : algorithms_)
+ algo->process(stats, imageMetadata);
+}
+
+Metadata &Controller::getGlobalMetadata()
+{
+ return globalMetadata_;
+}
+
+Algorithm *Controller::getAlgorithm(std::string const &name) const
+{
+ /*
+ * The passed name must be the entire algorithm name, or must match the
+ * last part of it with a period (.) just before.
+ */
+ size_t nameLen = name.length();
+ for (auto &algo : algorithms_) {
+ char const *algoName = algo->name();
+ size_t algoNameLen = strlen(algoName);
+ if (algoNameLen >= nameLen &&
+ strcasecmp(name.c_str(),
+ algoName + algoNameLen - nameLen) == 0 &&
+ (nameLen == algoNameLen ||
+ algoName[algoNameLen - nameLen - 1] == '.'))
+ return algo.get();
+ }
+ return nullptr;
+}
+
+const std::string &Controller::getTarget() const
+{
+ return target_;
+}
+
+const Controller::HardwareConfig &Controller::getHardwareConfig() const
+{
+ auto cfg = HardwareConfigMap.find(getTarget());
+
+ /*
+ * This really should not happen, the IPA ought to validate the target
+ * on initialisation.
+ */
+ ASSERT(cfg != HardwareConfigMap.end());
+ return cfg->second;
+}
diff --git a/src/ipa/rpi/controller/controller.h b/src/ipa/rpi/controller/controller.h
new file mode 100644
index 00000000..170aea74
--- /dev/null
+++ b/src/ipa/rpi/controller/controller.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * controller.h - ISP controller interface
+ */
+#pragma once
+
+/*
+ * The Controller is simply a container for a collecting together a number of
+ * "control algorithms" (such as AWB etc.) and for running them all in a
+ * convenient manner.
+ */
+
+#include <vector>
+#include <string>
+
+#include <libcamera/base/utils.h>
+#include "libcamera/internal/yaml_parser.h"
+
+#include "camera_mode.h"
+#include "device_status.h"
+#include "metadata.h"
+#include "statistics.h"
+
+namespace RPiController {
+
+class Algorithm;
+typedef std::unique_ptr<Algorithm> AlgorithmPtr;
+
+/*
+ * The Controller holds a pointer to some global_metadata, which is how
+ * different controllers and control algorithms within them can exchange
+ * information. The Prepare function returns a pointer to metadata for this
+ * specific image, and which should be passed on to the Process function.
+ */
+
+class Controller
+{
+public:
+ struct HardwareConfig {
+ libcamera::Size agcRegions;
+ libcamera::Size agcZoneWeights;
+ libcamera::Size awbRegions;
+ libcamera::Size cacRegions;
+ libcamera::Size focusRegions;
+ unsigned int numHistogramBins;
+ unsigned int numGammaPoints;
+ unsigned int pipelineWidth;
+ bool statsInline;
+ libcamera::utils::Duration minPixelProcessingTime;
+ };
+
+ Controller();
+ ~Controller();
+ int read(char const *filename);
+ void initialise();
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata);
+ void prepare(Metadata *imageMetadata);
+ void process(StatisticsPtr stats, Metadata *imageMetadata);
+ Metadata &getGlobalMetadata();
+ Algorithm *getAlgorithm(std::string const &name) const;
+ const std::string &getTarget() const;
+ const HardwareConfig &getHardwareConfig() const;
+
+protected:
+ int createAlgorithm(const std::string &name, const libcamera::YamlObject &params);
+
+ Metadata globalMetadata_;
+ std::vector<AlgorithmPtr> algorithms_;
+ bool switchModeCalled_;
+
+private:
+ std::string target_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/denoise_algorithm.h b/src/ipa/rpi/controller/denoise_algorithm.h
new file mode 100644
index 00000000..444cbc25
--- /dev/null
+++ b/src/ipa/rpi/controller/denoise_algorithm.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2021, Raspberry Pi Ltd
+ *
+ * denoise.h - Denoise control algorithm interface
+ */
+#pragma once
+
+#include <string>
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+enum class DenoiseMode { Off, ColourOff, ColourFast, ColourHighQuality };
+
+class DenoiseAlgorithm : public Algorithm
+{
+public:
+ DenoiseAlgorithm(Controller *controller) : Algorithm(controller) {}
+ /* A Denoise algorithm must provide the following: */
+ virtual void setMode(DenoiseMode mode) = 0;
+ /* Some platforms may not be able to define this, so supply a default. */
+ virtual void setConfig([[maybe_unused]] std::string const &name) {}
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/denoise_status.h b/src/ipa/rpi/controller/denoise_status.h
new file mode 100644
index 00000000..4d2bd291
--- /dev/null
+++ b/src/ipa/rpi/controller/denoise_status.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2021, Raspberry Pi Ltd
+ *
+ * denoise_status.h - Denoise control algorithm status
+ */
+#pragma once
+
+/* This stores the parameters required for Denoise. */
+
+struct DenoiseStatus {
+ double noiseConstant;
+ double noiseSlope;
+ double strength;
+ unsigned int mode;
+};
+
+struct SdnStatus {
+ double noiseConstant;
+ double noiseSlope;
+ double noiseConstant2;
+ double noiseSlope2;
+ double strength;
+};
+
+struct CdnStatus {
+ double strength;
+ double threshold;
+};
+
+struct TdnStatus {
+ double noiseConstant;
+ double noiseSlope;
+ double threshold;
+};
diff --git a/src/ipa/rpi/controller/device_status.cpp b/src/ipa/rpi/controller/device_status.cpp
new file mode 100644
index 00000000..c907efdd
--- /dev/null
+++ b/src/ipa/rpi/controller/device_status.cpp
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2021, Raspberry Pi Ltd
+ *
+ * device_status.cpp - device (image sensor) status
+ */
+#include "device_status.h"
+
+using namespace libcamera; /* for the Duration operator<< overload */
+
+std::ostream &operator<<(std::ostream &out, const DeviceStatus &d)
+{
+ out << "Exposure: " << d.shutterSpeed
+ << " Frame length: " << d.frameLength
+ << " Line length: " << d.lineLength
+ << " Gain: " << d.analogueGain;
+
+ if (d.aperture)
+ out << " Aperture: " << *d.aperture;
+
+ if (d.lensPosition)
+ out << " Lens: " << *d.lensPosition;
+
+ if (d.flashIntensity)
+ out << " Flash: " << *d.flashIntensity;
+
+ if (d.sensorTemperature)
+ out << " Temperature: " << *d.sensorTemperature;
+
+ return out;
+}
diff --git a/src/ipa/raspberrypi/controller/device_status.h b/src/ipa/rpi/controller/device_status.h
index b33f0d09..c45db749 100644
--- a/src/ipa/raspberrypi/controller/device_status.h
+++ b/src/ipa/rpi/controller/device_status.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019-2021, Raspberry Pi Ltd
*
* device_status.h - device (image sensor) status
*/
@@ -18,24 +18,26 @@
struct DeviceStatus {
DeviceStatus()
- : shutter_speed(std::chrono::seconds(0)), frame_length(0),
- analogue_gain(0.0)
+ : shutterSpeed(std::chrono::seconds(0)), frameLength(0),
+ lineLength(std::chrono::seconds(0)), analogueGain(0.0)
{
}
friend std::ostream &operator<<(std::ostream &out, const DeviceStatus &d);
/* time shutter is open */
- libcamera::utils::Duration shutter_speed;
+ libcamera::utils::Duration shutterSpeed;
/* frame length given in number of lines */
- uint32_t frame_length;
- double analogue_gain;
- /* 1.0/distance-in-metres, or 0 if unknown */
- std::optional<double> lens_position;
- /* 1/f so that brightness quadruples when this doubles, or 0 if unknown */
+ uint32_t frameLength;
+ /* line length for the current frame */
+ libcamera::utils::Duration lineLength;
+ double analogueGain;
+ /* 1.0/distance-in-metres */
+ std::optional<double> lensPosition;
+ /* 1/f so that brightness quadruples when this doubles */
std::optional<double> aperture;
/* proportional to brightness with 0 = no flash, 1 = maximum flash */
- std::optional<double> flash_intensity;
+ std::optional<double> flashIntensity;
/* Sensor reported temperature value (in degrees) */
- std::optional<double> sensor_temperature;
+ std::optional<double> sensorTemperature;
};
diff --git a/src/ipa/rpi/controller/dpc_status.h b/src/ipa/rpi/controller/dpc_status.h
new file mode 100644
index 00000000..46d0cf34
--- /dev/null
+++ b/src/ipa/rpi/controller/dpc_status.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * dpc_status.h - DPC (defective pixel correction) control algorithm status
+ */
+#pragma once
+
+/* The "DPC" algorithm sets defective pixel correction strength. */
+
+struct DpcStatus {
+ int strength; /* 0 = "off", 1 = "normal", 2 = "strong" */
+};
diff --git a/src/ipa/raspberrypi/controller/geq_status.h b/src/ipa/rpi/controller/geq_status.h
index 07fd5f03..2d749fc9 100644
--- a/src/ipa/raspberrypi/controller/geq_status.h
+++ b/src/ipa/rpi/controller/geq_status.h
@@ -1,22 +1,14 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
* geq_status.h - GEQ (green equalisation) control algorithm status
*/
#pragma once
-// The "GEQ" algorithm calculates the green equalisation thresholds
-
-#ifdef __cplusplus
-extern "C" {
-#endif
+/* The "GEQ" algorithm calculates the green equalisation thresholds */
struct GeqStatus {
uint16_t offset;
double slope;
};
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/ipa/rpi/controller/hdr_algorithm.h b/src/ipa/rpi/controller/hdr_algorithm.h
new file mode 100644
index 00000000..f622e099
--- /dev/null
+++ b/src/ipa/rpi/controller/hdr_algorithm.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * hdr_algorithm.h - HDR control algorithm interface
+ */
+#pragma once
+
+#include <vector>
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class HdrAlgorithm : public Algorithm
+{
+public:
+ HdrAlgorithm(Controller *controller)
+ : Algorithm(controller) {}
+ /* An HDR algorithm must provide the following: */
+ virtual int setMode(std::string const &modeName) = 0;
+ virtual std::vector<unsigned int> getChannels() const = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/hdr_status.h b/src/ipa/rpi/controller/hdr_status.h
new file mode 100644
index 00000000..24b1a935
--- /dev/null
+++ b/src/ipa/rpi/controller/hdr_status.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ *
+ * hdr_status.h - HDR control algorithm status
+ */
+#pragma once
+
+#include <string>
+
+/*
+ * The HDR algorithm process method should post an HdrStatus into the image
+ * metadata under the tag "hdr.status".
+ */
+
+struct HdrStatus {
+ std::string mode;
+ std::string channel;
+};
diff --git a/src/ipa/rpi/controller/histogram.cpp b/src/ipa/rpi/controller/histogram.cpp
new file mode 100644
index 00000000..78116141
--- /dev/null
+++ b/src/ipa/rpi/controller/histogram.cpp
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * histogram.cpp - histogram calculations
+ */
+#include <math.h>
+#include <stdio.h>
+
+#include "histogram.h"
+
+using namespace RPiController;
+
+uint64_t Histogram::cumulativeFreq(double bin) const
+{
+ if (bin <= 0)
+ return 0;
+ else if (bin >= bins())
+ return total();
+ int b = (int)bin;
+ return cumulative_[b] +
+ (bin - b) * (cumulative_[b + 1] - cumulative_[b]);
+}
+
+double Histogram::quantile(double q, int first, int last) const
+{
+ if (first == -1)
+ first = 0;
+ if (last == -1)
+ last = cumulative_.size() - 2;
+ assert(first <= last);
+ uint64_t items = q * total();
+ while (first < last) /* binary search to find the right bin */
+ {
+ int middle = (first + last) / 2;
+ if (cumulative_[middle + 1] > items)
+ last = middle; /* between first and middle */
+ else
+ first = middle + 1; /* after middle */
+ }
+ assert(items >= cumulative_[first] && items <= cumulative_[last + 1]);
+ double frac = cumulative_[first + 1] == cumulative_[first] ? 0
+ : (double)(items - cumulative_[first]) /
+ (cumulative_[first + 1] - cumulative_[first]);
+ return first + frac;
+}
+
+double Histogram::interBinMean(double binLo, double binHi) const
+{
+ assert(binHi >= binLo);
+ double sumBinFreq = 0, cumulFreq = 0;
+ for (double binNext = floor(binLo) + 1.0; binNext <= ceil(binHi);
+ binLo = binNext, binNext += 1.0) {
+ int bin = floor(binLo);
+ double freq = (cumulative_[bin + 1] - cumulative_[bin]) *
+ (std::min(binNext, binHi) - binLo);
+ sumBinFreq += bin * freq;
+ cumulFreq += freq;
+ }
+
+ if (cumulFreq == 0) {
+ /* interval had zero width or contained no weight? */
+ return binHi;
+ }
+
+ /* add 0.5 to give an average for bin mid-points */
+ return sumBinFreq / cumulFreq + 0.5;
+}
+
+double Histogram::interQuantileMean(double qLo, double qHi) const
+{
+ assert(qHi >= qLo);
+ double pLo = quantile(qLo);
+ double pHi = quantile(qHi, (int)pLo);
+ return interBinMean(pLo, pHi);
+}
diff --git a/src/ipa/rpi/controller/histogram.h b/src/ipa/rpi/controller/histogram.h
new file mode 100644
index 00000000..e2c5509b
--- /dev/null
+++ b/src/ipa/rpi/controller/histogram.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * histogram.h - histogram calculation interface
+ */
+#pragma once
+
+#include <stdint.h>
+#include <vector>
+#include <cassert>
+
+/*
+ * A simple histogram class, for use in particular to find "quantiles" and
+ * averages between "quantiles".
+ */
+
+namespace RPiController {
+
+class Histogram
+{
+public:
+ Histogram()
+ {
+ cumulative_.push_back(0);
+ }
+
+ template<typename T> Histogram(T *histogram, int num)
+ {
+ assert(num);
+ cumulative_.reserve(num + 1);
+ cumulative_.push_back(0);
+ for (int i = 0; i < num; i++)
+ cumulative_.push_back(cumulative_.back() +
+ histogram[i]);
+ }
+ uint32_t bins() const { return cumulative_.size() - 1; }
+ uint64_t total() const { return cumulative_[cumulative_.size() - 1]; }
+ /* Cumulative frequency up to a (fractional) point in a bin. */
+ uint64_t cumulativeFreq(double bin) const;
+ /* Return the mean value between two (fractional) bins. */
+ double interBinMean(double binLo, double binHi) const;
+ /*
+ * Return the (fractional) bin of the point q (0 <= q <= 1) through the
+ * histogram. Optionally provide limits to help.
+ */
+ double quantile(double q, int first = -1, int last = -1) const;
+ /* Return the average histogram bin value between the two quantiles. */
+ double interQuantileMean(double qLo, double qHi) const;
+
+private:
+ std::vector<uint64_t> cumulative_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/lux_status.h b/src/ipa/rpi/controller/lux_status.h
new file mode 100644
index 00000000..5eb9faac
--- /dev/null
+++ b/src/ipa/rpi/controller/lux_status.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * lux_status.h - Lux control algorithm status
+ */
+#pragma once
+
+/*
+ * The "lux" algorithm looks at the (AGC) histogram statistics of the frame and
+ * estimates the current lux level of the scene. It does this by a simple ratio
+ * calculation comparing to a reference image that was taken in known conditions
+ * with known statistics and a properly measured lux level. There is a slight
+ * problem with aperture, in that it may be variable without the system knowing
+ * or being aware of it. In this case an external application may set a
+ * "current_aperture" value if it wishes, which would be used in place of the
+ * (presumably meaningless) value in the image metadata.
+ */
+
+struct LuxStatus {
+ double lux;
+ double aperture;
+};
diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build
new file mode 100644
index 00000000..32a4d31c
--- /dev/null
+++ b/src/ipa/rpi/controller/meson.build
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: CC0-1.0
+
+rpi_ipa_controller_sources = files([
+ 'algorithm.cpp',
+ 'controller.cpp',
+ 'device_status.cpp',
+ 'histogram.cpp',
+ 'pwl.cpp',
+ 'rpi/af.cpp',
+ 'rpi/agc.cpp',
+ 'rpi/agc_channel.cpp',
+ 'rpi/alsc.cpp',
+ 'rpi/awb.cpp',
+ 'rpi/black_level.cpp',
+ 'rpi/cac.cpp',
+ 'rpi/ccm.cpp',
+ 'rpi/contrast.cpp',
+ 'rpi/denoise.cpp',
+ 'rpi/dpc.cpp',
+ 'rpi/geq.cpp',
+ 'rpi/hdr.cpp',
+ 'rpi/lux.cpp',
+ 'rpi/noise.cpp',
+ 'rpi/saturation.cpp',
+ 'rpi/sdn.cpp',
+ 'rpi/sharpen.cpp',
+ 'rpi/tonemap.cpp',
+])
+
+rpi_ipa_controller_deps = [
+ libcamera_private,
+]
+
+rpi_ipa_controller_lib = static_library('rpi_ipa_controller', rpi_ipa_controller_sources,
+ dependencies : rpi_ipa_controller_deps)
diff --git a/src/ipa/rpi/controller/metadata.h b/src/ipa/rpi/controller/metadata.h
new file mode 100644
index 00000000..a232dcb1
--- /dev/null
+++ b/src/ipa/rpi/controller/metadata.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2021, Raspberry Pi Ltd
+ *
+ * metadata.h - general metadata class
+ */
+#pragma once
+
+/* A simple class for carrying arbitrary metadata, for example about an image. */
+
+#include <any>
+#include <map>
+#include <mutex>
+#include <string>
+
+#include <libcamera/base/thread_annotations.h>
+
+namespace RPiController {
+
+class LIBCAMERA_TSA_CAPABILITY("mutex") Metadata
+{
+public:
+ Metadata() = default;
+
+ Metadata(Metadata const &other)
+ {
+ std::scoped_lock otherLock(other.mutex_);
+ data_ = other.data_;
+ }
+
+ Metadata(Metadata &&other)
+ {
+ std::scoped_lock otherLock(other.mutex_);
+ data_ = std::move(other.data_);
+ other.data_.clear();
+ }
+
+ template<typename T>
+ void set(std::string const &tag, T const &value)
+ {
+ std::scoped_lock lock(mutex_);
+ data_[tag] = value;
+ }
+
+ template<typename T>
+ int get(std::string const &tag, T &value) const
+ {
+ std::scoped_lock lock(mutex_);
+ auto it = data_.find(tag);
+ if (it == data_.end())
+ return -1;
+ value = std::any_cast<T>(it->second);
+ return 0;
+ }
+
+ void clear()
+ {
+ std::scoped_lock lock(mutex_);
+ data_.clear();
+ }
+
+ Metadata &operator=(Metadata const &other)
+ {
+ std::scoped_lock lock(mutex_, other.mutex_);
+ data_ = other.data_;
+ return *this;
+ }
+
+ Metadata &operator=(Metadata &&other)
+ {
+ std::scoped_lock lock(mutex_, other.mutex_);
+ data_ = std::move(other.data_);
+ other.data_.clear();
+ return *this;
+ }
+
+ void merge(Metadata &other)
+ {
+ std::scoped_lock lock(mutex_, other.mutex_);
+ data_.merge(other.data_);
+ }
+
+ void mergeCopy(const Metadata &other)
+ {
+ std::scoped_lock lock(mutex_, other.mutex_);
+ /*
+ * If the metadata key exists, ignore this item and copy only
+ * unique key/value pairs.
+ */
+ data_.insert(other.data_.begin(), other.data_.end());
+ }
+
+ template<typename T>
+ T *getLocked(std::string const &tag)
+ {
+ /*
+ * This allows in-place access to the Metadata contents,
+ * for which you should be holding the lock.
+ */
+ auto it = data_.find(tag);
+ if (it == data_.end())
+ return nullptr;
+ return std::any_cast<T>(&it->second);
+ }
+
+ template<typename T>
+ void setLocked(std::string const &tag, T const &value)
+ {
+ /* Use this only if you're holding the lock yourself. */
+ data_[tag] = value;
+ }
+
+ /*
+ * Note: use of (lowercase) lock and unlock means you can create scoped
+ * locks with the standard lock classes.
+ * e.g. std::lock_guard<RPiController::Metadata> lock(metadata)
+ */
+ void lock() LIBCAMERA_TSA_ACQUIRE() { mutex_.lock(); }
+ auto try_lock() LIBCAMERA_TSA_ACQUIRE() { return mutex_.try_lock(); }
+ void unlock() LIBCAMERA_TSA_RELEASE() { mutex_.unlock(); }
+
+private:
+ mutable std::mutex mutex_;
+ std::map<std::string, std::any> data_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/noise_status.h b/src/ipa/rpi/controller/noise_status.h
new file mode 100644
index 00000000..da194f71
--- /dev/null
+++ b/src/ipa/rpi/controller/noise_status.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * noise_status.h - Noise control algorithm status
+ */
+#pragma once
+
+/* The "noise" algorithm stores an estimate of the noise profile for this image. */
+
+struct NoiseStatus {
+ double noiseConstant;
+ double noiseSlope;
+};
diff --git a/src/ipa/rpi/controller/pdaf_data.h b/src/ipa/rpi/controller/pdaf_data.h
new file mode 100644
index 00000000..470510f2
--- /dev/null
+++ b/src/ipa/rpi/controller/pdaf_data.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * pdaf_data.h - PDAF Metadata
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include "region_stats.h"
+
+namespace RPiController {
+
+struct PdafData {
+ /* Confidence, in arbitrary units */
+ uint16_t conf;
+ /* Phase error, in s16 Q4 format (S.11.4) */
+ int16_t phase;
+};
+
+using PdafRegions = RegionStats<PdafData>;
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp
new file mode 100644
index 00000000..70c2e24b
--- /dev/null
+++ b/src/ipa/rpi/controller/pwl.cpp
@@ -0,0 +1,269 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * pwl.cpp - piecewise linear functions
+ */
+
+#include <cassert>
+#include <cmath>
+#include <stdexcept>
+
+#include "pwl.h"
+
+using namespace RPiController;
+
+int Pwl::read(const libcamera::YamlObject &params)
+{
+ if (!params.size() || params.size() % 2)
+ return -EINVAL;
+
+ const auto &list = params.asList();
+
+ for (auto it = list.begin(); it != list.end(); it++) {
+ auto x = it->get<double>();
+ if (!x)
+ return -EINVAL;
+ if (it != list.begin() && *x <= points_.back().x)
+ return -EINVAL;
+
+ auto y = (++it)->get<double>();
+ if (!y)
+ return -EINVAL;
+
+ points_.push_back(Point(*x, *y));
+ }
+
+ return 0;
+}
+
+void Pwl::append(double x, double y, const double eps)
+{
+ if (points_.empty() || points_.back().x + eps < x)
+ points_.push_back(Point(x, y));
+}
+
+void Pwl::prepend(double x, double y, const double eps)
+{
+ if (points_.empty() || points_.front().x - eps > x)
+ points_.insert(points_.begin(), Point(x, y));
+}
+
+Pwl::Interval Pwl::domain() const
+{
+ return Interval(points_[0].x, points_[points_.size() - 1].x);
+}
+
+Pwl::Interval Pwl::range() const
+{
+ double lo = points_[0].y, hi = lo;
+ for (auto &p : points_)
+ lo = std::min(lo, p.y), hi = std::max(hi, p.y);
+ return Interval(lo, hi);
+}
+
+bool Pwl::empty() const
+{
+ return points_.empty();
+}
+
+double Pwl::eval(double x, int *spanPtr, bool updateSpan) const
+{
+ int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1);
+ if (spanPtr && updateSpan)
+ *spanPtr = span;
+ return points_[span].y +
+ (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /
+ (points_[span + 1].x - points_[span].x);
+}
+
+int Pwl::findSpan(double x, int span) const
+{
+ /*
+ * Pwls are generally small, so linear search may well be faster than
+ * binary, though could review this if large PWls start turning up.
+ */
+ int lastSpan = points_.size() - 2;
+ /*
+ * some algorithms may call us with span pointing directly at the last
+ * control point
+ */
+ span = std::max(0, std::min(lastSpan, span));
+ while (span < lastSpan && x >= points_[span + 1].x)
+ span++;
+ while (span && x < points_[span].x)
+ span--;
+ return span;
+}
+
+Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span,
+ const double eps) const
+{
+ assert(span >= -1);
+ bool prevOffEnd = false;
+ for (span = span + 1; span < (int)points_.size() - 1; span++) {
+ Point spanVec = points_[span + 1] - points_[span];
+ double t = ((xy - points_[span]) % spanVec) / spanVec.len2();
+ if (t < -eps) /* off the start of this span */
+ {
+ if (span == 0) {
+ perp = points_[span];
+ return PerpType::Start;
+ } else if (prevOffEnd) {
+ perp = points_[span];
+ return PerpType::Vertex;
+ }
+ } else if (t > 1 + eps) /* off the end of this span */
+ {
+ if (span == (int)points_.size() - 2) {
+ perp = points_[span + 1];
+ return PerpType::End;
+ }
+ prevOffEnd = true;
+ } else /* a true perpendicular */
+ {
+ perp = points_[span] + spanVec * t;
+ return PerpType::Perpendicular;
+ }
+ }
+ return PerpType::None;
+}
+
+Pwl Pwl::inverse(bool *trueInverse, const double eps) const
+{
+ bool appended = false, prepended = false, neither = false;
+ Pwl inverse;
+
+ for (Point const &p : points_) {
+ if (inverse.empty())
+ inverse.append(p.y, p.x, eps);
+ else if (std::abs(inverse.points_.back().x - p.y) <= eps ||
+ std::abs(inverse.points_.front().x - p.y) <= eps)
+ /* do nothing */;
+ else if (p.y > inverse.points_.back().x) {
+ inverse.append(p.y, p.x, eps);
+ appended = true;
+ } else if (p.y < inverse.points_.front().x) {
+ inverse.prepend(p.y, p.x, eps);
+ prepended = true;
+ } else
+ neither = true;
+ }
+
+ /*
+ * This is not a proper inverse if we found ourselves putting points
+ * onto both ends of the inverse, or if there were points that couldn't
+ * go on either.
+ */
+ if (trueInverse)
+ *trueInverse = !(neither || (appended && prepended));
+
+ return inverse;
+}
+
+Pwl Pwl::compose(Pwl const &other, const double eps) const
+{
+ double thisX = points_[0].x, thisY = points_[0].y;
+ int thisSpan = 0, otherSpan = other.findSpan(thisY, 0);
+ Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } });
+ while (thisSpan != (int)points_.size() - 1) {
+ double dx = points_[thisSpan + 1].x - points_[thisSpan].x,
+ dy = points_[thisSpan + 1].y - points_[thisSpan].y;
+ if (std::abs(dy) > eps &&
+ otherSpan + 1 < (int)other.points_.size() &&
+ points_[thisSpan + 1].y >=
+ other.points_[otherSpan + 1].x + eps) {
+ /*
+ * next control point in result will be where this
+ * function's y reaches the next span in other
+ */
+ thisX = points_[thisSpan].x +
+ (other.points_[otherSpan + 1].x -
+ points_[thisSpan].y) *
+ dx / dy;
+ thisY = other.points_[++otherSpan].x;
+ } else if (std::abs(dy) > eps && otherSpan > 0 &&
+ points_[thisSpan + 1].y <=
+ other.points_[otherSpan - 1].x - eps) {
+ /*
+ * next control point in result will be where this
+ * function's y reaches the previous span in other
+ */
+ thisX = points_[thisSpan].x +
+ (other.points_[otherSpan + 1].x -
+ points_[thisSpan].y) *
+ dx / dy;
+ thisY = other.points_[--otherSpan].x;
+ } else {
+ /* we stay in the same span in other */
+ thisSpan++;
+ thisX = points_[thisSpan].x,
+ thisY = points_[thisSpan].y;
+ }
+ result.append(thisX, other.eval(thisY, &otherSpan, false),
+ eps);
+ }
+ return result;
+}
+
+void Pwl::map(std::function<void(double x, double y)> f) const
+{
+ for (auto &pt : points_)
+ f(pt.x, pt.y);
+}
+
+void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,
+ std::function<void(double x, double y0, double y1)> f)
+{
+ int span0 = 0, span1 = 0;
+ double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);
+ f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));
+ while (span0 < (int)pwl0.points_.size() - 1 ||
+ span1 < (int)pwl1.points_.size() - 1) {
+ if (span0 == (int)pwl0.points_.size() - 1)
+ x = pwl1.points_[++span1].x;
+ else if (span1 == (int)pwl1.points_.size() - 1)
+ x = pwl0.points_[++span0].x;
+ else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)
+ x = pwl1.points_[++span1].x;
+ else
+ x = pwl0.points_[++span0].x;
+ f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));
+ }
+}
+
+Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,
+ std::function<double(double x, double y0, double y1)> f,
+ const double eps)
+{
+ Pwl result;
+ map2(pwl0, pwl1, [&](double x, double y0, double y1) {
+ result.append(x, f(x, y0, y1), eps);
+ });
+ return result;
+}
+
+void Pwl::matchDomain(Interval const &domain, bool clip, const double eps)
+{
+ int span = 0;
+ prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span),
+ eps);
+ span = points_.size() - 2;
+ append(domain.end, eval(clip ? points_.back().x : domain.end, &span),
+ eps);
+}
+
+Pwl &Pwl::operator*=(double d)
+{
+ for (auto &pt : points_)
+ pt.y *= d;
+ return *this;
+}
+
+void Pwl::debug(FILE *fp) const
+{
+ fprintf(fp, "Pwl {\n");
+ for (auto &p : points_)
+ fprintf(fp, "\t(%g, %g)\n", p.x, p.y);
+ fprintf(fp, "}\n");
+}
diff --git a/src/ipa/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h
new file mode 100644
index 00000000..aacf6039
--- /dev/null
+++ b/src/ipa/rpi/controller/pwl.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * pwl.h - piecewise linear functions interface
+ */
+#pragma once
+
+#include <functional>
+#include <math.h>
+#include <vector>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace RPiController {
+
+class Pwl
+{
+public:
+ struct Interval {
+ Interval(double _start, double _end)
+ : start(_start), end(_end)
+ {
+ }
+ double start, end;
+ bool contains(double value)
+ {
+ return value >= start && value <= end;
+ }
+ double clip(double value)
+ {
+ return value < start ? start
+ : (value > end ? end : value);
+ }
+ double len() const { return end - start; }
+ };
+ struct Point {
+ Point() : x(0), y(0) {}
+ Point(double _x, double _y)
+ : x(_x), y(_y) {}
+ double x, y;
+ Point operator-(Point const &p) const
+ {
+ return Point(x - p.x, y - p.y);
+ }
+ Point operator+(Point const &p) const
+ {
+ return Point(x + p.x, y + p.y);
+ }
+ double operator%(Point const &p) const
+ {
+ return x * p.x + y * p.y;
+ }
+ Point operator*(double f) const { return Point(x * f, y * f); }
+ Point operator/(double f) const { return Point(x / f, y / f); }
+ double len2() const { return x * x + y * y; }
+ double len() const { return sqrt(len2()); }
+ };
+ Pwl() {}
+ Pwl(std::vector<Point> const &points) : points_(points) {}
+ int read(const libcamera::YamlObject &params);
+ void append(double x, double y, const double eps = 1e-6);
+ void prepend(double x, double y, const double eps = 1e-6);
+ Interval domain() const;
+ Interval range() const;
+ bool empty() const;
+ /*
+ * Evaluate Pwl, optionally supplying an initial guess for the
+ * "span". The "span" may be optionally be updated. If you want to know
+ * the "span" value but don't have an initial guess you can set it to
+ * -1.
+ */
+ double eval(double x, int *spanPtr = nullptr,
+ bool updateSpan = true) const;
+ /*
+ * Find perpendicular closest to xy, starting from span+1 so you can
+ * call it repeatedly to check for multiple closest points (set span to
+ * -1 on the first call). Also returns "pseudo" perpendiculars; see
+ * PerpType enum.
+ */
+ enum class PerpType {
+ None, /* no perpendicular found */
+ Start, /* start of Pwl is closest point */
+ End, /* end of Pwl is closest point */
+ Vertex, /* vertex of Pwl is closest point */
+ Perpendicular /* true perpendicular found */
+ };
+ PerpType invert(Point const &xy, Point &perp, int &span,
+ const double eps = 1e-6) const;
+ /*
+ * Compute the inverse function. Indicate if it is a proper (true)
+ * inverse, or only a best effort (e.g. input was non-monotonic).
+ */
+ Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const;
+ /* Compose two Pwls together, doing "this" first and "other" after. */
+ Pwl compose(Pwl const &other, const double eps = 1e-6) const;
+ /* Apply function to (x,y) values at every control point. */
+ void map(std::function<void(double x, double y)> f) const;
+ /*
+ * Apply function to (x, y0, y1) values wherever either Pwl has a
+ * control point.
+ */
+ static void map2(Pwl const &pwl0, Pwl const &pwl1,
+ std::function<void(double x, double y0, double y1)> f);
+ /*
+ * Combine two Pwls, meaning we create a new Pwl where the y values are
+ * given by running f wherever either has a knot.
+ */
+ static Pwl
+ combine(Pwl const &pwl0, Pwl const &pwl1,
+ std::function<double(double x, double y0, double y1)> f,
+ const double eps = 1e-6);
+ /*
+ * Make "this" match (at least) the given domain. Any extension my be
+ * clipped or linear.
+ */
+ void matchDomain(Interval const &domain, bool clip = true,
+ const double eps = 1e-6);
+ Pwl &operator*=(double d);
+ void debug(FILE *fp = stdout) const;
+
+private:
+ int findSpan(double x, int span) const;
+ std::vector<Point> points_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/region_stats.h b/src/ipa/rpi/controller/region_stats.h
new file mode 100644
index 00000000..a8860dc8
--- /dev/null
+++ b/src/ipa/rpi/controller/region_stats.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * region_stats.h - Raspberry Pi region based statistics container
+ */
+#pragma once
+
+#include <array>
+#include <stdint.h>
+#include <vector>
+
+#include <libcamera/geometry.h>
+
+namespace RPiController {
+
+template<typename T>
+class RegionStats
+{
+public:
+ struct Region {
+ T val;
+ uint32_t counted;
+ uint32_t uncounted;
+ };
+
+ RegionStats()
+ : size_({}), numFloating_(0), default_({})
+ {
+ }
+
+ void init(const libcamera::Size &size, unsigned int numFloating = 0)
+ {
+ size_ = size;
+ numFloating_ = numFloating;
+ regions_.clear();
+ regions_.resize(size_.width * size_.height + numFloating_);
+ }
+
+ void init(unsigned int num)
+ {
+ size_ = libcamera::Size(num, 1);
+ numFloating_ = 0;
+ regions_.clear();
+ regions_.resize(num);
+ }
+
+ unsigned int numRegions() const
+ {
+ return size_.width * size_.height;
+ }
+
+ unsigned int numFloatingRegions() const
+ {
+ return numFloating_;
+ }
+
+ libcamera::Size size() const
+ {
+ return size_;
+ }
+
+ void set(unsigned int index, const Region &region)
+ {
+ if (index >= numRegions())
+ return;
+ set_(index, region);
+ }
+
+ void set(const libcamera::Point &pos, const Region &region)
+ {
+ set(pos.y * size_.width + pos.x, region);
+ }
+
+ void setFloating(unsigned int index, const Region &region)
+ {
+ if (index >= numFloatingRegions())
+ return;
+ set(numRegions() + index, region);
+ }
+
+ const Region &get(unsigned int index) const
+ {
+ if (index >= numRegions())
+ return default_;
+ return get_(index);
+ }
+
+ const Region &get(const libcamera::Point &pos) const
+ {
+ return get(pos.y * size_.width + pos.x);
+ }
+
+ const Region &getFloating(unsigned int index) const
+ {
+ if (index >= numFloatingRegions())
+ return default_;
+ return get_(numRegions() + index);
+ }
+
+ typename std::vector<Region>::iterator begin() { return regions_.begin(); }
+ typename std::vector<Region>::iterator end() { return regions_.end(); }
+ typename std::vector<Region>::const_iterator begin() const { return regions_.begin(); }
+ typename std::vector<Region>::const_iterator end() const { return regions_.end(); }
+
+private:
+ void set_(unsigned int index, const Region &region)
+ {
+ regions_[index] = region;
+ }
+
+ const Region &get_(unsigned int index) const
+ {
+ return regions_[index];
+ }
+
+ libcamera::Size size_;
+ unsigned int numFloating_;
+ std::vector<Region> regions_;
+ Region default_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp
new file mode 100644
index 00000000..ed0c8a94
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/af.cpp
@@ -0,0 +1,797 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022-2023, Raspberry Pi Ltd
+ *
+ * af.cpp - Autofocus control algorithm
+ */
+
+#include "af.h"
+
+#include <iomanip>
+#include <math.h>
+#include <stdlib.h>
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiAf)
+
+#define NAME "rpi.af"
+
+/*
+ * Default values for parameters. All may be overridden in the tuning file.
+ * Many of these values are sensor- or module-dependent; the defaults here
+ * assume IMX708 in a Raspberry Pi V3 camera with the standard lens.
+ *
+ * Here all focus values are in dioptres (1/m). They are converted to hardware
+ * units when written to status.lensSetting or returned from setLensPosition().
+ *
+ * Gain and delay values are relative to the update rate, since much (not all)
+ * of the delay is in the sensor and (for CDAF) ISP, not the lens mechanism;
+ * but note that algorithms are updated at no more than 30 Hz.
+ */
+
+Af::RangeDependentParams::RangeDependentParams()
+ : focusMin(0.0),
+ focusMax(12.0),
+ focusDefault(1.0)
+{
+}
+
+Af::SpeedDependentParams::SpeedDependentParams()
+ : stepCoarse(1.0),
+ stepFine(0.25),
+ contrastRatio(0.75),
+ pdafGain(-0.02),
+ pdafSquelch(0.125),
+ maxSlew(2.0),
+ pdafFrames(20),
+ dropoutFrames(6),
+ stepFrames(4)
+{
+}
+
+Af::CfgParams::CfgParams()
+ : confEpsilon(8),
+ confThresh(16),
+ confClip(512),
+ skipFrames(5),
+ map()
+{
+}
+
+template<typename T>
+static void readNumber(T &dest, const libcamera::YamlObject &params, char const *name)
+{
+ auto value = params[name].get<T>();
+ if (value)
+ dest = *value;
+ else
+ LOG(RPiAf, Warning) << "Missing parameter \"" << name << "\"";
+}
+
+void Af::RangeDependentParams::read(const libcamera::YamlObject &params)
+{
+
+ readNumber<double>(focusMin, params, "min");
+ readNumber<double>(focusMax, params, "max");
+ readNumber<double>(focusDefault, params, "default");
+}
+
+void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
+{
+ readNumber<double>(stepCoarse, params, "step_coarse");
+ readNumber<double>(stepFine, params, "step_fine");
+ readNumber<double>(contrastRatio, params, "contrast_ratio");
+ readNumber<double>(pdafGain, params, "pdaf_gain");
+ readNumber<double>(pdafSquelch, params, "pdaf_squelch");
+ readNumber<double>(maxSlew, params, "max_slew");
+ readNumber<uint32_t>(pdafFrames, params, "pdaf_frames");
+ readNumber<uint32_t>(dropoutFrames, params, "dropout_frames");
+ readNumber<uint32_t>(stepFrames, params, "step_frames");
+}
+
+int Af::CfgParams::read(const libcamera::YamlObject &params)
+{
+ if (params.contains("ranges")) {
+ auto &rr = params["ranges"];
+
+ if (rr.contains("normal"))
+ ranges[AfRangeNormal].read(rr["normal"]);
+ else
+ LOG(RPiAf, Warning) << "Missing range \"normal\"";
+
+ ranges[AfRangeMacro] = ranges[AfRangeNormal];
+ if (rr.contains("macro"))
+ ranges[AfRangeMacro].read(rr["macro"]);
+
+ ranges[AfRangeFull].focusMin = std::min(ranges[AfRangeNormal].focusMin,
+ ranges[AfRangeMacro].focusMin);
+ ranges[AfRangeFull].focusMax = std::max(ranges[AfRangeNormal].focusMax,
+ ranges[AfRangeMacro].focusMax);
+ ranges[AfRangeFull].focusDefault = ranges[AfRangeNormal].focusDefault;
+ if (rr.contains("full"))
+ ranges[AfRangeFull].read(rr["full"]);
+ } else
+ LOG(RPiAf, Warning) << "No ranges defined";
+
+ if (params.contains("speeds")) {
+ auto &ss = params["speeds"];
+
+ if (ss.contains("normal"))
+ speeds[AfSpeedNormal].read(ss["normal"]);
+ else
+ LOG(RPiAf, Warning) << "Missing speed \"normal\"";
+
+ speeds[AfSpeedFast] = speeds[AfSpeedNormal];
+ if (ss.contains("fast"))
+ speeds[AfSpeedFast].read(ss["fast"]);
+ } else
+ LOG(RPiAf, Warning) << "No speeds defined";
+
+ readNumber<uint32_t>(confEpsilon, params, "conf_epsilon");
+ readNumber<uint32_t>(confThresh, params, "conf_thresh");
+ readNumber<uint32_t>(confClip, params, "conf_clip");
+ readNumber<uint32_t>(skipFrames, params, "skip_frames");
+
+ if (params.contains("map"))
+ map.read(params["map"]);
+ else
+ LOG(RPiAf, Warning) << "No map defined";
+
+ return 0;
+}
+
+void Af::CfgParams::initialise()
+{
+ if (map.empty()) {
+ /* Default mapping from dioptres to hardware setting */
+ static constexpr double DefaultMapX0 = 0.0;
+ static constexpr double DefaultMapY0 = 445.0;
+ static constexpr double DefaultMapX1 = 15.0;
+ static constexpr double DefaultMapY1 = 925.0;
+
+ map.append(DefaultMapX0, DefaultMapY0);
+ map.append(DefaultMapX1, DefaultMapY1);
+ }
+}
+
+/* Af Algorithm class */
+
+static constexpr unsigned MaxWindows = 10;
+
+Af::Af(Controller *controller)
+ : AfAlgorithm(controller),
+ cfg_(),
+ range_(AfRangeNormal),
+ speed_(AfSpeedNormal),
+ mode_(AfAlgorithm::AfModeManual),
+ pauseFlag_(false),
+ statsRegion_(0, 0, 0, 0),
+ windows_(),
+ useWindows_(false),
+ phaseWeights_(),
+ contrastWeights_(),
+ scanState_(ScanState::Idle),
+ initted_(false),
+ ftarget_(-1.0),
+ fsmooth_(-1.0),
+ prevContrast_(0.0),
+ skipCount_(0),
+ stepCount_(0),
+ dropCount_(0),
+ scanMaxContrast_(0.0),
+ scanMinContrast_(1.0e9),
+ scanData_(),
+ reportState_(AfState::Idle)
+{
+ /*
+ * Reserve space for data, to reduce memory fragmentation. It's too early
+ * to query the size of the PDAF (from camera) and Contrast (from ISP)
+ * statistics, but these are plausible upper bounds.
+ */
+ phaseWeights_.w.reserve(16 * 12);
+ contrastWeights_.w.reserve(getHardwareConfig().focusRegions.width *
+ getHardwareConfig().focusRegions.height);
+ scanData_.reserve(32);
+}
+
+Af::~Af()
+{
+}
+
+char const *Af::name() const
+{
+ return NAME;
+}
+
+int Af::read(const libcamera::YamlObject &params)
+{
+ return cfg_.read(params);
+}
+
+void Af::initialise()
+{
+ cfg_.initialise();
+}
+
+void Af::switchMode(CameraMode const &cameraMode, [[maybe_unused]] Metadata *metadata)
+{
+ (void)metadata;
+
+ /* Assume that PDAF and Focus stats grids cover the visible area */
+ statsRegion_.x = (int)cameraMode.cropX;
+ statsRegion_.y = (int)cameraMode.cropY;
+ statsRegion_.width = (unsigned)(cameraMode.width * cameraMode.scaleX);
+ statsRegion_.height = (unsigned)(cameraMode.height * cameraMode.scaleY);
+ LOG(RPiAf, Debug) << "switchMode: statsRegion: "
+ << statsRegion_.x << ','
+ << statsRegion_.y << ','
+ << statsRegion_.width << ','
+ << statsRegion_.height;
+ invalidateWeights();
+
+ if (scanState_ >= ScanState::Coarse && scanState_ < ScanState::Settle) {
+ /*
+ * If a scan was in progress, re-start it, as CDAF statistics
+ * may have changed. Though if the application is just about
+ * to take a still picture, this will not help...
+ */
+ startProgrammedScan();
+ }
+ skipCount_ = cfg_.skipFrames;
+}
+
+void Af::computeWeights(RegionWeights *wgts, unsigned rows, unsigned cols)
+{
+ wgts->rows = rows;
+ wgts->cols = cols;
+ wgts->sum = 0;
+ wgts->w.resize(rows * cols);
+ std::fill(wgts->w.begin(), wgts->w.end(), 0);
+
+ if (rows > 0 && cols > 0 && useWindows_ &&
+ statsRegion_.height >= rows && statsRegion_.width >= cols) {
+ /*
+ * Here we just merge all of the given windows, weighted by area.
+ * \todo Perhaps a better approach might be to find the phase in each
+ * window and choose either the closest or the highest-confidence one?
+ * Ensure weights sum to less than (1<<16). 46080 is a "round number"
+ * below 65536, for better rounding when window size is a simple
+ * fraction of image dimensions.
+ */
+ const unsigned maxCellWeight = 46080u / (MaxWindows * rows * cols);
+ const unsigned cellH = statsRegion_.height / rows;
+ const unsigned cellW = statsRegion_.width / cols;
+ const unsigned cellA = cellH * cellW;
+
+ for (auto &w : windows_) {
+ for (unsigned r = 0; r < rows; ++r) {
+ int y0 = std::max(statsRegion_.y + (int)(cellH * r), w.y);
+ int y1 = std::min(statsRegion_.y + (int)(cellH * (r + 1)),
+ w.y + (int)(w.height));
+ if (y0 >= y1)
+ continue;
+ y1 -= y0;
+ for (unsigned c = 0; c < cols; ++c) {
+ int x0 = std::max(statsRegion_.x + (int)(cellW * c), w.x);
+ int x1 = std::min(statsRegion_.x + (int)(cellW * (c + 1)),
+ w.x + (int)(w.width));
+ if (x0 >= x1)
+ continue;
+ unsigned a = y1 * (x1 - x0);
+ a = (maxCellWeight * a + cellA - 1) / cellA;
+ wgts->w[r * cols + c] += a;
+ wgts->sum += a;
+ }
+ }
+ }
+ }
+
+ if (wgts->sum == 0) {
+ /* Default AF window is the middle 1/2 width of the middle 1/3 height */
+ for (unsigned r = rows / 3; r < rows - rows / 3; ++r) {
+ for (unsigned c = cols / 4; c < cols - cols / 4; ++c) {
+ wgts->w[r * cols + c] = 1;
+ wgts->sum += 1;
+ }
+ }
+ }
+}
+
+void Af::invalidateWeights()
+{
+ phaseWeights_.sum = 0;
+ contrastWeights_.sum = 0;
+}
+
+bool Af::getPhase(PdafRegions const &regions, double &phase, double &conf)
+{
+ libcamera::Size size = regions.size();
+ if (size.height != phaseWeights_.rows || size.width != phaseWeights_.cols ||
+ phaseWeights_.sum == 0) {
+ LOG(RPiAf, Debug) << "Recompute Phase weights " << size.width << 'x' << size.height;
+ computeWeights(&phaseWeights_, size.height, size.width);
+ }
+
+ uint32_t sumWc = 0;
+ int64_t sumWcp = 0;
+ for (unsigned i = 0; i < regions.numRegions(); ++i) {
+ unsigned w = phaseWeights_.w[i];
+ if (w) {
+ const PdafData &data = regions.get(i).val;
+ unsigned c = data.conf;
+ if (c >= cfg_.confThresh) {
+ if (c > cfg_.confClip)
+ c = cfg_.confClip;
+ c -= (cfg_.confThresh >> 2);
+ sumWc += w * c;
+ c -= (cfg_.confThresh >> 2);
+ sumWcp += (int64_t)(w * c) * (int64_t)data.phase;
+ }
+ }
+ }
+
+ if (0 < phaseWeights_.sum && phaseWeights_.sum <= sumWc) {
+ phase = (double)sumWcp / (double)sumWc;
+ conf = (double)sumWc / (double)phaseWeights_.sum;
+ return true;
+ } else {
+ phase = 0.0;
+ conf = 0.0;
+ return false;
+ }
+}
+
+double Af::getContrast(const FocusRegions &focusStats)
+{
+ libcamera::Size size = focusStats.size();
+ if (size.height != contrastWeights_.rows ||
+ size.width != contrastWeights_.cols || contrastWeights_.sum == 0) {
+ LOG(RPiAf, Debug) << "Recompute Contrast weights "
+ << size.width << 'x' << size.height;
+ computeWeights(&contrastWeights_, size.height, size.width);
+ }
+
+ uint64_t sumWc = 0;
+ for (unsigned i = 0; i < focusStats.numRegions(); ++i)
+ sumWc += contrastWeights_.w[i] * focusStats.get(i).val;
+
+ return (contrastWeights_.sum > 0) ? ((double)sumWc / (double)contrastWeights_.sum) : 0.0;
+}
+
+void Af::doPDAF(double phase, double conf)
+{
+ /* Apply loop gain */
+ phase *= cfg_.speeds[speed_].pdafGain;
+
+ if (mode_ == AfModeContinuous) {
+ /*
+ * PDAF in Continuous mode. Scale down lens movement when
+ * delta is small or confidence is low, to suppress wobble.
+ */
+ phase *= conf / (conf + cfg_.confEpsilon);
+ if (std::abs(phase) < cfg_.speeds[speed_].pdafSquelch) {
+ double a = phase / cfg_.speeds[speed_].pdafSquelch;
+ phase *= a * a;
+ }
+ } else {
+ /*
+ * PDAF in triggered-auto mode. Allow early termination when
+ * phase delta is small; scale down lens movements towards
+ * the end of the sequence, to ensure a stable image.
+ */
+ if (stepCount_ >= cfg_.speeds[speed_].stepFrames) {
+ if (std::abs(phase) < cfg_.speeds[speed_].pdafSquelch)
+ stepCount_ = cfg_.speeds[speed_].stepFrames;
+ } else
+ phase *= stepCount_ / cfg_.speeds[speed_].stepFrames;
+ }
+
+ /* Apply slew rate limit. Report failure if out of bounds. */
+ if (phase < -cfg_.speeds[speed_].maxSlew) {
+ phase = -cfg_.speeds[speed_].maxSlew;
+ reportState_ = (ftarget_ <= cfg_.ranges[range_].focusMin) ? AfState::Failed
+ : AfState::Scanning;
+ } else if (phase > cfg_.speeds[speed_].maxSlew) {
+ phase = cfg_.speeds[speed_].maxSlew;
+ reportState_ = (ftarget_ >= cfg_.ranges[range_].focusMax) ? AfState::Failed
+ : AfState::Scanning;
+ } else
+ reportState_ = AfState::Focused;
+
+ ftarget_ = fsmooth_ + phase;
+}
+
+bool Af::earlyTerminationByPhase(double phase)
+{
+ if (scanData_.size() > 0 &&
+ scanData_[scanData_.size() - 1].conf >= cfg_.confEpsilon) {
+ double oldFocus = scanData_[scanData_.size() - 1].focus;
+ double oldPhase = scanData_[scanData_.size() - 1].phase;
+
+ /*
+ * Check that the gradient is finite and has the expected sign;
+ * Interpolate/extrapolate the lens position for zero phase.
+ * Check that the extrapolation is well-conditioned.
+ */
+ if ((ftarget_ - oldFocus) * (phase - oldPhase) > 0.0) {
+ double param = phase / (phase - oldPhase);
+ if (-3.0 <= param && param <= 3.5) {
+ ftarget_ += param * (oldFocus - ftarget_);
+ LOG(RPiAf, Debug) << "ETBP: param=" << param;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+double Af::findPeak(unsigned i) const
+{
+ double f = scanData_[i].focus;
+
+ if (i > 0 && i + 1 < scanData_.size()) {
+ double dropLo = scanData_[i].contrast - scanData_[i - 1].contrast;
+ double dropHi = scanData_[i].contrast - scanData_[i + 1].contrast;
+ if (0.0 <= dropLo && dropLo < dropHi) {
+ double param = 0.3125 * (1.0 - dropLo / dropHi) * (1.6 - dropLo / dropHi);
+ f += param * (scanData_[i - 1].focus - f);
+ } else if (0.0 <= dropHi && dropHi < dropLo) {
+ double param = 0.3125 * (1.0 - dropHi / dropLo) * (1.6 - dropHi / dropLo);
+ f += param * (scanData_[i + 1].focus - f);
+ }
+ }
+
+ LOG(RPiAf, Debug) << "FindPeak: " << f;
+ return f;
+}
+
+void Af::doScan(double contrast, double phase, double conf)
+{
+ /* Record lens position, contrast and phase values for the current scan */
+ if (scanData_.empty() || contrast > scanMaxContrast_) {
+ scanMaxContrast_ = contrast;
+ scanMaxIndex_ = scanData_.size();
+ }
+ if (contrast < scanMinContrast_)
+ scanMinContrast_ = contrast;
+ scanData_.emplace_back(ScanRecord{ ftarget_, contrast, phase, conf });
+
+ if (scanState_ == ScanState::Coarse) {
+ if (ftarget_ >= cfg_.ranges[range_].focusMax ||
+ contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) {
+ /*
+ * Finished course scan, or termination based on contrast.
+ * Jump to just after max contrast and start fine scan.
+ */
+ ftarget_ = std::min(ftarget_, findPeak(scanMaxIndex_) +
+ 2.0 * cfg_.speeds[speed_].stepFine);
+ scanState_ = ScanState::Fine;
+ scanData_.clear();
+ } else
+ ftarget_ += cfg_.speeds[speed_].stepCoarse;
+ } else { /* ScanState::Fine */
+ if (ftarget_ <= cfg_.ranges[range_].focusMin || scanData_.size() >= 5 ||
+ contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) {
+ /*
+ * Finished fine scan, or termination based on contrast.
+ * Use quadratic peak-finding to find best contrast position.
+ */
+ ftarget_ = findPeak(scanMaxIndex_);
+ scanState_ = ScanState::Settle;
+ } else
+ ftarget_ -= cfg_.speeds[speed_].stepFine;
+ }
+
+ stepCount_ = (ftarget_ == fsmooth_) ? 0 : cfg_.speeds[speed_].stepFrames;
+}
+
+void Af::doAF(double contrast, double phase, double conf)
+{
+ /* Skip frames at startup and after sensor mode change */
+ if (skipCount_ > 0) {
+ LOG(RPiAf, Debug) << "SKIP";
+ skipCount_--;
+ return;
+ }
+
+ if (scanState_ == ScanState::Pdaf) {
+ /*
+ * Use PDAF closed-loop control whenever available, in both CAF
+ * mode and (for a limited number of iterations) when triggered.
+ * If PDAF fails (due to poor contrast, noise or large defocus),
+ * fall back to a CDAF-based scan. To avoid "nuisance" scans,
+ * scan only after a number of frames with low PDAF confidence.
+ */
+ if (conf > (dropCount_ ? 1.0 : 0.25) * cfg_.confEpsilon) {
+ doPDAF(phase, conf);
+ if (stepCount_ > 0)
+ stepCount_--;
+ else if (mode_ != AfModeContinuous)
+ scanState_ = ScanState::Idle;
+ dropCount_ = 0;
+ } else if (++dropCount_ == cfg_.speeds[speed_].dropoutFrames)
+ startProgrammedScan();
+ } else if (scanState_ >= ScanState::Coarse && fsmooth_ == ftarget_) {
+ /*
+ * Scanning sequence. This means PDAF has become unavailable.
+ * Allow a delay between steps for CDAF FoM statistics to be
+ * updated, and a "settling time" at the end of the sequence.
+ * [A coarse or fine scan can be abandoned if two PDAF samples
+ * allow direct interpolation of the zero-phase lens position.]
+ */
+ if (stepCount_ > 0)
+ stepCount_--;
+ else if (scanState_ == ScanState::Settle) {
+ if (prevContrast_ >= cfg_.speeds[speed_].contrastRatio * scanMaxContrast_ &&
+ scanMinContrast_ <= cfg_.speeds[speed_].contrastRatio * scanMaxContrast_)
+ reportState_ = AfState::Focused;
+ else
+ reportState_ = AfState::Failed;
+ if (mode_ == AfModeContinuous && !pauseFlag_ &&
+ cfg_.speeds[speed_].dropoutFrames > 0)
+ scanState_ = ScanState::Pdaf;
+ else
+ scanState_ = ScanState::Idle;
+ scanData_.clear();
+ } else if (conf >= cfg_.confEpsilon && earlyTerminationByPhase(phase)) {
+ scanState_ = ScanState::Settle;
+ stepCount_ = (mode_ == AfModeContinuous) ? 0
+ : cfg_.speeds[speed_].stepFrames;
+ } else
+ doScan(contrast, phase, conf);
+ }
+}
+
+void Af::updateLensPosition()
+{
+ if (scanState_ >= ScanState::Pdaf) {
+ ftarget_ = std::clamp(ftarget_,
+ cfg_.ranges[range_].focusMin,
+ cfg_.ranges[range_].focusMax);
+ }
+
+ if (initted_) {
+ /* from a known lens position: apply slew rate limit */
+ fsmooth_ = std::clamp(ftarget_,
+ fsmooth_ - cfg_.speeds[speed_].maxSlew,
+ fsmooth_ + cfg_.speeds[speed_].maxSlew);
+ } else {
+ /* from an unknown position: go straight to target, but add delay */
+ fsmooth_ = ftarget_;
+ initted_ = true;
+ skipCount_ = cfg_.skipFrames;
+ }
+}
+
+void Af::startAF()
+{
+ /* Use PDAF if the tuning file allows it; else CDAF. */
+ if (cfg_.speeds[speed_].dropoutFrames > 0 &&
+ (mode_ == AfModeContinuous || cfg_.speeds[speed_].pdafFrames > 0)) {
+ if (!initted_) {
+ ftarget_ = cfg_.ranges[range_].focusDefault;
+ updateLensPosition();
+ }
+ stepCount_ = (mode_ == AfModeContinuous) ? 0 : cfg_.speeds[speed_].pdafFrames;
+ scanState_ = ScanState::Pdaf;
+ scanData_.clear();
+ dropCount_ = 0;
+ reportState_ = AfState::Scanning;
+ } else
+ startProgrammedScan();
+}
+
+void Af::startProgrammedScan()
+{
+ ftarget_ = cfg_.ranges[range_].focusMin;
+ updateLensPosition();
+ scanState_ = ScanState::Coarse;
+ scanMaxContrast_ = 0.0;
+ scanMinContrast_ = 1.0e9;
+ scanMaxIndex_ = 0;
+ scanData_.clear();
+ stepCount_ = cfg_.speeds[speed_].stepFrames;
+ reportState_ = AfState::Scanning;
+}
+
+void Af::goIdle()
+{
+ scanState_ = ScanState::Idle;
+ reportState_ = AfState::Idle;
+ scanData_.clear();
+}
+
+/*
+ * PDAF phase data are available in prepare(), but CDAF statistics are not
+ * available until process(). We are gambling on the availability of PDAF.
+ * To expedite feedback control using PDAF, issue the V4L2 lens control from
+ * prepare(). Conversely, during scans, we must allow an extra frame delay
+ * between steps, to retrieve CDAF statistics from the previous process()
+ * so we can terminate the scan early without having to change our minds.
+ */
+
+void Af::prepare(Metadata *imageMetadata)
+{
+ /* Initialize for triggered scan or start of CAF mode */
+ if (scanState_ == ScanState::Trigger)
+ startAF();
+
+ if (initted_) {
+ /* Get PDAF from the embedded metadata, and run AF algorithm core */
+ PdafRegions regions;
+ double phase = 0.0, conf = 0.0;
+ double oldFt = ftarget_;
+ double oldFs = fsmooth_;
+ ScanState oldSs = scanState_;
+ uint32_t oldSt = stepCount_;
+ if (imageMetadata->get("pdaf.regions", regions) == 0)
+ getPhase(regions, phase, conf);
+ doAF(prevContrast_, phase, conf);
+ updateLensPosition();
+ LOG(RPiAf, Debug) << std::fixed << std::setprecision(2)
+ << static_cast<unsigned int>(reportState_)
+ << " sst" << static_cast<unsigned int>(oldSs)
+ << "->" << static_cast<unsigned int>(scanState_)
+ << " stp" << oldSt << "->" << stepCount_
+ << " ft" << oldFt << "->" << ftarget_
+ << " fs" << oldFs << "->" << fsmooth_
+ << " cont=" << (int)prevContrast_
+ << " phase=" << (int)phase << " conf=" << (int)conf;
+ }
+
+ /* Report status and produce new lens setting */
+ AfStatus status;
+ if (pauseFlag_)
+ status.pauseState = (scanState_ == ScanState::Idle) ? AfPauseState::Paused
+ : AfPauseState::Pausing;
+ else
+ status.pauseState = AfPauseState::Running;
+
+ if (mode_ == AfModeAuto && scanState_ != ScanState::Idle)
+ status.state = AfState::Scanning;
+ else
+ status.state = reportState_;
+ status.lensSetting = initted_ ? std::optional<int>(cfg_.map.eval(fsmooth_))
+ : std::nullopt;
+ imageMetadata->set("af.status", status);
+}
+
+void Af::process(StatisticsPtr &stats, [[maybe_unused]] Metadata *imageMetadata)
+{
+ (void)imageMetadata;
+ prevContrast_ = getContrast(stats->focusRegions);
+}
+
+/* Controls */
+
+void Af::setRange(AfRange r)
+{
+ LOG(RPiAf, Debug) << "setRange: " << (unsigned)r;
+ if (r < AfAlgorithm::AfRangeMax)
+ range_ = r;
+}
+
+void Af::setSpeed(AfSpeed s)
+{
+ LOG(RPiAf, Debug) << "setSpeed: " << (unsigned)s;
+ if (s < AfAlgorithm::AfSpeedMax) {
+ if (scanState_ == ScanState::Pdaf &&
+ cfg_.speeds[s].pdafFrames > cfg_.speeds[speed_].pdafFrames)
+ stepCount_ += cfg_.speeds[s].pdafFrames - cfg_.speeds[speed_].pdafFrames;
+ speed_ = s;
+ }
+}
+
+void Af::setMetering(bool mode)
+{
+ if (useWindows_ != mode) {
+ useWindows_ = mode;
+ invalidateWeights();
+ }
+}
+
+void Af::setWindows(libcamera::Span<libcamera::Rectangle const> const &wins)
+{
+ windows_.clear();
+ for (auto &w : wins) {
+ LOG(RPiAf, Debug) << "Window: "
+ << w.x << ", "
+ << w.y << ", "
+ << w.width << ", "
+ << w.height;
+ windows_.push_back(w);
+ if (windows_.size() >= MaxWindows)
+ break;
+ }
+
+ if (useWindows_)
+ invalidateWeights();
+}
+
+bool Af::setLensPosition(double dioptres, int *hwpos)
+{
+ bool changed = false;
+
+ if (mode_ == AfModeManual) {
+ LOG(RPiAf, Debug) << "setLensPosition: " << dioptres;
+ ftarget_ = cfg_.map.domain().clip(dioptres);
+ changed = !(initted_ && fsmooth_ == ftarget_);
+ updateLensPosition();
+ }
+
+ if (hwpos)
+ *hwpos = cfg_.map.eval(fsmooth_);
+
+ return changed;
+}
+
+std::optional<double> Af::getLensPosition() const
+{
+ /*
+ * \todo We ought to perform some precise timing here to determine
+ * the current lens position.
+ */
+ return initted_ ? std::optional<double>(fsmooth_) : std::nullopt;
+}
+
+void Af::cancelScan()
+{
+ LOG(RPiAf, Debug) << "cancelScan";
+ if (mode_ == AfModeAuto)
+ goIdle();
+}
+
+void Af::triggerScan()
+{
+ LOG(RPiAf, Debug) << "triggerScan";
+ if (mode_ == AfModeAuto && scanState_ == ScanState::Idle)
+ scanState_ = ScanState::Trigger;
+}
+
+void Af::setMode(AfAlgorithm::AfMode mode)
+{
+ LOG(RPiAf, Debug) << "setMode: " << (unsigned)mode;
+ if (mode_ != mode) {
+ mode_ = mode;
+ pauseFlag_ = false;
+ if (mode == AfModeContinuous)
+ scanState_ = ScanState::Trigger;
+ else if (mode != AfModeAuto || scanState_ < ScanState::Coarse)
+ goIdle();
+ }
+}
+
+AfAlgorithm::AfMode Af::getMode() const
+{
+ return mode_;
+}
+
+void Af::pause(AfAlgorithm::AfPause pause)
+{
+ LOG(RPiAf, Debug) << "pause: " << (unsigned)pause;
+ if (mode_ == AfModeContinuous) {
+ if (pause == AfPauseResume && pauseFlag_) {
+ pauseFlag_ = false;
+ if (scanState_ < ScanState::Coarse)
+ scanState_ = ScanState::Trigger;
+ } else if (pause != AfPauseResume && !pauseFlag_) {
+ pauseFlag_ = true;
+ if (pause == AfPauseImmediate || scanState_ < ScanState::Coarse)
+ goIdle();
+ }
+ }
+}
+
+// Register algorithm with the system.
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Af(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h
new file mode 100644
index 00000000..6d2bae67
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/af.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022-2023, Raspberry Pi Ltd
+ *
+ * af.h - Autofocus control algorithm
+ */
+#pragma once
+
+#include "../af_algorithm.h"
+#include "../af_status.h"
+#include "../pdaf_data.h"
+#include "../pwl.h"
+
+/*
+ * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.
+ *
+ * Whenever PDAF is available, it is used in a continuous feedback loop.
+ * When triggered in auto mode, we simply enable AF for a limited number
+ * of frames (it may terminate early if the delta becomes small enough).
+ *
+ * When PDAF confidence is low (due e.g. to low contrast or extreme defocus)
+ * or PDAF data are absent, fall back to CDAF with a programmed scan pattern.
+ * A coarse and fine scan are performed, using ISP's CDAF focus FoM to
+ * estimate the lens position with peak contrast. This is slower due to
+ * extra latency in the ISP, and requires a settling time between steps.
+ *
+ * Some hysteresis is applied to the switch between PDAF and CDAF, to avoid
+ * "nuisance" scans. During each interval where PDAF is not working, only
+ * ONE scan will be performed; CAF cannot track objects using CDAF alone.
+ *
+ */
+
+namespace RPiController {
+
+class Af : public AfAlgorithm
+{
+public:
+ Af(Controller *controller = NULL);
+ ~Af();
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void initialise() override;
+
+ /* IPA calls */
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+
+ /* controls */
+ void setRange(AfRange range) override;
+ void setSpeed(AfSpeed speed) override;
+ void setMetering(bool use_windows) override;
+ void setWindows(libcamera::Span<libcamera::Rectangle const> const &wins) override;
+ void setMode(AfMode mode) override;
+ AfMode getMode() const override;
+ bool setLensPosition(double dioptres, int32_t *hwpos) override;
+ std::optional<double> getLensPosition() const override;
+ void triggerScan() override;
+ void cancelScan() override;
+ void pause(AfPause pause) override;
+
+private:
+ enum class ScanState {
+ Idle = 0,
+ Trigger,
+ Pdaf,
+ Coarse,
+ Fine,
+ Settle
+ };
+
+ struct RangeDependentParams {
+ double focusMin; /* lower (far) limit in dipotres */
+ double focusMax; /* upper (near) limit in dioptres */
+ double focusDefault; /* default setting ("hyperfocal") */
+
+ RangeDependentParams();
+ void read(const libcamera::YamlObject &params);
+ };
+
+ struct SpeedDependentParams {
+ double stepCoarse; /* used for scans */
+ double stepFine; /* used for scans */
+ double contrastRatio; /* used for scan termination and reporting */
+ double pdafGain; /* coefficient for PDAF feedback loop */
+ double pdafSquelch; /* PDAF stability parameter (device-specific) */
+ double maxSlew; /* limit for lens movement per frame */
+ uint32_t pdafFrames; /* number of iterations when triggered */
+ uint32_t dropoutFrames; /* number of non-PDAF frames to switch to CDAF */
+ uint32_t stepFrames; /* frames to skip in between steps of a scan */
+
+ SpeedDependentParams();
+ void read(const libcamera::YamlObject &params);
+ };
+
+ struct CfgParams {
+ RangeDependentParams ranges[AfRangeMax];
+ SpeedDependentParams speeds[AfSpeedMax];
+ uint32_t confEpsilon; /* PDAF hysteresis threshold (sensor-specific) */
+ uint32_t confThresh; /* PDAF confidence cell min (sensor-specific) */
+ uint32_t confClip; /* PDAF confidence cell max (sensor-specific) */
+ uint32_t skipFrames; /* frames to skip at start or modeswitch */
+ Pwl map; /* converts dioptres -> lens driver position */
+
+ CfgParams();
+ int read(const libcamera::YamlObject &params);
+ void initialise();
+ };
+
+ struct ScanRecord {
+ double focus;
+ double contrast;
+ double phase;
+ double conf;
+ };
+
+ struct RegionWeights {
+ unsigned rows;
+ unsigned cols;
+ uint32_t sum;
+ std::vector<uint16_t> w;
+
+ RegionWeights()
+ : rows(0), cols(0), sum(0), w() {}
+ };
+
+ void computeWeights(RegionWeights *wgts, unsigned rows, unsigned cols);
+ void invalidateWeights();
+ bool getPhase(PdafRegions const &regions, double &phase, double &conf);
+ double getContrast(const FocusRegions &focusStats);
+ void doPDAF(double phase, double conf);
+ bool earlyTerminationByPhase(double phase);
+ double findPeak(unsigned index) const;
+ void doScan(double contrast, double phase, double conf);
+ void doAF(double contrast, double phase, double conf);
+ void updateLensPosition();
+ void startAF();
+ void startProgrammedScan();
+ void goIdle();
+
+ /* Configuration and settings */
+ CfgParams cfg_;
+ AfRange range_;
+ AfSpeed speed_;
+ AfMode mode_;
+ bool pauseFlag_;
+ libcamera::Rectangle statsRegion_;
+ std::vector<libcamera::Rectangle> windows_;
+ bool useWindows_;
+ RegionWeights phaseWeights_;
+ RegionWeights contrastWeights_;
+
+ /* Working state. */
+ ScanState scanState_;
+ bool initted_;
+ double ftarget_, fsmooth_;
+ double prevContrast_;
+ unsigned skipCount_, stepCount_, dropCount_;
+ unsigned scanMaxIndex_;
+ double scanMaxContrast_, scanMinContrast_;
+ std::vector<ScanRecord> scanData_;
+ AfState reportState_;
+};
+
+} // namespace RPiController
diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
new file mode 100644
index 00000000..6549dedd
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -0,0 +1,338 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * agc.cpp - AGC/AEC control algorithm
+ */
+
+#include "agc.h"
+
+#include <libcamera/base/log.h>
+
+#include "../metadata.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using libcamera::utils::Duration;
+using namespace std::literals::chrono_literals;
+
+LOG_DEFINE_CATEGORY(RPiAgc)
+
+#define NAME "rpi.agc"
+
+Agc::Agc(Controller *controller)
+ : AgcAlgorithm(controller),
+ activeChannels_({ 0 }), index_(0)
+{
+}
+
+char const *Agc::name() const
+{
+ return NAME;
+}
+
+int Agc::read(const libcamera::YamlObject &params)
+{
+ /*
+ * When there is only a single channel we can read the old style syntax.
+ * Otherwise we expect a "channels" keyword followed by a list of configurations.
+ */
+ if (!params.contains("channels")) {
+ LOG(RPiAgc, Debug) << "Single channel only";
+ channelTotalExposures_.resize(1, 0s);
+ channelData_.emplace_back();
+ return channelData_.back().channel.read(params, getHardwareConfig());
+ }
+
+ const auto &channels = params["channels"].asList();
+ for (auto ch = channels.begin(); ch != channels.end(); ch++) {
+ LOG(RPiAgc, Debug) << "Read AGC channel";
+ channelData_.emplace_back();
+ int ret = channelData_.back().channel.read(*ch, getHardwareConfig());
+ if (ret)
+ return ret;
+ }
+
+ LOG(RPiAgc, Debug) << "Read " << channelData_.size() << " channel(s)";
+ if (channelData_.empty()) {
+ LOG(RPiAgc, Error) << "No AGC channels provided";
+ return -1;
+ }
+
+ channelTotalExposures_.resize(channelData_.size(), 0s);
+
+ return 0;
+}
+
+int Agc::checkChannel(unsigned int channelIndex) const
+{
+ if (channelIndex >= channelData_.size()) {
+ LOG(RPiAgc, Warning) << "AGC channel " << channelIndex << " not available";
+ return -1;
+ }
+
+ return 0;
+}
+
+void Agc::disableAuto()
+{
+ LOG(RPiAgc, Debug) << "disableAuto";
+
+ /* All channels are enabled/disabled together. */
+ for (auto &data : channelData_)
+ data.channel.disableAuto();
+}
+
+void Agc::enableAuto()
+{
+ LOG(RPiAgc, Debug) << "enableAuto";
+
+ /* All channels are enabled/disabled together. */
+ for (auto &data : channelData_)
+ data.channel.enableAuto();
+}
+
+unsigned int Agc::getConvergenceFrames() const
+{
+ /* If there are n channels, it presumably takes n times as long to converge. */
+ return channelData_[0].channel.getConvergenceFrames() * activeChannels_.size();
+}
+
+std::vector<double> const &Agc::getWeights() const
+{
+ /*
+ * In future the metering weights may be determined differently, making it
+ * difficult to associate different sets of weight with different channels.
+ * Therefore we shall impose a limitation, at least for now, that all
+ * channels will use the same weights.
+ */
+ return channelData_[0].channel.getWeights();
+}
+
+void Agc::setEv(unsigned int channelIndex, double ev)
+{
+ if (checkChannel(channelIndex))
+ return;
+
+ LOG(RPiAgc, Debug) << "setEv " << ev << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setEv(ev);
+}
+
+void Agc::setFlickerPeriod(Duration flickerPeriod)
+{
+ LOG(RPiAgc, Debug) << "setFlickerPeriod " << flickerPeriod;
+
+ /* Flicker period will be the same across all channels. */
+ for (auto &data : channelData_)
+ data.channel.setFlickerPeriod(flickerPeriod);
+}
+
+void Agc::setMaxShutter(Duration maxShutter)
+{
+ /* Frame durations will be the same across all channels too. */
+ for (auto &data : channelData_)
+ data.channel.setMaxShutter(maxShutter);
+}
+
+void Agc::setFixedShutter(unsigned int channelIndex, Duration fixedShutter)
+{
+ if (checkChannel(channelIndex))
+ return;
+
+ LOG(RPiAgc, Debug) << "setFixedShutter " << fixedShutter
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFixedShutter(fixedShutter);
+}
+
+void Agc::setFixedAnalogueGain(unsigned int channelIndex, double fixedAnalogueGain)
+{
+ if (checkChannel(channelIndex))
+ return;
+
+ LOG(RPiAgc, Debug) << "setFixedAnalogueGain " << fixedAnalogueGain
+ << " for channel " << channelIndex;
+ channelData_[channelIndex].channel.setFixedAnalogueGain(fixedAnalogueGain);
+}
+
+void Agc::setMeteringMode(std::string const &meteringModeName)
+{
+ /* Metering modes will be the same across all channels too. */
+ for (auto &data : channelData_)
+ data.channel.setMeteringMode(meteringModeName);
+}
+
+void Agc::setExposureMode(std::string const &exposureModeName)
+{
+ LOG(RPiAgc, Debug) << "setExposureMode " << exposureModeName;
+
+ /* Exposure mode will be the same across all channels. */
+ for (auto &data : channelData_)
+ data.channel.setExposureMode(exposureModeName);
+}
+
+void Agc::setConstraintMode(std::string const &constraintModeName)
+{
+ LOG(RPiAgc, Debug) << "setConstraintMode " << constraintModeName;
+
+ /* Constraint mode will be the same across all channels. */
+ for (auto &data : channelData_)
+ data.channel.setConstraintMode(constraintModeName);
+}
+
+template<typename T>
+std::ostream &operator<<(std::ostream &os, const std::vector<T> &v)
+{
+ os << "{";
+ for (const auto &e : v)
+ os << " " << e;
+ os << " }";
+ return os;
+}
+
+void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
+{
+ if (activeChannels.empty()) {
+ LOG(RPiAgc, Warning) << "No active AGC channels supplied";
+ return;
+ }
+
+ for (auto index : activeChannels)
+ if (checkChannel(index))
+ return;
+
+ LOG(RPiAgc, Debug) << "setActiveChannels " << activeChannels;
+ activeChannels_ = activeChannels;
+ index_ = 0;
+}
+
+void Agc::switchMode(CameraMode const &cameraMode,
+ Metadata *metadata)
+{
+ /*
+ * We run switchMode on every channel, and then we're going to start over
+ * with the first active channel again which means that this channel's
+ * status needs to be the one we leave in the metadata.
+ */
+ AgcStatus status;
+
+ for (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {
+ LOG(RPiAgc, Debug) << "switchMode for channel " << channelIndex;
+ channelData_[channelIndex].channel.switchMode(cameraMode, metadata);
+ if (channelIndex == activeChannels_[0])
+ metadata->get("agc.status", status);
+ }
+
+ status.channel = activeChannels_[0];
+ metadata->set("agc.status", status);
+ index_ = 0;
+}
+
+static void getDelayedChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)
+{
+ std::unique_lock<RPiController::Metadata> lock(*metadata);
+ AgcStatus *status = metadata->getLocked<AgcStatus>("agc.delayed_status");
+ if (status)
+ channelIndex = status->channel;
+ else {
+ /* This does happen at startup, otherwise it would be a Warning or Error. */
+ LOG(RPiAgc, Debug) << message;
+ }
+}
+
+static libcamera::utils::Duration
+setCurrentChannelIndexGetExposure(Metadata *metadata, const char *message, unsigned int channelIndex)
+{
+ std::unique_lock<RPiController::Metadata> lock(*metadata);
+ AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
+ libcamera::utils::Duration dur = 0s;
+
+ if (status) {
+ status->channel = channelIndex;
+ dur = status->totalExposureValue;
+ } else {
+ /* This does happen at startup, otherwise it would be a Warning or Error. */
+ LOG(RPiAgc, Debug) << message;
+ }
+
+ return dur;
+}
+
+void Agc::prepare(Metadata *imageMetadata)
+{
+ /*
+ * The DeviceStatus in the metadata should be correct for the image we
+ * are processing. The delayed status should tell us what channel this frame
+ * was from, so we will use that channel's prepare method.
+ *
+ * \todo To be honest, there's not much that's stateful in the prepare methods
+ * so we should perhaps re-evaluate whether prepare even needs to be done
+ * "per channel".
+ */
+ unsigned int channelIndex = activeChannels_[0];
+ getDelayedChannelIndex(imageMetadata, "prepare: no delayed status", channelIndex);
+
+ LOG(RPiAgc, Debug) << "prepare for channel " << channelIndex;
+ channelData_[channelIndex].channel.prepare(imageMetadata);
+}
+
+void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ /*
+ * We want to generate values for the next channel in round robin fashion
+ * (i.e. the channel at location index_ in the activeChannel list), even though
+ * the statistics we have will be for a different channel (which we find
+ * again from the delayed status).
+ */
+
+ /* Generate updated AGC values for channel for new channel that we are requesting. */
+ unsigned int channelIndex = activeChannels_[index_];
+ AgcChannelData &channelData = channelData_[channelIndex];
+ /* The stats that arrived with this image correspond to the following channel. */
+ unsigned int statsIndex = 0;
+ getDelayedChannelIndex(imageMetadata, "process: no delayed status for stats", statsIndex);
+ LOG(RPiAgc, Debug) << "process for channel " << channelIndex;
+
+ /*
+ * We keep a cache of the most recent DeviceStatus and stats for each channel,
+ * so that we can invoke the next channel's process method with the most up to date
+ * values.
+ */
+ LOG(RPiAgc, Debug) << "Save DeviceStatus and stats for channel " << statsIndex;
+ DeviceStatus deviceStatus;
+ if (imageMetadata->get<DeviceStatus>("device.status", deviceStatus) == 0)
+ channelData_[statsIndex].deviceStatus = deviceStatus;
+ else
+ /* Every frame should have a DeviceStatus. */
+ LOG(RPiAgc, Error) << "process: no device status found";
+ channelData_[statsIndex].statistics = stats;
+
+ /*
+ * Finally fetch the most recent DeviceStatus and stats for the new channel, if both
+ * exist, and call process(). We must make the agc.status metadata record correctly
+ * which channel this is.
+ */
+ StatisticsPtr *statsPtr = &stats;
+ if (channelData.statistics && channelData.deviceStatus) {
+ deviceStatus = *channelData.deviceStatus;
+ statsPtr = &channelData.statistics;
+ } else {
+ /* Can also happen when new channels start. */
+ LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
+ }
+
+ channelData.channel.process(*statsPtr, deviceStatus, imageMetadata, channelTotalExposures_);
+ auto dur = setCurrentChannelIndexGetExposure(imageMetadata, "process: no AGC status found",
+ channelIndex);
+ if (dur)
+ channelTotalExposures_[channelIndex] = dur;
+
+ /* And onto the next channel for the next call. */
+ index_ = (index_ + 1) % activeChannels_.size();
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Agc(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
new file mode 100644
index 00000000..7d26bdf6
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/agc.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * agc.h - AGC/AEC control algorithm
+ */
+#pragma once
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "../agc_algorithm.h"
+
+#include "agc_channel.h"
+
+namespace RPiController {
+
+struct AgcChannelData {
+ AgcChannel channel;
+ std::optional<DeviceStatus> deviceStatus;
+ StatisticsPtr statistics;
+};
+
+class Agc : public AgcAlgorithm
+{
+public:
+ Agc(Controller *controller);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ unsigned int getConvergenceFrames() const override;
+ std::vector<double> const &getWeights() const override;
+ void setEv(unsigned int channel, double ev) override;
+ void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) override;
+ void setMaxShutter(libcamera::utils::Duration maxShutter) override;
+ void setFixedShutter(unsigned int channelIndex,
+ libcamera::utils::Duration fixedShutter) override;
+ void setFixedAnalogueGain(unsigned int channelIndex,
+ double fixedAnalogueGain) override;
+ void setMeteringMode(std::string const &meteringModeName) override;
+ void setExposureMode(std::string const &exposureModeName) override;
+ void setConstraintMode(std::string const &contraintModeName) override;
+ void enableAuto() override;
+ void disableAuto() override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+ void setActiveChannels(const std::vector<unsigned int> &activeChannels) override;
+
+private:
+ int checkChannel(unsigned int channel) const;
+ std::vector<AgcChannelData> channelData_;
+ std::vector<unsigned int> activeChannels_;
+ unsigned int index_; /* index into the activeChannels_ */
+ AgcChannelTotalExposures channelTotalExposures_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
new file mode 100644
index 00000000..8116c6c1
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -0,0 +1,1022 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * agc_channel.cpp - AGC/AEC control algorithm
+ */
+
+#include "agc_channel.h"
+
+#include <algorithm>
+#include <tuple>
+
+#include <libcamera/base/log.h>
+
+#include "../awb_status.h"
+#include "../device_status.h"
+#include "../histogram.h"
+#include "../lux_status.h"
+#include "../metadata.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using libcamera::utils::Duration;
+using namespace std::literals::chrono_literals;
+
+LOG_DECLARE_CATEGORY(RPiAgc)
+
+int AgcMeteringMode::read(const libcamera::YamlObject &params)
+{
+ const YamlObject &yamlWeights = params["weights"];
+
+ for (const auto &p : yamlWeights.asList()) {
+ auto value = p.get<double>();
+ if (!value)
+ return -EINVAL;
+ weights.push_back(*value);
+ }
+
+ return 0;
+}
+
+static std::tuple<int, std::string>
+readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
+ const libcamera::YamlObject &params)
+{
+ std::string first;
+ int ret;
+
+ for (const auto &[key, value] : params.asDict()) {
+ AgcMeteringMode meteringMode;
+ ret = meteringMode.read(value);
+ if (ret)
+ return { ret, {} };
+
+ metering_modes[key] = std::move(meteringMode);
+ if (first.empty())
+ first = key;
+ }
+
+ return { 0, first };
+}
+
+int AgcExposureMode::read(const libcamera::YamlObject &params)
+{
+ auto value = params["shutter"].getList<double>();
+ if (!value)
+ return -EINVAL;
+ std::transform(value->begin(), value->end(), std::back_inserter(shutter),
+ [](double v) { return v * 1us; });
+
+ value = params["gain"].getList<double>();
+ if (!value)
+ return -EINVAL;
+ gain = std::move(*value);
+
+ if (shutter.size() < 2 || gain.size() < 2) {
+ LOG(RPiAgc, Error)
+ << "AgcExposureMode: must have at least two entries in exposure profile";
+ return -EINVAL;
+ }
+
+ if (shutter.size() != gain.size()) {
+ LOG(RPiAgc, Error)
+ << "AgcExposureMode: expect same number of exposure and gain entries in exposure profile";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static std::tuple<int, std::string>
+readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
+ const libcamera::YamlObject &params)
+{
+ std::string first;
+ int ret;
+
+ for (const auto &[key, value] : params.asDict()) {
+ AgcExposureMode exposureMode;
+ ret = exposureMode.read(value);
+ if (ret)
+ return { ret, {} };
+
+ exposureModes[key] = std::move(exposureMode);
+ if (first.empty())
+ first = key;
+ }
+
+ return { 0, first };
+}
+
+int AgcConstraint::read(const libcamera::YamlObject &params)
+{
+ std::string boundString = params["bound"].get<std::string>("");
+ transform(boundString.begin(), boundString.end(),
+ boundString.begin(), ::toupper);
+ if (boundString != "UPPER" && boundString != "LOWER") {
+ LOG(RPiAgc, Error) << "AGC constraint type should be UPPER or LOWER";
+ return -EINVAL;
+ }
+ bound = boundString == "UPPER" ? Bound::UPPER : Bound::LOWER;
+
+ auto value = params["q_lo"].get<double>();
+ if (!value)
+ return -EINVAL;
+ qLo = *value;
+
+ value = params["q_hi"].get<double>();
+ if (!value)
+ return -EINVAL;
+ qHi = *value;
+
+ return yTarget.read(params["y_target"]);
+}
+
+static std::tuple<int, AgcConstraintMode>
+readConstraintMode(const libcamera::YamlObject &params)
+{
+ AgcConstraintMode mode;
+ int ret;
+
+ for (const auto &p : params.asList()) {
+ AgcConstraint constraint;
+ ret = constraint.read(p);
+ if (ret)
+ return { ret, {} };
+
+ mode.push_back(std::move(constraint));
+ }
+
+ return { 0, mode };
+}
+
+static std::tuple<int, std::string>
+readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
+ const libcamera::YamlObject &params)
+{
+ std::string first;
+ int ret;
+
+ for (const auto &[key, value] : params.asDict()) {
+ std::tie(ret, constraintModes[key]) = readConstraintMode(value);
+ if (ret)
+ return { ret, {} };
+
+ if (first.empty())
+ first = key;
+ }
+
+ return { 0, first };
+}
+
+int AgcChannelConstraint::read(const libcamera::YamlObject &params)
+{
+ auto channelValue = params["channel"].get<unsigned int>();
+ if (!channelValue) {
+ LOG(RPiAgc, Error) << "AGC channel constraint must have a channel";
+ return -EINVAL;
+ }
+ channel = *channelValue;
+
+ std::string boundString = params["bound"].get<std::string>("");
+ transform(boundString.begin(), boundString.end(),
+ boundString.begin(), ::toupper);
+ if (boundString != "UPPER" && boundString != "LOWER") {
+ LOG(RPiAgc, Error) << "AGC channel constraint type should be UPPER or LOWER";
+ return -EINVAL;
+ }
+ bound = boundString == "UPPER" ? Bound::UPPER : Bound::LOWER;
+
+ auto factorValue = params["factor"].get<double>();
+ if (!factorValue) {
+ LOG(RPiAgc, Error) << "AGC channel constraint must have a factor";
+ return -EINVAL;
+ }
+ factor = *factorValue;
+
+ return 0;
+}
+
+static int readChannelConstraints(std::vector<AgcChannelConstraint> &channelConstraints,
+ const libcamera::YamlObject &params)
+{
+ for (const auto &p : params.asList()) {
+ AgcChannelConstraint constraint;
+ int ret = constraint.read(p);
+ if (ret)
+ return ret;
+
+ channelConstraints.push_back(constraint);
+ }
+
+ return 0;
+}
+
+int AgcConfig::read(const libcamera::YamlObject &params)
+{
+ LOG(RPiAgc, Debug) << "AgcConfig";
+ int ret;
+
+ std::tie(ret, defaultMeteringMode) =
+ readMeteringModes(meteringModes, params["metering_modes"]);
+ if (ret)
+ return ret;
+ std::tie(ret, defaultExposureMode) =
+ readExposureModes(exposureModes, params["exposure_modes"]);
+ if (ret)
+ return ret;
+ std::tie(ret, defaultConstraintMode) =
+ readConstraintModes(constraintModes, params["constraint_modes"]);
+ if (ret)
+ return ret;
+
+ if (params.contains("channel_constraints")) {
+ ret = readChannelConstraints(channelConstraints, params["channel_constraints"]);
+ if (ret)
+ return ret;
+ }
+
+ ret = yTarget.read(params["y_target"]);
+ if (ret)
+ return ret;
+
+ speed = params["speed"].get<double>(0.2);
+ startupFrames = params["startup_frames"].get<uint16_t>(10);
+ convergenceFrames = params["convergence_frames"].get<unsigned int>(6);
+ fastReduceThreshold = params["fast_reduce_threshold"].get<double>(0.4);
+ baseEv = params["base_ev"].get<double>(1.0);
+
+ /* Start with quite a low value as ramping up is easier than ramping down. */
+ defaultExposureTime = params["default_exposure_time"].get<double>(1000) * 1us;
+ defaultAnalogueGain = params["default_analogue_gain"].get<double>(1.0);
+
+ stableRegion = params["stable_region"].get<double>(0.02);
+
+ desaturate = params["desaturate"].get<int>(1);
+
+ return 0;
+}
+
+AgcChannel::ExposureValues::ExposureValues()
+ : shutter(0s), analogueGain(0),
+ totalExposure(0s), totalExposureNoDG(0s)
+{
+}
+
+AgcChannel::AgcChannel()
+ : meteringMode_(nullptr), exposureMode_(nullptr), constraintMode_(nullptr),
+ frameCount_(0), lockCount_(0),
+ lastTargetExposure_(0s), ev_(1.0), flickerPeriod_(0s),
+ maxShutter_(0s), fixedShutter_(0s), fixedAnalogueGain_(0.0)
+{
+ /* Set AWB default values in case early frames have no updates in metadata. */
+ awb_.gainR = 1.0;
+ awb_.gainG = 1.0;
+ awb_.gainB = 1.0;
+
+ /*
+ * Setting status_.totalExposureValue_ to zero initially tells us
+ * it's not been calculated yet (i.e. Process hasn't yet run).
+ */
+ status_ = {};
+ status_.ev = ev_;
+}
+
+int AgcChannel::read(const libcamera::YamlObject &params,
+ const Controller::HardwareConfig &hardwareConfig)
+{
+ int ret = config_.read(params);
+ if (ret)
+ return ret;
+
+ const Size &size = hardwareConfig.agcZoneWeights;
+ for (auto const &modes : config_.meteringModes) {
+ if (modes.second.weights.size() != size.width * size.height) {
+ LOG(RPiAgc, Error) << "AgcMeteringMode: Incorrect number of weights";
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * Set the config's defaults (which are the first ones it read) as our
+ * current modes, until someone changes them. (they're all known to
+ * exist at this point)
+ */
+ meteringModeName_ = config_.defaultMeteringMode;
+ meteringMode_ = &config_.meteringModes[meteringModeName_];
+ exposureModeName_ = config_.defaultExposureMode;
+ exposureMode_ = &config_.exposureModes[exposureModeName_];
+ constraintModeName_ = config_.defaultConstraintMode;
+ constraintMode_ = &config_.constraintModes[constraintModeName_];
+ /* Set up the "last shutter/gain" values, in case AGC starts "disabled". */
+ status_.shutterTime = config_.defaultExposureTime;
+ status_.analogueGain = config_.defaultAnalogueGain;
+ return 0;
+}
+
+void AgcChannel::disableAuto()
+{
+ fixedShutter_ = status_.shutterTime;
+ fixedAnalogueGain_ = status_.analogueGain;
+}
+
+void AgcChannel::enableAuto()
+{
+ fixedShutter_ = 0s;
+ fixedAnalogueGain_ = 0;
+}
+
+unsigned int AgcChannel::getConvergenceFrames() const
+{
+ /*
+ * If shutter and gain have been explicitly set, there is no
+ * convergence to happen, so no need to drop any frames - return zero.
+ */
+ if (fixedShutter_ && fixedAnalogueGain_)
+ return 0;
+ else
+ return config_.convergenceFrames;
+}
+
+std::vector<double> const &AgcChannel::getWeights() const
+{
+ /*
+ * In case someone calls setMeteringMode and then this before the
+ * algorithm has run and updated the meteringMode_ pointer.
+ */
+ auto it = config_.meteringModes.find(meteringModeName_);
+ if (it == config_.meteringModes.end())
+ return meteringMode_->weights;
+ return it->second.weights;
+}
+
+void AgcChannel::setEv(double ev)
+{
+ ev_ = ev;
+}
+
+void AgcChannel::setFlickerPeriod(Duration flickerPeriod)
+{
+ flickerPeriod_ = flickerPeriod;
+}
+
+void AgcChannel::setMaxShutter(Duration maxShutter)
+{
+ maxShutter_ = maxShutter;
+}
+
+void AgcChannel::setFixedShutter(Duration fixedShutter)
+{
+ fixedShutter_ = fixedShutter;
+ /* Set this in case someone calls disableAuto() straight after. */
+ status_.shutterTime = limitShutter(fixedShutter_);
+}
+
+void AgcChannel::setFixedAnalogueGain(double fixedAnalogueGain)
+{
+ fixedAnalogueGain_ = fixedAnalogueGain;
+ /* Set this in case someone calls disableAuto() straight after. */
+ status_.analogueGain = limitGain(fixedAnalogueGain);
+}
+
+void AgcChannel::setMeteringMode(std::string const &meteringModeName)
+{
+ meteringModeName_ = meteringModeName;
+}
+
+void AgcChannel::setExposureMode(std::string const &exposureModeName)
+{
+ exposureModeName_ = exposureModeName;
+}
+
+void AgcChannel::setConstraintMode(std::string const &constraintModeName)
+{
+ constraintModeName_ = constraintModeName;
+}
+
+void AgcChannel::switchMode(CameraMode const &cameraMode,
+ Metadata *metadata)
+{
+ /* AGC expects the mode sensitivity always to be non-zero. */
+ ASSERT(cameraMode.sensitivity);
+
+ housekeepConfig();
+
+ /*
+ * Store the mode in the local state. We must cache the sensitivity of
+ * of the previous mode for the calculations below.
+ */
+ double lastSensitivity = mode_.sensitivity;
+ mode_ = cameraMode;
+
+ Duration fixedShutter = limitShutter(fixedShutter_);
+ if (fixedShutter && fixedAnalogueGain_) {
+ /* We're going to reset the algorithm here with these fixed values. */
+ fetchAwbStatus(metadata);
+ double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
+ ASSERT(minColourGain != 0.0);
+
+ /* This is the equivalent of computeTargetExposure and applyDigitalGain. */
+ target_.totalExposureNoDG = fixedShutter_ * fixedAnalogueGain_;
+ target_.totalExposure = target_.totalExposureNoDG / minColourGain;
+
+ /* Equivalent of filterExposure. This resets any "history". */
+ filtered_ = target_;
+
+ /* Equivalent of divideUpExposure. */
+ filtered_.shutter = fixedShutter;
+ filtered_.analogueGain = fixedAnalogueGain_;
+ } else if (status_.totalExposureValue) {
+ /*
+ * On a mode switch, various things could happen:
+ * - the exposure profile might change
+ * - a fixed exposure or gain might be set
+ * - the new mode's sensitivity might be different
+ * We cope with the last of these by scaling the target values. After
+ * that we just need to re-divide the exposure/gain according to the
+ * current exposure profile, which takes care of everything else.
+ */
+
+ double ratio = lastSensitivity / cameraMode.sensitivity;
+ target_.totalExposureNoDG *= ratio;
+ target_.totalExposure *= ratio;
+ filtered_.totalExposureNoDG *= ratio;
+ filtered_.totalExposure *= ratio;
+
+ divideUpExposure();
+ } else {
+ /*
+ * We come through here on startup, when at least one of the shutter
+ * or gain has not been fixed. We must still write those values out so
+ * that they will be applied immediately. We supply some arbitrary defaults
+ * for any that weren't set.
+ */
+
+ /* Equivalent of divideUpExposure. */
+ filtered_.shutter = fixedShutter ? fixedShutter : config_.defaultExposureTime;
+ filtered_.analogueGain = fixedAnalogueGain_ ? fixedAnalogueGain_ : config_.defaultAnalogueGain;
+ }
+
+ writeAndFinish(metadata, false);
+}
+
+void AgcChannel::prepare(Metadata *imageMetadata)
+{
+ Duration totalExposureValue = status_.totalExposureValue;
+ AgcStatus delayedStatus;
+ AgcPrepareStatus prepareStatus;
+
+ /* Fetch the AWB status now because AWB also sets it in the prepare method. */
+ fetchAwbStatus(imageMetadata);
+
+ if (!imageMetadata->get("agc.delayed_status", delayedStatus))
+ totalExposureValue = delayedStatus.totalExposureValue;
+
+ prepareStatus.digitalGain = 1.0;
+ prepareStatus.locked = false;
+
+ if (status_.totalExposureValue) {
+ /* Process has run, so we have meaningful values. */
+ DeviceStatus deviceStatus;
+ if (imageMetadata->get("device.status", deviceStatus) == 0) {
+ Duration actualExposure = deviceStatus.shutterSpeed *
+ deviceStatus.analogueGain;
+ if (actualExposure) {
+ double digitalGain = totalExposureValue / actualExposure;
+ LOG(RPiAgc, Debug) << "Want total exposure " << totalExposureValue;
+ /*
+ * Never ask for a gain < 1.0, and also impose
+ * some upper limit. Make it customisable?
+ */
+ prepareStatus.digitalGain = std::max(1.0, std::min(digitalGain, 4.0));
+ LOG(RPiAgc, Debug) << "Actual exposure " << actualExposure;
+ LOG(RPiAgc, Debug) << "Use digitalGain " << prepareStatus.digitalGain;
+ LOG(RPiAgc, Debug) << "Effective exposure "
+ << actualExposure * prepareStatus.digitalGain;
+ /* Decide whether AEC/AGC has converged. */
+ prepareStatus.locked = updateLockStatus(deviceStatus);
+ }
+ } else
+ LOG(RPiAgc, Warning) << "AgcChannel: no device metadata";
+ imageMetadata->set("agc.prepare_status", prepareStatus);
+ }
+}
+
+void AgcChannel::process(StatisticsPtr &stats, DeviceStatus const &deviceStatus,
+ Metadata *imageMetadata,
+ const AgcChannelTotalExposures &channelTotalExposures)
+{
+ frameCount_++;
+ /*
+ * First a little bit of housekeeping, fetching up-to-date settings and
+ * configuration, that kind of thing.
+ */
+ housekeepConfig();
+ /* Get the current exposure values for the frame that's just arrived. */
+ fetchCurrentExposure(deviceStatus);
+ /* Compute the total gain we require relative to the current exposure. */
+ double gain, targetY;
+ computeGain(stats, imageMetadata, gain, targetY);
+ /* Now compute the target (final) exposure which we think we want. */
+ computeTargetExposure(gain);
+ /* The results have to be filtered so as not to change too rapidly. */
+ filterExposure();
+ /*
+ * We may be asked to limit the exposure using other channels. If another channel
+ * determines our upper bound we may want to know this later.
+ */
+ bool channelBound = applyChannelConstraints(channelTotalExposures);
+ /*
+ * Some of the exposure has to be applied as digital gain, so work out
+ * what that is. It also tells us whether it's trying to desaturate the image
+ * more quickly, which can only happen when another channel is not limiting us.
+ */
+ bool desaturate = applyDigitalGain(gain, targetY, channelBound);
+ /*
+ * The last thing is to divide up the exposure value into a shutter time
+ * and analogue gain, according to the current exposure mode.
+ */
+ divideUpExposure();
+ /* Finally advertise what we've done. */
+ writeAndFinish(imageMetadata, desaturate);
+}
+
+bool AgcChannel::updateLockStatus(DeviceStatus const &deviceStatus)
+{
+ const double errorFactor = 0.10; /* make these customisable? */
+ const int maxLockCount = 5;
+ /* Reset "lock count" when we exceed this multiple of errorFactor */
+ const double resetMargin = 1.5;
+
+ /* Add 200us to the exposure time error to allow for line quantisation. */
+ Duration exposureError = lastDeviceStatus_.shutterSpeed * errorFactor + 200us;
+ double gainError = lastDeviceStatus_.analogueGain * errorFactor;
+ Duration targetError = lastTargetExposure_ * errorFactor;
+
+ /*
+ * Note that we don't know the exposure/gain limits of the sensor, so
+ * the values we keep requesting may be unachievable. For this reason
+ * we only insist that we're close to values in the past few frames.
+ */
+ if (deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed - exposureError &&
+ deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed + exposureError &&
+ deviceStatus.analogueGain > lastDeviceStatus_.analogueGain - gainError &&
+ deviceStatus.analogueGain < lastDeviceStatus_.analogueGain + gainError &&
+ status_.targetExposureValue > lastTargetExposure_ - targetError &&
+ status_.targetExposureValue < lastTargetExposure_ + targetError)
+ lockCount_ = std::min(lockCount_ + 1, maxLockCount);
+ else if (deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed - resetMargin * exposureError ||
+ deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed + resetMargin * exposureError ||
+ deviceStatus.analogueGain < lastDeviceStatus_.analogueGain - resetMargin * gainError ||
+ deviceStatus.analogueGain > lastDeviceStatus_.analogueGain + resetMargin * gainError ||
+ status_.targetExposureValue < lastTargetExposure_ - resetMargin * targetError ||
+ status_.targetExposureValue > lastTargetExposure_ + resetMargin * targetError)
+ lockCount_ = 0;
+
+ lastDeviceStatus_ = deviceStatus;
+ lastTargetExposure_ = status_.targetExposureValue;
+
+ LOG(RPiAgc, Debug) << "Lock count updated to " << lockCount_;
+ return lockCount_ == maxLockCount;
+}
+
+void AgcChannel::housekeepConfig()
+{
+ /* First fetch all the up-to-date settings, so no one else has to do it. */
+ status_.ev = ev_;
+ status_.fixedShutter = limitShutter(fixedShutter_);
+ status_.fixedAnalogueGain = fixedAnalogueGain_;
+ status_.flickerPeriod = flickerPeriod_;
+ LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixedShutter "
+ << status_.fixedShutter << " fixedAnalogueGain "
+ << status_.fixedAnalogueGain;
+ /*
+ * Make sure the "mode" pointers point to the up-to-date things, if
+ * they've changed.
+ */
+ if (meteringModeName_ != status_.meteringMode) {
+ auto it = config_.meteringModes.find(meteringModeName_);
+ if (it == config_.meteringModes.end()) {
+ LOG(RPiAgc, Warning) << "No metering mode " << meteringModeName_;
+ meteringModeName_ = status_.meteringMode;
+ } else {
+ meteringMode_ = &it->second;
+ status_.meteringMode = meteringModeName_;
+ }
+ }
+ if (exposureModeName_ != status_.exposureMode) {
+ auto it = config_.exposureModes.find(exposureModeName_);
+ if (it == config_.exposureModes.end()) {
+ LOG(RPiAgc, Warning) << "No exposure profile " << exposureModeName_;
+ exposureModeName_ = status_.exposureMode;
+ } else {
+ exposureMode_ = &it->second;
+ status_.exposureMode = exposureModeName_;
+ }
+ }
+ if (constraintModeName_ != status_.constraintMode) {
+ auto it = config_.constraintModes.find(constraintModeName_);
+ if (it == config_.constraintModes.end()) {
+ LOG(RPiAgc, Warning) << "No constraint list " << constraintModeName_;
+ constraintModeName_ = status_.constraintMode;
+ } else {
+ constraintMode_ = &it->second;
+ status_.constraintMode = constraintModeName_;
+ }
+ }
+ LOG(RPiAgc, Debug) << "exposureMode "
+ << exposureModeName_ << " constraintMode "
+ << constraintModeName_ << " meteringMode "
+ << meteringModeName_;
+}
+
+void AgcChannel::fetchCurrentExposure(DeviceStatus const &deviceStatus)
+{
+ current_.shutter = deviceStatus.shutterSpeed;
+ current_.analogueGain = deviceStatus.analogueGain;
+ current_.totalExposure = 0s; /* this value is unused */
+ current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
+}
+
+void AgcChannel::fetchAwbStatus(Metadata *imageMetadata)
+{
+ if (imageMetadata->get("awb.status", awb_) != 0)
+ LOG(RPiAgc, Debug) << "No AWB status found";
+}
+
+static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,
+ std::vector<double> &weights, double gain)
+{
+ constexpr uint64_t maxVal = 1 << Statistics::NormalisationFactorPow2;
+
+ /*
+ * If we have no AGC region stats, but do have a a Y histogram, use that
+ * directly to caluclate the mean Y value of the image.
+ */
+ if (!stats->agcRegions.numRegions() && stats->yHist.bins()) {
+ /*
+ * When the gain is applied to the histogram, anything below minBin
+ * will scale up directly with the gain, but anything above that
+ * will saturate into the top bin.
+ */
+ auto &hist = stats->yHist;
+ double minBin = std::min(1.0, 1.0 / gain) * hist.bins();
+ double binMean = hist.interBinMean(0.0, minBin);
+ double numUnsaturated = hist.cumulativeFreq(minBin);
+ /* This term is from all the pixels that won't saturate. */
+ double ySum = binMean * gain * numUnsaturated;
+ /* And add the ones that will saturate. */
+ ySum += (hist.total() - numUnsaturated) * hist.bins();
+ return ySum / hist.total() / hist.bins();
+ }
+
+ ASSERT(weights.size() == stats->agcRegions.numRegions());
+
+ /*
+ * Note that the weights are applied by the IPA to the statistics directly,
+ * before they are given to us here.
+ */
+ double rSum = 0, gSum = 0, bSum = 0, pixelSum = 0;
+ for (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) {
+ auto &region = stats->agcRegions.get(i);
+ rSum += std::min<double>(region.val.rSum * gain, (maxVal - 1) * region.counted);
+ gSum += std::min<double>(region.val.gSum * gain, (maxVal - 1) * region.counted);
+ bSum += std::min<double>(region.val.bSum * gain, (maxVal - 1) * region.counted);
+ pixelSum += region.counted;
+ }
+ if (pixelSum == 0.0) {
+ LOG(RPiAgc, Warning) << "computeInitialY: pixelSum is zero";
+ return 0;
+ }
+
+ double ySum;
+ /* Factor in the AWB correction if needed. */
+ if (stats->agcStatsPos == Statistics::AgcStatsPos::PreWb) {
+ ySum = rSum * awb.gainR * .299 +
+ gSum * awb.gainG * .587 +
+ bSum * awb.gainB * .114;
+ } else
+ ySum = rSum * .299 + gSum * .587 + bSum * .114;
+
+ return ySum / pixelSum / (1 << 16);
+}
+
+/*
+ * We handle extra gain through EV by adjusting our Y targets. However, you
+ * simply can't monitor histograms once they get very close to (or beyond!)
+ * saturation, so we clamp the Y targets to this value. It does mean that EV
+ * increases don't necessarily do quite what you might expect in certain
+ * (contrived) cases.
+ */
+
+static constexpr double EvGainYTargetLimit = 0.9;
+
+static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,
+ double evGain, double &targetY)
+{
+ targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));
+ targetY = std::min(EvGainYTargetLimit, targetY * evGain);
+ double iqm = h.interQuantileMean(c.qLo, c.qHi);
+ return (targetY * h.bins()) / iqm;
+}
+
+void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
+ double &gain, double &targetY)
+{
+ struct LuxStatus lux = {};
+ lux.lux = 400; /* default lux level to 400 in case no metadata found */
+ if (imageMetadata->get("lux.status", lux) != 0)
+ LOG(RPiAgc, Warning) << "No lux level found";
+ const Histogram &h = statistics->yHist;
+ double evGain = status_.ev * config_.baseEv;
+ /*
+ * The initial gain and target_Y come from some of the regions. After
+ * that we consider the histogram constraints.
+ */
+ targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux));
+ targetY = std::min(EvGainYTargetLimit, targetY * evGain);
+
+ /*
+ * Do this calculation a few times as brightness increase can be
+ * non-linear when there are saturated regions.
+ */
+ gain = 1.0;
+ for (int i = 0; i < 8; i++) {
+ double initialY = computeInitialY(statistics, awb_, meteringMode_->weights, gain);
+ double extraGain = std::min(10.0, targetY / (initialY + .001));
+ gain *= extraGain;
+ LOG(RPiAgc, Debug) << "Initial Y " << initialY << " target " << targetY
+ << " gives gain " << gain;
+ if (extraGain < 1.01) /* close enough */
+ break;
+ }
+
+ for (auto &c : *constraintMode_) {
+ double newTargetY;
+ double newGain = constraintComputeGain(c, h, lux.lux, evGain, newTargetY);
+ LOG(RPiAgc, Debug) << "Constraint has target_Y "
+ << newTargetY << " giving gain " << newGain;
+ if (c.bound == AgcConstraint::Bound::LOWER && newGain > gain) {
+ LOG(RPiAgc, Debug) << "Lower bound constraint adopted";
+ gain = newGain;
+ targetY = newTargetY;
+ } else if (c.bound == AgcConstraint::Bound::UPPER && newGain < gain) {
+ LOG(RPiAgc, Debug) << "Upper bound constraint adopted";
+ gain = newGain;
+ targetY = newTargetY;
+ }
+ }
+ LOG(RPiAgc, Debug) << "Final gain " << gain << " (target_Y " << targetY << " ev "
+ << status_.ev << " base_ev " << config_.baseEv
+ << ")";
+}
+
+void AgcChannel::computeTargetExposure(double gain)
+{
+ if (status_.fixedShutter && status_.fixedAnalogueGain) {
+ /*
+ * When ag and shutter are both fixed, we need to drive the
+ * total exposure so that we end up with a digital gain of at least
+ * 1/minColourGain. Otherwise we'd desaturate channels causing
+ * white to go cyan or magenta.
+ */
+ double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
+ ASSERT(minColourGain != 0.0);
+ target_.totalExposure =
+ status_.fixedShutter * status_.fixedAnalogueGain / minColourGain;
+ } else {
+ /*
+ * The statistics reflect the image without digital gain, so the final
+ * total exposure we're aiming for is:
+ */
+ target_.totalExposure = current_.totalExposureNoDG * gain;
+ /* The final target exposure is also limited to what the exposure mode allows. */
+ Duration maxShutter = status_.fixedShutter
+ ? status_.fixedShutter
+ : exposureMode_->shutter.back();
+ maxShutter = limitShutter(maxShutter);
+ Duration maxTotalExposure =
+ maxShutter *
+ (status_.fixedAnalogueGain != 0.0
+ ? status_.fixedAnalogueGain
+ : exposureMode_->gain.back());
+ target_.totalExposure = std::min(target_.totalExposure, maxTotalExposure);
+ }
+ LOG(RPiAgc, Debug) << "Target totalExposure " << target_.totalExposure;
+}
+
+bool AgcChannel::applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures)
+{
+ bool channelBound = false;
+ LOG(RPiAgc, Debug)
+ << "Total exposure before channel constraints " << filtered_.totalExposure;
+
+ for (const auto &constraint : config_.channelConstraints) {
+ LOG(RPiAgc, Debug)
+ << "Check constraint: channel " << constraint.channel << " bound "
+ << (constraint.bound == AgcChannelConstraint::Bound::UPPER ? "UPPER" : "LOWER")
+ << " factor " << constraint.factor;
+ if (constraint.channel >= channelTotalExposures.size() ||
+ !channelTotalExposures[constraint.channel]) {
+ LOG(RPiAgc, Debug) << "no such channel or no exposure available- skipped";
+ continue;
+ }
+
+ libcamera::utils::Duration limitExposure =
+ channelTotalExposures[constraint.channel] * constraint.factor;
+ LOG(RPiAgc, Debug) << "Limit exposure " << limitExposure;
+ if ((constraint.bound == AgcChannelConstraint::Bound::UPPER &&
+ filtered_.totalExposure > limitExposure) ||
+ (constraint.bound == AgcChannelConstraint::Bound::LOWER &&
+ filtered_.totalExposure < limitExposure)) {
+ filtered_.totalExposure = limitExposure;
+ LOG(RPiAgc, Debug) << "Constraint applies";
+ channelBound = true;
+ } else
+ LOG(RPiAgc, Debug) << "Constraint does not apply";
+ }
+
+ LOG(RPiAgc, Debug)
+ << "Total exposure after channel constraints " << filtered_.totalExposure;
+
+ return channelBound;
+}
+
+bool AgcChannel::applyDigitalGain(double gain, double targetY, bool channelBound)
+{
+ double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
+ ASSERT(minColourGain != 0.0);
+ double dg = 1.0 / minColourGain;
+ /*
+ * I think this pipeline subtracts black level and rescales before we
+ * get the stats, so no need to worry about it.
+ */
+ LOG(RPiAgc, Debug) << "after AWB, target dg " << dg << " gain " << gain
+ << " target_Y " << targetY;
+ /*
+ * Finally, if we're trying to reduce exposure but the target_Y is
+ * "close" to 1.0, then the gain computed for that constraint will be
+ * only slightly less than one, because the measured Y can never be
+ * larger than 1.0. When this happens, demand a large digital gain so
+ * that the exposure can be reduced, de-saturating the image much more
+ * quickly (and we then approach the correct value more quickly from
+ * below).
+ */
+ bool desaturate = false;
+ if (config_.desaturate)
+ desaturate = !channelBound &&
+ targetY > config_.fastReduceThreshold && gain < sqrt(targetY);
+ if (desaturate)
+ dg /= config_.fastReduceThreshold;
+ LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate;
+ filtered_.totalExposureNoDG = filtered_.totalExposure / dg;
+ LOG(RPiAgc, Debug) << "Target totalExposureNoDG " << filtered_.totalExposureNoDG;
+ return desaturate;
+}
+
+void AgcChannel::filterExposure()
+{
+ double speed = config_.speed;
+ double stableRegion = config_.stableRegion;
+
+ /*
+ * AGC adapts instantly if both shutter and gain are directly specified
+ * or we're in the startup phase.
+ */
+ if ((status_.fixedShutter && status_.fixedAnalogueGain) ||
+ frameCount_ <= config_.startupFrames)
+ speed = 1.0;
+ if (!filtered_.totalExposure) {
+ filtered_.totalExposure = target_.totalExposure;
+ } else if (filtered_.totalExposure * (1.0 - stableRegion) < target_.totalExposure &&
+ filtered_.totalExposure * (1.0 + stableRegion) > target_.totalExposure) {
+ /* Total exposure must change by more than this or we leave it alone. */
+ } else {
+ /*
+ * If close to the result go faster, to save making so many
+ * micro-adjustments on the way. (Make this customisable?)
+ */
+ if (filtered_.totalExposure < 1.2 * target_.totalExposure &&
+ filtered_.totalExposure > 0.8 * target_.totalExposure)
+ speed = sqrt(speed);
+ filtered_.totalExposure = speed * target_.totalExposure +
+ filtered_.totalExposure * (1.0 - speed);
+ }
+ LOG(RPiAgc, Debug) << "After filtering, totalExposure " << filtered_.totalExposure
+ << " no dg " << filtered_.totalExposureNoDG;
+}
+
+void AgcChannel::divideUpExposure()
+{
+ /*
+ * Sending the fixed shutter/gain cases through the same code may seem
+ * unnecessary, but it will make more sense when extend this to cover
+ * variable aperture.
+ */
+ Duration exposureValue = filtered_.totalExposureNoDG;
+ Duration shutterTime;
+ double analogueGain;
+ shutterTime = status_.fixedShutter ? status_.fixedShutter
+ : exposureMode_->shutter[0];
+ shutterTime = limitShutter(shutterTime);
+ analogueGain = status_.fixedAnalogueGain != 0.0 ? status_.fixedAnalogueGain
+ : exposureMode_->gain[0];
+ analogueGain = limitGain(analogueGain);
+ if (shutterTime * analogueGain < exposureValue) {
+ for (unsigned int stage = 1;
+ stage < exposureMode_->gain.size(); stage++) {
+ if (!status_.fixedShutter) {
+ Duration stageShutter =
+ limitShutter(exposureMode_->shutter[stage]);
+ if (stageShutter * analogueGain >= exposureValue) {
+ shutterTime = exposureValue / analogueGain;
+ break;
+ }
+ shutterTime = stageShutter;
+ }
+ if (status_.fixedAnalogueGain == 0.0) {
+ if (exposureMode_->gain[stage] * shutterTime >= exposureValue) {
+ analogueGain = exposureValue / shutterTime;
+ break;
+ }
+ analogueGain = exposureMode_->gain[stage];
+ analogueGain = limitGain(analogueGain);
+ }
+ }
+ }
+ LOG(RPiAgc, Debug) << "Divided up shutter and gain are " << shutterTime << " and "
+ << analogueGain;
+ /*
+ * Finally adjust shutter time for flicker avoidance (require both
+ * shutter and gain not to be fixed).
+ */
+ if (!status_.fixedShutter && !status_.fixedAnalogueGain &&
+ status_.flickerPeriod) {
+ int flickerPeriods = shutterTime / status_.flickerPeriod;
+ if (flickerPeriods) {
+ Duration newShutterTime = flickerPeriods * status_.flickerPeriod;
+ analogueGain *= shutterTime / newShutterTime;
+ /*
+ * We should still not allow the ag to go over the
+ * largest value in the exposure mode. Note that this
+ * may force more of the total exposure into the digital
+ * gain as a side-effect.
+ */
+ analogueGain = std::min(analogueGain, exposureMode_->gain.back());
+ analogueGain = limitGain(analogueGain);
+ shutterTime = newShutterTime;
+ }
+ LOG(RPiAgc, Debug) << "After flicker avoidance, shutter "
+ << shutterTime << " gain " << analogueGain;
+ }
+ filtered_.shutter = shutterTime;
+ filtered_.analogueGain = analogueGain;
+}
+
+void AgcChannel::writeAndFinish(Metadata *imageMetadata, bool desaturate)
+{
+ status_.totalExposureValue = filtered_.totalExposure;
+ status_.targetExposureValue = desaturate ? 0s : target_.totalExposure;
+ status_.shutterTime = filtered_.shutter;
+ status_.analogueGain = filtered_.analogueGain;
+ /*
+ * Write to metadata as well, in case anyone wants to update the camera
+ * immediately.
+ */
+ imageMetadata->set("agc.status", status_);
+ LOG(RPiAgc, Debug) << "Output written, total exposure requested is "
+ << filtered_.totalExposure;
+ LOG(RPiAgc, Debug) << "Camera exposure update: shutter time " << filtered_.shutter
+ << " analogue gain " << filtered_.analogueGain;
+}
+
+Duration AgcChannel::limitShutter(Duration shutter)
+{
+ /*
+ * shutter == 0 is a special case for fixed shutter values, and must pass
+ * through unchanged
+ */
+ if (!shutter)
+ return shutter;
+
+ shutter = std::clamp(shutter, mode_.minShutter, maxShutter_);
+ return shutter;
+}
+
+double AgcChannel::limitGain(double gain) const
+{
+ /*
+ * Only limit the lower bounds of the gain value to what the sensor limits.
+ * The upper bound on analogue gain will be made up with additional digital
+ * gain applied by the ISP.
+ *
+ * gain == 0.0 is a special case for fixed shutter values, and must pass
+ * through unchanged
+ */
+ if (!gain)
+ return gain;
+
+ gain = std::max(gain, mode_.minAnalogueGain);
+ return gain;
+}
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
new file mode 100644
index 00000000..4cf7233e
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/agc_channel.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * agc_channel.h - AGC/AEC control algorithm
+ */
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <libcamera/base/utils.h>
+
+#include "../agc_status.h"
+#include "../awb_status.h"
+#include "../controller.h"
+#include "../pwl.h"
+
+/* This is our implementation of AGC. */
+
+namespace RPiController {
+
+using AgcChannelTotalExposures = std::vector<libcamera::utils::Duration>;
+
+struct AgcMeteringMode {
+ std::vector<double> weights;
+ int read(const libcamera::YamlObject &params);
+};
+
+struct AgcExposureMode {
+ std::vector<libcamera::utils::Duration> shutter;
+ std::vector<double> gain;
+ int read(const libcamera::YamlObject &params);
+};
+
+struct AgcConstraint {
+ enum class Bound { LOWER = 0,
+ UPPER = 1 };
+ Bound bound;
+ double qLo;
+ double qHi;
+ Pwl yTarget;
+ int read(const libcamera::YamlObject &params);
+};
+
+typedef std::vector<AgcConstraint> AgcConstraintMode;
+
+struct AgcChannelConstraint {
+ enum class Bound { LOWER = 0,
+ UPPER = 1 };
+ Bound bound;
+ unsigned int channel;
+ double factor;
+ int read(const libcamera::YamlObject &params);
+};
+
+struct AgcConfig {
+ int read(const libcamera::YamlObject &params);
+ std::map<std::string, AgcMeteringMode> meteringModes;
+ std::map<std::string, AgcExposureMode> exposureModes;
+ std::map<std::string, AgcConstraintMode> constraintModes;
+ std::vector<AgcChannelConstraint> channelConstraints;
+ Pwl yTarget;
+ double speed;
+ uint16_t startupFrames;
+ unsigned int convergenceFrames;
+ double maxChange;
+ double minChange;
+ double fastReduceThreshold;
+ double speedUpThreshold;
+ std::string defaultMeteringMode;
+ std::string defaultExposureMode;
+ std::string defaultConstraintMode;
+ double baseEv;
+ libcamera::utils::Duration defaultExposureTime;
+ double defaultAnalogueGain;
+ double stableRegion;
+ bool desaturate;
+};
+
+class AgcChannel
+{
+public:
+ AgcChannel();
+ int read(const libcamera::YamlObject &params,
+ const Controller::HardwareConfig &hardwareConfig);
+ unsigned int getConvergenceFrames() const;
+ std::vector<double> const &getWeights() const;
+ void setEv(double ev);
+ void setFlickerPeriod(libcamera::utils::Duration flickerPeriod);
+ void setMaxShutter(libcamera::utils::Duration maxShutter);
+ void setFixedShutter(libcamera::utils::Duration fixedShutter);
+ void setFixedAnalogueGain(double fixedAnalogueGain);
+ void setMeteringMode(std::string const &meteringModeName);
+ void setExposureMode(std::string const &exposureModeName);
+ void setConstraintMode(std::string const &contraintModeName);
+ void enableAuto();
+ void disableAuto();
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata);
+ void prepare(Metadata *imageMetadata);
+ void process(StatisticsPtr &stats, DeviceStatus const &deviceStatus, Metadata *imageMetadata,
+ const AgcChannelTotalExposures &channelTotalExposures);
+
+private:
+ bool updateLockStatus(DeviceStatus const &deviceStatus);
+ AgcConfig config_;
+ void housekeepConfig();
+ void fetchCurrentExposure(DeviceStatus const &deviceStatus);
+ void fetchAwbStatus(Metadata *imageMetadata);
+ void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
+ double &gain, double &targetY);
+ void computeTargetExposure(double gain);
+ void filterExposure();
+ bool applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures);
+ bool applyDigitalGain(double gain, double targetY, bool channelBound);
+ void divideUpExposure();
+ void writeAndFinish(Metadata *imageMetadata, bool desaturate);
+ libcamera::utils::Duration limitShutter(libcamera::utils::Duration shutter);
+ double limitGain(double gain) const;
+ AgcMeteringMode *meteringMode_;
+ AgcExposureMode *exposureMode_;
+ AgcConstraintMode *constraintMode_;
+ CameraMode mode_;
+ uint64_t frameCount_;
+ AwbStatus awb_;
+ struct ExposureValues {
+ ExposureValues();
+
+ libcamera::utils::Duration shutter;
+ double analogueGain;
+ libcamera::utils::Duration totalExposure;
+ libcamera::utils::Duration totalExposureNoDG; /* without digital gain */
+ };
+ ExposureValues current_; /* values for the current frame */
+ ExposureValues target_; /* calculate the values we want here */
+ ExposureValues filtered_; /* these values are filtered towards target */
+ AgcStatus status_;
+ int lockCount_;
+ DeviceStatus lastDeviceStatus_;
+ libcamera::utils::Duration lastTargetExposure_;
+ /* Below here the "settings" that applications can change. */
+ std::string meteringModeName_;
+ std::string exposureModeName_;
+ std::string constraintModeName_;
+ double ev_;
+ libcamera::utils::Duration flickerPeriod_;
+ libcamera::utils::Duration maxShutter_;
+ libcamera::utils::Duration fixedShutter_;
+ double fixedAnalogueGain_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp
new file mode 100644
index 00000000..8a205c60
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/alsc.cpp
@@ -0,0 +1,867 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * alsc.cpp - ALSC (auto lens shading correction) control algorithm
+ */
+
+#include <algorithm>
+#include <functional>
+#include <math.h>
+#include <numeric>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include "../awb_status.h"
+#include "alsc.h"
+
+/* Raspberry Pi ALSC (Auto Lens Shading Correction) algorithm. */
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiAlsc)
+
+#define NAME "rpi.alsc"
+
+static const double InsufficientData = -1.0;
+
+Alsc::Alsc(Controller *controller)
+ : Algorithm(controller)
+{
+ asyncAbort_ = asyncStart_ = asyncStarted_ = asyncFinished_ = false;
+ asyncThread_ = std::thread(std::bind(&Alsc::asyncFunc, this));
+}
+
+Alsc::~Alsc()
+{
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ asyncAbort_ = true;
+ }
+ asyncSignal_.notify_one();
+ asyncThread_.join();
+}
+
+char const *Alsc::name() const
+{
+ return NAME;
+}
+
+static int generateLut(Array2D<double> &lut, const libcamera::YamlObject &params)
+{
+ /* These must be signed ints for the co-ordinate calculations below. */
+ int X = lut.dimensions().width, Y = lut.dimensions().height;
+ double cstrength = params["corner_strength"].get<double>(2.0);
+ if (cstrength <= 1.0) {
+ LOG(RPiAlsc, Error) << "corner_strength must be > 1.0";
+ return -EINVAL;
+ }
+
+ double asymmetry = params["asymmetry"].get<double>(1.0);
+ if (asymmetry < 0) {
+ LOG(RPiAlsc, Error) << "asymmetry must be >= 0";
+ return -EINVAL;
+ }
+
+ double f1 = cstrength - 1, f2 = 1 + sqrt(cstrength);
+ double R2 = X * Y / 4 * (1 + asymmetry * asymmetry);
+ int num = 0;
+ for (int y = 0; y < Y; y++) {
+ for (int x = 0; x < X; x++) {
+ double dy = y - Y / 2 + 0.5,
+ dx = (x - X / 2 + 0.5) * asymmetry;
+ double r2 = (dx * dx + dy * dy) / R2;
+ lut[num++] =
+ (f1 * r2 + f2) * (f1 * r2 + f2) /
+ (f2 * f2); /* this reproduces the cos^4 rule */
+ }
+ }
+ return 0;
+}
+
+static int readLut(Array2D<double> &lut, const libcamera::YamlObject &params)
+{
+ if (params.size() != lut.size()) {
+ LOG(RPiAlsc, Error) << "Invalid number of entries in LSC table";
+ return -EINVAL;
+ }
+
+ int num = 0;
+ for (const auto &p : params.asList()) {
+ auto value = p.get<double>();
+ if (!value)
+ return -EINVAL;
+ lut[num++] = *value;
+ }
+
+ return 0;
+}
+
+static int readCalibrations(std::vector<AlscCalibration> &calibrations,
+ const libcamera::YamlObject &params,
+ std::string const &name, const Size &size)
+{
+ if (params.contains(name)) {
+ double lastCt = 0;
+ for (const auto &p : params[name].asList()) {
+ auto value = p["ct"].get<double>();
+ if (!value)
+ return -EINVAL;
+ double ct = *value;
+ if (ct <= lastCt) {
+ LOG(RPiAlsc, Error)
+ << "Entries in " << name << " must be in increasing ct order";
+ return -EINVAL;
+ }
+ AlscCalibration calibration;
+ calibration.ct = lastCt = ct;
+
+ const libcamera::YamlObject &table = p["table"];
+ if (table.size() != size.width * size.height) {
+ LOG(RPiAlsc, Error)
+ << "Incorrect number of values for ct "
+ << ct << " in " << name;
+ return -EINVAL;
+ }
+
+ int num = 0;
+ calibration.table.resize(size);
+ for (const auto &elem : table.asList()) {
+ value = elem.get<double>();
+ if (!value)
+ return -EINVAL;
+ calibration.table[num++] = *value;
+ }
+
+ calibrations.push_back(std::move(calibration));
+ LOG(RPiAlsc, Debug)
+ << "Read " << name << " calibration for ct " << ct;
+ }
+ }
+ return 0;
+}
+
+int Alsc::read(const libcamera::YamlObject &params)
+{
+ config_.tableSize = getHardwareConfig().awbRegions;
+ config_.framePeriod = params["frame_period"].get<uint16_t>(12);
+ config_.startupFrames = params["startup_frames"].get<uint16_t>(10);
+ config_.speed = params["speed"].get<double>(0.05);
+ double sigma = params["sigma"].get<double>(0.01);
+ config_.sigmaCr = params["sigma_Cr"].get<double>(sigma);
+ config_.sigmaCb = params["sigma_Cb"].get<double>(sigma);
+ config_.minCount = params["min_count"].get<double>(10.0);
+ config_.minG = params["min_G"].get<uint16_t>(50);
+ config_.omega = params["omega"].get<double>(1.3);
+ config_.nIter = params["n_iter"].get<uint32_t>(config_.tableSize.width + config_.tableSize.height);
+ config_.luminanceStrength =
+ params["luminance_strength"].get<double>(1.0);
+
+ config_.luminanceLut.resize(config_.tableSize, 1.0);
+ int ret = 0;
+
+ if (params.contains("corner_strength"))
+ ret = generateLut(config_.luminanceLut, params);
+ else if (params.contains("luminance_lut"))
+ ret = readLut(config_.luminanceLut, params["luminance_lut"]);
+ else
+ LOG(RPiAlsc, Warning)
+ << "no luminance table - assume unity everywhere";
+ if (ret)
+ return ret;
+
+ ret = readCalibrations(config_.calibrationsCr, params, "calibrations_Cr",
+ config_.tableSize);
+ if (ret)
+ return ret;
+ ret = readCalibrations(config_.calibrationsCb, params, "calibrations_Cb",
+ config_.tableSize);
+ if (ret)
+ return ret;
+
+ config_.defaultCt = params["default_ct"].get<double>(4500.0);
+ config_.threshold = params["threshold"].get<double>(1e-3);
+ config_.lambdaBound = params["lambda_bound"].get<double>(0.05);
+
+ return 0;
+}
+
+static double getCt(Metadata *metadata, double defaultCt);
+static void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,
+ Array2D<double> &calTable);
+static void resampleCalTable(const Array2D<double> &calTableIn, CameraMode const &cameraMode,
+ Array2D<double> &calTableOut);
+static void compensateLambdasForCal(const Array2D<double> &calTable,
+ const Array2D<double> &oldLambdas,
+ Array2D<double> &newLambdas);
+static void addLuminanceToTables(std::array<Array2D<double>, 3> &results,
+ const Array2D<double> &lambdaR, double lambdaG,
+ const Array2D<double> &lambdaB,
+ const Array2D<double> &luminanceLut,
+ double luminanceStrength);
+
+void Alsc::initialise()
+{
+ frameCount2_ = frameCount_ = framePhase_ = 0;
+ firstTime_ = true;
+ ct_ = config_.defaultCt;
+
+ const size_t XY = config_.tableSize.width * config_.tableSize.height;
+
+ for (auto &r : syncResults_)
+ r.resize(config_.tableSize);
+ for (auto &r : prevSyncResults_)
+ r.resize(config_.tableSize);
+ for (auto &r : asyncResults_)
+ r.resize(config_.tableSize);
+
+ luminanceTable_.resize(config_.tableSize);
+ asyncLambdaR_.resize(config_.tableSize);
+ asyncLambdaB_.resize(config_.tableSize);
+ /* The lambdas are initialised in the SwitchMode. */
+ lambdaR_.resize(config_.tableSize);
+ lambdaB_.resize(config_.tableSize);
+
+ /* Temporaries for the computations, but sensible to allocate this up-front! */
+ for (auto &c : tmpC_)
+ c.resize(config_.tableSize);
+ for (auto &m : tmpM_)
+ m.resize(XY);
+}
+
+void Alsc::waitForAysncThread()
+{
+ if (asyncStarted_) {
+ asyncStarted_ = false;
+ std::unique_lock<std::mutex> lock(mutex_);
+ syncSignal_.wait(lock, [&] {
+ return asyncFinished_;
+ });
+ asyncFinished_ = false;
+ }
+}
+
+static bool compareModes(CameraMode const &cm0, CameraMode const &cm1)
+{
+ /*
+ * Return true if the modes crop from the sensor significantly differently,
+ * or if the user transform has changed.
+ */
+ if (cm0.transform != cm1.transform)
+ return true;
+ int leftDiff = abs(cm0.cropX - cm1.cropX);
+ int topDiff = abs(cm0.cropY - cm1.cropY);
+ int rightDiff = fabs(cm0.cropX + cm0.scaleX * cm0.width -
+ cm1.cropX - cm1.scaleX * cm1.width);
+ int bottomDiff = fabs(cm0.cropY + cm0.scaleY * cm0.height -
+ cm1.cropY - cm1.scaleY * cm1.height);
+ /*
+ * These thresholds are a rather arbitrary amount chosen to trigger
+ * when carrying on with the previously calculated tables might be
+ * worse than regenerating them (but without the adaptive algorithm).
+ */
+ int thresholdX = cm0.sensorWidth >> 4;
+ int thresholdY = cm0.sensorHeight >> 4;
+ return leftDiff > thresholdX || rightDiff > thresholdX ||
+ topDiff > thresholdY || bottomDiff > thresholdY;
+}
+
+void Alsc::switchMode(CameraMode const &cameraMode,
+ [[maybe_unused]] Metadata *metadata)
+{
+ /*
+ * We're going to start over with the tables if there's any "significant"
+ * change.
+ */
+ bool resetTables = firstTime_ || compareModes(cameraMode_, cameraMode);
+
+ /* Believe the colour temperature from the AWB, if there is one. */
+ ct_ = getCt(metadata, ct_);
+
+ /* Ensure the other thread isn't running while we do this. */
+ waitForAysncThread();
+
+ cameraMode_ = cameraMode;
+
+ /*
+ * We must resample the luminance table like we do the others, but it's
+ * fixed so we can simply do it up front here.
+ */
+ resampleCalTable(config_.luminanceLut, cameraMode_, luminanceTable_);
+
+ if (resetTables) {
+ /*
+ * Upon every "table reset", arrange for something sensible to be
+ * generated. Construct the tables for the previous recorded colour
+ * temperature. In order to start over from scratch we initialise
+ * the lambdas, but the rest of this code then echoes the code in
+ * doAlsc, without the adaptive algorithm.
+ */
+ std::fill(lambdaR_.begin(), lambdaR_.end(), 1.0);
+ std::fill(lambdaB_.begin(), lambdaB_.end(), 1.0);
+ Array2D<double> &calTableR = tmpC_[0], &calTableB = tmpC_[1], &calTableTmp = tmpC_[2];
+ getCalTable(ct_, config_.calibrationsCr, calTableTmp);
+ resampleCalTable(calTableTmp, cameraMode_, calTableR);
+ getCalTable(ct_, config_.calibrationsCb, calTableTmp);
+ resampleCalTable(calTableTmp, cameraMode_, calTableB);
+ compensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);
+ compensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);
+ addLuminanceToTables(syncResults_, asyncLambdaR_, 1.0, asyncLambdaB_,
+ luminanceTable_, config_.luminanceStrength);
+ prevSyncResults_ = syncResults_;
+ framePhase_ = config_.framePeriod; /* run the algo again asap */
+ firstTime_ = false;
+ }
+}
+
+void Alsc::fetchAsyncResults()
+{
+ LOG(RPiAlsc, Debug) << "Fetch ALSC results";
+ asyncFinished_ = false;
+ asyncStarted_ = false;
+ syncResults_ = asyncResults_;
+}
+
+double getCt(Metadata *metadata, double defaultCt)
+{
+ AwbStatus awbStatus;
+ awbStatus.temperatureK = defaultCt; /* in case nothing found */
+ if (metadata->get("awb.status", awbStatus) != 0)
+ LOG(RPiAlsc, Debug) << "no AWB results found, using "
+ << awbStatus.temperatureK;
+ else
+ LOG(RPiAlsc, Debug) << "AWB results found, using "
+ << awbStatus.temperatureK;
+ return awbStatus.temperatureK;
+}
+
+static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,
+ std::array<Array2D<double>, 3> &prevSyncResults)
+{
+ if (!regions.numRegions())
+ regions.init(stats->awbRegions.size());
+
+ const std::vector<double> &rTable = prevSyncResults[0].data(); //status.r;
+ const std::vector<double> &gTable = prevSyncResults[1].data(); //status.g;
+ const std::vector<double> &bTable = prevSyncResults[2].data(); //status.b;
+ for (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {
+ auto r = stats->awbRegions.get(i);
+ if (stats->colourStatsPos == Statistics::ColourStatsPos::PostLsc) {
+ r.val.rSum = static_cast<uint64_t>(r.val.rSum / rTable[i]);
+ r.val.gSum = static_cast<uint64_t>(r.val.gSum / gTable[i]);
+ r.val.bSum = static_cast<uint64_t>(r.val.bSum / bTable[i]);
+ }
+ regions.set(i, r);
+ }
+}
+
+void Alsc::restartAsync(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ LOG(RPiAlsc, Debug) << "Starting ALSC calculation";
+ /*
+ * Get the current colour temperature. It's all we need from the
+ * metadata. Default to the last CT value (which could be the default).
+ */
+ ct_ = getCt(imageMetadata, ct_);
+ /*
+ * We have to copy the statistics here, dividing out our best guess of
+ * the LSC table that the pipeline applied to them which we get from
+ * prevSyncResults_.
+ */
+ copyStats(statistics_, stats, prevSyncResults_);
+ framePhase_ = 0;
+ asyncStarted_ = true;
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ asyncStart_ = true;
+ }
+ asyncSignal_.notify_one();
+}
+
+void Alsc::prepare(Metadata *imageMetadata)
+{
+ /*
+ * Count frames since we started, and since we last poked the async
+ * thread.
+ */
+ if (frameCount_ < (int)config_.startupFrames)
+ frameCount_++;
+ double speed = frameCount_ < (int)config_.startupFrames
+ ? 1.0
+ : config_.speed;
+ LOG(RPiAlsc, Debug)
+ << "frame count " << frameCount_ << " speed " << speed;
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (asyncStarted_ && asyncFinished_)
+ fetchAsyncResults();
+ }
+ /* Apply IIR filter to results and program into the pipeline. */
+ for (unsigned int j = 0; j < syncResults_.size(); j++) {
+ for (unsigned int i = 0; i < syncResults_[j].size(); i++)
+ prevSyncResults_[j][i] = speed * syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];
+ }
+ /* Put output values into status metadata. */
+ AlscStatus status;
+ status.r = prevSyncResults_[0].data();
+ status.g = prevSyncResults_[1].data();
+ status.b = prevSyncResults_[2].data();
+ imageMetadata->set("alsc.status", status);
+ /*
+ * Put the results in the global metadata as well. This will be used by
+ * AWB to factor in the colour shading correction.
+ */
+ getGlobalMetadata().set("alsc.status", status);
+}
+
+void Alsc::process(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ /*
+ * Count frames since we started, and since we last poked the async
+ * thread.
+ */
+ if (framePhase_ < (int)config_.framePeriod)
+ framePhase_++;
+ if (frameCount2_ < (int)config_.startupFrames)
+ frameCount2_++;
+ LOG(RPiAlsc, Debug) << "frame_phase " << framePhase_;
+ if (framePhase_ >= (int)config_.framePeriod ||
+ frameCount2_ < (int)config_.startupFrames) {
+ if (asyncStarted_ == false)
+ restartAsync(stats, imageMetadata);
+ }
+}
+
+void Alsc::asyncFunc()
+{
+ while (true) {
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ asyncSignal_.wait(lock, [&] {
+ return asyncStart_ || asyncAbort_;
+ });
+ asyncStart_ = false;
+ if (asyncAbort_)
+ break;
+ }
+ doAlsc();
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ asyncFinished_ = true;
+ }
+ syncSignal_.notify_one();
+ }
+}
+
+void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,
+ Array2D<double> &calTable)
+{
+ if (calibrations.empty()) {
+ std::fill(calTable.begin(), calTable.end(), 1.0);
+ LOG(RPiAlsc, Debug) << "no calibrations found";
+ } else if (ct <= calibrations.front().ct) {
+ calTable = calibrations.front().table;
+ LOG(RPiAlsc, Debug) << "using calibration for "
+ << calibrations.front().ct;
+ } else if (ct >= calibrations.back().ct) {
+ calTable = calibrations.back().table;
+ LOG(RPiAlsc, Debug) << "using calibration for "
+ << calibrations.back().ct;
+ } else {
+ int idx = 0;
+ while (ct > calibrations[idx + 1].ct)
+ idx++;
+ double ct0 = calibrations[idx].ct, ct1 = calibrations[idx + 1].ct;
+ LOG(RPiAlsc, Debug)
+ << "ct is " << ct << ", interpolating between "
+ << ct0 << " and " << ct1;
+ for (unsigned int i = 0; i < calTable.size(); i++)
+ calTable[i] =
+ (calibrations[idx].table[i] * (ct1 - ct) +
+ calibrations[idx + 1].table[i] * (ct - ct0)) /
+ (ct1 - ct0);
+ }
+}
+
+void resampleCalTable(const Array2D<double> &calTableIn,
+ CameraMode const &cameraMode,
+ Array2D<double> &calTableOut)
+{
+ int X = calTableIn.dimensions().width;
+ int Y = calTableIn.dimensions().height;
+
+ /*
+ * Precalculate and cache the x sampling locations and phases to save
+ * recomputing them on every row.
+ */
+ int xLo[X], xHi[X];
+ double xf[X];
+ double scaleX = cameraMode.sensorWidth /
+ (cameraMode.width * cameraMode.scaleX);
+ double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth;
+ double x = .5 / scaleX + xOff * X - .5;
+ double xInc = 1 / scaleX;
+ for (int i = 0; i < X; i++, x += xInc) {
+ xLo[i] = floor(x);
+ xf[i] = x - xLo[i];
+ xHi[i] = std::min(xLo[i] + 1, X - 1);
+ xLo[i] = std::max(xLo[i], 0);
+ if (!!(cameraMode.transform & libcamera::Transform::HFlip)) {
+ xLo[i] = X - 1 - xLo[i];
+ xHi[i] = X - 1 - xHi[i];
+ }
+ }
+ /* Now march over the output table generating the new values. */
+ double scaleY = cameraMode.sensorHeight /
+ (cameraMode.height * cameraMode.scaleY);
+ double yOff = cameraMode.cropY / (double)cameraMode.sensorHeight;
+ double y = .5 / scaleY + yOff * Y - .5;
+ double yInc = 1 / scaleY;
+ for (int j = 0; j < Y; j++, y += yInc) {
+ int yLo = floor(y);
+ double yf = y - yLo;
+ int yHi = std::min(yLo + 1, Y - 1);
+ yLo = std::max(yLo, 0);
+ if (!!(cameraMode.transform & libcamera::Transform::VFlip)) {
+ yLo = Y - 1 - yLo;
+ yHi = Y - 1 - yHi;
+ }
+ double const *rowAbove = calTableIn.ptr() + X * yLo;
+ double const *rowBelow = calTableIn.ptr() + X * yHi;
+ double *out = calTableOut.ptr() + X * j;
+ for (int i = 0; i < X; i++) {
+ double above = rowAbove[xLo[i]] * (1 - xf[i]) +
+ rowAbove[xHi[i]] * xf[i];
+ double below = rowBelow[xLo[i]] * (1 - xf[i]) +
+ rowBelow[xHi[i]] * xf[i];
+ *(out++) = above * (1 - yf) + below * yf;
+ }
+ }
+}
+
+/* Calculate chrominance statistics (R/G and B/G) for each region. */
+static void calculateCrCb(const RgbyRegions &awbRegion, Array2D<double> &cr,
+ Array2D<double> &cb, uint32_t minCount, uint16_t minG)
+{
+ for (unsigned int i = 0; i < cr.size(); i++) {
+ auto s = awbRegion.get(i);
+
+ /* Do not return unreliable, or zero, colour ratio statistics. */
+ if (s.counted <= minCount || s.val.gSum / s.counted <= minG ||
+ s.val.rSum / s.counted <= minG || s.val.bSum / s.counted <= minG) {
+ cr[i] = cb[i] = InsufficientData;
+ continue;
+ }
+
+ cr[i] = s.val.rSum / (double)s.val.gSum;
+ cb[i] = s.val.bSum / (double)s.val.gSum;
+ }
+}
+
+static void applyCalTable(const Array2D<double> &calTable, Array2D<double> &C)
+{
+ for (unsigned int i = 0; i < C.size(); i++)
+ if (C[i] != InsufficientData)
+ C[i] *= calTable[i];
+}
+
+void compensateLambdasForCal(const Array2D<double> &calTable,
+ const Array2D<double> &oldLambdas,
+ Array2D<double> &newLambdas)
+{
+ double minNewLambda = std::numeric_limits<double>::max();
+ for (unsigned int i = 0; i < newLambdas.size(); i++) {
+ newLambdas[i] = oldLambdas[i] * calTable[i];
+ minNewLambda = std::min(minNewLambda, newLambdas[i]);
+ }
+ for (unsigned int i = 0; i < newLambdas.size(); i++)
+ newLambdas[i] /= minNewLambda;
+}
+
+[[maybe_unused]] static void printCalTable(const Array2D<double> &C)
+{
+ const Size &size = C.dimensions();
+ printf("table: [\n");
+ for (unsigned int j = 0; j < size.height; j++) {
+ for (unsigned int i = 0; i < size.width; i++) {
+ printf("%5.3f", 1.0 / C[j * size.width + i]);
+ if (i != size.width - 1 || j != size.height - 1)
+ printf(",");
+ }
+ printf("\n");
+ }
+ printf("]\n");
+}
+
+/*
+ * Compute weight out of 1.0 which reflects how similar we wish to make the
+ * colours of these two regions.
+ */
+static double computeWeight(double Ci, double Cj, double sigma)
+{
+ if (Ci == InsufficientData || Cj == InsufficientData)
+ return 0;
+ double diff = (Ci - Cj) / sigma;
+ return exp(-diff * diff / 2);
+}
+
+/* Compute all weights. */
+static void computeW(const Array2D<double> &C, double sigma,
+ SparseArray<double> &W)
+{
+ size_t XY = C.size();
+ size_t X = C.dimensions().width;
+
+ for (unsigned int i = 0; i < XY; i++) {
+ /* Start with neighbour above and go clockwise. */
+ W[i][0] = i >= X ? computeWeight(C[i], C[i - X], sigma) : 0;
+ W[i][1] = i % X < X - 1 ? computeWeight(C[i], C[i + 1], sigma) : 0;
+ W[i][2] = i < XY - X ? computeWeight(C[i], C[i + X], sigma) : 0;
+ W[i][3] = i % X ? computeWeight(C[i], C[i - 1], sigma) : 0;
+ }
+}
+
+/* Compute M, the large but sparse matrix such that M * lambdas = 0. */
+static void constructM(const Array2D<double> &C,
+ const SparseArray<double> &W,
+ SparseArray<double> &M)
+{
+ size_t XY = C.size();
+ size_t X = C.dimensions().width;
+
+ double epsilon = 0.001;
+ for (unsigned int i = 0; i < XY; i++) {
+ /*
+ * Note how, if C[i] == INSUFFICIENT_DATA, the weights will all
+ * be zero so the equation is still set up correctly.
+ */
+ int m = !!(i >= X) + !!(i % X < X - 1) + !!(i < XY - X) +
+ !!(i % X); /* total number of neighbours */
+ /* we'll divide the diagonal out straight away */
+ double diagonal = (epsilon + W[i][0] + W[i][1] + W[i][2] + W[i][3]) * C[i];
+ M[i][0] = i >= X ? (W[i][0] * C[i - X] + epsilon / m * C[i]) / diagonal : 0;
+ M[i][1] = i % X < X - 1 ? (W[i][1] * C[i + 1] + epsilon / m * C[i]) / diagonal : 0;
+ M[i][2] = i < XY - X ? (W[i][2] * C[i + X] + epsilon / m * C[i]) / diagonal : 0;
+ M[i][3] = i % X ? (W[i][3] * C[i - 1] + epsilon / m * C[i]) / diagonal : 0;
+ }
+}
+
+/*
+ * In the compute_lambda_ functions, note that the matrix coefficients for the
+ * left/right neighbours are zero down the left/right edges, so we don't need
+ * need to test the i value to exclude them.
+ */
+static double computeLambdaBottom(int i, const SparseArray<double> &M,
+ Array2D<double> &lambda)
+{
+ return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + lambda.dimensions().width] +
+ M[i][3] * lambda[i - 1];
+}
+static double computeLambdaBottomStart(int i, const SparseArray<double> &M,
+ Array2D<double> &lambda)
+{
+ return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + lambda.dimensions().width];
+}
+static double computeLambdaInterior(int i, const SparseArray<double> &M,
+ Array2D<double> &lambda)
+{
+ return M[i][0] * lambda[i - lambda.dimensions().width] + M[i][1] * lambda[i + 1] +
+ M[i][2] * lambda[i + lambda.dimensions().width] + M[i][3] * lambda[i - 1];
+}
+static double computeLambdaTop(int i, const SparseArray<double> &M,
+ Array2D<double> &lambda)
+{
+ return M[i][0] * lambda[i - lambda.dimensions().width] + M[i][1] * lambda[i + 1] +
+ M[i][3] * lambda[i - 1];
+}
+static double computeLambdaTopEnd(int i, const SparseArray<double> &M,
+ Array2D<double> &lambda)
+{
+ return M[i][0] * lambda[i - lambda.dimensions().width] + M[i][3] * lambda[i - 1];
+}
+
+/* Gauss-Seidel iteration with over-relaxation. */
+static double gaussSeidel2Sor(const SparseArray<double> &M, double omega,
+ Array2D<double> &lambda, double lambdaBound)
+{
+ int XY = lambda.size();
+ int X = lambda.dimensions().width;
+ const double min = 1 - lambdaBound, max = 1 + lambdaBound;
+ Array2D<double> oldLambda = lambda;
+ int i;
+ lambda[0] = computeLambdaBottomStart(0, M, lambda);
+ lambda[0] = std::clamp(lambda[0], min, max);
+ for (i = 1; i < X; i++) {
+ lambda[i] = computeLambdaBottom(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ }
+ for (; i < XY - X; i++) {
+ lambda[i] = computeLambdaInterior(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ }
+ for (; i < XY - 1; i++) {
+ lambda[i] = computeLambdaTop(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ }
+ lambda[i] = computeLambdaTopEnd(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ /*
+ * Also solve the system from bottom to top, to help spread the updates
+ * better.
+ */
+ lambda[i] = computeLambdaTopEnd(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ for (i = XY - 2; i >= XY - X; i--) {
+ lambda[i] = computeLambdaTop(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ }
+ for (; i >= X; i--) {
+ lambda[i] = computeLambdaInterior(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ }
+ for (; i >= 1; i--) {
+ lambda[i] = computeLambdaBottom(i, M, lambda);
+ lambda[i] = std::clamp(lambda[i], min, max);
+ }
+ lambda[0] = computeLambdaBottomStart(0, M, lambda);
+ lambda[0] = std::clamp(lambda[0], min, max);
+ double maxDiff = 0;
+ for (i = 0; i < XY; i++) {
+ lambda[i] = oldLambda[i] + (lambda[i] - oldLambda[i]) * omega;
+ if (fabs(lambda[i] - oldLambda[i]) > fabs(maxDiff))
+ maxDiff = lambda[i] - oldLambda[i];
+ }
+ return maxDiff;
+}
+
+/* Normalise the values so that the smallest value is 1. */
+static void normalise(Array2D<double> &results)
+{
+ double minval = *std::min_element(results.begin(), results.end());
+ std::for_each(results.begin(), results.end(),
+ [minval](double val) { return val / minval; });
+}
+
+/* Rescale the values so that the average value is 1. */
+static void reaverage(Array2D<double> &data)
+{
+ double sum = std::accumulate(data.begin(), data.end(), 0.0);
+ double ratio = 1 / (sum / data.size());
+ std::for_each(data.begin(), data.end(),
+ [ratio](double val) { return val * ratio; });
+}
+
+static void runMatrixIterations(const Array2D<double> &C,
+ Array2D<double> &lambda,
+ const SparseArray<double> &W,
+ SparseArray<double> &M, double omega,
+ unsigned int nIter, double threshold, double lambdaBound)
+{
+ constructM(C, W, M);
+ double lastMaxDiff = std::numeric_limits<double>::max();
+ for (unsigned int i = 0; i < nIter; i++) {
+ double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound));
+ if (maxDiff < threshold) {
+ LOG(RPiAlsc, Debug)
+ << "Stop after " << i + 1 << " iterations";
+ break;
+ }
+ /*
+ * this happens very occasionally (so make a note), though
+ * doesn't seem to matter
+ */
+ if (maxDiff > lastMaxDiff)
+ LOG(RPiAlsc, Debug)
+ << "Iteration " << i << ": maxDiff gone up "
+ << lastMaxDiff << " to " << maxDiff;
+ lastMaxDiff = maxDiff;
+ }
+ /* We're going to normalise the lambdas so the total average is 1. */
+ reaverage(lambda);
+}
+
+static void addLuminanceRb(Array2D<double> &result, const Array2D<double> &lambda,
+ const Array2D<double> &luminanceLut,
+ double luminanceStrength)
+{
+ for (unsigned int i = 0; i < result.size(); i++)
+ result[i] = lambda[i] * ((luminanceLut[i] - 1) * luminanceStrength + 1);
+}
+
+static void addLuminanceG(Array2D<double> &result, double lambda,
+ const Array2D<double> &luminanceLut,
+ double luminanceStrength)
+{
+ for (unsigned int i = 0; i < result.size(); i++)
+ result[i] = lambda * ((luminanceLut[i] - 1) * luminanceStrength + 1);
+}
+
+void addLuminanceToTables(std::array<Array2D<double>, 3> &results,
+ const Array2D<double> &lambdaR,
+ double lambdaG, const Array2D<double> &lambdaB,
+ const Array2D<double> &luminanceLut,
+ double luminanceStrength)
+{
+ addLuminanceRb(results[0], lambdaR, luminanceLut, luminanceStrength);
+ addLuminanceG(results[1], lambdaG, luminanceLut, luminanceStrength);
+ addLuminanceRb(results[2], lambdaB, luminanceLut, luminanceStrength);
+ for (auto &r : results)
+ normalise(r);
+}
+
+void Alsc::doAlsc()
+{
+ Array2D<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR = tmpC_[2],
+ &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];
+ SparseArray<double> &wr = tmpM_[0], &wb = tmpM_[1], &M = tmpM_[2];
+
+ /*
+ * Calculate our R/B ("Cr"/"Cb") colour statistics, and assess which are
+ * usable.
+ */
+ calculateCrCb(statistics_, cr, cb, config_.minCount, config_.minG);
+ /*
+ * Fetch the new calibrations (if any) for this CT. Resample them in
+ * case the camera mode is not full-frame.
+ */
+ getCalTable(ct_, config_.calibrationsCr, calTableTmp);
+ resampleCalTable(calTableTmp, cameraMode_, calTableR);
+ getCalTable(ct_, config_.calibrationsCb, calTableTmp);
+ resampleCalTable(calTableTmp, cameraMode_, calTableB);
+ /*
+ * You could print out the cal tables for this image here, if you're
+ * tuning the algorithm...
+ * Apply any calibration to the statistics, so the adaptive algorithm
+ * makes only the extra adjustments.
+ */
+ applyCalTable(calTableR, cr);
+ applyCalTable(calTableB, cb);
+ /* Compute weights between zones. */
+ computeW(cr, config_.sigmaCr, wr);
+ computeW(cb, config_.sigmaCb, wb);
+ /* Run Gauss-Seidel iterations over the resulting matrix, for R and B. */
+ runMatrixIterations(cr, lambdaR_, wr, M, config_.omega, config_.nIter,
+ config_.threshold, config_.lambdaBound);
+ runMatrixIterations(cb, lambdaB_, wb, M, config_.omega, config_.nIter,
+ config_.threshold, config_.lambdaBound);
+ /*
+ * Fold the calibrated gains into our final lambda values. (Note that on
+ * the next run, we re-start with the lambda values that don't have the
+ * calibration gains included.)
+ */
+ compensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);
+ compensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);
+ /* Fold in the luminance table at the appropriate strength. */
+ addLuminanceToTables(asyncResults_, asyncLambdaR_, 1.0,
+ asyncLambdaB_, luminanceTable_,
+ config_.luminanceStrength);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Alsc(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/alsc.h b/src/ipa/rpi/controller/rpi/alsc.h
new file mode 100644
index 00000000..0b6d9478
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/alsc.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * alsc.h - ALSC (auto lens shading correction) control algorithm
+ */
+#pragma once
+
+#include <array>
+#include <mutex>
+#include <condition_variable>
+#include <thread>
+#include <vector>
+
+#include <libcamera/geometry.h>
+
+#include "../algorithm.h"
+#include "../alsc_status.h"
+#include "../statistics.h"
+
+namespace RPiController {
+
+/* Algorithm to generate automagic LSC (Lens Shading Correction) tables. */
+
+/*
+ * The Array2D class is a very thin wrapper round std::vector so that it can
+ * be used in exactly the same way in the code but carries its correct width
+ * and height ("dimensions") with it.
+ */
+
+template<typename T>
+class Array2D
+{
+public:
+ using Size = libcamera::Size;
+
+ const Size &dimensions() const { return dimensions_; }
+
+ size_t size() const { return data_.size(); }
+
+ const std::vector<T> &data() const { return data_; }
+
+ void resize(const Size &dims)
+ {
+ dimensions_ = dims;
+ data_.resize(dims.width * dims.height);
+ }
+
+ void resize(const Size &dims, const T &value)
+ {
+ resize(dims);
+ std::fill(data_.begin(), data_.end(), value);
+ }
+
+ T &operator[](int index) { return data_[index]; }
+
+ const T &operator[](int index) const { return data_[index]; }
+
+ T *ptr() { return data_.data(); }
+
+ const T *ptr() const { return data_.data(); }
+
+ auto begin() { return data_.begin(); }
+ auto end() { return data_.end(); }
+
+private:
+ Size dimensions_;
+ std::vector<T> data_;
+};
+
+/*
+ * We'll use the term SparseArray for the large sparse matrices that are
+ * XY tall but have only 4 non-zero elements on each row.
+ */
+
+template<typename T>
+using SparseArray = std::vector<std::array<T, 4>>;
+
+struct AlscCalibration {
+ double ct;
+ Array2D<double> table;
+};
+
+struct AlscConfig {
+ /* Only repeat the ALSC calculation every "this many" frames */
+ uint16_t framePeriod;
+ /* number of initial frames for which speed taken as 1.0 (maximum) */
+ uint16_t startupFrames;
+ /* IIR filter speed applied to algorithm results */
+ double speed;
+ double sigmaCr;
+ double sigmaCb;
+ double minCount;
+ uint16_t minG;
+ double omega;
+ uint32_t nIter;
+ Array2D<double> luminanceLut;
+ double luminanceStrength;
+ std::vector<AlscCalibration> calibrationsCr;
+ std::vector<AlscCalibration> calibrationsCb;
+ double defaultCt; /* colour temperature if no metadata found */
+ double threshold; /* iteration termination threshold */
+ double lambdaBound; /* upper/lower bound for lambda from a value of 1 */
+ libcamera::Size tableSize;
+};
+
+class Alsc : public Algorithm
+{
+public:
+ Alsc(Controller *controller = NULL);
+ ~Alsc();
+ char const *name() const override;
+ void initialise() override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+
+private:
+ /* configuration is read-only, and available to both threads */
+ AlscConfig config_;
+ bool firstTime_;
+ CameraMode cameraMode_;
+ Array2D<double> luminanceTable_;
+ std::thread asyncThread_;
+ void asyncFunc(); /* asynchronous thread function */
+ std::mutex mutex_;
+ /* condvar for async thread to wait on */
+ std::condition_variable asyncSignal_;
+ /* condvar for synchronous thread to wait on */
+ std::condition_variable syncSignal_;
+ /* for sync thread to check if async thread finished (requires mutex) */
+ bool asyncFinished_;
+ /* for async thread to check if it's been told to run (requires mutex) */
+ bool asyncStart_;
+ /* for async thread to check if it's been told to quit (requires mutex) */
+ bool asyncAbort_;
+
+ /*
+ * The following are only for the synchronous thread to use:
+ * for sync thread to note its has asked async thread to run
+ */
+ bool asyncStarted_;
+ /* counts up to framePeriod before restarting the async thread */
+ int framePhase_;
+ /* counts up to startupFrames */
+ int frameCount_;
+ /* counts up to startupFrames for Process function */
+ int frameCount2_;
+ std::array<Array2D<double>, 3> syncResults_;
+ std::array<Array2D<double>, 3> prevSyncResults_;
+ void waitForAysncThread();
+ /*
+ * The following are for the asynchronous thread to use, though the main
+ * thread can set/reset them if the async thread is known to be idle:
+ */
+ void restartAsync(StatisticsPtr &stats, Metadata *imageMetadata);
+ /* copy out the results from the async thread so that it can be restarted */
+ void fetchAsyncResults();
+ double ct_;
+ RgbyRegions statistics_;
+ std::array<Array2D<double>, 3> asyncResults_;
+ Array2D<double> asyncLambdaR_;
+ Array2D<double> asyncLambdaB_;
+ void doAlsc();
+ Array2D<double> lambdaR_;
+ Array2D<double> lambdaB_;
+
+ /* Temporaries for the computations */
+ std::array<Array2D<double>, 5> tmpC_;
+ std::array<SparseArray<double>, 3> tmpM_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp
new file mode 100644
index 00000000..dde5785a
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/awb.cpp
@@ -0,0 +1,751 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * awb.cpp - AWB control algorithm
+ */
+
+#include <assert.h>
+#include <functional>
+
+#include <libcamera/base/log.h>
+
+#include "../lux_status.h"
+
+#include "alsc_status.h"
+#include "awb.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiAwb)
+
+#define NAME "rpi.awb"
+
+/*
+ * todo - the locking in this algorithm needs some tidying up as has been done
+ * elsewhere (ALSC and AGC).
+ */
+
+int AwbMode::read(const libcamera::YamlObject &params)
+{
+ auto value = params["lo"].get<double>();
+ if (!value)
+ return -EINVAL;
+ ctLo = *value;
+
+ value = params["hi"].get<double>();
+ if (!value)
+ return -EINVAL;
+ ctHi = *value;
+
+ return 0;
+}
+
+int AwbPrior::read(const libcamera::YamlObject &params)
+{
+ auto value = params["lux"].get<double>();
+ if (!value)
+ return -EINVAL;
+ lux = *value;
+
+ return prior.read(params["prior"]);
+}
+
+static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject &params)
+{
+ if (params.size() % 3) {
+ LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry";
+ return -EINVAL;
+ }
+
+ if (params.size() < 6) {
+ LOG(RPiAwb, Error) << "AwbConfig: insufficient points in CT curve";
+ return -EINVAL;
+ }
+
+ const auto &list = params.asList();
+
+ for (auto it = list.begin(); it != list.end(); it++) {
+ auto value = it->get<double>();
+ if (!value)
+ return -EINVAL;
+ double ct = *value;
+
+ assert(it == list.begin() || ct != ctR.domain().end);
+
+ value = (++it)->get<double>();
+ if (!value)
+ return -EINVAL;
+ ctR.append(ct, *value);
+
+ value = (++it)->get<double>();
+ if (!value)
+ return -EINVAL;
+ ctB.append(ct, *value);
+ }
+
+ return 0;
+}
+
+int AwbConfig::read(const libcamera::YamlObject &params)
+{
+ int ret;
+
+ bayes = params["bayes"].get<int>(1);
+ framePeriod = params["frame_period"].get<uint16_t>(10);
+ startupFrames = params["startup_frames"].get<uint16_t>(10);
+ convergenceFrames = params["convergence_frames"].get<unsigned int>(3);
+ speed = params["speed"].get<double>(0.05);
+
+ if (params.contains("ct_curve")) {
+ ret = readCtCurve(ctR, ctB, params["ct_curve"]);
+ if (ret)
+ return ret;
+ /* We will want the inverse functions of these too. */
+ ctRInverse = ctR.inverse();
+ ctBInverse = ctB.inverse();
+ }
+
+ if (params.contains("priors")) {
+ for (const auto &p : params["priors"].asList()) {
+ AwbPrior prior;
+ ret = prior.read(p);
+ if (ret)
+ return ret;
+ if (!priors.empty() && prior.lux <= priors.back().lux) {
+ LOG(RPiAwb, Error) << "AwbConfig: Prior must be ordered in increasing lux value";
+ return -EINVAL;
+ }
+ priors.push_back(prior);
+ }
+ if (priors.empty()) {
+ LOG(RPiAwb, Error) << "AwbConfig: no AWB priors configured";
+ return ret;
+ }
+ }
+ if (params.contains("modes")) {
+ for (const auto &[key, value] : params["modes"].asDict()) {
+ ret = modes[key].read(value);
+ if (ret)
+ return ret;
+ if (defaultMode == nullptr)
+ defaultMode = &modes[key];
+ }
+ if (defaultMode == nullptr) {
+ LOG(RPiAwb, Error) << "AwbConfig: no AWB modes configured";
+ return -EINVAL;
+ }
+ }
+
+ minPixels = params["min_pixels"].get<double>(16.0);
+ minG = params["min_G"].get<uint16_t>(32);
+ minRegions = params["min_regions"].get<uint32_t>(10);
+ deltaLimit = params["delta_limit"].get<double>(0.2);
+ coarseStep = params["coarse_step"].get<double>(0.2);
+ transversePos = params["transverse_pos"].get<double>(0.01);
+ transverseNeg = params["transverse_neg"].get<double>(0.01);
+ if (transversePos <= 0 || transverseNeg <= 0) {
+ LOG(RPiAwb, Error) << "AwbConfig: transverse_pos/neg must be > 0";
+ return -EINVAL;
+ }
+
+ sensitivityR = params["sensitivity_r"].get<double>(1.0);
+ sensitivityB = params["sensitivity_b"].get<double>(1.0);
+
+ if (bayes) {
+ if (ctR.empty() || ctB.empty() || priors.empty() ||
+ defaultMode == nullptr) {
+ LOG(RPiAwb, Warning)
+ << "Bayesian AWB mis-configured - switch to Grey method";
+ bayes = false;
+ }
+ }
+ fast = params[fast].get<int>(bayes); /* default to fast for Bayesian, otherwise slow */
+ whitepointR = params["whitepoint_r"].get<double>(0.0);
+ whitepointB = params["whitepoint_b"].get<double>(0.0);
+ if (bayes == false)
+ sensitivityR = sensitivityB = 1.0; /* nor do sensitivities make any sense */
+ return 0;
+}
+
+Awb::Awb(Controller *controller)
+ : AwbAlgorithm(controller)
+{
+ asyncAbort_ = asyncStart_ = asyncStarted_ = asyncFinished_ = false;
+ mode_ = nullptr;
+ manualR_ = manualB_ = 0.0;
+ asyncThread_ = std::thread(std::bind(&Awb::asyncFunc, this));
+}
+
+Awb::~Awb()
+{
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ asyncAbort_ = true;
+ }
+ asyncSignal_.notify_one();
+ asyncThread_.join();
+}
+
+char const *Awb::name() const
+{
+ return NAME;
+}
+
+int Awb::read(const libcamera::YamlObject &params)
+{
+ return config_.read(params);
+}
+
+void Awb::initialise()
+{
+ frameCount_ = framePhase_ = 0;
+ /*
+ * Put something sane into the status that we are filtering towards,
+ * just in case the first few frames don't have anything meaningful in
+ * them.
+ */
+ if (!config_.ctR.empty() && !config_.ctB.empty()) {
+ syncResults_.temperatureK = config_.ctR.domain().clip(4000);
+ syncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK);
+ syncResults_.gainG = 1.0;
+ syncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK);
+ } else {
+ /* random values just to stop the world blowing up */
+ syncResults_.temperatureK = 4500;
+ syncResults_.gainR = syncResults_.gainG = syncResults_.gainB = 1.0;
+ }
+ prevSyncResults_ = syncResults_;
+ asyncResults_ = syncResults_;
+}
+
+void Awb::initialValues(double &gainR, double &gainB)
+{
+ gainR = syncResults_.gainR;
+ gainB = syncResults_.gainB;
+}
+
+void Awb::disableAuto()
+{
+ /* Freeze the most recent values, and treat them as manual gains */
+ manualR_ = syncResults_.gainR = prevSyncResults_.gainR;
+ manualB_ = syncResults_.gainB = prevSyncResults_.gainB;
+ syncResults_.gainG = prevSyncResults_.gainG;
+ syncResults_.temperatureK = prevSyncResults_.temperatureK;
+}
+
+void Awb::enableAuto()
+{
+ manualR_ = 0.0;
+ manualB_ = 0.0;
+}
+
+unsigned int Awb::getConvergenceFrames() const
+{
+ /*
+ * If not in auto mode, there is no convergence
+ * to happen, so no need to drop any frames - return zero.
+ */
+ if (!isAutoEnabled())
+ return 0;
+ else
+ return config_.convergenceFrames;
+}
+
+void Awb::setMode(std::string const &modeName)
+{
+ modeName_ = modeName;
+}
+
+void Awb::setManualGains(double manualR, double manualB)
+{
+ /* If any of these are 0.0, we swich back to auto. */
+ manualR_ = manualR;
+ manualB_ = manualB;
+ /*
+ * If not in auto mode, set these values into the syncResults which
+ * means that Prepare() will adopt them immediately.
+ */
+ if (!isAutoEnabled()) {
+ syncResults_.gainR = prevSyncResults_.gainR = manualR_;
+ syncResults_.gainG = prevSyncResults_.gainG = 1.0;
+ syncResults_.gainB = prevSyncResults_.gainB = manualB_;
+ if (config_.bayes) {
+ /* Also estimate the best corresponding colour temperature from the curves. */
+ double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_));
+ double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_));
+ prevSyncResults_.temperatureK = (ctR + ctB) / 2;
+ syncResults_.temperatureK = prevSyncResults_.temperatureK;
+ }
+ }
+}
+
+void Awb::switchMode([[maybe_unused]] CameraMode const &cameraMode,
+ Metadata *metadata)
+{
+ /* Let other algorithms know the current white balance values. */
+ metadata->set("awb.status", prevSyncResults_);
+}
+
+bool Awb::isAutoEnabled() const
+{
+ return manualR_ == 0.0 || manualB_ == 0.0;
+}
+
+void Awb::fetchAsyncResults()
+{
+ LOG(RPiAwb, Debug) << "Fetch AWB results";
+ asyncFinished_ = false;
+ asyncStarted_ = false;
+ /*
+ * It's possible manual gains could be set even while the async
+ * thread was running, so only copy the results if still in auto mode.
+ */
+ if (isAutoEnabled())
+ syncResults_ = asyncResults_;
+}
+
+void Awb::restartAsync(StatisticsPtr &stats, double lux)
+{
+ LOG(RPiAwb, Debug) << "Starting AWB calculation";
+ /* this makes a new reference which belongs to the asynchronous thread */
+ statistics_ = stats;
+ /* store the mode as it could technically change */
+ auto m = config_.modes.find(modeName_);
+ mode_ = m != config_.modes.end()
+ ? &m->second
+ : (mode_ == nullptr ? config_.defaultMode : mode_);
+ lux_ = lux;
+ framePhase_ = 0;
+ asyncStarted_ = true;
+ size_t len = modeName_.copy(asyncResults_.mode,
+ sizeof(asyncResults_.mode) - 1);
+ asyncResults_.mode[len] = '\0';
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ asyncStart_ = true;
+ }
+ asyncSignal_.notify_one();
+}
+
+void Awb::prepare(Metadata *imageMetadata)
+{
+ if (frameCount_ < (int)config_.startupFrames)
+ frameCount_++;
+ double speed = frameCount_ < (int)config_.startupFrames
+ ? 1.0
+ : config_.speed;
+ LOG(RPiAwb, Debug)
+ << "frame_count " << frameCount_ << " speed " << speed;
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (asyncStarted_ && asyncFinished_)
+ fetchAsyncResults();
+ }
+ /* Finally apply IIR filter to results and put into metadata. */
+ memcpy(prevSyncResults_.mode, syncResults_.mode,
+ sizeof(prevSyncResults_.mode));
+ prevSyncResults_.temperatureK = speed * syncResults_.temperatureK +
+ (1.0 - speed) * prevSyncResults_.temperatureK;
+ prevSyncResults_.gainR = speed * syncResults_.gainR +
+ (1.0 - speed) * prevSyncResults_.gainR;
+ prevSyncResults_.gainG = speed * syncResults_.gainG +
+ (1.0 - speed) * prevSyncResults_.gainG;
+ prevSyncResults_.gainB = speed * syncResults_.gainB +
+ (1.0 - speed) * prevSyncResults_.gainB;
+ imageMetadata->set("awb.status", prevSyncResults_);
+ LOG(RPiAwb, Debug)
+ << "Using AWB gains r " << prevSyncResults_.gainR << " g "
+ << prevSyncResults_.gainG << " b "
+ << prevSyncResults_.gainB;
+}
+
+void Awb::process(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ /* Count frames since we last poked the async thread. */
+ if (framePhase_ < (int)config_.framePeriod)
+ framePhase_++;
+ LOG(RPiAwb, Debug) << "frame_phase " << framePhase_;
+ /* We do not restart the async thread if we're not in auto mode. */
+ if (isAutoEnabled() &&
+ (framePhase_ >= (int)config_.framePeriod ||
+ frameCount_ < (int)config_.startupFrames)) {
+ /* Update any settings and any image metadata that we need. */
+ struct LuxStatus luxStatus = {};
+ luxStatus.lux = 400; /* in case no metadata */
+ if (imageMetadata->get("lux.status", luxStatus) != 0)
+ LOG(RPiAwb, Debug) << "No lux metadata found";
+ LOG(RPiAwb, Debug) << "Awb lux value is " << luxStatus.lux;
+
+ if (asyncStarted_ == false)
+ restartAsync(stats, luxStatus.lux);
+ }
+}
+
+void Awb::asyncFunc()
+{
+ while (true) {
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ asyncSignal_.wait(lock, [&] {
+ return asyncStart_ || asyncAbort_;
+ });
+ asyncStart_ = false;
+ if (asyncAbort_)
+ break;
+ }
+ doAwb();
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ asyncFinished_ = true;
+ }
+ syncSignal_.notify_one();
+ }
+}
+
+static void generateStats(std::vector<Awb::RGB> &zones,
+ StatisticsPtr &stats, double minPixels,
+ double minG, Metadata &globalMetadata)
+{
+ std::scoped_lock<RPiController::Metadata> l(globalMetadata);
+
+ for (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {
+ Awb::RGB zone;
+ auto &region = stats->awbRegions.get(i);
+ if (region.counted >= minPixels) {
+ zone.G = region.val.gSum / region.counted;
+ if (zone.G < minG)
+ continue;
+ zone.R = region.val.rSum / region.counted;
+ zone.B = region.val.bSum / region.counted;
+ /* Factor in the ALSC applied colour shading correction if required. */
+ const AlscStatus *alscStatus = globalMetadata.getLocked<AlscStatus>("alsc.status");
+ if (stats->colourStatsPos == Statistics::ColourStatsPos::PreLsc && alscStatus) {
+ zone.R *= alscStatus->r[i];
+ zone.G *= alscStatus->g[i];
+ zone.B *= alscStatus->b[i];
+ }
+ zones.push_back(zone);
+ }
+ }
+}
+
+void Awb::prepareStats()
+{
+ zones_.clear();
+ /*
+ * LSC has already been applied to the stats in this pipeline, so stop
+ * any LSC compensation. We also ignore config_.fast in this version.
+ */
+ generateStats(zones_, statistics_, config_.minPixels,
+ config_.minG, getGlobalMetadata());
+ /*
+ * apply sensitivities, so values appear to come from our "canonical"
+ * sensor.
+ */
+ for (auto &zone : zones_) {
+ zone.R *= config_.sensitivityR;
+ zone.B *= config_.sensitivityB;
+ }
+}
+
+double Awb::computeDelta2Sum(double gainR, double gainB)
+{
+ /*
+ * Compute the sum of the squared colour error (non-greyness) as it
+ * appears in the log likelihood equation.
+ */
+ double delta2Sum = 0;
+ for (auto &z : zones_) {
+ double deltaR = gainR * z.R - 1 - config_.whitepointR;
+ double deltaB = gainB * z.B - 1 - config_.whitepointB;
+ double delta2 = deltaR * deltaR + deltaB * deltaB;
+ /* LOG(RPiAwb, Debug) << "deltaR " << deltaR << " deltaB " << deltaB << " delta2 " << delta2; */
+ delta2 = std::min(delta2, config_.deltaLimit);
+ delta2Sum += delta2;
+ }
+ return delta2Sum;
+}
+
+Pwl Awb::interpolatePrior()
+{
+ /*
+ * Interpolate the prior log likelihood function for our current lux
+ * value.
+ */
+ if (lux_ <= config_.priors.front().lux)
+ return config_.priors.front().prior;
+ else if (lux_ >= config_.priors.back().lux)
+ return config_.priors.back().prior;
+ else {
+ int idx = 0;
+ /* find which two we lie between */
+ while (config_.priors[idx + 1].lux < lux_)
+ idx++;
+ double lux0 = config_.priors[idx].lux,
+ lux1 = config_.priors[idx + 1].lux;
+ return Pwl::combine(config_.priors[idx].prior,
+ config_.priors[idx + 1].prior,
+ [&](double /*x*/, double y0, double y1) {
+ return y0 + (y1 - y0) *
+ (lux_ - lux0) / (lux1 - lux0);
+ });
+ }
+}
+
+static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,
+ Pwl::Point const &c)
+{
+ /*
+ * Given 3 points on a curve, find the extremum of the function in that
+ * interval by fitting a quadratic.
+ */
+ const double eps = 1e-3;
+ Pwl::Point ca = c - a, ba = b - a;
+ double denominator = 2 * (ba.y * ca.x - ca.y * ba.x);
+ if (abs(denominator) > eps) {
+ double numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x;
+ double result = numerator / denominator + a.x;
+ return std::max(a.x, std::min(c.x, result));
+ }
+ /* has degenerated to straight line segment */
+ return a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x);
+}
+
+double Awb::coarseSearch(Pwl const &prior)
+{
+ points_.clear(); /* assume doesn't deallocate memory */
+ size_t bestPoint = 0;
+ double t = mode_->ctLo;
+ int spanR = 0, spanB = 0;
+ /* Step down the CT curve evaluating log likelihood. */
+ while (true) {
+ double r = config_.ctR.eval(t, &spanR);
+ double b = config_.ctB.eval(t, &spanB);
+ double gainR = 1 / r, gainB = 1 / b;
+ double delta2Sum = computeDelta2Sum(gainR, gainB);
+ double priorLogLikelihood = prior.eval(prior.domain().clip(t));
+ double finalLogLikelihood = delta2Sum - priorLogLikelihood;
+ LOG(RPiAwb, Debug)
+ << "t: " << t << " gain R " << gainR << " gain B "
+ << gainB << " delta2_sum " << delta2Sum
+ << " prior " << priorLogLikelihood << " final "
+ << finalLogLikelihood;
+ points_.push_back(Pwl::Point(t, finalLogLikelihood));
+ if (points_.back().y < points_[bestPoint].y)
+ bestPoint = points_.size() - 1;
+ if (t == mode_->ctHi)
+ break;
+ /* for even steps along the r/b curve scale them by the current t */
+ t = std::min(t + t / 10 * config_.coarseStep, mode_->ctHi);
+ }
+ t = points_[bestPoint].x;
+ LOG(RPiAwb, Debug) << "Coarse search found CT " << t;
+ /*
+ * We have the best point of the search, but refine it with a quadratic
+ * interpolation around its neighbours.
+ */
+ if (points_.size() > 2) {
+ unsigned long bp = std::min(bestPoint, points_.size() - 2);
+ bestPoint = std::max(1UL, bp);
+ t = interpolateQuadatric(points_[bestPoint - 1],
+ points_[bestPoint],
+ points_[bestPoint + 1]);
+ LOG(RPiAwb, Debug)
+ << "After quadratic refinement, coarse search has CT "
+ << t;
+ }
+ return t;
+}
+
+void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
+{
+ int spanR = -1, spanB = -1;
+ config_.ctR.eval(t, &spanR);
+ config_.ctB.eval(t, &spanB);
+ double step = t / 10 * config_.coarseStep * 0.1;
+ int nsteps = 5;
+ double rDiff = config_.ctR.eval(t + nsteps * step, &spanR) -
+ config_.ctR.eval(t - nsteps * step, &spanR);
+ double bDiff = config_.ctB.eval(t + nsteps * step, &spanB) -
+ config_.ctB.eval(t - nsteps * step, &spanB);
+ Pwl::Point transverse(bDiff, -rDiff);
+ if (transverse.len2() < 1e-6)
+ return;
+ /*
+ * unit vector orthogonal to the b vs. r function (pointing outwards
+ * with r and b increasing)
+ */
+ transverse = transverse / transverse.len();
+ double bestLogLikelihood = 0, bestT = 0, bestR = 0, bestB = 0;
+ double transverseRange = config_.transverseNeg + config_.transversePos;
+ const int maxNumDeltas = 12;
+ /* a transverse step approximately every 0.01 r/b units */
+ int numDeltas = floor(transverseRange * 100 + 0.5) + 1;
+ numDeltas = numDeltas < 3 ? 3 : (numDeltas > maxNumDeltas ? maxNumDeltas : numDeltas);
+ /*
+ * Step down CT curve. March a bit further if the transverse range is
+ * large.
+ */
+ nsteps += numDeltas;
+ for (int i = -nsteps; i <= nsteps; i++) {
+ double tTest = t + i * step;
+ double priorLogLikelihood =
+ prior.eval(prior.domain().clip(tTest));
+ double rCurve = config_.ctR.eval(tTest, &spanR);
+ double bCurve = config_.ctB.eval(tTest, &spanB);
+ /* x will be distance off the curve, y the log likelihood there */
+ Pwl::Point points[maxNumDeltas];
+ int bestPoint = 0;
+ /* Take some measurements transversely *off* the CT curve. */
+ for (int j = 0; j < numDeltas; j++) {
+ points[j].x = -config_.transverseNeg +
+ (transverseRange * j) / (numDeltas - 1);
+ Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +
+ transverse * points[j].x;
+ double rTest = rbTest.x, bTest = rbTest.y;
+ double gainR = 1 / rTest, gainB = 1 / bTest;
+ double delta2Sum = computeDelta2Sum(gainR, gainB);
+ points[j].y = delta2Sum - priorLogLikelihood;
+ LOG(RPiAwb, Debug)
+ << "At t " << tTest << " r " << rTest << " b "
+ << bTest << ": " << points[j].y;
+ if (points[j].y < points[bestPoint].y)
+ bestPoint = j;
+ }
+ /*
+ * We have NUM_DELTAS points transversely across the CT curve,
+ * now let's do a quadratic interpolation for the best result.
+ */
+ bestPoint = std::max(1, std::min(bestPoint, numDeltas - 2));
+ Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +
+ transverse * interpolateQuadatric(points[bestPoint - 1],
+ points[bestPoint],
+ points[bestPoint + 1]);
+ double rTest = rbTest.x, bTest = rbTest.y;
+ double gainR = 1 / rTest, gainB = 1 / bTest;
+ double delta2Sum = computeDelta2Sum(gainR, gainB);
+ double finalLogLikelihood = delta2Sum - priorLogLikelihood;
+ LOG(RPiAwb, Debug)
+ << "Finally "
+ << tTest << " r " << rTest << " b " << bTest << ": "
+ << finalLogLikelihood
+ << (finalLogLikelihood < bestLogLikelihood ? " BEST" : "");
+ if (bestT == 0 || finalLogLikelihood < bestLogLikelihood)
+ bestLogLikelihood = finalLogLikelihood,
+ bestT = tTest, bestR = rTest, bestB = bTest;
+ }
+ t = bestT, r = bestR, b = bestB;
+ LOG(RPiAwb, Debug)
+ << "Fine search found t " << t << " r " << r << " b " << b;
+}
+
+void Awb::awbBayes()
+{
+ /*
+ * May as well divide out G to save computeDelta2Sum from doing it over
+ * and over.
+ */
+ for (auto &z : zones_)
+ z.R = z.R / (z.G + 1), z.B = z.B / (z.G + 1);
+ /*
+ * Get the current prior, and scale according to how many zones are
+ * valid... not entirely sure about this.
+ */
+ Pwl prior = interpolatePrior();
+ prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());
+ prior.map([](double x, double y) {
+ LOG(RPiAwb, Debug) << "(" << x << "," << y << ")";
+ });
+ double t = coarseSearch(prior);
+ double r = config_.ctR.eval(t);
+ double b = config_.ctB.eval(t);
+ LOG(RPiAwb, Debug)
+ << "After coarse search: r " << r << " b " << b << " (gains r "
+ << 1 / r << " b " << 1 / b << ")";
+ /*
+ * Not entirely sure how to handle the fine search yet. Mostly the
+ * estimated CT is already good enough, but the fine search allows us to
+ * wander transverely off the CT curve. Under some illuminants, where
+ * there may be more or less green light, this may prove beneficial,
+ * though I probably need more real datasets before deciding exactly how
+ * this should be controlled and tuned.
+ */
+ fineSearch(t, r, b, prior);
+ LOG(RPiAwb, Debug)
+ << "After fine search: r " << r << " b " << b << " (gains r "
+ << 1 / r << " b " << 1 / b << ")";
+ /*
+ * Write results out for the main thread to pick up. Remember to adjust
+ * the gains from the ones that the "canonical sensor" would require to
+ * the ones needed by *this* sensor.
+ */
+ asyncResults_.temperatureK = t;
+ asyncResults_.gainR = 1.0 / r * config_.sensitivityR;
+ asyncResults_.gainG = 1.0;
+ asyncResults_.gainB = 1.0 / b * config_.sensitivityB;
+}
+
+void Awb::awbGrey()
+{
+ LOG(RPiAwb, Debug) << "Grey world AWB";
+ /*
+ * Make a separate list of the derivatives for each of red and blue, so
+ * that we can sort them to exclude the extreme gains. We could
+ * consider some variations, such as normalising all the zones first, or
+ * doing an L2 average etc.
+ */
+ std::vector<RGB> &derivsR(zones_);
+ std::vector<RGB> derivsB(derivsR);
+ std::sort(derivsR.begin(), derivsR.end(),
+ [](RGB const &a, RGB const &b) {
+ return a.G * b.R < b.G * a.R;
+ });
+ std::sort(derivsB.begin(), derivsB.end(),
+ [](RGB const &a, RGB const &b) {
+ return a.G * b.B < b.G * a.B;
+ });
+ /* Average the middle half of the values. */
+ int discard = derivsR.size() / 4;
+ RGB sumR(0, 0, 0), sumB(0, 0, 0);
+ for (auto ri = derivsR.begin() + discard,
+ bi = derivsB.begin() + discard;
+ ri != derivsR.end() - discard; ri++, bi++)
+ sumR += *ri, sumB += *bi;
+ double gainR = sumR.G / (sumR.R + 1),
+ gainB = sumB.G / (sumB.B + 1);
+ asyncResults_.temperatureK = 4500; /* don't know what it is */
+ asyncResults_.gainR = gainR;
+ asyncResults_.gainG = 1.0;
+ asyncResults_.gainB = gainB;
+}
+
+void Awb::doAwb()
+{
+ prepareStats();
+ LOG(RPiAwb, Debug) << "Valid zones: " << zones_.size();
+ if (zones_.size() > config_.minRegions) {
+ if (config_.bayes)
+ awbBayes();
+ else
+ awbGrey();
+ LOG(RPiAwb, Debug)
+ << "CT found is "
+ << asyncResults_.temperatureK
+ << " with gains r " << asyncResults_.gainR
+ << " and b " << asyncResults_.gainB;
+ }
+ /*
+ * we're done with these; we may as well relinquish our hold on the
+ * pointer.
+ */
+ statistics_.reset();
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Awb(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h
new file mode 100644
index 00000000..cde6a62f
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/awb.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * awb.h - AWB control algorithm
+ */
+#pragma once
+
+#include <mutex>
+#include <condition_variable>
+#include <thread>
+
+#include "../awb_algorithm.h"
+#include "../pwl.h"
+#include "../awb_status.h"
+#include "../statistics.h"
+
+namespace RPiController {
+
+/* Control algorithm to perform AWB calculations. */
+
+struct AwbMode {
+ int read(const libcamera::YamlObject &params);
+ double ctLo; /* low CT value for search */
+ double ctHi; /* high CT value for search */
+};
+
+struct AwbPrior {
+ int read(const libcamera::YamlObject &params);
+ double lux; /* lux level */
+ Pwl prior; /* maps CT to prior log likelihood for this lux level */
+};
+
+struct AwbConfig {
+ AwbConfig() : defaultMode(nullptr) {}
+ int read(const libcamera::YamlObject &params);
+ /* Only repeat the AWB calculation every "this many" frames */
+ uint16_t framePeriod;
+ /* number of initial frames for which speed taken as 1.0 (maximum) */
+ uint16_t startupFrames;
+ unsigned int convergenceFrames; /* approx number of frames to converge */
+ double speed; /* IIR filter speed applied to algorithm results */
+ bool fast; /* "fast" mode uses a 16x16 rather than 32x32 grid */
+ Pwl ctR; /* function maps CT to r (= R/G) */
+ Pwl ctB; /* function maps CT to b (= B/G) */
+ Pwl ctRInverse; /* inverse of ctR */
+ Pwl ctBInverse; /* inverse of ctB */
+ /* table of illuminant priors at different lux levels */
+ std::vector<AwbPrior> priors;
+ /* AWB "modes" (determines the search range) */
+ std::map<std::string, AwbMode> modes;
+ AwbMode *defaultMode; /* mode used if no mode selected */
+ /*
+ * minimum proportion of pixels counted within AWB region for it to be
+ * "useful"
+ */
+ double minPixels;
+ /* minimum G value of those pixels, to be regarded a "useful" */
+ uint16_t minG;
+ /*
+ * number of AWB regions that must be "useful" in order to do the AWB
+ * calculation
+ */
+ uint32_t minRegions;
+ /* clamp on colour error term (so as not to penalise non-grey excessively) */
+ double deltaLimit;
+ /* step size control in coarse search */
+ double coarseStep;
+ /* how far to wander off CT curve towards "more purple" */
+ double transversePos;
+ /* how far to wander off CT curve towards "more green" */
+ double transverseNeg;
+ /*
+ * red sensitivity ratio (set to canonical sensor's R/G divided by this
+ * sensor's R/G)
+ */
+ double sensitivityR;
+ /*
+ * blue sensitivity ratio (set to canonical sensor's B/G divided by this
+ * sensor's B/G)
+ */
+ double sensitivityB;
+ /* The whitepoint (which we normally "aim" for) can be moved. */
+ double whitepointR;
+ double whitepointB;
+ bool bayes; /* use Bayesian algorithm */
+};
+
+class Awb : public AwbAlgorithm
+{
+public:
+ Awb(Controller *controller = NULL);
+ ~Awb();
+ char const *name() const override;
+ void initialise() override;
+ int read(const libcamera::YamlObject &params) override;
+ unsigned int getConvergenceFrames() const override;
+ void initialValues(double &gainR, double &gainB) override;
+ void setMode(std::string const &name) override;
+ void setManualGains(double manualR, double manualB) override;
+ void enableAuto() override;
+ void disableAuto() override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+ struct RGB {
+ RGB(double r = 0, double g = 0, double b = 0)
+ : R(r), G(g), B(b)
+ {
+ }
+ double R, G, B;
+ RGB &operator+=(RGB const &other)
+ {
+ R += other.R, G += other.G, B += other.B;
+ return *this;
+ }
+ };
+
+private:
+ bool isAutoEnabled() const;
+ /* configuration is read-only, and available to both threads */
+ AwbConfig config_;
+ std::thread asyncThread_;
+ void asyncFunc(); /* asynchronous thread function */
+ std::mutex mutex_;
+ /* condvar for async thread to wait on */
+ std::condition_variable asyncSignal_;
+ /* condvar for synchronous thread to wait on */
+ std::condition_variable syncSignal_;
+ /* for sync thread to check if async thread finished (requires mutex) */
+ bool asyncFinished_;
+ /* for async thread to check if it's been told to run (requires mutex) */
+ bool asyncStart_;
+ /* for async thread to check if it's been told to quit (requires mutex) */
+ bool asyncAbort_;
+
+ /*
+ * The following are only for the synchronous thread to use:
+ * for sync thread to note its has asked async thread to run
+ */
+ bool asyncStarted_;
+ /* counts up to framePeriod before restarting the async thread */
+ int framePhase_;
+ int frameCount_; /* counts up to startup_frames */
+ AwbStatus syncResults_;
+ AwbStatus prevSyncResults_;
+ std::string modeName_;
+ /*
+ * The following are for the asynchronous thread to use, though the main
+ * thread can set/reset them if the async thread is known to be idle:
+ */
+ void restartAsync(StatisticsPtr &stats, double lux);
+ /* copy out the results from the async thread so that it can be restarted */
+ void fetchAsyncResults();
+ StatisticsPtr statistics_;
+ AwbMode *mode_;
+ double lux_;
+ AwbStatus asyncResults_;
+ void doAwb();
+ void awbBayes();
+ void awbGrey();
+ void prepareStats();
+ double computeDelta2Sum(double gainR, double gainB);
+ Pwl interpolatePrior();
+ double coarseSearch(Pwl const &prior);
+ void fineSearch(double &t, double &r, double &b, Pwl const &prior);
+ std::vector<RGB> zones_;
+ std::vector<Pwl::Point> points_;
+ /* manual r setting */
+ double manualR_;
+ /* manual b setting */
+ double manualB_;
+};
+
+static inline Awb::RGB operator+(Awb::RGB const &a, Awb::RGB const &b)
+{
+ return Awb::RGB(a.R + b.R, a.G + b.G, a.B + b.B);
+}
+static inline Awb::RGB operator-(Awb::RGB const &a, Awb::RGB const &b)
+{
+ return Awb::RGB(a.R - b.R, a.G - b.G, a.B - b.B);
+}
+static inline Awb::RGB operator*(double d, Awb::RGB const &rgb)
+{
+ return Awb::RGB(d * rgb.R, d * rgb.G, d * rgb.B);
+}
+static inline Awb::RGB operator*(Awb::RGB const &rgb, double d)
+{
+ return d * rgb;
+}
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/black_level.cpp b/src/ipa/rpi/controller/rpi/black_level.cpp
new file mode 100644
index 00000000..2e3db51f
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/black_level.cpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * black_level.cpp - black level control algorithm
+ */
+
+#include <math.h>
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+
+#include "../black_level_status.h"
+
+#include "black_level.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiBlackLevel)
+
+#define NAME "rpi.black_level"
+
+BlackLevel::BlackLevel(Controller *controller)
+ : BlackLevelAlgorithm(controller)
+{
+}
+
+char const *BlackLevel::name() const
+{
+ return NAME;
+}
+
+int BlackLevel::read(const libcamera::YamlObject &params)
+{
+ /* 64 in 10 bits scaled to 16 bits */
+ uint16_t blackLevel = params["black_level"].get<uint16_t>(4096);
+ blackLevelR_ = params["black_level_r"].get<uint16_t>(blackLevel);
+ blackLevelG_ = params["black_level_g"].get<uint16_t>(blackLevel);
+ blackLevelB_ = params["black_level_b"].get<uint16_t>(blackLevel);
+ LOG(RPiBlackLevel, Debug)
+ << " Read black levels red " << blackLevelR_
+ << " green " << blackLevelG_
+ << " blue " << blackLevelB_;
+ return 0;
+}
+
+void BlackLevel::initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG,
+ uint16_t &blackLevelB)
+{
+ blackLevelR = blackLevelR_;
+ blackLevelG = blackLevelG_;
+ blackLevelB = blackLevelB_;
+}
+
+void BlackLevel::prepare(Metadata *imageMetadata)
+{
+ /*
+ * Possibly we should think about doing this in a switchMode or
+ * something?
+ */
+ struct BlackLevelStatus status;
+ status.blackLevelR = blackLevelR_;
+ status.blackLevelG = blackLevelG_;
+ status.blackLevelB = blackLevelB_;
+ imageMetadata->set("black_level.status", status);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return new BlackLevel(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/black_level.h b/src/ipa/rpi/controller/rpi/black_level.h
new file mode 100644
index 00000000..d8c41c62
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/black_level.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * black_level.h - black level control algorithm
+ */
+#pragma once
+
+#include "../black_level_algorithm.h"
+#include "../black_level_status.h"
+
+/* This is our implementation of the "black level algorithm". */
+
+namespace RPiController {
+
+class BlackLevel : public BlackLevelAlgorithm
+{
+public:
+ BlackLevel(Controller *controller);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG,
+ uint16_t &blackLevelB) override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ double blackLevelR_;
+ double blackLevelG_;
+ double blackLevelB_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/cac.cpp b/src/ipa/rpi/controller/rpi/cac.cpp
new file mode 100644
index 00000000..f2c8d282
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/cac.cpp
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ *
+ * cac.cpp - Chromatic Aberration Correction algorithm
+ */
+#include "cac.h"
+
+#include <libcamera/base/log.h>
+
+#include "cac_status.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiCac)
+
+#define NAME "rpi.cac"
+
+Cac::Cac(Controller *controller)
+ : Algorithm(controller)
+{
+}
+
+char const *Cac::name() const
+{
+ return NAME;
+}
+
+static bool arrayToSet(const libcamera::YamlObject &params, std::vector<double> &inputArray, const Size &size)
+{
+ int num = 0;
+ int max_num = (size.width + 1) * (size.height + 1);
+ inputArray.resize(max_num);
+
+ for (const auto &p : params.asList()) {
+ if (num == max_num)
+ return false;
+ inputArray[num++] = p.get<double>(0);
+ }
+
+ return num == max_num;
+}
+
+static void setStrength(std::vector<double> &inputArray, std::vector<double> &outputArray,
+ double strengthFactor)
+{
+ int num = 0;
+ for (const auto &p : inputArray) {
+ outputArray[num++] = p * strengthFactor;
+ }
+}
+
+int Cac::read(const libcamera::YamlObject &params)
+{
+ config_.enabled = params.contains("lut_rx") && params.contains("lut_ry") &&
+ params.contains("lut_bx") && params.contains("lut_by");
+ if (!config_.enabled)
+ return 0;
+
+ const Size &size = getHardwareConfig().cacRegions;
+
+ if (!arrayToSet(params["lut_rx"], config_.lutRx, size)) {
+ LOG(RPiCac, Error) << "Bad CAC lut_rx table";
+ return -EINVAL;
+ }
+
+ if (!arrayToSet(params["lut_ry"], config_.lutRy, size)) {
+ LOG(RPiCac, Error) << "Bad CAC lut_ry table";
+ return -EINVAL;
+ }
+
+ if (!arrayToSet(params["lut_bx"], config_.lutBx, size)) {
+ LOG(RPiCac, Error) << "Bad CAC lut_bx table";
+ return -EINVAL;
+ }
+
+ if (!arrayToSet(params["lut_by"], config_.lutBy, size)) {
+ LOG(RPiCac, Error) << "Bad CAC lut_by table";
+ return -EINVAL;
+ }
+
+ double strength = params["strength"].get<double>(1);
+ cacStatus_.lutRx = config_.lutRx;
+ cacStatus_.lutRy = config_.lutRy;
+ cacStatus_.lutBx = config_.lutBx;
+ cacStatus_.lutBy = config_.lutBy;
+ setStrength(config_.lutRx, cacStatus_.lutRx, strength);
+ setStrength(config_.lutBx, cacStatus_.lutBx, strength);
+ setStrength(config_.lutRy, cacStatus_.lutRy, strength);
+ setStrength(config_.lutBy, cacStatus_.lutBy, strength);
+
+ return 0;
+}
+
+void Cac::prepare(Metadata *imageMetadata)
+{
+ if (config_.enabled)
+ imageMetadata->set("cac.status", cacStatus_);
+}
+
+// Register algorithm with the system.
+static Algorithm *Create(Controller *controller)
+{
+ return (Algorithm *)new Cac(controller);
+}
+static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/rpi/controller/rpi/cac.h b/src/ipa/rpi/controller/rpi/cac.h
new file mode 100644
index 00000000..a7b14c00
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/cac.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * cac.hpp - CAC control algorithm
+ */
+#pragma once
+
+#include "algorithm.h"
+#include "cac_status.h"
+
+namespace RPiController {
+
+struct CacConfig {
+ bool enabled;
+ std::vector<double> lutRx;
+ std::vector<double> lutRy;
+ std::vector<double> lutBx;
+ std::vector<double> lutBy;
+};
+
+class Cac : public Algorithm
+{
+public:
+ Cac(Controller *controller = NULL);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ CacConfig config_;
+ CacStatus cacStatus_;
+};
+
+} // namespace RPiController
diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp
new file mode 100644
index 00000000..2e2e6664
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/ccm.cpp
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * ccm.cpp - CCM (colour correction matrix) control algorithm
+ */
+
+#include <libcamera/base/log.h>
+
+#include "../awb_status.h"
+#include "../ccm_status.h"
+#include "../lux_status.h"
+#include "../metadata.h"
+
+#include "ccm.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiCcm)
+
+/*
+ * This algorithm selects a CCM (Colour Correction Matrix) according to the
+ * colour temperature estimated by AWB (interpolating between known matricies as
+ * necessary). Additionally the amount of colour saturation can be controlled
+ * both according to the current estimated lux level and according to a
+ * saturation setting that is exposed to applications.
+ */
+
+#define NAME "rpi.ccm"
+
+Matrix::Matrix()
+{
+ memset(m, 0, sizeof(m));
+}
+Matrix::Matrix(double m0, double m1, double m2, double m3, double m4, double m5,
+ double m6, double m7, double m8)
+{
+ m[0][0] = m0, m[0][1] = m1, m[0][2] = m2, m[1][0] = m3, m[1][1] = m4,
+ m[1][2] = m5, m[2][0] = m6, m[2][1] = m7, m[2][2] = m8;
+}
+int Matrix::read(const libcamera::YamlObject &params)
+{
+ double *ptr = (double *)m;
+
+ if (params.size() != 9) {
+ LOG(RPiCcm, Error) << "Wrong number of values in CCM";
+ return -EINVAL;
+ }
+
+ for (const auto &param : params.asList()) {
+ auto value = param.get<double>();
+ if (!value)
+ return -EINVAL;
+ *ptr++ = *value;
+ }
+
+ return 0;
+}
+
+Ccm::Ccm(Controller *controller)
+ : CcmAlgorithm(controller), saturation_(1.0) {}
+
+char const *Ccm::name() const
+{
+ return NAME;
+}
+
+int Ccm::read(const libcamera::YamlObject &params)
+{
+ int ret;
+
+ if (params.contains("saturation")) {
+ ret = config_.saturation.read(params["saturation"]);
+ if (ret)
+ return ret;
+ }
+
+ for (auto &p : params["ccms"].asList()) {
+ auto value = p["ct"].get<double>();
+ if (!value)
+ return -EINVAL;
+
+ CtCcm ctCcm;
+ ctCcm.ct = *value;
+ ret = ctCcm.ccm.read(p["ccm"]);
+ if (ret)
+ return ret;
+
+ if (!config_.ccms.empty() && ctCcm.ct <= config_.ccms.back().ct) {
+ LOG(RPiCcm, Error)
+ << "CCM not in increasing colour temperature order";
+ return -EINVAL;
+ }
+
+ config_.ccms.push_back(std::move(ctCcm));
+ }
+
+ if (config_.ccms.empty()) {
+ LOG(RPiCcm, Error) << "No CCMs specified";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void Ccm::setSaturation(double saturation)
+{
+ saturation_ = saturation;
+}
+
+void Ccm::initialise()
+{
+}
+
+template<typename T>
+static bool getLocked(Metadata *metadata, std::string const &tag, T &value)
+{
+ T *ptr = metadata->getLocked<T>(tag);
+ if (ptr == nullptr)
+ return false;
+ value = *ptr;
+ return true;
+}
+
+Matrix calculateCcm(std::vector<CtCcm> const &ccms, double ct)
+{
+ if (ct <= ccms.front().ct)
+ return ccms.front().ccm;
+ else if (ct >= ccms.back().ct)
+ return ccms.back().ccm;
+ else {
+ int i = 0;
+ for (; ct > ccms[i].ct; i++)
+ ;
+ double lambda =
+ (ct - ccms[i - 1].ct) / (ccms[i].ct - ccms[i - 1].ct);
+ return lambda * ccms[i].ccm + (1.0 - lambda) * ccms[i - 1].ccm;
+ }
+}
+
+Matrix applySaturation(Matrix const &ccm, double saturation)
+{
+ Matrix RGB2Y(0.299, 0.587, 0.114, -0.169, -0.331, 0.500, 0.500, -0.419,
+ -0.081);
+ Matrix Y2RGB(1.000, 0.000, 1.402, 1.000, -0.345, -0.714, 1.000, 1.771,
+ 0.000);
+ Matrix S(1, 0, 0, 0, saturation, 0, 0, 0, saturation);
+ return Y2RGB * S * RGB2Y * ccm;
+}
+
+void Ccm::prepare(Metadata *imageMetadata)
+{
+ bool awbOk = false, luxOk = false;
+ struct AwbStatus awb = {};
+ awb.temperatureK = 4000; /* in case no metadata */
+ struct LuxStatus lux = {};
+ lux.lux = 400; /* in case no metadata */
+ {
+ /* grab mutex just once to get everything */
+ std::lock_guard<Metadata> lock(*imageMetadata);
+ awbOk = getLocked(imageMetadata, "awb.status", awb);
+ luxOk = getLocked(imageMetadata, "lux.status", lux);
+ }
+ if (!awbOk)
+ LOG(RPiCcm, Warning) << "no colour temperature found";
+ if (!luxOk)
+ LOG(RPiCcm, Warning) << "no lux value found";
+ Matrix ccm = calculateCcm(config_.ccms, awb.temperatureK);
+ double saturation = saturation_;
+ struct CcmStatus ccmStatus;
+ ccmStatus.saturation = saturation;
+ if (!config_.saturation.empty())
+ saturation *= config_.saturation.eval(
+ config_.saturation.domain().clip(lux.lux));
+ ccm = applySaturation(ccm, saturation);
+ for (int j = 0; j < 3; j++)
+ for (int i = 0; i < 3; i++)
+ ccmStatus.matrix[j * 3 + i] =
+ std::max(-8.0, std::min(7.9999, ccm.m[j][i]));
+ LOG(RPiCcm, Debug)
+ << "colour temperature " << awb.temperatureK << "K";
+ LOG(RPiCcm, Debug)
+ << "CCM: " << ccmStatus.matrix[0] << " " << ccmStatus.matrix[1]
+ << " " << ccmStatus.matrix[2] << " "
+ << ccmStatus.matrix[3] << " " << ccmStatus.matrix[4]
+ << " " << ccmStatus.matrix[5] << " "
+ << ccmStatus.matrix[6] << " " << ccmStatus.matrix[7]
+ << " " << ccmStatus.matrix[8];
+ imageMetadata->set("ccm.status", ccmStatus);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Ccm(controller);
+ ;
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/ccm.hpp b/src/ipa/rpi/controller/rpi/ccm.h
index 330ed51f..286d0b33 100644
--- a/src/ipa/raspberrypi/controller/rpi/ccm.hpp
+++ b/src/ipa/rpi/controller/rpi/ccm.h
@@ -1,26 +1,26 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2019, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2019, Raspberry Pi Ltd
*
- * ccm.hpp - CCM (colour correction matrix) control algorithm
+ * ccm.h - CCM (colour correction matrix) control algorithm
*/
#pragma once
#include <vector>
-#include "../ccm_algorithm.hpp"
-#include "../pwl.hpp"
+#include "../ccm_algorithm.h"
+#include "../pwl.h"
namespace RPiController {
-// Algorithm to calculate colour matrix. Should be placed after AWB.
+/* Algorithm to calculate colour matrix. Should be placed after AWB. */
struct Matrix {
Matrix(double m0, double m1, double m2, double m3, double m4, double m5,
double m6, double m7, double m8);
Matrix();
double m[3][3];
- void Read(boost::property_tree::ptree const &params);
+ int read(const libcamera::YamlObject &params);
};
static inline Matrix operator*(double d, Matrix const &m)
{
@@ -61,15 +61,15 @@ class Ccm : public CcmAlgorithm
{
public:
Ccm(Controller *controller = NULL);
- char const *Name() const override;
- void Read(boost::property_tree::ptree const &params) override;
- void SetSaturation(double saturation) override;
- void Initialise() override;
- void Prepare(Metadata *image_metadata) override;
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void setSaturation(double saturation) override;
+ void initialise() override;
+ void prepare(Metadata *imageMetadata) override;
private:
CcmConfig config_;
double saturation_;
};
-} // namespace RPiController
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp
new file mode 100644
index 00000000..4e038a02
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/contrast.cpp
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * contrast.cpp - contrast (gamma) control algorithm
+ */
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+
+#include "../contrast_status.h"
+#include "../histogram.h"
+
+#include "contrast.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiContrast)
+
+/*
+ * This is a very simple control algorithm which simply retrieves the results of
+ * AGC and AWB via their "status" metadata, and applies digital gain to the
+ * colour channels in accordance with those instructions. We take care never to
+ * apply less than unity gains, as that would cause fully saturated pixels to go
+ * off-white.
+ */
+
+#define NAME "rpi.contrast"
+
+Contrast::Contrast(Controller *controller)
+ : ContrastAlgorithm(controller), brightness_(0.0), contrast_(1.0)
+{
+}
+
+char const *Contrast::name() const
+{
+ return NAME;
+}
+
+int Contrast::read(const libcamera::YamlObject &params)
+{
+ // enable adaptive enhancement by default
+ config_.ceEnable = params["ce_enable"].get<int>(1);
+ ceEnable_ = config_.ceEnable;
+ // the point near the bottom of the histogram to move
+ config_.loHistogram = params["lo_histogram"].get<double>(0.01);
+ // where in the range to try and move it to
+ config_.loLevel = params["lo_level"].get<double>(0.015);
+ // but don't move by more than this
+ config_.loMax = params["lo_max"].get<double>(500);
+ // equivalent values for the top of the histogram...
+ config_.hiHistogram = params["hi_histogram"].get<double>(0.95);
+ config_.hiLevel = params["hi_level"].get<double>(0.95);
+ config_.hiMax = params["hi_max"].get<double>(2000);
+ return config_.gammaCurve.read(params["gamma_curve"]);
+}
+
+void Contrast::setBrightness(double brightness)
+{
+ brightness_ = brightness;
+}
+
+void Contrast::setContrast(double contrast)
+{
+ contrast_ = contrast;
+}
+
+void Contrast::enableCe(bool enable)
+{
+ ceEnable_ = enable;
+}
+
+void Contrast::restoreCe()
+{
+ ceEnable_ = config_.ceEnable;
+}
+
+void Contrast::initialise()
+{
+ /*
+ * Fill in some default values as Prepare will run before Process gets
+ * called.
+ */
+ status_.brightness = brightness_;
+ status_.contrast = contrast_;
+ status_.gammaCurve = config_.gammaCurve;
+}
+
+void Contrast::prepare(Metadata *imageMetadata)
+{
+ imageMetadata->set("contrast.status", status_);
+}
+
+Pwl computeStretchCurve(Histogram const &histogram,
+ ContrastConfig const &config)
+{
+ Pwl enhance;
+ enhance.append(0, 0);
+ /*
+ * If the start of the histogram is rather empty, try to pull it down a
+ * bit.
+ */
+ double histLo = histogram.quantile(config.loHistogram) *
+ (65536 / histogram.bins());
+ double levelLo = config.loLevel * 65536;
+ LOG(RPiContrast, Debug)
+ << "Move histogram point " << histLo << " to " << levelLo;
+ histLo = std::max(levelLo,
+ std::min(65535.0, std::min(histLo, levelLo + config.loMax)));
+ LOG(RPiContrast, Debug)
+ << "Final values " << histLo << " -> " << levelLo;
+ enhance.append(histLo, levelLo);
+ /*
+ * Keep the mid-point (median) in the same place, though, to limit the
+ * apparent amount of global brightness shift.
+ */
+ double mid = histogram.quantile(0.5) * (65536 / histogram.bins());
+ enhance.append(mid, mid);
+
+ /*
+ * If the top to the histogram is empty, try to pull the pixel values
+ * there up.
+ */
+ double histHi = histogram.quantile(config.hiHistogram) *
+ (65536 / histogram.bins());
+ double levelHi = config.hiLevel * 65536;
+ LOG(RPiContrast, Debug)
+ << "Move histogram point " << histHi << " to " << levelHi;
+ histHi = std::min(levelHi,
+ std::max(0.0, std::max(histHi, levelHi - config.hiMax)));
+ LOG(RPiContrast, Debug)
+ << "Final values " << histHi << " -> " << levelHi;
+ enhance.append(histHi, levelHi);
+ enhance.append(65535, 65535);
+ return enhance;
+}
+
+Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,
+ double contrast)
+{
+ Pwl newGammaCurve;
+ LOG(RPiContrast, Debug)
+ << "Manual brightness " << brightness << " contrast " << contrast;
+ gammaCurve.map([&](double x, double y) {
+ newGammaCurve.append(
+ x, std::max(0.0, std::min(65535.0,
+ (y - 32768) * contrast +
+ 32768 + brightness)));
+ });
+ return newGammaCurve;
+}
+
+void Contrast::process(StatisticsPtr &stats,
+ [[maybe_unused]] Metadata *imageMetadata)
+{
+ Histogram &histogram = stats->yHist;
+ /*
+ * We look at the histogram and adjust the gamma curve in the following
+ * ways: 1. Adjust the gamma curve so as to pull the start of the
+ * histogram down, and possibly push the end up.
+ */
+ Pwl gammaCurve = config_.gammaCurve;
+ if (ceEnable_) {
+ if (config_.loMax != 0 || config_.hiMax != 0)
+ gammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve);
+ /*
+ * We could apply other adjustments (e.g. partial equalisation)
+ * based on the histogram...?
+ */
+ }
+ /*
+ * 2. Finally apply any manually selected brightness/contrast
+ * adjustment.
+ */
+ if (brightness_ != 0 || contrast_ != 1.0)
+ gammaCurve = applyManualContrast(gammaCurve, brightness_, contrast_);
+ /*
+ * And fill in the status for output. Use more points towards the bottom
+ * of the curve.
+ */
+ status_.brightness = brightness_;
+ status_.contrast = contrast_;
+ status_.gammaCurve = std::move(gammaCurve);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Contrast(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h
new file mode 100644
index 00000000..59aa70dc
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/contrast.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * contrast.h - contrast (gamma) control algorithm
+ */
+#pragma once
+
+#include <mutex>
+
+#include "../contrast_algorithm.h"
+#include "../pwl.h"
+
+namespace RPiController {
+
+/*
+ * Back End algorithm to appaly correct digital gain. Should be placed after
+ * Back End AWB.
+ */
+
+struct ContrastConfig {
+ bool ceEnable;
+ double loHistogram;
+ double loLevel;
+ double loMax;
+ double hiHistogram;
+ double hiLevel;
+ double hiMax;
+ Pwl gammaCurve;
+};
+
+class Contrast : public ContrastAlgorithm
+{
+public:
+ Contrast(Controller *controller = NULL);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void setBrightness(double brightness) override;
+ void setContrast(double contrast) override;
+ void enableCe(bool enable) override;
+ void restoreCe() override;
+ void initialise() override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+
+private:
+ ContrastConfig config_;
+ double brightness_;
+ double contrast_;
+ ContrastStatus status_;
+ double ceEnable_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/denoise.cpp b/src/ipa/rpi/controller/rpi/denoise.cpp
new file mode 100644
index 00000000..154ee604
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/denoise.cpp
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * Denoise.cpp - Denoise (spatial, colour, temporal) control algorithm
+ */
+#include "denoise.h"
+
+#include <libcamera/base/log.h>
+
+#include "denoise_status.h"
+#include "noise_status.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiDenoise)
+
+// Calculate settings for the denoise blocks using the noise profile in
+// the image metadata.
+
+#define NAME "rpi.denoise"
+
+int DenoiseConfig::read(const libcamera::YamlObject &params)
+{
+ sdnEnable = params.contains("sdn");
+ if (sdnEnable) {
+ auto &sdnParams = params["sdn"];
+ sdnDeviation = sdnParams["deviation"].get<double>(3.2);
+ sdnStrength = sdnParams["strength"].get<double>(0.25);
+ sdnDeviation2 = sdnParams["deviation2"].get<double>(sdnDeviation);
+ sdnDeviationNoTdn = sdnParams["deviation_no_tdn"].get<double>(sdnDeviation);
+ sdnStrengthNoTdn = sdnParams["strength_no_tdn"].get<double>(sdnStrength);
+ sdnTdnBackoff = sdnParams["backoff"].get<double>(0.75);
+ }
+
+ cdnEnable = params.contains("cdn");
+ if (cdnEnable) {
+ auto &cdnParams = params["cdn"];
+ cdnDeviation = cdnParams["deviation"].get<double>(120);
+ cdnStrength = cdnParams["strength"].get<double>(0.2);
+ }
+
+ tdnEnable = params.contains("tdn");
+ if (tdnEnable) {
+ auto &tdnParams = params["tdn"];
+ tdnDeviation = tdnParams["deviation"].get<double>(0.5);
+ tdnThreshold = tdnParams["threshold"].get<double>(0.75);
+ } else if (sdnEnable) {
+ /*
+ * If SDN is enabled but TDN isn't, overwrite all the SDN settings
+ * with the "no TDN" versions. This makes it easier to enable or
+ * disable TDN in the tuning file without editing all the other
+ * parameters.
+ */
+ sdnDeviation = sdnDeviation2 = sdnDeviationNoTdn;
+ sdnStrength = sdnStrengthNoTdn;
+ }
+
+ return 0;
+}
+
+Denoise::Denoise(Controller *controller)
+ : DenoiseAlgorithm(controller), mode_(DenoiseMode::ColourHighQuality)
+{
+}
+
+char const *Denoise::name() const
+{
+ return NAME;
+}
+
+int Denoise::read(const libcamera::YamlObject &params)
+{
+ if (!params.contains("normal")) {
+ configs_["normal"].read(params);
+ currentConfig_ = &configs_["normal"];
+
+ return 0;
+ }
+
+ for (const auto &[key, value] : params.asDict()) {
+ if (configs_[key].read(value)) {
+ LOG(RPiDenoise, Error) << "Failed to read denoise config " << key;
+ return -EINVAL;
+ }
+ }
+
+ auto it = configs_.find("normal");
+ if (it == configs_.end()) {
+ LOG(RPiDenoise, Error) << "No normal denoise settings found";
+ return -EINVAL;
+ }
+ currentConfig_ = &it->second;
+
+ return 0;
+}
+
+void Denoise::initialise()
+{
+}
+
+void Denoise::switchMode([[maybe_unused]] CameraMode const &cameraMode,
+ [[maybe_unused]] Metadata *metadata)
+{
+ /* A mode switch effectively resets temporal denoise and it has to start over. */
+ currentSdnDeviation_ = currentConfig_->sdnDeviationNoTdn;
+ currentSdnStrength_ = currentConfig_->sdnStrengthNoTdn;
+ currentSdnDeviation2_ = currentConfig_->sdnDeviationNoTdn;
+}
+
+void Denoise::prepare(Metadata *imageMetadata)
+{
+ struct NoiseStatus noiseStatus = {};
+ noiseStatus.noiseSlope = 3.0; // in case no metadata
+ if (imageMetadata->get("noise.status", noiseStatus) != 0)
+ LOG(RPiDenoise, Warning) << "no noise profile found";
+
+ LOG(RPiDenoise, Debug)
+ << "Noise profile: constant " << noiseStatus.noiseConstant
+ << " slope " << noiseStatus.noiseSlope;
+
+ if (mode_ == DenoiseMode::Off)
+ return;
+
+ if (currentConfig_->sdnEnable) {
+ struct SdnStatus sdn;
+ sdn.noiseConstant = noiseStatus.noiseConstant * currentSdnDeviation_;
+ sdn.noiseSlope = noiseStatus.noiseSlope * currentSdnDeviation_;
+ sdn.noiseConstant2 = noiseStatus.noiseConstant * currentConfig_->sdnDeviation2;
+ sdn.noiseSlope2 = noiseStatus.noiseSlope * currentSdnDeviation2_;
+ sdn.strength = currentSdnStrength_;
+ imageMetadata->set("sdn.status", sdn);
+ LOG(RPiDenoise, Debug)
+ << "const " << sdn.noiseConstant
+ << " slope " << sdn.noiseSlope
+ << " str " << sdn.strength
+ << " const2 " << sdn.noiseConstant2
+ << " slope2 " << sdn.noiseSlope2;
+
+ /* For the next frame, we back off the SDN parameters as TDN ramps up. */
+ double f = currentConfig_->sdnTdnBackoff;
+ currentSdnDeviation_ = f * currentSdnDeviation_ + (1 - f) * currentConfig_->sdnDeviation;
+ currentSdnStrength_ = f * currentSdnStrength_ + (1 - f) * currentConfig_->sdnStrength;
+ currentSdnDeviation2_ = f * currentSdnDeviation2_ + (1 - f) * currentConfig_->sdnDeviation2;
+ }
+
+ if (currentConfig_->tdnEnable) {
+ struct TdnStatus tdn;
+ tdn.noiseConstant = noiseStatus.noiseConstant * currentConfig_->tdnDeviation;
+ tdn.noiseSlope = noiseStatus.noiseSlope * currentConfig_->tdnDeviation;
+ tdn.threshold = currentConfig_->tdnThreshold;
+ imageMetadata->set("tdn.status", tdn);
+ LOG(RPiDenoise, Debug)
+ << "programmed tdn threshold " << tdn.threshold
+ << " constant " << tdn.noiseConstant
+ << " slope " << tdn.noiseSlope;
+ }
+
+ if (currentConfig_->cdnEnable && mode_ != DenoiseMode::ColourOff) {
+ struct CdnStatus cdn;
+ cdn.threshold = currentConfig_->cdnDeviation * noiseStatus.noiseSlope + noiseStatus.noiseConstant;
+ cdn.strength = currentConfig_->cdnStrength;
+ imageMetadata->set("cdn.status", cdn);
+ LOG(RPiDenoise, Debug)
+ << "programmed cdn threshold " << cdn.threshold
+ << " strength " << cdn.strength;
+ }
+}
+
+void Denoise::setMode(DenoiseMode mode)
+{
+ // We only distinguish between off and all other modes.
+ mode_ = mode;
+}
+
+void Denoise::setConfig(std::string const &name)
+{
+ auto it = configs_.find(name);
+ if (it == configs_.end()) {
+ /*
+ * Some platforms may have no need for different denoise settings, so we only issue
+ * a warning if there clearly are several configurations.
+ */
+ if (configs_.size() > 1)
+ LOG(RPiDenoise, Warning) << "No denoise config found for " << name;
+ else
+ LOG(RPiDenoise, Debug) << "No denoise config found for " << name;
+ } else
+ currentConfig_ = &it->second;
+}
+
+// Register algorithm with the system.
+static Algorithm *Create(Controller *controller)
+{
+ return (Algorithm *)new Denoise(controller);
+}
+static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/rpi/controller/rpi/denoise.h b/src/ipa/rpi/controller/rpi/denoise.h
new file mode 100644
index 00000000..92ff4f93
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/denoise.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * denoise.hpp - Denoise (spatial, colour, temporal) control algorithm
+ */
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "algorithm.h"
+#include "denoise_algorithm.h"
+
+namespace RPiController {
+
+// Algorithm to calculate correct denoise settings.
+
+struct DenoiseConfig {
+ double sdnDeviation;
+ double sdnStrength;
+ double sdnDeviation2;
+ double sdnDeviationNoTdn;
+ double sdnStrengthNoTdn;
+ double sdnTdnBackoff;
+ double cdnDeviation;
+ double cdnStrength;
+ double tdnDeviation;
+ double tdnThreshold;
+ bool tdnEnable;
+ bool sdnEnable;
+ bool cdnEnable;
+ int read(const libcamera::YamlObject &params);
+};
+
+class Denoise : public DenoiseAlgorithm
+{
+public:
+ Denoise(Controller *controller);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void initialise() override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ void prepare(Metadata *imageMetadata) override;
+ void setMode(DenoiseMode mode) override;
+ void setConfig(std::string const &name) override;
+
+private:
+ std::map<std::string, DenoiseConfig> configs_;
+ DenoiseConfig *currentConfig_;
+ DenoiseMode mode_;
+
+ /* SDN parameters attenuate over time if TDN is running. */
+ double currentSdnDeviation_;
+ double currentSdnStrength_;
+ double currentSdnDeviation2_;
+};
+
+} // namespace RPiController
diff --git a/src/ipa/rpi/controller/rpi/dpc.cpp b/src/ipa/rpi/controller/rpi/dpc.cpp
new file mode 100644
index 00000000..be3871df
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/dpc.cpp
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * dpc.cpp - DPC (defective pixel correction) control algorithm
+ */
+
+#include <libcamera/base/log.h>
+
+#include "dpc.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiDpc)
+
+/*
+ * We use the lux status so that we can apply stronger settings in darkness (if
+ * necessary).
+ */
+
+#define NAME "rpi.dpc"
+
+Dpc::Dpc(Controller *controller)
+ : Algorithm(controller)
+{
+}
+
+char const *Dpc::name() const
+{
+ return NAME;
+}
+
+int Dpc::read(const libcamera::YamlObject &params)
+{
+ config_.strength = params["strength"].get<int>(1);
+ if (config_.strength < 0 || config_.strength > 2) {
+ LOG(RPiDpc, Error) << "Bad strength value";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void Dpc::prepare(Metadata *imageMetadata)
+{
+ DpcStatus dpcStatus = {};
+ /* Should we vary this with lux level or analogue gain? TBD. */
+ dpcStatus.strength = config_.strength;
+ LOG(RPiDpc, Debug) << "strength " << dpcStatus.strength;
+ imageMetadata->set("dpc.status", dpcStatus);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Dpc(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/dpc.h b/src/ipa/rpi/controller/rpi/dpc.h
new file mode 100644
index 00000000..84a05604
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/dpc.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * dpc.h - DPC (defective pixel correction) control algorithm
+ */
+#pragma once
+
+#include "../algorithm.h"
+#include "../dpc_status.h"
+
+namespace RPiController {
+
+/* Back End algorithm to apply appropriate GEQ settings. */
+
+struct DpcConfig {
+ int strength;
+};
+
+class Dpc : public Algorithm
+{
+public:
+ Dpc(Controller *controller);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ DpcConfig config_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/focus.hpp b/src/ipa/rpi/controller/rpi/focus.h
index 131b1d0f..8556039d 100644
--- a/src/ipa/raspberrypi/controller/rpi/focus.hpp
+++ b/src/ipa/rpi/controller/rpi/focus.h
@@ -1,13 +1,13 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
- * focus.hpp - focus algorithm
+ * focus.h - focus algorithm
*/
#pragma once
-#include "../algorithm.hpp"
-#include "../metadata.hpp"
+#include "../algorithm.h"
+#include "../metadata.h"
/*
* The "focus" algorithm. All it does it print out a version of the
@@ -21,8 +21,8 @@ class Focus : public Algorithm
{
public:
Focus(Controller *controller);
- char const *Name() const override;
- void Process(StatisticsPtr &stats, Metadata *image_metadata) override;
+ char const *name() const override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
};
} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp
new file mode 100644
index 00000000..510870e9
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/geq.cpp
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * geq.cpp - GEQ (green equalisation) control algorithm
+ */
+
+#include <libcamera/base/log.h>
+
+#include "../device_status.h"
+#include "../lux_status.h"
+#include "../pwl.h"
+
+#include "geq.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiGeq)
+
+/*
+ * We use the lux status so that we can apply stronger settings in darkness (if
+ * necessary).
+ */
+
+#define NAME "rpi.geq"
+
+Geq::Geq(Controller *controller)
+ : Algorithm(controller)
+{
+}
+
+char const *Geq::name() const
+{
+ return NAME;
+}
+
+int Geq::read(const libcamera::YamlObject &params)
+{
+ config_.offset = params["offset"].get<uint16_t>(0);
+ config_.slope = params["slope"].get<double>(0.0);
+ if (config_.slope < 0.0 || config_.slope >= 1.0) {
+ LOG(RPiGeq, Error) << "Bad slope value";
+ return -EINVAL;
+ }
+
+ if (params.contains("strength")) {
+ int ret = config_.strength.read(params["strength"]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void Geq::prepare(Metadata *imageMetadata)
+{
+ LuxStatus luxStatus = {};
+ luxStatus.lux = 400;
+ if (imageMetadata->get("lux.status", luxStatus))
+ LOG(RPiGeq, Warning) << "no lux data found";
+ DeviceStatus deviceStatus;
+ deviceStatus.analogueGain = 1.0; /* in case not found */
+ if (imageMetadata->get("device.status", deviceStatus))
+ LOG(RPiGeq, Warning)
+ << "no device metadata - use analogue gain of 1x";
+ GeqStatus geqStatus = {};
+ double strength = config_.strength.empty()
+ ? 1.0
+ : config_.strength.eval(config_.strength.domain().clip(luxStatus.lux));
+ strength *= deviceStatus.analogueGain;
+ double offset = config_.offset * strength;
+ double slope = config_.slope * strength;
+ geqStatus.offset = std::min(65535.0, std::max(0.0, offset));
+ geqStatus.slope = std::min(.99999, std::max(0.0, slope));
+ LOG(RPiGeq, Debug)
+ << "offset " << geqStatus.offset << " slope "
+ << geqStatus.slope << " (analogue gain "
+ << deviceStatus.analogueGain << " lux "
+ << luxStatus.lux << ")";
+ imageMetadata->set("geq.status", geqStatus);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Geq(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h
new file mode 100644
index 00000000..ee3a52ff
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/geq.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * geq.h - GEQ (green equalisation) control algorithm
+ */
+#pragma once
+
+#include "../algorithm.h"
+#include "../geq_status.h"
+
+namespace RPiController {
+
+/* Back End algorithm to apply appropriate GEQ settings. */
+
+struct GeqConfig {
+ uint16_t offset;
+ double slope;
+ Pwl strength; /* lux to strength factor */
+};
+
+class Geq : public Algorithm
+{
+public:
+ Geq(Controller *controller);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ GeqConfig config_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp
new file mode 100644
index 00000000..fb580548
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/hdr.cpp
@@ -0,0 +1,337 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ *
+ * hdr.cpp - HDR control algorithm
+ */
+
+#include "hdr.h"
+
+#include <libcamera/base/log.h>
+
+#include "../agc_status.h"
+#include "../alsc_status.h"
+#include "../stitch_status.h"
+#include "../tonemap_status.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiHdr)
+
+#define NAME "rpi.hdr"
+
+void HdrConfig::read(const libcamera::YamlObject &params, const std::string &modeName)
+{
+ name = modeName;
+
+ if (!params.contains("cadence"))
+ LOG(RPiHdr, Fatal) << "No cadence for HDR mode " << name;
+ cadence = params["cadence"].getList<unsigned int>().value();
+ if (cadence.empty())
+ LOG(RPiHdr, Fatal) << "Empty cadence in HDR mode " << name;
+
+ /*
+ * In the JSON file it's easier to use the channel name as the key, but
+ * for us it's convenient to swap them over.
+ */
+ for (const auto &[k, v] : params["channel_map"].asDict())
+ channelMap[v.get<unsigned int>().value()] = k;
+
+ /* Lens shading related parameters. */
+ if (params.contains("spatial_gain")) {
+ spatialGain.read(params["spatial_gain"]);
+ diffusion = params["diffusion"].get<unsigned int>(3);
+ /* Clip to an arbitrary limit just to stop typos from killing the system! */
+ const unsigned int MAX_DIFFUSION = 15;
+ if (diffusion > MAX_DIFFUSION) {
+ diffusion = MAX_DIFFUSION;
+ LOG(RPiHdr, Warning) << "Diffusion value clipped to " << MAX_DIFFUSION;
+ }
+ }
+
+ /* Read any tonemap parameters. */
+ tonemapEnable = params["tonemap_enable"].get<int>(0);
+ detailConstant = params["detail_constant"].get<uint16_t>(50);
+ detailSlope = params["detail_slope"].get<double>(8.0);
+ iirStrength = params["iir_strength"].get<double>(8.0);
+ strength = params["strength"].get<double>(1.5);
+ if (tonemapEnable)
+ tonemap.read(params["tonemap"]);
+
+ /* Read any stitch parameters. */
+ stitchEnable = params["stitch_enable"].get<int>(0);
+ thresholdLo = params["threshold_lo"].get<uint16_t>(50000);
+ motionThreshold = params["motion_threshold"].get<double>(0.005);
+ diffPower = params["diff_power"].get<uint8_t>(13);
+ if (diffPower > 15)
+ LOG(RPiHdr, Fatal) << "Bad diff_power value in HDR mode " << name;
+}
+
+Hdr::Hdr(Controller *controller)
+ : HdrAlgorithm(controller)
+{
+ regions_ = controller->getHardwareConfig().awbRegions;
+ numRegions_ = regions_.width * regions_.height;
+ gains_[0].resize(numRegions_, 1.0);
+ gains_[1].resize(numRegions_, 1.0);
+}
+
+char const *Hdr::name() const
+{
+ return NAME;
+}
+
+int Hdr::read(const libcamera::YamlObject &params)
+{
+ /* Make an "HDR off" mode by default so that tuning files don't have to. */
+ HdrConfig &offMode = config_["Off"];
+ offMode.name = "Off";
+ offMode.cadence = { 0 };
+ offMode.channelMap[0] = "None";
+ status_.mode = offMode.name;
+ delayedStatus_.mode = offMode.name;
+
+ /*
+ * But we still allow the tuning file to override the "Off" mode if it wants.
+ * For example, maybe an application will make channel 0 be the "short"
+ * channel, in order to apply other AGC controls to it.
+ */
+ for (const auto &[key, value] : params.asDict())
+ config_[key].read(value, key);
+
+ return 0;
+}
+
+int Hdr::setMode(std::string const &mode)
+{
+ /* Always validate the mode, so it can be used later without checking. */
+ auto it = config_.find(mode);
+ if (it == config_.end()) {
+ LOG(RPiHdr, Warning) << "No such HDR mode " << mode;
+ return -1;
+ }
+
+ status_.mode = it->second.name;
+
+ return 0;
+}
+
+std::vector<unsigned int> Hdr::getChannels() const
+{
+ return config_.at(status_.mode).cadence;
+}
+
+void Hdr::updateAgcStatus(Metadata *metadata)
+{
+ std::scoped_lock lock(*metadata);
+ AgcStatus *agcStatus = metadata->getLocked<AgcStatus>("agc.status");
+ if (agcStatus) {
+ HdrConfig &hdrConfig = config_[status_.mode];
+ auto it = hdrConfig.channelMap.find(agcStatus->channel);
+ if (it != hdrConfig.channelMap.end()) {
+ status_.channel = it->second;
+ agcStatus->hdr = status_;
+ } else
+ LOG(RPiHdr, Warning) << "Channel " << agcStatus->channel
+ << " not found in mode " << status_.mode;
+ } else
+ LOG(RPiHdr, Warning) << "No agc.status found";
+}
+
+void Hdr::switchMode([[maybe_unused]] CameraMode const &cameraMode, Metadata *metadata)
+{
+ updateAgcStatus(metadata);
+ delayedStatus_ = status_;
+}
+
+void Hdr::prepare(Metadata *imageMetadata)
+{
+ AgcStatus agcStatus;
+ if (!imageMetadata->get<AgcStatus>("agc.delayed_status", agcStatus))
+ delayedStatus_ = agcStatus.hdr;
+
+ auto it = config_.find(delayedStatus_.mode);
+ if (it == config_.end()) {
+ /* Shouldn't be possible. There would be nothing we could do. */
+ LOG(RPiHdr, Warning) << "Unexpected HDR mode " << delayedStatus_.mode;
+ return;
+ }
+
+ HdrConfig &config = it->second;
+ if (config.spatialGain.empty())
+ return;
+
+ AlscStatus alscStatus{}; /* some compilers seem to require the braces */
+ if (imageMetadata->get<AlscStatus>("alsc.status", alscStatus)) {
+ LOG(RPiHdr, Warning) << "No ALSC status";
+ return;
+ }
+
+ /* The final gains ended up in the odd or even array, according to diffusion. */
+ std::vector<double> &gains = gains_[config.diffusion & 1];
+ for (unsigned int i = 0; i < numRegions_; i++) {
+ alscStatus.r[i] *= gains[i];
+ alscStatus.g[i] *= gains[i];
+ alscStatus.b[i] *= gains[i];
+ }
+ imageMetadata->set("alsc.status", alscStatus);
+}
+
+bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config)
+{
+ /* When there's a change of HDR mode we start over with a new tonemap curve. */
+ if (delayedStatus_.mode != previousMode_) {
+ previousMode_ = delayedStatus_.mode;
+ tonemap_ = Pwl();
+ }
+
+ /* No tonemapping. No need to output a tonemap.status. */
+ if (!config.tonemapEnable)
+ return false;
+
+ /* If an explicit tonemap was given, use it. */
+ if (!config.tonemap.empty()) {
+ tonemap_ = config.tonemap;
+ return true;
+ }
+
+ /*
+ * We wouldn't update the tonemap on short frames when in multi-exposure mode. But
+ * we still need to output the most recent tonemap. Possibly we should make the
+ * config indicate the channels for which we should update the tonemap?
+ */
+ if (delayedStatus_.mode == "MultiExposure" && delayedStatus_.channel != "short")
+ return true;
+
+ /*
+ * If we wanted to build or adjust tonemaps dynamically, this would be the place
+ * to do it. But for now we seem to be getting by without.
+ */
+
+ return true;
+}
+
+static void averageGains(std::vector<double> &src, std::vector<double> &dst, const Size &size)
+{
+#define IDX(y, x) ((y)*size.width + (x))
+ unsigned int lastCol = size.width - 1; /* index of last column */
+ unsigned int preLastCol = lastCol - 1; /* and the column before that */
+ unsigned int lastRow = size.height - 1; /* index of last row */
+ unsigned int preLastRow = lastRow - 1; /* and the row before that */
+
+ /* Corners first. */
+ dst[IDX(0, 0)] = (src[IDX(0, 0)] + src[IDX(0, 1)] + src[IDX(1, 0)]) / 3;
+ dst[IDX(0, lastCol)] = (src[IDX(0, lastCol)] + src[IDX(0, preLastCol)] + src[IDX(1, lastCol)]) / 3;
+ dst[IDX(lastRow, 0)] = (src[IDX(lastRow, 0)] + src[IDX(lastRow, 1)] + src[IDX(preLastRow, 0)]) / 3;
+ dst[IDX(lastRow, lastCol)] = (src[IDX(lastRow, lastCol)] + src[IDX(lastRow, preLastCol)] +
+ src[IDX(preLastRow, lastCol)]) /
+ 3;
+
+ /* Now the edges. */
+ for (unsigned int i = 1; i < lastCol; i++) {
+ dst[IDX(0, i)] = (src[IDX(0, i - 1)] + src[IDX(0, i)] + src[IDX(0, i + 1)] + src[IDX(1, i)]) / 4;
+ dst[IDX(lastRow, i)] = (src[IDX(lastRow, i - 1)] + src[IDX(lastRow, i)] +
+ src[IDX(lastRow, i + 1)] + src[IDX(preLastRow, i)]) /
+ 4;
+ }
+
+ for (unsigned int i = 1; i < lastRow; i++) {
+ dst[IDX(i, 0)] = (src[IDX(i - 1, 0)] + src[IDX(i, 0)] + src[IDX(i + 1, 0)] + src[IDX(i, 1)]) / 4;
+ dst[IDX(i, 31)] = (src[IDX(i - 1, lastCol)] + src[IDX(i, lastCol)] +
+ src[IDX(i + 1, lastCol)] + src[IDX(i, preLastCol)]) /
+ 4;
+ }
+
+ /* Finally the interior. */
+ for (unsigned int j = 1; j < lastRow; j++) {
+ for (unsigned int i = 1; i < lastCol; i++) {
+ dst[IDX(j, i)] = (src[IDX(j - 1, i)] + src[IDX(j, i - 1)] + src[IDX(j, i)] +
+ src[IDX(j, i + 1)] + src[IDX(j + 1, i)]) /
+ 5;
+ }
+ }
+}
+
+void Hdr::updateGains(StatisticsPtr &stats, HdrConfig &config)
+{
+ if (config.spatialGain.empty())
+ return;
+
+ /* When alternating exposures, only compute these gains for the short frame. */
+ if (delayedStatus_.mode == "MultiExposure" && delayedStatus_.channel != "short")
+ return;
+
+ for (unsigned int i = 0; i < numRegions_; i++) {
+ auto &region = stats->awbRegions.get(i);
+ unsigned int counted = region.counted;
+ counted += (counted == 0); /* avoid div by zero */
+ double r = region.val.rSum / counted;
+ double g = region.val.gSum / counted;
+ double b = region.val.bSum / counted;
+ double brightness = std::max({ r, g, b }) / 65535;
+ gains_[0][i] = config.spatialGain.eval(brightness);
+ }
+
+ /* Ping-pong between the two gains_ buffers. */
+ for (unsigned int i = 0; i < config.diffusion; i++)
+ averageGains(gains_[i & 1], gains_[(i & 1) ^ 1], regions_);
+}
+
+void Hdr::process(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ /* Note what HDR channel this frame will be once it comes back to us. */
+ updateAgcStatus(imageMetadata);
+
+ /*
+ * Now figure out what HDR channel this frame is. It should be available in the
+ * agc.delayed_status, unless this is an early frame after a mode switch, in which
+ * case delayedStatus_ should be right.
+ */
+ AgcStatus agcStatus;
+ if (!imageMetadata->get<AgcStatus>("agc.delayed_status", agcStatus))
+ delayedStatus_ = agcStatus.hdr;
+
+ auto it = config_.find(delayedStatus_.mode);
+ if (it == config_.end()) {
+ /* Shouldn't be possible. There would be nothing we could do. */
+ LOG(RPiHdr, Warning) << "Unexpected HDR mode " << delayedStatus_.mode;
+ return;
+ }
+
+ HdrConfig &config = it->second;
+
+ /* Update the spatially varying gains. They get written in prepare(). */
+ updateGains(stats, config);
+
+ if (updateTonemap(stats, config)) {
+ /* Add tonemap.status metadata. */
+ TonemapStatus tonemapStatus;
+
+ tonemapStatus.detailConstant = config.detailConstant;
+ tonemapStatus.detailSlope = config.detailSlope;
+ tonemapStatus.iirStrength = config.iirStrength;
+ tonemapStatus.strength = config.strength;
+ tonemapStatus.tonemap = tonemap_;
+
+ imageMetadata->set("tonemap.status", tonemapStatus);
+ }
+
+ if (config.stitchEnable) {
+ /* Add stitch.status metadata. */
+ StitchStatus stitchStatus;
+
+ stitchStatus.diffPower = config.diffPower;
+ stitchStatus.motionThreshold = config.motionThreshold;
+ stitchStatus.thresholdLo = config.thresholdLo;
+
+ imageMetadata->set("stitch.status", stitchStatus);
+ }
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Hdr(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h
new file mode 100644
index 00000000..980aa3d1
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/hdr.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023, Raspberry Pi Ltd
+ *
+ * hdr.h - HDR control algorithm
+ */
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <libcamera/geometry.h>
+
+#include "../hdr_algorithm.h"
+#include "../hdr_status.h"
+#include "../pwl.h"
+
+/* This is our implementation of an HDR algorithm. */
+
+namespace RPiController {
+
+struct HdrConfig {
+ std::string name;
+ std::vector<unsigned int> cadence;
+ std::map<unsigned int, std::string> channelMap;
+
+ /* Lens shading related parameters. */
+ Pwl spatialGain; /* Brightness to gain curve for different image regions. */
+ unsigned int diffusion; /* How much to diffuse the gain spatially. */
+
+ /* Tonemap related parameters. */
+ bool tonemapEnable;
+ uint16_t detailConstant;
+ double detailSlope;
+ double iirStrength;
+ double strength;
+ Pwl tonemap;
+
+ /* Stitch related parameters. */
+ bool stitchEnable;
+ uint16_t thresholdLo;
+ uint8_t diffPower;
+ double motionThreshold;
+
+ void read(const libcamera::YamlObject &params, const std::string &name);
+};
+
+class Hdr : public HdrAlgorithm
+{
+public:
+ Hdr(Controller *controller);
+ char const *name() const override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+ int setMode(std::string const &mode) override;
+ std::vector<unsigned int> getChannels() const override;
+
+private:
+ void updateAgcStatus(Metadata *metadata);
+ void updateGains(StatisticsPtr &stats, HdrConfig &config);
+ bool updateTonemap(StatisticsPtr &stats, HdrConfig &config);
+
+ std::map<std::string, HdrConfig> config_;
+ HdrStatus status_; /* track the current HDR mode and channel */
+ HdrStatus delayedStatus_; /* track the delayed HDR mode and channel */
+ std::string previousMode_;
+ Pwl tonemap_;
+ libcamera::Size regions_; /* stats regions */
+ unsigned int numRegions_; /* total number of stats regions */
+ std::vector<double> gains_[2];
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/lux.cpp b/src/ipa/rpi/controller/rpi/lux.cpp
new file mode 100644
index 00000000..06625f3a
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/lux.cpp
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * lux.cpp - Lux control algorithm
+ */
+#include <math.h>
+
+#include <libcamera/base/log.h>
+
+#include "../device_status.h"
+
+#include "lux.h"
+
+using namespace RPiController;
+using namespace libcamera;
+using namespace std::literals::chrono_literals;
+
+LOG_DEFINE_CATEGORY(RPiLux)
+
+#define NAME "rpi.lux"
+
+Lux::Lux(Controller *controller)
+ : Algorithm(controller)
+{
+ /*
+ * Put in some defaults as there will be no meaningful values until
+ * Process has run.
+ */
+ status_.aperture = 1.0;
+ status_.lux = 400;
+}
+
+char const *Lux::name() const
+{
+ return NAME;
+}
+
+int Lux::read(const libcamera::YamlObject &params)
+{
+ auto value = params["reference_shutter_speed"].get<double>();
+ if (!value)
+ return -EINVAL;
+ referenceShutterSpeed_ = *value * 1.0us;
+
+ value = params["reference_gain"].get<double>();
+ if (!value)
+ return -EINVAL;
+ referenceGain_ = *value;
+
+ referenceAperture_ = params["reference_aperture"].get<double>(1.0);
+
+ value = params["reference_Y"].get<double>();
+ if (!value)
+ return -EINVAL;
+ referenceY_ = *value;
+
+ value = params["reference_lux"].get<double>();
+ if (!value)
+ return -EINVAL;
+ referenceLux_ = *value;
+
+ currentAperture_ = referenceAperture_;
+ return 0;
+}
+
+void Lux::setCurrentAperture(double aperture)
+{
+ currentAperture_ = aperture;
+}
+
+void Lux::prepare(Metadata *imageMetadata)
+{
+ std::unique_lock<std::mutex> lock(mutex_);
+ imageMetadata->set("lux.status", status_);
+}
+
+void Lux::process(StatisticsPtr &stats, Metadata *imageMetadata)
+{
+ DeviceStatus deviceStatus;
+ if (imageMetadata->get("device.status", deviceStatus) == 0) {
+ double currentGain = deviceStatus.analogueGain;
+ double currentAperture = deviceStatus.aperture.value_or(currentAperture_);
+ double currentY = stats->yHist.interQuantileMean(0, 1);
+ double gainRatio = referenceGain_ / currentGain;
+ double shutterSpeedRatio =
+ referenceShutterSpeed_ / deviceStatus.shutterSpeed;
+ double apertureRatio = referenceAperture_ / currentAperture;
+ double yRatio = currentY * (65536 / stats->yHist.bins()) / referenceY_;
+ double estimatedLux = shutterSpeedRatio * gainRatio *
+ apertureRatio * apertureRatio *
+ yRatio * referenceLux_;
+ LuxStatus status;
+ status.lux = estimatedLux;
+ status.aperture = currentAperture;
+ LOG(RPiLux, Debug) << ": estimated lux " << estimatedLux;
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ status_ = status;
+ }
+ /*
+ * Overwrite the metadata here as well, so that downstream
+ * algorithms get the latest value.
+ */
+ imageMetadata->set("lux.status", status);
+ } else
+ LOG(RPiLux, Warning) << ": no device metadata";
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Lux(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/lux.h b/src/ipa/rpi/controller/rpi/lux.h
new file mode 100644
index 00000000..89411a54
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/lux.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * lux.h - Lux control algorithm
+ */
+#pragma once
+
+#include <mutex>
+
+#include <libcamera/base/utils.h>
+
+#include "../lux_status.h"
+#include "../algorithm.h"
+
+/* This is our implementation of the "lux control algorithm". */
+
+namespace RPiController {
+
+class Lux : public Algorithm
+{
+public:
+ Lux(Controller *controller);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+ void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
+ void setCurrentAperture(double aperture);
+
+private:
+ /*
+ * These values define the conditions of the reference image, against
+ * which we compare the new image.
+ */
+ libcamera::utils::Duration referenceShutterSpeed_;
+ double referenceGain_;
+ double referenceAperture_; /* units of 1/f */
+ double referenceY_; /* out of 65536 */
+ double referenceLux_;
+ double currentAperture_;
+ LuxStatus status_;
+ std::mutex mutex_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/noise.cpp b/src/ipa/rpi/controller/rpi/noise.cpp
new file mode 100644
index 00000000..bcd8b9ed
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/noise.cpp
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * noise.cpp - Noise control algorithm
+ */
+
+#include <math.h>
+
+#include <libcamera/base/log.h>
+
+#include "../device_status.h"
+#include "../noise_status.h"
+
+#include "noise.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiNoise)
+
+#define NAME "rpi.noise"
+
+Noise::Noise(Controller *controller)
+ : Algorithm(controller), modeFactor_(1.0)
+{
+}
+
+char const *Noise::name() const
+{
+ return NAME;
+}
+
+void Noise::switchMode(CameraMode const &cameraMode,
+ [[maybe_unused]] Metadata *metadata)
+{
+ /*
+ * For example, we would expect a 2x2 binned mode to have a "noise
+ * factor" of sqrt(2x2) = 2. (can't be less than one, right?)
+ */
+ modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
+}
+
+int Noise::read(const libcamera::YamlObject &params)
+{
+ auto value = params["reference_constant"].get<double>();
+ if (!value)
+ return -EINVAL;
+ referenceConstant_ = *value;
+
+ value = params["reference_slope"].get<double>();
+ if (!value)
+ return -EINVAL;
+ referenceSlope_ = *value;
+
+ return 0;
+}
+
+void Noise::prepare(Metadata *imageMetadata)
+{
+ struct DeviceStatus deviceStatus;
+ deviceStatus.analogueGain = 1.0; /* keep compiler calm */
+ if (imageMetadata->get("device.status", deviceStatus) == 0) {
+ /*
+ * There is a slight question as to exactly how the noise
+ * profile, specifically the constant part of it, scales. For
+ * now we assume it all scales the same, and we'll revisit this
+ * if it proves substantially wrong. NOTE: we may also want to
+ * make some adjustments based on the camera mode (such as
+ * binning), if we knew how to discover it...
+ */
+ double factor = sqrt(deviceStatus.analogueGain) / modeFactor_;
+ struct NoiseStatus status;
+ status.noiseConstant = referenceConstant_ * factor;
+ status.noiseSlope = referenceSlope_ * factor;
+ imageMetadata->set("noise.status", status);
+ LOG(RPiNoise, Debug)
+ << "constant " << status.noiseConstant
+ << " slope " << status.noiseSlope;
+ } else
+ LOG(RPiNoise, Warning) << " no metadata";
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return new Noise(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/noise.h b/src/ipa/rpi/controller/rpi/noise.h
new file mode 100644
index 00000000..74c31e64
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/noise.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * noise.h - Noise control algorithm
+ */
+#pragma once
+
+#include "../algorithm.h"
+#include "../noise_status.h"
+
+/* This is our implementation of the "noise algorithm". */
+
+namespace RPiController {
+
+class Noise : public Algorithm
+{
+public:
+ Noise(Controller *controller);
+ char const *name() const override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ int read(const libcamera::YamlObject &params) override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ /* the noise profile for analogue gain of 1.0 */
+ double referenceConstant_;
+ double referenceSlope_;
+ double modeFactor_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/saturation.cpp b/src/ipa/rpi/controller/rpi/saturation.cpp
new file mode 100644
index 00000000..813540e5
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/saturation.cpp
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * saturation.cpp - Saturation control algorithm
+ */
+#include "saturation.h"
+
+#include <libcamera/base/log.h>
+
+#include "saturation_status.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiSaturation)
+
+#define NAME "rpi.saturation"
+
+Saturation::Saturation(Controller *controller)
+ : Algorithm(controller)
+{
+}
+
+char const *Saturation::name() const
+{
+ return NAME;
+}
+
+int Saturation::read(const libcamera::YamlObject &params)
+{
+ config_.shiftR = params["shift_r"].get<uint8_t>(0);
+ config_.shiftG = params["shift_g"].get<uint8_t>(0);
+ config_.shiftB = params["shift_b"].get<uint8_t>(0);
+ return 0;
+}
+
+void Saturation::initialise()
+{
+}
+
+void Saturation::prepare(Metadata *imageMetadata)
+{
+ SaturationStatus saturation;
+
+ saturation.shiftR = config_.shiftR;
+ saturation.shiftG = config_.shiftG;
+ saturation.shiftB = config_.shiftB;
+ imageMetadata->set("saturation.status", saturation);
+}
+
+// Register algorithm with the system.
+static Algorithm *Create(Controller *controller)
+{
+ return (Algorithm *)new Saturation(controller);
+}
+static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/rpi/controller/rpi/saturation.h b/src/ipa/rpi/controller/rpi/saturation.h
new file mode 100644
index 00000000..97da412a
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/saturation.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * saturation.hpp - Saturation control algorithm
+ */
+#pragma once
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+struct SaturationConfig {
+ uint8_t shiftR;
+ uint8_t shiftG;
+ uint8_t shiftB;
+};
+
+class Saturation : public Algorithm
+{
+public:
+ Saturation(Controller *controller = NULL);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void initialise() override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ SaturationConfig config_;
+};
+
+} // namespace RPiController
diff --git a/src/ipa/rpi/controller/rpi/sdn.cpp b/src/ipa/rpi/controller/rpi/sdn.cpp
new file mode 100644
index 00000000..2f777dd7
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/sdn.cpp
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2021, Raspberry Pi Ltd
+ *
+ * sdn.cpp - SDN (spatial denoise) control algorithm
+ */
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include "../denoise_status.h"
+#include "../noise_status.h"
+
+#include "sdn.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiSdn)
+
+/*
+ * Calculate settings for the spatial denoise block using the noise profile in
+ * the image metadata.
+ */
+
+#define NAME "rpi.sdn"
+
+Sdn::Sdn(Controller *controller)
+ : DenoiseAlgorithm(controller), mode_(DenoiseMode::ColourOff)
+{
+}
+
+char const *Sdn::name() const
+{
+ return NAME;
+}
+
+int Sdn::read(const libcamera::YamlObject &params)
+{
+ LOG(RPiSdn, Warning)
+ << "Using legacy SDN tuning - please consider moving SDN inside rpi.denoise";
+ deviation_ = params["deviation"].get<double>(3.2);
+ strength_ = params["strength"].get<double>(0.75);
+ return 0;
+}
+
+void Sdn::initialise()
+{
+}
+
+void Sdn::prepare(Metadata *imageMetadata)
+{
+ struct NoiseStatus noiseStatus = {};
+ noiseStatus.noiseSlope = 3.0; /* in case no metadata */
+ if (imageMetadata->get("noise.status", noiseStatus) != 0)
+ LOG(RPiSdn, Warning) << "no noise profile found";
+ LOG(RPiSdn, Debug)
+ << "Noise profile: constant " << noiseStatus.noiseConstant
+ << " slope " << noiseStatus.noiseSlope;
+ struct DenoiseStatus status;
+ status.noiseConstant = noiseStatus.noiseConstant * deviation_;
+ status.noiseSlope = noiseStatus.noiseSlope * deviation_;
+ status.strength = strength_;
+ status.mode = utils::to_underlying(mode_);
+ imageMetadata->set("denoise.status", status);
+ LOG(RPiSdn, Debug)
+ << "programmed constant " << status.noiseConstant
+ << " slope " << status.noiseSlope
+ << " strength " << status.strength;
+}
+
+void Sdn::setMode(DenoiseMode mode)
+{
+ /* We only distinguish between off and all other modes. */
+ mode_ = mode;
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return (Algorithm *)new Sdn(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/sdn.h b/src/ipa/rpi/controller/rpi/sdn.h
new file mode 100644
index 00000000..9dd73c38
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/sdn.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * sdn.h - SDN (spatial denoise) control algorithm
+ */
+#pragma once
+
+#include "../algorithm.h"
+#include "../denoise_algorithm.h"
+
+namespace RPiController {
+
+/* Algorithm to calculate correct spatial denoise (SDN) settings. */
+
+class Sdn : public DenoiseAlgorithm
+{
+public:
+ Sdn(Controller *controller = NULL);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void initialise() override;
+ void prepare(Metadata *imageMetadata) override;
+ void setMode(DenoiseMode mode) override;
+
+private:
+ double deviation_;
+ double strength_;
+ DenoiseMode mode_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/sharpen.cpp b/src/ipa/rpi/controller/rpi/sharpen.cpp
new file mode 100644
index 00000000..4f6f020a
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/sharpen.cpp
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * sharpen.cpp - sharpening control algorithm
+ */
+
+#include <math.h>
+
+#include <libcamera/base/log.h>
+
+#include "../sharpen_status.h"
+
+#include "sharpen.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiSharpen)
+
+#define NAME "rpi.sharpen"
+
+Sharpen::Sharpen(Controller *controller)
+ : SharpenAlgorithm(controller), userStrength_(1.0)
+{
+}
+
+char const *Sharpen::name() const
+{
+ return NAME;
+}
+
+void Sharpen::switchMode(CameraMode const &cameraMode,
+ [[maybe_unused]] Metadata *metadata)
+{
+ /* can't be less than one, right? */
+ modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
+}
+
+int Sharpen::read(const libcamera::YamlObject &params)
+{
+ threshold_ = params["threshold"].get<double>(1.0);
+ strength_ = params["strength"].get<double>(1.0);
+ limit_ = params["limit"].get<double>(1.0);
+ LOG(RPiSharpen, Debug)
+ << "Read threshold " << threshold_
+ << " strength " << strength_
+ << " limit " << limit_;
+ return 0;
+}
+
+void Sharpen::setStrength(double strength)
+{
+ /*
+ * Note that this function is how an application sets the overall
+ * sharpening "strength". We call this the "user strength" field
+ * as there already is a strength_ field - being an internal gain
+ * parameter that gets passed to the ISP control code. Negative
+ * values are not allowed - coerce them to zero (no sharpening).
+ */
+ userStrength_ = std::max(0.0, strength);
+}
+
+void Sharpen::prepare(Metadata *imageMetadata)
+{
+ /*
+ * The userStrength_ affects the algorithm's internal gain directly, but
+ * we adjust the limit and threshold less aggressively. Using a sqrt
+ * function is an arbitrary but gentle way of accomplishing this.
+ */
+ double userStrengthSqrt = sqrt(userStrength_);
+ struct SharpenStatus status;
+ /*
+ * Binned modes seem to need the sharpening toned down with this
+ * pipeline, thus we use the modeFactor_ here. Also avoid
+ * divide-by-zero with the userStrengthSqrt.
+ */
+ status.threshold = threshold_ * modeFactor_ /
+ std::max(0.01, userStrengthSqrt);
+ status.strength = strength_ / modeFactor_ * userStrength_;
+ status.limit = limit_ / modeFactor_ * userStrengthSqrt;
+ /* Finally, report any application-supplied parameters that were used. */
+ status.userStrength = userStrength_;
+ imageMetadata->set("sharpen.status", status);
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+ return new Sharpen(controller);
+}
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/sharpen.h b/src/ipa/rpi/controller/rpi/sharpen.h
new file mode 100644
index 00000000..8bb7631e
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/sharpen.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * sharpen.h - sharpening control algorithm
+ */
+#pragma once
+
+#include "../sharpen_algorithm.h"
+#include "../sharpen_status.h"
+
+/* This is our implementation of the "sharpen algorithm". */
+
+namespace RPiController {
+
+class Sharpen : public SharpenAlgorithm
+{
+public:
+ Sharpen(Controller *controller);
+ char const *name() const override;
+ void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+ int read(const libcamera::YamlObject &params) override;
+ void setStrength(double strength) override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ double threshold_;
+ double strength_;
+ double limit_;
+ double modeFactor_;
+ double userStrength_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp
new file mode 100644
index 00000000..5f8b2bf2
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/tonemap.cpp
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * tonemap.cpp - Tonemap control algorithm
+ */
+#include "tonemap.h"
+
+#include <libcamera/base/log.h>
+
+#include "tonemap_status.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiTonemap)
+
+#define NAME "rpi.tonemap"
+
+Tonemap::Tonemap(Controller *controller)
+ : Algorithm(controller)
+{
+}
+
+char const *Tonemap::name() const
+{
+ return NAME;
+}
+
+int Tonemap::read(const libcamera::YamlObject &params)
+{
+ config_.detailConstant = params["detail_constant"].get<uint16_t>(0);
+ config_.detailSlope = params["detail_slope"].get<double>(0.1);
+ config_.iirStrength = params["iir_strength"].get<double>(1.0);
+ config_.strength = params["strength"].get<double>(1.0);
+ config_.tonemap.read(params["tone_curve"]);
+ return 0;
+}
+
+void Tonemap::initialise()
+{
+}
+
+void Tonemap::prepare(Metadata *imageMetadata)
+{
+ TonemapStatus tonemapStatus;
+
+ tonemapStatus.detailConstant = config_.detailConstant;
+ tonemapStatus.detailSlope = config_.detailSlope;
+ tonemapStatus.iirStrength = config_.iirStrength;
+ tonemapStatus.strength = config_.strength;
+ tonemapStatus.tonemap = config_.tonemap;
+ imageMetadata->set("tonemap.status", tonemapStatus);
+}
+
+// Register algorithm with the system.
+static Algorithm *Create(Controller *controller)
+{
+ return (Algorithm *)new Tonemap(controller);
+}
+static RegisterAlgorithm reg(NAME, &Create);
diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h
new file mode 100644
index 00000000..f25aa47f
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/tonemap.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * tonemap.hpp - Tonemap control algorithm
+ */
+#pragma once
+
+#include "algorithm.h"
+#include "pwl.h"
+
+namespace RPiController {
+
+struct TonemapConfig {
+ uint16_t detailConstant;
+ double detailSlope;
+ double iirStrength;
+ double strength;
+ Pwl tonemap;
+};
+
+class Tonemap : public Algorithm
+{
+public:
+ Tonemap(Controller *controller = NULL);
+ char const *name() const override;
+ int read(const libcamera::YamlObject &params) override;
+ void initialise() override;
+ void prepare(Metadata *imageMetadata) override;
+
+private:
+ TonemapConfig config_;
+};
+
+} // namespace RPiController
diff --git a/src/ipa/rpi/controller/saturation_status.h b/src/ipa/rpi/controller/saturation_status.h
new file mode 100644
index 00000000..337b66a3
--- /dev/null
+++ b/src/ipa/rpi/controller/saturation_status.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * saturation_status.h - Saturation control algorithm status
+ */
+#pragma once
+
+struct SaturationStatus {
+ uint8_t shiftR;
+ uint8_t shiftG;
+ uint8_t shiftB;
+};
diff --git a/src/ipa/rpi/controller/sharpen_algorithm.h b/src/ipa/rpi/controller/sharpen_algorithm.h
new file mode 100644
index 00000000..3be21c32
--- /dev/null
+++ b/src/ipa/rpi/controller/sharpen_algorithm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * sharpen_algorithm.h - sharpness control algorithm interface
+ */
+#pragma once
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+class SharpenAlgorithm : public Algorithm
+{
+public:
+ SharpenAlgorithm(Controller *controller) : Algorithm(controller) {}
+ /* A sharpness control algorithm must provide the following: */
+ virtual void setStrength(double strength) = 0;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/sharpen_status.h b/src/ipa/rpi/controller/sharpen_status.h
new file mode 100644
index 00000000..106166db
--- /dev/null
+++ b/src/ipa/rpi/controller/sharpen_status.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * sharpen_status.h - Sharpen control algorithm status
+ */
+#pragma once
+
+/* The "sharpen" algorithm stores the strength to use. */
+
+struct SharpenStatus {
+ /* controls the smallest level of detail (or noise!) that sharpening will pick up */
+ double threshold;
+ /* the rate at which the sharpening response ramps once above the threshold */
+ double strength;
+ /* upper limit of the allowed sharpening response */
+ double limit;
+ /* The sharpening strength requested by the user or application. */
+ double userStrength;
+};
diff --git a/src/ipa/rpi/controller/statistics.h b/src/ipa/rpi/controller/statistics.h
new file mode 100644
index 00000000..015d4efc
--- /dev/null
+++ b/src/ipa/rpi/controller/statistics.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * statistics.h - Raspberry Pi generic statistics structure
+ */
+#pragma once
+
+#include <memory>
+#include <stdint.h>
+#include <vector>
+
+#include "histogram.h"
+#include "region_stats.h"
+
+namespace RPiController {
+
+struct RgbySums {
+ RgbySums(uint64_t _rSum = 0, uint64_t _gSum = 0, uint64_t _bSum = 0, uint64_t _ySum = 0)
+ : rSum(_rSum), gSum(_gSum), bSum(_bSum), ySum(_ySum)
+ {
+ }
+ uint64_t rSum;
+ uint64_t gSum;
+ uint64_t bSum;
+ uint64_t ySum;
+};
+
+using RgbyRegions = RegionStats<RgbySums>;
+using FocusRegions = RegionStats<uint64_t>;
+
+struct Statistics {
+ /*
+ * All region based statistics are normalised to 16-bits, giving a
+ * maximum value of (1 << NormalisationFactorPow2) - 1.
+ */
+ static constexpr unsigned int NormalisationFactorPow2 = 16;
+
+ /*
+ * Positioning of the AGC statistics gathering in the pipeline:
+ * Pre-WB correction or post-WB correction.
+ * Assume this is post-LSC.
+ */
+ enum class AgcStatsPos { PreWb, PostWb };
+ const AgcStatsPos agcStatsPos;
+
+ /*
+ * Positioning of the AWB/ALSC statistics gathering in the pipeline:
+ * Pre-LSC or post-LSC.
+ */
+ enum class ColourStatsPos { PreLsc, PostLsc };
+ const ColourStatsPos colourStatsPos;
+
+ Statistics(AgcStatsPos a, ColourStatsPos c)
+ : agcStatsPos(a), colourStatsPos(c)
+ {
+ }
+
+ /* Histogram statistics. Not all histograms may be populated! */
+ Histogram rHist;
+ Histogram gHist;
+ Histogram bHist;
+ Histogram yHist;
+
+ /* Row sums for flicker avoidance. */
+ std::vector<RgbySums> rowSums;
+
+ /* Region based colour sums. */
+ RgbyRegions agcRegions;
+ RgbyRegions awbRegions;
+
+ /* Region based focus FoM. */
+ FocusRegions focusRegions;
+};
+
+using StatisticsPtr = std::shared_ptr<Statistics>;
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/stitch_status.h b/src/ipa/rpi/controller/stitch_status.h
new file mode 100644
index 00000000..b17800ed
--- /dev/null
+++ b/src/ipa/rpi/controller/stitch_status.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ *
+ * stitch_status.h - stitch control algorithm status
+ */
+#pragma once
+
+/*
+ * Parameters for the stitch block.
+ */
+
+struct StitchStatus {
+ uint16_t thresholdLo;
+ uint8_t diffPower;
+ double motionThreshold;
+};
diff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h
new file mode 100644
index 00000000..0e639946
--- /dev/null
+++ b/src/ipa/rpi/controller/tonemap_status.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * hdr.h - Tonemap control algorithm status
+ */
+#pragma once
+
+#include "pwl.h"
+
+struct TonemapStatus {
+ uint16_t detailConstant;
+ double detailSlope;
+ double iirStrength;
+ double strength;
+ RPiController::Pwl tonemap;
+};
diff --git a/src/ipa/rpi/meson.build b/src/ipa/rpi/meson.build
new file mode 100644
index 00000000..4811c76f
--- /dev/null
+++ b/src/ipa/rpi/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('cam_helper')
+subdir('common')
+subdir('controller')
+
+foreach pipeline : pipelines
+ pipeline = pipeline.split('/')
+ if pipeline.length() < 2 or pipeline[0] != 'rpi'
+ continue
+ endif
+
+ subdir(pipeline[1])
+endforeach
diff --git a/src/ipa/rpi/vc4/data/imx219.json b/src/ipa/rpi/vc4/data/imx219.json
new file mode 100644
index 00000000..54defc0b
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx219.json
@@ -0,0 +1,670 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 27685,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 998,
+ "reference_Y": 12744
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 3.67
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 204,
+ "slope": 0.01633
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2498.0, 0.9309, 0.3599,
+ 2911.0, 0.8682, 0.4283,
+ 2919.0, 0.8358, 0.4621,
+ 3627.0, 0.7646, 0.5327,
+ 4600.0, 0.6079, 0.6721,
+ 5716.0, 0.5712, 0.7017,
+ 8575.0, 0.4331, 0.8037
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.04791,
+ "transverse_neg": 0.04881
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.7,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.487, 1.481, 1.481, 1.445, 1.389, 1.327, 1.307, 1.307, 1.307, 1.309, 1.341, 1.405, 1.458, 1.494, 1.494, 1.497,
+ 1.491, 1.481, 1.448, 1.397, 1.331, 1.275, 1.243, 1.229, 1.229, 1.249, 1.287, 1.349, 1.409, 1.463, 1.494, 1.497,
+ 1.491, 1.469, 1.405, 1.331, 1.275, 1.217, 1.183, 1.172, 1.172, 1.191, 1.231, 1.287, 1.349, 1.424, 1.484, 1.499,
+ 1.487, 1.444, 1.363, 1.283, 1.217, 1.183, 1.148, 1.138, 1.138, 1.159, 1.191, 1.231, 1.302, 1.385, 1.461, 1.492,
+ 1.481, 1.423, 1.334, 1.253, 1.189, 1.148, 1.135, 1.119, 1.123, 1.137, 1.159, 1.203, 1.272, 1.358, 1.442, 1.488,
+ 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.118, 1.114, 1.116, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
+ 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.116, 1.114, 1.115, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
+ 1.479, 1.425, 1.336, 1.251, 1.189, 1.149, 1.136, 1.118, 1.121, 1.138, 1.158, 1.206, 1.275, 1.358, 1.443, 1.488,
+ 1.488, 1.448, 1.368, 1.285, 1.219, 1.189, 1.149, 1.139, 1.139, 1.158, 1.195, 1.235, 1.307, 1.387, 1.462, 1.493,
+ 1.496, 1.475, 1.411, 1.337, 1.284, 1.219, 1.189, 1.176, 1.176, 1.195, 1.235, 1.296, 1.356, 1.429, 1.487, 1.501,
+ 1.495, 1.489, 1.458, 1.407, 1.337, 1.287, 1.253, 1.239, 1.239, 1.259, 1.296, 1.356, 1.419, 1.472, 1.499, 1.499,
+ 1.494, 1.489, 1.489, 1.453, 1.398, 1.336, 1.317, 1.317, 1.317, 1.321, 1.351, 1.416, 1.467, 1.501, 1.501, 1.499
+ ]
+ },
+ {
+ "ct": 3850,
+ "table":
+ [
+ 1.694, 1.688, 1.688, 1.649, 1.588, 1.518, 1.495, 1.495, 1.495, 1.497, 1.532, 1.602, 1.659, 1.698, 1.698, 1.703,
+ 1.698, 1.688, 1.653, 1.597, 1.525, 1.464, 1.429, 1.413, 1.413, 1.437, 1.476, 1.542, 1.606, 1.665, 1.698, 1.703,
+ 1.697, 1.673, 1.605, 1.525, 1.464, 1.401, 1.369, 1.354, 1.354, 1.377, 1.417, 1.476, 1.542, 1.623, 1.687, 1.705,
+ 1.692, 1.646, 1.561, 1.472, 1.401, 1.368, 1.337, 1.323, 1.324, 1.348, 1.377, 1.417, 1.492, 1.583, 1.661, 1.697,
+ 1.686, 1.625, 1.528, 1.439, 1.372, 1.337, 1.321, 1.311, 1.316, 1.324, 1.348, 1.389, 1.461, 1.553, 1.642, 1.694,
+ 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.306, 1.306, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
+ 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.305, 1.305, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
+ 1.685, 1.624, 1.529, 1.438, 1.372, 1.336, 1.324, 1.309, 1.314, 1.323, 1.348, 1.392, 1.462, 1.555, 1.646, 1.694,
+ 1.692, 1.648, 1.561, 1.473, 1.403, 1.372, 1.336, 1.324, 1.324, 1.348, 1.378, 1.423, 1.495, 1.585, 1.667, 1.701,
+ 1.701, 1.677, 1.608, 1.527, 1.471, 1.403, 1.375, 1.359, 1.359, 1.378, 1.423, 1.488, 1.549, 1.631, 1.694, 1.709,
+ 1.702, 1.694, 1.656, 1.601, 1.527, 1.473, 1.441, 1.424, 1.424, 1.443, 1.488, 1.549, 1.621, 1.678, 1.706, 1.707,
+ 1.699, 1.694, 1.694, 1.654, 1.593, 1.525, 1.508, 1.508, 1.508, 1.509, 1.546, 1.614, 1.674, 1.708, 1.708, 1.707
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 2.179, 2.176, 2.176, 2.125, 2.048, 1.975, 1.955, 1.954, 1.954, 1.956, 1.993, 2.071, 2.141, 2.184, 2.185, 2.188,
+ 2.189, 2.176, 2.128, 2.063, 1.973, 1.908, 1.872, 1.856, 1.856, 1.876, 1.922, 1.999, 2.081, 2.144, 2.184, 2.192,
+ 2.187, 2.152, 2.068, 1.973, 1.907, 1.831, 1.797, 1.786, 1.786, 1.804, 1.853, 1.922, 1.999, 2.089, 2.166, 2.191,
+ 2.173, 2.117, 2.013, 1.908, 1.831, 1.791, 1.755, 1.749, 1.749, 1.767, 1.804, 1.853, 1.939, 2.041, 2.135, 2.181,
+ 2.166, 2.089, 1.975, 1.869, 1.792, 1.755, 1.741, 1.731, 1.734, 1.749, 1.767, 1.818, 1.903, 2.005, 2.111, 2.173,
+ 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.729, 1.725, 1.729, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
+ 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.727, 1.724, 1.725, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
+ 2.166, 2.085, 1.975, 1.869, 1.791, 1.755, 1.741, 1.729, 1.733, 1.749, 1.769, 1.819, 1.904, 2.009, 2.114, 2.174,
+ 2.174, 2.118, 2.015, 1.913, 1.831, 1.791, 1.755, 1.749, 1.749, 1.769, 1.811, 1.855, 1.943, 2.047, 2.139, 2.183,
+ 2.187, 2.151, 2.072, 1.979, 1.911, 1.831, 1.801, 1.791, 1.791, 1.811, 1.855, 1.933, 2.006, 2.101, 2.173, 2.197,
+ 2.189, 2.178, 2.132, 2.069, 1.979, 1.913, 1.879, 1.867, 1.867, 1.891, 1.933, 2.006, 2.091, 2.156, 2.195, 2.197,
+ 2.181, 2.179, 2.178, 2.131, 2.057, 1.981, 1.965, 1.965, 1.965, 1.969, 1.999, 2.083, 2.153, 2.197, 2.197, 2.196
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.967, 1.961, 1.955, 1.953, 1.954, 1.957, 1.961, 1.963, 1.963, 1.961, 1.959, 1.957, 1.954, 1.951, 1.951, 1.955,
+ 1.961, 1.959, 1.957, 1.956, 1.962, 1.967, 1.975, 1.979, 1.979, 1.975, 1.971, 1.967, 1.957, 1.952, 1.951, 1.951,
+ 1.959, 1.959, 1.959, 1.966, 1.976, 1.989, 1.999, 2.004, 2.003, 1.997, 1.991, 1.981, 1.967, 1.956, 1.951, 1.951,
+ 1.959, 1.962, 1.967, 1.978, 1.993, 2.009, 2.021, 2.028, 2.026, 2.021, 2.011, 1.995, 1.981, 1.964, 1.953, 1.951,
+ 1.961, 1.965, 1.977, 1.993, 2.009, 2.023, 2.041, 2.047, 2.047, 2.037, 2.024, 2.011, 1.995, 1.975, 1.958, 1.953,
+ 1.963, 1.968, 1.981, 2.001, 2.019, 2.039, 2.046, 2.052, 2.052, 2.051, 2.035, 2.021, 2.001, 1.978, 1.959, 1.955,
+ 1.961, 1.966, 1.981, 2.001, 2.019, 2.038, 2.043, 2.051, 2.052, 2.042, 2.034, 2.019, 2.001, 1.978, 1.959, 1.954,
+ 1.957, 1.961, 1.972, 1.989, 2.003, 2.021, 2.038, 2.039, 2.039, 2.034, 2.019, 2.004, 1.988, 1.971, 1.954, 1.949,
+ 1.952, 1.953, 1.959, 1.972, 1.989, 2.003, 2.016, 2.019, 2.019, 2.014, 2.003, 1.988, 1.971, 1.955, 1.948, 1.947,
+ 1.949, 1.948, 1.949, 1.957, 1.971, 1.978, 1.991, 1.994, 1.994, 1.989, 1.979, 1.967, 1.954, 1.946, 1.947, 1.947,
+ 1.949, 1.946, 1.944, 1.946, 1.949, 1.954, 1.962, 1.967, 1.967, 1.963, 1.956, 1.948, 1.943, 1.943, 1.946, 1.949,
+ 1.951, 1.946, 1.944, 1.942, 1.943, 1.943, 1.947, 1.948, 1.949, 1.947, 1.945, 1.941, 1.938, 1.939, 1.948, 1.952
+ ]
+ },
+ {
+ "ct": 3850,
+ "table":
+ [
+ 1.726, 1.724, 1.722, 1.723, 1.731, 1.735, 1.743, 1.746, 1.746, 1.741, 1.735, 1.729, 1.725, 1.721, 1.721, 1.721,
+ 1.724, 1.723, 1.723, 1.727, 1.735, 1.744, 1.749, 1.756, 1.756, 1.749, 1.744, 1.735, 1.727, 1.719, 1.719, 1.719,
+ 1.723, 1.723, 1.724, 1.735, 1.746, 1.759, 1.767, 1.775, 1.775, 1.766, 1.758, 1.746, 1.735, 1.723, 1.718, 1.716,
+ 1.723, 1.725, 1.732, 1.746, 1.759, 1.775, 1.782, 1.792, 1.792, 1.782, 1.772, 1.759, 1.745, 1.729, 1.718, 1.716,
+ 1.725, 1.729, 1.738, 1.756, 1.775, 1.785, 1.796, 1.803, 1.804, 1.794, 1.783, 1.772, 1.757, 1.736, 1.722, 1.718,
+ 1.728, 1.731, 1.741, 1.759, 1.781, 1.795, 1.803, 1.806, 1.808, 1.805, 1.791, 1.779, 1.762, 1.739, 1.722, 1.721,
+ 1.727, 1.731, 1.741, 1.759, 1.781, 1.791, 1.799, 1.804, 1.806, 1.801, 1.791, 1.779, 1.762, 1.739, 1.722, 1.717,
+ 1.722, 1.724, 1.733, 1.751, 1.768, 1.781, 1.791, 1.796, 1.799, 1.791, 1.781, 1.766, 1.754, 1.731, 1.717, 1.714,
+ 1.718, 1.718, 1.724, 1.737, 1.752, 1.768, 1.776, 1.782, 1.784, 1.781, 1.766, 1.754, 1.737, 1.724, 1.713, 1.709,
+ 1.716, 1.715, 1.716, 1.725, 1.737, 1.749, 1.756, 1.763, 1.764, 1.762, 1.749, 1.737, 1.724, 1.717, 1.709, 1.708,
+ 1.715, 1.714, 1.712, 1.715, 1.722, 1.729, 1.736, 1.741, 1.742, 1.739, 1.731, 1.723, 1.717, 1.712, 1.711, 1.709,
+ 1.716, 1.714, 1.711, 1.712, 1.715, 1.719, 1.723, 1.728, 1.731, 1.729, 1.723, 1.718, 1.711, 1.711, 1.713, 1.713
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 1.374, 1.372, 1.373, 1.374, 1.375, 1.378, 1.378, 1.381, 1.382, 1.382, 1.378, 1.373, 1.372, 1.369, 1.365, 1.365,
+ 1.371, 1.371, 1.372, 1.374, 1.378, 1.381, 1.384, 1.386, 1.388, 1.387, 1.384, 1.377, 1.372, 1.368, 1.364, 1.362,
+ 1.369, 1.371, 1.372, 1.377, 1.383, 1.391, 1.394, 1.396, 1.397, 1.395, 1.391, 1.382, 1.374, 1.369, 1.362, 1.361,
+ 1.369, 1.371, 1.375, 1.383, 1.391, 1.399, 1.402, 1.404, 1.405, 1.403, 1.398, 1.391, 1.379, 1.371, 1.363, 1.361,
+ 1.371, 1.373, 1.378, 1.388, 1.399, 1.407, 1.411, 1.413, 1.413, 1.411, 1.405, 1.397, 1.385, 1.374, 1.366, 1.362,
+ 1.371, 1.374, 1.379, 1.389, 1.405, 1.411, 1.414, 1.414, 1.415, 1.415, 1.411, 1.401, 1.388, 1.376, 1.367, 1.363,
+ 1.371, 1.373, 1.379, 1.389, 1.405, 1.408, 1.413, 1.414, 1.414, 1.413, 1.409, 1.401, 1.388, 1.376, 1.367, 1.362,
+ 1.366, 1.369, 1.374, 1.384, 1.396, 1.404, 1.407, 1.408, 1.408, 1.408, 1.401, 1.395, 1.382, 1.371, 1.363, 1.359,
+ 1.364, 1.365, 1.368, 1.375, 1.386, 1.396, 1.399, 1.401, 1.399, 1.399, 1.395, 1.385, 1.374, 1.365, 1.359, 1.357,
+ 1.361, 1.363, 1.365, 1.368, 1.377, 1.384, 1.388, 1.391, 1.391, 1.388, 1.385, 1.375, 1.366, 1.361, 1.358, 1.356,
+ 1.361, 1.362, 1.362, 1.364, 1.367, 1.373, 1.376, 1.377, 1.377, 1.375, 1.373, 1.366, 1.362, 1.358, 1.358, 1.358,
+ 1.361, 1.362, 1.362, 1.362, 1.363, 1.367, 1.369, 1.368, 1.367, 1.367, 1.367, 1.364, 1.358, 1.357, 1.358, 1.359
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.716, 2.568, 2.299, 2.065, 1.845, 1.693, 1.605, 1.597, 1.596, 1.634, 1.738, 1.914, 2.145, 2.394, 2.719, 2.901,
+ 2.593, 2.357, 2.093, 1.876, 1.672, 1.528, 1.438, 1.393, 1.394, 1.459, 1.569, 1.731, 1.948, 2.169, 2.481, 2.756,
+ 2.439, 2.197, 1.922, 1.691, 1.521, 1.365, 1.266, 1.222, 1.224, 1.286, 1.395, 1.573, 1.747, 1.988, 2.299, 2.563,
+ 2.363, 2.081, 1.797, 1.563, 1.376, 1.244, 1.152, 1.099, 1.101, 1.158, 1.276, 1.421, 1.607, 1.851, 2.163, 2.455,
+ 2.342, 2.003, 1.715, 1.477, 1.282, 1.152, 1.074, 1.033, 1.035, 1.083, 1.163, 1.319, 1.516, 1.759, 2.064, 2.398,
+ 2.342, 1.985, 1.691, 1.446, 1.249, 1.111, 1.034, 1.004, 1.004, 1.028, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
+ 2.342, 1.991, 1.691, 1.446, 1.249, 1.112, 1.034, 1.011, 1.005, 1.035, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
+ 2.365, 2.052, 1.751, 1.499, 1.299, 1.171, 1.089, 1.039, 1.042, 1.084, 1.162, 1.312, 1.516, 1.761, 2.059, 2.393,
+ 2.434, 2.159, 1.856, 1.601, 1.403, 1.278, 1.166, 1.114, 1.114, 1.162, 1.266, 1.402, 1.608, 1.847, 2.146, 2.435,
+ 2.554, 2.306, 2.002, 1.748, 1.563, 1.396, 1.299, 1.247, 1.243, 1.279, 1.386, 1.551, 1.746, 1.977, 2.272, 2.518,
+ 2.756, 2.493, 2.195, 1.947, 1.739, 1.574, 1.481, 1.429, 1.421, 1.457, 1.559, 1.704, 1.929, 2.159, 2.442, 2.681,
+ 2.935, 2.739, 2.411, 2.151, 1.922, 1.749, 1.663, 1.628, 1.625, 1.635, 1.716, 1.872, 2.113, 2.368, 2.663, 2.824
+ ],
+ "sigma": 0.00381,
+ "sigma_Cb": 0.00216
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2860,
+ "ccm":
+ [
+ 2.12089, -0.52461, -0.59629,
+ -0.85342, 2.80445, -0.95103,
+ -0.26897, -1.14788, 2.41685
+ ]
+ },
+ {
+ "ct": 2960,
+ "ccm":
+ [
+ 2.26962, -0.54174, -0.72789,
+ -0.77008, 2.60271, -0.83262,
+ -0.26036, -1.51254, 2.77289
+ ]
+ },
+ {
+ "ct": 3603,
+ "ccm":
+ [
+ 2.18644, -0.66148, -0.52496,
+ -0.77828, 2.69474, -0.91645,
+ -0.25239, -0.83059, 2.08298
+ ]
+ },
+ {
+ "ct": 4650,
+ "ccm":
+ [
+ 2.18174, -0.70887, -0.47287,
+ -0.70196, 2.76426, -1.06231,
+ -0.25157, -0.71978, 1.97135
+ ]
+ },
+ {
+ "ct": 5858,
+ "ccm":
+ [
+ 2.32392, -0.88421, -0.43971,
+ -0.63821, 2.58348, -0.94527,
+ -0.28541, -0.54112, 1.82653
+ ]
+ },
+ {
+ "ct": 7580,
+ "ccm":
+ [
+ 2.21175, -0.53242, -0.67933,
+ -0.57875, 3.07922, -1.50047,
+ -0.27709, -0.73338, 2.01048
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx219_noir.json b/src/ipa/rpi/vc4/data/imx219_noir.json
new file mode 100644
index 00000000..e823a90d
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx219_noir.json
@@ -0,0 +1,604 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 27685,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 998,
+ "reference_Y": 12744
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 3.67
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 204,
+ "slope": 0.01633
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "bayes": 0
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.7,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.487, 1.481, 1.481, 1.445, 1.389, 1.327, 1.307, 1.307, 1.307, 1.309, 1.341, 1.405, 1.458, 1.494, 1.494, 1.497,
+ 1.491, 1.481, 1.448, 1.397, 1.331, 1.275, 1.243, 1.229, 1.229, 1.249, 1.287, 1.349, 1.409, 1.463, 1.494, 1.497,
+ 1.491, 1.469, 1.405, 1.331, 1.275, 1.217, 1.183, 1.172, 1.172, 1.191, 1.231, 1.287, 1.349, 1.424, 1.484, 1.499,
+ 1.487, 1.444, 1.363, 1.283, 1.217, 1.183, 1.148, 1.138, 1.138, 1.159, 1.191, 1.231, 1.302, 1.385, 1.461, 1.492,
+ 1.481, 1.423, 1.334, 1.253, 1.189, 1.148, 1.135, 1.119, 1.123, 1.137, 1.159, 1.203, 1.272, 1.358, 1.442, 1.488,
+ 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.118, 1.114, 1.116, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
+ 1.479, 1.413, 1.321, 1.236, 1.176, 1.139, 1.116, 1.114, 1.115, 1.123, 1.149, 1.192, 1.258, 1.344, 1.432, 1.487,
+ 1.479, 1.425, 1.336, 1.251, 1.189, 1.149, 1.136, 1.118, 1.121, 1.138, 1.158, 1.206, 1.275, 1.358, 1.443, 1.488,
+ 1.488, 1.448, 1.368, 1.285, 1.219, 1.189, 1.149, 1.139, 1.139, 1.158, 1.195, 1.235, 1.307, 1.387, 1.462, 1.493,
+ 1.496, 1.475, 1.411, 1.337, 1.284, 1.219, 1.189, 1.176, 1.176, 1.195, 1.235, 1.296, 1.356, 1.429, 1.487, 1.501,
+ 1.495, 1.489, 1.458, 1.407, 1.337, 1.287, 1.253, 1.239, 1.239, 1.259, 1.296, 1.356, 1.419, 1.472, 1.499, 1.499,
+ 1.494, 1.489, 1.489, 1.453, 1.398, 1.336, 1.317, 1.317, 1.317, 1.321, 1.351, 1.416, 1.467, 1.501, 1.501, 1.499
+ ]
+ },
+ {
+ "ct": 3850,
+ "table":
+ [
+ 1.694, 1.688, 1.688, 1.649, 1.588, 1.518, 1.495, 1.495, 1.495, 1.497, 1.532, 1.602, 1.659, 1.698, 1.698, 1.703,
+ 1.698, 1.688, 1.653, 1.597, 1.525, 1.464, 1.429, 1.413, 1.413, 1.437, 1.476, 1.542, 1.606, 1.665, 1.698, 1.703,
+ 1.697, 1.673, 1.605, 1.525, 1.464, 1.401, 1.369, 1.354, 1.354, 1.377, 1.417, 1.476, 1.542, 1.623, 1.687, 1.705,
+ 1.692, 1.646, 1.561, 1.472, 1.401, 1.368, 1.337, 1.323, 1.324, 1.348, 1.377, 1.417, 1.492, 1.583, 1.661, 1.697,
+ 1.686, 1.625, 1.528, 1.439, 1.372, 1.337, 1.321, 1.311, 1.316, 1.324, 1.348, 1.389, 1.461, 1.553, 1.642, 1.694,
+ 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.306, 1.306, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
+ 1.684, 1.613, 1.514, 1.423, 1.359, 1.328, 1.311, 1.305, 1.305, 1.316, 1.339, 1.378, 1.446, 1.541, 1.633, 1.693,
+ 1.685, 1.624, 1.529, 1.438, 1.372, 1.336, 1.324, 1.309, 1.314, 1.323, 1.348, 1.392, 1.462, 1.555, 1.646, 1.694,
+ 1.692, 1.648, 1.561, 1.473, 1.403, 1.372, 1.336, 1.324, 1.324, 1.348, 1.378, 1.423, 1.495, 1.585, 1.667, 1.701,
+ 1.701, 1.677, 1.608, 1.527, 1.471, 1.403, 1.375, 1.359, 1.359, 1.378, 1.423, 1.488, 1.549, 1.631, 1.694, 1.709,
+ 1.702, 1.694, 1.656, 1.601, 1.527, 1.473, 1.441, 1.424, 1.424, 1.443, 1.488, 1.549, 1.621, 1.678, 1.706, 1.707,
+ 1.699, 1.694, 1.694, 1.654, 1.593, 1.525, 1.508, 1.508, 1.508, 1.509, 1.546, 1.614, 1.674, 1.708, 1.708, 1.707
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 2.179, 2.176, 2.176, 2.125, 2.048, 1.975, 1.955, 1.954, 1.954, 1.956, 1.993, 2.071, 2.141, 2.184, 2.185, 2.188,
+ 2.189, 2.176, 2.128, 2.063, 1.973, 1.908, 1.872, 1.856, 1.856, 1.876, 1.922, 1.999, 2.081, 2.144, 2.184, 2.192,
+ 2.187, 2.152, 2.068, 1.973, 1.907, 1.831, 1.797, 1.786, 1.786, 1.804, 1.853, 1.922, 1.999, 2.089, 2.166, 2.191,
+ 2.173, 2.117, 2.013, 1.908, 1.831, 1.791, 1.755, 1.749, 1.749, 1.767, 1.804, 1.853, 1.939, 2.041, 2.135, 2.181,
+ 2.166, 2.089, 1.975, 1.869, 1.792, 1.755, 1.741, 1.731, 1.734, 1.749, 1.767, 1.818, 1.903, 2.005, 2.111, 2.173,
+ 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.729, 1.725, 1.729, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
+ 2.165, 2.074, 1.956, 1.849, 1.777, 1.742, 1.727, 1.724, 1.725, 1.734, 1.758, 1.804, 1.884, 1.991, 2.099, 2.172,
+ 2.166, 2.085, 1.975, 1.869, 1.791, 1.755, 1.741, 1.729, 1.733, 1.749, 1.769, 1.819, 1.904, 2.009, 2.114, 2.174,
+ 2.174, 2.118, 2.015, 1.913, 1.831, 1.791, 1.755, 1.749, 1.749, 1.769, 1.811, 1.855, 1.943, 2.047, 2.139, 2.183,
+ 2.187, 2.151, 2.072, 1.979, 1.911, 1.831, 1.801, 1.791, 1.791, 1.811, 1.855, 1.933, 2.006, 2.101, 2.173, 2.197,
+ 2.189, 2.178, 2.132, 2.069, 1.979, 1.913, 1.879, 1.867, 1.867, 1.891, 1.933, 2.006, 2.091, 2.156, 2.195, 2.197,
+ 2.181, 2.179, 2.178, 2.131, 2.057, 1.981, 1.965, 1.965, 1.965, 1.969, 1.999, 2.083, 2.153, 2.197, 2.197, 2.196
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.967, 1.961, 1.955, 1.953, 1.954, 1.957, 1.961, 1.963, 1.963, 1.961, 1.959, 1.957, 1.954, 1.951, 1.951, 1.955,
+ 1.961, 1.959, 1.957, 1.956, 1.962, 1.967, 1.975, 1.979, 1.979, 1.975, 1.971, 1.967, 1.957, 1.952, 1.951, 1.951,
+ 1.959, 1.959, 1.959, 1.966, 1.976, 1.989, 1.999, 2.004, 2.003, 1.997, 1.991, 1.981, 1.967, 1.956, 1.951, 1.951,
+ 1.959, 1.962, 1.967, 1.978, 1.993, 2.009, 2.021, 2.028, 2.026, 2.021, 2.011, 1.995, 1.981, 1.964, 1.953, 1.951,
+ 1.961, 1.965, 1.977, 1.993, 2.009, 2.023, 2.041, 2.047, 2.047, 2.037, 2.024, 2.011, 1.995, 1.975, 1.958, 1.953,
+ 1.963, 1.968, 1.981, 2.001, 2.019, 2.039, 2.046, 2.052, 2.052, 2.051, 2.035, 2.021, 2.001, 1.978, 1.959, 1.955,
+ 1.961, 1.966, 1.981, 2.001, 2.019, 2.038, 2.043, 2.051, 2.052, 2.042, 2.034, 2.019, 2.001, 1.978, 1.959, 1.954,
+ 1.957, 1.961, 1.972, 1.989, 2.003, 2.021, 2.038, 2.039, 2.039, 2.034, 2.019, 2.004, 1.988, 1.971, 1.954, 1.949,
+ 1.952, 1.953, 1.959, 1.972, 1.989, 2.003, 2.016, 2.019, 2.019, 2.014, 2.003, 1.988, 1.971, 1.955, 1.948, 1.947,
+ 1.949, 1.948, 1.949, 1.957, 1.971, 1.978, 1.991, 1.994, 1.994, 1.989, 1.979, 1.967, 1.954, 1.946, 1.947, 1.947,
+ 1.949, 1.946, 1.944, 1.946, 1.949, 1.954, 1.962, 1.967, 1.967, 1.963, 1.956, 1.948, 1.943, 1.943, 1.946, 1.949,
+ 1.951, 1.946, 1.944, 1.942, 1.943, 1.943, 1.947, 1.948, 1.949, 1.947, 1.945, 1.941, 1.938, 1.939, 1.948, 1.952
+ ]
+ },
+ {
+ "ct": 3850,
+ "table":
+ [
+ 1.726, 1.724, 1.722, 1.723, 1.731, 1.735, 1.743, 1.746, 1.746, 1.741, 1.735, 1.729, 1.725, 1.721, 1.721, 1.721,
+ 1.724, 1.723, 1.723, 1.727, 1.735, 1.744, 1.749, 1.756, 1.756, 1.749, 1.744, 1.735, 1.727, 1.719, 1.719, 1.719,
+ 1.723, 1.723, 1.724, 1.735, 1.746, 1.759, 1.767, 1.775, 1.775, 1.766, 1.758, 1.746, 1.735, 1.723, 1.718, 1.716,
+ 1.723, 1.725, 1.732, 1.746, 1.759, 1.775, 1.782, 1.792, 1.792, 1.782, 1.772, 1.759, 1.745, 1.729, 1.718, 1.716,
+ 1.725, 1.729, 1.738, 1.756, 1.775, 1.785, 1.796, 1.803, 1.804, 1.794, 1.783, 1.772, 1.757, 1.736, 1.722, 1.718,
+ 1.728, 1.731, 1.741, 1.759, 1.781, 1.795, 1.803, 1.806, 1.808, 1.805, 1.791, 1.779, 1.762, 1.739, 1.722, 1.721,
+ 1.727, 1.731, 1.741, 1.759, 1.781, 1.791, 1.799, 1.804, 1.806, 1.801, 1.791, 1.779, 1.762, 1.739, 1.722, 1.717,
+ 1.722, 1.724, 1.733, 1.751, 1.768, 1.781, 1.791, 1.796, 1.799, 1.791, 1.781, 1.766, 1.754, 1.731, 1.717, 1.714,
+ 1.718, 1.718, 1.724, 1.737, 1.752, 1.768, 1.776, 1.782, 1.784, 1.781, 1.766, 1.754, 1.737, 1.724, 1.713, 1.709,
+ 1.716, 1.715, 1.716, 1.725, 1.737, 1.749, 1.756, 1.763, 1.764, 1.762, 1.749, 1.737, 1.724, 1.717, 1.709, 1.708,
+ 1.715, 1.714, 1.712, 1.715, 1.722, 1.729, 1.736, 1.741, 1.742, 1.739, 1.731, 1.723, 1.717, 1.712, 1.711, 1.709,
+ 1.716, 1.714, 1.711, 1.712, 1.715, 1.719, 1.723, 1.728, 1.731, 1.729, 1.723, 1.718, 1.711, 1.711, 1.713, 1.713
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 1.374, 1.372, 1.373, 1.374, 1.375, 1.378, 1.378, 1.381, 1.382, 1.382, 1.378, 1.373, 1.372, 1.369, 1.365, 1.365,
+ 1.371, 1.371, 1.372, 1.374, 1.378, 1.381, 1.384, 1.386, 1.388, 1.387, 1.384, 1.377, 1.372, 1.368, 1.364, 1.362,
+ 1.369, 1.371, 1.372, 1.377, 1.383, 1.391, 1.394, 1.396, 1.397, 1.395, 1.391, 1.382, 1.374, 1.369, 1.362, 1.361,
+ 1.369, 1.371, 1.375, 1.383, 1.391, 1.399, 1.402, 1.404, 1.405, 1.403, 1.398, 1.391, 1.379, 1.371, 1.363, 1.361,
+ 1.371, 1.373, 1.378, 1.388, 1.399, 1.407, 1.411, 1.413, 1.413, 1.411, 1.405, 1.397, 1.385, 1.374, 1.366, 1.362,
+ 1.371, 1.374, 1.379, 1.389, 1.405, 1.411, 1.414, 1.414, 1.415, 1.415, 1.411, 1.401, 1.388, 1.376, 1.367, 1.363,
+ 1.371, 1.373, 1.379, 1.389, 1.405, 1.408, 1.413, 1.414, 1.414, 1.413, 1.409, 1.401, 1.388, 1.376, 1.367, 1.362,
+ 1.366, 1.369, 1.374, 1.384, 1.396, 1.404, 1.407, 1.408, 1.408, 1.408, 1.401, 1.395, 1.382, 1.371, 1.363, 1.359,
+ 1.364, 1.365, 1.368, 1.375, 1.386, 1.396, 1.399, 1.401, 1.399, 1.399, 1.395, 1.385, 1.374, 1.365, 1.359, 1.357,
+ 1.361, 1.363, 1.365, 1.368, 1.377, 1.384, 1.388, 1.391, 1.391, 1.388, 1.385, 1.375, 1.366, 1.361, 1.358, 1.356,
+ 1.361, 1.362, 1.362, 1.364, 1.367, 1.373, 1.376, 1.377, 1.377, 1.375, 1.373, 1.366, 1.362, 1.358, 1.358, 1.358,
+ 1.361, 1.362, 1.362, 1.362, 1.363, 1.367, 1.369, 1.368, 1.367, 1.367, 1.367, 1.364, 1.358, 1.357, 1.358, 1.359
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.716, 2.568, 2.299, 2.065, 1.845, 1.693, 1.605, 1.597, 1.596, 1.634, 1.738, 1.914, 2.145, 2.394, 2.719, 2.901,
+ 2.593, 2.357, 2.093, 1.876, 1.672, 1.528, 1.438, 1.393, 1.394, 1.459, 1.569, 1.731, 1.948, 2.169, 2.481, 2.756,
+ 2.439, 2.197, 1.922, 1.691, 1.521, 1.365, 1.266, 1.222, 1.224, 1.286, 1.395, 1.573, 1.747, 1.988, 2.299, 2.563,
+ 2.363, 2.081, 1.797, 1.563, 1.376, 1.244, 1.152, 1.099, 1.101, 1.158, 1.276, 1.421, 1.607, 1.851, 2.163, 2.455,
+ 2.342, 2.003, 1.715, 1.477, 1.282, 1.152, 1.074, 1.033, 1.035, 1.083, 1.163, 1.319, 1.516, 1.759, 2.064, 2.398,
+ 2.342, 1.985, 1.691, 1.446, 1.249, 1.111, 1.034, 1.004, 1.004, 1.028, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
+ 2.342, 1.991, 1.691, 1.446, 1.249, 1.112, 1.034, 1.011, 1.005, 1.035, 1.114, 1.274, 1.472, 1.716, 2.019, 2.389,
+ 2.365, 2.052, 1.751, 1.499, 1.299, 1.171, 1.089, 1.039, 1.042, 1.084, 1.162, 1.312, 1.516, 1.761, 2.059, 2.393,
+ 2.434, 2.159, 1.856, 1.601, 1.403, 1.278, 1.166, 1.114, 1.114, 1.162, 1.266, 1.402, 1.608, 1.847, 2.146, 2.435,
+ 2.554, 2.306, 2.002, 1.748, 1.563, 1.396, 1.299, 1.247, 1.243, 1.279, 1.386, 1.551, 1.746, 1.977, 2.272, 2.518,
+ 2.756, 2.493, 2.195, 1.947, 1.739, 1.574, 1.481, 1.429, 1.421, 1.457, 1.559, 1.704, 1.929, 2.159, 2.442, 2.681,
+ 2.935, 2.739, 2.411, 2.151, 1.922, 1.749, 1.663, 1.628, 1.625, 1.635, 1.716, 1.872, 2.113, 2.368, 2.663, 2.824
+ ],
+ "sigma": 0.00381,
+ "sigma_Cb": 0.00216
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2498,
+ "ccm":
+ [
+ 1.58731, -0.18011, -0.40721,
+ -0.60639, 2.03422, -0.42782,
+ -0.19612, -1.69203, 2.88815
+ ]
+ },
+ {
+ "ct": 2811,
+ "ccm":
+ [
+ 1.61593, -0.33164, -0.28429,
+ -0.55048, 1.97779, -0.42731,
+ -0.12042, -1.42847, 2.54889
+ ]
+ },
+ {
+ "ct": 2911,
+ "ccm":
+ [
+ 1.62771, -0.41282, -0.21489,
+ -0.57991, 2.04176, -0.46186,
+ -0.07613, -1.13359, 2.20972
+ ]
+ },
+ {
+ "ct": 2919,
+ "ccm":
+ [
+ 1.62661, -0.37736, -0.24925,
+ -0.52519, 1.95233, -0.42714,
+ -0.10842, -1.34929, 2.45771
+ ]
+ },
+ {
+ "ct": 3627,
+ "ccm":
+ [
+ 1.70385, -0.57231, -0.13154,
+ -0.47763, 1.85998, -0.38235,
+ -0.07467, -0.82678, 1.90145
+ ]
+ },
+ {
+ "ct": 4600,
+ "ccm":
+ [
+ 1.68486, -0.61085, -0.07402,
+ -0.41927, 2.04016, -0.62089,
+ -0.08633, -0.67672, 1.76305
+ ]
+ },
+ {
+ "ct": 5716,
+ "ccm":
+ [
+ 1.80439, -0.73699, -0.06739,
+ -0.36073, 1.83327, -0.47255,
+ -0.08378, -0.56403, 1.64781
+ ]
+ },
+ {
+ "ct": 8575,
+ "ccm":
+ [
+ 1.89357, -0.76427, -0.12931,
+ -0.27399, 2.15605, -0.88206,
+ -0.12035, -0.68256, 1.80292
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx290.json b/src/ipa/rpi/vc4/data/imx290.json
new file mode 100644
index 00000000..8a7cadba
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx290.json
@@ -0,0 +1,205 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 3840
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 6813,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 890,
+ "reference_Y": 12900
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.67
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 187,
+ "slope": 0.00842
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "bayes": 0
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "speed": 0.2,
+ "metering_modes":
+ {
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ },
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 10, 30000, 60000 ],
+ "gain": [ 1.0, 2.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 10, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.16,
+ 10000, 0.16
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.7,
+ "luminance_lut":
+ [
+ 2.844, 2.349, 2.018, 1.775, 1.599, 1.466, 1.371, 1.321, 1.306, 1.316, 1.357, 1.439, 1.552, 1.705, 1.915, 2.221,
+ 2.576, 2.151, 1.851, 1.639, 1.478, 1.358, 1.272, 1.231, 1.218, 1.226, 1.262, 1.335, 1.438, 1.571, 1.766, 2.067,
+ 2.381, 2.005, 1.739, 1.545, 1.389, 1.278, 1.204, 1.166, 1.153, 1.161, 1.194, 1.263, 1.356, 1.489, 1.671, 1.943,
+ 2.242, 1.899, 1.658, 1.481, 1.329, 1.225, 1.156, 1.113, 1.096, 1.107, 1.143, 1.201, 1.289, 1.423, 1.607, 1.861,
+ 2.152, 1.831, 1.602, 1.436, 1.291, 1.193, 1.121, 1.069, 1.047, 1.062, 1.107, 1.166, 1.249, 1.384, 1.562, 1.801,
+ 2.104, 1.795, 1.572, 1.407, 1.269, 1.174, 1.099, 1.041, 1.008, 1.029, 1.083, 1.146, 1.232, 1.364, 1.547, 1.766,
+ 2.104, 1.796, 1.572, 1.403, 1.264, 1.171, 1.097, 1.036, 1.001, 1.025, 1.077, 1.142, 1.231, 1.363, 1.549, 1.766,
+ 2.148, 1.827, 1.594, 1.413, 1.276, 1.184, 1.114, 1.062, 1.033, 1.049, 1.092, 1.153, 1.242, 1.383, 1.577, 1.795,
+ 2.211, 1.881, 1.636, 1.455, 1.309, 1.214, 1.149, 1.104, 1.081, 1.089, 1.125, 1.184, 1.273, 1.423, 1.622, 1.846,
+ 2.319, 1.958, 1.698, 1.516, 1.362, 1.262, 1.203, 1.156, 1.137, 1.142, 1.171, 1.229, 1.331, 1.484, 1.682, 1.933,
+ 2.459, 2.072, 1.789, 1.594, 1.441, 1.331, 1.261, 1.219, 1.199, 1.205, 1.232, 1.301, 1.414, 1.571, 1.773, 2.052,
+ 2.645, 2.206, 1.928, 1.728, 1.559, 1.451, 1.352, 1.301, 1.282, 1.289, 1.319, 1.395, 1.519, 1.685, 1.904, 2.227
+ ],
+ "sigma": 0.005,
+ "sigma_Cb": 0.005
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 3900,
+ "ccm":
+ [
+ 1.54659, -0.17707, -0.36953,
+ -0.51471, 1.72733, -0.21262,
+ 0.06667, -0.92279, 1.85612
+ ]
+ }
+ ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/imx296.json b/src/ipa/rpi/vc4/data/imx296.json
new file mode 100644
index 00000000..7621f759
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx296.json
@@ -0,0 +1,434 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 3840
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 7598,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 800,
+ "reference_Y": 14028
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.671
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 215,
+ "slope": 0.01058
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 7600
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 7600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2500.0, 0.5386, 0.2458,
+ 2800.0, 0.4883, 0.3303,
+ 2900.0, 0.4855, 0.3349,
+ 3620.0, 0.4203, 0.4367,
+ 4560.0, 0.3455, 0.5444,
+ 5600.0, 0.2948, 0.6124,
+ 7400.0, 0.2336, 0.6894
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.03093,
+ "transverse_neg": 0.02374
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 30000, 45000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 12.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 30000 ],
+ "gain": [ 1.0, 2.0, 4.0, 8.0, 16.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 4000,
+ "table":
+ [
+ 2.726, 2.736, 2.737, 2.739, 2.741, 2.741, 2.742, 2.742, 2.743, 2.743, 2.742, 2.742, 2.742, 2.742, 2.741, 2.739,
+ 2.728, 2.736, 2.739, 2.741, 2.742, 2.743, 2.744, 2.745, 2.746, 2.746, 2.745, 2.743, 2.742, 2.742, 2.742, 2.741,
+ 2.729, 2.737, 2.741, 2.744, 2.746, 2.747, 2.748, 2.749, 2.751, 2.751, 2.749, 2.746, 2.744, 2.743, 2.743, 2.743,
+ 2.729, 2.738, 2.743, 2.746, 2.749, 2.749, 2.751, 2.752, 2.753, 2.753, 2.752, 2.751, 2.746, 2.744, 2.744, 2.746,
+ 2.728, 2.737, 2.742, 2.746, 2.749, 2.751, 2.754, 2.755, 2.754, 2.755, 2.754, 2.751, 2.748, 2.746, 2.747, 2.748,
+ 2.724, 2.738, 2.742, 2.746, 2.749, 2.752, 2.755, 2.755, 2.755, 2.755, 2.754, 2.752, 2.749, 2.749, 2.748, 2.748,
+ 2.726, 2.738, 2.741, 2.745, 2.749, 2.753, 2.754, 2.755, 2.755, 2.755, 2.754, 2.753, 2.749, 2.748, 2.748, 2.748,
+ 2.726, 2.738, 2.741, 2.745, 2.746, 2.752, 2.753, 2.753, 2.753, 2.753, 2.754, 2.751, 2.748, 2.748, 2.746, 2.745,
+ 2.726, 2.736, 2.738, 2.742, 2.745, 2.749, 2.752, 2.753, 2.752, 2.752, 2.751, 2.749, 2.747, 2.745, 2.744, 2.742,
+ 2.724, 2.733, 2.736, 2.739, 2.742, 2.745, 2.748, 2.749, 2.749, 2.748, 2.748, 2.747, 2.744, 2.743, 2.742, 2.741,
+ 2.722, 2.726, 2.733, 2.735, 2.737, 2.741, 2.743, 2.744, 2.744, 2.744, 2.744, 2.742, 2.741, 2.741, 2.739, 2.737,
+ 2.719, 2.722, 2.727, 2.729, 2.731, 2.732, 2.734, 2.734, 2.735, 2.735, 2.735, 2.734, 2.733, 2.732, 2.732, 2.732
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 3.507, 3.522, 3.525, 3.527, 3.531, 3.533, 3.534, 3.535, 3.535, 3.536, 3.536, 3.537, 3.537, 3.538, 3.537, 3.536,
+ 3.511, 3.524, 3.528, 3.532, 3.533, 3.535, 3.537, 3.538, 3.538, 3.541, 3.539, 3.539, 3.539, 3.539, 3.538, 3.538,
+ 3.513, 3.528, 3.532, 3.535, 3.538, 3.542, 3.543, 3.546, 3.548, 3.551, 3.547, 3.543, 3.541, 3.541, 3.541, 3.541,
+ 3.513, 3.528, 3.533, 3.539, 3.544, 3.546, 3.548, 3.552, 3.553, 3.553, 3.552, 3.548, 3.543, 3.542, 3.542, 3.545,
+ 3.513, 3.528, 3.534, 3.541, 3.547, 3.549, 3.552, 3.553, 3.554, 3.554, 3.553, 3.549, 3.546, 3.544, 3.547, 3.549,
+ 3.508, 3.528, 3.533, 3.541, 3.548, 3.551, 3.553, 3.554, 3.555, 3.555, 3.555, 3.551, 3.548, 3.547, 3.549, 3.551,
+ 3.511, 3.529, 3.534, 3.541, 3.548, 3.551, 3.553, 3.555, 3.555, 3.555, 3.556, 3.554, 3.549, 3.548, 3.548, 3.548,
+ 3.511, 3.528, 3.533, 3.539, 3.546, 3.549, 3.553, 3.554, 3.554, 3.554, 3.554, 3.553, 3.549, 3.547, 3.547, 3.547,
+ 3.511, 3.527, 3.533, 3.536, 3.541, 3.547, 3.551, 3.553, 3.553, 3.552, 3.551, 3.551, 3.548, 3.544, 3.542, 3.543,
+ 3.507, 3.523, 3.528, 3.533, 3.538, 3.541, 3.546, 3.548, 3.549, 3.548, 3.548, 3.546, 3.542, 3.541, 3.541, 3.541,
+ 3.505, 3.514, 3.523, 3.527, 3.532, 3.537, 3.538, 3.544, 3.544, 3.544, 3.542, 3.541, 3.537, 3.537, 3.536, 3.535,
+ 3.503, 3.508, 3.515, 3.519, 3.521, 3.523, 3.524, 3.525, 3.526, 3.526, 3.527, 3.526, 3.524, 3.526, 3.527, 3.527
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 4000,
+ "table":
+ [
+ 2.032, 2.037, 2.039, 2.041, 2.041, 2.042, 2.043, 2.044, 2.045, 2.045, 2.044, 2.043, 2.042, 2.041, 2.041, 2.034,
+ 2.032, 2.036, 2.039, 2.041, 2.042, 2.042, 2.043, 2.044, 2.045, 2.046, 2.045, 2.044, 2.042, 2.041, 2.039, 2.035,
+ 2.032, 2.036, 2.038, 2.041, 2.043, 2.044, 2.044, 2.045, 2.046, 2.047, 2.047, 2.045, 2.043, 2.042, 2.041, 2.037,
+ 2.032, 2.035, 2.039, 2.042, 2.043, 2.044, 2.045, 2.046, 2.048, 2.048, 2.047, 2.046, 2.045, 2.044, 2.042, 2.039,
+ 2.031, 2.034, 2.037, 2.039, 2.043, 2.045, 2.045, 2.046, 2.047, 2.047, 2.047, 2.046, 2.045, 2.044, 2.043, 2.039,
+ 2.029, 2.033, 2.036, 2.039, 2.042, 2.043, 2.045, 2.046, 2.046, 2.046, 2.046, 2.046, 2.046, 2.045, 2.044, 2.041,
+ 2.028, 2.032, 2.035, 2.039, 2.041, 2.043, 2.044, 2.045, 2.045, 2.046, 2.046, 2.046, 2.046, 2.045, 2.044, 2.039,
+ 2.027, 2.032, 2.035, 2.038, 2.039, 2.041, 2.044, 2.044, 2.044, 2.045, 2.046, 2.046, 2.046, 2.045, 2.044, 2.039,
+ 2.027, 2.031, 2.034, 2.035, 2.037, 2.039, 2.042, 2.043, 2.044, 2.045, 2.045, 2.046, 2.045, 2.044, 2.043, 2.038,
+ 2.025, 2.028, 2.032, 2.034, 2.036, 2.037, 2.041, 2.042, 2.043, 2.044, 2.044, 2.044, 2.044, 2.043, 2.041, 2.036,
+ 2.024, 2.026, 2.029, 2.032, 2.034, 2.036, 2.038, 2.041, 2.041, 2.042, 2.043, 2.042, 2.041, 2.041, 2.037, 2.036,
+ 2.022, 2.024, 2.027, 2.029, 2.032, 2.034, 2.036, 2.039, 2.039, 2.039, 2.041, 2.039, 2.039, 2.038, 2.036, 2.034
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 1.585, 1.587, 1.589, 1.589, 1.589, 1.591, 1.591, 1.591, 1.591, 1.591, 1.589, 1.589, 1.588, 1.588, 1.587, 1.581,
+ 1.585, 1.587, 1.588, 1.589, 1.591, 1.591, 1.591, 1.591, 1.591, 1.591, 1.591, 1.589, 1.588, 1.588, 1.587, 1.582,
+ 1.585, 1.586, 1.588, 1.589, 1.591, 1.591, 1.591, 1.591, 1.592, 1.592, 1.591, 1.591, 1.589, 1.588, 1.587, 1.584,
+ 1.585, 1.586, 1.588, 1.589, 1.591, 1.592, 1.592, 1.592, 1.593, 1.593, 1.592, 1.591, 1.589, 1.589, 1.588, 1.586,
+ 1.584, 1.586, 1.587, 1.589, 1.591, 1.591, 1.592, 1.592, 1.592, 1.592, 1.591, 1.591, 1.591, 1.589, 1.589, 1.586,
+ 1.583, 1.585, 1.587, 1.588, 1.589, 1.591, 1.591, 1.592, 1.592, 1.591, 1.591, 1.591, 1.591, 1.591, 1.589, 1.586,
+ 1.583, 1.584, 1.586, 1.588, 1.589, 1.589, 1.591, 1.591, 1.591, 1.591, 1.591, 1.591, 1.591, 1.591, 1.589, 1.585,
+ 1.581, 1.584, 1.586, 1.587, 1.588, 1.588, 1.589, 1.591, 1.591, 1.591, 1.591, 1.591, 1.591, 1.589, 1.589, 1.585,
+ 1.581, 1.583, 1.584, 1.586, 1.587, 1.588, 1.589, 1.589, 1.591, 1.591, 1.591, 1.591, 1.591, 1.589, 1.589, 1.585,
+ 1.579, 1.581, 1.583, 1.584, 1.586, 1.586, 1.588, 1.589, 1.589, 1.589, 1.589, 1.589, 1.589, 1.589, 1.587, 1.584,
+ 1.578, 1.579, 1.581, 1.583, 1.584, 1.585, 1.586, 1.587, 1.588, 1.588, 1.588, 1.588, 1.588, 1.587, 1.585, 1.583,
+ 1.577, 1.578, 1.579, 1.582, 1.583, 1.584, 1.585, 1.586, 1.586, 1.587, 1.587, 1.587, 1.586, 1.586, 1.584, 1.583
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 1.112, 1.098, 1.078, 1.062, 1.049, 1.039, 1.031, 1.027, 1.026, 1.027, 1.034, 1.043, 1.054, 1.069, 1.087, 1.096,
+ 1.106, 1.091, 1.073, 1.056, 1.042, 1.032, 1.025, 1.021, 1.021, 1.022, 1.027, 1.036, 1.047, 1.061, 1.077, 1.088,
+ 1.101, 1.085, 1.066, 1.049, 1.035, 1.026, 1.019, 1.013, 1.013, 1.015, 1.021, 1.028, 1.039, 1.052, 1.069, 1.083,
+ 1.098, 1.081, 1.059, 1.045, 1.031, 1.021, 1.013, 1.007, 1.007, 1.009, 1.014, 1.021, 1.033, 1.046, 1.063, 1.081,
+ 1.097, 1.076, 1.057, 1.041, 1.027, 1.016, 1.007, 1.004, 1.002, 1.005, 1.009, 1.017, 1.028, 1.043, 1.061, 1.077,
+ 1.096, 1.075, 1.054, 1.039, 1.025, 1.014, 1.005, 1.001, 1.001, 1.002, 1.006, 1.015, 1.027, 1.041, 1.058, 1.076,
+ 1.096, 1.074, 1.054, 1.039, 1.025, 1.013, 1.005, 1.001, 1.001, 1.001, 1.006, 1.015, 1.026, 1.041, 1.058, 1.076,
+ 1.096, 1.075, 1.056, 1.041, 1.026, 1.014, 1.007, 1.003, 1.002, 1.004, 1.008, 1.016, 1.028, 1.041, 1.059, 1.076,
+ 1.096, 1.079, 1.059, 1.044, 1.029, 1.018, 1.011, 1.007, 1.005, 1.008, 1.012, 1.019, 1.031, 1.044, 1.061, 1.077,
+ 1.101, 1.084, 1.065, 1.049, 1.035, 1.024, 1.017, 1.011, 1.011, 1.012, 1.018, 1.025, 1.036, 1.051, 1.068, 1.081,
+ 1.106, 1.092, 1.072, 1.055, 1.042, 1.033, 1.024, 1.019, 1.018, 1.019, 1.025, 1.032, 1.044, 1.058, 1.076, 1.088,
+ 1.113, 1.097, 1.079, 1.063, 1.049, 1.039, 1.031, 1.025, 1.025, 1.025, 1.031, 1.039, 1.051, 1.065, 1.083, 1.094
+ ],
+ "sigma": 0.00047,
+ "sigma_Cb": 0.00056
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2500,
+ "ccm":
+ [
+ 1.95054, -0.57435, -0.37619,
+ -0.46945, 1.86661, -0.39716,
+ 0.07977, -1.14072, 2.06095
+ ]
+ },
+ {
+ "ct": 2800,
+ "ccm":
+ [
+ 1.94104, -0.60261, -0.33844,
+ -0.43162, 1.85422, -0.42261,
+ 0.03799, -0.95022, 1.91222
+ ]
+ },
+ {
+ "ct": 2900,
+ "ccm":
+ [
+ 1.91828, -0.59569, -0.32258,
+ -0.51902, 2.09091, -0.57189,
+ -0.03324, -0.73462, 1.76785
+ ]
+ },
+ {
+ "ct": 3620,
+ "ccm":
+ [
+ 1.97199, -0.66403, -0.30797,
+ -0.46411, 2.02612, -0.56201,
+ -0.07764, -0.61178, 1.68942
+ ]
+ },
+ {
+ "ct": 4560,
+ "ccm":
+ [
+ 2.15256, -0.84787, -0.30469,
+ -0.48422, 2.28962, -0.80541,
+ -0.15113, -0.53014, 1.68127
+ ]
+ },
+ {
+ "ct": 5600,
+ "ccm":
+ [
+ 2.04576, -0.74771, -0.29805,
+ -0.36332, 1.98993, -0.62662,
+ -0.09328, -0.46543, 1.55871
+ ]
+ },
+ {
+ "ct": 7400,
+ "ccm":
+ [
+ 2.37532, -0.83069, -0.54462,
+ -0.48279, 2.84309, -1.36031,
+ -0.21178, -0.66532, 1.87709
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen":
+ {
+ "threshold": 0.1,
+ "strength": 1.0,
+ "limit": 0.18
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx296_mono.json b/src/ipa/rpi/vc4/data/imx296_mono.json
new file mode 100644
index 00000000..d4140c81
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx296_mono.json
@@ -0,0 +1,231 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 3840
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 9998,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 823,
+ "reference_Y": 12396
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.753
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 0,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 4000,
+ "table":
+ [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 4000,
+ "table":
+ [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 1.308, 1.293, 1.228, 1.175, 1.139, 1.108, 1.092, 1.082, 1.082, 1.086, 1.097, 1.114, 1.149, 1.199, 1.279, 1.303,
+ 1.293, 1.249, 1.199, 1.162, 1.136, 1.109, 1.087, 1.077, 1.072, 1.081, 1.095, 1.103, 1.133, 1.172, 1.225, 1.282,
+ 1.251, 1.212, 1.186, 1.159, 1.129, 1.114, 1.102, 1.088, 1.088, 1.088, 1.095, 1.117, 1.123, 1.158, 1.198, 1.249,
+ 1.223, 1.192, 1.177, 1.163, 1.147, 1.139, 1.132, 1.112, 1.111, 1.107, 1.113, 1.118, 1.139, 1.155, 1.186, 1.232,
+ 1.207, 1.186, 1.171, 1.162, 1.168, 1.163, 1.153, 1.138, 1.129, 1.128, 1.132, 1.136, 1.149, 1.167, 1.189, 1.216,
+ 1.198, 1.186, 1.176, 1.176, 1.177, 1.185, 1.171, 1.157, 1.146, 1.144, 1.146, 1.149, 1.161, 1.181, 1.201, 1.221,
+ 1.203, 1.181, 1.176, 1.178, 1.191, 1.189, 1.188, 1.174, 1.159, 1.153, 1.158, 1.161, 1.169, 1.185, 1.211, 1.227,
+ 1.211, 1.179, 1.177, 1.187, 1.194, 1.196, 1.194, 1.187, 1.176, 1.169, 1.171, 1.171, 1.175, 1.189, 1.214, 1.226,
+ 1.219, 1.182, 1.184, 1.191, 1.195, 1.199, 1.197, 1.194, 1.188, 1.185, 1.179, 1.179, 1.182, 1.194, 1.212, 1.227,
+ 1.237, 1.192, 1.194, 1.194, 1.198, 1.199, 1.198, 1.197, 1.196, 1.193, 1.189, 1.189, 1.192, 1.203, 1.214, 1.231,
+ 1.282, 1.199, 1.199, 1.197, 1.199, 1.199, 1.192, 1.193, 1.193, 1.194, 1.196, 1.197, 1.206, 1.216, 1.228, 1.244,
+ 1.309, 1.236, 1.204, 1.203, 1.202, 1.194, 1.194, 1.188, 1.192, 1.192, 1.199, 1.201, 1.212, 1.221, 1.235, 1.247
+ ],
+ "sigma": 0.005,
+ "sigma_Cb": 0.005
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.sharpen":
+ {
+ "threshold": 0.1,
+ "strength": 1.0,
+ "limit": 0.18
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx378.json b/src/ipa/rpi/vc4/data/imx378.json
new file mode 100644
index 00000000..f7b68011
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx378.json
@@ -0,0 +1,418 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 9999,
+ "reference_gain": 1.95,
+ "reference_aperture": 1.0,
+ "reference_lux": 1000,
+ "reference_Y": 12996
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.641
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 235,
+ "slope": 0.00902
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8100
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2850.0, 0.6361, 0.3911,
+ 3550.0, 0.5386, 0.5077,
+ 4500.0, 0.4472, 0.6171,
+ 5600.0, 0.3906, 0.6848,
+ 8000.0, 0.3412, 0.7441
+ ],
+ "sensitivity_r": 1.0,
+ "sensitivity_b": 1.0,
+ "transverse_pos": 0.01667,
+ "transverse_neg": 0.01195
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 2800,
+ "table":
+ [
+ 1.604, 1.601, 1.593, 1.581, 1.568, 1.561, 1.561, 1.561, 1.561, 1.567, 1.582, 1.596, 1.609, 1.622, 1.632, 1.636,
+ 1.601, 1.594, 1.586, 1.571, 1.555, 1.546, 1.543, 1.543, 1.547, 1.555, 1.572, 1.584, 1.599, 1.614, 1.625, 1.632,
+ 1.599, 1.586, 1.571, 1.555, 1.542, 1.528, 1.518, 1.518, 1.523, 1.537, 1.555, 1.572, 1.589, 1.607, 1.622, 1.629,
+ 1.597, 1.579, 1.561, 1.542, 1.528, 1.512, 1.493, 1.493, 1.499, 1.523, 1.537, 1.563, 1.582, 1.601, 1.619, 1.629,
+ 1.597, 1.577, 1.557, 1.535, 1.512, 1.493, 1.481, 1.479, 1.492, 1.499, 1.524, 1.555, 1.578, 1.599, 1.619, 1.629,
+ 1.597, 1.577, 1.557, 1.534, 1.508, 1.483, 1.476, 1.476, 1.481, 1.496, 1.522, 1.554, 1.578, 1.599, 1.619, 1.629,
+ 1.597, 1.578, 1.557, 1.534, 1.508, 1.483, 1.481, 1.479, 1.481, 1.496, 1.522, 1.554, 1.579, 1.601, 1.619, 1.631,
+ 1.597, 1.581, 1.562, 1.539, 1.517, 1.504, 1.483, 1.481, 1.496, 1.511, 1.531, 1.561, 1.585, 1.607, 1.623, 1.632,
+ 1.601, 1.589, 1.569, 1.554, 1.539, 1.517, 1.504, 1.504, 1.511, 1.531, 1.553, 1.573, 1.596, 1.614, 1.629, 1.636,
+ 1.609, 1.601, 1.586, 1.569, 1.554, 1.542, 1.535, 1.535, 1.541, 1.553, 1.573, 1.592, 1.608, 1.625, 1.637, 1.645,
+ 1.617, 1.611, 1.601, 1.586, 1.574, 1.565, 1.564, 1.564, 1.571, 1.579, 1.592, 1.608, 1.622, 1.637, 1.646, 1.654,
+ 1.619, 1.617, 1.611, 1.601, 1.588, 1.585, 1.585, 1.585, 1.588, 1.592, 1.607, 1.622, 1.637, 1.645, 1.654, 1.655
+ ]
+ },
+ {
+ "ct": 5500,
+ "table":
+ [
+ 2.664, 2.658, 2.645, 2.629, 2.602, 2.602, 2.602, 2.606, 2.617, 2.628, 2.649, 2.677, 2.699, 2.722, 2.736, 2.747,
+ 2.658, 2.653, 2.629, 2.605, 2.576, 2.575, 2.577, 2.592, 2.606, 2.618, 2.629, 2.651, 2.678, 2.707, 2.727, 2.741,
+ 2.649, 2.631, 2.605, 2.576, 2.563, 2.552, 2.552, 2.557, 2.577, 2.604, 2.619, 2.641, 2.669, 2.698, 2.721, 2.741,
+ 2.643, 2.613, 2.583, 2.563, 2.552, 2.531, 2.527, 2.527, 2.551, 2.577, 2.604, 2.638, 2.665, 2.694, 2.721, 2.741,
+ 2.643, 2.606, 2.575, 2.558, 2.531, 2.516, 2.504, 2.516, 2.527, 2.551, 2.596, 2.635, 2.665, 2.694, 2.721, 2.741,
+ 2.643, 2.606, 2.575, 2.558, 2.531, 2.503, 2.501, 2.502, 2.522, 2.551, 2.592, 2.635, 2.669, 2.696, 2.727, 2.744,
+ 2.648, 2.611, 2.579, 2.558, 2.532, 2.511, 2.502, 2.511, 2.522, 2.552, 2.592, 2.642, 2.673, 2.702, 2.731, 2.752,
+ 2.648, 2.619, 2.589, 2.571, 2.556, 2.532, 2.519, 2.522, 2.552, 2.568, 2.605, 2.648, 2.683, 2.715, 2.743, 2.758,
+ 2.659, 2.637, 2.613, 2.589, 2.571, 2.556, 2.555, 2.555, 2.568, 2.605, 2.641, 2.671, 2.699, 2.729, 2.758, 2.776,
+ 2.679, 2.665, 2.637, 2.613, 2.602, 2.599, 2.599, 2.606, 2.619, 2.641, 2.671, 2.698, 2.723, 2.754, 2.776, 2.787,
+ 2.695, 2.684, 2.671, 2.646, 2.636, 2.636, 2.641, 2.648, 2.661, 2.681, 2.698, 2.723, 2.751, 2.776, 2.788, 2.803,
+ 2.702, 2.699, 2.684, 2.671, 2.664, 2.664, 2.664, 2.668, 2.681, 2.698, 2.723, 2.751, 2.773, 2.788, 2.803, 2.805
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 2800,
+ "table":
+ [
+ 2.876, 2.868, 2.863, 2.851, 2.846, 2.846, 2.847, 2.851, 2.851, 2.857, 2.867, 2.875, 2.889, 2.899, 2.913, 2.926,
+ 2.863, 2.861, 2.856, 2.846, 2.846, 2.847, 2.848, 2.851, 2.857, 2.859, 2.875, 2.882, 2.886, 2.896, 2.909, 2.917,
+ 2.861, 2.856, 2.846, 2.841, 2.841, 2.855, 2.867, 2.875, 2.888, 2.888, 2.885, 2.883, 2.886, 2.889, 2.901, 2.913,
+ 2.858, 2.851, 2.846, 2.846, 2.855, 2.867, 2.884, 2.895, 2.902, 2.902, 2.901, 2.891, 2.891, 2.894, 2.901, 2.909,
+ 2.858, 2.851, 2.846, 2.846, 2.867, 2.884, 2.895, 2.902, 2.909, 2.915, 2.911, 2.901, 2.895, 2.898, 2.904, 2.909,
+ 2.858, 2.851, 2.849, 2.853, 2.874, 2.888, 2.901, 2.909, 2.917, 2.922, 2.917, 2.911, 2.901, 2.899, 2.905, 2.908,
+ 2.861, 2.855, 2.853, 2.855, 2.874, 2.888, 2.901, 2.913, 2.918, 2.922, 2.921, 2.911, 2.901, 2.901, 2.907, 2.908,
+ 2.862, 2.859, 2.855, 2.856, 2.872, 2.885, 2.899, 2.906, 2.915, 2.917, 2.911, 2.907, 2.907, 2.907, 2.908, 2.909,
+ 2.863, 2.863, 2.859, 2.864, 2.871, 2.881, 2.885, 2.899, 2.905, 2.905, 2.904, 2.904, 2.907, 2.909, 2.913, 2.913,
+ 2.866, 2.865, 2.865, 2.867, 2.868, 2.872, 2.881, 2.885, 2.889, 2.894, 2.895, 2.902, 2.906, 2.913, 2.914, 2.917,
+ 2.875, 2.875, 2.871, 2.871, 2.871, 2.871, 2.869, 2.869, 2.878, 2.889, 2.894, 2.895, 2.906, 2.914, 2.917, 2.921,
+ 2.882, 2.879, 2.876, 2.874, 2.871, 2.871, 2.869, 2.869, 2.869, 2.878, 2.891, 2.894, 2.905, 2.914, 2.919, 2.921
+ ]
+ },
+ {
+ "ct": 5500,
+ "table":
+ [
+ 1.488, 1.488, 1.488, 1.488, 1.491, 1.492, 1.492, 1.491, 1.491, 1.491, 1.492, 1.495, 1.497, 1.499, 1.499, 1.503,
+ 1.482, 1.485, 1.485, 1.487, 1.489, 1.492, 1.492, 1.492, 1.492, 1.492, 1.494, 1.494, 1.492, 1.491, 1.493, 1.494,
+ 1.482, 1.482, 1.484, 1.485, 1.487, 1.492, 1.496, 1.498, 1.499, 1.498, 1.494, 1.492, 1.491, 1.491, 1.491, 1.491,
+ 1.481, 1.481, 1.482, 1.485, 1.491, 1.496, 1.498, 1.499, 1.501, 1.499, 1.498, 1.493, 1.491, 1.488, 1.488, 1.488,
+ 1.481, 1.481, 1.481, 1.483, 1.491, 1.497, 1.498, 1.499, 1.501, 1.499, 1.498, 1.492, 1.488, 1.485, 1.483, 1.483,
+ 1.479, 1.479, 1.481, 1.482, 1.489, 1.495, 1.497, 1.498, 1.499, 1.499, 1.495, 1.492, 1.485, 1.482, 1.482, 1.481,
+ 1.479, 1.479, 1.479, 1.481, 1.489, 1.494, 1.496, 1.497, 1.497, 1.496, 1.495, 1.489, 1.482, 1.481, 1.479, 1.477,
+ 1.478, 1.478, 1.479, 1.481, 1.487, 1.491, 1.494, 1.496, 1.496, 1.495, 1.492, 1.487, 1.482, 1.479, 1.478, 1.476,
+ 1.478, 1.478, 1.479, 1.482, 1.486, 1.488, 1.491, 1.493, 1.493, 1.492, 1.487, 1.484, 1.481, 1.479, 1.476, 1.476,
+ 1.477, 1.479, 1.481, 1.483, 1.485, 1.486, 1.488, 1.488, 1.487, 1.487, 1.484, 1.483, 1.481, 1.479, 1.476, 1.476,
+ 1.477, 1.479, 1.482, 1.483, 1.484, 1.485, 1.484, 1.482, 1.482, 1.484, 1.483, 1.482, 1.481, 1.479, 1.477, 1.476,
+ 1.477, 1.479, 1.482, 1.483, 1.484, 1.484, 1.482, 1.482, 1.482, 1.482, 1.482, 1.481, 1.479, 1.479, 1.479, 1.479
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.764, 2.654, 2.321, 2.043, 1.768, 1.594, 1.558, 1.558, 1.558, 1.568, 1.661, 1.904, 2.193, 2.497, 2.888, 3.043,
+ 2.654, 2.373, 2.049, 1.819, 1.569, 1.446, 1.381, 1.356, 1.356, 1.403, 1.501, 1.679, 1.939, 2.218, 2.586, 2.888,
+ 2.376, 2.154, 1.819, 1.569, 1.438, 1.301, 1.246, 1.224, 1.224, 1.263, 1.349, 1.501, 1.679, 1.985, 2.359, 2.609,
+ 2.267, 1.987, 1.662, 1.438, 1.301, 1.235, 1.132, 1.105, 1.105, 1.164, 1.263, 1.349, 1.528, 1.808, 2.184, 2.491,
+ 2.218, 1.876, 1.568, 1.367, 1.235, 1.132, 1.087, 1.022, 1.023, 1.104, 1.164, 1.278, 1.439, 1.695, 2.066, 2.429,
+ 2.218, 1.832, 1.533, 1.341, 1.206, 1.089, 1.013, 1.002, 1.013, 1.026, 1.122, 1.246, 1.399, 1.642, 2.004, 2.426,
+ 2.218, 1.832, 1.533, 1.341, 1.206, 1.089, 1.011, 1.001, 1.009, 1.026, 1.122, 1.246, 1.399, 1.642, 2.004, 2.426,
+ 2.224, 1.896, 1.584, 1.382, 1.248, 1.147, 1.088, 1.016, 1.026, 1.118, 1.168, 1.283, 1.444, 1.697, 2.066, 2.428,
+ 2.292, 2.019, 1.689, 1.462, 1.322, 1.247, 1.147, 1.118, 1.118, 1.168, 1.275, 1.358, 1.532, 1.809, 2.189, 2.491,
+ 2.444, 2.204, 1.856, 1.606, 1.462, 1.322, 1.257, 1.234, 1.234, 1.275, 1.358, 1.516, 1.686, 1.993, 2.371, 2.622,
+ 2.748, 2.444, 2.108, 1.856, 1.606, 1.476, 1.399, 1.376, 1.376, 1.422, 1.516, 1.686, 1.968, 2.238, 2.611, 2.935,
+ 2.862, 2.748, 2.395, 2.099, 1.811, 1.621, 1.582, 1.582, 1.582, 1.592, 1.677, 1.919, 2.223, 2.534, 2.935, 3.078
+ ],
+ "sigma": 0.00428,
+ "sigma_Cb": 0.00363
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2850,
+ "ccm":
+ [
+ 1.42601, -0.20537, -0.22063,
+ -0.47682, 1.81987, -0.34305,
+ 0.01854, -0.86036, 1.84181
+ ]
+ },
+ {
+ "ct": 2900,
+ "ccm":
+ [
+ 1.29755, 0.04602, -0.34356,
+ -0.41491, 1.73477, -0.31987,
+ -0.01345, -0.97115, 1.98459
+ ]
+ },
+ {
+ "ct": 3550,
+ "ccm":
+ [
+ 1.49811, -0.33412, -0.16398,
+ -0.40869, 1.72995, -0.32127,
+ -0.01924, -0.62181, 1.64105
+ ]
+ },
+ {
+ "ct": 4500,
+ "ccm":
+ [
+ 1.47015, -0.29229, -0.17786,
+ -0.36561, 1.88919, -0.52358,
+ -0.03552, -0.56717, 1.60269
+ ]
+ },
+ {
+ "ct": 5600,
+ "ccm":
+ [
+ 1.60962, -0.47434, -0.13528,
+ -0.32701, 1.73797, -0.41096,
+ -0.07626, -0.40171, 1.47796
+ ]
+ },
+ {
+ "ct": 8000,
+ "ccm":
+ [
+ 1.54642, -0.20396, -0.34246,
+ -0.31748, 2.22559, -0.90811,
+ -0.10035, -0.65877, 1.75912
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/imx477.json b/src/ipa/rpi/vc4/data/imx477.json
new file mode 100644
index 00000000..853bfa67
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx477.json
@@ -0,0 +1,675 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 27242,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 830,
+ "reference_Y": 17755
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.767
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 204,
+ "slope": 0.01078
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2360.0, 0.6009, 0.3093,
+ 2848.0, 0.5071, 0.4000,
+ 2903.0, 0.4905, 0.4392,
+ 3628.0, 0.4261, 0.5564,
+ 3643.0, 0.4228, 0.5623,
+ 4660.0, 0.3529, 0.6800,
+ 5579.0, 0.3227, 0.7000,
+ 6125.0, 0.3129, 0.7100,
+ 6671.0, 0.3065, 0.7200,
+ 7217.0, 0.3014, 0.7300,
+ 7763.0, 0.2950, 0.7400,
+ 9505.0, 0.2524, 0.7856
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.0238,
+ "transverse_neg": 0.04429
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 2960,
+ "table":
+ [
+ 2.088, 2.086, 2.082, 2.081, 2.077, 2.071, 2.068, 2.068, 2.072, 2.073, 2.075, 2.078, 2.084, 2.092, 2.095, 2.098,
+ 2.086, 2.084, 2.079, 2.078, 2.075, 2.068, 2.064, 2.063, 2.068, 2.071, 2.072, 2.075, 2.081, 2.089, 2.092, 2.094,
+ 2.083, 2.081, 2.077, 2.072, 2.069, 2.062, 2.059, 2.059, 2.063, 2.067, 2.069, 2.072, 2.079, 2.088, 2.089, 2.089,
+ 2.081, 2.077, 2.072, 2.068, 2.065, 2.058, 2.055, 2.054, 2.057, 2.062, 2.066, 2.069, 2.077, 2.084, 2.086, 2.086,
+ 2.078, 2.075, 2.069, 2.065, 2.061, 2.055, 2.052, 2.049, 2.051, 2.056, 2.062, 2.065, 2.072, 2.079, 2.081, 2.079,
+ 2.079, 2.075, 2.069, 2.064, 2.061, 2.053, 2.049, 2.046, 2.049, 2.051, 2.057, 2.062, 2.069, 2.075, 2.077, 2.075,
+ 2.082, 2.079, 2.072, 2.065, 2.061, 2.054, 2.049, 2.047, 2.049, 2.051, 2.056, 2.061, 2.066, 2.073, 2.073, 2.069,
+ 2.086, 2.082, 2.075, 2.068, 2.062, 2.054, 2.051, 2.049, 2.051, 2.052, 2.056, 2.061, 2.066, 2.073, 2.073, 2.072,
+ 2.088, 2.086, 2.079, 2.074, 2.066, 2.057, 2.051, 2.051, 2.054, 2.055, 2.056, 2.061, 2.067, 2.072, 2.073, 2.072,
+ 2.091, 2.087, 2.079, 2.075, 2.068, 2.057, 2.052, 2.052, 2.056, 2.055, 2.055, 2.059, 2.066, 2.072, 2.072, 2.072,
+ 2.093, 2.088, 2.081, 2.077, 2.069, 2.059, 2.054, 2.054, 2.057, 2.056, 2.056, 2.058, 2.066, 2.072, 2.073, 2.073,
+ 2.095, 2.091, 2.084, 2.078, 2.075, 2.067, 2.057, 2.057, 2.059, 2.059, 2.058, 2.059, 2.068, 2.073, 2.075, 2.078
+ ]
+ },
+ {
+ "ct": 4850,
+ "table":
+ [
+ 2.973, 2.968, 2.956, 2.943, 2.941, 2.932, 2.923, 2.921, 2.924, 2.929, 2.931, 2.939, 2.953, 2.965, 2.966, 2.976,
+ 2.969, 2.962, 2.951, 2.941, 2.934, 2.928, 2.919, 2.918, 2.919, 2.923, 2.927, 2.933, 2.945, 2.957, 2.962, 2.962,
+ 2.964, 2.956, 2.944, 2.932, 2.929, 2.924, 2.915, 2.914, 2.915, 2.919, 2.924, 2.928, 2.941, 2.952, 2.958, 2.959,
+ 2.957, 2.951, 2.939, 2.928, 2.924, 2.919, 2.913, 2.911, 2.911, 2.915, 2.919, 2.925, 2.936, 2.947, 2.952, 2.953,
+ 2.954, 2.947, 2.935, 2.924, 2.919, 2.915, 2.908, 2.906, 2.906, 2.907, 2.914, 2.921, 2.932, 2.941, 2.943, 2.942,
+ 2.953, 2.946, 2.932, 2.921, 2.916, 2.911, 2.904, 2.902, 2.901, 2.904, 2.909, 2.919, 2.926, 2.937, 2.939, 2.939,
+ 2.953, 2.947, 2.932, 2.918, 2.915, 2.909, 2.903, 2.901, 2.901, 2.906, 2.911, 2.918, 2.924, 2.936, 2.936, 2.932,
+ 2.956, 2.948, 2.934, 2.919, 2.916, 2.908, 2.903, 2.901, 2.902, 2.907, 2.909, 2.917, 2.926, 2.936, 2.939, 2.939,
+ 2.957, 2.951, 2.936, 2.923, 2.917, 2.907, 2.904, 2.901, 2.902, 2.908, 2.911, 2.919, 2.929, 2.939, 2.942, 2.942,
+ 2.961, 2.951, 2.936, 2.922, 2.918, 2.906, 2.904, 2.901, 2.901, 2.907, 2.911, 2.921, 2.931, 2.941, 2.942, 2.944,
+ 2.964, 2.954, 2.936, 2.924, 2.918, 2.909, 2.905, 2.905, 2.905, 2.907, 2.912, 2.923, 2.933, 2.942, 2.944, 2.944,
+ 2.964, 2.958, 2.943, 2.927, 2.921, 2.914, 2.909, 2.907, 2.907, 2.912, 2.916, 2.928, 2.936, 2.944, 2.947, 2.952
+ ]
+ },
+ {
+ "ct": 5930,
+ "table":
+ [
+ 3.312, 3.308, 3.301, 3.294, 3.288, 3.277, 3.268, 3.261, 3.259, 3.261, 3.267, 3.273, 3.285, 3.301, 3.303, 3.312,
+ 3.308, 3.304, 3.294, 3.291, 3.283, 3.271, 3.263, 3.259, 3.257, 3.258, 3.261, 3.268, 3.278, 3.293, 3.299, 3.299,
+ 3.302, 3.296, 3.288, 3.282, 3.276, 3.267, 3.259, 3.254, 3.252, 3.253, 3.256, 3.261, 3.273, 3.289, 3.292, 3.292,
+ 3.296, 3.289, 3.282, 3.276, 3.269, 3.263, 3.256, 3.251, 3.248, 3.249, 3.251, 3.257, 3.268, 3.279, 3.284, 3.284,
+ 3.292, 3.285, 3.279, 3.271, 3.264, 3.257, 3.249, 3.243, 3.241, 3.241, 3.246, 3.252, 3.261, 3.274, 3.275, 3.273,
+ 3.291, 3.285, 3.276, 3.268, 3.259, 3.251, 3.242, 3.239, 3.236, 3.238, 3.244, 3.248, 3.258, 3.268, 3.269, 3.265,
+ 3.294, 3.288, 3.275, 3.266, 3.257, 3.248, 3.239, 3.238, 3.237, 3.238, 3.243, 3.246, 3.255, 3.264, 3.264, 3.257,
+ 3.297, 3.293, 3.279, 3.268, 3.258, 3.249, 3.238, 3.237, 3.239, 3.239, 3.243, 3.245, 3.255, 3.264, 3.264, 3.263,
+ 3.301, 3.295, 3.281, 3.271, 3.259, 3.248, 3.237, 3.237, 3.239, 3.241, 3.243, 3.246, 3.257, 3.265, 3.266, 3.264,
+ 3.306, 3.295, 3.279, 3.271, 3.261, 3.247, 3.235, 3.234, 3.239, 3.239, 3.243, 3.247, 3.258, 3.265, 3.265, 3.264,
+ 3.308, 3.297, 3.279, 3.272, 3.261, 3.249, 3.239, 3.239, 3.241, 3.243, 3.245, 3.248, 3.261, 3.265, 3.266, 3.265,
+ 3.309, 3.301, 3.286, 3.276, 3.267, 3.256, 3.246, 3.242, 3.244, 3.244, 3.249, 3.253, 3.263, 3.267, 3.271, 3.274
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 2960,
+ "table":
+ [
+ 2.133, 2.134, 2.139, 2.143, 2.148, 2.155, 2.158, 2.158, 2.158, 2.161, 2.161, 2.162, 2.159, 2.156, 2.152, 2.151,
+ 2.132, 2.133, 2.135, 2.142, 2.147, 2.153, 2.158, 2.158, 2.158, 2.158, 2.159, 2.159, 2.157, 2.154, 2.151, 2.148,
+ 2.133, 2.133, 2.135, 2.142, 2.149, 2.154, 2.158, 2.158, 2.157, 2.156, 2.158, 2.157, 2.155, 2.153, 2.148, 2.146,
+ 2.133, 2.133, 2.138, 2.145, 2.149, 2.154, 2.158, 2.159, 2.158, 2.155, 2.157, 2.156, 2.153, 2.149, 2.146, 2.144,
+ 2.133, 2.134, 2.139, 2.146, 2.149, 2.154, 2.158, 2.159, 2.159, 2.156, 2.154, 2.154, 2.149, 2.145, 2.143, 2.139,
+ 2.135, 2.135, 2.139, 2.146, 2.151, 2.155, 2.158, 2.159, 2.158, 2.156, 2.153, 2.151, 2.146, 2.143, 2.139, 2.136,
+ 2.135, 2.135, 2.138, 2.145, 2.151, 2.154, 2.157, 2.158, 2.157, 2.156, 2.153, 2.151, 2.147, 2.143, 2.141, 2.137,
+ 2.135, 2.134, 2.135, 2.141, 2.149, 2.154, 2.157, 2.157, 2.157, 2.157, 2.157, 2.153, 2.149, 2.146, 2.142, 2.139,
+ 2.132, 2.133, 2.135, 2.139, 2.148, 2.153, 2.158, 2.159, 2.159, 2.161, 2.161, 2.157, 2.154, 2.149, 2.144, 2.141,
+ 2.132, 2.133, 2.135, 2.141, 2.149, 2.155, 2.161, 2.161, 2.162, 2.162, 2.163, 2.159, 2.154, 2.149, 2.144, 2.138,
+ 2.136, 2.136, 2.137, 2.143, 2.149, 2.156, 2.162, 2.163, 2.162, 2.163, 2.164, 2.161, 2.157, 2.152, 2.146, 2.138,
+ 2.137, 2.137, 2.141, 2.147, 2.152, 2.157, 2.162, 2.162, 2.159, 2.161, 2.162, 2.162, 2.157, 2.152, 2.148, 2.148
+ ]
+ },
+ {
+ "ct": 4850,
+ "table":
+ [
+ 1.463, 1.464, 1.471, 1.478, 1.479, 1.483, 1.484, 1.486, 1.486, 1.484, 1.483, 1.481, 1.478, 1.475, 1.471, 1.468,
+ 1.463, 1.463, 1.468, 1.476, 1.479, 1.482, 1.484, 1.487, 1.486, 1.484, 1.483, 1.482, 1.478, 1.473, 1.469, 1.468,
+ 1.463, 1.464, 1.468, 1.476, 1.479, 1.483, 1.484, 1.486, 1.486, 1.485, 1.484, 1.482, 1.477, 1.473, 1.469, 1.468,
+ 1.463, 1.464, 1.469, 1.477, 1.481, 1.483, 1.485, 1.487, 1.487, 1.485, 1.485, 1.482, 1.478, 1.474, 1.469, 1.468,
+ 1.465, 1.465, 1.471, 1.478, 1.481, 1.484, 1.486, 1.488, 1.488, 1.487, 1.485, 1.482, 1.477, 1.472, 1.468, 1.467,
+ 1.465, 1.466, 1.472, 1.479, 1.482, 1.485, 1.486, 1.488, 1.488, 1.486, 1.484, 1.479, 1.475, 1.472, 1.468, 1.466,
+ 1.466, 1.466, 1.472, 1.478, 1.482, 1.484, 1.485, 1.488, 1.487, 1.485, 1.483, 1.479, 1.475, 1.472, 1.469, 1.468,
+ 1.465, 1.466, 1.469, 1.476, 1.481, 1.485, 1.485, 1.486, 1.486, 1.485, 1.483, 1.479, 1.477, 1.474, 1.471, 1.469,
+ 1.464, 1.465, 1.469, 1.476, 1.481, 1.484, 1.485, 1.487, 1.487, 1.486, 1.485, 1.481, 1.478, 1.475, 1.471, 1.469,
+ 1.463, 1.464, 1.469, 1.477, 1.481, 1.485, 1.485, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.471, 1.468,
+ 1.464, 1.465, 1.471, 1.478, 1.482, 1.486, 1.486, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.472, 1.468,
+ 1.465, 1.466, 1.472, 1.481, 1.483, 1.487, 1.487, 1.488, 1.488, 1.486, 1.485, 1.481, 1.479, 1.476, 1.473, 1.472
+ ]
+ },
+ {
+ "ct": 5930,
+ "table":
+ [
+ 1.443, 1.444, 1.448, 1.453, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.466, 1.462, 1.457, 1.454, 1.451,
+ 1.443, 1.444, 1.445, 1.451, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.451,
+ 1.444, 1.444, 1.445, 1.451, 1.459, 1.463, 1.466, 1.468, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.449,
+ 1.444, 1.444, 1.447, 1.452, 1.459, 1.464, 1.467, 1.469, 1.471, 1.469, 1.467, 1.466, 1.461, 1.456, 1.452, 1.449,
+ 1.444, 1.445, 1.448, 1.452, 1.459, 1.465, 1.469, 1.471, 1.471, 1.471, 1.468, 1.465, 1.461, 1.455, 1.451, 1.449,
+ 1.445, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.471, 1.472, 1.469, 1.467, 1.465, 1.459, 1.455, 1.451, 1.447,
+ 1.446, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.459, 1.455, 1.452, 1.449,
+ 1.446, 1.446, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.461, 1.457, 1.454, 1.451,
+ 1.444, 1.444, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.471, 1.471, 1.468, 1.466, 1.462, 1.458, 1.454, 1.452,
+ 1.444, 1.444, 1.448, 1.453, 1.459, 1.466, 1.469, 1.471, 1.472, 1.472, 1.468, 1.466, 1.462, 1.458, 1.454, 1.449,
+ 1.446, 1.447, 1.449, 1.454, 1.461, 1.466, 1.471, 1.471, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.455, 1.449,
+ 1.447, 1.447, 1.452, 1.457, 1.462, 1.468, 1.472, 1.472, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.456, 1.455
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 1.548, 1.499, 1.387, 1.289, 1.223, 1.183, 1.164, 1.154, 1.153, 1.169, 1.211, 1.265, 1.345, 1.448, 1.581, 1.619,
+ 1.513, 1.412, 1.307, 1.228, 1.169, 1.129, 1.105, 1.098, 1.103, 1.127, 1.157, 1.209, 1.272, 1.361, 1.481, 1.583,
+ 1.449, 1.365, 1.257, 1.175, 1.124, 1.085, 1.062, 1.054, 1.059, 1.079, 1.113, 1.151, 1.211, 1.293, 1.407, 1.488,
+ 1.424, 1.324, 1.222, 1.139, 1.089, 1.056, 1.034, 1.031, 1.034, 1.049, 1.075, 1.115, 1.164, 1.241, 1.351, 1.446,
+ 1.412, 1.297, 1.203, 1.119, 1.069, 1.039, 1.021, 1.016, 1.022, 1.032, 1.052, 1.086, 1.135, 1.212, 1.321, 1.439,
+ 1.406, 1.287, 1.195, 1.115, 1.059, 1.028, 1.014, 1.012, 1.015, 1.026, 1.041, 1.074, 1.125, 1.201, 1.302, 1.425,
+ 1.406, 1.294, 1.205, 1.126, 1.062, 1.031, 1.013, 1.009, 1.011, 1.019, 1.042, 1.079, 1.129, 1.203, 1.302, 1.435,
+ 1.415, 1.318, 1.229, 1.146, 1.076, 1.039, 1.019, 1.014, 1.017, 1.031, 1.053, 1.093, 1.144, 1.219, 1.314, 1.436,
+ 1.435, 1.348, 1.246, 1.164, 1.094, 1.059, 1.036, 1.032, 1.037, 1.049, 1.072, 1.114, 1.167, 1.257, 1.343, 1.462,
+ 1.471, 1.385, 1.278, 1.189, 1.124, 1.084, 1.064, 1.061, 1.069, 1.078, 1.101, 1.146, 1.207, 1.298, 1.415, 1.496,
+ 1.522, 1.436, 1.323, 1.228, 1.169, 1.118, 1.101, 1.094, 1.099, 1.113, 1.146, 1.194, 1.265, 1.353, 1.474, 1.571,
+ 1.578, 1.506, 1.378, 1.281, 1.211, 1.156, 1.135, 1.134, 1.139, 1.158, 1.194, 1.251, 1.327, 1.427, 1.559, 1.611
+ ],
+ "sigma": 0.00121,
+ "sigma_Cb": 0.00115
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2850,
+ "ccm":
+ [
+ 1.97469, -0.71439, -0.26031,
+ -0.43521, 2.09769, -0.66248,
+ -0.04826, -0.84642, 1.89468
+ ]
+ },
+ {
+ "ct": 2960,
+ "ccm":
+ [
+ 2.12952, -0.91185, -0.21768,
+ -0.38018, 1.90789, -0.52771,
+ 0.03988, -1.10079, 2.06092
+ ]
+ },
+ {
+ "ct": 3580,
+ "ccm":
+ [
+ 2.03422, -0.80048, -0.23374,
+ -0.39089, 1.97221, -0.58132,
+ -0.08969, -0.61439, 1.70408
+ ]
+ },
+ {
+ "ct": 4559,
+ "ccm":
+ [
+ 2.15423, -0.98143, -0.17279,
+ -0.38131, 2.14763, -0.76632,
+ -0.10069, -0.54383, 1.64452
+ ]
+ },
+ {
+ "ct": 5881,
+ "ccm":
+ [
+ 2.18464, -0.95493, -0.22971,
+ -0.36826, 2.00298, -0.63471,
+ -0.15219, -0.38055, 1.53274
+ ]
+ },
+ {
+ "ct": 7600,
+ "ccm":
+ [
+ 2.30687, -0.97295, -0.33392,
+ -0.30872, 2.32779, -1.01908,
+ -0.17761, -0.55891, 1.73651
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/imx477_noir.json b/src/ipa/rpi/vc4/data/imx477_noir.json
new file mode 100644
index 00000000..143e20bd
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx477_noir.json
@@ -0,0 +1,631 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 27242,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 830,
+ "reference_Y": 17755
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.767
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 204,
+ "slope": 0.01078
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "bayes": 0
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 2960,
+ "table":
+ [
+ 2.088, 2.086, 2.082, 2.081, 2.077, 2.071, 2.068, 2.068, 2.072, 2.073, 2.075, 2.078, 2.084, 2.092, 2.095, 2.098,
+ 2.086, 2.084, 2.079, 2.078, 2.075, 2.068, 2.064, 2.063, 2.068, 2.071, 2.072, 2.075, 2.081, 2.089, 2.092, 2.094,
+ 2.083, 2.081, 2.077, 2.072, 2.069, 2.062, 2.059, 2.059, 2.063, 2.067, 2.069, 2.072, 2.079, 2.088, 2.089, 2.089,
+ 2.081, 2.077, 2.072, 2.068, 2.065, 2.058, 2.055, 2.054, 2.057, 2.062, 2.066, 2.069, 2.077, 2.084, 2.086, 2.086,
+ 2.078, 2.075, 2.069, 2.065, 2.061, 2.055, 2.052, 2.049, 2.051, 2.056, 2.062, 2.065, 2.072, 2.079, 2.081, 2.079,
+ 2.079, 2.075, 2.069, 2.064, 2.061, 2.053, 2.049, 2.046, 2.049, 2.051, 2.057, 2.062, 2.069, 2.075, 2.077, 2.075,
+ 2.082, 2.079, 2.072, 2.065, 2.061, 2.054, 2.049, 2.047, 2.049, 2.051, 2.056, 2.061, 2.066, 2.073, 2.073, 2.069,
+ 2.086, 2.082, 2.075, 2.068, 2.062, 2.054, 2.051, 2.049, 2.051, 2.052, 2.056, 2.061, 2.066, 2.073, 2.073, 2.072,
+ 2.088, 2.086, 2.079, 2.074, 2.066, 2.057, 2.051, 2.051, 2.054, 2.055, 2.056, 2.061, 2.067, 2.072, 2.073, 2.072,
+ 2.091, 2.087, 2.079, 2.075, 2.068, 2.057, 2.052, 2.052, 2.056, 2.055, 2.055, 2.059, 2.066, 2.072, 2.072, 2.072,
+ 2.093, 2.088, 2.081, 2.077, 2.069, 2.059, 2.054, 2.054, 2.057, 2.056, 2.056, 2.058, 2.066, 2.072, 2.073, 2.073,
+ 2.095, 2.091, 2.084, 2.078, 2.075, 2.067, 2.057, 2.057, 2.059, 2.059, 2.058, 2.059, 2.068, 2.073, 2.075, 2.078
+ ]
+ },
+ {
+ "ct": 4850,
+ "table":
+ [
+ 2.973, 2.968, 2.956, 2.943, 2.941, 2.932, 2.923, 2.921, 2.924, 2.929, 2.931, 2.939, 2.953, 2.965, 2.966, 2.976,
+ 2.969, 2.962, 2.951, 2.941, 2.934, 2.928, 2.919, 2.918, 2.919, 2.923, 2.927, 2.933, 2.945, 2.957, 2.962, 2.962,
+ 2.964, 2.956, 2.944, 2.932, 2.929, 2.924, 2.915, 2.914, 2.915, 2.919, 2.924, 2.928, 2.941, 2.952, 2.958, 2.959,
+ 2.957, 2.951, 2.939, 2.928, 2.924, 2.919, 2.913, 2.911, 2.911, 2.915, 2.919, 2.925, 2.936, 2.947, 2.952, 2.953,
+ 2.954, 2.947, 2.935, 2.924, 2.919, 2.915, 2.908, 2.906, 2.906, 2.907, 2.914, 2.921, 2.932, 2.941, 2.943, 2.942,
+ 2.953, 2.946, 2.932, 2.921, 2.916, 2.911, 2.904, 2.902, 2.901, 2.904, 2.909, 2.919, 2.926, 2.937, 2.939, 2.939,
+ 2.953, 2.947, 2.932, 2.918, 2.915, 2.909, 2.903, 2.901, 2.901, 2.906, 2.911, 2.918, 2.924, 2.936, 2.936, 2.932,
+ 2.956, 2.948, 2.934, 2.919, 2.916, 2.908, 2.903, 2.901, 2.902, 2.907, 2.909, 2.917, 2.926, 2.936, 2.939, 2.939,
+ 2.957, 2.951, 2.936, 2.923, 2.917, 2.907, 2.904, 2.901, 2.902, 2.908, 2.911, 2.919, 2.929, 2.939, 2.942, 2.942,
+ 2.961, 2.951, 2.936, 2.922, 2.918, 2.906, 2.904, 2.901, 2.901, 2.907, 2.911, 2.921, 2.931, 2.941, 2.942, 2.944,
+ 2.964, 2.954, 2.936, 2.924, 2.918, 2.909, 2.905, 2.905, 2.905, 2.907, 2.912, 2.923, 2.933, 2.942, 2.944, 2.944,
+ 2.964, 2.958, 2.943, 2.927, 2.921, 2.914, 2.909, 2.907, 2.907, 2.912, 2.916, 2.928, 2.936, 2.944, 2.947, 2.952
+ ]
+ },
+ {
+ "ct": 5930,
+ "table":
+ [
+ 3.312, 3.308, 3.301, 3.294, 3.288, 3.277, 3.268, 3.261, 3.259, 3.261, 3.267, 3.273, 3.285, 3.301, 3.303, 3.312,
+ 3.308, 3.304, 3.294, 3.291, 3.283, 3.271, 3.263, 3.259, 3.257, 3.258, 3.261, 3.268, 3.278, 3.293, 3.299, 3.299,
+ 3.302, 3.296, 3.288, 3.282, 3.276, 3.267, 3.259, 3.254, 3.252, 3.253, 3.256, 3.261, 3.273, 3.289, 3.292, 3.292,
+ 3.296, 3.289, 3.282, 3.276, 3.269, 3.263, 3.256, 3.251, 3.248, 3.249, 3.251, 3.257, 3.268, 3.279, 3.284, 3.284,
+ 3.292, 3.285, 3.279, 3.271, 3.264, 3.257, 3.249, 3.243, 3.241, 3.241, 3.246, 3.252, 3.261, 3.274, 3.275, 3.273,
+ 3.291, 3.285, 3.276, 3.268, 3.259, 3.251, 3.242, 3.239, 3.236, 3.238, 3.244, 3.248, 3.258, 3.268, 3.269, 3.265,
+ 3.294, 3.288, 3.275, 3.266, 3.257, 3.248, 3.239, 3.238, 3.237, 3.238, 3.243, 3.246, 3.255, 3.264, 3.264, 3.257,
+ 3.297, 3.293, 3.279, 3.268, 3.258, 3.249, 3.238, 3.237, 3.239, 3.239, 3.243, 3.245, 3.255, 3.264, 3.264, 3.263,
+ 3.301, 3.295, 3.281, 3.271, 3.259, 3.248, 3.237, 3.237, 3.239, 3.241, 3.243, 3.246, 3.257, 3.265, 3.266, 3.264,
+ 3.306, 3.295, 3.279, 3.271, 3.261, 3.247, 3.235, 3.234, 3.239, 3.239, 3.243, 3.247, 3.258, 3.265, 3.265, 3.264,
+ 3.308, 3.297, 3.279, 3.272, 3.261, 3.249, 3.239, 3.239, 3.241, 3.243, 3.245, 3.248, 3.261, 3.265, 3.266, 3.265,
+ 3.309, 3.301, 3.286, 3.276, 3.267, 3.256, 3.246, 3.242, 3.244, 3.244, 3.249, 3.253, 3.263, 3.267, 3.271, 3.274
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 2960,
+ "table":
+ [
+ 2.133, 2.134, 2.139, 2.143, 2.148, 2.155, 2.158, 2.158, 2.158, 2.161, 2.161, 2.162, 2.159, 2.156, 2.152, 2.151,
+ 2.132, 2.133, 2.135, 2.142, 2.147, 2.153, 2.158, 2.158, 2.158, 2.158, 2.159, 2.159, 2.157, 2.154, 2.151, 2.148,
+ 2.133, 2.133, 2.135, 2.142, 2.149, 2.154, 2.158, 2.158, 2.157, 2.156, 2.158, 2.157, 2.155, 2.153, 2.148, 2.146,
+ 2.133, 2.133, 2.138, 2.145, 2.149, 2.154, 2.158, 2.159, 2.158, 2.155, 2.157, 2.156, 2.153, 2.149, 2.146, 2.144,
+ 2.133, 2.134, 2.139, 2.146, 2.149, 2.154, 2.158, 2.159, 2.159, 2.156, 2.154, 2.154, 2.149, 2.145, 2.143, 2.139,
+ 2.135, 2.135, 2.139, 2.146, 2.151, 2.155, 2.158, 2.159, 2.158, 2.156, 2.153, 2.151, 2.146, 2.143, 2.139, 2.136,
+ 2.135, 2.135, 2.138, 2.145, 2.151, 2.154, 2.157, 2.158, 2.157, 2.156, 2.153, 2.151, 2.147, 2.143, 2.141, 2.137,
+ 2.135, 2.134, 2.135, 2.141, 2.149, 2.154, 2.157, 2.157, 2.157, 2.157, 2.157, 2.153, 2.149, 2.146, 2.142, 2.139,
+ 2.132, 2.133, 2.135, 2.139, 2.148, 2.153, 2.158, 2.159, 2.159, 2.161, 2.161, 2.157, 2.154, 2.149, 2.144, 2.141,
+ 2.132, 2.133, 2.135, 2.141, 2.149, 2.155, 2.161, 2.161, 2.162, 2.162, 2.163, 2.159, 2.154, 2.149, 2.144, 2.138,
+ 2.136, 2.136, 2.137, 2.143, 2.149, 2.156, 2.162, 2.163, 2.162, 2.163, 2.164, 2.161, 2.157, 2.152, 2.146, 2.138,
+ 2.137, 2.137, 2.141, 2.147, 2.152, 2.157, 2.162, 2.162, 2.159, 2.161, 2.162, 2.162, 2.157, 2.152, 2.148, 2.148
+ ]
+ },
+ {
+ "ct": 4850,
+ "table":
+ [
+ 1.463, 1.464, 1.471, 1.478, 1.479, 1.483, 1.484, 1.486, 1.486, 1.484, 1.483, 1.481, 1.478, 1.475, 1.471, 1.468,
+ 1.463, 1.463, 1.468, 1.476, 1.479, 1.482, 1.484, 1.487, 1.486, 1.484, 1.483, 1.482, 1.478, 1.473, 1.469, 1.468,
+ 1.463, 1.464, 1.468, 1.476, 1.479, 1.483, 1.484, 1.486, 1.486, 1.485, 1.484, 1.482, 1.477, 1.473, 1.469, 1.468,
+ 1.463, 1.464, 1.469, 1.477, 1.481, 1.483, 1.485, 1.487, 1.487, 1.485, 1.485, 1.482, 1.478, 1.474, 1.469, 1.468,
+ 1.465, 1.465, 1.471, 1.478, 1.481, 1.484, 1.486, 1.488, 1.488, 1.487, 1.485, 1.482, 1.477, 1.472, 1.468, 1.467,
+ 1.465, 1.466, 1.472, 1.479, 1.482, 1.485, 1.486, 1.488, 1.488, 1.486, 1.484, 1.479, 1.475, 1.472, 1.468, 1.466,
+ 1.466, 1.466, 1.472, 1.478, 1.482, 1.484, 1.485, 1.488, 1.487, 1.485, 1.483, 1.479, 1.475, 1.472, 1.469, 1.468,
+ 1.465, 1.466, 1.469, 1.476, 1.481, 1.485, 1.485, 1.486, 1.486, 1.485, 1.483, 1.479, 1.477, 1.474, 1.471, 1.469,
+ 1.464, 1.465, 1.469, 1.476, 1.481, 1.484, 1.485, 1.487, 1.487, 1.486, 1.485, 1.481, 1.478, 1.475, 1.471, 1.469,
+ 1.463, 1.464, 1.469, 1.477, 1.481, 1.485, 1.485, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.471, 1.468,
+ 1.464, 1.465, 1.471, 1.478, 1.482, 1.486, 1.486, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.472, 1.468,
+ 1.465, 1.466, 1.472, 1.481, 1.483, 1.487, 1.487, 1.488, 1.488, 1.486, 1.485, 1.481, 1.479, 1.476, 1.473, 1.472
+ ]
+ },
+ {
+ "ct": 5930,
+ "table":
+ [
+ 1.443, 1.444, 1.448, 1.453, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.466, 1.462, 1.457, 1.454, 1.451,
+ 1.443, 1.444, 1.445, 1.451, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.451,
+ 1.444, 1.444, 1.445, 1.451, 1.459, 1.463, 1.466, 1.468, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.449,
+ 1.444, 1.444, 1.447, 1.452, 1.459, 1.464, 1.467, 1.469, 1.471, 1.469, 1.467, 1.466, 1.461, 1.456, 1.452, 1.449,
+ 1.444, 1.445, 1.448, 1.452, 1.459, 1.465, 1.469, 1.471, 1.471, 1.471, 1.468, 1.465, 1.461, 1.455, 1.451, 1.449,
+ 1.445, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.471, 1.472, 1.469, 1.467, 1.465, 1.459, 1.455, 1.451, 1.447,
+ 1.446, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.459, 1.455, 1.452, 1.449,
+ 1.446, 1.446, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.461, 1.457, 1.454, 1.451,
+ 1.444, 1.444, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.471, 1.471, 1.468, 1.466, 1.462, 1.458, 1.454, 1.452,
+ 1.444, 1.444, 1.448, 1.453, 1.459, 1.466, 1.469, 1.471, 1.472, 1.472, 1.468, 1.466, 1.462, 1.458, 1.454, 1.449,
+ 1.446, 1.447, 1.449, 1.454, 1.461, 1.466, 1.471, 1.471, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.455, 1.449,
+ 1.447, 1.447, 1.452, 1.457, 1.462, 1.468, 1.472, 1.472, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.456, 1.455
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 1.548, 1.499, 1.387, 1.289, 1.223, 1.183, 1.164, 1.154, 1.153, 1.169, 1.211, 1.265, 1.345, 1.448, 1.581, 1.619,
+ 1.513, 1.412, 1.307, 1.228, 1.169, 1.129, 1.105, 1.098, 1.103, 1.127, 1.157, 1.209, 1.272, 1.361, 1.481, 1.583,
+ 1.449, 1.365, 1.257, 1.175, 1.124, 1.085, 1.062, 1.054, 1.059, 1.079, 1.113, 1.151, 1.211, 1.293, 1.407, 1.488,
+ 1.424, 1.324, 1.222, 1.139, 1.089, 1.056, 1.034, 1.031, 1.034, 1.049, 1.075, 1.115, 1.164, 1.241, 1.351, 1.446,
+ 1.412, 1.297, 1.203, 1.119, 1.069, 1.039, 1.021, 1.016, 1.022, 1.032, 1.052, 1.086, 1.135, 1.212, 1.321, 1.439,
+ 1.406, 1.287, 1.195, 1.115, 1.059, 1.028, 1.014, 1.012, 1.015, 1.026, 1.041, 1.074, 1.125, 1.201, 1.302, 1.425,
+ 1.406, 1.294, 1.205, 1.126, 1.062, 1.031, 1.013, 1.009, 1.011, 1.019, 1.042, 1.079, 1.129, 1.203, 1.302, 1.435,
+ 1.415, 1.318, 1.229, 1.146, 1.076, 1.039, 1.019, 1.014, 1.017, 1.031, 1.053, 1.093, 1.144, 1.219, 1.314, 1.436,
+ 1.435, 1.348, 1.246, 1.164, 1.094, 1.059, 1.036, 1.032, 1.037, 1.049, 1.072, 1.114, 1.167, 1.257, 1.343, 1.462,
+ 1.471, 1.385, 1.278, 1.189, 1.124, 1.084, 1.064, 1.061, 1.069, 1.078, 1.101, 1.146, 1.207, 1.298, 1.415, 1.496,
+ 1.522, 1.436, 1.323, 1.228, 1.169, 1.118, 1.101, 1.094, 1.099, 1.113, 1.146, 1.194, 1.265, 1.353, 1.474, 1.571,
+ 1.578, 1.506, 1.378, 1.281, 1.211, 1.156, 1.135, 1.134, 1.139, 1.158, 1.194, 1.251, 1.327, 1.427, 1.559, 1.611
+ ],
+ "sigma": 0.00121,
+ "sigma_Cb": 0.00115
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2360,
+ "ccm":
+ [
+ 1.66078, -0.23588, -0.42491,
+ -0.47456, 1.82763, -0.35307,
+ -0.00545, -1.44729, 2.45273
+ ]
+ },
+ {
+ "ct": 2870,
+ "ccm":
+ [
+ 1.78373, -0.55344, -0.23029,
+ -0.39951, 1.69701, -0.29751,
+ 0.01986, -1.06525, 2.04539
+ ]
+ },
+ {
+ "ct": 2970,
+ "ccm":
+ [
+ 1.73511, -0.56973, -0.16537,
+ -0.36338, 1.69878, -0.33539,
+ -0.02354, -0.76813, 1.79168
+ ]
+ },
+ {
+ "ct": 3000,
+ "ccm":
+ [
+ 2.06374, -0.92218, -0.14156,
+ -0.41721, 1.69289, -0.27568,
+ -0.00554, -0.92741, 1.93295
+ ]
+ },
+ {
+ "ct": 3700,
+ "ccm":
+ [
+ 2.13792, -1.08136, -0.05655,
+ -0.34739, 1.58989, -0.24249,
+ -0.00349, -0.76789, 1.77138
+ ]
+ },
+ {
+ "ct": 3870,
+ "ccm":
+ [
+ 1.83834, -0.70528, -0.13307,
+ -0.30499, 1.60523, -0.30024,
+ -0.05701, -0.58313, 1.64014
+ ]
+ },
+ {
+ "ct": 4000,
+ "ccm":
+ [
+ 2.15741, -1.10295, -0.05447,
+ -0.34631, 1.61158, -0.26528,
+ -0.02723, -0.70288, 1.73011
+ ]
+ },
+ {
+ "ct": 4400,
+ "ccm":
+ [
+ 2.05729, -0.95007, -0.10723,
+ -0.41712, 1.78606, -0.36894,
+ -0.11899, -0.55727, 1.67626
+ ]
+ },
+ {
+ "ct": 4715,
+ "ccm":
+ [
+ 1.90255, -0.77478, -0.12777,
+ -0.31338, 1.88197, -0.56858,
+ -0.06001, -0.61785, 1.67786
+ ]
+ },
+ {
+ "ct": 5920,
+ "ccm":
+ [
+ 1.98691, -0.84671, -0.14019,
+ -0.26581, 1.70615, -0.44035,
+ -0.09532, -0.47332, 1.56864
+ ]
+ },
+ {
+ "ct": 9050,
+ "ccm":
+ [
+ 2.09255, -0.76541, -0.32714,
+ -0.28973, 2.27462, -0.98489,
+ -0.17299, -0.61275, 1.78574
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx477_scientific.json b/src/ipa/rpi/vc4/data/imx477_scientific.json
new file mode 100644
index 00000000..26c692fd
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx477_scientific.json
@@ -0,0 +1,479 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 27242,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 830,
+ "reference_Y": 17755
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.767
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 204,
+ "slope": 0.01078
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2000.0, 0.6331025775790707, 0.27424225990946915,
+ 2200.0, 0.5696117366212947, 0.3116091368689487,
+ 2400.0, 0.5204264653110015, 0.34892179554105873,
+ 2600.0, 0.48148675531667223, 0.38565229719076793,
+ 2800.0, 0.450085403501908, 0.42145684622485047,
+ 3000.0, 0.42436130159169017, 0.45611835670028816,
+ 3200.0, 0.40300023695527337, 0.48950766215198593,
+ 3400.0, 0.3850520052612984, 0.5215567075837261,
+ 3600.0, 0.36981508088230314, 0.5522397906415475,
+ 4100.0, 0.333468007836758, 0.5909770465167908,
+ 4600.0, 0.31196097364221376, 0.6515706327327178,
+ 5100.0, 0.2961860409294588, 0.7068178946570284,
+ 5600.0, 0.2842607232745885, 0.7564837749584288,
+ 6100.0, 0.2750265787051251, 0.8006183524920533,
+ 6600.0, 0.2677057225584924, 0.8398879225373039,
+ 7100.0, 0.2617955199757274, 0.8746456080032436,
+ 7600.0, 0.25693714288250125, 0.905569559506562,
+ 8100.0, 0.25287531441063316, 0.9331696750390895,
+ 8600.0, 0.24946601483331993, 0.9576820904825795
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.0238,
+ "transverse_neg": 0.04429,
+ "coarse_step": 0.1
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 0,
+ "gamma_curve":
+ [
+ 0, 0,
+ 512, 2304,
+ 1024, 4608,
+ 1536, 6573,
+ 2048, 8401,
+ 2560, 9992,
+ 3072, 11418,
+ 3584, 12719,
+ 4096, 13922,
+ 4608, 15045,
+ 5120, 16103,
+ 5632, 17104,
+ 6144, 18056,
+ 6656, 18967,
+ 7168, 19839,
+ 7680, 20679,
+ 8192, 21488,
+ 9216, 23028,
+ 10240, 24477,
+ 11264, 25849,
+ 12288, 27154,
+ 13312, 28401,
+ 14336, 29597,
+ 15360, 30747,
+ 16384, 31856,
+ 17408, 32928,
+ 18432, 33966,
+ 19456, 34973,
+ 20480, 35952,
+ 22528, 37832,
+ 24576, 39621,
+ 26624, 41330,
+ 28672, 42969,
+ 30720, 44545,
+ 32768, 46065,
+ 34816, 47534,
+ 36864, 48956,
+ 38912, 50336,
+ 40960, 51677,
+ 43008, 52982,
+ 45056, 54253,
+ 47104, 55493,
+ 49152, 56704,
+ 51200, 57888,
+ 53248, 59046,
+ 55296, 60181,
+ 57344, 61292,
+ 59392, 62382,
+ 61440, 63452,
+ 63488, 64503,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2000,
+ "ccm":
+ [
+ 1.5813882365848004, -0.35293683714581114, -0.27378771561617715,
+ -0.4347297185453639, 1.5792631087746074, -0.12102601986382337,
+ 0.2322290578987574, -1.4382672640468128, 2.1386425781770755
+ ]
+ },
+ {
+ "ct": 2200,
+ "ccm":
+ [
+ 1.6322048484088305, -0.45932286857238486, -0.21373542690252198,
+ -0.3970719209901105, 1.5877868651467202, -0.17249380832122455,
+ 0.20753774825903412, -1.2660673594740142, 2.005654261091916
+ ]
+ },
+ {
+ "ct": 2400,
+ "ccm":
+ [
+ 1.6766610071470398, -0.5447101051688111, -0.16838641107407676,
+ -0.3659845183388154, 1.592223692670396, -0.2127091997471162,
+ 0.1833964516767549, -1.1339155942419321, 1.9089342978542396
+ ]
+ },
+ {
+ "ct": 2600,
+ "ccm":
+ [
+ 1.7161984340622154, -0.6152585785678794, -0.1331100845092582,
+ -0.33972082628066275, 1.5944888273736966, -0.2453979465898787,
+ 0.1615577497676328, -1.0298684958833109, 1.8357854177422053
+ ]
+ },
+ {
+ "ct": 2800,
+ "ccm":
+ [
+ 1.7519307259815728, -0.6748682080165339, -0.10515169074540848,
+ -0.3171703484479931, 1.5955820297498486, -0.2727395854813966,
+ 0.14230870739974305, -0.9460976023551511, 1.778709391659538
+ ]
+ },
+ {
+ "ct": 3000,
+ "ccm":
+ [
+ 1.7846716625128374, -0.7261240476375332, -0.08274697420358428,
+ -0.2975654035173307, 1.5960425637021738, -0.2961043416505157,
+ 0.12546426281675097, -0.8773434727076518, 1.7330356805246685
+ ]
+ },
+ {
+ "ct": 3200,
+ "ccm":
+ [
+ 1.8150085872943436, -0.7708109672515514, -0.06469468211419174,
+ -0.2803468940646277, 1.596168842967451, -0.3164044170681625,
+ 0.11071494533513807, -0.8199772290209191, 1.69572135046367
+ ]
+ },
+ {
+ "ct": 3400,
+ "ccm":
+ [
+ 1.8433668304932087, -0.8102060605062592, -0.05013485852801454,
+ -0.2650934036324084, 1.5961288492969294, -0.33427554893845535,
+ 0.0977478941863518, -0.7714303112098978, 1.6647070820146963
+ ]
+ },
+ {
+ "ct": 3600,
+ "ccm":
+ [
+ 1.8700575831917468, -0.8452518300291346, -0.03842644337477299,
+ -0.2514794528347016, 1.5960178299141876, -0.3501774949366156,
+ 0.08628520830733245, -0.729841503339915, 1.638553343939267
+ ]
+ },
+ {
+ "ct": 4100,
+ "ccm":
+ [
+ 1.8988700903560716, -0.8911278803351247, -0.018848644425650693,
+ -0.21487101487384094, 1.599236541382614, -0.39405450457918206,
+ 0.08251488056482173, -0.7178919368326191, 1.6267009056502704
+ ]
+ },
+ {
+ "ct": 4600,
+ "ccm":
+ [
+ 1.960355191764125, -0.9624344812121991, -0.0017122408632169205,
+ -0.19444620905212898, 1.5978493736948447, -0.416727638296156,
+ 0.06310261513271084, -0.6483790952487849, 1.5834605477213093
+ ]
+ },
+ {
+ "ct": 5100,
+ "ccm":
+ [
+ 2.014680536961399, -1.0195930302148566, 0.007728256612638915,
+ -0.17751999660735496, 1.5977081555831, -0.4366085498741474,
+ 0.04741267583041334, -0.5950327902073489, 1.5512919847321853
+ ]
+ },
+ {
+ "ct": 5600,
+ "ccm":
+ [
+ 2.062652337917251, -1.0658386679125478, 0.011886354256281267,
+ -0.16319197721451495, 1.598363237584736, -0.45422061523742235,
+ 0.03465810928795378, -0.5535454108047286, 1.5269025836946852
+ ]
+ },
+ {
+ "ct": 6100,
+ "ccm":
+ [
+ 2.104985902038069, -1.103597868736314, 0.012503517136539277,
+ -0.15090797064906178, 1.5994703078166095, -0.4698414300864995,
+ 0.02421766063474242, -0.5208922818196823, 1.5081270847783788
+ ]
+ },
+ {
+ "ct": 6600,
+ "ccm":
+ [
+ 2.1424988751299714, -1.134760232367728, 0.010730356010435522,
+ -0.14021846798466234, 1.600822462230719, -0.48379204794526487,
+ 0.015521315410496622, -0.49463630325832275, 1.4933313534840327
+ ]
+ },
+ {
+ "ct": 7100,
+ "ccm":
+ [
+ 2.1758034100130925, -1.1607558481037359, 0.007452724895469076,
+ -0.13085694672641826, 1.6022648614493245, -0.4962330524084075,
+ 0.008226943206113427, -0.4733077192319791, 1.4815336120437468
+ ]
+ },
+ {
+ "ct": 7600,
+ "ccm":
+ [
+ 2.205529206931895, -1.1826662383072108, 0.0032019529917605167,
+ -0.122572009780486, 1.6037258133595753, -0.5073973734282445,
+ 0.0020132587619863425, -0.4556590236414181, 1.471939788496745
+ ]
+ },
+ {
+ "ct": 8100,
+ "ccm":
+ [
+ 2.232224969223067, -1.2013672897252885, -0.0016234598095482985,
+ -0.11518026734442414, 1.6051544769439803, -0.5174558699422255,
+ -0.0033378143542219835, -0.4408590373867774, 1.4640252230667452
+ ]
+ },
+ {
+ "ct": 8600,
+ "ccm":
+ [
+ 2.256082295891265, -1.2173210549996634, -0.0067231350481711675,
+ -0.10860272839843167, 1.6065150139140594, -0.5264728573611493,
+ -0.007952618707984149, -0.4284003574050791, 1.4574646927117558
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/imx477_v1.json b/src/ipa/rpi/vc4/data/imx477_v1.json
new file mode 100644
index 00000000..d6402009
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx477_v1.json
@@ -0,0 +1,516 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 27242,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 830,
+ "reference_Y": 17755
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.767
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 204,
+ "slope": 0.01078
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2360.0, 0.6009, 0.3093,
+ 2870.0, 0.5047, 0.3936,
+ 2970.0, 0.4782, 0.4221,
+ 3700.0, 0.4212, 0.4923,
+ 3870.0, 0.4037, 0.5166,
+ 4000.0, 0.3965, 0.5271,
+ 4400.0, 0.3703, 0.5666,
+ 4715.0, 0.3411, 0.6147,
+ 5920.0, 0.3108, 0.6687,
+ 9050.0, 0.2524, 0.7856
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.0238,
+ "transverse_neg": 0.04429
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.3,
+ 1000, 0.3
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 2960,
+ "table":
+ [
+ 2.088, 2.086, 2.082, 2.081, 2.077, 2.071, 2.068, 2.068, 2.072, 2.073, 2.075, 2.078, 2.084, 2.092, 2.095, 2.098,
+ 2.086, 2.084, 2.079, 2.078, 2.075, 2.068, 2.064, 2.063, 2.068, 2.071, 2.072, 2.075, 2.081, 2.089, 2.092, 2.094,
+ 2.083, 2.081, 2.077, 2.072, 2.069, 2.062, 2.059, 2.059, 2.063, 2.067, 2.069, 2.072, 2.079, 2.088, 2.089, 2.089,
+ 2.081, 2.077, 2.072, 2.068, 2.065, 2.058, 2.055, 2.054, 2.057, 2.062, 2.066, 2.069, 2.077, 2.084, 2.086, 2.086,
+ 2.078, 2.075, 2.069, 2.065, 2.061, 2.055, 2.052, 2.049, 2.051, 2.056, 2.062, 2.065, 2.072, 2.079, 2.081, 2.079,
+ 2.079, 2.075, 2.069, 2.064, 2.061, 2.053, 2.049, 2.046, 2.049, 2.051, 2.057, 2.062, 2.069, 2.075, 2.077, 2.075,
+ 2.082, 2.079, 2.072, 2.065, 2.061, 2.054, 2.049, 2.047, 2.049, 2.051, 2.056, 2.061, 2.066, 2.073, 2.073, 2.069,
+ 2.086, 2.082, 2.075, 2.068, 2.062, 2.054, 2.051, 2.049, 2.051, 2.052, 2.056, 2.061, 2.066, 2.073, 2.073, 2.072,
+ 2.088, 2.086, 2.079, 2.074, 2.066, 2.057, 2.051, 2.051, 2.054, 2.055, 2.056, 2.061, 2.067, 2.072, 2.073, 2.072,
+ 2.091, 2.087, 2.079, 2.075, 2.068, 2.057, 2.052, 2.052, 2.056, 2.055, 2.055, 2.059, 2.066, 2.072, 2.072, 2.072,
+ 2.093, 2.088, 2.081, 2.077, 2.069, 2.059, 2.054, 2.054, 2.057, 2.056, 2.056, 2.058, 2.066, 2.072, 2.073, 2.073,
+ 2.095, 2.091, 2.084, 2.078, 2.075, 2.067, 2.057, 2.057, 2.059, 2.059, 2.058, 2.059, 2.068, 2.073, 2.075, 2.078
+ ]
+ },
+ {
+ "ct": 4850,
+ "table":
+ [
+ 2.973, 2.968, 2.956, 2.943, 2.941, 2.932, 2.923, 2.921, 2.924, 2.929, 2.931, 2.939, 2.953, 2.965, 2.966, 2.976,
+ 2.969, 2.962, 2.951, 2.941, 2.934, 2.928, 2.919, 2.918, 2.919, 2.923, 2.927, 2.933, 2.945, 2.957, 2.962, 2.962,
+ 2.964, 2.956, 2.944, 2.932, 2.929, 2.924, 2.915, 2.914, 2.915, 2.919, 2.924, 2.928, 2.941, 2.952, 2.958, 2.959,
+ 2.957, 2.951, 2.939, 2.928, 2.924, 2.919, 2.913, 2.911, 2.911, 2.915, 2.919, 2.925, 2.936, 2.947, 2.952, 2.953,
+ 2.954, 2.947, 2.935, 2.924, 2.919, 2.915, 2.908, 2.906, 2.906, 2.907, 2.914, 2.921, 2.932, 2.941, 2.943, 2.942,
+ 2.953, 2.946, 2.932, 2.921, 2.916, 2.911, 2.904, 2.902, 2.901, 2.904, 2.909, 2.919, 2.926, 2.937, 2.939, 2.939,
+ 2.953, 2.947, 2.932, 2.918, 2.915, 2.909, 2.903, 2.901, 2.901, 2.906, 2.911, 2.918, 2.924, 2.936, 2.936, 2.932,
+ 2.956, 2.948, 2.934, 2.919, 2.916, 2.908, 2.903, 2.901, 2.902, 2.907, 2.909, 2.917, 2.926, 2.936, 2.939, 2.939,
+ 2.957, 2.951, 2.936, 2.923, 2.917, 2.907, 2.904, 2.901, 2.902, 2.908, 2.911, 2.919, 2.929, 2.939, 2.942, 2.942,
+ 2.961, 2.951, 2.936, 2.922, 2.918, 2.906, 2.904, 2.901, 2.901, 2.907, 2.911, 2.921, 2.931, 2.941, 2.942, 2.944,
+ 2.964, 2.954, 2.936, 2.924, 2.918, 2.909, 2.905, 2.905, 2.905, 2.907, 2.912, 2.923, 2.933, 2.942, 2.944, 2.944,
+ 2.964, 2.958, 2.943, 2.927, 2.921, 2.914, 2.909, 2.907, 2.907, 2.912, 2.916, 2.928, 2.936, 2.944, 2.947, 2.952
+ ]
+ },
+ {
+ "ct": 5930,
+ "table":
+ [
+ 3.312, 3.308, 3.301, 3.294, 3.288, 3.277, 3.268, 3.261, 3.259, 3.261, 3.267, 3.273, 3.285, 3.301, 3.303, 3.312,
+ 3.308, 3.304, 3.294, 3.291, 3.283, 3.271, 3.263, 3.259, 3.257, 3.258, 3.261, 3.268, 3.278, 3.293, 3.299, 3.299,
+ 3.302, 3.296, 3.288, 3.282, 3.276, 3.267, 3.259, 3.254, 3.252, 3.253, 3.256, 3.261, 3.273, 3.289, 3.292, 3.292,
+ 3.296, 3.289, 3.282, 3.276, 3.269, 3.263, 3.256, 3.251, 3.248, 3.249, 3.251, 3.257, 3.268, 3.279, 3.284, 3.284,
+ 3.292, 3.285, 3.279, 3.271, 3.264, 3.257, 3.249, 3.243, 3.241, 3.241, 3.246, 3.252, 3.261, 3.274, 3.275, 3.273,
+ 3.291, 3.285, 3.276, 3.268, 3.259, 3.251, 3.242, 3.239, 3.236, 3.238, 3.244, 3.248, 3.258, 3.268, 3.269, 3.265,
+ 3.294, 3.288, 3.275, 3.266, 3.257, 3.248, 3.239, 3.238, 3.237, 3.238, 3.243, 3.246, 3.255, 3.264, 3.264, 3.257,
+ 3.297, 3.293, 3.279, 3.268, 3.258, 3.249, 3.238, 3.237, 3.239, 3.239, 3.243, 3.245, 3.255, 3.264, 3.264, 3.263,
+ 3.301, 3.295, 3.281, 3.271, 3.259, 3.248, 3.237, 3.237, 3.239, 3.241, 3.243, 3.246, 3.257, 3.265, 3.266, 3.264,
+ 3.306, 3.295, 3.279, 3.271, 3.261, 3.247, 3.235, 3.234, 3.239, 3.239, 3.243, 3.247, 3.258, 3.265, 3.265, 3.264,
+ 3.308, 3.297, 3.279, 3.272, 3.261, 3.249, 3.239, 3.239, 3.241, 3.243, 3.245, 3.248, 3.261, 3.265, 3.266, 3.265,
+ 3.309, 3.301, 3.286, 3.276, 3.267, 3.256, 3.246, 3.242, 3.244, 3.244, 3.249, 3.253, 3.263, 3.267, 3.271, 3.274
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 2960,
+ "table":
+ [
+ 2.133, 2.134, 2.139, 2.143, 2.148, 2.155, 2.158, 2.158, 2.158, 2.161, 2.161, 2.162, 2.159, 2.156, 2.152, 2.151,
+ 2.132, 2.133, 2.135, 2.142, 2.147, 2.153, 2.158, 2.158, 2.158, 2.158, 2.159, 2.159, 2.157, 2.154, 2.151, 2.148,
+ 2.133, 2.133, 2.135, 2.142, 2.149, 2.154, 2.158, 2.158, 2.157, 2.156, 2.158, 2.157, 2.155, 2.153, 2.148, 2.146,
+ 2.133, 2.133, 2.138, 2.145, 2.149, 2.154, 2.158, 2.159, 2.158, 2.155, 2.157, 2.156, 2.153, 2.149, 2.146, 2.144,
+ 2.133, 2.134, 2.139, 2.146, 2.149, 2.154, 2.158, 2.159, 2.159, 2.156, 2.154, 2.154, 2.149, 2.145, 2.143, 2.139,
+ 2.135, 2.135, 2.139, 2.146, 2.151, 2.155, 2.158, 2.159, 2.158, 2.156, 2.153, 2.151, 2.146, 2.143, 2.139, 2.136,
+ 2.135, 2.135, 2.138, 2.145, 2.151, 2.154, 2.157, 2.158, 2.157, 2.156, 2.153, 2.151, 2.147, 2.143, 2.141, 2.137,
+ 2.135, 2.134, 2.135, 2.141, 2.149, 2.154, 2.157, 2.157, 2.157, 2.157, 2.157, 2.153, 2.149, 2.146, 2.142, 2.139,
+ 2.132, 2.133, 2.135, 2.139, 2.148, 2.153, 2.158, 2.159, 2.159, 2.161, 2.161, 2.157, 2.154, 2.149, 2.144, 2.141,
+ 2.132, 2.133, 2.135, 2.141, 2.149, 2.155, 2.161, 2.161, 2.162, 2.162, 2.163, 2.159, 2.154, 2.149, 2.144, 2.138,
+ 2.136, 2.136, 2.137, 2.143, 2.149, 2.156, 2.162, 2.163, 2.162, 2.163, 2.164, 2.161, 2.157, 2.152, 2.146, 2.138,
+ 2.137, 2.137, 2.141, 2.147, 2.152, 2.157, 2.162, 2.162, 2.159, 2.161, 2.162, 2.162, 2.157, 2.152, 2.148, 2.148
+ ]
+ },
+ {
+ "ct": 4850,
+ "table":
+ [
+ 1.463, 1.464, 1.471, 1.478, 1.479, 1.483, 1.484, 1.486, 1.486, 1.484, 1.483, 1.481, 1.478, 1.475, 1.471, 1.468,
+ 1.463, 1.463, 1.468, 1.476, 1.479, 1.482, 1.484, 1.487, 1.486, 1.484, 1.483, 1.482, 1.478, 1.473, 1.469, 1.468,
+ 1.463, 1.464, 1.468, 1.476, 1.479, 1.483, 1.484, 1.486, 1.486, 1.485, 1.484, 1.482, 1.477, 1.473, 1.469, 1.468,
+ 1.463, 1.464, 1.469, 1.477, 1.481, 1.483, 1.485, 1.487, 1.487, 1.485, 1.485, 1.482, 1.478, 1.474, 1.469, 1.468,
+ 1.465, 1.465, 1.471, 1.478, 1.481, 1.484, 1.486, 1.488, 1.488, 1.487, 1.485, 1.482, 1.477, 1.472, 1.468, 1.467,
+ 1.465, 1.466, 1.472, 1.479, 1.482, 1.485, 1.486, 1.488, 1.488, 1.486, 1.484, 1.479, 1.475, 1.472, 1.468, 1.466,
+ 1.466, 1.466, 1.472, 1.478, 1.482, 1.484, 1.485, 1.488, 1.487, 1.485, 1.483, 1.479, 1.475, 1.472, 1.469, 1.468,
+ 1.465, 1.466, 1.469, 1.476, 1.481, 1.485, 1.485, 1.486, 1.486, 1.485, 1.483, 1.479, 1.477, 1.474, 1.471, 1.469,
+ 1.464, 1.465, 1.469, 1.476, 1.481, 1.484, 1.485, 1.487, 1.487, 1.486, 1.485, 1.481, 1.478, 1.475, 1.471, 1.469,
+ 1.463, 1.464, 1.469, 1.477, 1.481, 1.485, 1.485, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.471, 1.468,
+ 1.464, 1.465, 1.471, 1.478, 1.482, 1.486, 1.486, 1.488, 1.488, 1.487, 1.486, 1.481, 1.478, 1.475, 1.472, 1.468,
+ 1.465, 1.466, 1.472, 1.481, 1.483, 1.487, 1.487, 1.488, 1.488, 1.486, 1.485, 1.481, 1.479, 1.476, 1.473, 1.472
+ ]
+ },
+ {
+ "ct": 5930,
+ "table":
+ [
+ 1.443, 1.444, 1.448, 1.453, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.466, 1.462, 1.457, 1.454, 1.451,
+ 1.443, 1.444, 1.445, 1.451, 1.459, 1.463, 1.465, 1.467, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.451,
+ 1.444, 1.444, 1.445, 1.451, 1.459, 1.463, 1.466, 1.468, 1.469, 1.469, 1.467, 1.465, 1.461, 1.456, 1.452, 1.449,
+ 1.444, 1.444, 1.447, 1.452, 1.459, 1.464, 1.467, 1.469, 1.471, 1.469, 1.467, 1.466, 1.461, 1.456, 1.452, 1.449,
+ 1.444, 1.445, 1.448, 1.452, 1.459, 1.465, 1.469, 1.471, 1.471, 1.471, 1.468, 1.465, 1.461, 1.455, 1.451, 1.449,
+ 1.445, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.471, 1.472, 1.469, 1.467, 1.465, 1.459, 1.455, 1.451, 1.447,
+ 1.446, 1.446, 1.449, 1.453, 1.461, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.459, 1.455, 1.452, 1.449,
+ 1.446, 1.446, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.469, 1.469, 1.467, 1.465, 1.461, 1.457, 1.454, 1.451,
+ 1.444, 1.444, 1.447, 1.451, 1.459, 1.466, 1.469, 1.469, 1.471, 1.471, 1.468, 1.466, 1.462, 1.458, 1.454, 1.452,
+ 1.444, 1.444, 1.448, 1.453, 1.459, 1.466, 1.469, 1.471, 1.472, 1.472, 1.468, 1.466, 1.462, 1.458, 1.454, 1.449,
+ 1.446, 1.447, 1.449, 1.454, 1.461, 1.466, 1.471, 1.471, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.455, 1.449,
+ 1.447, 1.447, 1.452, 1.457, 1.462, 1.468, 1.472, 1.472, 1.471, 1.471, 1.468, 1.466, 1.462, 1.459, 1.456, 1.455
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 1.548, 1.499, 1.387, 1.289, 1.223, 1.183, 1.164, 1.154, 1.153, 1.169, 1.211, 1.265, 1.345, 1.448, 1.581, 1.619,
+ 1.513, 1.412, 1.307, 1.228, 1.169, 1.129, 1.105, 1.098, 1.103, 1.127, 1.157, 1.209, 1.272, 1.361, 1.481, 1.583,
+ 1.449, 1.365, 1.257, 1.175, 1.124, 1.085, 1.062, 1.054, 1.059, 1.079, 1.113, 1.151, 1.211, 1.293, 1.407, 1.488,
+ 1.424, 1.324, 1.222, 1.139, 1.089, 1.056, 1.034, 1.031, 1.034, 1.049, 1.075, 1.115, 1.164, 1.241, 1.351, 1.446,
+ 1.412, 1.297, 1.203, 1.119, 1.069, 1.039, 1.021, 1.016, 1.022, 1.032, 1.052, 1.086, 1.135, 1.212, 1.321, 1.439,
+ 1.406, 1.287, 1.195, 1.115, 1.059, 1.028, 1.014, 1.012, 1.015, 1.026, 1.041, 1.074, 1.125, 1.201, 1.302, 1.425,
+ 1.406, 1.294, 1.205, 1.126, 1.062, 1.031, 1.013, 1.009, 1.011, 1.019, 1.042, 1.079, 1.129, 1.203, 1.302, 1.435,
+ 1.415, 1.318, 1.229, 1.146, 1.076, 1.039, 1.019, 1.014, 1.017, 1.031, 1.053, 1.093, 1.144, 1.219, 1.314, 1.436,
+ 1.435, 1.348, 1.246, 1.164, 1.094, 1.059, 1.036, 1.032, 1.037, 1.049, 1.072, 1.114, 1.167, 1.257, 1.343, 1.462,
+ 1.471, 1.385, 1.278, 1.189, 1.124, 1.084, 1.064, 1.061, 1.069, 1.078, 1.101, 1.146, 1.207, 1.298, 1.415, 1.496,
+ 1.522, 1.436, 1.323, 1.228, 1.169, 1.118, 1.101, 1.094, 1.099, 1.113, 1.146, 1.194, 1.265, 1.353, 1.474, 1.571,
+ 1.578, 1.506, 1.378, 1.281, 1.211, 1.156, 1.135, 1.134, 1.139, 1.158, 1.194, 1.251, 1.327, 1.427, 1.559, 1.611
+ ],
+ "sigma": 0.00121,
+ "sigma_Cb": 0.00115
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2360,
+ "ccm":
+ [
+ 1.66078, -0.23588, -0.42491,
+ -0.47456, 1.82763, -0.35307,
+ -0.00545, -1.44729, 2.45273
+ ]
+ },
+ {
+ "ct": 2870,
+ "ccm":
+ [
+ 1.78373, -0.55344, -0.23029,
+ -0.39951, 1.69701, -0.29751,
+ 0.01986, -1.06525, 2.04539
+ ]
+ },
+ {
+ "ct": 2970,
+ "ccm":
+ [
+ 1.73511, -0.56973, -0.16537,
+ -0.36338, 1.69878, -0.33539,
+ -0.02354, -0.76813, 1.79168
+ ]
+ },
+ {
+ "ct": 3000,
+ "ccm":
+ [
+ 2.06374, -0.92218, -0.14156,
+ -0.41721, 1.69289, -0.27568,
+ -0.00554, -0.92741, 1.93295
+ ]
+ },
+ {
+ "ct": 3700,
+ "ccm":
+ [
+ 2.13792, -1.08136, -0.05655,
+ -0.34739, 1.58989, -0.24249,
+ -0.00349, -0.76789, 1.77138
+ ]
+ },
+ {
+ "ct": 3870,
+ "ccm":
+ [
+ 1.83834, -0.70528, -0.13307,
+ -0.30499, 1.60523, -0.30024,
+ -0.05701, -0.58313, 1.64014
+ ]
+ },
+ {
+ "ct": 4000,
+ "ccm":
+ [
+ 2.15741, -1.10295, -0.05447,
+ -0.34631, 1.61158, -0.26528,
+ -0.02723, -0.70288, 1.73011
+ ]
+ },
+ {
+ "ct": 4400,
+ "ccm":
+ [
+ 2.05729, -0.95007, -0.10723,
+ -0.41712, 1.78606, -0.36894,
+ -0.11899, -0.55727, 1.67626
+ ]
+ },
+ {
+ "ct": 4715,
+ "ccm":
+ [
+ 1.90255, -0.77478, -0.12777,
+ -0.31338, 1.88197, -0.56858,
+ -0.06001, -0.61785, 1.67786
+ ]
+ },
+ {
+ "ct": 5920,
+ "ccm":
+ [
+ 1.98691, -0.84671, -0.14019,
+ -0.26581, 1.70615, -0.44035,
+ -0.09532, -0.47332, 1.56864
+ ]
+ },
+ {
+ "ct": 9050,
+ "ccm":
+ [
+ 2.09255, -0.76541, -0.32714,
+ -0.28973, 2.27462, -0.98489,
+ -0.17299, -0.61275, 1.78574
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/imx519.json b/src/ipa/rpi/vc4/data/imx519.json
new file mode 100644
index 00000000..1b0a7747
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx519.json
@@ -0,0 +1,418 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 13841,
+ "reference_gain": 2.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 900,
+ "reference_Y": 12064
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.776
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 189,
+ "slope": 0.01495
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 7900
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8000
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2890.0, 0.7328, 0.3734,
+ 3550.0, 0.6228, 0.4763,
+ 4500.0, 0.5208, 0.5825,
+ 5700.0, 0.4467, 0.6671,
+ 7900.0, 0.3858, 0.7411
+ ],
+ "sensitivity_r": 1.0,
+ "sensitivity_b": 1.0,
+ "transverse_pos": 0.02027,
+ "transverse_neg": 0.01935
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.527, 1.521, 1.508, 1.493, 1.476, 1.455, 1.442, 1.441, 1.441, 1.441, 1.448, 1.467, 1.483, 1.494, 1.503, 1.504,
+ 1.525, 1.513, 1.496, 1.477, 1.461, 1.434, 1.418, 1.409, 1.409, 1.416, 1.429, 1.449, 1.469, 1.485, 1.495, 1.503,
+ 1.517, 1.506, 1.485, 1.461, 1.434, 1.412, 1.388, 1.376, 1.376, 1.386, 1.405, 1.429, 1.449, 1.471, 1.488, 1.495,
+ 1.512, 1.496, 1.471, 1.442, 1.412, 1.388, 1.361, 1.344, 1.344, 1.358, 1.384, 1.405, 1.431, 1.456, 1.479, 1.489,
+ 1.508, 1.488, 1.458, 1.425, 1.393, 1.361, 1.343, 1.322, 1.321, 1.342, 1.358, 1.385, 1.416, 1.445, 1.471, 1.484,
+ 1.507, 1.482, 1.453, 1.418, 1.382, 1.349, 1.322, 1.318, 1.318, 1.321, 1.345, 1.373, 1.405, 1.437, 1.465, 1.483,
+ 1.507, 1.482, 1.453, 1.418, 1.382, 1.349, 1.322, 1.313, 1.313, 1.321, 1.345, 1.373, 1.405, 1.437, 1.465, 1.483,
+ 1.507, 1.485, 1.455, 1.422, 1.387, 1.355, 1.333, 1.319, 1.321, 1.333, 1.351, 1.381, 1.411, 1.441, 1.467, 1.483,
+ 1.508, 1.489, 1.463, 1.432, 1.401, 1.372, 1.355, 1.333, 1.333, 1.351, 1.369, 1.393, 1.422, 1.448, 1.471, 1.484,
+ 1.511, 1.494, 1.472, 1.444, 1.416, 1.398, 1.372, 1.361, 1.361, 1.369, 1.393, 1.411, 1.436, 1.458, 1.477, 1.487,
+ 1.511, 1.496, 1.478, 1.455, 1.436, 1.416, 1.399, 1.391, 1.391, 1.397, 1.411, 1.429, 1.451, 1.466, 1.479, 1.487,
+ 1.511, 1.495, 1.478, 1.462, 1.448, 1.432, 1.419, 1.419, 1.419, 1.419, 1.429, 1.445, 1.459, 1.471, 1.482, 1.487
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 2.581, 2.573, 2.558, 2.539, 2.514, 2.487, 2.473, 2.471, 2.471, 2.471, 2.479, 2.499, 2.517, 2.532, 2.543, 2.544,
+ 2.575, 2.559, 2.539, 2.521, 2.491, 2.458, 2.435, 2.421, 2.421, 2.429, 2.449, 2.477, 2.499, 2.519, 2.534, 2.543,
+ 2.561, 2.549, 2.521, 2.491, 2.457, 2.423, 2.393, 2.375, 2.375, 2.387, 2.412, 2.444, 2.475, 2.499, 2.519, 2.532,
+ 2.552, 2.531, 2.498, 2.459, 2.423, 2.391, 2.349, 2.325, 2.325, 2.344, 2.374, 2.412, 2.444, 2.476, 2.505, 2.519,
+ 2.543, 2.518, 2.479, 2.435, 2.392, 2.349, 2.324, 2.285, 2.283, 2.313, 2.344, 2.374, 2.417, 2.457, 2.489, 2.506,
+ 2.541, 2.511, 2.469, 2.421, 2.372, 2.326, 2.284, 2.277, 2.279, 2.283, 2.313, 2.357, 2.401, 2.443, 2.479, 2.504,
+ 2.541, 2.511, 2.469, 2.421, 2.372, 2.326, 2.284, 2.267, 2.267, 2.281, 2.313, 2.357, 2.401, 2.443, 2.479, 2.504,
+ 2.541, 2.512, 2.472, 2.425, 2.381, 2.338, 2.302, 2.278, 2.279, 2.301, 2.324, 2.364, 2.407, 2.447, 2.481, 2.504,
+ 2.544, 2.519, 2.483, 2.441, 2.401, 2.363, 2.338, 2.302, 2.302, 2.324, 2.355, 2.385, 2.423, 2.459, 2.488, 2.506,
+ 2.549, 2.527, 2.497, 2.463, 2.427, 2.401, 2.363, 2.345, 2.345, 2.355, 2.385, 2.412, 2.444, 2.473, 2.497, 2.509,
+ 2.552, 2.532, 2.507, 2.481, 2.459, 2.427, 2.402, 2.389, 2.389, 2.394, 2.412, 2.444, 2.465, 2.481, 2.499, 2.511,
+ 2.553, 2.533, 2.508, 2.489, 2.475, 2.454, 2.429, 2.429, 2.429, 2.429, 2.439, 2.463, 2.481, 2.492, 2.504, 2.511
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 3.132, 3.126, 3.116, 3.103, 3.097, 3.091, 3.087, 3.086, 3.088, 3.091, 3.092, 3.102, 3.113, 3.121, 3.141, 3.144,
+ 3.149, 3.132, 3.123, 3.108, 3.101, 3.096, 3.091, 3.089, 3.091, 3.092, 3.101, 3.107, 3.116, 3.129, 3.144, 3.153,
+ 3.161, 3.149, 3.129, 3.121, 3.108, 3.103, 3.101, 3.101, 3.101, 3.103, 3.107, 3.116, 3.125, 3.134, 3.153, 3.159,
+ 3.176, 3.161, 3.144, 3.129, 3.124, 3.121, 3.117, 3.118, 3.118, 3.119, 3.122, 3.125, 3.134, 3.146, 3.159, 3.171,
+ 3.183, 3.176, 3.157, 3.144, 3.143, 3.143, 3.139, 3.141, 3.141, 3.141, 3.141, 3.141, 3.146, 3.161, 3.171, 3.179,
+ 3.189, 3.183, 3.165, 3.157, 3.156, 3.157, 3.159, 3.163, 3.163, 3.163, 3.163, 3.161, 3.163, 3.169, 3.179, 3.187,
+ 3.199, 3.189, 3.171, 3.165, 3.164, 3.167, 3.171, 3.173, 3.173, 3.172, 3.171, 3.169, 3.169, 3.175, 3.187, 3.189,
+ 3.206, 3.196, 3.177, 3.171, 3.165, 3.167, 3.171, 3.173, 3.173, 3.172, 3.171, 3.171, 3.173, 3.177, 3.192, 3.194,
+ 3.209, 3.197, 3.178, 3.171, 3.164, 3.161, 3.159, 3.161, 3.162, 3.164, 3.167, 3.171, 3.173, 3.181, 3.193, 3.198,
+ 3.204, 3.194, 3.176, 3.165, 3.161, 3.156, 3.154, 3.154, 3.159, 3.161, 3.164, 3.168, 3.173, 3.182, 3.198, 3.199,
+ 3.199, 3.191, 3.176, 3.169, 3.161, 3.157, 3.153, 3.153, 3.156, 3.161, 3.164, 3.168, 3.173, 3.186, 3.196, 3.199,
+ 3.199, 3.188, 3.179, 3.173, 3.165, 3.157, 3.153, 3.154, 3.156, 3.159, 3.167, 3.171, 3.176, 3.185, 3.193, 3.198
+ ]
+ },
+ {
+ "ct": 6000,
+ "table":
+ [
+ 1.579, 1.579, 1.577, 1.574, 1.573, 1.571, 1.571, 1.571, 1.571, 1.569, 1.569, 1.571, 1.572, 1.574, 1.577, 1.578,
+ 1.584, 1.579, 1.578, 1.575, 1.573, 1.572, 1.571, 1.572, 1.572, 1.571, 1.571, 1.572, 1.573, 1.576, 1.578, 1.579,
+ 1.587, 1.584, 1.579, 1.578, 1.575, 1.573, 1.573, 1.575, 1.575, 1.574, 1.573, 1.574, 1.576, 1.578, 1.581, 1.581,
+ 1.591, 1.587, 1.584, 1.579, 1.578, 1.579, 1.579, 1.581, 1.581, 1.581, 1.578, 1.577, 1.578, 1.581, 1.585, 1.586,
+ 1.595, 1.591, 1.587, 1.585, 1.585, 1.586, 1.587, 1.587, 1.588, 1.588, 1.585, 1.584, 1.584, 1.586, 1.589, 1.589,
+ 1.597, 1.595, 1.591, 1.589, 1.591, 1.593, 1.595, 1.596, 1.597, 1.597, 1.595, 1.594, 1.592, 1.592, 1.593, 1.593,
+ 1.601, 1.597, 1.593, 1.592, 1.593, 1.595, 1.598, 1.599, 1.602, 1.601, 1.598, 1.596, 1.595, 1.596, 1.595, 1.595,
+ 1.601, 1.599, 1.594, 1.593, 1.593, 1.595, 1.598, 1.599, 1.602, 1.601, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597,
+ 1.602, 1.599, 1.594, 1.593, 1.592, 1.593, 1.595, 1.597, 1.597, 1.598, 1.598, 1.597, 1.597, 1.597, 1.598, 1.598,
+ 1.599, 1.598, 1.594, 1.592, 1.591, 1.591, 1.592, 1.595, 1.596, 1.597, 1.597, 1.597, 1.597, 1.599, 1.599, 1.599,
+ 1.598, 1.596, 1.594, 1.593, 1.592, 1.592, 1.592, 1.594, 1.595, 1.597, 1.597, 1.597, 1.598, 1.599, 1.599, 1.599,
+ 1.597, 1.595, 1.594, 1.594, 1.593, 1.592, 1.593, 1.595, 1.595, 1.597, 1.598, 1.598, 1.598, 1.599, 1.599, 1.599
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.887, 2.754, 2.381, 2.105, 1.859, 1.678, 1.625, 1.623, 1.623, 1.624, 1.669, 1.849, 2.092, 2.362, 2.723, 2.838,
+ 2.754, 2.443, 2.111, 1.905, 1.678, 1.542, 1.455, 1.412, 1.412, 1.452, 1.535, 1.665, 1.893, 2.096, 2.413, 2.723,
+ 2.443, 2.216, 1.911, 1.678, 1.537, 1.372, 1.288, 1.245, 1.245, 1.283, 1.363, 1.527, 1.665, 1.895, 2.193, 2.413,
+ 2.318, 2.057, 1.764, 1.541, 1.372, 1.282, 1.159, 1.113, 1.113, 1.151, 1.269, 1.363, 1.527, 1.749, 2.034, 2.278,
+ 2.259, 1.953, 1.671, 1.452, 1.283, 1.159, 1.107, 1.018, 1.017, 1.097, 1.151, 1.269, 1.437, 1.655, 1.931, 2.222,
+ 2.257, 1.902, 1.624, 1.408, 1.239, 1.111, 1.019, 1.011, 1.005, 1.014, 1.098, 1.227, 1.395, 1.608, 1.883, 2.222,
+ 2.257, 1.902, 1.624, 1.408, 1.239, 1.111, 1.016, 1.001, 1.001, 1.007, 1.098, 1.227, 1.395, 1.608, 1.883, 2.222,
+ 2.257, 1.946, 1.666, 1.448, 1.281, 1.153, 1.093, 1.013, 1.008, 1.089, 1.143, 1.269, 1.437, 1.654, 1.934, 2.226,
+ 2.309, 2.044, 1.756, 1.532, 1.363, 1.259, 1.153, 1.093, 1.093, 1.143, 1.264, 1.354, 1.524, 1.746, 2.035, 2.284,
+ 2.425, 2.201, 1.896, 1.662, 1.519, 1.363, 1.259, 1.214, 1.214, 1.264, 1.354, 1.519, 1.655, 1.888, 2.191, 2.413,
+ 2.724, 2.417, 2.091, 1.888, 1.662, 1.519, 1.419, 1.373, 1.373, 1.425, 1.521, 1.655, 1.885, 2.089, 2.409, 2.722,
+ 2.858, 2.724, 2.356, 2.085, 1.842, 1.658, 1.581, 1.577, 1.577, 1.579, 1.653, 1.838, 2.084, 2.359, 2.722, 2.842
+ ],
+ "sigma": 0.00372,
+ "sigma_Cb": 0.00244
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2890,
+ "ccm":
+ [
+ 1.36754, -0.18448, -0.18306,
+ -0.32356, 1.44826, -0.12471,
+ -0.00412, -0.69936, 1.70348
+ ]
+ },
+ {
+ "ct": 2920,
+ "ccm":
+ [
+ 1.26704, 0.01624, -0.28328,
+ -0.28516, 1.38934, -0.10419,
+ -0.04854, -0.82211, 1.87066
+ ]
+ },
+ {
+ "ct": 3550,
+ "ccm":
+ [
+ 1.42836, -0.27235, -0.15601,
+ -0.28751, 1.41075, -0.12325,
+ -0.01812, -0.54849, 1.56661
+ ]
+ },
+ {
+ "ct": 4500,
+ "ccm":
+ [
+ 1.36328, -0.19569, -0.16759,
+ -0.25254, 1.52248, -0.26994,
+ -0.01575, -0.53155, 1.54729
+ ]
+ },
+ {
+ "ct": 5700,
+ "ccm":
+ [
+ 1.49207, -0.37245, -0.11963,
+ -0.21493, 1.40005, -0.18512,
+ -0.03781, -0.38779, 1.42561
+ ]
+ },
+ {
+ "ct": 7900,
+ "ccm":
+ [
+ 1.34849, -0.05425, -0.29424,
+ -0.22182, 1.77684, -0.55502,
+ -0.07403, -0.55336, 1.62739
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/imx708.json b/src/ipa/rpi/vc4/data/imx708.json
new file mode 100644
index 00000000..26aafc95
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx708.json
@@ -0,0 +1,646 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 10672,
+ "reference_gain": 1.12,
+ "reference_aperture": 1.0,
+ "reference_lux": 977,
+ "reference_Y": 8627
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 16.0,
+ "reference_slope": 4.0
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 215,
+ "slope": 0.00287
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2498.0, 0.8733, 0.2606,
+ 2821.0, 0.7707, 0.3245,
+ 2925.0, 0.7338, 0.3499,
+ 2926.0, 0.7193, 0.3603,
+ 2951.0, 0.7144, 0.3639,
+ 2954.0, 0.7111, 0.3663,
+ 3578.0, 0.6038, 0.4516,
+ 3717.0, 0.5861, 0.4669,
+ 3784.0, 0.5786, 0.4737,
+ 4485.0, 0.5113, 0.5368,
+ 4615.0, 0.4994, 0.5486,
+ 4671.0, 0.4927, 0.5554,
+ 5753.0, 0.4274, 0.6246,
+ 5773.0, 0.4265, 0.6256,
+ 7433.0, 0.3723, 0.6881
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.03148,
+ "transverse_neg": 0.03061
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.562, 1.566, 1.566, 1.556, 1.533, 1.506, 1.475, 1.475, 1.475, 1.475, 1.506, 1.533, 1.555, 1.563, 1.562, 1.555,
+ 1.563, 1.564, 1.561, 1.538, 1.508, 1.482, 1.449, 1.436, 1.436, 1.449, 1.481, 1.508, 1.537, 1.557, 1.558, 1.557,
+ 1.564, 1.563, 1.554, 1.522, 1.482, 1.449, 1.421, 1.403, 1.403, 1.419, 1.449, 1.481, 1.519, 1.549, 1.557, 1.559,
+ 1.564, 1.563, 1.545, 1.506, 1.462, 1.421, 1.403, 1.378, 1.378, 1.402, 1.419, 1.459, 1.503, 1.541, 1.557, 1.559,
+ 1.564, 1.562, 1.537, 1.494, 1.447, 1.404, 1.378, 1.364, 1.364, 1.377, 1.402, 1.444, 1.491, 1.532, 1.556, 1.559,
+ 1.564, 1.559, 1.532, 1.487, 1.438, 1.395, 1.365, 1.359, 1.359, 1.364, 1.393, 1.436, 1.484, 1.527, 1.555, 1.558,
+ 1.564, 1.559, 1.532, 1.487, 1.438, 1.395, 1.365, 1.356, 1.356, 1.364, 1.393, 1.436, 1.484, 1.527, 1.554, 1.557,
+ 1.564, 1.561, 1.536, 1.492, 1.444, 1.402, 1.374, 1.364, 1.363, 1.373, 1.401, 1.442, 1.489, 1.531, 1.554, 1.557,
+ 1.564, 1.563, 1.544, 1.504, 1.458, 1.418, 1.397, 1.374, 1.374, 1.395, 1.416, 1.456, 1.501, 1.538, 1.556, 1.557,
+ 1.564, 1.562, 1.551, 1.518, 1.477, 1.441, 1.418, 1.397, 1.397, 1.416, 1.438, 1.474, 1.514, 1.546, 1.556, 1.556,
+ 1.562, 1.562, 1.558, 1.534, 1.499, 1.476, 1.441, 1.426, 1.426, 1.438, 1.473, 1.496, 1.531, 1.552, 1.556, 1.555,
+ 1.561, 1.564, 1.564, 1.552, 1.525, 1.497, 1.466, 1.461, 1.461, 1.464, 1.495, 1.523, 1.548, 1.556, 1.556, 1.552
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 2.609, 2.616, 2.617, 2.607, 2.573, 2.527, 2.483, 2.481, 2.481, 2.483, 2.529, 2.573, 2.604, 2.613, 2.613, 2.604,
+ 2.609, 2.615, 2.608, 2.576, 2.533, 2.489, 2.439, 2.418, 2.418, 2.439, 2.491, 2.532, 2.577, 2.605, 2.609, 2.607,
+ 2.611, 2.611, 2.597, 2.551, 2.489, 2.439, 2.391, 2.364, 2.364, 2.391, 2.439, 2.491, 2.551, 2.592, 2.607, 2.609,
+ 2.612, 2.608, 2.583, 2.526, 2.457, 2.391, 2.362, 2.318, 2.318, 2.362, 2.391, 2.458, 2.526, 2.581, 2.607, 2.611,
+ 2.612, 2.604, 2.571, 2.507, 2.435, 2.362, 2.317, 2.293, 2.294, 2.318, 2.363, 2.434, 2.508, 2.568, 2.604, 2.612,
+ 2.611, 2.602, 2.564, 2.496, 2.419, 2.349, 2.293, 2.284, 2.284, 2.294, 2.347, 2.421, 2.497, 2.562, 2.603, 2.611,
+ 2.609, 2.601, 2.564, 2.496, 2.419, 2.349, 2.293, 2.278, 2.278, 2.294, 2.347, 2.421, 2.497, 2.562, 2.602, 2.609,
+ 2.609, 2.602, 2.568, 2.503, 2.429, 2.361, 2.311, 2.292, 2.292, 2.309, 2.357, 2.429, 2.504, 2.567, 2.602, 2.609,
+ 2.606, 2.604, 2.579, 2.519, 2.449, 2.384, 2.348, 2.311, 2.311, 2.346, 2.383, 2.449, 2.521, 2.577, 2.604, 2.608,
+ 2.604, 2.603, 2.586, 2.537, 2.474, 2.418, 2.384, 2.348, 2.348, 2.383, 2.417, 2.476, 2.538, 2.586, 2.601, 2.603,
+ 2.603, 2.605, 2.596, 2.561, 2.508, 2.474, 2.418, 2.396, 2.396, 2.417, 2.474, 2.511, 2.562, 2.596, 2.603, 2.602,
+ 2.601, 2.607, 2.606, 2.589, 2.549, 2.507, 2.456, 2.454, 2.454, 2.458, 2.508, 2.554, 2.594, 2.605, 2.605, 2.602
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 3.221, 3.226, 3.231, 3.236, 3.239, 3.243, 3.245, 3.247, 3.249, 3.253, 3.255, 3.254, 3.253, 3.242, 3.235, 3.226,
+ 3.225, 3.231, 3.235, 3.238, 3.241, 3.244, 3.246, 3.247, 3.249, 3.254, 3.256, 3.255, 3.252, 3.248, 3.241, 3.232,
+ 3.226, 3.234, 3.239, 3.243, 3.243, 3.245, 3.247, 3.248, 3.251, 3.255, 3.256, 3.256, 3.254, 3.249, 3.244, 3.236,
+ 3.232, 3.238, 3.245, 3.245, 3.246, 3.247, 3.248, 3.251, 3.251, 3.256, 3.257, 3.257, 3.256, 3.254, 3.249, 3.239,
+ 3.232, 3.243, 3.246, 3.246, 3.246, 3.247, 3.248, 3.251, 3.253, 3.257, 3.258, 3.258, 3.257, 3.256, 3.254, 3.239,
+ 3.232, 3.242, 3.246, 3.247, 3.246, 3.246, 3.248, 3.251, 3.252, 3.253, 3.256, 3.255, 3.255, 3.254, 3.251, 3.239,
+ 3.233, 3.241, 3.244, 3.245, 3.244, 3.245, 3.246, 3.249, 3.251, 3.252, 3.253, 3.252, 3.252, 3.252, 3.249, 3.238,
+ 3.238, 3.241, 3.246, 3.246, 3.245, 3.245, 3.247, 3.249, 3.251, 3.252, 3.253, 3.253, 3.252, 3.252, 3.249, 3.239,
+ 3.235, 3.241, 3.245, 3.245, 3.245, 3.245, 3.246, 3.247, 3.251, 3.254, 3.253, 3.255, 3.256, 3.255, 3.251, 3.241,
+ 3.226, 3.235, 3.241, 3.241, 3.241, 3.241, 3.243, 3.245, 3.246, 3.252, 3.253, 3.254, 3.256, 3.254, 3.241, 3.237,
+ 3.205, 3.213, 3.213, 3.214, 3.214, 3.214, 3.214, 3.213, 3.213, 3.216, 3.218, 3.216, 3.214, 3.213, 3.211, 3.208,
+ 3.205, 3.205, 3.212, 3.212, 3.212, 3.213, 3.211, 3.211, 3.211, 3.213, 3.216, 3.214, 3.213, 3.211, 3.208, 3.196
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.645, 1.646, 1.649, 1.653, 1.654, 1.657, 1.659, 1.661, 1.663, 1.662, 1.661, 1.659, 1.656, 1.651, 1.645, 1.642,
+ 1.646, 1.649, 1.652, 1.654, 1.656, 1.659, 1.662, 1.663, 1.664, 1.664, 1.662, 1.661, 1.657, 1.653, 1.649, 1.644,
+ 1.648, 1.652, 1.654, 1.656, 1.658, 1.662, 1.665, 1.668, 1.668, 1.668, 1.665, 1.662, 1.658, 1.655, 1.652, 1.646,
+ 1.649, 1.653, 1.656, 1.658, 1.661, 1.665, 1.667, 1.671, 1.673, 1.671, 1.668, 1.663, 1.659, 1.656, 1.654, 1.647,
+ 1.649, 1.655, 1.657, 1.659, 1.661, 1.666, 1.671, 1.674, 1.675, 1.673, 1.671, 1.664, 1.659, 1.656, 1.654, 1.648,
+ 1.649, 1.654, 1.656, 1.659, 1.661, 1.666, 1.673, 1.676, 1.676, 1.675, 1.671, 1.664, 1.659, 1.656, 1.654, 1.648,
+ 1.649, 1.654, 1.656, 1.658, 1.659, 1.665, 1.672, 1.675, 1.675, 1.674, 1.668, 1.662, 1.658, 1.655, 1.654, 1.646,
+ 1.652, 1.655, 1.657, 1.659, 1.661, 1.665, 1.671, 1.673, 1.673, 1.672, 1.668, 1.662, 1.658, 1.655, 1.654, 1.647,
+ 1.652, 1.655, 1.657, 1.659, 1.661, 1.664, 1.667, 1.671, 1.672, 1.668, 1.666, 1.662, 1.659, 1.656, 1.654, 1.647,
+ 1.647, 1.652, 1.655, 1.656, 1.657, 1.661, 1.664, 1.665, 1.665, 1.665, 1.663, 1.661, 1.657, 1.655, 1.647, 1.647,
+ 1.639, 1.642, 1.644, 1.645, 1.646, 1.648, 1.648, 1.648, 1.649, 1.649, 1.649, 1.646, 1.645, 1.642, 1.639, 1.636,
+ 1.639, 1.641, 1.642, 1.644, 1.645, 1.646, 1.647, 1.647, 1.648, 1.648, 1.647, 1.645, 1.642, 1.639, 1.636, 1.633
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.644, 2.396, 2.077, 1.863, 1.682, 1.535, 1.392, 1.382, 1.382, 1.382, 1.515, 1.657, 1.826, 2.035, 2.351, 2.604,
+ 2.497, 2.229, 1.947, 1.733, 1.539, 1.424, 1.296, 1.249, 1.249, 1.285, 1.401, 1.519, 1.699, 1.908, 2.183, 2.456,
+ 2.389, 2.109, 1.848, 1.622, 1.424, 1.296, 1.201, 1.146, 1.146, 1.188, 1.285, 1.401, 1.591, 1.811, 2.065, 2.347,
+ 2.317, 2.026, 1.771, 1.535, 1.339, 1.201, 1.145, 1.069, 1.069, 1.134, 1.188, 1.318, 1.505, 1.734, 1.983, 2.273,
+ 2.276, 1.972, 1.715, 1.474, 1.281, 1.148, 1.069, 1.033, 1.024, 1.065, 1.134, 1.262, 1.446, 1.679, 1.929, 2.233,
+ 2.268, 1.941, 1.682, 1.441, 1.251, 1.119, 1.033, 1.013, 1.013, 1.024, 1.105, 1.231, 1.415, 1.649, 1.898, 2.227,
+ 2.268, 1.941, 1.682, 1.441, 1.251, 1.119, 1.033, 1.001, 1.001, 1.024, 1.105, 1.231, 1.415, 1.649, 1.898, 2.227,
+ 2.268, 1.951, 1.694, 1.456, 1.265, 1.131, 1.044, 1.026, 1.019, 1.039, 1.118, 1.246, 1.429, 1.663, 1.912, 2.227,
+ 2.291, 1.992, 1.738, 1.505, 1.311, 1.175, 1.108, 1.044, 1.041, 1.106, 1.161, 1.292, 1.478, 1.707, 1.955, 2.252,
+ 2.347, 2.058, 1.803, 1.581, 1.384, 1.245, 1.175, 1.108, 1.108, 1.161, 1.239, 1.364, 1.551, 1.773, 2.023, 2.311,
+ 2.438, 2.156, 1.884, 1.674, 1.484, 1.373, 1.245, 1.199, 1.199, 1.239, 1.363, 1.463, 1.647, 1.858, 2.123, 2.406,
+ 2.563, 2.305, 1.998, 1.792, 1.615, 1.472, 1.339, 1.322, 1.322, 1.326, 1.456, 1.593, 1.767, 1.973, 2.273, 2.532
+ ],
+ "sigma": 0.00178,
+ "sigma_Cb": 0.00217
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2964,
+ "ccm":
+ [
+ 1.72129, -0.45961, -0.26169,
+ -0.30042, 1.56924, -0.26882,
+ 0.15133, -1.13293, 1.98161
+ ]
+ },
+ {
+ "ct": 3610,
+ "ccm":
+ [
+ 1.54474, -0.35082, -0.19391,
+ -0.36989, 1.67926, -0.30936,
+ -0.00524, -0.55197, 1.55722
+ ]
+ },
+ {
+ "ct": 4640,
+ "ccm":
+ [
+ 1.52972, -0.35168, -0.17804,
+ -0.28309, 1.67098, -0.38788,
+ 0.01695, -0.57209, 1.55515
+ ]
+ },
+ {
+ "ct": 5910,
+ "ccm":
+ [
+ 1.56879, -0.42159, -0.14719,
+ -0.27275, 1.59354, -0.32079,
+ -0.02862, -0.40662, 1.43525
+ ]
+ },
+ {
+ "ct": 7590,
+ "ccm":
+ [
+ 1.41424, -0.21092, -0.20332,
+ -0.17646, 1.71734, -0.54087,
+ 0.01297, -0.63111, 1.61814
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.af":
+ {
+ "ranges":
+ {
+ "normal":
+ {
+ "min": 0.0,
+ "max": 12.0,
+ "default": 1.0
+ },
+ "macro":
+ {
+ "min": 3.0,
+ "max": 15.0,
+ "default": 4.0
+ }
+ },
+ "speeds":
+ {
+ "normal":
+ {
+ "step_coarse": 1.0,
+ "step_fine": 0.25,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.02,
+ "pdaf_squelch": 0.125,
+ "max_slew": 2.0,
+ "pdaf_frames": 20,
+ "dropout_frames": 6,
+ "step_frames": 4
+ }
+ },
+ "conf_epsilon": 8,
+ "conf_thresh": 16,
+ "conf_clip": 512,
+ "skip_frames": 5,
+ "map": [ 0.0, 445, 15.0, 925 ]
+ }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx708_noir.json b/src/ipa/rpi/vc4/data/imx708_noir.json
new file mode 100644
index 00000000..8259ca4d
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx708_noir.json
@@ -0,0 +1,745 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 10672,
+ "reference_gain": 1.12,
+ "reference_aperture": 1.0,
+ "reference_lux": 977,
+ "reference_Y": 8627
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 16.0,
+ "reference_slope": 4.0
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 215,
+ "slope": 0.00287
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 0,
+ "ct_curve":
+ [
+ 2498.0, 0.8733, 0.2606,
+ 2821.0, 0.7707, 0.3245,
+ 2925.0, 0.7338, 0.3499,
+ 2926.0, 0.7193, 0.3603,
+ 2951.0, 0.7144, 0.3639,
+ 2954.0, 0.7111, 0.3663,
+ 3578.0, 0.6038, 0.4516,
+ 3717.0, 0.5861, 0.4669,
+ 3784.0, 0.5786, 0.4737,
+ 4485.0, 0.5113, 0.5368,
+ 4615.0, 0.4994, 0.5486,
+ 4671.0, 0.4927, 0.5554,
+ 5753.0, 0.4274, 0.6246,
+ 5773.0, 0.4265, 0.6256,
+ 7433.0, 0.3723, 0.6881
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.03148,
+ "transverse_neg": 0.03061
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.562, 1.566, 1.566, 1.556, 1.533, 1.506, 1.475, 1.475, 1.475, 1.475, 1.506, 1.533, 1.555, 1.563, 1.562, 1.555,
+ 1.563, 1.564, 1.561, 1.538, 1.508, 1.482, 1.449, 1.436, 1.436, 1.449, 1.481, 1.508, 1.537, 1.557, 1.558, 1.557,
+ 1.564, 1.563, 1.554, 1.522, 1.482, 1.449, 1.421, 1.403, 1.403, 1.419, 1.449, 1.481, 1.519, 1.549, 1.557, 1.559,
+ 1.564, 1.563, 1.545, 1.506, 1.462, 1.421, 1.403, 1.378, 1.378, 1.402, 1.419, 1.459, 1.503, 1.541, 1.557, 1.559,
+ 1.564, 1.562, 1.537, 1.494, 1.447, 1.404, 1.378, 1.364, 1.364, 1.377, 1.402, 1.444, 1.491, 1.532, 1.556, 1.559,
+ 1.564, 1.559, 1.532, 1.487, 1.438, 1.395, 1.365, 1.359, 1.359, 1.364, 1.393, 1.436, 1.484, 1.527, 1.555, 1.558,
+ 1.564, 1.559, 1.532, 1.487, 1.438, 1.395, 1.365, 1.356, 1.356, 1.364, 1.393, 1.436, 1.484, 1.527, 1.554, 1.557,
+ 1.564, 1.561, 1.536, 1.492, 1.444, 1.402, 1.374, 1.364, 1.363, 1.373, 1.401, 1.442, 1.489, 1.531, 1.554, 1.557,
+ 1.564, 1.563, 1.544, 1.504, 1.458, 1.418, 1.397, 1.374, 1.374, 1.395, 1.416, 1.456, 1.501, 1.538, 1.556, 1.557,
+ 1.564, 1.562, 1.551, 1.518, 1.477, 1.441, 1.418, 1.397, 1.397, 1.416, 1.438, 1.474, 1.514, 1.546, 1.556, 1.556,
+ 1.562, 1.562, 1.558, 1.534, 1.499, 1.476, 1.441, 1.426, 1.426, 1.438, 1.473, 1.496, 1.531, 1.552, 1.556, 1.555,
+ 1.561, 1.564, 1.564, 1.552, 1.525, 1.497, 1.466, 1.461, 1.461, 1.464, 1.495, 1.523, 1.548, 1.556, 1.556, 1.552
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 2.609, 2.616, 2.617, 2.607, 2.573, 2.527, 2.483, 2.481, 2.481, 2.483, 2.529, 2.573, 2.604, 2.613, 2.613, 2.604,
+ 2.609, 2.615, 2.608, 2.576, 2.533, 2.489, 2.439, 2.418, 2.418, 2.439, 2.491, 2.532, 2.577, 2.605, 2.609, 2.607,
+ 2.611, 2.611, 2.597, 2.551, 2.489, 2.439, 2.391, 2.364, 2.364, 2.391, 2.439, 2.491, 2.551, 2.592, 2.607, 2.609,
+ 2.612, 2.608, 2.583, 2.526, 2.457, 2.391, 2.362, 2.318, 2.318, 2.362, 2.391, 2.458, 2.526, 2.581, 2.607, 2.611,
+ 2.612, 2.604, 2.571, 2.507, 2.435, 2.362, 2.317, 2.293, 2.294, 2.318, 2.363, 2.434, 2.508, 2.568, 2.604, 2.612,
+ 2.611, 2.602, 2.564, 2.496, 2.419, 2.349, 2.293, 2.284, 2.284, 2.294, 2.347, 2.421, 2.497, 2.562, 2.603, 2.611,
+ 2.609, 2.601, 2.564, 2.496, 2.419, 2.349, 2.293, 2.278, 2.278, 2.294, 2.347, 2.421, 2.497, 2.562, 2.602, 2.609,
+ 2.609, 2.602, 2.568, 2.503, 2.429, 2.361, 2.311, 2.292, 2.292, 2.309, 2.357, 2.429, 2.504, 2.567, 2.602, 2.609,
+ 2.606, 2.604, 2.579, 2.519, 2.449, 2.384, 2.348, 2.311, 2.311, 2.346, 2.383, 2.449, 2.521, 2.577, 2.604, 2.608,
+ 2.604, 2.603, 2.586, 2.537, 2.474, 2.418, 2.384, 2.348, 2.348, 2.383, 2.417, 2.476, 2.538, 2.586, 2.601, 2.603,
+ 2.603, 2.605, 2.596, 2.561, 2.508, 2.474, 2.418, 2.396, 2.396, 2.417, 2.474, 2.511, 2.562, 2.596, 2.603, 2.602,
+ 2.601, 2.607, 2.606, 2.589, 2.549, 2.507, 2.456, 2.454, 2.454, 2.458, 2.508, 2.554, 2.594, 2.605, 2.605, 2.602
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 3.221, 3.226, 3.231, 3.236, 3.239, 3.243, 3.245, 3.247, 3.249, 3.253, 3.255, 3.254, 3.253, 3.242, 3.235, 3.226,
+ 3.225, 3.231, 3.235, 3.238, 3.241, 3.244, 3.246, 3.247, 3.249, 3.254, 3.256, 3.255, 3.252, 3.248, 3.241, 3.232,
+ 3.226, 3.234, 3.239, 3.243, 3.243, 3.245, 3.247, 3.248, 3.251, 3.255, 3.256, 3.256, 3.254, 3.249, 3.244, 3.236,
+ 3.232, 3.238, 3.245, 3.245, 3.246, 3.247, 3.248, 3.251, 3.251, 3.256, 3.257, 3.257, 3.256, 3.254, 3.249, 3.239,
+ 3.232, 3.243, 3.246, 3.246, 3.246, 3.247, 3.248, 3.251, 3.253, 3.257, 3.258, 3.258, 3.257, 3.256, 3.254, 3.239,
+ 3.232, 3.242, 3.246, 3.247, 3.246, 3.246, 3.248, 3.251, 3.252, 3.253, 3.256, 3.255, 3.255, 3.254, 3.251, 3.239,
+ 3.233, 3.241, 3.244, 3.245, 3.244, 3.245, 3.246, 3.249, 3.251, 3.252, 3.253, 3.252, 3.252, 3.252, 3.249, 3.238,
+ 3.238, 3.241, 3.246, 3.246, 3.245, 3.245, 3.247, 3.249, 3.251, 3.252, 3.253, 3.253, 3.252, 3.252, 3.249, 3.239,
+ 3.235, 3.241, 3.245, 3.245, 3.245, 3.245, 3.246, 3.247, 3.251, 3.254, 3.253, 3.255, 3.256, 3.255, 3.251, 3.241,
+ 3.226, 3.235, 3.241, 3.241, 3.241, 3.241, 3.243, 3.245, 3.246, 3.252, 3.253, 3.254, 3.256, 3.254, 3.241, 3.237,
+ 3.205, 3.213, 3.213, 3.214, 3.214, 3.214, 3.214, 3.213, 3.213, 3.216, 3.218, 3.216, 3.214, 3.213, 3.211, 3.208,
+ 3.205, 3.205, 3.212, 3.212, 3.212, 3.213, 3.211, 3.211, 3.211, 3.213, 3.216, 3.214, 3.213, 3.211, 3.208, 3.196
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.645, 1.646, 1.649, 1.653, 1.654, 1.657, 1.659, 1.661, 1.663, 1.662, 1.661, 1.659, 1.656, 1.651, 1.645, 1.642,
+ 1.646, 1.649, 1.652, 1.654, 1.656, 1.659, 1.662, 1.663, 1.664, 1.664, 1.662, 1.661, 1.657, 1.653, 1.649, 1.644,
+ 1.648, 1.652, 1.654, 1.656, 1.658, 1.662, 1.665, 1.668, 1.668, 1.668, 1.665, 1.662, 1.658, 1.655, 1.652, 1.646,
+ 1.649, 1.653, 1.656, 1.658, 1.661, 1.665, 1.667, 1.671, 1.673, 1.671, 1.668, 1.663, 1.659, 1.656, 1.654, 1.647,
+ 1.649, 1.655, 1.657, 1.659, 1.661, 1.666, 1.671, 1.674, 1.675, 1.673, 1.671, 1.664, 1.659, 1.656, 1.654, 1.648,
+ 1.649, 1.654, 1.656, 1.659, 1.661, 1.666, 1.673, 1.676, 1.676, 1.675, 1.671, 1.664, 1.659, 1.656, 1.654, 1.648,
+ 1.649, 1.654, 1.656, 1.658, 1.659, 1.665, 1.672, 1.675, 1.675, 1.674, 1.668, 1.662, 1.658, 1.655, 1.654, 1.646,
+ 1.652, 1.655, 1.657, 1.659, 1.661, 1.665, 1.671, 1.673, 1.673, 1.672, 1.668, 1.662, 1.658, 1.655, 1.654, 1.647,
+ 1.652, 1.655, 1.657, 1.659, 1.661, 1.664, 1.667, 1.671, 1.672, 1.668, 1.666, 1.662, 1.659, 1.656, 1.654, 1.647,
+ 1.647, 1.652, 1.655, 1.656, 1.657, 1.661, 1.664, 1.665, 1.665, 1.665, 1.663, 1.661, 1.657, 1.655, 1.647, 1.647,
+ 1.639, 1.642, 1.644, 1.645, 1.646, 1.648, 1.648, 1.648, 1.649, 1.649, 1.649, 1.646, 1.645, 1.642, 1.639, 1.636,
+ 1.639, 1.641, 1.642, 1.644, 1.645, 1.646, 1.647, 1.647, 1.648, 1.648, 1.647, 1.645, 1.642, 1.639, 1.636, 1.633
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.644, 2.396, 2.077, 1.863, 1.682, 1.535, 1.392, 1.382, 1.382, 1.382, 1.515, 1.657, 1.826, 2.035, 2.351, 2.604,
+ 2.497, 2.229, 1.947, 1.733, 1.539, 1.424, 1.296, 1.249, 1.249, 1.285, 1.401, 1.519, 1.699, 1.908, 2.183, 2.456,
+ 2.389, 2.109, 1.848, 1.622, 1.424, 1.296, 1.201, 1.146, 1.146, 1.188, 1.285, 1.401, 1.591, 1.811, 2.065, 2.347,
+ 2.317, 2.026, 1.771, 1.535, 1.339, 1.201, 1.145, 1.069, 1.069, 1.134, 1.188, 1.318, 1.505, 1.734, 1.983, 2.273,
+ 2.276, 1.972, 1.715, 1.474, 1.281, 1.148, 1.069, 1.033, 1.024, 1.065, 1.134, 1.262, 1.446, 1.679, 1.929, 2.233,
+ 2.268, 1.941, 1.682, 1.441, 1.251, 1.119, 1.033, 1.013, 1.013, 1.024, 1.105, 1.231, 1.415, 1.649, 1.898, 2.227,
+ 2.268, 1.941, 1.682, 1.441, 1.251, 1.119, 1.033, 1.001, 1.001, 1.024, 1.105, 1.231, 1.415, 1.649, 1.898, 2.227,
+ 2.268, 1.951, 1.694, 1.456, 1.265, 1.131, 1.044, 1.026, 1.019, 1.039, 1.118, 1.246, 1.429, 1.663, 1.912, 2.227,
+ 2.291, 1.992, 1.738, 1.505, 1.311, 1.175, 1.108, 1.044, 1.041, 1.106, 1.161, 1.292, 1.478, 1.707, 1.955, 2.252,
+ 2.347, 2.058, 1.803, 1.581, 1.384, 1.245, 1.175, 1.108, 1.108, 1.161, 1.239, 1.364, 1.551, 1.773, 2.023, 2.311,
+ 2.438, 2.156, 1.884, 1.674, 1.484, 1.373, 1.245, 1.199, 1.199, 1.239, 1.363, 1.463, 1.647, 1.858, 2.123, 2.406,
+ 2.563, 2.305, 1.998, 1.792, 1.615, 1.472, 1.339, 1.322, 1.322, 1.326, 1.456, 1.593, 1.767, 1.973, 2.273, 2.532
+ ],
+ "sigma": 0.00178,
+ "sigma_Cb": 0.00217
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2498,
+ "ccm":
+ [
+ 1.14912, 0.28638, -0.43551,
+ -0.49691, 1.60391, -0.10701,
+ -0.10513, -1.09534, 2.20047
+ ]
+ },
+ {
+ "ct": 2821,
+ "ccm":
+ [
+ 1.18251, 0.15501, -0.33752,
+ -0.44304, 1.58495, -0.14191,
+ -0.05077, -0.96422, 2.01498
+ ]
+ },
+ {
+ "ct": 2925,
+ "ccm":
+ [
+ 1.18668, 0.00195, -0.18864,
+ -0.41617, 1.50514, -0.08897,
+ -0.02675, -0.91143, 1.93818
+ ]
+ },
+ {
+ "ct": 2926,
+ "ccm":
+ [
+ 1.50948, -0.44421, -0.06527,
+ -0.37241, 1.41726, -0.04486,
+ 0.07098, -0.84694, 1.77596
+ ]
+ },
+ {
+ "ct": 2951,
+ "ccm":
+ [
+ 1.52743, -0.47333, -0.05411,
+ -0.36485, 1.40764, -0.04279,
+ 0.08672, -0.90479, 1.81807
+ ]
+ },
+ {
+ "ct": 2954,
+ "ccm":
+ [
+ 1.51683, -0.46841, -0.04841,
+ -0.36288, 1.39914, -0.03625,
+ 0.06421, -0.82034, 1.75613
+ ]
+ },
+ {
+ "ct": 3578,
+ "ccm":
+ [
+ 1.59888, -0.59105, -0.00784,
+ -0.29366, 1.32037, -0.02671,
+ 0.06627, -0.76465, 1.69838
+ ]
+ },
+ {
+ "ct": 3717,
+ "ccm":
+ [
+ 1.59063, -0.58059, -0.01003,
+ -0.29583, 1.32715, -0.03132,
+ 0.03613, -0.67431, 1.63817
+ ]
+ },
+ {
+ "ct": 3784,
+ "ccm":
+ [
+ 1.59379, -0.58861, -0.00517,
+ -0.29178, 1.33292, -0.04115,
+ 0.03541, -0.66162, 1.62622
+ ]
+ },
+ {
+ "ct": 4485,
+ "ccm":
+ [
+ 1.40761, -0.34561, -0.06201,
+ -0.32388, 1.57221, -0.24832,
+ -0.01014, -0.63427, 1.64441
+ ]
+ },
+ {
+ "ct": 4615,
+ "ccm":
+ [
+ 1.41537, -0.35832, -0.05705,
+ -0.31429, 1.56019, -0.24591,
+ -0.01761, -0.61859, 1.63621
+ ]
+ },
+ {
+ "ct": 4671,
+ "ccm":
+ [
+ 1.42941, -0.38178, -0.04764,
+ -0.31421, 1.55925, -0.24504,
+ -0.01141, -0.62987, 1.64129
+ ]
+ },
+ {
+ "ct": 5753,
+ "ccm":
+ [
+ 1.64549, -0.63329, -0.01221,
+ -0.22431, 1.36423, -0.13992,
+ -0.00831, -0.55373, 1.56204
+ ]
+ },
+ {
+ "ct": 5773,
+ "ccm":
+ [
+ 1.63668, -0.63557, -0.00111,
+ -0.21919, 1.36234, -0.14315,
+ -0.00399, -0.57428, 1.57827
+ ]
+ },
+ {
+ "ct": 7433,
+ "ccm":
+ [
+ 1.36007, -0.09277, -0.26729,
+ -0.36886, 2.09249, -0.72363,
+ -0.12573, -0.76761, 1.89334
+ ]
+ },
+ {
+ "ct": 55792,
+ "ccm":
+ [
+ 1.65091, -0.63689, -0.01401,
+ -0.22277, 1.35752, -0.13475,
+ -0.00943, -0.55091, 1.56033
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.af":
+ {
+ "ranges":
+ {
+ "normal":
+ {
+ "min": 0.0,
+ "max": 12.0,
+ "default": 1.0
+ },
+ "macro":
+ {
+ "min": 3.0,
+ "max": 15.0,
+ "default": 4.0
+ }
+ },
+ "speeds":
+ {
+ "normal":
+ {
+ "step_coarse": 1.0,
+ "step_fine": 0.25,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.02,
+ "pdaf_squelch": 0.125,
+ "max_slew": 2.0,
+ "pdaf_frames": 20,
+ "dropout_frames": 6,
+ "step_frames": 4
+ }
+ },
+ "conf_epsilon": 8,
+ "conf_thresh": 16,
+ "conf_clip": 512,
+ "skip_frames": 5,
+ "map": [ 0.0, 445, 15.0, 925 ]
+ }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx708_wide.json b/src/ipa/rpi/vc4/data/imx708_wide.json
new file mode 100644
index 00000000..0f846ea2
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx708_wide.json
@@ -0,0 +1,657 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 9989,
+ "reference_gain": 1.23,
+ "reference_aperture": 1.0,
+ "reference_lux": 980,
+ "reference_Y": 8345
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 16.0,
+ "reference_slope": 4.0
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 215,
+ "slope": 0.00287
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2750.0, 0.7881, 0.2849,
+ 2940.0, 0.7559, 0.3103,
+ 3650.0, 0.6291, 0.4206,
+ 4625.0, 0.5336, 0.5161,
+ 5715.0, 0.4668, 0.5898
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.01165,
+ "transverse_neg": 0.01601
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.529, 1.526, 1.522, 1.506, 1.489, 1.473, 1.458, 1.456, 1.456, 1.458, 1.474, 1.493, 1.513, 1.531, 1.541, 1.544,
+ 1.527, 1.523, 1.511, 1.491, 1.474, 1.459, 1.445, 1.441, 1.441, 1.446, 1.461, 1.479, 1.499, 1.521, 1.536, 1.541,
+ 1.524, 1.515, 1.498, 1.477, 1.459, 1.444, 1.431, 1.426, 1.426, 1.435, 1.446, 1.466, 1.487, 1.507, 1.528, 1.538,
+ 1.522, 1.512, 1.491, 1.468, 1.447, 1.431, 1.423, 1.417, 1.418, 1.425, 1.435, 1.455, 1.479, 1.499, 1.523, 1.537,
+ 1.522, 1.509, 1.485, 1.463, 1.441, 1.423, 1.416, 1.413, 1.415, 1.418, 1.429, 1.449, 1.473, 1.495, 1.521, 1.538,
+ 1.522, 1.508, 1.483, 1.461, 1.438, 1.421, 1.413, 1.412, 1.412, 1.415, 1.428, 1.447, 1.471, 1.493, 1.519, 1.538,
+ 1.522, 1.509, 1.484, 1.462, 1.439, 1.421, 1.414, 1.411, 1.412, 1.416, 1.428, 1.447, 1.471, 1.493, 1.519, 1.537,
+ 1.523, 1.511, 1.487, 1.465, 1.443, 1.424, 1.417, 1.413, 1.415, 1.419, 1.429, 1.451, 1.473, 1.494, 1.519, 1.536,
+ 1.524, 1.514, 1.493, 1.471, 1.451, 1.434, 1.424, 1.419, 1.419, 1.428, 1.437, 1.457, 1.477, 1.498, 1.521, 1.538,
+ 1.527, 1.521, 1.503, 1.481, 1.462, 1.449, 1.434, 1.429, 1.429, 1.437, 1.451, 1.469, 1.488, 1.508, 1.527, 1.539,
+ 1.529, 1.527, 1.515, 1.495, 1.477, 1.462, 1.449, 1.444, 1.444, 1.451, 1.467, 1.481, 1.499, 1.519, 1.535, 1.543,
+ 1.534, 1.531, 1.527, 1.512, 1.492, 1.476, 1.463, 1.461, 1.461, 1.464, 1.479, 1.495, 1.515, 1.533, 1.543, 1.546
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 2.603, 2.599, 2.591, 2.567, 2.539, 2.515, 2.489, 2.489, 2.489, 2.491, 2.516, 2.543, 2.574, 2.597, 2.614, 2.617,
+ 2.596, 2.591, 2.571, 2.542, 2.516, 2.489, 2.464, 2.458, 2.458, 2.469, 2.492, 2.518, 2.547, 2.576, 2.602, 2.614,
+ 2.591, 2.576, 2.546, 2.519, 2.489, 2.464, 2.437, 2.427, 2.427, 2.441, 2.467, 2.492, 2.525, 2.553, 2.586, 2.605,
+ 2.588, 2.568, 2.534, 2.503, 2.472, 2.437, 2.423, 2.409, 2.411, 2.425, 2.441, 2.475, 2.513, 2.541, 2.577, 2.602,
+ 2.588, 2.565, 2.527, 2.494, 2.461, 2.425, 2.409, 2.399, 2.403, 2.409, 2.431, 2.466, 2.503, 2.534, 2.571, 2.601,
+ 2.586, 2.561, 2.525, 2.491, 2.454, 2.418, 2.399, 2.396, 2.395, 2.402, 2.424, 2.461, 2.501, 2.531, 2.567, 2.599,
+ 2.583, 2.559, 2.525, 2.491, 2.454, 2.418, 2.398, 2.393, 2.393, 2.401, 2.423, 2.459, 2.498, 2.531, 2.566, 2.597,
+ 2.583, 2.559, 2.526, 2.494, 2.458, 2.421, 2.404, 2.397, 2.399, 2.404, 2.426, 2.461, 2.501, 2.531, 2.566, 2.596,
+ 2.583, 2.563, 2.531, 2.501, 2.469, 2.435, 2.419, 2.405, 2.404, 2.422, 2.435, 2.471, 2.505, 2.537, 2.572, 2.596,
+ 2.585, 2.571, 2.539, 2.516, 2.486, 2.458, 2.435, 2.424, 2.424, 2.435, 2.459, 2.489, 2.521, 2.546, 2.579, 2.601,
+ 2.589, 2.578, 2.557, 2.532, 2.506, 2.483, 2.458, 2.449, 2.449, 2.459, 2.485, 2.507, 2.535, 2.563, 2.591, 2.605,
+ 2.589, 2.586, 2.575, 2.551, 2.525, 2.503, 2.481, 2.476, 2.476, 2.481, 2.504, 2.526, 2.555, 2.583, 2.604, 2.611
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 3.311, 3.339, 3.369, 3.374, 3.371, 3.363, 3.356, 3.353, 3.353, 3.353, 3.357, 3.362, 3.362, 3.356, 3.328, 3.311,
+ 3.321, 3.354, 3.374, 3.374, 3.368, 3.359, 3.352, 3.349, 3.347, 3.347, 3.349, 3.357, 3.361, 3.359, 3.343, 3.324,
+ 3.334, 3.368, 3.375, 3.374, 3.365, 3.356, 3.349, 3.347, 3.346, 3.346, 3.347, 3.349, 3.358, 3.361, 3.357, 3.336,
+ 3.346, 3.378, 3.378, 3.369, 3.363, 3.358, 3.351, 3.348, 3.347, 3.346, 3.347, 3.348, 3.354, 3.364, 3.363, 3.345,
+ 3.351, 3.381, 3.381, 3.368, 3.361, 3.357, 3.349, 3.347, 3.347, 3.345, 3.345, 3.347, 3.353, 3.364, 3.364, 3.347,
+ 3.353, 3.379, 3.379, 3.366, 3.359, 3.351, 3.348, 3.343, 3.342, 3.342, 3.343, 3.345, 3.351, 3.363, 3.363, 3.347,
+ 3.353, 3.376, 3.376, 3.363, 3.351, 3.347, 3.343, 3.338, 3.336, 3.338, 3.339, 3.343, 3.351, 3.361, 3.361, 3.347,
+ 3.351, 3.374, 3.374, 3.359, 3.351, 3.345, 3.338, 3.334, 3.333, 3.334, 3.336, 3.339, 3.347, 3.358, 3.358, 3.345,
+ 3.346, 3.368, 3.368, 3.359, 3.349, 3.343, 3.336, 3.332, 3.327, 3.331, 3.333, 3.337, 3.346, 3.356, 3.356, 3.341,
+ 3.336, 3.362, 3.364, 3.359, 3.351, 3.342, 3.334, 3.324, 3.324, 3.325, 3.329, 3.336, 3.346, 3.351, 3.351, 3.333,
+ 3.324, 3.349, 3.359, 3.358, 3.352, 3.341, 3.329, 3.323, 3.321, 3.322, 3.326, 3.336, 3.346, 3.347, 3.339, 3.319,
+ 3.311, 3.328, 3.352, 3.354, 3.352, 3.341, 3.329, 3.321, 3.319, 3.321, 3.324, 3.338, 3.343, 3.343, 3.319, 3.312
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.634, 1.647, 1.665, 1.668, 1.668, 1.664, 1.662, 1.662, 1.661, 1.661, 1.661, 1.663, 1.663, 1.659, 1.643, 1.636,
+ 1.639, 1.656, 1.668, 1.669, 1.668, 1.666, 1.664, 1.663, 1.663, 1.661, 1.661, 1.662, 1.663, 1.662, 1.654, 1.642,
+ 1.645, 1.663, 1.669, 1.668, 1.667, 1.667, 1.667, 1.668, 1.668, 1.665, 1.662, 1.661, 1.662, 1.664, 1.661, 1.649,
+ 1.651, 1.669, 1.669, 1.667, 1.666, 1.668, 1.669, 1.672, 1.672, 1.668, 1.665, 1.661, 1.661, 1.665, 1.665, 1.655,
+ 1.654, 1.669, 1.669, 1.666, 1.666, 1.669, 1.672, 1.673, 1.673, 1.671, 1.666, 1.661, 1.661, 1.665, 1.665, 1.659,
+ 1.654, 1.669, 1.669, 1.666, 1.666, 1.669, 1.671, 1.673, 1.672, 1.669, 1.667, 1.661, 1.661, 1.665, 1.665, 1.659,
+ 1.654, 1.668, 1.668, 1.664, 1.663, 1.667, 1.669, 1.671, 1.669, 1.668, 1.665, 1.661, 1.661, 1.663, 1.663, 1.659,
+ 1.653, 1.665, 1.665, 1.661, 1.661, 1.664, 1.667, 1.668, 1.668, 1.665, 1.661, 1.658, 1.659, 1.662, 1.662, 1.657,
+ 1.651, 1.664, 1.664, 1.659, 1.659, 1.661, 1.663, 1.663, 1.662, 1.661, 1.658, 1.656, 1.657, 1.662, 1.662, 1.655,
+ 1.645, 1.661, 1.663, 1.661, 1.659, 1.659, 1.659, 1.657, 1.657, 1.656, 1.654, 1.655, 1.656, 1.661, 1.661, 1.649,
+ 1.641, 1.654, 1.661, 1.661, 1.659, 1.657, 1.655, 1.653, 1.652, 1.651, 1.652, 1.653, 1.657, 1.658, 1.655, 1.644,
+ 1.635, 1.645, 1.661, 1.661, 1.661, 1.655, 1.653, 1.649, 1.648, 1.647, 1.651, 1.653, 1.657, 1.657, 1.646, 1.638
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 3.535, 3.279, 3.049, 2.722, 2.305, 1.958, 1.657, 1.647, 1.647, 1.656, 1.953, 2.289, 2.707, 3.058, 3.325, 3.589,
+ 3.379, 3.157, 2.874, 2.421, 1.973, 1.735, 1.472, 1.388, 1.388, 1.471, 1.724, 1.963, 2.409, 2.877, 3.185, 3.416,
+ 3.288, 3.075, 2.696, 2.169, 1.735, 1.472, 1.311, 1.208, 1.208, 1.306, 1.471, 1.724, 2.159, 2.695, 3.092, 3.321,
+ 3.238, 3.001, 2.534, 1.981, 1.572, 1.311, 1.207, 1.082, 1.082, 1.204, 1.306, 1.563, 1.973, 2.529, 3.008, 3.259,
+ 3.211, 2.938, 2.414, 1.859, 1.468, 1.221, 1.082, 1.036, 1.031, 1.079, 1.217, 1.463, 1.851, 2.403, 2.931, 3.229,
+ 3.206, 2.904, 2.356, 1.802, 1.421, 1.181, 1.037, 1.002, 1.002, 1.032, 1.175, 1.414, 1.793, 2.343, 2.899, 3.223,
+ 3.206, 2.904, 2.356, 1.802, 1.421, 1.181, 1.037, 1.005, 1.005, 1.032, 1.175, 1.414, 1.793, 2.343, 2.899, 3.223,
+ 3.211, 2.936, 2.417, 1.858, 1.468, 1.222, 1.083, 1.037, 1.032, 1.083, 1.218, 1.463, 1.848, 2.403, 2.932, 3.226,
+ 3.234, 2.997, 2.536, 1.979, 1.569, 1.311, 1.206, 1.084, 1.084, 1.204, 1.305, 1.565, 1.966, 2.524, 2.996, 3.251,
+ 3.282, 3.069, 2.697, 2.166, 1.731, 1.471, 1.311, 1.207, 1.207, 1.305, 1.466, 1.729, 2.158, 2.689, 3.077, 3.304,
+ 3.369, 3.146, 2.873, 2.415, 1.964, 1.722, 1.471, 1.382, 1.382, 1.466, 1.722, 1.964, 2.408, 2.871, 3.167, 3.401,
+ 3.524, 3.253, 3.025, 2.691, 2.275, 1.939, 1.657, 1.628, 1.628, 1.654, 1.936, 2.275, 2.687, 3.029, 3.284, 3.574
+ ],
+ "sigma": 0.00195,
+ "sigma_Cb": 0.00241
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2868,
+ "ccm":
+ [
+ 1.58923, -0.36649, -0.22273,
+ -0.43591, 1.84858, -0.41268,
+ 0.02948, -0.77666, 1.74718
+ ]
+ },
+ {
+ "ct": 2965,
+ "ccm":
+ [
+ 1.73397, -0.42794, -0.30603,
+ -0.36504, 1.72431, -0.35926,
+ 0.12765, -1.10933, 1.98168
+ ]
+ },
+ {
+ "ct": 3603,
+ "ccm":
+ [
+ 1.61787, -0.42704, -0.19084,
+ -0.37819, 1.74588, -0.36769,
+ 0.00961, -0.59807, 1.58847
+ ]
+ },
+ {
+ "ct": 4620,
+ "ccm":
+ [
+ 1.55581, -0.35422, -0.20158,
+ -0.31805, 1.79309, -0.47505,
+ -0.01256, -0.54489, 1.55746
+ ]
+ },
+ {
+ "ct": 5901,
+ "ccm":
+ [
+ 1.64439, -0.48855, -0.15585,
+ -0.29149, 1.67122, -0.37972,
+ -0.03111, -0.44052, 1.47163
+ ]
+ },
+ {
+ "ct": 7610,
+ "ccm":
+ [
+ 1.48667, -0.26072, -0.22595,
+ -0.21815, 1.86724, -0.64909,
+ -0.00985, -0.64485, 1.65471
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.af":
+ {
+ "ranges":
+ {
+ "normal":
+ {
+ "min": 0.0,
+ "max": 12.0,
+ "default": 1.0
+ },
+ "macro":
+ {
+ "min": 4.0,
+ "max": 32.0,
+ "default": 6.0
+ }
+ },
+ "speeds":
+ {
+ "normal":
+ {
+ "step_coarse": 2.0,
+ "step_fine": 0.5,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.03,
+ "pdaf_squelch": 0.2,
+ "max_slew": 4.0,
+ "pdaf_frames": 20,
+ "dropout_frames": 6,
+ "step_frames": 4
+ },
+ "fast":
+ {
+ "step_coarse": 2.0,
+ "step_fine": 0.5,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.05,
+ "pdaf_squelch": 0.2,
+ "max_slew": 5.0,
+ "pdaf_frames": 16,
+ "dropout_frames": 6,
+ "step_frames": 4
+ }
+ },
+ "conf_epsilon": 8,
+ "conf_thresh": 12,
+ "conf_clip": 512,
+ "skip_frames": 5,
+ "map": [ 0.0, 420, 35.0, 920 ]
+ }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/imx708_wide_noir.json b/src/ipa/rpi/vc4/data/imx708_wide_noir.json
new file mode 100644
index 00000000..f12ddbb6
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx708_wide_noir.json
@@ -0,0 +1,648 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 9989,
+ "reference_gain": 1.23,
+ "reference_aperture": 1.0,
+ "reference_lux": 980,
+ "reference_Y": 8345
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 16.0,
+ "reference_slope": 4.0
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 215,
+ "slope": 0.00287
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 0,
+ "ct_curve":
+ [
+ 2750.0, 0.7881, 0.2849,
+ 2940.0, 0.7559, 0.3103,
+ 3650.0, 0.6291, 0.4206,
+ 4625.0, 0.5336, 0.5161,
+ 5715.0, 0.4668, 0.5898
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.01165,
+ "transverse_neg": 0.01601
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.2,
+ 1000, 0.2
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "startup_frames": 5,
+ "convergence_frames": 6,
+ "speed": 0.15
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.529, 1.526, 1.522, 1.506, 1.489, 1.473, 1.458, 1.456, 1.456, 1.458, 1.474, 1.493, 1.513, 1.531, 1.541, 1.544,
+ 1.527, 1.523, 1.511, 1.491, 1.474, 1.459, 1.445, 1.441, 1.441, 1.446, 1.461, 1.479, 1.499, 1.521, 1.536, 1.541,
+ 1.524, 1.515, 1.498, 1.477, 1.459, 1.444, 1.431, 1.426, 1.426, 1.435, 1.446, 1.466, 1.487, 1.507, 1.528, 1.538,
+ 1.522, 1.512, 1.491, 1.468, 1.447, 1.431, 1.423, 1.417, 1.418, 1.425, 1.435, 1.455, 1.479, 1.499, 1.523, 1.537,
+ 1.522, 1.509, 1.485, 1.463, 1.441, 1.423, 1.416, 1.413, 1.415, 1.418, 1.429, 1.449, 1.473, 1.495, 1.521, 1.538,
+ 1.522, 1.508, 1.483, 1.461, 1.438, 1.421, 1.413, 1.412, 1.412, 1.415, 1.428, 1.447, 1.471, 1.493, 1.519, 1.538,
+ 1.522, 1.509, 1.484, 1.462, 1.439, 1.421, 1.414, 1.411, 1.412, 1.416, 1.428, 1.447, 1.471, 1.493, 1.519, 1.537,
+ 1.523, 1.511, 1.487, 1.465, 1.443, 1.424, 1.417, 1.413, 1.415, 1.419, 1.429, 1.451, 1.473, 1.494, 1.519, 1.536,
+ 1.524, 1.514, 1.493, 1.471, 1.451, 1.434, 1.424, 1.419, 1.419, 1.428, 1.437, 1.457, 1.477, 1.498, 1.521, 1.538,
+ 1.527, 1.521, 1.503, 1.481, 1.462, 1.449, 1.434, 1.429, 1.429, 1.437, 1.451, 1.469, 1.488, 1.508, 1.527, 1.539,
+ 1.529, 1.527, 1.515, 1.495, 1.477, 1.462, 1.449, 1.444, 1.444, 1.451, 1.467, 1.481, 1.499, 1.519, 1.535, 1.543,
+ 1.534, 1.531, 1.527, 1.512, 1.492, 1.476, 1.463, 1.461, 1.461, 1.464, 1.479, 1.495, 1.515, 1.533, 1.543, 1.546
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 2.603, 2.599, 2.591, 2.567, 2.539, 2.515, 2.489, 2.489, 2.489, 2.491, 2.516, 2.543, 2.574, 2.597, 2.614, 2.617,
+ 2.596, 2.591, 2.571, 2.542, 2.516, 2.489, 2.464, 2.458, 2.458, 2.469, 2.492, 2.518, 2.547, 2.576, 2.602, 2.614,
+ 2.591, 2.576, 2.546, 2.519, 2.489, 2.464, 2.437, 2.427, 2.427, 2.441, 2.467, 2.492, 2.525, 2.553, 2.586, 2.605,
+ 2.588, 2.568, 2.534, 2.503, 2.472, 2.437, 2.423, 2.409, 2.411, 2.425, 2.441, 2.475, 2.513, 2.541, 2.577, 2.602,
+ 2.588, 2.565, 2.527, 2.494, 2.461, 2.425, 2.409, 2.399, 2.403, 2.409, 2.431, 2.466, 2.503, 2.534, 2.571, 2.601,
+ 2.586, 2.561, 2.525, 2.491, 2.454, 2.418, 2.399, 2.396, 2.395, 2.402, 2.424, 2.461, 2.501, 2.531, 2.567, 2.599,
+ 2.583, 2.559, 2.525, 2.491, 2.454, 2.418, 2.398, 2.393, 2.393, 2.401, 2.423, 2.459, 2.498, 2.531, 2.566, 2.597,
+ 2.583, 2.559, 2.526, 2.494, 2.458, 2.421, 2.404, 2.397, 2.399, 2.404, 2.426, 2.461, 2.501, 2.531, 2.566, 2.596,
+ 2.583, 2.563, 2.531, 2.501, 2.469, 2.435, 2.419, 2.405, 2.404, 2.422, 2.435, 2.471, 2.505, 2.537, 2.572, 2.596,
+ 2.585, 2.571, 2.539, 2.516, 2.486, 2.458, 2.435, 2.424, 2.424, 2.435, 2.459, 2.489, 2.521, 2.546, 2.579, 2.601,
+ 2.589, 2.578, 2.557, 2.532, 2.506, 2.483, 2.458, 2.449, 2.449, 2.459, 2.485, 2.507, 2.535, 2.563, 2.591, 2.605,
+ 2.589, 2.586, 2.575, 2.551, 2.525, 2.503, 2.481, 2.476, 2.476, 2.481, 2.504, 2.526, 2.555, 2.583, 2.604, 2.611
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 3.311, 3.339, 3.369, 3.374, 3.371, 3.363, 3.356, 3.353, 3.353, 3.353, 3.357, 3.362, 3.362, 3.356, 3.328, 3.311,
+ 3.321, 3.354, 3.374, 3.374, 3.368, 3.359, 3.352, 3.349, 3.347, 3.347, 3.349, 3.357, 3.361, 3.359, 3.343, 3.324,
+ 3.334, 3.368, 3.375, 3.374, 3.365, 3.356, 3.349, 3.347, 3.346, 3.346, 3.347, 3.349, 3.358, 3.361, 3.357, 3.336,
+ 3.346, 3.378, 3.378, 3.369, 3.363, 3.358, 3.351, 3.348, 3.347, 3.346, 3.347, 3.348, 3.354, 3.364, 3.363, 3.345,
+ 3.351, 3.381, 3.381, 3.368, 3.361, 3.357, 3.349, 3.347, 3.347, 3.345, 3.345, 3.347, 3.353, 3.364, 3.364, 3.347,
+ 3.353, 3.379, 3.379, 3.366, 3.359, 3.351, 3.348, 3.343, 3.342, 3.342, 3.343, 3.345, 3.351, 3.363, 3.363, 3.347,
+ 3.353, 3.376, 3.376, 3.363, 3.351, 3.347, 3.343, 3.338, 3.336, 3.338, 3.339, 3.343, 3.351, 3.361, 3.361, 3.347,
+ 3.351, 3.374, 3.374, 3.359, 3.351, 3.345, 3.338, 3.334, 3.333, 3.334, 3.336, 3.339, 3.347, 3.358, 3.358, 3.345,
+ 3.346, 3.368, 3.368, 3.359, 3.349, 3.343, 3.336, 3.332, 3.327, 3.331, 3.333, 3.337, 3.346, 3.356, 3.356, 3.341,
+ 3.336, 3.362, 3.364, 3.359, 3.351, 3.342, 3.334, 3.324, 3.324, 3.325, 3.329, 3.336, 3.346, 3.351, 3.351, 3.333,
+ 3.324, 3.349, 3.359, 3.358, 3.352, 3.341, 3.329, 3.323, 3.321, 3.322, 3.326, 3.336, 3.346, 3.347, 3.339, 3.319,
+ 3.311, 3.328, 3.352, 3.354, 3.352, 3.341, 3.329, 3.321, 3.319, 3.321, 3.324, 3.338, 3.343, 3.343, 3.319, 3.312
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.634, 1.647, 1.665, 1.668, 1.668, 1.664, 1.662, 1.662, 1.661, 1.661, 1.661, 1.663, 1.663, 1.659, 1.643, 1.636,
+ 1.639, 1.656, 1.668, 1.669, 1.668, 1.666, 1.664, 1.663, 1.663, 1.661, 1.661, 1.662, 1.663, 1.662, 1.654, 1.642,
+ 1.645, 1.663, 1.669, 1.668, 1.667, 1.667, 1.667, 1.668, 1.668, 1.665, 1.662, 1.661, 1.662, 1.664, 1.661, 1.649,
+ 1.651, 1.669, 1.669, 1.667, 1.666, 1.668, 1.669, 1.672, 1.672, 1.668, 1.665, 1.661, 1.661, 1.665, 1.665, 1.655,
+ 1.654, 1.669, 1.669, 1.666, 1.666, 1.669, 1.672, 1.673, 1.673, 1.671, 1.666, 1.661, 1.661, 1.665, 1.665, 1.659,
+ 1.654, 1.669, 1.669, 1.666, 1.666, 1.669, 1.671, 1.673, 1.672, 1.669, 1.667, 1.661, 1.661, 1.665, 1.665, 1.659,
+ 1.654, 1.668, 1.668, 1.664, 1.663, 1.667, 1.669, 1.671, 1.669, 1.668, 1.665, 1.661, 1.661, 1.663, 1.663, 1.659,
+ 1.653, 1.665, 1.665, 1.661, 1.661, 1.664, 1.667, 1.668, 1.668, 1.665, 1.661, 1.658, 1.659, 1.662, 1.662, 1.657,
+ 1.651, 1.664, 1.664, 1.659, 1.659, 1.661, 1.663, 1.663, 1.662, 1.661, 1.658, 1.656, 1.657, 1.662, 1.662, 1.655,
+ 1.645, 1.661, 1.663, 1.661, 1.659, 1.659, 1.659, 1.657, 1.657, 1.656, 1.654, 1.655, 1.656, 1.661, 1.661, 1.649,
+ 1.641, 1.654, 1.661, 1.661, 1.659, 1.657, 1.655, 1.653, 1.652, 1.651, 1.652, 1.653, 1.657, 1.658, 1.655, 1.644,
+ 1.635, 1.645, 1.661, 1.661, 1.661, 1.655, 1.653, 1.649, 1.648, 1.647, 1.651, 1.653, 1.657, 1.657, 1.646, 1.638
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 3.535, 3.279, 3.049, 2.722, 2.305, 1.958, 1.657, 1.647, 1.647, 1.656, 1.953, 2.289, 2.707, 3.058, 3.325, 3.589,
+ 3.379, 3.157, 2.874, 2.421, 1.973, 1.735, 1.472, 1.388, 1.388, 1.471, 1.724, 1.963, 2.409, 2.877, 3.185, 3.416,
+ 3.288, 3.075, 2.696, 2.169, 1.735, 1.472, 1.311, 1.208, 1.208, 1.306, 1.471, 1.724, 2.159, 2.695, 3.092, 3.321,
+ 3.238, 3.001, 2.534, 1.981, 1.572, 1.311, 1.207, 1.082, 1.082, 1.204, 1.306, 1.563, 1.973, 2.529, 3.008, 3.259,
+ 3.211, 2.938, 2.414, 1.859, 1.468, 1.221, 1.082, 1.036, 1.031, 1.079, 1.217, 1.463, 1.851, 2.403, 2.931, 3.229,
+ 3.206, 2.904, 2.356, 1.802, 1.421, 1.181, 1.037, 1.002, 1.002, 1.032, 1.175, 1.414, 1.793, 2.343, 2.899, 3.223,
+ 3.206, 2.904, 2.356, 1.802, 1.421, 1.181, 1.037, 1.005, 1.005, 1.032, 1.175, 1.414, 1.793, 2.343, 2.899, 3.223,
+ 3.211, 2.936, 2.417, 1.858, 1.468, 1.222, 1.083, 1.037, 1.032, 1.083, 1.218, 1.463, 1.848, 2.403, 2.932, 3.226,
+ 3.234, 2.997, 2.536, 1.979, 1.569, 1.311, 1.206, 1.084, 1.084, 1.204, 1.305, 1.565, 1.966, 2.524, 2.996, 3.251,
+ 3.282, 3.069, 2.697, 2.166, 1.731, 1.471, 1.311, 1.207, 1.207, 1.305, 1.466, 1.729, 2.158, 2.689, 3.077, 3.304,
+ 3.369, 3.146, 2.873, 2.415, 1.964, 1.722, 1.471, 1.382, 1.382, 1.466, 1.722, 1.964, 2.408, 2.871, 3.167, 3.401,
+ 3.524, 3.253, 3.025, 2.691, 2.275, 1.939, 1.657, 1.628, 1.628, 1.654, 1.936, 2.275, 2.687, 3.029, 3.284, 3.574
+ ],
+ "sigma": 0.00195,
+ "sigma_Cb": 0.00241
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2750,
+ "ccm":
+ [
+ 1.13004, 0.36392, -0.49396,
+ -0.45885, 1.68171, -0.22286,
+ -0.06473, -0.86962, 1.93435
+ ]
+ },
+ {
+ "ct": 2940,
+ "ccm":
+ [
+ 1.29876, 0.09627, -0.39503,
+ -0.43085, 1.60258, -0.17172,
+ -0.02638, -0.92581, 1.95218
+ ]
+ },
+ {
+ "ct": 3650,
+ "ccm":
+ [
+ 1.57729, -0.29734, -0.27995,
+ -0.42965, 1.66231, -0.23265,
+ -0.02183, -0.62331, 1.64514
+ ]
+ },
+ {
+ "ct": 4625,
+ "ccm":
+ [
+ 1.52145, -0.22382, -0.29763,
+ -0.40445, 1.82186, -0.41742,
+ -0.05732, -0.56222, 1.61954
+ ]
+ },
+ {
+ "ct": 5715,
+ "ccm":
+ [
+ 1.67851, -0.39193, -0.28658,
+ -0.37169, 1.72949, -0.35781,
+ -0.09556, -0.41951, 1.51508
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.af":
+ {
+ "ranges":
+ {
+ "normal":
+ {
+ "min": 0.0,
+ "max": 12.0,
+ "default": 1.0
+ },
+ "macro":
+ {
+ "min": 4.0,
+ "max": 32.0,
+ "default": 6.0
+ }
+ },
+ "speeds":
+ {
+ "normal":
+ {
+ "step_coarse": 2.0,
+ "step_fine": 0.5,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.03,
+ "pdaf_squelch": 0.2,
+ "max_slew": 4.0,
+ "pdaf_frames": 20,
+ "dropout_frames": 6,
+ "step_frames": 4
+ },
+ "fast":
+ {
+ "step_coarse": 2.0,
+ "step_fine": 0.5,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.05,
+ "pdaf_squelch": 0.2,
+ "max_slew": 5.0,
+ "pdaf_frames": 16,
+ "dropout_frames": 6,
+ "step_frames": 4
+ }
+ },
+ "conf_epsilon": 8,
+ "conf_thresh": 12,
+ "conf_clip": 512,
+ "skip_frames": 5,
+ "map": [ 0.0, 420, 35.0, 920 ]
+ }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/raspberrypi/data/meson.build b/src/ipa/rpi/vc4/data/meson.build
index 211811cf..afbf875a 100644
--- a/src/ipa/raspberrypi/data/meson.build
+++ b/src/ipa/rpi/vc4/data/meson.build
@@ -5,16 +5,24 @@ conf_files = files([
'imx219_noir.json',
'imx290.json',
'imx296.json',
+ 'imx296_mono.json',
'imx378.json',
'imx477.json',
'imx477_noir.json',
+ 'imx477_scientific.json',
'imx519.json',
+ 'imx708.json',
+ 'imx708_noir.json',
+ 'imx708_wide.json',
+ 'imx708_wide_noir.json',
'ov5647.json',
'ov5647_noir.json',
- 'ov9281.json',
+ 'ov64a40.json',
+ 'ov9281_mono.json',
'se327m12.json',
'uncalibrated.json',
])
install_data(conf_files,
- install_dir : ipa_data_dir / 'raspberrypi')
+ install_dir : ipa_data_dir / 'rpi' / 'vc4',
+ install_tag : 'runtime')
diff --git a/src/ipa/rpi/vc4/data/ov5647.json b/src/ipa/rpi/vc4/data/ov5647.json
new file mode 100644
index 00000000..4def9ffc
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/ov5647.json
@@ -0,0 +1,673 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 1024
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 21663,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 987,
+ "reference_Y": 8961
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 4.25
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 401,
+ "slope": 0.05619
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2500.0, 1.0289, 0.4503,
+ 2803.0, 0.9428, 0.5108,
+ 2914.0, 0.9406, 0.5127,
+ 3605.0, 0.8261, 0.6249,
+ 4540.0, 0.7331, 0.7533,
+ 5699.0, 0.6715, 0.8627,
+ 8625.0, 0.6081, 1.0012
+ ],
+ "sensitivity_r": 1.05,
+ "sensitivity_b": 1.05,
+ "transverse_pos": 0.0321,
+ "transverse_neg": 0.04313
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "channels":
+ [
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "base_ev": 1.25
+ },
+ {
+ "base_ev": 0.125,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "base_ev": 1.25
+ },
+ {
+ "base_ev": 1.5,
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "base_ev": 1.25
+ }
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.105, 1.103, 1.093, 1.083, 1.071, 1.065, 1.065, 1.065, 1.066, 1.069, 1.072, 1.077, 1.084, 1.089, 1.093, 1.093,
+ 1.103, 1.096, 1.084, 1.072, 1.059, 1.051, 1.047, 1.047, 1.051, 1.053, 1.059, 1.067, 1.075, 1.082, 1.085, 1.086,
+ 1.096, 1.084, 1.072, 1.059, 1.051, 1.045, 1.039, 1.038, 1.039, 1.045, 1.049, 1.057, 1.063, 1.072, 1.081, 1.082,
+ 1.092, 1.075, 1.061, 1.052, 1.045, 1.039, 1.036, 1.035, 1.035, 1.039, 1.044, 1.049, 1.056, 1.063, 1.072, 1.081,
+ 1.092, 1.073, 1.058, 1.048, 1.043, 1.038, 1.035, 1.033, 1.033, 1.035, 1.039, 1.044, 1.051, 1.057, 1.069, 1.078,
+ 1.091, 1.068, 1.054, 1.045, 1.041, 1.038, 1.035, 1.032, 1.032, 1.032, 1.036, 1.041, 1.045, 1.055, 1.069, 1.078,
+ 1.091, 1.068, 1.052, 1.043, 1.041, 1.038, 1.035, 1.032, 1.031, 1.032, 1.034, 1.036, 1.043, 1.055, 1.069, 1.078,
+ 1.092, 1.068, 1.052, 1.047, 1.042, 1.041, 1.038, 1.035, 1.032, 1.032, 1.035, 1.039, 1.043, 1.055, 1.071, 1.079,
+ 1.092, 1.073, 1.057, 1.051, 1.047, 1.047, 1.044, 1.041, 1.038, 1.038, 1.039, 1.043, 1.051, 1.059, 1.076, 1.083,
+ 1.092, 1.081, 1.068, 1.058, 1.056, 1.056, 1.053, 1.052, 1.049, 1.048, 1.048, 1.051, 1.059, 1.066, 1.083, 1.085,
+ 1.091, 1.087, 1.081, 1.068, 1.065, 1.064, 1.062, 1.062, 1.061, 1.056, 1.056, 1.056, 1.064, 1.069, 1.084, 1.089,
+ 1.091, 1.089, 1.085, 1.079, 1.069, 1.068, 1.067, 1.067, 1.067, 1.063, 1.061, 1.063, 1.068, 1.069, 1.081, 1.092
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.486, 1.484, 1.468, 1.449, 1.427, 1.403, 1.399, 1.399, 1.399, 1.404, 1.413, 1.433, 1.454, 1.473, 1.482, 1.488,
+ 1.484, 1.472, 1.454, 1.431, 1.405, 1.381, 1.365, 1.365, 1.367, 1.373, 1.392, 1.411, 1.438, 1.458, 1.476, 1.481,
+ 1.476, 1.458, 1.433, 1.405, 1.381, 1.361, 1.339, 1.334, 1.334, 1.346, 1.362, 1.391, 1.411, 1.438, 1.462, 1.474,
+ 1.471, 1.443, 1.417, 1.388, 1.361, 1.339, 1.321, 1.313, 1.313, 1.327, 1.346, 1.362, 1.391, 1.422, 1.453, 1.473,
+ 1.469, 1.439, 1.408, 1.377, 1.349, 1.321, 1.312, 1.299, 1.299, 1.311, 1.327, 1.348, 1.378, 1.415, 1.446, 1.468,
+ 1.468, 1.434, 1.402, 1.371, 1.341, 1.316, 1.299, 1.296, 1.295, 1.299, 1.314, 1.338, 1.371, 1.408, 1.441, 1.466,
+ 1.468, 1.434, 1.401, 1.371, 1.341, 1.316, 1.301, 1.296, 1.295, 1.297, 1.314, 1.338, 1.369, 1.408, 1.441, 1.465,
+ 1.469, 1.436, 1.401, 1.374, 1.348, 1.332, 1.315, 1.301, 1.301, 1.313, 1.324, 1.342, 1.372, 1.409, 1.442, 1.465,
+ 1.471, 1.444, 1.413, 1.388, 1.371, 1.348, 1.332, 1.323, 1.323, 1.324, 1.342, 1.362, 1.386, 1.418, 1.449, 1.467,
+ 1.473, 1.454, 1.431, 1.407, 1.388, 1.371, 1.359, 1.352, 1.351, 1.351, 1.362, 1.383, 1.404, 1.433, 1.462, 1.472,
+ 1.474, 1.461, 1.447, 1.424, 1.407, 1.394, 1.385, 1.381, 1.379, 1.381, 1.383, 1.401, 1.419, 1.444, 1.466, 1.481,
+ 1.474, 1.464, 1.455, 1.442, 1.421, 1.408, 1.403, 1.403, 1.403, 1.399, 1.402, 1.415, 1.432, 1.446, 1.467, 1.483
+ ]
+ },
+ {
+ "ct": 6500,
+ "table":
+ [
+ 1.567, 1.565, 1.555, 1.541, 1.525, 1.518, 1.518, 1.518, 1.521, 1.527, 1.532, 1.541, 1.551, 1.559, 1.567, 1.569,
+ 1.565, 1.557, 1.542, 1.527, 1.519, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.533, 1.542, 1.553, 1.559, 1.562,
+ 1.561, 1.546, 1.532, 1.521, 1.518, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.529, 1.533, 1.542, 1.554, 1.559,
+ 1.561, 1.539, 1.526, 1.524, 1.521, 1.521, 1.522, 1.524, 1.525, 1.531, 1.529, 1.529, 1.531, 1.538, 1.549, 1.558,
+ 1.559, 1.538, 1.526, 1.525, 1.524, 1.528, 1.534, 1.536, 1.536, 1.536, 1.532, 1.529, 1.531, 1.537, 1.548, 1.556,
+ 1.561, 1.537, 1.525, 1.524, 1.526, 1.532, 1.537, 1.539, 1.538, 1.537, 1.532, 1.529, 1.529, 1.537, 1.546, 1.556,
+ 1.561, 1.536, 1.524, 1.522, 1.525, 1.532, 1.538, 1.538, 1.537, 1.533, 1.528, 1.526, 1.527, 1.536, 1.546, 1.555,
+ 1.561, 1.537, 1.522, 1.521, 1.524, 1.531, 1.536, 1.537, 1.534, 1.529, 1.526, 1.522, 1.523, 1.534, 1.547, 1.555,
+ 1.561, 1.538, 1.524, 1.522, 1.526, 1.531, 1.535, 1.535, 1.534, 1.527, 1.524, 1.522, 1.522, 1.535, 1.549, 1.556,
+ 1.558, 1.543, 1.532, 1.526, 1.526, 1.529, 1.534, 1.535, 1.533, 1.526, 1.523, 1.522, 1.524, 1.537, 1.552, 1.557,
+ 1.555, 1.546, 1.541, 1.528, 1.527, 1.528, 1.531, 1.533, 1.531, 1.527, 1.522, 1.522, 1.526, 1.536, 1.552, 1.561,
+ 1.555, 1.547, 1.542, 1.538, 1.526, 1.526, 1.529, 1.531, 1.529, 1.528, 1.519, 1.519, 1.527, 1.531, 1.543, 1.561
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.684, 1.688, 1.691, 1.697, 1.709, 1.722, 1.735, 1.745, 1.747, 1.745, 1.731, 1.719, 1.709, 1.705, 1.699, 1.699,
+ 1.684, 1.689, 1.694, 1.708, 1.721, 1.735, 1.747, 1.762, 1.762, 1.758, 1.745, 1.727, 1.716, 1.707, 1.701, 1.699,
+ 1.684, 1.691, 1.704, 1.719, 1.734, 1.755, 1.772, 1.786, 1.789, 1.788, 1.762, 1.745, 1.724, 1.709, 1.702, 1.698,
+ 1.682, 1.694, 1.709, 1.729, 1.755, 1.773, 1.798, 1.815, 1.817, 1.808, 1.788, 1.762, 1.733, 1.714, 1.704, 1.699,
+ 1.682, 1.693, 1.713, 1.742, 1.772, 1.798, 1.815, 1.829, 1.831, 1.821, 1.807, 1.773, 1.742, 1.716, 1.703, 1.699,
+ 1.681, 1.693, 1.713, 1.742, 1.772, 1.799, 1.828, 1.839, 1.839, 1.828, 1.807, 1.774, 1.742, 1.715, 1.699, 1.695,
+ 1.679, 1.691, 1.712, 1.739, 1.771, 1.798, 1.825, 1.829, 1.831, 1.818, 1.801, 1.774, 1.738, 1.712, 1.695, 1.691,
+ 1.676, 1.685, 1.703, 1.727, 1.761, 1.784, 1.801, 1.817, 1.817, 1.801, 1.779, 1.761, 1.729, 1.706, 1.691, 1.684,
+ 1.669, 1.678, 1.692, 1.714, 1.741, 1.764, 1.784, 1.795, 1.795, 1.779, 1.761, 1.738, 1.713, 1.696, 1.683, 1.679,
+ 1.664, 1.671, 1.679, 1.693, 1.716, 1.741, 1.762, 1.769, 1.769, 1.753, 1.738, 1.713, 1.701, 1.687, 1.681, 1.676,
+ 1.661, 1.664, 1.671, 1.679, 1.693, 1.714, 1.732, 1.739, 1.739, 1.729, 1.708, 1.701, 1.685, 1.679, 1.676, 1.677,
+ 1.659, 1.661, 1.664, 1.671, 1.679, 1.693, 1.712, 1.714, 1.714, 1.708, 1.701, 1.687, 1.679, 1.672, 1.673, 1.677
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.177, 1.183, 1.187, 1.191, 1.197, 1.206, 1.213, 1.215, 1.215, 1.215, 1.211, 1.204, 1.196, 1.191, 1.183, 1.182,
+ 1.179, 1.185, 1.191, 1.196, 1.206, 1.217, 1.224, 1.229, 1.229, 1.226, 1.221, 1.212, 1.202, 1.195, 1.188, 1.182,
+ 1.183, 1.191, 1.196, 1.206, 1.217, 1.229, 1.239, 1.245, 1.245, 1.245, 1.233, 1.221, 1.212, 1.199, 1.193, 1.187,
+ 1.183, 1.192, 1.201, 1.212, 1.229, 1.241, 1.252, 1.259, 1.259, 1.257, 1.245, 1.233, 1.217, 1.201, 1.194, 1.192,
+ 1.183, 1.192, 1.202, 1.219, 1.238, 1.252, 1.261, 1.269, 1.268, 1.261, 1.257, 1.241, 1.223, 1.204, 1.194, 1.191,
+ 1.182, 1.192, 1.202, 1.219, 1.239, 1.255, 1.266, 1.271, 1.271, 1.265, 1.258, 1.242, 1.223, 1.205, 1.192, 1.191,
+ 1.181, 1.189, 1.199, 1.218, 1.239, 1.254, 1.262, 1.268, 1.268, 1.258, 1.253, 1.241, 1.221, 1.204, 1.191, 1.187,
+ 1.179, 1.184, 1.193, 1.211, 1.232, 1.243, 1.254, 1.257, 1.256, 1.253, 1.242, 1.232, 1.216, 1.199, 1.187, 1.183,
+ 1.174, 1.179, 1.187, 1.202, 1.218, 1.232, 1.243, 1.246, 1.246, 1.239, 1.232, 1.218, 1.207, 1.191, 1.183, 1.179,
+ 1.169, 1.175, 1.181, 1.189, 1.202, 1.218, 1.229, 1.232, 1.232, 1.224, 1.218, 1.207, 1.199, 1.185, 1.181, 1.174,
+ 1.164, 1.168, 1.175, 1.179, 1.189, 1.201, 1.209, 1.213, 1.213, 1.209, 1.201, 1.198, 1.186, 1.181, 1.174, 1.173,
+ 1.161, 1.166, 1.171, 1.175, 1.179, 1.189, 1.197, 1.198, 1.198, 1.197, 1.196, 1.186, 1.182, 1.175, 1.173, 1.173
+ ]
+ },
+ {
+ "ct": 6500,
+ "table":
+ [
+ 1.166, 1.171, 1.173, 1.178, 1.187, 1.193, 1.201, 1.205, 1.205, 1.205, 1.199, 1.191, 1.184, 1.179, 1.174, 1.171,
+ 1.166, 1.172, 1.176, 1.184, 1.195, 1.202, 1.209, 1.216, 1.216, 1.213, 1.208, 1.201, 1.189, 1.182, 1.176, 1.171,
+ 1.166, 1.173, 1.183, 1.195, 1.202, 1.214, 1.221, 1.228, 1.229, 1.228, 1.221, 1.209, 1.201, 1.186, 1.179, 1.174,
+ 1.165, 1.174, 1.187, 1.201, 1.214, 1.223, 1.235, 1.241, 1.242, 1.241, 1.229, 1.221, 1.205, 1.188, 1.181, 1.177,
+ 1.165, 1.174, 1.189, 1.207, 1.223, 1.235, 1.242, 1.253, 1.252, 1.245, 1.241, 1.228, 1.211, 1.189, 1.181, 1.178,
+ 1.164, 1.173, 1.189, 1.207, 1.224, 1.238, 1.249, 1.255, 1.255, 1.249, 1.242, 1.228, 1.211, 1.191, 1.179, 1.176,
+ 1.163, 1.172, 1.187, 1.207, 1.223, 1.237, 1.245, 1.253, 1.252, 1.243, 1.237, 1.228, 1.207, 1.188, 1.176, 1.173,
+ 1.159, 1.167, 1.179, 1.199, 1.217, 1.227, 1.237, 1.241, 1.241, 1.237, 1.228, 1.217, 1.201, 1.184, 1.174, 1.169,
+ 1.156, 1.164, 1.172, 1.189, 1.205, 1.217, 1.226, 1.229, 1.229, 1.222, 1.217, 1.204, 1.192, 1.177, 1.171, 1.166,
+ 1.154, 1.159, 1.166, 1.177, 1.189, 1.205, 1.213, 1.216, 1.216, 1.209, 1.204, 1.192, 1.183, 1.172, 1.168, 1.162,
+ 1.152, 1.155, 1.161, 1.166, 1.177, 1.188, 1.195, 1.198, 1.199, 1.196, 1.187, 1.183, 1.173, 1.168, 1.163, 1.162,
+ 1.151, 1.154, 1.158, 1.162, 1.168, 1.177, 1.183, 1.184, 1.184, 1.184, 1.182, 1.172, 1.168, 1.165, 1.162, 1.161
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.236, 2.111, 1.912, 1.741, 1.579, 1.451, 1.379, 1.349, 1.349, 1.361, 1.411, 1.505, 1.644, 1.816, 2.034, 2.159,
+ 2.139, 1.994, 1.796, 1.625, 1.467, 1.361, 1.285, 1.248, 1.239, 1.265, 1.321, 1.408, 1.536, 1.703, 1.903, 2.087,
+ 2.047, 1.898, 1.694, 1.511, 1.373, 1.254, 1.186, 1.152, 1.142, 1.166, 1.226, 1.309, 1.441, 1.598, 1.799, 1.978,
+ 1.999, 1.824, 1.615, 1.429, 1.281, 1.179, 1.113, 1.077, 1.071, 1.096, 1.153, 1.239, 1.357, 1.525, 1.726, 1.915,
+ 1.976, 1.773, 1.563, 1.374, 1.222, 1.119, 1.064, 1.032, 1.031, 1.049, 1.099, 1.188, 1.309, 1.478, 1.681, 1.893,
+ 1.973, 1.756, 1.542, 1.351, 1.196, 1.088, 1.028, 1.011, 1.004, 1.029, 1.077, 1.169, 1.295, 1.459, 1.663, 1.891,
+ 1.973, 1.761, 1.541, 1.349, 1.193, 1.087, 1.031, 1.006, 1.006, 1.023, 1.075, 1.169, 1.298, 1.463, 1.667, 1.891,
+ 1.982, 1.789, 1.568, 1.373, 1.213, 1.111, 1.051, 1.029, 1.024, 1.053, 1.106, 1.199, 1.329, 1.495, 1.692, 1.903,
+ 2.015, 1.838, 1.621, 1.426, 1.268, 1.159, 1.101, 1.066, 1.068, 1.099, 1.166, 1.259, 1.387, 1.553, 1.751, 1.937,
+ 2.076, 1.911, 1.692, 1.507, 1.346, 1.236, 1.169, 1.136, 1.139, 1.174, 1.242, 1.349, 1.475, 1.641, 1.833, 2.004,
+ 2.193, 2.011, 1.798, 1.604, 1.444, 1.339, 1.265, 1.235, 1.237, 1.273, 1.351, 1.461, 1.598, 1.758, 1.956, 2.125,
+ 2.263, 2.154, 1.916, 1.711, 1.549, 1.432, 1.372, 1.356, 1.356, 1.383, 1.455, 1.578, 1.726, 1.914, 2.119, 2.211
+ ],
+ "sigma": 0.006,
+ "sigma_Cb": 0.00208
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2873,
+ "ccm":
+ [
+ 1.88195, -0.26249, -0.61946,
+ -0.63842, 2.11535, -0.47693,
+ -0.13531, -0.99739, 2.13271
+ ]
+ },
+ {
+ "ct": 2965,
+ "ccm":
+ [
+ 2.15048, -0.51859, -0.63189,
+ -0.53572, 1.92585, -0.39013,
+ 0.01831, -1.48576, 2.46744
+ ]
+ },
+ {
+ "ct": 3606,
+ "ccm":
+ [
+ 1.97522, -0.43847, -0.53675,
+ -0.56151, 1.99765, -0.43614,
+ -0.12438, -0.77056, 1.89493
+ ]
+ },
+ {
+ "ct": 4700,
+ "ccm":
+ [
+ 2.00971, -0.51461, -0.49511,
+ -0.52109, 2.01003, -0.48894,
+ -0.09527, -0.67318, 1.76845
+ ]
+ },
+ {
+ "ct": 5890,
+ "ccm":
+ [
+ 2.13616, -0.65283, -0.48333,
+ -0.48364, 1.93115, -0.44751,
+ -0.13465, -0.54831, 1.68295
+ ]
+ },
+ {
+ "ct": 7600,
+ "ccm":
+ [
+ 2.06599, -0.39161, -0.67439,
+ -0.50883, 2.27467, -0.76583,
+ -0.13961, -0.66121, 1.80081
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.hdr":
+ {
+ "MultiExposure":
+ {
+ "cadence": [ 1, 2 ],
+ "channel_map": { "short": 1, "long": 2 }
+ },
+ "SingleExposure":
+ {
+ "cadence": [ 1 ],
+ "channel_map": { "short": 1 }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/ov5647_noir.json b/src/ipa/rpi/vc4/data/ov5647_noir.json
new file mode 100644
index 00000000..a6c6722f
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/ov5647_noir.json
@@ -0,0 +1,403 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 1024
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 21663,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 987,
+ "reference_Y": 8961
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 4.25
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 401,
+ "slope": 0.05619
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "bayes": 0
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 66666 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 33333 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ],
+ "shadows": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.0,
+ "q_hi": 0.5,
+ "y_target":
+ [
+ 0, 0.17,
+ 1000, 0.17
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ],
+ "base_ev": 1.25
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.105, 1.103, 1.093, 1.083, 1.071, 1.065, 1.065, 1.065, 1.066, 1.069, 1.072, 1.077, 1.084, 1.089, 1.093, 1.093,
+ 1.103, 1.096, 1.084, 1.072, 1.059, 1.051, 1.047, 1.047, 1.051, 1.053, 1.059, 1.067, 1.075, 1.082, 1.085, 1.086,
+ 1.096, 1.084, 1.072, 1.059, 1.051, 1.045, 1.039, 1.038, 1.039, 1.045, 1.049, 1.057, 1.063, 1.072, 1.081, 1.082,
+ 1.092, 1.075, 1.061, 1.052, 1.045, 1.039, 1.036, 1.035, 1.035, 1.039, 1.044, 1.049, 1.056, 1.063, 1.072, 1.081,
+ 1.092, 1.073, 1.058, 1.048, 1.043, 1.038, 1.035, 1.033, 1.033, 1.035, 1.039, 1.044, 1.051, 1.057, 1.069, 1.078,
+ 1.091, 1.068, 1.054, 1.045, 1.041, 1.038, 1.035, 1.032, 1.032, 1.032, 1.036, 1.041, 1.045, 1.055, 1.069, 1.078,
+ 1.091, 1.068, 1.052, 1.043, 1.041, 1.038, 1.035, 1.032, 1.031, 1.032, 1.034, 1.036, 1.043, 1.055, 1.069, 1.078,
+ 1.092, 1.068, 1.052, 1.047, 1.042, 1.041, 1.038, 1.035, 1.032, 1.032, 1.035, 1.039, 1.043, 1.055, 1.071, 1.079,
+ 1.092, 1.073, 1.057, 1.051, 1.047, 1.047, 1.044, 1.041, 1.038, 1.038, 1.039, 1.043, 1.051, 1.059, 1.076, 1.083,
+ 1.092, 1.081, 1.068, 1.058, 1.056, 1.056, 1.053, 1.052, 1.049, 1.048, 1.048, 1.051, 1.059, 1.066, 1.083, 1.085,
+ 1.091, 1.087, 1.081, 1.068, 1.065, 1.064, 1.062, 1.062, 1.061, 1.056, 1.056, 1.056, 1.064, 1.069, 1.084, 1.089,
+ 1.091, 1.089, 1.085, 1.079, 1.069, 1.068, 1.067, 1.067, 1.067, 1.063, 1.061, 1.063, 1.068, 1.069, 1.081, 1.092
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.486, 1.484, 1.468, 1.449, 1.427, 1.403, 1.399, 1.399, 1.399, 1.404, 1.413, 1.433, 1.454, 1.473, 1.482, 1.488,
+ 1.484, 1.472, 1.454, 1.431, 1.405, 1.381, 1.365, 1.365, 1.367, 1.373, 1.392, 1.411, 1.438, 1.458, 1.476, 1.481,
+ 1.476, 1.458, 1.433, 1.405, 1.381, 1.361, 1.339, 1.334, 1.334, 1.346, 1.362, 1.391, 1.411, 1.438, 1.462, 1.474,
+ 1.471, 1.443, 1.417, 1.388, 1.361, 1.339, 1.321, 1.313, 1.313, 1.327, 1.346, 1.362, 1.391, 1.422, 1.453, 1.473,
+ 1.469, 1.439, 1.408, 1.377, 1.349, 1.321, 1.312, 1.299, 1.299, 1.311, 1.327, 1.348, 1.378, 1.415, 1.446, 1.468,
+ 1.468, 1.434, 1.402, 1.371, 1.341, 1.316, 1.299, 1.296, 1.295, 1.299, 1.314, 1.338, 1.371, 1.408, 1.441, 1.466,
+ 1.468, 1.434, 1.401, 1.371, 1.341, 1.316, 1.301, 1.296, 1.295, 1.297, 1.314, 1.338, 1.369, 1.408, 1.441, 1.465,
+ 1.469, 1.436, 1.401, 1.374, 1.348, 1.332, 1.315, 1.301, 1.301, 1.313, 1.324, 1.342, 1.372, 1.409, 1.442, 1.465,
+ 1.471, 1.444, 1.413, 1.388, 1.371, 1.348, 1.332, 1.323, 1.323, 1.324, 1.342, 1.362, 1.386, 1.418, 1.449, 1.467,
+ 1.473, 1.454, 1.431, 1.407, 1.388, 1.371, 1.359, 1.352, 1.351, 1.351, 1.362, 1.383, 1.404, 1.433, 1.462, 1.472,
+ 1.474, 1.461, 1.447, 1.424, 1.407, 1.394, 1.385, 1.381, 1.379, 1.381, 1.383, 1.401, 1.419, 1.444, 1.466, 1.481,
+ 1.474, 1.464, 1.455, 1.442, 1.421, 1.408, 1.403, 1.403, 1.403, 1.399, 1.402, 1.415, 1.432, 1.446, 1.467, 1.483
+ ]
+ },
+ {
+ "ct": 6500,
+ "table":
+ [
+ 1.567, 1.565, 1.555, 1.541, 1.525, 1.518, 1.518, 1.518, 1.521, 1.527, 1.532, 1.541, 1.551, 1.559, 1.567, 1.569,
+ 1.565, 1.557, 1.542, 1.527, 1.519, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.533, 1.542, 1.553, 1.559, 1.562,
+ 1.561, 1.546, 1.532, 1.521, 1.518, 1.515, 1.511, 1.516, 1.519, 1.524, 1.528, 1.529, 1.533, 1.542, 1.554, 1.559,
+ 1.561, 1.539, 1.526, 1.524, 1.521, 1.521, 1.522, 1.524, 1.525, 1.531, 1.529, 1.529, 1.531, 1.538, 1.549, 1.558,
+ 1.559, 1.538, 1.526, 1.525, 1.524, 1.528, 1.534, 1.536, 1.536, 1.536, 1.532, 1.529, 1.531, 1.537, 1.548, 1.556,
+ 1.561, 1.537, 1.525, 1.524, 1.526, 1.532, 1.537, 1.539, 1.538, 1.537, 1.532, 1.529, 1.529, 1.537, 1.546, 1.556,
+ 1.561, 1.536, 1.524, 1.522, 1.525, 1.532, 1.538, 1.538, 1.537, 1.533, 1.528, 1.526, 1.527, 1.536, 1.546, 1.555,
+ 1.561, 1.537, 1.522, 1.521, 1.524, 1.531, 1.536, 1.537, 1.534, 1.529, 1.526, 1.522, 1.523, 1.534, 1.547, 1.555,
+ 1.561, 1.538, 1.524, 1.522, 1.526, 1.531, 1.535, 1.535, 1.534, 1.527, 1.524, 1.522, 1.522, 1.535, 1.549, 1.556,
+ 1.558, 1.543, 1.532, 1.526, 1.526, 1.529, 1.534, 1.535, 1.533, 1.526, 1.523, 1.522, 1.524, 1.537, 1.552, 1.557,
+ 1.555, 1.546, 1.541, 1.528, 1.527, 1.528, 1.531, 1.533, 1.531, 1.527, 1.522, 1.522, 1.526, 1.536, 1.552, 1.561,
+ 1.555, 1.547, 1.542, 1.538, 1.526, 1.526, 1.529, 1.531, 1.529, 1.528, 1.519, 1.519, 1.527, 1.531, 1.543, 1.561
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 3000,
+ "table":
+ [
+ 1.684, 1.688, 1.691, 1.697, 1.709, 1.722, 1.735, 1.745, 1.747, 1.745, 1.731, 1.719, 1.709, 1.705, 1.699, 1.699,
+ 1.684, 1.689, 1.694, 1.708, 1.721, 1.735, 1.747, 1.762, 1.762, 1.758, 1.745, 1.727, 1.716, 1.707, 1.701, 1.699,
+ 1.684, 1.691, 1.704, 1.719, 1.734, 1.755, 1.772, 1.786, 1.789, 1.788, 1.762, 1.745, 1.724, 1.709, 1.702, 1.698,
+ 1.682, 1.694, 1.709, 1.729, 1.755, 1.773, 1.798, 1.815, 1.817, 1.808, 1.788, 1.762, 1.733, 1.714, 1.704, 1.699,
+ 1.682, 1.693, 1.713, 1.742, 1.772, 1.798, 1.815, 1.829, 1.831, 1.821, 1.807, 1.773, 1.742, 1.716, 1.703, 1.699,
+ 1.681, 1.693, 1.713, 1.742, 1.772, 1.799, 1.828, 1.839, 1.839, 1.828, 1.807, 1.774, 1.742, 1.715, 1.699, 1.695,
+ 1.679, 1.691, 1.712, 1.739, 1.771, 1.798, 1.825, 1.829, 1.831, 1.818, 1.801, 1.774, 1.738, 1.712, 1.695, 1.691,
+ 1.676, 1.685, 1.703, 1.727, 1.761, 1.784, 1.801, 1.817, 1.817, 1.801, 1.779, 1.761, 1.729, 1.706, 1.691, 1.684,
+ 1.669, 1.678, 1.692, 1.714, 1.741, 1.764, 1.784, 1.795, 1.795, 1.779, 1.761, 1.738, 1.713, 1.696, 1.683, 1.679,
+ 1.664, 1.671, 1.679, 1.693, 1.716, 1.741, 1.762, 1.769, 1.769, 1.753, 1.738, 1.713, 1.701, 1.687, 1.681, 1.676,
+ 1.661, 1.664, 1.671, 1.679, 1.693, 1.714, 1.732, 1.739, 1.739, 1.729, 1.708, 1.701, 1.685, 1.679, 1.676, 1.677,
+ 1.659, 1.661, 1.664, 1.671, 1.679, 1.693, 1.712, 1.714, 1.714, 1.708, 1.701, 1.687, 1.679, 1.672, 1.673, 1.677
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.177, 1.183, 1.187, 1.191, 1.197, 1.206, 1.213, 1.215, 1.215, 1.215, 1.211, 1.204, 1.196, 1.191, 1.183, 1.182,
+ 1.179, 1.185, 1.191, 1.196, 1.206, 1.217, 1.224, 1.229, 1.229, 1.226, 1.221, 1.212, 1.202, 1.195, 1.188, 1.182,
+ 1.183, 1.191, 1.196, 1.206, 1.217, 1.229, 1.239, 1.245, 1.245, 1.245, 1.233, 1.221, 1.212, 1.199, 1.193, 1.187,
+ 1.183, 1.192, 1.201, 1.212, 1.229, 1.241, 1.252, 1.259, 1.259, 1.257, 1.245, 1.233, 1.217, 1.201, 1.194, 1.192,
+ 1.183, 1.192, 1.202, 1.219, 1.238, 1.252, 1.261, 1.269, 1.268, 1.261, 1.257, 1.241, 1.223, 1.204, 1.194, 1.191,
+ 1.182, 1.192, 1.202, 1.219, 1.239, 1.255, 1.266, 1.271, 1.271, 1.265, 1.258, 1.242, 1.223, 1.205, 1.192, 1.191,
+ 1.181, 1.189, 1.199, 1.218, 1.239, 1.254, 1.262, 1.268, 1.268, 1.258, 1.253, 1.241, 1.221, 1.204, 1.191, 1.187,
+ 1.179, 1.184, 1.193, 1.211, 1.232, 1.243, 1.254, 1.257, 1.256, 1.253, 1.242, 1.232, 1.216, 1.199, 1.187, 1.183,
+ 1.174, 1.179, 1.187, 1.202, 1.218, 1.232, 1.243, 1.246, 1.246, 1.239, 1.232, 1.218, 1.207, 1.191, 1.183, 1.179,
+ 1.169, 1.175, 1.181, 1.189, 1.202, 1.218, 1.229, 1.232, 1.232, 1.224, 1.218, 1.207, 1.199, 1.185, 1.181, 1.174,
+ 1.164, 1.168, 1.175, 1.179, 1.189, 1.201, 1.209, 1.213, 1.213, 1.209, 1.201, 1.198, 1.186, 1.181, 1.174, 1.173,
+ 1.161, 1.166, 1.171, 1.175, 1.179, 1.189, 1.197, 1.198, 1.198, 1.197, 1.196, 1.186, 1.182, 1.175, 1.173, 1.173
+ ]
+ },
+ {
+ "ct": 6500,
+ "table":
+ [
+ 1.166, 1.171, 1.173, 1.178, 1.187, 1.193, 1.201, 1.205, 1.205, 1.205, 1.199, 1.191, 1.184, 1.179, 1.174, 1.171,
+ 1.166, 1.172, 1.176, 1.184, 1.195, 1.202, 1.209, 1.216, 1.216, 1.213, 1.208, 1.201, 1.189, 1.182, 1.176, 1.171,
+ 1.166, 1.173, 1.183, 1.195, 1.202, 1.214, 1.221, 1.228, 1.229, 1.228, 1.221, 1.209, 1.201, 1.186, 1.179, 1.174,
+ 1.165, 1.174, 1.187, 1.201, 1.214, 1.223, 1.235, 1.241, 1.242, 1.241, 1.229, 1.221, 1.205, 1.188, 1.181, 1.177,
+ 1.165, 1.174, 1.189, 1.207, 1.223, 1.235, 1.242, 1.253, 1.252, 1.245, 1.241, 1.228, 1.211, 1.189, 1.181, 1.178,
+ 1.164, 1.173, 1.189, 1.207, 1.224, 1.238, 1.249, 1.255, 1.255, 1.249, 1.242, 1.228, 1.211, 1.191, 1.179, 1.176,
+ 1.163, 1.172, 1.187, 1.207, 1.223, 1.237, 1.245, 1.253, 1.252, 1.243, 1.237, 1.228, 1.207, 1.188, 1.176, 1.173,
+ 1.159, 1.167, 1.179, 1.199, 1.217, 1.227, 1.237, 1.241, 1.241, 1.237, 1.228, 1.217, 1.201, 1.184, 1.174, 1.169,
+ 1.156, 1.164, 1.172, 1.189, 1.205, 1.217, 1.226, 1.229, 1.229, 1.222, 1.217, 1.204, 1.192, 1.177, 1.171, 1.166,
+ 1.154, 1.159, 1.166, 1.177, 1.189, 1.205, 1.213, 1.216, 1.216, 1.209, 1.204, 1.192, 1.183, 1.172, 1.168, 1.162,
+ 1.152, 1.155, 1.161, 1.166, 1.177, 1.188, 1.195, 1.198, 1.199, 1.196, 1.187, 1.183, 1.173, 1.168, 1.163, 1.162,
+ 1.151, 1.154, 1.158, 1.162, 1.168, 1.177, 1.183, 1.184, 1.184, 1.184, 1.182, 1.172, 1.168, 1.165, 1.162, 1.161
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 2.236, 2.111, 1.912, 1.741, 1.579, 1.451, 1.379, 1.349, 1.349, 1.361, 1.411, 1.505, 1.644, 1.816, 2.034, 2.159,
+ 2.139, 1.994, 1.796, 1.625, 1.467, 1.361, 1.285, 1.248, 1.239, 1.265, 1.321, 1.408, 1.536, 1.703, 1.903, 2.087,
+ 2.047, 1.898, 1.694, 1.511, 1.373, 1.254, 1.186, 1.152, 1.142, 1.166, 1.226, 1.309, 1.441, 1.598, 1.799, 1.978,
+ 1.999, 1.824, 1.615, 1.429, 1.281, 1.179, 1.113, 1.077, 1.071, 1.096, 1.153, 1.239, 1.357, 1.525, 1.726, 1.915,
+ 1.976, 1.773, 1.563, 1.374, 1.222, 1.119, 1.064, 1.032, 1.031, 1.049, 1.099, 1.188, 1.309, 1.478, 1.681, 1.893,
+ 1.973, 1.756, 1.542, 1.351, 1.196, 1.088, 1.028, 1.011, 1.004, 1.029, 1.077, 1.169, 1.295, 1.459, 1.663, 1.891,
+ 1.973, 1.761, 1.541, 1.349, 1.193, 1.087, 1.031, 1.006, 1.006, 1.023, 1.075, 1.169, 1.298, 1.463, 1.667, 1.891,
+ 1.982, 1.789, 1.568, 1.373, 1.213, 1.111, 1.051, 1.029, 1.024, 1.053, 1.106, 1.199, 1.329, 1.495, 1.692, 1.903,
+ 2.015, 1.838, 1.621, 1.426, 1.268, 1.159, 1.101, 1.066, 1.068, 1.099, 1.166, 1.259, 1.387, 1.553, 1.751, 1.937,
+ 2.076, 1.911, 1.692, 1.507, 1.346, 1.236, 1.169, 1.136, 1.139, 1.174, 1.242, 1.349, 1.475, 1.641, 1.833, 2.004,
+ 2.193, 2.011, 1.798, 1.604, 1.444, 1.339, 1.265, 1.235, 1.237, 1.273, 1.351, 1.461, 1.598, 1.758, 1.956, 2.125,
+ 2.263, 2.154, 1.916, 1.711, 1.549, 1.432, 1.372, 1.356, 1.356, 1.383, 1.455, 1.578, 1.726, 1.914, 2.119, 2.211
+ ],
+ "sigma": 0.006,
+ "sigma_Cb": 0.00208
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2500,
+ "ccm":
+ [
+ 1.70741, -0.05307, -0.65433,
+ -0.62822, 1.68836, -0.06014,
+ -0.04452, -1.87628, 2.92079
+ ]
+ },
+ {
+ "ct": 2803,
+ "ccm":
+ [
+ 1.74383, -0.18731, -0.55652,
+ -0.56491, 1.67772, -0.11281,
+ -0.01522, -1.60635, 2.62157
+ ]
+ },
+ {
+ "ct": 2912,
+ "ccm":
+ [
+ 1.75215, -0.22221, -0.52995,
+ -0.54568, 1.63522, -0.08954,
+ 0.02633, -1.56997, 2.54364
+ ]
+ },
+ {
+ "ct": 2914,
+ "ccm":
+ [
+ 1.72423, -0.28939, -0.43484,
+ -0.55188, 1.62925, -0.07737,
+ 0.01959, -1.28661, 2.26702
+ ]
+ },
+ {
+ "ct": 3605,
+ "ccm":
+ [
+ 1.80381, -0.43646, -0.36735,
+ -0.46505, 1.56814, -0.10309,
+ 0.00929, -1.00424, 1.99495
+ ]
+ },
+ {
+ "ct": 4540,
+ "ccm":
+ [
+ 1.85263, -0.46545, -0.38719,
+ -0.44136, 1.68443, -0.24307,
+ 0.04108, -0.85599, 1.81491
+ ]
+ },
+ {
+ "ct": 5699,
+ "ccm":
+ [
+ 1.98595, -0.63542, -0.35054,
+ -0.34623, 1.54146, -0.19522,
+ 0.00411, -0.70936, 1.70525
+ ]
+ },
+ {
+ "ct": 8625,
+ "ccm":
+ [
+ 2.21637, -0.56663, -0.64974,
+ -0.41133, 1.96625, -0.55492,
+ -0.02307, -0.83529, 1.85837
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/ov64a40.json b/src/ipa/rpi/vc4/data/ov64a40.json
new file mode 100644
index 00000000..096f0b1e
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/ov64a40.json
@@ -0,0 +1,422 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 17861,
+ "reference_gain": 2.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 1073,
+ "reference_Y": 9022
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.984
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 215,
+ "slope": 0.01121
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2300.0, 1.0522, 0.4091,
+ 2700.0, 0.7884, 0.4327,
+ 3000.0, 0.7597, 0.4421,
+ 4000.0, 0.5972, 0.5404,
+ 4150.0, 0.5598, 0.5779,
+ 6500.0, 0.4388, 0.7582
+ ],
+ "sensitivity_r": 1.0,
+ "sensitivity_b": 1.0,
+ "transverse_pos": 0.0558,
+ "transverse_neg": 0.04278
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights":
+ [
+ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
+ ]
+ },
+ "spot":
+ {
+ "weights":
+ [
+ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+ },
+ "matrix":
+ {
+ "weights":
+ [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.8,
+ "calibrations_Cr": [
+ {
+ "ct": 6500,
+ "table":
+ [
+ 2.437, 2.415, 2.392, 2.378, 2.369, 2.353, 2.344, 2.336, 2.329, 2.325, 2.325, 2.325, 2.333, 2.344, 2.366, 2.381,
+ 2.434, 2.405, 2.386, 2.369, 2.361, 2.334, 2.314, 2.302, 2.295, 2.289, 2.289, 2.303, 2.327, 2.334, 2.356, 2.378,
+ 2.434, 2.405, 2.385, 2.363, 2.334, 2.314, 2.289, 2.277, 2.269, 2.262, 2.262, 2.283, 2.303, 2.328, 2.352, 2.375,
+ 2.434, 2.405, 2.385, 2.348, 2.315, 2.289, 2.277, 2.258, 2.251, 2.242, 2.249, 2.258, 2.283, 2.321, 2.352, 2.375,
+ 2.434, 2.413, 2.385, 2.343, 2.311, 2.282, 2.258, 2.251, 2.229, 2.233, 2.242, 2.251, 2.281, 2.321, 2.356, 2.375,
+ 2.437, 2.418, 2.388, 2.343, 2.311, 2.282, 2.251, 2.229, 2.221, 2.226, 2.233, 2.251, 2.281, 2.322, 2.361, 2.381,
+ 2.444, 2.422, 2.393, 2.351, 2.314, 2.284, 2.251, 2.227, 2.221, 2.227, 2.234, 2.256, 2.287, 2.326, 2.366, 2.389,
+ 2.445, 2.424, 2.395, 2.353, 2.316, 2.287, 2.266, 2.251, 2.228, 2.234, 2.251, 2.259, 2.289, 2.331, 2.371, 2.395,
+ 2.445, 2.424, 2.399, 2.364, 2.329, 2.308, 2.287, 2.266, 2.259, 2.254, 2.259, 2.283, 2.304, 2.343, 2.375, 2.395,
+ 2.445, 2.425, 2.407, 2.385, 2.364, 2.329, 2.308, 2.299, 2.291, 2.284, 2.284, 2.304, 2.335, 2.354, 2.381, 2.399,
+ 2.449, 2.427, 2.418, 2.407, 2.385, 2.364, 2.349, 2.338, 2.333, 2.326, 2.326, 2.335, 2.354, 2.374, 2.389, 2.408,
+ 2.458, 2.441, 2.427, 2.411, 2.403, 2.395, 2.391, 2.383, 2.375, 2.369, 2.369, 2.369, 2.369, 2.385, 2.408, 2.418
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 6500,
+ "table":
+ [
+ 1.297, 1.297, 1.289, 1.289, 1.289, 1.291, 1.293, 1.294, 1.294, 1.294, 1.294, 1.296, 1.298, 1.304, 1.312, 1.313,
+ 1.297, 1.289, 1.286, 1.286, 1.287, 1.289, 1.292, 1.294, 1.294, 1.294, 1.294, 1.294, 1.296, 1.298, 1.306, 1.312,
+ 1.289, 1.286, 1.283, 1.283, 1.285, 1.287, 1.291, 1.294, 1.294, 1.292, 1.291, 1.289, 1.293, 1.294, 1.298, 1.304,
+ 1.283, 1.282, 1.279, 1.281, 1.282, 1.285, 1.287, 1.294, 1.294, 1.291, 1.289, 1.289, 1.289, 1.293, 1.294, 1.298,
+ 1.281, 1.279, 1.279, 1.279, 1.281, 1.283, 1.287, 1.292, 1.292, 1.291, 1.291, 1.289, 1.289, 1.291, 1.294, 1.297,
+ 1.279, 1.277, 1.277, 1.279, 1.281, 1.282, 1.286, 1.289, 1.291, 1.291, 1.291, 1.291, 1.289, 1.291, 1.293, 1.297,
+ 1.277, 1.275, 1.275, 1.278, 1.279, 1.281, 1.284, 1.287, 1.289, 1.291, 1.291, 1.291, 1.289, 1.289, 1.292, 1.297,
+ 1.277, 1.275, 1.274, 1.275, 1.277, 1.278, 1.279, 1.284, 1.285, 1.285, 1.286, 1.288, 1.289, 1.289, 1.292, 1.297,
+ 1.277, 1.272, 1.272, 1.274, 1.274, 1.277, 1.279, 1.282, 1.284, 1.284, 1.285, 1.286, 1.288, 1.289, 1.292, 1.297,
+ 1.277, 1.272, 1.272, 1.273, 1.274, 1.276, 1.279, 1.282, 1.284, 1.284, 1.286, 1.286, 1.288, 1.289, 1.293, 1.297,
+ 1.279, 1.272, 1.271, 1.272, 1.274, 1.276, 1.279, 1.283, 1.284, 1.284, 1.285, 1.286, 1.288, 1.291, 1.294, 1.299,
+ 1.281, 1.274, 1.271, 1.271, 1.273, 1.276, 1.278, 1.282, 1.284, 1.284, 1.285, 1.286, 1.286, 1.291, 1.295, 1.302
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 3.811, 3.611, 3.038, 2.632, 2.291, 2.044, 1.967, 1.957, 1.957, 1.957, 2.009, 2.222, 2.541, 2.926, 3.455, 3.652,
+ 3.611, 3.135, 2.636, 2.343, 2.044, 1.846, 1.703, 1.626, 1.626, 1.671, 1.796, 1.983, 2.266, 2.549, 3.007, 3.455,
+ 3.135, 2.781, 2.343, 2.044, 1.831, 1.554, 1.411, 1.337, 1.337, 1.379, 1.502, 1.749, 1.983, 2.266, 2.671, 3.007,
+ 2.903, 2.538, 2.149, 1.831, 1.554, 1.401, 1.208, 1.145, 1.145, 1.183, 1.339, 1.502, 1.749, 2.072, 2.446, 2.801,
+ 2.812, 2.389, 2.018, 1.684, 1.401, 1.208, 1.139, 1.028, 1.028, 1.109, 1.183, 1.339, 1.604, 1.939, 2.309, 2.723,
+ 2.799, 2.317, 1.948, 1.606, 1.327, 1.139, 1.028, 1.019, 1.001, 1.021, 1.109, 1.272, 1.531, 1.869, 2.246, 2.717,
+ 2.799, 2.317, 1.948, 1.606, 1.327, 1.139, 1.027, 1.006, 1.001, 1.007, 1.109, 1.272, 1.531, 1.869, 2.246, 2.717,
+ 2.799, 2.372, 1.997, 1.661, 1.378, 1.184, 1.118, 1.019, 1.012, 1.103, 1.158, 1.326, 1.589, 1.926, 2.302, 2.717,
+ 2.884, 2.507, 2.116, 1.795, 1.511, 1.361, 1.184, 1.118, 1.118, 1.158, 1.326, 1.461, 1.726, 2.056, 2.434, 2.799,
+ 3.083, 2.738, 2.303, 1.989, 1.783, 1.511, 1.361, 1.291, 1.291, 1.337, 1.461, 1.726, 1.942, 2.251, 2.657, 2.999,
+ 3.578, 3.083, 2.589, 2.303, 1.989, 1.783, 1.637, 1.563, 1.563, 1.613, 1.743, 1.942, 2.251, 2.537, 2.999, 3.492,
+ 3.764, 3.578, 2.999, 2.583, 2.237, 1.986, 1.913, 1.905, 1.905, 1.905, 1.962, 2.196, 2.525, 2.932, 3.492, 3.659
+ ],
+ "sigma": 0.005,
+ "sigma_Cb": 0.005
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2300,
+ "ccm":
+ [
+ 1.77644, -0.14825, -0.62819,
+ -0.25816, 1.66348, -0.40532,
+ -0.21633, -1.95132, 3.16765
+ ]
+ },
+ {
+ "ct": 2700,
+ "ccm":
+ [
+ 1.53605, 0.03047, -0.56652,
+ -0.27159, 1.78525, -0.51366,
+ -0.13581, -1.22128, 2.35709
+ ]
+ },
+ {
+ "ct": 3000,
+ "ccm":
+ [
+ 1.72928, -0.18819, -0.54108,
+ -0.44398, 2.04756, -0.60358,
+ -0.13203, -0.94711, 2.07913
+ ]
+ },
+ {
+ "ct": 4000,
+ "ccm":
+ [
+ 1.69895, -0.23055, -0.46841,
+ -0.33934, 1.80391, -0.46456,
+ -0.13902, -0.75385, 1.89287
+ ]
+ },
+ {
+ "ct": 4150,
+ "ccm":
+ [
+ 2.08494, -0.68698, -0.39796,
+ -0.37928, 1.78795, -0.40867,
+ -0.11537, -0.74686, 1.86223
+ ]
+ },
+ {
+ "ct": 6500,
+ "ccm":
+ [
+ 1.69813, -0.27304, -0.42509,
+ -0.23364, 1.87586, -0.64221,
+ -0.07587, -0.62348, 1.69935
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ },
+ {
+ "rpi.af":
+ {
+ "ranges":
+ {
+ "normal":
+ {
+ "min": 0.0,
+ "max": 12.0,
+ "default": 1.0
+ },
+ "macro":
+ {
+ "min": 3.0,
+ "max": 15.0,
+ "default": 4.0
+ }
+ },
+ "speeds":
+ {
+ "normal":
+ {
+ "step_coarse": 1.0,
+ "step_fine": 0.25,
+ "contrast_ratio": 0.75,
+ "pdaf_gain": -0.02,
+ "pdaf_squelch": 0.125,
+ "max_slew": 2.0,
+ "pdaf_frames": 0,
+ "dropout_frames": 0,
+ "step_frames": 4
+ }
+ },
+ "conf_epsilon": 8,
+ "conf_thresh": 16,
+ "conf_clip": 512,
+ "skip_frames": 5,
+ "map": [ 0.0, 0, 15.0, 1023 ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/ov9281_mono.json b/src/ipa/rpi/vc4/data/ov9281_mono.json
new file mode 100644
index 00000000..2b7292ec
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/ov9281_mono.json
@@ -0,0 +1,133 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 2000,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 800,
+ "reference_Y": 20000
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.5
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 3.0, 4.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 30000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.4,
+ 1000, 0.4
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "n_iter": 0,
+ "luminance_strength": 1.0,
+ "corner_strength": 1.5
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 0,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/se327m12.json b/src/ipa/rpi/vc4/data/se327m12.json
new file mode 100644
index 00000000..8552ed92
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/se327m12.json
@@ -0,0 +1,423 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 3840
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 6873,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 800,
+ "reference_Y": 12293
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 1.986
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 207,
+ "slope": 0.00539
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ },
+ "cloudy":
+ {
+ "lo": 7000,
+ "hi": 8600
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2900.0, 0.9217, 0.3657,
+ 3600.0, 0.7876, 0.4651,
+ 4600.0, 0.6807, 0.5684,
+ 5800.0, 0.5937, 0.6724,
+ 8100.0, 0.5447, 0.7403
+ ],
+ "sensitivity_r": 1.0,
+ "sensitivity_b": 1.0,
+ "transverse_pos": 0.0162,
+ "transverse_neg": 0.0204
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ },
+ "spot":
+ {
+ "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
+ },
+ "matrix":
+ {
+ "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.5,
+ "calibrations_Cr": [
+ {
+ "ct": 4000,
+ "table":
+ [
+ 1.481, 1.471, 1.449, 1.429, 1.416, 1.404, 1.394, 1.389, 1.389, 1.389, 1.392, 1.397, 1.404, 1.416, 1.429, 1.437,
+ 1.472, 1.456, 1.436, 1.418, 1.405, 1.394, 1.389, 1.384, 1.382, 1.382, 1.386, 1.388, 1.398, 1.407, 1.422, 1.429,
+ 1.465, 1.443, 1.426, 1.411, 1.397, 1.389, 1.383, 1.377, 1.377, 1.377, 1.379, 1.384, 1.388, 1.398, 1.411, 1.422,
+ 1.462, 1.441, 1.423, 1.409, 1.395, 1.385, 1.379, 1.376, 1.374, 1.374, 1.375, 1.379, 1.384, 1.394, 1.407, 1.418,
+ 1.461, 1.439, 1.421, 1.407, 1.394, 1.385, 1.381, 1.376, 1.373, 1.373, 1.373, 1.376, 1.381, 1.389, 1.403, 1.415,
+ 1.461, 1.439, 1.419, 1.404, 1.392, 1.384, 1.379, 1.376, 1.373, 1.372, 1.374, 1.375, 1.379, 1.389, 1.401, 1.413,
+ 1.461, 1.438, 1.419, 1.402, 1.389, 1.383, 1.377, 1.375, 1.373, 1.372, 1.372, 1.375, 1.381, 1.388, 1.401, 1.414,
+ 1.462, 1.438, 1.419, 1.403, 1.391, 1.381, 1.377, 1.374, 1.373, 1.373, 1.374, 1.376, 1.381, 1.389, 1.401, 1.414,
+ 1.462, 1.441, 1.423, 1.405, 1.392, 1.383, 1.377, 1.374, 1.373, 1.372, 1.373, 1.376, 1.382, 1.391, 1.402, 1.414,
+ 1.465, 1.444, 1.424, 1.407, 1.393, 1.382, 1.378, 1.373, 1.369, 1.369, 1.372, 1.375, 1.381, 1.389, 1.402, 1.417,
+ 1.469, 1.449, 1.427, 1.413, 1.396, 1.384, 1.381, 1.375, 1.371, 1.371, 1.373, 1.377, 1.385, 1.393, 1.407, 1.422,
+ 1.474, 1.456, 1.436, 1.419, 1.407, 1.391, 1.383, 1.379, 1.377, 1.377, 1.378, 1.381, 1.391, 1.404, 1.422, 1.426
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.742, 1.721, 1.689, 1.661, 1.639, 1.623, 1.613, 1.609, 1.607, 1.606, 1.609, 1.617, 1.626, 1.641, 1.665, 1.681,
+ 1.728, 1.703, 1.672, 1.645, 1.631, 1.614, 1.602, 1.599, 1.596, 1.597, 1.601, 1.608, 1.618, 1.631, 1.653, 1.671,
+ 1.713, 1.691, 1.658, 1.635, 1.618, 1.606, 1.595, 1.591, 1.588, 1.588, 1.591, 1.601, 1.608, 1.624, 1.641, 1.658,
+ 1.707, 1.681, 1.651, 1.627, 1.613, 1.599, 1.591, 1.585, 1.583, 1.584, 1.587, 1.591, 1.601, 1.615, 1.633, 1.655,
+ 1.699, 1.672, 1.644, 1.622, 1.606, 1.593, 1.586, 1.581, 1.579, 1.581, 1.583, 1.587, 1.597, 1.611, 1.631, 1.652,
+ 1.697, 1.665, 1.637, 1.617, 1.601, 1.589, 1.584, 1.579, 1.577, 1.578, 1.581, 1.585, 1.597, 1.607, 1.627, 1.652,
+ 1.697, 1.662, 1.634, 1.613, 1.599, 1.591, 1.583, 1.578, 1.576, 1.576, 1.579, 1.586, 1.597, 1.607, 1.628, 1.653,
+ 1.697, 1.662, 1.633, 1.613, 1.598, 1.589, 1.582, 1.578, 1.576, 1.577, 1.582, 1.589, 1.598, 1.611, 1.635, 1.655,
+ 1.701, 1.666, 1.636, 1.616, 1.602, 1.589, 1.583, 1.578, 1.577, 1.581, 1.583, 1.591, 1.601, 1.617, 1.639, 1.659,
+ 1.708, 1.671, 1.641, 1.618, 1.603, 1.591, 1.584, 1.581, 1.578, 1.581, 1.585, 1.594, 1.604, 1.622, 1.646, 1.666,
+ 1.714, 1.681, 1.648, 1.622, 1.608, 1.599, 1.591, 1.584, 1.583, 1.584, 1.589, 1.599, 1.614, 1.629, 1.653, 1.673,
+ 1.719, 1.691, 1.659, 1.631, 1.618, 1.606, 1.596, 1.591, 1.591, 1.593, 1.599, 1.608, 1.623, 1.642, 1.665, 1.681
+ ]
+ }
+ ],
+ "calibrations_Cb": [
+ {
+ "ct": 4000,
+ "table":
+ [
+ 2.253, 2.267, 2.289, 2.317, 2.342, 2.359, 2.373, 2.381, 2.381, 2.378, 2.368, 2.361, 2.344, 2.337, 2.314, 2.301,
+ 2.262, 2.284, 2.314, 2.335, 2.352, 2.371, 2.383, 2.391, 2.393, 2.391, 2.381, 2.368, 2.361, 2.342, 2.322, 2.308,
+ 2.277, 2.303, 2.321, 2.346, 2.364, 2.381, 2.391, 2.395, 2.397, 2.397, 2.395, 2.381, 2.367, 2.354, 2.332, 2.321,
+ 2.277, 2.304, 2.327, 2.349, 2.369, 2.388, 2.393, 2.396, 2.396, 2.398, 2.396, 2.391, 2.376, 2.359, 2.339, 2.328,
+ 2.279, 2.311, 2.327, 2.354, 2.377, 2.389, 2.393, 2.397, 2.397, 2.398, 2.395, 2.393, 2.382, 2.363, 2.344, 2.332,
+ 2.282, 2.311, 2.329, 2.354, 2.377, 2.386, 2.396, 2.396, 2.395, 2.396, 2.397, 2.394, 2.383, 2.367, 2.346, 2.333,
+ 2.283, 2.314, 2.333, 2.353, 2.375, 2.389, 2.394, 2.395, 2.395, 2.395, 2.396, 2.394, 2.386, 2.368, 2.354, 2.336,
+ 2.287, 2.309, 2.331, 2.352, 2.373, 2.386, 2.394, 2.395, 2.395, 2.396, 2.396, 2.394, 2.384, 2.371, 2.354, 2.339,
+ 2.289, 2.307, 2.326, 2.347, 2.369, 2.385, 2.392, 2.397, 2.398, 2.398, 2.397, 2.392, 2.383, 2.367, 2.352, 2.337,
+ 2.286, 2.303, 2.322, 2.342, 2.361, 2.379, 2.389, 2.394, 2.397, 2.398, 2.396, 2.389, 2.381, 2.366, 2.346, 2.332,
+ 2.284, 2.291, 2.312, 2.329, 2.351, 2.372, 2.381, 2.389, 2.393, 2.394, 2.389, 2.385, 2.374, 2.362, 2.338, 2.325,
+ 2.283, 2.288, 2.305, 2.319, 2.339, 2.365, 2.374, 2.381, 2.384, 2.386, 2.385, 2.379, 2.368, 2.342, 2.325, 2.318
+ ]
+ },
+ {
+ "ct": 5000,
+ "table":
+ [
+ 1.897, 1.919, 1.941, 1.969, 1.989, 2.003, 2.014, 2.019, 2.019, 2.017, 2.014, 2.008, 1.999, 1.988, 1.968, 1.944,
+ 1.914, 1.932, 1.957, 1.982, 1.998, 2.014, 2.023, 2.029, 2.031, 2.029, 2.022, 2.014, 2.006, 1.995, 1.976, 1.955,
+ 1.925, 1.951, 1.974, 1.996, 2.013, 2.027, 2.035, 2.039, 2.039, 2.038, 2.035, 2.026, 2.015, 2.002, 1.984, 1.963,
+ 1.932, 1.958, 1.986, 2.007, 2.024, 2.034, 2.041, 2.041, 2.045, 2.045, 2.042, 2.033, 2.023, 2.009, 1.995, 1.971,
+ 1.942, 1.964, 1.994, 2.012, 2.029, 2.038, 2.043, 2.046, 2.047, 2.046, 2.045, 2.039, 2.029, 2.014, 1.997, 1.977,
+ 1.946, 1.974, 1.999, 2.015, 2.031, 2.041, 2.046, 2.047, 2.048, 2.047, 2.044, 2.041, 2.031, 2.019, 1.999, 1.978,
+ 1.948, 1.975, 2.002, 2.018, 2.031, 2.041, 2.046, 2.047, 2.048, 2.048, 2.045, 2.041, 2.029, 2.019, 1.998, 1.978,
+ 1.948, 1.973, 2.002, 2.018, 2.029, 2.042, 2.045, 2.048, 2.048, 2.048, 2.044, 2.037, 2.027, 2.014, 1.993, 1.978,
+ 1.945, 1.969, 1.998, 2.015, 2.028, 2.037, 2.045, 2.046, 2.047, 2.044, 2.039, 2.033, 2.022, 2.008, 1.989, 1.971,
+ 1.939, 1.964, 1.991, 2.011, 2.024, 2.032, 2.036, 2.042, 2.042, 2.039, 2.035, 2.024, 2.012, 1.998, 1.977, 1.964,
+ 1.932, 1.953, 1.981, 2.006, 2.016, 2.024, 2.028, 2.031, 2.034, 2.031, 2.024, 2.015, 2.005, 1.989, 1.966, 1.955,
+ 1.928, 1.944, 1.973, 1.999, 2.007, 2.016, 2.019, 2.025, 2.026, 2.025, 2.017, 2.008, 1.997, 1.975, 1.958, 1.947
+ ]
+ }
+ ],
+ "luminance_lut":
+ [
+ 1.877, 1.597, 1.397, 1.269, 1.191, 1.131, 1.093, 1.078, 1.071, 1.069, 1.086, 1.135, 1.221, 1.331, 1.474, 1.704,
+ 1.749, 1.506, 1.334, 1.229, 1.149, 1.088, 1.058, 1.053, 1.051, 1.046, 1.053, 1.091, 1.163, 1.259, 1.387, 1.587,
+ 1.661, 1.451, 1.295, 1.195, 1.113, 1.061, 1.049, 1.048, 1.047, 1.049, 1.049, 1.066, 1.124, 1.211, 1.333, 1.511,
+ 1.615, 1.411, 1.267, 1.165, 1.086, 1.052, 1.047, 1.047, 1.047, 1.049, 1.052, 1.056, 1.099, 1.181, 1.303, 1.471,
+ 1.576, 1.385, 1.252, 1.144, 1.068, 1.049, 1.044, 1.044, 1.045, 1.049, 1.053, 1.054, 1.083, 1.163, 1.283, 1.447,
+ 1.561, 1.373, 1.245, 1.135, 1.064, 1.049, 1.044, 1.044, 1.044, 1.046, 1.048, 1.054, 1.073, 1.153, 1.271, 1.432,
+ 1.571, 1.377, 1.242, 1.137, 1.066, 1.055, 1.052, 1.051, 1.051, 1.049, 1.047, 1.048, 1.068, 1.148, 1.271, 1.427,
+ 1.582, 1.396, 1.259, 1.156, 1.085, 1.068, 1.059, 1.054, 1.049, 1.045, 1.041, 1.043, 1.074, 1.157, 1.284, 1.444,
+ 1.623, 1.428, 1.283, 1.178, 1.105, 1.074, 1.069, 1.063, 1.056, 1.048, 1.046, 1.051, 1.094, 1.182, 1.311, 1.473,
+ 1.691, 1.471, 1.321, 1.213, 1.135, 1.088, 1.073, 1.069, 1.063, 1.059, 1.053, 1.071, 1.129, 1.222, 1.351, 1.521,
+ 1.808, 1.543, 1.371, 1.253, 1.174, 1.118, 1.085, 1.072, 1.067, 1.064, 1.071, 1.106, 1.176, 1.274, 1.398, 1.582,
+ 1.969, 1.666, 1.447, 1.316, 1.223, 1.166, 1.123, 1.094, 1.089, 1.097, 1.118, 1.163, 1.239, 1.336, 1.471, 1.681
+ ],
+ "sigma": 0.00218,
+ "sigma_Cb": 0.00194
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2900,
+ "ccm":
+ [
+ 1.44924, -0.12935, -0.31989,
+ -0.65839, 1.95441, -0.29602,
+ 0.18344, -1.22282, 2.03938
+ ]
+ },
+ {
+ "ct": 3000,
+ "ccm":
+ [
+ 1.38736, 0.07714, -0.46451,
+ -0.59691, 1.84335, -0.24644,
+ 0.10092, -1.30441, 2.20349
+ ]
+ },
+ {
+ "ct": 3600,
+ "ccm":
+ [
+ 1.51261, -0.27921, -0.23339,
+ -0.55129, 1.83241, -0.28111,
+ 0.11649, -0.93195, 1.81546
+ ]
+ },
+ {
+ "ct": 4600,
+ "ccm":
+ [
+ 1.47082, -0.18523, -0.28559,
+ -0.48923, 1.95126, -0.46203,
+ 0.07951, -0.83987, 1.76036
+ ]
+ },
+ {
+ "ct": 5800,
+ "ccm":
+ [
+ 1.57294, -0.36229, -0.21065,
+ -0.42272, 1.80305, -0.38032,
+ 0.03671, -0.66862, 1.63191
+ ]
+ },
+ {
+ "ct": 8100,
+ "ccm":
+ [
+ 1.58803, -0.09912, -0.48891,
+ -0.42594, 2.22303, -0.79709,
+ -0.00621, -0.90516, 1.91137
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen":
+ {
+ "threshold": 2.0,
+ "strength": 0.5,
+ "limit": 0.5
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/data/uncalibrated.json b/src/ipa/rpi/vc4/data/uncalibrated.json
new file mode 100644
index 00000000..7654defa
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/uncalibrated.json
@@ -0,0 +1,128 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 4096
+ }
+ },
+ {
+ "rpi.awb":
+ {
+ "use_derivatives": 0,
+ "bayes": 0
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights": [ 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 15000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 3.0, 4.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 30000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ]
+ },
+ "long":
+ {
+ "shutter": [ 1000, 30000, 60000, 90000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.4,
+ 1000, 0.4
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 4000,
+ "ccm":
+ [
+ 2.0, -1.0, 0.0,
+ -0.5, 2.0, -0.5,
+ 0, -1.0, 2.0
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 0,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/ipa/rpi/vc4/meson.build b/src/ipa/rpi/vc4/meson.build
new file mode 100644
index 00000000..590e9197
--- /dev/null
+++ b/src/ipa/rpi/vc4/meson.build
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: CC0-1.0
+
+ipa_name = 'ipa_rpi_vc4'
+
+vc4_ipa_deps = [
+ libcamera_private,
+ libatomic,
+]
+
+vc4_ipa_libs = [
+ rpi_ipa_cam_helper_lib,
+ rpi_ipa_common_lib,
+ rpi_ipa_controller_lib
+]
+
+vc4_ipa_includes = [
+ ipa_includes,
+ libipa_includes,
+]
+
+vc4_ipa_sources = files([
+ 'vc4.cpp',
+])
+
+vc4_ipa_includes += include_directories('..')
+
+mod = shared_module(ipa_name,
+ [vc4_ipa_sources, libcamera_generated_ipa_headers],
+ name_prefix : '',
+ include_directories : vc4_ipa_includes,
+ dependencies : vc4_ipa_deps,
+ link_with : libipa,
+ link_whole : vc4_ipa_libs,
+ install : true,
+ install_dir : ipa_install_dir)
+
+if ipa_sign_module
+ custom_target(ipa_name + '.so.sign',
+ input : mod,
+ output : ipa_name + '.so.sign',
+ command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
+ install : false,
+ build_by_default : true)
+endif
+
+subdir('data')
+
+ipa_names += ipa_name
diff --git a/src/ipa/rpi/vc4/vc4.cpp b/src/ipa/rpi/vc4/vc4.cpp
new file mode 100644
index 00000000..d2159a51
--- /dev/null
+++ b/src/ipa/rpi/vc4/vc4.cpp
@@ -0,0 +1,597 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2021, Raspberry Pi Ltd
+ *
+ * rpi.cpp - Raspberry Pi VC4/BCM2835 ISP IPA.
+ */
+
+#include <string.h>
+#include <sys/mman.h>
+
+#include <linux/bcm2835-isp.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/ipa/ipa_module_info.h>
+
+#include "common/ipa_base.h"
+#include "controller/af_status.h"
+#include "controller/agc_algorithm.h"
+#include "controller/alsc_status.h"
+#include "controller/awb_status.h"
+#include "controller/black_level_status.h"
+#include "controller/ccm_status.h"
+#include "controller/contrast_status.h"
+#include "controller/denoise_algorithm.h"
+#include "controller/denoise_status.h"
+#include "controller/dpc_status.h"
+#include "controller/geq_status.h"
+#include "controller/lux_status.h"
+#include "controller/noise_status.h"
+#include "controller/sharpen_status.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPARPI)
+
+namespace ipa::RPi {
+
+class IpaVc4 final : public IpaBase
+{
+public:
+ IpaVc4()
+ : IpaBase(), lsTable_(nullptr)
+ {
+ }
+
+ ~IpaVc4()
+ {
+ if (lsTable_)
+ munmap(lsTable_, MaxLsGridSize);
+ }
+
+private:
+ int32_t platformInit(const InitParams &params, InitResult *result) override;
+ int32_t platformStart(const ControlList &controls, StartResult *result) override;
+ int32_t platformConfigure(const ConfigParams &params, ConfigResult *result) override;
+
+ void platformPrepareIsp(const PrepareParams &params, RPiController::Metadata &rpiMetadata) override;
+ RPiController::StatisticsPtr platformProcessStats(Span<uint8_t> mem) override;
+
+ void handleControls(const ControlList &controls) override;
+ bool validateIspControls();
+
+ void applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls);
+ void applyDG(const struct AgcPrepareStatus *dgStatus, ControlList &ctrls);
+ void applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls);
+ void applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls);
+ void applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls);
+ void applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls);
+ void applyDenoise(const struct DenoiseStatus *denoiseStatus, ControlList &ctrls);
+ void applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls);
+ void applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls);
+ void applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls);
+ void applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls);
+ void resampleTable(uint16_t dest[], const std::vector<double> &src, int destW, int destH);
+
+ /* VC4 ISP controls. */
+ ControlInfoMap ispCtrls_;
+
+ /* LS table allocation passed in from the pipeline handler. */
+ SharedFD lsTableHandle_;
+ void *lsTable_;
+};
+
+int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, [[maybe_unused]] InitResult *result)
+{
+ const std::string &target = controller_.getTarget();
+
+ if (target != "bcm2835") {
+ LOG(IPARPI, Error)
+ << "Tuning data file target returned \"" << target << "\""
+ << ", expected \"bcm2835\"";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int32_t IpaVc4::platformStart([[maybe_unused]] const ControlList &controls,
+ [[maybe_unused]] StartResult *result)
+{
+ return 0;
+}
+
+int32_t IpaVc4::platformConfigure(const ConfigParams &params, [[maybe_unused]] ConfigResult *result)
+{
+ ispCtrls_ = params.ispControls;
+ if (!validateIspControls()) {
+ LOG(IPARPI, Error) << "ISP control validation failed.";
+ return -1;
+ }
+
+ /* Store the lens shading table pointer and handle if available. */
+ if (params.lsTableHandle.isValid()) {
+ /* Remove any previous table, if there was one. */
+ if (lsTable_) {
+ munmap(lsTable_, MaxLsGridSize);
+ lsTable_ = nullptr;
+ }
+
+ /* Map the LS table buffer into user space. */
+ lsTableHandle_ = std::move(params.lsTableHandle);
+ if (lsTableHandle_.isValid()) {
+ lsTable_ = mmap(nullptr, MaxLsGridSize, PROT_READ | PROT_WRITE,
+ MAP_SHARED, lsTableHandle_.get(), 0);
+
+ if (lsTable_ == MAP_FAILED) {
+ LOG(IPARPI, Error) << "dmaHeap mmap failure for LS table.";
+ lsTable_ = nullptr;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void IpaVc4::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,
+ RPiController::Metadata &rpiMetadata)
+{
+ ControlList ctrls(ispCtrls_);
+
+ /* Lock the metadata buffer to avoid constant locks/unlocks. */
+ std::unique_lock<RPiController::Metadata> lock(rpiMetadata);
+
+ AwbStatus *awbStatus = rpiMetadata.getLocked<AwbStatus>("awb.status");
+ if (awbStatus)
+ applyAWB(awbStatus, ctrls);
+
+ CcmStatus *ccmStatus = rpiMetadata.getLocked<CcmStatus>("ccm.status");
+ if (ccmStatus)
+ applyCCM(ccmStatus, ctrls);
+
+ AgcPrepareStatus *dgStatus = rpiMetadata.getLocked<AgcPrepareStatus>("agc.prepare_status");
+ if (dgStatus)
+ applyDG(dgStatus, ctrls);
+
+ AlscStatus *lsStatus = rpiMetadata.getLocked<AlscStatus>("alsc.status");
+ if (lsStatus)
+ applyLS(lsStatus, ctrls);
+
+ ContrastStatus *contrastStatus = rpiMetadata.getLocked<ContrastStatus>("contrast.status");
+ if (contrastStatus)
+ applyGamma(contrastStatus, ctrls);
+
+ BlackLevelStatus *blackLevelStatus = rpiMetadata.getLocked<BlackLevelStatus>("black_level.status");
+ if (blackLevelStatus)
+ applyBlackLevel(blackLevelStatus, ctrls);
+
+ GeqStatus *geqStatus = rpiMetadata.getLocked<GeqStatus>("geq.status");
+ if (geqStatus)
+ applyGEQ(geqStatus, ctrls);
+
+ DenoiseStatus *denoiseStatus = rpiMetadata.getLocked<DenoiseStatus>("denoise.status");
+ if (denoiseStatus)
+ applyDenoise(denoiseStatus, ctrls);
+
+ SharpenStatus *sharpenStatus = rpiMetadata.getLocked<SharpenStatus>("sharpen.status");
+ if (sharpenStatus)
+ applySharpen(sharpenStatus, ctrls);
+
+ DpcStatus *dpcStatus = rpiMetadata.getLocked<DpcStatus>("dpc.status");
+ if (dpcStatus)
+ applyDPC(dpcStatus, ctrls);
+
+ const AfStatus *afStatus = rpiMetadata.getLocked<AfStatus>("af.status");
+ if (afStatus) {
+ ControlList lensctrls(lensCtrls_);
+ applyAF(afStatus, lensctrls);
+ if (!lensctrls.empty())
+ setLensControls.emit(lensctrls);
+ }
+
+ if (!ctrls.empty())
+ setIspControls.emit(ctrls);
+}
+
+RPiController::StatisticsPtr IpaVc4::platformProcessStats(Span<uint8_t> mem)
+{
+ using namespace RPiController;
+
+ const bcm2835_isp_stats *stats = reinterpret_cast<bcm2835_isp_stats *>(mem.data());
+ StatisticsPtr statistics = std::make_shared<Statistics>(Statistics::AgcStatsPos::PreWb,
+ Statistics::ColourStatsPos::PostLsc);
+ const Controller::HardwareConfig &hw = controller_.getHardwareConfig();
+ unsigned int i;
+
+ /* RGB histograms are not used, so do not populate them. */
+ statistics->yHist = RPiController::Histogram(stats->hist[0].g_hist,
+ hw.numHistogramBins);
+
+ /* All region sums are based on a 16-bit normalised pipeline bit-depth. */
+ unsigned int scale = Statistics::NormalisationFactorPow2 - hw.pipelineWidth;
+
+ statistics->awbRegions.init(hw.awbRegions);
+ for (i = 0; i < statistics->awbRegions.numRegions(); i++)
+ statistics->awbRegions.set(i, { { stats->awb_stats[i].r_sum << scale,
+ stats->awb_stats[i].g_sum << scale,
+ stats->awb_stats[i].b_sum << scale },
+ stats->awb_stats[i].counted,
+ stats->awb_stats[i].notcounted });
+
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Debug) << "No AGC algorithm - not copying statistics";
+ statistics->agcRegions.init(0);
+ } else {
+ statistics->agcRegions.init(hw.agcRegions);
+ const std::vector<double> &weights = agc->getWeights();
+ for (i = 0; i < statistics->agcRegions.numRegions(); i++) {
+ uint64_t rSum = (stats->agc_stats[i].r_sum << scale) * weights[i];
+ uint64_t gSum = (stats->agc_stats[i].g_sum << scale) * weights[i];
+ uint64_t bSum = (stats->agc_stats[i].b_sum << scale) * weights[i];
+ uint32_t counted = stats->agc_stats[i].counted * weights[i];
+ uint32_t notcounted = stats->agc_stats[i].notcounted * weights[i];
+ statistics->agcRegions.set(i, { { rSum, gSum, bSum },
+ counted,
+ notcounted });
+ }
+ }
+
+ statistics->focusRegions.init(hw.focusRegions);
+ for (i = 0; i < statistics->focusRegions.numRegions(); i++)
+ statistics->focusRegions.set(i, { stats->focus_stats[i].contrast_val[1][1] / 1000,
+ stats->focus_stats[i].contrast_val_num[1][1],
+ stats->focus_stats[i].contrast_val_num[1][0] });
+
+ if (statsMetadataOutput_) {
+ Span<const uint8_t> statsSpan(reinterpret_cast<const uint8_t *>(stats),
+ sizeof(bcm2835_isp_stats));
+ libcameraMetadata_.set(controls::rpi::Bcm2835StatsOutput, statsSpan);
+ }
+
+ return statistics;
+}
+
+void IpaVc4::handleControls(const ControlList &controls)
+{
+ static const std::map<int32_t, RPiController::DenoiseMode> DenoiseModeTable = {
+ { controls::draft::NoiseReductionModeOff, RPiController::DenoiseMode::Off },
+ { controls::draft::NoiseReductionModeFast, RPiController::DenoiseMode::ColourFast },
+ { controls::draft::NoiseReductionModeHighQuality, RPiController::DenoiseMode::ColourHighQuality },
+ { controls::draft::NoiseReductionModeMinimal, RPiController::DenoiseMode::ColourOff },
+ { controls::draft::NoiseReductionModeZSL, RPiController::DenoiseMode::ColourHighQuality },
+ };
+
+ for (auto const &ctrl : controls) {
+ switch (ctrl.first) {
+ case controls::draft::NOISE_REDUCTION_MODE: {
+ RPiController::DenoiseAlgorithm *sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>(
+ controller_.getAlgorithm("SDN"));
+ /* Some platforms may have a combined "denoise" algorithm instead. */
+ if (!sdn)
+ sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>(
+ controller_.getAlgorithm("denoise"));
+ if (!sdn) {
+ LOG(IPARPI, Warning)
+ << "Could not set NOISE_REDUCTION_MODE - no SDN algorithm";
+ return;
+ }
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ auto mode = DenoiseModeTable.find(idx);
+ if (mode != DenoiseModeTable.end())
+ sdn->setMode(mode->second);
+ break;
+ }
+ }
+ }
+}
+
+bool IpaVc4::validateIspControls()
+{
+ static const uint32_t ctrls[] = {
+ V4L2_CID_RED_BALANCE,
+ V4L2_CID_BLUE_BALANCE,
+ V4L2_CID_DIGITAL_GAIN,
+ V4L2_CID_USER_BCM2835_ISP_CC_MATRIX,
+ V4L2_CID_USER_BCM2835_ISP_GAMMA,
+ V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL,
+ V4L2_CID_USER_BCM2835_ISP_GEQ,
+ V4L2_CID_USER_BCM2835_ISP_DENOISE,
+ V4L2_CID_USER_BCM2835_ISP_SHARPEN,
+ V4L2_CID_USER_BCM2835_ISP_DPC,
+ V4L2_CID_USER_BCM2835_ISP_LENS_SHADING,
+ V4L2_CID_USER_BCM2835_ISP_CDN,
+ };
+
+ for (auto c : ctrls) {
+ if (ispCtrls_.find(c) == ispCtrls_.end()) {
+ LOG(IPARPI, Error) << "Unable to find ISP control "
+ << utils::hex(c);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void IpaVc4::applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls)
+{
+ LOG(IPARPI, Debug) << "Applying WB R: " << awbStatus->gainR << " B: "
+ << awbStatus->gainB;
+
+ ctrls.set(V4L2_CID_RED_BALANCE,
+ static_cast<int32_t>(awbStatus->gainR * 1000));
+ ctrls.set(V4L2_CID_BLUE_BALANCE,
+ static_cast<int32_t>(awbStatus->gainB * 1000));
+}
+
+void IpaVc4::applyDG(const struct AgcPrepareStatus *dgStatus, ControlList &ctrls)
+{
+ ctrls.set(V4L2_CID_DIGITAL_GAIN,
+ static_cast<int32_t>(dgStatus->digitalGain * 1000));
+}
+
+void IpaVc4::applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls)
+{
+ bcm2835_isp_custom_ccm ccm;
+
+ for (int i = 0; i < 9; i++) {
+ ccm.ccm.ccm[i / 3][i % 3].den = 1000;
+ ccm.ccm.ccm[i / 3][i % 3].num = 1000 * ccmStatus->matrix[i];
+ }
+
+ ccm.enabled = 1;
+ ccm.ccm.offsets[0] = ccm.ccm.offsets[1] = ccm.ccm.offsets[2] = 0;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&ccm),
+ sizeof(ccm) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_CC_MATRIX, c);
+}
+
+void IpaVc4::applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls)
+{
+ bcm2835_isp_black_level blackLevel;
+
+ blackLevel.enabled = 1;
+ blackLevel.black_level_r = blackLevelStatus->blackLevelR;
+ blackLevel.black_level_g = blackLevelStatus->blackLevelG;
+ blackLevel.black_level_b = blackLevelStatus->blackLevelB;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&blackLevel),
+ sizeof(blackLevel) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL, c);
+}
+
+void IpaVc4::applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls)
+{
+ const unsigned int numGammaPoints = controller_.getHardwareConfig().numGammaPoints;
+ struct bcm2835_isp_gamma gamma;
+
+ for (unsigned int i = 0; i < numGammaPoints - 1; i++) {
+ int x = i < 16 ? i * 1024
+ : (i < 24 ? (i - 16) * 2048 + 16384
+ : (i - 24) * 4096 + 32768);
+ gamma.x[i] = x;
+ gamma.y[i] = std::min<uint16_t>(65535, contrastStatus->gammaCurve.eval(x));
+ }
+
+ gamma.x[numGammaPoints - 1] = 65535;
+ gamma.y[numGammaPoints - 1] = 65535;
+ gamma.enabled = 1;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&gamma),
+ sizeof(gamma) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_GAMMA, c);
+}
+
+void IpaVc4::applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls)
+{
+ bcm2835_isp_geq geq;
+
+ geq.enabled = 1;
+ geq.offset = geqStatus->offset;
+ geq.slope.den = 1000;
+ geq.slope.num = 1000 * geqStatus->slope;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&geq),
+ sizeof(geq) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_GEQ, c);
+}
+
+void IpaVc4::applyDenoise(const struct DenoiseStatus *denoiseStatus, ControlList &ctrls)
+{
+ using RPiController::DenoiseMode;
+
+ bcm2835_isp_denoise denoise;
+ DenoiseMode mode = static_cast<DenoiseMode>(denoiseStatus->mode);
+
+ denoise.enabled = mode != DenoiseMode::Off;
+ denoise.constant = denoiseStatus->noiseConstant;
+ denoise.slope.num = 1000 * denoiseStatus->noiseSlope;
+ denoise.slope.den = 1000;
+ denoise.strength.num = 1000 * denoiseStatus->strength;
+ denoise.strength.den = 1000;
+
+ /* Set the CDN mode to match the SDN operating mode. */
+ bcm2835_isp_cdn cdn;
+ switch (mode) {
+ case DenoiseMode::ColourFast:
+ cdn.enabled = 1;
+ cdn.mode = CDN_MODE_FAST;
+ break;
+ case DenoiseMode::ColourHighQuality:
+ cdn.enabled = 1;
+ cdn.mode = CDN_MODE_HIGH_QUALITY;
+ break;
+ default:
+ cdn.enabled = 0;
+ }
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&denoise),
+ sizeof(denoise) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_DENOISE, c);
+
+ c = ControlValue(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&cdn),
+ sizeof(cdn) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_CDN, c);
+}
+
+void IpaVc4::applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls)
+{
+ bcm2835_isp_sharpen sharpen;
+
+ sharpen.enabled = 1;
+ sharpen.threshold.num = 1000 * sharpenStatus->threshold;
+ sharpen.threshold.den = 1000;
+ sharpen.strength.num = 1000 * sharpenStatus->strength;
+ sharpen.strength.den = 1000;
+ sharpen.limit.num = 1000 * sharpenStatus->limit;
+ sharpen.limit.den = 1000;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&sharpen),
+ sizeof(sharpen) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_SHARPEN, c);
+}
+
+void IpaVc4::applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls)
+{
+ bcm2835_isp_dpc dpc;
+
+ dpc.enabled = 1;
+ dpc.strength = dpcStatus->strength;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&dpc),
+ sizeof(dpc) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_DPC, c);
+}
+
+void IpaVc4::applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls)
+{
+ /*
+ * Program lens shading tables into pipeline.
+ * Choose smallest cell size that won't exceed 63x48 cells.
+ */
+ const int cellSizes[] = { 16, 32, 64, 128, 256 };
+ unsigned int numCells = std::size(cellSizes);
+ unsigned int i, w, h, cellSize;
+ for (i = 0; i < numCells; i++) {
+ cellSize = cellSizes[i];
+ w = (mode_.width + cellSize - 1) / cellSize;
+ h = (mode_.height + cellSize - 1) / cellSize;
+ if (w < 64 && h <= 48)
+ break;
+ }
+
+ if (i == numCells) {
+ LOG(IPARPI, Error) << "Cannot find cell size";
+ return;
+ }
+
+ /* We're going to supply corner sampled tables, 16 bit samples. */
+ w++, h++;
+ bcm2835_isp_lens_shading ls = {
+ .enabled = 1,
+ .grid_cell_size = cellSize,
+ .grid_width = w,
+ .grid_stride = w,
+ .grid_height = h,
+ /* .dmabuf will be filled in by pipeline handler. */
+ .dmabuf = 0,
+ .ref_transform = 0,
+ .corner_sampled = 1,
+ .gain_format = GAIN_FORMAT_U4P10
+ };
+
+ if (!lsTable_ || w * h * 4 * sizeof(uint16_t) > MaxLsGridSize) {
+ LOG(IPARPI, Error) << "Do not have a correctly allocate lens shading table!";
+ return;
+ }
+
+ if (lsStatus) {
+ /* Format will be u4.10 */
+ uint16_t *grid = static_cast<uint16_t *>(lsTable_);
+
+ resampleTable(grid, lsStatus->r, w, h);
+ resampleTable(grid + w * h, lsStatus->g, w, h);
+ memcpy(grid + 2 * w * h, grid + w * h, w * h * sizeof(uint16_t));
+ resampleTable(grid + 3 * w * h, lsStatus->b, w, h);
+ }
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&ls),
+ sizeof(ls) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING, c);
+}
+
+void IpaVc4::applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls)
+{
+ if (afStatus->lensSetting) {
+ ControlValue v(afStatus->lensSetting.value());
+ lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, v);
+ }
+}
+
+/*
+ * Resamples a 16x12 table with central sampling to destW x destH with corner
+ * sampling.
+ */
+void IpaVc4::resampleTable(uint16_t dest[], const std::vector<double> &src,
+ int destW, int destH)
+{
+ /*
+ * Precalculate and cache the x sampling locations and phases to
+ * save recomputing them on every row.
+ */
+ assert(destW > 1 && destH > 1 && destW <= 64);
+ int xLo[64], xHi[64];
+ double xf[64];
+ double x = -0.5, xInc = 16.0 / (destW - 1);
+ for (int i = 0; i < destW; i++, x += xInc) {
+ xLo[i] = floor(x);
+ xf[i] = x - xLo[i];
+ xHi[i] = xLo[i] < 15 ? xLo[i] + 1 : 15;
+ xLo[i] = xLo[i] > 0 ? xLo[i] : 0;
+ }
+
+ /* Now march over the output table generating the new values. */
+ double y = -0.5, yInc = 12.0 / (destH - 1);
+ for (int j = 0; j < destH; j++, y += yInc) {
+ int yLo = floor(y);
+ double yf = y - yLo;
+ int yHi = yLo < 11 ? yLo + 1 : 11;
+ yLo = yLo > 0 ? yLo : 0;
+ double const *rowAbove = src.data() + yLo * 16;
+ double const *rowBelow = src.data() + yHi * 16;
+ for (int i = 0; i < destW; i++) {
+ double above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i];
+ double below = rowBelow[xLo[i]] * (1 - xf[i]) + rowBelow[xHi[i]] * xf[i];
+ int result = floor(1024 * (above * (1 - yf) + below * yf) + .5);
+ *(dest++) = result > 16383 ? 16383 : result; /* want u4.10 */
+ }
+ }
+}
+
+} /* namespace ipa::RPi */
+
+/*
+ * External IPA module interface
+ */
+extern "C" {
+const struct IPAModuleInfo ipaModuleInfo = {
+ IPA_MODULE_API_VERSION,
+ 1,
+ "PipelineHandlerVc4",
+ "rpi/vc4",
+};
+
+IPAInterface *ipaCreate()
+{
+ return new ipa::RPi::IpaVc4();
+}
+
+} /* extern "C" */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/black_level.cpp b/src/ipa/simple/black_level.cpp
new file mode 100644
index 00000000..c7e8d8b7
--- /dev/null
+++ b/src/ipa/simple/black_level.cpp
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * black_level.cpp - black level handling
+ */
+
+#include "black_level.h"
+
+#include <numeric>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPASoftBL)
+
+/**
+ * \class BlackLevel
+ * \brief Object providing black point level for software ISP
+ *
+ * Black level can be provided in hardware tuning files or, if no tuning file is
+ * available for the given hardware, guessed automatically, with less accuracy.
+ * As tuning files are not yet implemented for software ISP, BlackLevel
+ * currently provides only guessed black levels.
+ *
+ * This class serves for tracking black level as a property of the underlying
+ * hardware, not as means of enhancing a particular scene or image.
+ *
+ * The class is supposed to be instantiated for the given camera stream.
+ * The black level can be retrieved using BlackLevel::get() method. It is
+ * initially 0 and may change when updated using BlackLevel::update() method.
+ */
+
+BlackLevel::BlackLevel()
+ : blackLevel_(255), blackLevelSet_(false)
+{
+}
+
+/**
+ * \brief Return the current black level
+ *
+ * \return The black level, in the range from 0 (minimum) to 255 (maximum).
+ * If the black level couldn't be determined yet, return 0.
+ */
+unsigned int BlackLevel::get() const
+{
+ return blackLevelSet_ ? blackLevel_ : 0;
+}
+
+/**
+ * \brief Update black level from the provided histogram
+ * \param[in] yHistogram The histogram to be used for updating black level
+ *
+ * The black level is property of the given hardware, not image. It is updated
+ * only if it has not been yet set or if it is lower than the lowest value seen
+ * so far.
+ */
+void BlackLevel::update(SwIspStats::Histogram &yHistogram)
+{
+ /*
+ * The constant is selected to be "good enough", not overly conservative or
+ * aggressive. There is no magic about the given value.
+ */
+ constexpr float ignoredPercentage_ = 0.02;
+ const unsigned int total =
+ std::accumulate(begin(yHistogram), end(yHistogram), 0);
+ const unsigned int pixelThreshold = ignoredPercentage_ * total;
+ const unsigned int histogramRatio = 256 / SwIspStats::kYHistogramSize;
+ const unsigned int currentBlackIdx = blackLevel_ / histogramRatio;
+
+ for (unsigned int i = 0, seen = 0;
+ i < currentBlackIdx && i < SwIspStats::kYHistogramSize;
+ i++) {
+ seen += yHistogram[i];
+ if (seen >= pixelThreshold) {
+ blackLevel_ = i * histogramRatio;
+ blackLevelSet_ = true;
+ LOG(IPASoftBL, Debug)
+ << "Auto-set black level: "
+ << i << "/" << SwIspStats::kYHistogramSize
+ << " (" << 100 * (seen - yHistogram[i]) / total << "% below, "
+ << 100 * seen / total << "% at or below)";
+ break;
+ }
+ };
+}
+} /* namespace libcamera */
diff --git a/src/ipa/simple/black_level.h b/src/ipa/simple/black_level.h
new file mode 100644
index 00000000..7e37757e
--- /dev/null
+++ b/src/ipa/simple/black_level.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * black_level.h - black level handling
+ */
+
+#pragma once
+
+#include <array>
+
+#include "libcamera/internal/software_isp/swisp_stats.h"
+
+namespace libcamera {
+
+class BlackLevel
+{
+public:
+ BlackLevel();
+ unsigned int get() const;
+ void update(SwIspStats::Histogram &yHistogram);
+
+private:
+ unsigned int blackLevel_;
+ bool blackLevelSet_;
+};
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/data/meson.build b/src/ipa/simple/data/meson.build
new file mode 100644
index 00000000..92795ee4
--- /dev/null
+++ b/src/ipa/simple/data/meson.build
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: CC0-1.0
+
+conf_files = files([
+ 'uncalibrated.yaml',
+])
+
+# The install_dir must match the name from the IPAModuleInfo
+install_data(conf_files,
+ install_dir : ipa_data_dir / 'simple',
+ install_tag : 'runtime')
diff --git a/test/pipeline/meson.build b/src/ipa/simple/data/uncalibrated.yaml
index 6e7901fe..ff981a1a 100644
--- a/test/pipeline/meson.build
+++ b/src/ipa/simple/data/uncalibrated.yaml
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-
-subdir('ipu3')
-subdir('rkisp1')
+%YAML 1.1
+---
+version: 1
+...
diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
new file mode 100644
index 00000000..44b5f1d7
--- /dev/null
+++ b/src/ipa/simple/meson.build
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: CC0-1.0
+
+ipa_name = 'ipa_soft_simple'
+
+soft_simple_sources = files([
+ 'soft_simple.cpp',
+ 'black_level.cpp',
+])
+
+mod = shared_module(ipa_name,
+ [soft_simple_sources, libcamera_generated_ipa_headers],
+ name_prefix : '',
+ include_directories : [ipa_includes, libipa_includes],
+ dependencies : libcamera_private,
+ link_with : libipa,
+ install : true,
+ install_dir : ipa_install_dir)
+
+if ipa_sign_module
+ custom_target(ipa_name + '.so.sign',
+ input : mod,
+ output : ipa_name + '.so.sign',
+ command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
+ install : false,
+ build_by_default : true)
+endif
+
+subdir('data')
+
+ipa_names += ipa_name
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
new file mode 100644
index 00000000..b9fb58b5
--- /dev/null
+++ b/src/ipa/simple/soft_simple.cpp
@@ -0,0 +1,403 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * soft_simple.cpp - Simple Software Image Processing Algorithm module
+ */
+
+#include <sys/mman.h>
+
+#include <linux/v4l2-controls.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/shared_fd.h>
+
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/ipa_module_info.h>
+#include <libcamera/ipa/soft_ipa_interface.h>
+
+#include "libcamera/internal/software_isp/debayer_params.h"
+#include "libcamera/internal/software_isp/swisp_stats.h"
+#include "libcamera/internal/yaml_parser.h"
+
+#include "libipa/camera_sensor_helper.h"
+
+#include "black_level.h"
+
+namespace libcamera {
+LOG_DEFINE_CATEGORY(IPASoft)
+
+namespace ipa::soft {
+
+/*
+ * The number of bins to use for the optimal exposure calculations.
+ */
+static constexpr unsigned int kExposureBinsCount = 5;
+
+/*
+ * The exposure is optimal when the mean sample value of the histogram is
+ * in the middle of the range.
+ */
+static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
+
+/*
+ * The below value implements the hysteresis for the exposure adjustment.
+ * It is small enough to have the exposure close to the optimal, and is big
+ * enough to prevent the exposure from wobbling around the optimal value.
+ */
+static constexpr float kExposureSatisfactory = 0.2;
+
+class IPASoftSimple : public ipa::soft::IPASoftInterface
+{
+public:
+ IPASoftSimple()
+ : params_(nullptr), stats_(nullptr), blackLevel_(BlackLevel()),
+ ignoreUpdates_(0)
+ {
+ }
+
+ ~IPASoftSimple();
+
+ int init(const IPASettings &settings,
+ const SharedFD &fdStats,
+ const SharedFD &fdParams,
+ const ControlInfoMap &sensorInfoMap) override;
+ int configure(const ControlInfoMap &sensorInfoMap) override;
+
+ int start() override;
+ void stop() override;
+
+ void processStats(const ControlList &sensorControls) override;
+
+private:
+ void updateExposure(double exposureMSV);
+
+ DebayerParams *params_;
+ SwIspStats *stats_;
+ std::unique_ptr<CameraSensorHelper> camHelper_;
+ ControlInfoMap sensorInfoMap_;
+ BlackLevel blackLevel_;
+
+ int32_t exposureMin_, exposureMax_;
+ int32_t exposure_;
+ double againMin_, againMax_, againMinStep_;
+ double again_;
+ unsigned int ignoreUpdates_;
+};
+
+IPASoftSimple::~IPASoftSimple()
+{
+ if (stats_)
+ munmap(stats_, sizeof(SwIspStats));
+ if (params_)
+ munmap(params_, sizeof(DebayerParams));
+}
+
+int IPASoftSimple::init(const IPASettings &settings,
+ const SharedFD &fdStats,
+ const SharedFD &fdParams,
+ const ControlInfoMap &sensorInfoMap)
+{
+ camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+ if (!camHelper_) {
+ LOG(IPASoft, Warning)
+ << "Failed to create camera sensor helper for "
+ << settings.sensorModel;
+ }
+
+ /* Load the tuning data file */
+ File file(settings.configurationFile);
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ int ret = file.error();
+ LOG(IPASoft, Error)
+ << "Failed to open configuration file "
+ << settings.configurationFile << ": " << strerror(-ret);
+ return ret;
+ }
+
+ std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+ if (!data)
+ return -EINVAL;
+
+ /* \todo Use the IPA configuration file for real. */
+ unsigned int version = (*data)["version"].get<uint32_t>(0);
+ LOG(IPASoft, Debug) << "Tuning file version " << version;
+
+ params_ = nullptr;
+ stats_ = nullptr;
+
+ if (!fdStats.isValid()) {
+ LOG(IPASoft, Error) << "Invalid Statistics handle";
+ return -ENODEV;
+ }
+
+ if (!fdParams.isValid()) {
+ LOG(IPASoft, Error) << "Invalid Parameters handle";
+ return -ENODEV;
+ }
+
+ {
+ void *mem = mmap(nullptr, sizeof(DebayerParams), PROT_WRITE,
+ MAP_SHARED, fdParams.get(), 0);
+ if (mem == MAP_FAILED) {
+ LOG(IPASoft, Error) << "Unable to map Parameters";
+ return -errno;
+ }
+
+ params_ = static_cast<DebayerParams *>(mem);
+ }
+
+ {
+ void *mem = mmap(nullptr, sizeof(SwIspStats), PROT_READ,
+ MAP_SHARED, fdStats.get(), 0);
+ if (mem == MAP_FAILED) {
+ LOG(IPASoft, Error) << "Unable to map Statistics";
+ return -errno;
+ }
+
+ stats_ = static_cast<SwIspStats *>(mem);
+ }
+
+ /*
+ * Check if the sensor driver supports the controls required by the
+ * Soft IPA.
+ * Don't save the min and max control values yet, as e.g. the limits
+ * for V4L2_CID_EXPOSURE depend on the configured sensor resolution.
+ */
+ if (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) {
+ LOG(IPASoft, Error) << "Don't have exposure control";
+ return -EINVAL;
+ }
+
+ if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) {
+ LOG(IPASoft, Error) << "Don't have gain control";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
+{
+ sensorInfoMap_ = sensorInfoMap;
+
+ const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;
+ const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;
+
+ exposureMin_ = exposureInfo.min().get<int32_t>();
+ exposureMax_ = exposureInfo.max().get<int32_t>();
+ if (!exposureMin_) {
+ LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
+ exposureMin_ = 1;
+ }
+
+ int32_t againMin = gainInfo.min().get<int32_t>();
+ int32_t againMax = gainInfo.max().get<int32_t>();
+
+ if (camHelper_) {
+ againMin_ = camHelper_->gain(againMin);
+ againMax_ = camHelper_->gain(againMax);
+ againMinStep_ = (againMax_ - againMin_) / 100.0;
+ } else {
+ /*
+ * The camera sensor gain (g) is usually not equal to the value written
+ * into the gain register (x). But the way how the AGC algorithm changes
+ * the gain value to make the total exposure closer to the optimum
+ * assumes that g(x) is not too far from linear function. If the minimal
+ * gain is 0, the g(x) is likely to be far from the linear, like
+ * g(x) = a / (b * x + c). To avoid unexpected changes to the gain by
+ * the AGC algorithm (abrupt near one edge, and very small near the
+ * other) we limit the range of the gain values used.
+ */
+ againMax_ = againMax;
+ if (!againMin) {
+ LOG(IPASoft, Warning)
+ << "Minimum gain is zero, that can't be linear";
+ againMin_ = std::min(100, againMin / 2 + againMax / 2);
+ }
+ againMinStep_ = 1.0;
+ }
+
+ LOG(IPASoft, Info) << "Exposure " << exposureMin_ << "-" << exposureMax_
+ << ", gain " << againMin_ << "-" << againMax_
+ << " (" << againMinStep_ << ")";
+
+ return 0;
+}
+
+int IPASoftSimple::start()
+{
+ return 0;
+}
+
+void IPASoftSimple::stop()
+{
+}
+
+void IPASoftSimple::processStats(const ControlList &sensorControls)
+{
+ /*
+ * Calculate red and blue gains for AWB.
+ * Clamp max gain at 4.0, this also avoids 0 division.
+ */
+ if (stats_->sumR_ <= stats_->sumG_ / 4)
+ params_->gainR = 1024;
+ else
+ params_->gainR = 256 * stats_->sumG_ / stats_->sumR_;
+
+ if (stats_->sumB_ <= stats_->sumG_ / 4)
+ params_->gainB = 1024;
+ else
+ params_->gainB = 256 * stats_->sumG_ / stats_->sumB_;
+
+ /* Green gain and gamma values are fixed */
+ params_->gainG = 256;
+ params_->gamma = 0.5;
+
+ if (ignoreUpdates_ > 0)
+ blackLevel_.update(stats_->yHistogram);
+ params_->blackLevel = blackLevel_.get();
+
+ setIspParams.emit();
+
+ /* \todo Switch to the libipa/algorithm.h API someday. */
+
+ /*
+ * AE / AGC, use 2 frames delay to make sure that the exposure and
+ * the gain set have applied to the camera sensor.
+ * \todo This could be handled better with DelayedControls.
+ */
+ if (ignoreUpdates_ > 0) {
+ --ignoreUpdates_;
+ return;
+ }
+
+ /*
+ * Calculate Mean Sample Value (MSV) according to formula from:
+ * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+ */
+ const unsigned int blackLevelHistIdx =
+ params_->blackLevel / (256 / SwIspStats::kYHistogramSize);
+ const unsigned int histogramSize =
+ SwIspStats::kYHistogramSize - blackLevelHistIdx;
+ const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
+ const unsigned int yHistValsPerBinMod =
+ histogramSize / (histogramSize % kExposureBinsCount + 1);
+ int exposureBins[kExposureBinsCount] = {};
+ unsigned int denom = 0;
+ unsigned int num = 0;
+
+ for (unsigned int i = 0; i < histogramSize; i++) {
+ unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
+ exposureBins[idx] += stats_->yHistogram[blackLevelHistIdx + i];
+ }
+
+ for (unsigned int i = 0; i < kExposureBinsCount; i++) {
+ LOG(IPASoft, Debug) << i << ": " << exposureBins[i];
+ denom += exposureBins[i];
+ num += exposureBins[i] * (i + 1);
+ }
+
+ float exposureMSV = static_cast<float>(num) / denom;
+
+ /* Sanity check */
+ if (!sensorControls.contains(V4L2_CID_EXPOSURE) ||
+ !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) {
+ LOG(IPASoft, Error) << "Control(s) missing";
+ return;
+ }
+
+ exposure_ = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+ int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
+ again_ = camHelper_ ? camHelper_->gain(again) : again;
+
+ updateExposure(exposureMSV);
+
+ ControlList ctrls(sensorInfoMap_);
+
+ ctrls.set(V4L2_CID_EXPOSURE, exposure_);
+ ctrls.set(V4L2_CID_ANALOGUE_GAIN,
+ static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(again_) : again_));
+
+ ignoreUpdates_ = 2;
+
+ setSensorControls.emit(ctrls);
+
+ LOG(IPASoft, Debug) << "exposureMSV " << exposureMSV
+ << " exp " << exposure_ << " again " << again_
+ << " gain R/B " << params_->gainR << "/" << params_->gainB
+ << " black level " << params_->blackLevel;
+}
+
+void IPASoftSimple::updateExposure(double exposureMSV)
+{
+ /*
+ * kExpDenominator of 10 gives ~10% increment/decrement;
+ * kExpDenominator of 5 - about ~20%
+ */
+ static constexpr uint8_t kExpDenominator = 10;
+ static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
+ static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
+
+ double next;
+
+ if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
+ next = exposure_ * kExpNumeratorUp / kExpDenominator;
+ if (next - exposure_ < 1)
+ exposure_ += 1;
+ else
+ exposure_ = next;
+ if (exposure_ >= exposureMax_) {
+ next = again_ * kExpNumeratorUp / kExpDenominator;
+ if (next - again_ < againMinStep_)
+ again_ += againMinStep_;
+ else
+ again_ = next;
+ }
+ }
+
+ if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
+ if (exposure_ == exposureMax_ && again_ > againMin_) {
+ next = again_ * kExpNumeratorDown / kExpDenominator;
+ if (again_ - next < againMinStep_)
+ again_ -= againMinStep_;
+ else
+ again_ = next;
+ } else {
+ next = exposure_ * kExpNumeratorDown / kExpDenominator;
+ if (exposure_ - next < 1)
+ exposure_ -= 1;
+ else
+ exposure_ = next;
+ }
+ }
+
+ exposure_ = std::clamp(exposure_, exposureMin_, exposureMax_);
+ again_ = std::clamp(again_, againMin_, againMax_);
+}
+
+} /* namespace ipa::soft */
+
+/*
+ * External IPA module interface
+ */
+extern "C" {
+const struct IPAModuleInfo ipaModuleInfo = {
+ IPA_MODULE_API_VERSION,
+ 0,
+ "SimplePipelineHandler",
+ "simple",
+};
+
+IPAInterface *ipaCreate()
+{
+ return new ipa::soft::IPASoftSimple();
+}
+
+} /* extern "C" */
+
+} /* namespace libcamera */
diff --git a/src/ipa/vimc/data/meson.build b/src/ipa/vimc/data/meson.build
index 42ec651c..628d6a29 100644
--- a/src/ipa/vimc/data/meson.build
+++ b/src/ipa/vimc/data/meson.build
@@ -5,4 +5,5 @@ conf_files = files([
])
install_data(conf_files,
- install_dir : ipa_data_dir / 'vimc')
+ install_dir : ipa_data_dir / 'vimc',
+ install_tag : 'runtime')
diff --git a/src/ipa/vimc/meson.build b/src/ipa/vimc/meson.build
index ecbeee13..264a2d9a 100644
--- a/src/ipa/vimc/meson.build
+++ b/src/ipa/vimc/meson.build
@@ -21,3 +21,5 @@ if ipa_sign_module
endif
subdir('data')
+
+ipa_names += ipa_name
diff --git a/src/ipa/vimc/vimc.cpp b/src/ipa/vimc/vimc.cpp
index 85afb279..2c255778 100644
--- a/src/ipa/vimc/vimc.cpp
+++ b/src/ipa/vimc/vimc.cpp
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * ipa_vimc.cpp - Vimc Image Processing Algorithm module
+ * vimc.cpp - Vimc Image Processing Algorithm module
*/
#include <libcamera/ipa/vimc_ipa_interface.h>
@@ -31,7 +31,10 @@ public:
IPAVimc();
~IPAVimc();
- int init(const IPASettings &settings) override;
+ int init(const IPASettings &settings,
+ const ipa::vimc::IPAOperationCode code,
+ const Flags<ipa::vimc::TestFlag> inFlags,
+ Flags<ipa::vimc::TestFlag> *outFlags) override;
int start() override;
void stop() override;
@@ -66,7 +69,10 @@ IPAVimc::~IPAVimc()
::close(fd_);
}
-int IPAVimc::init(const IPASettings &settings)
+int IPAVimc::init(const IPASettings &settings,
+ const ipa::vimc::IPAOperationCode code,
+ const Flags<ipa::vimc::TestFlag> inFlags,
+ Flags<ipa::vimc::TestFlag> *outFlags)
{
trace(ipa::vimc::IPAOperationInit);
@@ -74,6 +80,15 @@ int IPAVimc::init(const IPASettings &settings)
<< "initializing vimc IPA with configuration file "
<< settings.configurationFile;
+ LOG(IPAVimc, Debug) << "Got opcode " << code;
+
+ LOG(IPAVimc, Debug)
+ << "Flag 2 was "
+ << (inFlags & ipa::vimc::TestFlag::Flag2 ? "" : "not ")
+ << "set";
+
+ *outFlags |= ipa::vimc::TestFlag::Flag1;
+
File conf(settings.configurationFile);
if (!conf.open(File::OpenModeFlag::ReadOnly)) {
LOG(IPAVimc, Error) << "Failed to open configuration file";
@@ -142,7 +157,8 @@ void IPAVimc::fillParamsBuffer([[maybe_unused]] uint32_t frame, uint32_t bufferI
return;
}
- paramsBufferReady.emit(bufferId);
+ Flags<ipa::vimc::TestFlag> flags;
+ paramsBufferReady.emit(bufferId, flags);
}
void IPAVimc::initTrace()
@@ -152,7 +168,7 @@ void IPAVimc::initTrace()
if (ret)
return;
- ret = ::open(ipa::vimc::VimcIPAFIFOPath.c_str(), O_WRONLY);
+ ret = ::open(ipa::vimc::VimcIPAFIFOPath.c_str(), O_WRONLY | O_CLOEXEC);
if (ret < 0) {
ret = errno;
LOG(IPAVimc, Error) << "Failed to open vimc IPA test FIFO: "
diff --git a/src/libcamera/base/backtrace.cpp b/src/libcamera/base/backtrace.cpp
index 483492c3..be30589d 100644
--- a/src/libcamera/base/backtrace.cpp
+++ b/src/libcamera/base/backtrace.cpp
@@ -191,10 +191,22 @@ __attribute__((__noinline__))
bool Backtrace::unwindTrace()
{
#if HAVE_UNWIND
+/*
+ * unw_getcontext() for ARM32 is an inline assembly function using the stmia
+ * instruction to store SP and PC. This is considered by clang-11 as deprecated,
+ * and generates a warning.
+ */
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Winline-asm"
+#endif
unw_context_t uc;
int ret = unw_getcontext(&uc);
if (ret)
return false;
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
unw_cursor_t cursor;
ret = unw_init_local(&cursor, &uc);
diff --git a/src/libcamera/base/bound_method.cpp b/src/libcamera/base/bound_method.cpp
index 3ecec51c..c83d623f 100644
--- a/src/libcamera/base/bound_method.cpp
+++ b/src/libcamera/base/bound_method.cpp
@@ -7,6 +7,7 @@
#include <libcamera/base/bound_method.h>
#include <libcamera/base/message.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/semaphore.h>
#include <libcamera/base/thread.h>
diff --git a/src/libcamera/base/event_notifier.cpp b/src/libcamera/base/event_notifier.cpp
index fd93c087..a519aec3 100644
--- a/src/libcamera/base/event_notifier.cpp
+++ b/src/libcamera/base/event_notifier.cpp
@@ -8,6 +8,7 @@
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/log.h>
#include <libcamera/base/message.h>
#include <libcamera/base/thread.h>
@@ -20,6 +21,8 @@
namespace libcamera {
+LOG_DECLARE_CATEGORY(Event)
+
/**
* \class EventNotifier
* \brief Notify of activity on a file descriptor
@@ -104,6 +107,9 @@ EventNotifier::~EventNotifier()
*/
void EventNotifier::setEnabled(bool enable)
{
+ if (!assertThreadBound("EventNotifier can't be enabled from another thread"))
+ return;
+
if (enabled_ == enable)
return;
diff --git a/src/libcamera/base/file.cpp b/src/libcamera/base/file.cpp
index fb3e276d..d1ab1aa5 100644
--- a/src/libcamera/base/file.cpp
+++ b/src/libcamera/base/file.cpp
@@ -163,6 +163,9 @@ bool File::exists() const
* attempt to create the file with initial permissions set to 0666 (modified by
* the process' umask).
*
+ * The file is opened with the O_CLOEXEC flag, and will be closed automatically
+ * when a new binary is executed with one of the exec(3) functions.
+ *
* The error() status is updated.
*
* \return True on success, false otherwise
@@ -178,7 +181,7 @@ bool File::open(File::OpenMode mode)
if (mode & OpenModeFlag::WriteOnly)
flags |= O_CREAT;
- fd_ = UniqueFD(::open(name_.c_str(), flags, 0666));
+ fd_ = UniqueFD(::open(name_.c_str(), flags | O_CLOEXEC, 0666));
if (!fd_.isValid()) {
error_ = -errno;
return false;
diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
index 5c359a22..c8045ef7 100644
--- a/src/libcamera/base/log.cpp
+++ b/src/libcamera/base/log.cpp
@@ -21,6 +21,7 @@
#include <libcamera/logging.h>
#include <libcamera/base/backtrace.h>
+#include <libcamera/base/mutex.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/utils.h>
@@ -314,10 +315,11 @@ private:
friend LogCategory;
void registerCategory(LogCategory *category);
+ LogCategory *findCategory(const char *name) const;
static bool destroyed_;
- std::unordered_set<LogCategory *> categories_;
+ std::vector<LogCategory *> categories_;
std::list<std::pair<std::string, LogSeverity>> levels_;
std::shared_ptr<LogOutput> output_;
@@ -568,7 +570,7 @@ void Logger::logSetLevel(const char *category, const char *level)
return;
for (LogCategory *c : categories_) {
- if (!strcmp(c->name(), category)) {
+ if (c->name() == category) {
c->setSeverity(severity);
break;
}
@@ -707,12 +709,12 @@ LogSeverity Logger::parseLogLevel(const std::string &level)
* \brief Register a log category with the logger
* \param[in] category The log category
*
- * Log categories must have unique names. If a category with the same name
- * already exists this function performs no operation.
+ * Log categories must have unique names. It is invalid to call this function
+ * if a log category with the same name already exists.
*/
void Logger::registerCategory(LogCategory *category)
{
- categories_.insert(category);
+ categories_.push_back(category);
const std::string &name = category->name();
for (const std::pair<std::string, LogSeverity> &level : levels_) {
@@ -737,6 +739,22 @@ void Logger::registerCategory(LogCategory *category)
}
/**
+ * \brief Find an existing log category with the given name
+ * \param[in] name Name of the log category
+ * \return The pointer to the found log category or nullptr if not found
+ */
+LogCategory *Logger::findCategory(const char *name) const
+{
+ if (auto it = std::find_if(categories_.begin(), categories_.end(),
+ [name](auto c) { return c->name() == name; });
+ it != categories_.end()) {
+ return *it;
+ }
+
+ return nullptr;
+}
+
+/**
* \enum LogSeverity
* Log message severity
* \var LogDebug
@@ -761,13 +779,35 @@ void Logger::registerCategory(LogCategory *category)
*/
/**
+ * \brief Create a new LogCategory or return an existing one
+ * \param[in] name Name of the log category
+ *
+ * Create and return a new LogCategory with the given name if such a category
+ * does not yet exist, or return the existing one.
+ *
+ * \return The pointer to the LogCategory
+ */
+LogCategory *LogCategory::create(const char *name)
+{
+ static Mutex mutex_;
+ MutexLocker locker(mutex_);
+ LogCategory *category = Logger::instance()->findCategory(name);
+
+ if (!category) {
+ category = new LogCategory(name);
+ Logger::instance()->registerCategory(category);
+ }
+
+ return category;
+}
+
+/**
* \brief Construct a log category
* \param[in] name The category name
*/
LogCategory::LogCategory(const char *name)
: name_(name), severity_(LogSeverity::LogInfo)
{
- Logger::instance()->registerCategory(this);
}
/**
@@ -804,7 +844,7 @@ void LogCategory::setSeverity(LogSeverity severity)
*/
const LogCategory &LogCategory::defaultCategory()
{
- static const LogCategory *category = new LogCategory("default");
+ static const LogCategory *category = LogCategory::create("default");
return *category;
}
diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
index 7030ad1f..7a7fd7e4 100644
--- a/src/libcamera/base/meson.build
+++ b/src/libcamera/base/meson.build
@@ -22,8 +22,8 @@ libcamera_base_sources = files([
'utils.cpp',
])
-libdw = cc.find_library('libdw', required : false)
-libunwind = cc.find_library('libunwind', required : false)
+libdw = dependency('libdw', required : false)
+libunwind = dependency('libunwind', required : false)
if cc.has_header_symbol('execinfo.h', 'backtrace')
config_h.set('HAVE_BACKTRACE', 1)
@@ -38,9 +38,9 @@ if libunwind.found()
endif
libcamera_base_deps = [
- dependency('threads'),
libatomic,
libdw,
+ libthreads,
libunwind,
]
@@ -51,6 +51,7 @@ libcamera_base_args = [ '-DLIBCAMERA_BASE_PRIVATE' ]
libcamera_base_lib = shared_library('libcamera-base',
[libcamera_base_sources, libcamera_base_headers],
version : libcamera_version,
+ soversion : libcamera_soversion,
name_prefix : '',
install : true,
cpp_args : libcamera_base_args,
diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp
index 92cecd22..81054b58 100644
--- a/src/libcamera/base/object.cpp
+++ b/src/libcamera/base/object.cpp
@@ -40,8 +40,9 @@ LOG_DEFINE_CATEGORY(Object)
* Object class.
*
* Deleting an object from a thread other than the one the object is bound to is
- * unsafe, unless the caller ensures that the object isn't processing any
- * message concurrently.
+ * unsafe, unless the caller ensures that the object's thread is stopped and no
+ * parent or child of the object gets deleted concurrently. See
+ * Object::~Object() for more information.
*
* Object slots connected to signals will also run in the context of the
* object's thread, regardless of whether the signal is emitted in the same or
@@ -84,9 +85,20 @@ Object::Object(Object *parent)
* Object instances shall be destroyed from the thread they are bound to,
* otherwise undefined behaviour may occur. If deletion of an Object needs to
* be scheduled from a different thread, deleteLater() shall be used.
+ *
+ * As an exception to this rule, Object instances may be deleted from a
+ * different thread if the thread the instance is bound to is stopped through
+ * the whole duration of the object's destruction, *and* the parent and children
+ * of the object do not get deleted concurrently. The caller is responsible for
+ * fulfilling those requirements.
+ *
+ * In all cases Object instances shall be deleted before the Thread they are
+ * bound to.
*/
Object::~Object()
{
+ ASSERT(Thread::current() == thread_ || !thread_->isRunning());
+
/*
* Move signals to a private list to avoid concurrent iteration and
* deletion of items from Signal::disconnect().
@@ -116,8 +128,9 @@ Object::~Object()
* event loop that the object belongs to. This ensures the object is destroyed
* from the right context, as required by the libcamera threading model.
*
- * If this function is called before the thread's event loop is started, the
- * object will be deleted when the event loop starts.
+ * If this function is called before the thread's event loop is started or after
+ * it has stopped, the object will be deleted when the event loop (re)starts. If
+ * this never occurs, the object will be leaked.
*
* Deferred deletion can be used to control the destruction context with shared
* pointers. An object managed with shared pointers is deleted when the last
@@ -213,6 +226,35 @@ void Object::message(Message *msg)
}
/**
+ * \fn Object::assertThreadBound()
+ * \brief Check if the caller complies with thread-bound constraints
+ * \param[in] message The message to be printed on error
+ *
+ * This function verifies the calling constraints required by the \threadbound
+ * definition. It shall be called at the beginning of member functions of an
+ * Object subclass that are explicitly marked as thread-bound in their
+ * documentation.
+ *
+ * If the thread-bound constraints are not met, the function prints \a message
+ * as an error message. For debug builds, it additionally causes an assertion
+ * error.
+ *
+ * \todo Verify the thread-bound requirements for functions marked as
+ * thread-bound at the class level.
+ *
+ * \return True if the call is thread-bound compliant, false otherwise
+ */
+bool Object::assertThreadBound(const char *message)
+{
+ if (Thread::current() == thread_)
+ return true;
+
+ LOG(Object, Error) << message;
+ ASSERT(false);
+ return false;
+}
+
+/**
* \fn R Object::invokeMethod()
* \brief Invoke a method asynchronously on an Object instance
* \param[in] func The object method to invoke
@@ -259,11 +301,12 @@ void Object::message(Message *msg)
* Moving an object that has a parent is not allowed, and causes undefined
* behaviour.
*
- * \context This function is thread-bound.
+ * \context This function is \threadbound.
*/
void Object::moveToThread(Thread *thread)
{
- ASSERT(Thread::current() == thread_);
+ if (!assertThreadBound("Object can't be moved from another thread"))
+ return;
if (thread_ == thread)
return;
diff --git a/src/libcamera/base/semaphore.cpp b/src/libcamera/base/semaphore.cpp
index 4fe30293..6217e386 100644
--- a/src/libcamera/base/semaphore.cpp
+++ b/src/libcamera/base/semaphore.cpp
@@ -56,7 +56,9 @@ unsigned int Semaphore::available()
void Semaphore::acquire(unsigned int n)
{
MutexLocker locker(mutex_);
- cv_.wait(locker, [&] { return available_ >= n; });
+ cv_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(mutex_) {
+ return available_ >= n;
+ });
available_ -= n;
}
diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp
index a46386a0..f1018b37 100644
--- a/src/libcamera/base/signal.cpp
+++ b/src/libcamera/base/signal.cpp
@@ -8,6 +8,7 @@
#include <libcamera/base/signal.h>
#include <libcamera/base/mutex.h>
+#include <libcamera/base/object.h>
/**
* \file base/signal.h
@@ -74,7 +75,7 @@ SignalBase::SlotList SignalBase::slots()
*
* Signals and slots are a language construct aimed at communication between
* objects through the observer pattern without the need for boilerplate code.
- * See http://doc.qt.io/qt-5/signalsandslots.html for more information.
+ * See http://doc.qt.io/qt-6/signalsandslots.html for more information.
*
* Signals model events that can be observed from objects unrelated to the event
* source. Slots are functions that are called in response to a signal. Signals
diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
index 6bda9d14..4ac72036 100644
--- a/src/libcamera/base/thread.cpp
+++ b/src/libcamera/base/thread.cpp
@@ -18,6 +18,7 @@
#include <libcamera/base/log.h>
#include <libcamera/base/message.h>
#include <libcamera/base/mutex.h>
+#include <libcamera/base/object.h>
/**
* \page thread Thread Support
@@ -151,7 +152,7 @@ private:
friend class ThreadMain;
Thread *thread_;
- bool running_;
+ bool running_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
pid_t tid_;
Mutex mutex_;
@@ -370,6 +371,12 @@ void Thread::run()
void Thread::finishThread()
{
+ /*
+ * Objects may have been scheduled for deletion right before the thread
+ * exited. Ensure they get deleted now, before the thread stops.
+ */
+ dispatchMessages(Message::Type::DeferredDelete);
+
data_->mutex_.lock();
data_->running_ = false;
data_->mutex_.unlock();
@@ -422,11 +429,15 @@ bool Thread::wait(utils::duration duration)
{
MutexLocker locker(data_->mutex_);
+ auto isRunning = ([&]() LIBCAMERA_TSA_REQUIRES(data_->mutex_) {
+ return !data_->running_;
+ });
+
if (duration == utils::duration::max())
- data_->cv_.wait(locker, [&]() { return !data_->running_; });
+ data_->cv_.wait(locker, isRunning);
else
hasFinished = data_->cv_.wait_for(locker, duration,
- [&]() { return !data_->running_; });
+ isRunning);
}
if (thread_.joinable())
diff --git a/src/libcamera/base/timer.cpp b/src/libcamera/base/timer.cpp
index 74b060af..24dbf1e8 100644
--- a/src/libcamera/base/timer.cpp
+++ b/src/libcamera/base/timer.cpp
@@ -85,10 +85,8 @@ void Timer::start(std::chrono::milliseconds duration)
*/
void Timer::start(std::chrono::steady_clock::time_point deadline)
{
- if (Thread::current() != thread()) {
- LOG(Timer, Error) << "Timer " << this << " << can't be started from another thread";
+ if (!assertThreadBound("Timer can't be started from another thread"))
return;
- }
deadline_ = deadline;
@@ -114,13 +112,11 @@ void Timer::start(std::chrono::steady_clock::time_point deadline)
*/
void Timer::stop()
{
- if (!isRunning())
+ if (!assertThreadBound("Timer can't be stopped from another thread"))
return;
- if (Thread::current() != thread()) {
- LOG(Timer, Error) << "Timer " << this << " can't be stopped from another thread";
+ if (!isRunning())
return;
- }
unregisterTimer();
}
diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp
index 6a307940..2f4c3177 100644
--- a/src/libcamera/base/utils.cpp
+++ b/src/libcamera/base/utils.cpp
@@ -8,6 +8,7 @@
#include <libcamera/base/utils.h>
#include <iomanip>
+#include <locale.h>
#include <sstream>
#include <stdlib.h>
#include <string.h>
@@ -463,6 +464,73 @@ std::string toAscii(const std::string &str)
* \a b
*/
+#if HAVE_LOCALE_T
+
+namespace {
+
+/*
+ * RAII wrapper around locale_t instances, to support global locale instances
+ * without leaking memory.
+ */
+class Locale
+{
+public:
+ Locale(const char *locale)
+ {
+ locale_ = newlocale(LC_ALL_MASK, locale, static_cast<locale_t>(0));
+ }
+
+ ~Locale()
+ {
+ freelocale(locale_);
+ }
+
+ locale_t locale() { return locale_; }
+
+private:
+ locale_t locale_;
+};
+
+Locale cLocale("C");
+
+} /* namespace */
+
+#endif /* HAVE_LOCALE_T */
+
+/**
+ * \brief Convert a string to a double independently of the current locale
+ * \param[in] nptr The string to convert
+ * \param[out] endptr Pointer to trailing portion of the string after conversion
+ *
+ * This function is a locale-independent version of the std::strtod() function.
+ * It behaves as the standard function, but uses the "C" locale instead of the
+ * current locale.
+ *
+ * \return The converted value, if any, or 0.0 if the conversion failed.
+ */
+double strtod(const char *__restrict nptr, char **__restrict endptr)
+{
+#if HAVE_LOCALE_T
+ return strtod_l(nptr, endptr, cLocale.locale());
+#else
+ /*
+ * If the libc implementation doesn't provide locale object support,
+ * assume that strtod() is locale-independent.
+ */
+ return strtod(nptr, endptr);
+#endif
+}
+
+/**
+ * \fn to_underlying(Enum e)
+ * \brief Convert an enumeration to its underlygin type
+ * \param[in] e Enumeration value to convert
+ *
+ * This function is equivalent to the C++23 std::to_underlying().
+ *
+ * \return The value of e converted to its underlying type
+ */
+
} /* namespace utils */
#ifndef __DOXYGEN__
diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp
index 4882707e..20aedfa6 100644
--- a/src/libcamera/bayer_format.cpp
+++ b/src/libcamera/bayer_format.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* bayer_format.cpp - Class to represent Bayer formats
*/
@@ -140,6 +140,22 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{
{ formats::SGRBG12_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12P) } },
{ { BayerFormat::RGGB, 12, BayerFormat::Packing::CSI2 },
{ formats::SRGGB12_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12P) } },
+ { { BayerFormat::BGGR, 14, BayerFormat::Packing::None },
+ { formats::SBGGR14, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR14) } },
+ { { BayerFormat::GBRG, 14, BayerFormat::Packing::None },
+ { formats::SGBRG14, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG14) } },
+ { { BayerFormat::GRBG, 14, BayerFormat::Packing::None },
+ { formats::SGRBG14, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG14) } },
+ { { BayerFormat::RGGB, 14, BayerFormat::Packing::None },
+ { formats::SRGGB14, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB14) } },
+ { { BayerFormat::BGGR, 14, BayerFormat::Packing::CSI2 },
+ { formats::SBGGR14_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR14P) } },
+ { { BayerFormat::GBRG, 14, BayerFormat::Packing::CSI2 },
+ { formats::SGBRG14_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG14P) } },
+ { { BayerFormat::GRBG, 14, BayerFormat::Packing::CSI2 },
+ { formats::SGRBG14_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG14P) } },
+ { { BayerFormat::RGGB, 14, BayerFormat::Packing::CSI2 },
+ { formats::SRGGB14_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB14P) } },
{ { BayerFormat::BGGR, 16, BayerFormat::Packing::None },
{ formats::SBGGR16, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR16) } },
{ { BayerFormat::GBRG, 16, BayerFormat::Packing::None },
@@ -154,6 +170,10 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{
{ formats::R10, V4L2PixelFormat(V4L2_PIX_FMT_Y10) } },
{ { BayerFormat::MONO, 10, BayerFormat::Packing::CSI2 },
{ formats::R10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y10P) } },
+ { { BayerFormat::MONO, 12, BayerFormat::Packing::None },
+ { formats::R12, V4L2PixelFormat(V4L2_PIX_FMT_Y12) } },
+ { { BayerFormat::MONO, 16, BayerFormat::Packing::None },
+ { formats::R16, V4L2PixelFormat(V4L2_PIX_FMT_Y16) } },
};
const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{
@@ -191,6 +211,8 @@ const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{
{ MEDIA_BUS_FMT_SRGGB16_1X16, { BayerFormat::RGGB, 16, BayerFormat::Packing::None } },
{ MEDIA_BUS_FMT_Y8_1X8, { BayerFormat::MONO, 8, BayerFormat::Packing::None } },
{ MEDIA_BUS_FMT_Y10_1X10, { BayerFormat::MONO, 10, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_Y12_1X12, { BayerFormat::MONO, 12, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_Y16_1X16, { BayerFormat::MONO, 16, BayerFormat::Packing::None } },
};
} /* namespace */
@@ -355,11 +377,14 @@ BayerFormat BayerFormat::fromPixelFormat(PixelFormat format)
* \brief Apply a transform to this BayerFormat
* \param[in] t The transform to apply
*
- * Appplying a transform to an image stored in a Bayer format affects the Bayer
- * order. For example, performing a horizontal flip on the Bayer pattern
- * RGGB causes the RG rows of pixels to become GR, and the GB rows to become BG.
- * The transformed image would have a GRBG order. The bit depth and modifiers
- * are not affected.
+ * Applying a transform to an image stored in a Bayer format affects the Bayer
+ * order. For example, performing a horizontal flip on the Bayer pattern RGGB
+ * causes the RG rows of pixels to become GR, and the GB rows to become BG. The
+ * transformed image would have a GRBG order. Performing a vertical flip on the
+ * Bayer pattern RGGB causes the GB rows to come before the RG ones and the
+ * transformed image would have GBRG order. Applying both vertical and
+ * horizontal flips on the Bayer patter RGGB results in transformed images with
+ * BGGR order. The bit depth and modifiers are not affected.
*
* Horizontal and vertical flips are applied before transpose.
*
@@ -374,8 +399,11 @@ BayerFormat BayerFormat::transform(Transform t) const
/*
* Observe that flipping bit 0 of the Order enum performs a horizontal
- * mirror on the Bayer pattern (e.g. RGGB goes to GRBG). Similarly,
- * flipping bit 1 performs a vertical mirror operation on it. Hence:
+ * mirror on the Bayer pattern (e.g. RG/GB goes to GR/BG). Similarly,
+ * flipping bit 1 performs a vertical mirror operation on it (e.g RG/GB
+ * goes to GB/RG). Applying both vertical and horizontal flips
+ * combines vertical and horizontal mirroring on the Bayer pattern
+ * (e.g. RG/GB goes to BG/GR). Hence:
*/
if (!!(t & Transform::HFlip))
result.order = static_cast<Order>(result.order ^ 1);
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index 713543fd..a71dc933 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -97,6 +97,16 @@
* implemented in the above order at the hardware level. The libcamera pipeline
* handlers translate the pipeline model to the real hardware configuration.
*
+ * \subsection camera-sensor-model Camera Sensor Model
+ *
+ * By default, libcamera configures the camera sensor automatically based on the
+ * configuration of the streams. Applications may instead specify a manual
+ * configuration for the camera sensor. This allows precise control of the frame
+ * geometry and frame rate delivered by the sensor.
+ *
+ * More details about the camera sensor model implemented by libcamera are
+ * available in the libcamera camera-sensor-model documentation page.
+ *
* \subsection digital-zoom Digital Zoom
*
* Digital zoom is implemented as a combination of the cropping and scaling
@@ -112,6 +122,127 @@ namespace libcamera {
LOG_DECLARE_CATEGORY(Camera)
/**
+ * \class SensorConfiguration
+ * \brief Camera sensor configuration
+ *
+ * The SensorConfiguration class collects parameters to control the operations
+ * of the camera sensor, according to the abstract camera sensor model
+ * implemented by libcamera.
+ *
+ * \todo Applications shall fully populate all fields of the
+ * CameraConfiguration::sensorConfig class members before validating the
+ * CameraConfiguration. If the SensorConfiguration is not fully populated, or if
+ * any of its parameters cannot be applied to the sensor in use, the
+ * CameraConfiguration validation process will fail and return
+ * CameraConfiguration::Status::Invalid.
+ *
+ * Applications that populate the SensorConfiguration class members are
+ * expected to be highly-specialized applications that know what sensor
+ * they are operating with and what parameters are valid for the sensor in use.
+ *
+ * A detailed description of the abstract camera sensor model implemented by
+ * libcamera and the description of its configuration parameters is available
+ * in the libcamera documentation camera-sensor-model file.
+ */
+
+/**
+ * \var SensorConfiguration::bitDepth
+ * \brief The sensor image format bit depth
+ *
+ * The number of bits (resolution) used to represent a pixel sample.
+ */
+
+/**
+ * \var SensorConfiguration::analogCrop
+ * \brief The analog crop rectangle
+ *
+ * The selected portion of the active pixel array used to produce the image
+ * frame.
+ */
+
+/**
+ * \var SensorConfiguration::binning
+ * \brief Sensor binning configuration
+ *
+ * Refer to the camera-sensor-model documentation for an accurate description
+ * of the binning operations. Disabled by default.
+ */
+
+/**
+ * \var SensorConfiguration::binX
+ * \brief Horizontal binning factor
+ *
+ * The horizontal binning factor. Default to 1.
+ */
+
+/**
+ * \var SensorConfiguration::binY
+ * \brief Vertical binning factor
+ *
+ * The vertical binning factor. Default to 1.
+ */
+
+/**
+ * \var SensorConfiguration::skipping
+ * \brief The sensor skipping configuration
+ *
+ * Refer to the camera-sensor-model documentation for an accurate description
+ * of the skipping operations.
+ *
+ * If no skipping is performed, all the structure fields should be
+ * set to 1. Disabled by default.
+ */
+
+/**
+ * \var SensorConfiguration::xOddInc
+ * \brief Horizontal increment for odd rows. Default to 1.
+ */
+
+/**
+ * \var SensorConfiguration::xEvenInc
+ * \brief Horizontal increment for even rows. Default to 1.
+ */
+
+/**
+ * \var SensorConfiguration::yOddInc
+ * \brief Vertical increment for odd columns. Default to 1.
+ */
+
+/**
+ * \var SensorConfiguration::yEvenInc
+ * \brief Vertical increment for even columns. Default to 1.
+ */
+
+/**
+ * \var SensorConfiguration::outputSize
+ * \brief The frame output (visible) size
+ *
+ * The size of the data frame as received by the host processor.
+ */
+
+/**
+ * \brief Check if the sensor configuration is valid
+ *
+ * A sensor configuration is valid if it's fully populated.
+ *
+ * \todo For now allow applications to populate the bitDepth and the outputSize
+ * only as skipping and binnings factors are initialized to 1 and the analog
+ * crop is ignored.
+ *
+ * \return True if the sensor configuration is valid, false otherwise
+ */
+bool SensorConfiguration::isValid() const
+{
+ if (bitDepth && binning.binX && binning.binY &&
+ skipping.xOddInc && skipping.yOddInc &&
+ skipping.xEvenInc && skipping.yEvenInc &&
+ !outputSize.isNull())
+ return true;
+
+ return false;
+}
+
+/**
* \class CameraConfiguration
* \brief Hold configuration for streams of the camera
@@ -160,7 +291,7 @@ LOG_DECLARE_CATEGORY(Camera)
* \brief Create an empty camera configuration
*/
CameraConfiguration::CameraConfiguration()
- : transform(Transform::Identity), config_({})
+ : orientation(Orientation::Rotate0), config_({})
{
}
@@ -184,12 +315,12 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)
* This function adjusts the camera configuration to the closest valid
* configuration and returns the validation status.
*
- * \todo: Define exactly when to return each status code. Should stream
+ * \todo Define exactly when to return each status code. Should stream
* parameters set to 0 by the caller be adjusted without returning Adjusted ?
* This would potentially be useful for applications but would get in the way
* in Camera::configure(). Do we need an extra status code to signal this ?
*
- * \todo: Handle validation of buffers count when refactoring the buffers API.
+ * \todo Handle validation of buffers count when refactoring the buffers API.
*
* \return A CameraConfiguration::Status value that describes the validation
* status.
@@ -317,17 +448,6 @@ std::size_t CameraConfiguration::size() const
return config_.size();
}
-namespace {
-
-bool isRaw(const PixelFormat &pixFmt)
-{
- const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
- return info.isValid() &&
- info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
-}
-
-} /* namespace */
-
/**
* \enum CameraConfiguration::ColorSpaceFlag
* \brief Specify the behaviour of validateColorSpaces
@@ -358,8 +478,8 @@ bool isRaw(const PixelFormat &pixFmt)
* \return A CameraConfiguration::Status value that describes the validation
* status.
* \retval CameraConfigutation::Adjusted The configuration has been adjusted
- * and is now valid. The color space of some or all of the streams may bave
- * benn changed. The caller shall check the color spaces carefully.
+ * and is now valid. The color space of some or all of the streams may have
+ * been changed. The caller shall check the color spaces carefully.
* \retval CameraConfiguration::Valid The configuration was already valid and
* hasn't been adjusted.
*/
@@ -368,29 +488,33 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF
Status status = Valid;
/*
- * Set all raw streams to the Raw color space, and make a note of the largest
- * non-raw stream with a defined color space (if there is one).
+ * Set all raw streams to the Raw color space, and make a note of the
+ * largest non-raw stream with a defined color space (if there is one).
*/
- int index = -1;
- for (auto [i, cfg] : utils::enumerate(config_)) {
- if (isRaw(cfg.pixelFormat)) {
- if (cfg.colorSpace != ColorSpace::Raw) {
- cfg.colorSpace = ColorSpace::Raw;
- status = Adjusted;
- }
- } else if (cfg.colorSpace && (index == -1 || cfg.size > config_[i].size)) {
- index = i;
+ std::optional<ColorSpace> colorSpace;
+ Size size;
+
+ for (StreamConfiguration &cfg : config_) {
+ if (!cfg.colorSpace)
+ continue;
+
+ if (cfg.colorSpace->adjust(cfg.pixelFormat))
+ status = Adjusted;
+
+ if (cfg.colorSpace != ColorSpace::Raw && cfg.size > size) {
+ colorSpace = cfg.colorSpace;
+ size = cfg.size;
}
}
- if (index < 0 || !(flags & ColorSpaceFlag::StreamsShareColorSpace))
+ if (!colorSpace || !(flags & ColorSpaceFlag::StreamsShareColorSpace))
return status;
/* Make all output color spaces the same, if requested. */
for (auto &cfg : config_) {
- if (!isRaw(cfg.pixelFormat) &&
- cfg.colorSpace != config_[index].colorSpace) {
- cfg.colorSpace = config_[index].colorSpace;
+ if (cfg.colorSpace != ColorSpace::Raw &&
+ cfg.colorSpace != colorSpace) {
+ cfg.colorSpace = colorSpace;
status = Adjusted;
}
}
@@ -399,17 +523,35 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF
}
/**
- * \var CameraConfiguration::transform
- * \brief User-specified transform to be applied to the image
+ * \var CameraConfiguration::sensorConfig
+ * \brief The camera sensor configuration
+ *
+ * The sensorConfig member allows manual control of the configuration of the
+ * camera sensor. By default, if sensorConfig is not set, the camera will
+ * configure the sensor automatically based on the configuration of the streams.
+ * Applications can override this by manually specifying the full sensor
+ * configuration.
+ *
+ * Refer to the camera-sensor-model documentation and to the SensorConfiguration
+ * class documentation for details about the sensor configuration process.
+ *
+ * The camera sensor configuration applies to all streams produced by a camera
+ * from the same image source.
+ */
+
+/**
+ * \var CameraConfiguration::orientation
+ * \brief The desired orientation of the images produced by the camera
+ *
+ * The orientation field is a user-specified 2D plane transformation that
+ * specifies how the application wants the camera images to be rotated in
+ * the memory buffers.
*
- * The transform is a user-specified 2D plane transform that will be applied
- * to the camera images by the processing pipeline before being handed to
- * the application. This is subsequent to any transform that is already
- * required to fix up any platform-defined rotation.
+ * If the orientation requested by the application cannot be obtained, the
+ * camera will not rotate or flip the images, and the validate() function will
+ * Adjust this value to the native image orientation produced by the camera.
*
- * The usual 2D plane transforms are allowed here (horizontal/vertical
- * flips, multiple of 90-degree rotations etc.), but the validate() function
- * may adjust this field at its discretion if the selection is not supported.
+ * By default the orientation field is set to Orientation::Rotate0.
*/
/**
@@ -497,7 +639,7 @@ Camera::Private::~Private()
* facilitate debugging of internal request usage.
*
* The requestSequence_ tracks the number of requests queued to a camera
- * over its lifetime.
+ * over a single capture session.
*/
static const char *const camera_state_names[] = {
@@ -508,6 +650,11 @@ static const char *const camera_state_names[] = {
"Running",
};
+bool Camera::Private::isAcquired() const
+{
+ return state_.load(std::memory_order_acquire) != CameraAvailable;
+}
+
bool Camera::Private::isRunning() const
{
return state_.load(std::memory_order_acquire) == CameraRunning;
@@ -811,7 +958,7 @@ int Camera::exportFrameBuffers(Stream *stream,
* not blocking, if the device has already been acquired (by the same or another
* process) the -EBUSY error code is returned.
*
- * Acquiring a camera will limit usage of any other camera(s) provided by the
+ * Acquiring a camera may limit usage of any other camera(s) provided by the
* same pipeline handler to the same instance of libcamera. The limit is in
* effect until all cameras from the pipeline handler are released. Other
* instances of libcamera can still list and examine the cameras but will fail
@@ -839,7 +986,7 @@ int Camera::acquire()
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
- if (!d->pipe_->lock()) {
+ if (!d->pipe_->acquire()) {
LOG(Camera, Info)
<< "Pipeline handler in use by another process";
return -EBUSY;
@@ -873,7 +1020,8 @@ int Camera::release()
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
- d->pipe_->unlock();
+ if (d->isAcquired())
+ d->pipe_->release(this);
d->setState(Private::CameraAvailable);
@@ -937,10 +1085,9 @@ const std::set<Stream *> &Camera::streams() const
* \context This function is \threadsafe.
*
* \return A CameraConfiguration if the requested roles can be satisfied, or a
- * null pointer otherwise. The ownership of the returned configuration is
- * passed to the caller.
+ * null pointer otherwise.
*/
-std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(const StreamRoles &roles)
+std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(Span<const StreamRole> roles)
{
Private *const d = _d();
@@ -952,7 +1099,8 @@ std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(const StreamR
if (roles.size() > streams().size())
return nullptr;
- CameraConfiguration *config = d->pipe_->generateConfiguration(this, roles);
+ std::unique_ptr<CameraConfiguration> config =
+ d->pipe_->generateConfiguration(this, roles);
if (!config) {
LOG(Camera, Debug)
<< "Pipeline handler failed to generate configuration";
@@ -969,10 +1117,16 @@ std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(const StreamR
LOG(Camera, Debug) << msg.str();
- return std::unique_ptr<CameraConfiguration>(config);
+ return config;
}
/**
+ * \fn std::unique_ptr<CameraConfiguration> \
+ * Camera::generateConfiguration(std::initializer_list<StreamRole> roles)
+ * \overload
+ */
+
+/**
* \brief Configure the camera prior to capture
* \param[in] config The camera configurations to setup
*
@@ -1127,6 +1281,11 @@ int Camera::queueRequest(Request *request)
return -EXDEV;
}
+ if (request->status() != Request::RequestPending) {
+ LOG(Camera, Error) << request->toString() << " is not valid";
+ return -EINVAL;
+ }
+
/*
* The camera state may change until the end of the function. No locking
* is however needed as PipelineHandler::queueRequest() will handle
@@ -1181,6 +1340,8 @@ int Camera::start(const ControlList *controls)
LOG(Camera, Debug) << "Starting capture";
+ ASSERT(d->requestSequence_ == 0);
+
ret = d->pipe_->invokeMethod(&PipelineHandler::start,
ConnectionTypeBlocking, this, controls);
if (ret)
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 70d73822..355f3ada 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -5,74 +5,35 @@
* camera_manager.h - Camera management
*/
-#include <libcamera/camera_manager.h>
-
-#include <map>
-
-#include <libcamera/camera.h>
+#include "libcamera/internal/camera_manager.h"
#include <libcamera/base/log.h>
-#include <libcamera/base/mutex.h>
-#include <libcamera/base/thread.h>
#include <libcamera/base/utils.h>
+#include <libcamera/camera.h>
+#include <libcamera/property_ids.h>
+
+#include "libcamera/internal/camera.h"
#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/pipeline_handler.h"
-#include "libcamera/internal/process.h"
/**
- * \file camera_manager.h
+ * \file libcamera/camera_manager.h
* \brief The camera manager
*/
/**
+ * \file libcamera/internal/camera_manager.h
+ * \brief Internal camera manager support
+ */
+
+/**
* \brief Top-level libcamera namespace
*/
namespace libcamera {
LOG_DEFINE_CATEGORY(Camera)
-class CameraManager::Private : public Extensible::Private, public Thread
-{
- LIBCAMERA_DECLARE_PUBLIC(CameraManager)
-
-public:
- Private();
-
- int start();
- void addCamera(std::shared_ptr<Camera> camera,
- const std::vector<dev_t> &devnums);
- void removeCamera(Camera *camera);
-
- /*
- * This mutex protects
- *
- * - initialized_ and status_ during initialization
- * - cameras_ and camerasByDevnum_ after initialization
- */
- mutable Mutex mutex_;
- std::vector<std::shared_ptr<Camera>> cameras_;
- std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
-
-protected:
- void run() override;
-
-private:
- int init();
- void createPipelineHandlers();
- void cleanup();
-
- ConditionVariable cv_;
- bool initialized_;
- int status_;
-
- std::unique_ptr<DeviceEnumerator> enumerator_;
-
- IPAManager ipaManager_;
- ProcessManager processManager_;
-};
-
CameraManager::Private::Private()
: initialized_(false)
{
@@ -87,7 +48,9 @@ int CameraManager::Private::start()
{
MutexLocker locker(mutex_);
- cv_.wait(locker, [&] { return initialized_; });
+ cv_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(mutex_) {
+ return initialized_;
+ });
status = status_;
}
@@ -129,6 +92,7 @@ int CameraManager::Private::init()
return -ENODEV;
createPipelineHandlers();
+ enumerator_->devicesAdded.connect(this, &Private::createPipelineHandlers);
return 0;
}
@@ -142,10 +106,10 @@ void CameraManager::Private::createPipelineHandlers()
* file and only fallback on all handlers if there is no
* configuration file.
*/
- std::vector<PipelineHandlerFactory *> &factories =
- PipelineHandlerFactory::factories();
+ const std::vector<PipelineHandlerFactoryBase *> &factories =
+ PipelineHandlerFactoryBase::factories();
- for (PipelineHandlerFactory *factory : factories) {
+ for (const PipelineHandlerFactoryBase *factory : factories) {
LOG(Camera, Debug)
<< "Found registered pipeline handler '"
<< factory->name() << "'";
@@ -163,8 +127,6 @@ void CameraManager::Private::createPipelineHandlers()
<< "\" matched";
}
}
-
- enumerator_->devicesAdded.connect(this, &Private::createPipelineHandlers);
}
void CameraManager::Private::cleanup()
@@ -178,18 +140,36 @@ void CameraManager::Private::cleanup()
* process deletion requests from the thread's message queue as the event
* loop is not in action here.
*/
- cameras_.clear();
+ {
+ MutexLocker locker(mutex_);
+ cameras_.clear();
+ }
+
dispatchMessages(Message::Type::DeferredDelete);
enumerator_.reset(nullptr);
}
-void CameraManager::Private::addCamera(std::shared_ptr<Camera> camera,
- const std::vector<dev_t> &devnums)
+/**
+ * \brief Add a camera to the camera manager
+ * \param[in] camera The camera to be added
+ *
+ * This function is called by pipeline handlers to register the cameras they
+ * handle with the camera manager. Registered cameras are immediately made
+ * available to the system.
+ *
+ * Device numbers from the SystemDevices property are used by the V4L2
+ * compatibility layer to map V4L2 device nodes to Camera instances.
+ *
+ * \context This function shall be called from the CameraManager thread.
+ */
+void CameraManager::Private::addCamera(std::shared_ptr<Camera> camera)
{
+ ASSERT(Thread::current() == this);
+
MutexLocker locker(mutex_);
- for (std::shared_ptr<Camera> c : cameras_) {
+ for (const std::shared_ptr<Camera> &c : cameras_) {
if (c->id() == camera->id()) {
LOG(Camera, Fatal)
<< "Trying to register a camera with a duplicated ID '"
@@ -201,17 +181,31 @@ void CameraManager::Private::addCamera(std::shared_ptr<Camera> camera,
cameras_.push_back(std::move(camera));
unsigned int index = cameras_.size() - 1;
- for (dev_t devnum : devnums)
- camerasByDevnum_[devnum] = cameras_[index];
+
+ /* Report the addition to the public signal */
+ CameraManager *const o = LIBCAMERA_O_PTR();
+ o->cameraAdded.emit(cameras_[index]);
}
-void CameraManager::Private::removeCamera(Camera *camera)
+/**
+ * \brief Remove a camera from the camera manager
+ * \param[in] camera The camera to be removed
+ *
+ * This function is called by pipeline handlers to unregister cameras from the
+ * camera manager. Unregistered cameras won't be reported anymore by the
+ * cameras() and get() calls, but references may still exist in applications.
+ *
+ * \context This function shall be called from the CameraManager thread.
+ */
+void CameraManager::Private::removeCamera(std::shared_ptr<Camera> camera)
{
+ ASSERT(Thread::current() == this);
+
MutexLocker locker(mutex_);
auto iter = std::find_if(cameras_.begin(), cameras_.end(),
[camera](std::shared_ptr<Camera> &c) {
- return c.get() == camera;
+ return c.get() == camera.get();
});
if (iter == cameras_.end())
return;
@@ -219,14 +213,11 @@ void CameraManager::Private::removeCamera(Camera *camera)
LOG(Camera, Debug)
<< "Unregistering camera '" << camera->id() << "'";
- auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
- [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
- return p.second.lock().get() == camera;
- });
- if (iter_d != camerasByDevnum_.end())
- camerasByDevnum_.erase(iter_d);
-
cameras_.erase(iter);
+
+ /* Report the removal to the public signal */
+ CameraManager *const o = LIBCAMERA_O_PTR();
+ o->cameraRemoved.emit(camera);
}
/**
@@ -363,35 +354,6 @@ std::shared_ptr<Camera> CameraManager::get(const std::string &id)
}
/**
- * \brief Retrieve a camera based on device number
- * \param[in] devnum Device number of camera to get
- *
- * This function is meant solely for the use of the V4L2 compatibility
- * layer, to map device nodes to Camera instances. Applications shall
- * not use it and shall instead retrieve cameras by name.
- *
- * Before calling this function the caller is responsible for ensuring that
- * the camera manager is running.
- *
- * \context This function is \threadsafe.
- *
- * \return Shared pointer to Camera object, which is empty if the camera is
- * not found
- */
-std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
-{
- Private *const d = _d();
-
- MutexLocker locker(d->mutex_);
-
- auto iter = d->camerasByDevnum_.find(devnum);
- if (iter == d->camerasByDevnum_.end())
- return nullptr;
-
- return iter->second.lock();
-}
-
-/**
* \var CameraManager::cameraAdded
* \brief Notify of a new camera added to the system
*
@@ -420,51 +382,6 @@ std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
*/
/**
- * \brief Add a camera to the camera manager
- * \param[in] camera The camera to be added
- * \param[in] devnums The device numbers to associate with \a camera
- *
- * This function is called by pipeline handlers to register the cameras they
- * handle with the camera manager. Registered cameras are immediately made
- * available to the system.
- *
- * \a devnums are used by the V4L2 compatibility layer to map V4L2 device nodes
- * to Camera instances.
- *
- * \context This function shall be called from the CameraManager thread.
- */
-void CameraManager::addCamera(std::shared_ptr<Camera> camera,
- const std::vector<dev_t> &devnums)
-{
- Private *const d = _d();
-
- ASSERT(Thread::current() == d);
-
- d->addCamera(camera, devnums);
- cameraAdded.emit(camera);
-}
-
-/**
- * \brief Remove a camera from the camera manager
- * \param[in] camera The camera to be removed
- *
- * This function is called by pipeline handlers to unregister cameras from the
- * camera manager. Unregistered cameras won't be reported anymore by the
- * cameras() and get() calls, but references may still exist in applications.
- *
- * \context This function shall be called from the CameraManager thread.
- */
-void CameraManager::removeCamera(std::shared_ptr<Camera> camera)
-{
- Private *const d = _d();
-
- ASSERT(Thread::current() == d);
-
- d->removeCamera(camera.get());
- cameraRemoved.emit(camera);
-}
-
-/**
* \fn const std::string &CameraManager::version()
* \brief Retrieve the libcamera version string
* \context This function is \a threadsafe.
diff --git a/src/libcamera/color_space.cpp b/src/libcamera/color_space.cpp
index 895e5c8e..7356bf7d 100644
--- a/src/libcamera/color_space.cpp
+++ b/src/libcamera/color_space.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2021, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2021, Raspberry Pi Ltd
*
* color_space.cpp - color spaces.
*/
@@ -12,6 +12,11 @@
#include <map>
#include <sstream>
#include <utility>
+#include <vector>
+
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/formats.h"
/**
* \file color_space.h
@@ -29,21 +34,33 @@ namespace libcamera {
* (sometimes also referred to as the quantisation) of the color space.
*
* Certain combinations of these fields form well-known standard color
- * spaces such as "JPEG" or "REC709".
+ * spaces such as "sRGB" or "Rec709".
*
* In the strictest sense a "color space" formally only refers to the
* color primaries and white point. Here, however, the ColorSpace class
* adopts the common broader usage that includes the transfer function,
* Y'CbCr encoding method and quantisation.
*
- * For more information on the specific color spaces described here, please
- * see:
+ * More information on color spaces is available in the V4L2 documentation, see
+ * in particular
*
* - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-srgb">sRGB</a>
* - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-jpeg">JPEG</a>
* - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-smpte-170m">SMPTE 170M</a>
* - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-rec709">Rec.709</a>
* - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-bt2020">Rec.2020</a>
+ *
+ * Note that there is no guarantee of a 1:1 mapping between color space names
+ * and definitions in libcamera and V4L2. Two notable differences are
+ *
+ * - The sRGB libcamera color space is defined for RGB formats only with no
+ * Y'CbCr encoding and a full quantization range, while the V4L2 SRGB color
+ * space has a Y'CbCr encoding and a limited quantization range.
+ * - The sYCC libcamera color space is called JPEG in V4L2 due to historical
+ * reasons.
+ *
+ * \todo Define the color space fully in the libcamera API to avoid referencing
+ * V4L2
*/
/**
@@ -118,11 +135,130 @@ namespace libcamera {
*/
/**
+ * \brief A constant representing a raw color space (from a sensor)
+ */
+const ColorSpace ColorSpace::Raw = {
+ Primaries::Raw,
+ TransferFunction::Linear,
+ YcbcrEncoding::None,
+ Range::Full
+};
+
+/**
+ * \brief A constant representing the sRGB color space (RGB formats only)
+ */
+const ColorSpace ColorSpace::Srgb = {
+ Primaries::Rec709,
+ TransferFunction::Srgb,
+ YcbcrEncoding::None,
+ Range::Full
+};
+
+/**
+ * \brief A constant representing the sYCC color space, typically used for
+ * encoding JPEG images
+ */
+const ColorSpace ColorSpace::Sycc = {
+ Primaries::Rec709,
+ TransferFunction::Srgb,
+ YcbcrEncoding::Rec601,
+ Range::Full
+};
+
+/**
+ * \brief A constant representing the SMPTE170M color space
+ */
+const ColorSpace ColorSpace::Smpte170m = {
+ Primaries::Smpte170m,
+ TransferFunction::Rec709,
+ YcbcrEncoding::Rec601,
+ Range::Limited
+};
+
+/**
+ * \brief A constant representing the Rec.709 color space
+ */
+const ColorSpace ColorSpace::Rec709 = {
+ Primaries::Rec709,
+ TransferFunction::Rec709,
+ YcbcrEncoding::Rec709,
+ Range::Limited
+};
+
+/**
+ * \brief A constant representing the Rec.2020 color space
+ */
+const ColorSpace ColorSpace::Rec2020 = {
+ Primaries::Rec2020,
+ TransferFunction::Rec709,
+ YcbcrEncoding::Rec2020,
+ Range::Limited
+};
+
+/**
+ * \var ColorSpace::primaries
+ * \brief The color primaries of this color space
+ */
+
+/**
+ * \var ColorSpace::transferFunction
+ * \brief The transfer function used by this color space
+ */
+
+/**
+ * \var ColorSpace::ycbcrEncoding
+ * \brief The Y'CbCr encoding used by this color space
+ */
+
+/**
+ * \var ColorSpace::range
+ * \brief The pixel range used with by color space
+ */
+
+namespace {
+
+const std::array<std::pair<ColorSpace, const char *>, 6> colorSpaceNames = { {
+ { ColorSpace::Raw, "RAW" },
+ { ColorSpace::Srgb, "sRGB" },
+ { ColorSpace::Sycc, "sYCC" },
+ { ColorSpace::Smpte170m, "SMPTE170M" },
+ { ColorSpace::Rec709, "Rec709" },
+ { ColorSpace::Rec2020, "Rec2020" },
+} };
+
+const std::map<ColorSpace::Primaries, std::string> primariesNames = {
+ { ColorSpace::Primaries::Raw, "RAW" },
+ { ColorSpace::Primaries::Smpte170m, "SMPTE170M" },
+ { ColorSpace::Primaries::Rec709, "Rec709" },
+ { ColorSpace::Primaries::Rec2020, "Rec2020" },
+};
+
+const std::map<ColorSpace::TransferFunction, std::string> transferNames = {
+ { ColorSpace::TransferFunction::Linear, "Linear" },
+ { ColorSpace::TransferFunction::Srgb, "sRGB" },
+ { ColorSpace::TransferFunction::Rec709, "Rec709" },
+};
+
+const std::map<ColorSpace::YcbcrEncoding, std::string> encodingNames = {
+ { ColorSpace::YcbcrEncoding::None, "None" },
+ { ColorSpace::YcbcrEncoding::Rec601, "Rec601" },
+ { ColorSpace::YcbcrEncoding::Rec709, "Rec709" },
+ { ColorSpace::YcbcrEncoding::Rec2020, "Rec2020" },
+};
+
+const std::map<ColorSpace::Range, std::string> rangeNames = {
+ { ColorSpace::Range::Full, "Full" },
+ { ColorSpace::Range::Limited, "Limited" },
+};
+
+} /* namespace */
+
+/**
* \brief Assemble and return a readable string representation of the
* ColorSpace
*
- * If the color space matches a standard ColorSpace (such as ColorSpace::Jpeg)
- * then the short name of the color space ("JPEG") is returned. Otherwise
+ * If the color space matches a standard ColorSpace (such as ColorSpace::Sycc)
+ * then the short name of the color space ("sYCC") is returned. Otherwise
* the four constituent parts of the ColorSpace are assembled into a longer
* string.
*
@@ -132,14 +268,6 @@ std::string ColorSpace::toString() const
{
/* Print out a brief name only for standard color spaces. */
- static const std::array<std::pair<ColorSpace, const char *>, 6> colorSpaceNames = { {
- { ColorSpace::Raw, "RAW" },
- { ColorSpace::Jpeg, "JPEG" },
- { ColorSpace::Srgb, "sRGB" },
- { ColorSpace::Smpte170m, "SMPTE170M" },
- { ColorSpace::Rec709, "Rec709" },
- { ColorSpace::Rec2020, "Rec2020" },
- } };
auto it = std::find_if(colorSpaceNames.begin(), colorSpaceNames.end(),
[this](const auto &item) {
return *this == item.first;
@@ -149,28 +277,6 @@ std::string ColorSpace::toString() const
/* Assemble a name made of the constituent fields. */
- static const std::map<Primaries, std::string> primariesNames = {
- { Primaries::Raw, "RAW" },
- { Primaries::Smpte170m, "SMPTE170M" },
- { Primaries::Rec709, "Rec709" },
- { Primaries::Rec2020, "Rec2020" },
- };
- static const std::map<TransferFunction, std::string> transferNames = {
- { TransferFunction::Linear, "Linear" },
- { TransferFunction::Srgb, "sRGB" },
- { TransferFunction::Rec709, "Rec709" },
- };
- static const std::map<YcbcrEncoding, std::string> encodingNames = {
- { YcbcrEncoding::None, "None" },
- { YcbcrEncoding::Rec601, "Rec601" },
- { YcbcrEncoding::Rec709, "Rec709" },
- { YcbcrEncoding::Rec2020, "Rec2020" },
- };
- static const std::map<Range, std::string> rangeNames = {
- { Range::Full, "Full" },
- { Range::Limited, "Limited" },
- };
-
auto itPrimaries = primariesNames.find(primaries);
std::string primariesName =
itPrimaries == primariesNames.end() ? "Invalid" : itPrimaries->second;
@@ -213,88 +319,185 @@ std::string ColorSpace::toString(const std::optional<ColorSpace> &colorSpace)
}
/**
- * \var ColorSpace::primaries
- * \brief The color primaries of this color space
- */
-
-/**
- * \var ColorSpace::transferFunction
- * \brief The transfer function used by this color space
- */
-
-/**
- * \var ColorSpace::ycbcrEncoding
- * \brief The Y'CbCr encoding used by this color space
- */
-
-/**
- * \var ColorSpace::range
- * \brief The pixel range used with by color space
- */
-
-/**
- * \brief A constant representing a raw color space (from a sensor)
- */
-const ColorSpace ColorSpace::Raw = {
- Primaries::Raw,
- TransferFunction::Linear,
- YcbcrEncoding::None,
- Range::Full
-};
-
-/**
- * \brief A constant representing the JPEG color space used for
- * encoding JPEG images
- */
-const ColorSpace ColorSpace::Jpeg = {
- Primaries::Rec709,
- TransferFunction::Srgb,
- YcbcrEncoding::Rec601,
- Range::Full
-};
-
-/**
- * \brief A constant representing the sRGB color space
+ * \brief Construct a color space from a string
+ * \param[in] str The string
*
- * This is identical to the JPEG color space except that the Y'CbCr
- * range is limited rather than full.
- */
-const ColorSpace ColorSpace::Srgb = {
- Primaries::Rec709,
- TransferFunction::Srgb,
- YcbcrEncoding::Rec601,
- Range::Limited
-};
-
-/**
- * \brief A constant representing the SMPTE170M color space
- */
-const ColorSpace ColorSpace::Smpte170m = {
- Primaries::Smpte170m,
- TransferFunction::Rec709,
- YcbcrEncoding::Rec601,
- Range::Limited
-};
-
-/**
- * \brief A constant representing the Rec.709 color space
+ * The string \a str can contain the name of a well-known color space, or be
+ * made of the four color space components separated by a '/' character, ordered
+ * as
+ *
+ * \verbatim primaries '/' transferFunction '/' ycbcrEncoding '/' range \endverbatim
+ *
+ * Any failure to parse the string, either because it doesn't match the expected
+ * format, or because the one of the names isn't recognized, will cause this
+ * function to return std::nullopt.
+ *
+ * \return The ColorSpace corresponding to the string, or std::nullopt if the
+ * string doesn't describe a known color space
*/
-const ColorSpace ColorSpace::Rec709 = {
- Primaries::Rec709,
- TransferFunction::Rec709,
- YcbcrEncoding::Rec709,
- Range::Limited
-};
+std::optional<ColorSpace> ColorSpace::fromString(const std::string &str)
+{
+ /* First search for a standard color space name match. */
+ auto itColorSpace = std::find_if(colorSpaceNames.begin(), colorSpaceNames.end(),
+ [&str](const auto &item) {
+ return str == item.second;
+ });
+ if (itColorSpace != colorSpaceNames.end())
+ return itColorSpace->first;
+
+ /*
+ * If not found, the string must contain the four color space
+ * components separated by a '/' character.
+ */
+ const auto &split = utils::split(str, "/");
+ std::vector<std::string> components{ split.begin(), split.end() };
+
+ if (components.size() != 4)
+ return std::nullopt;
+
+ ColorSpace colorSpace = ColorSpace::Raw;
+
+ /* Color primaries */
+ auto itPrimaries = std::find_if(primariesNames.begin(), primariesNames.end(),
+ [&components](const auto &item) {
+ return components[0] == item.second;
+ });
+ if (itPrimaries == primariesNames.end())
+ return std::nullopt;
+
+ colorSpace.primaries = itPrimaries->first;
+
+ /* Transfer function */
+ auto itTransfer = std::find_if(transferNames.begin(), transferNames.end(),
+ [&components](const auto &item) {
+ return components[1] == item.second;
+ });
+ if (itTransfer == transferNames.end())
+ return std::nullopt;
+
+ colorSpace.transferFunction = itTransfer->first;
+
+ /* YCbCr encoding */
+ auto itEncoding = std::find_if(encodingNames.begin(), encodingNames.end(),
+ [&components](const auto &item) {
+ return components[2] == item.second;
+ });
+ if (itEncoding == encodingNames.end())
+ return std::nullopt;
+
+ colorSpace.ycbcrEncoding = itEncoding->first;
+
+ /* Quantization range */
+ auto itRange = std::find_if(rangeNames.begin(), rangeNames.end(),
+ [&components](const auto &item) {
+ return components[3] == item.second;
+ });
+ if (itRange == rangeNames.end())
+ return std::nullopt;
+
+ colorSpace.range = itRange->first;
+
+ return colorSpace;
+}
/**
- * \brief A constant representing the Rec.2020 color space
+ * \brief Adjust the color space to match a pixel format
+ * \param[in] format The pixel format
+ *
+ * Not all combinations of pixel formats and color spaces make sense. For
+ * instance, nobody uses a limited quantization range with raw Bayer formats,
+ * and the YcbcrEncoding::None encoding isn't valid for YUV formats. This
+ * function adjusts the ColorSpace to make it compatible with the given \a
+ * format, by applying the following rules:
+ *
+ * - The color space for RAW formats must be Raw.
+ * - The Y'CbCr encoding and quantization range for RGB formats must be
+ * YcbcrEncoding::None and Range::Full respectively.
+ * - The Y'CbCr encoding for YUV formats must not be YcbcrEncoding::None. The
+ * best encoding is in that case guessed based on the primaries and transfer
+ * function.
+ *
+ * \return True if the color space has been adjusted, or false if it was
+ * already compatible with the format and hasn't been changed
*/
-const ColorSpace ColorSpace::Rec2020 = {
- Primaries::Rec2020,
- TransferFunction::Rec709,
- YcbcrEncoding::Rec2020,
- Range::Limited
-};
+bool ColorSpace::adjust(PixelFormat format)
+{
+ const PixelFormatInfo &info = PixelFormatInfo::info(format);
+ bool adjusted = false;
+
+ switch (info.colourEncoding) {
+ case PixelFormatInfo::ColourEncodingRAW:
+ /* Raw formats must use the raw color space. */
+ if (*this != ColorSpace::Raw) {
+ *this = ColorSpace::Raw;
+ adjusted = true;
+ }
+ break;
+
+ case PixelFormatInfo::ColourEncodingRGB:
+ /*
+ * RGB formats can't have a Y'CbCr encoding, and must use full
+ * range quantization.
+ */
+ if (ycbcrEncoding != YcbcrEncoding::None) {
+ ycbcrEncoding = YcbcrEncoding::None;
+ adjusted = true;
+ }
+
+ if (range != Range::Full) {
+ range = Range::Full;
+ adjusted = true;
+ }
+ break;
+
+ case PixelFormatInfo::ColourEncodingYUV:
+ if (ycbcrEncoding != YcbcrEncoding::None)
+ break;
+
+ /*
+ * YUV formats must have a Y'CbCr encoding. Infer the most
+ * probable option from the transfer function and primaries.
+ */
+ switch (transferFunction) {
+ case TransferFunction::Linear:
+ /*
+ * Linear YUV is not used in any standard color space,
+ * pick the widely supported and used Rec601 as default.
+ */
+ ycbcrEncoding = YcbcrEncoding::Rec601;
+ break;
+
+ case TransferFunction::Rec709:
+ switch (primaries) {
+ /* Raw should never happen. */
+ case Primaries::Raw:
+ case Primaries::Smpte170m:
+ ycbcrEncoding = YcbcrEncoding::Rec601;
+ break;
+ case Primaries::Rec709:
+ ycbcrEncoding = YcbcrEncoding::Rec709;
+ break;
+ case Primaries::Rec2020:
+ ycbcrEncoding = YcbcrEncoding::Rec2020;
+ break;
+ }
+ break;
+
+ case TransferFunction::Srgb:
+ /*
+ * Only the sYCC color space uses the sRGB transfer
+ * function, the corresponding encoding is Rec601.
+ */
+ ycbcrEncoding = YcbcrEncoding::Rec601;
+ break;
+ }
+
+ adjusted = true;
+ break;
+ }
+
+ return adjusted;
+}
/**
* \brief Compare color spaces for equality
diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in
index 5fb1c2c3..d283c1c1 100644
--- a/src/libcamera/control_ids.cpp.in
+++ b/src/libcamera/control_ids.cpp.in
@@ -24,14 +24,7 @@ namespace controls {
${controls_doc}
-/**
- * \brief Namespace for libcamera draft controls
- */
-namespace draft {
-
-${draft_controls_doc}
-
-} /* namespace draft */
+${vendor_controls_doc}
#ifndef __DOXYGEN__
/*
@@ -40,11 +33,8 @@ ${draft_controls_doc}
*/
${controls_def}
-namespace draft {
-
-${draft_controls_def}
+${vendor_controls_def}
-} /* namespace draft */
#endif
/**
diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids_core.yaml
index ecab3ae9..bf1f1a83 100644
--- a/src/libcamera/control_ids.yaml
+++ b/src/libcamera/control_ids_core.yaml
@@ -2,10 +2,11 @@
#
# Copyright (C) 2019, Google Inc.
#
-%YAML 1.2
+%YAML 1.1
---
# Unless otherwise stated, all controls are bi-directional, i.e. they can be
# set through Request::controls() and returned out through Request::metadata().
+vendor: libcamera
controls:
- AeEnable:
type: bool
@@ -156,6 +157,79 @@ controls:
control of which features should be automatically adjusted shouldn't
better be handled through a separate AE mode control.
+ - AeFlickerMode:
+ type: int32_t
+ description: |
+ Set the flicker mode, which determines whether, and how, the AGC/AEC
+ algorithm attempts to hide flicker effects caused by the duty cycle of
+ artificial lighting.
+
+ Although implementation dependent, many algorithms for "flicker
+ avoidance" work by restricting this exposure time to integer multiples
+ of the cycle period, wherever possible.
+
+ Implementations may not support all of the flicker modes listed below.
+
+ By default the system will start in FlickerAuto mode if this is
+ supported, otherwise the flicker mode will be set to FlickerOff.
+
+ enum:
+ - name: FlickerOff
+ value: 0
+ description: No flicker avoidance is performed.
+ - name: FlickerManual
+ value: 1
+ description: Manual flicker avoidance.
+ Suppress flicker effects caused by lighting running with a period
+ specified by the AeFlickerPeriod control.
+ \sa AeFlickerPeriod
+ - name: FlickerAuto
+ value: 2
+ description: Automatic flicker period detection and avoidance.
+ The system will automatically determine the most likely value of
+ flicker period, and avoid flicker of this frequency. Once flicker
+ is being corrected, it is implementation dependent whether the
+ system is still able to detect a change in the flicker period.
+ \sa AeFlickerDetected
+
+ - AeFlickerPeriod:
+ type: int32_t
+ description: Manual flicker period in microseconds.
+ This value sets the current flicker period to avoid. It is used when
+ AeFlickerMode is set to FlickerManual.
+
+ To cancel 50Hz mains flicker, this should be set to 10000 (corresponding
+ to 100Hz), or 8333 (120Hz) for 60Hz mains.
+
+ Setting the mode to FlickerManual when no AeFlickerPeriod has ever been
+ set means that no flicker cancellation occurs (until the value of this
+ control is updated).
+
+ Switching to modes other than FlickerManual has no effect on the
+ value of the AeFlickerPeriod control.
+
+ \sa AeFlickerMode
+
+ - AeFlickerDetected:
+ type: int32_t
+ description: Flicker period detected in microseconds.
+ The value reported here indicates the currently detected flicker
+ period, or zero if no flicker at all is detected.
+
+ When AeFlickerMode is set to FlickerAuto, there may be a period during
+ which the value reported here remains zero. Once a non-zero value is
+ reported, then this is the flicker period that has been detected and is
+ now being cancelled.
+
+ In the case of 50Hz mains flicker, the value would be 10000
+ (corresponding to 100Hz), or 8333 (120Hz) for 60Hz mains flicker.
+
+ It is implementation dependent whether the system can continue to detect
+ flicker of different periods when another frequency is already being
+ cancelled.
+
+ \sa AeFlickerMode
+
- Brightness:
type: float
description: |
@@ -275,12 +349,12 @@ controls:
type: int32_t
description: |
Reports a Figure of Merit (FoM) to indicate how in-focus the frame is.
- A larger FocusFoM value indicates a more in-focus frame. This control
- depends on the IPA to gather ISP statistics from the defined focus
- region, and combine them in a suitable way to generate a FocusFoM value.
- In this respect, it is not necessarily aimed at providing a way to
- implement a focus algorithm by the application, rather an indication of
- how in-focus a frame is.
+ A larger FocusFoM value indicates a more in-focus frame. This singular
+ value may be based on a combination of statistics gathered from
+ multiple focus regions within an image. The number of focus regions and
+ method of combination is platform dependent. In this respect, it is not
+ necessarily aimed at providing a way to implement a focus algorithm by
+ the application, rather an indication of how in-focus a frame is.
- ColourCorrectionMatrix:
type: float
@@ -291,7 +365,7 @@ controls:
transformation. The 3x3 matrix is stored in conventional reading
order in an array of 9 floating point values.
- size: [3x3]
+ size: [3,3]
- ScalerCrop:
type: Rectangle
@@ -333,8 +407,8 @@ controls:
- FrameDurationLimits:
type: int64_t
description: |
- The minimum and maximum (in that order) frame duration,
- expressed in microseconds.
+ The minimum and maximum (in that order) frame duration, expressed in
+ microseconds.
When provided by applications, the control specifies the sensor frame
duration interval the pipeline has to use. This limits the largest
@@ -343,7 +417,7 @@ controls:
the sensor will not be able to raise the exposure time above 33ms.
A fixed frame duration is achieved by setting the minimum and maximum
values to be the same. Setting both values to 0 reverts to using the
- IPA provided defaults.
+ camera defaults.
The maximum frame duration provides the absolute limit to the shutter
speed computed by the AE algorithm and it overrides any exposure mode
@@ -375,7 +449,7 @@ controls:
range of reported temperatures is device dependent.
The SensorTemperature control will only be returned in metadata if a
- themal sensor is present.
+ thermal sensor is present.
- SensorTimestamp:
type: int64_t
@@ -408,6 +482,13 @@ controls:
LensPosition control.
In this mode the AfState will always report AfStateIdle.
+
+ If the camera is started in AfModeManual, it will move the focus
+ lens to the position specified by the LensPosition control.
+
+ This mode is the recommended default value for the AfMode control.
+ External cameras (as reported by the Location property set to
+ CameraLocationExternal) may use a different default value.
- name: AfModeAuto
value: 1
description: |
@@ -591,25 +672,27 @@ controls:
AfModeManual, though the value is reported back unconditionally in all
modes.
- The units are a reciprocal distance scale like dioptres but normalised
- for the hyperfocal distance. That is, for a lens with hyperfocal
- distance H, and setting it to a focal distance D, the lens position LP,
- which is generally a non-integer, is given by
+ This value, which is generally a non-integer, is the reciprocal of the
+ focal distance in metres, also known as dioptres. That is, to set a
+ focal distance D, the lens position LP is given by
- \f$LP = \frac{H}{D}\f$
+ \f$LP = \frac{1\mathrm{m}}{D}\f$
For example:
0 moves the lens to infinity.
- 0.5 moves the lens to twice the hyperfocal distance.
- 1 moves the lens to the hyperfocal position.
- And larger values will focus the lens ever closer.
+ 0.5 moves the lens to focus on objects 2m away.
+ 2 moves the lens to focus on objects 50cm away.
+ And larger values will focus the lens closer.
- \todo Define a property to report the Hyperforcal distance of calibrated
- lenses.
+ The default value of the control should indicate a good general position
+ for the lens, often corresponding to the hyperfocal distance (the
+ closest position for which objects at infinity are still acceptably
+ sharp). The minimum will often be zero (meaning infinity), and the
+ maximum value defines the closest focus position.
- \todo Define a property to report the maximum and minimum positions of
- this lens. The minimum value will often be zero (meaning infinity).
+ \todo Define a property to report the Hyperfocal distance of calibrated
+ lenses.
- AfState:
type: int32_t
@@ -692,253 +775,94 @@ controls:
Continuous AF is paused. No further state changes or lens movements
will occur until the AfPauseResume control is sent.
- # ----------------------------------------------------------------------------
- # Draft controls section
-
- - AePrecaptureTrigger:
+ - HdrMode:
type: int32_t
- draft: true
description: |
- Control for AE metering trigger. Currently identical to
- ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER.
+ Control to set the mode to be used for High Dynamic Range (HDR)
+ imaging. HDR techniques typically include multiple exposure, image
+ fusion and tone mapping techniques to improve the dynamic range of the
+ resulting images.
- Whether the camera device will trigger a precapture metering sequence
- when it processes this request.
- enum:
- - name: AePrecaptureTriggerIdle
- value: 0
- description: The trigger is idle.
- - name: AePrecaptureTriggerStart
- value: 1
- description: The pre-capture AE metering is started by the camera.
- - name: AePrecaptureTriggerCancel
- value: 2
- description: |
- The camera will cancel any active or completed metering sequence.
- The AE algorithm is reset to its initial state.
+ When using an HDR mode, images are captured with different sets of AGC
+ settings called HDR channels. Channels indicate in particular the type
+ of exposure (short, medium or long) used to capture the raw image,
+ before fusion. Each HDR image is tagged with the corresponding channel
+ using the HdrChannel control.
- - NoiseReductionMode:
- type: int32_t
- draft: true
- description: |
- Control to select the noise reduction algorithm mode. Currently
- identical to ANDROID_NOISE_REDUCTION_MODE.
+ \sa HdrChannel
- Mode of operation for the noise reduction algorithm.
enum:
- - name: NoiseReductionModeOff
+ - name: HdrModeOff
value: 0
- description: No noise reduction is applied
- - name: NoiseReductionModeFast
+ description: |
+ HDR is disabled. Metadata for this frame will not include the
+ HdrChannel control.
+ - name: HdrModeMultiExposureUnmerged
value: 1
description: |
- Noise reduction is applied without reducing the frame rate.
- - name: NoiseReductionModeHighQuality
+ Multiple exposures will be generated in an alternating fashion.
+ However, they will not be merged together and will be returned to
+ the application as they are. Each image will be tagged with the
+ correct HDR channel, indicating what kind of exposure it is. The
+ tag should be the same as in the HdrModeMultiExposure case.
+
+ The expectation is that an application using this mode would merge
+ the frames to create HDR images for itself if it requires them.
+ - name: HdrModeMultiExposure
value: 2
description: |
- High quality noise reduction at the expense of frame rate.
- - name: NoiseReductionModeMinimal
+ Multiple exposures will be generated and merged to create HDR
+ images. Each image will be tagged with the HDR channel (long, medium
+ or short) that arrived and which caused this image to be output.
+
+ Systems that use two channels for HDR will return images tagged
+ alternately as the short and long channel. Systems that use three
+ channels for HDR will cycle through the short, medium and long
+ channel before repeating.
+ - name: HdrModeSingleExposure
value: 3
description: |
- Minimal noise reduction is applied without reducing the frame rate.
- - name: NoiseReductionModeZSL
- value: 4
- description: |
- Noise reduction is applied at different levels to different streams.
-
- - ColorCorrectionAberrationMode:
- type: int32_t
- draft: true
- description: |
- Control to select the color correction aberration mode. Currently
- identical to ANDROID_COLOR_CORRECTION_ABERRATION_MODE.
-
- Mode of operation for the chromatic aberration correction algorithm.
- enum:
- - name: ColorCorrectionAberrationOff
- value: 0
- description: No aberration correction is applied.
- - name: ColorCorrectionAberrationFast
- value: 1
- description: Aberration correction will not slow down the frame rate.
- - name: ColorCorrectionAberrationHighQuality
- value: 2
- description: |
- High quality aberration correction which might reduce the frame
- rate.
-
- - AeState:
- type: int32_t
- draft: true
- description: |
- Control to report the current AE algorithm state. Currently identical to
- ANDROID_CONTROL_AE_STATE.
-
- Current state of the AE algorithm.
- enum:
- - name: AeStateInactive
- value: 0
- description: The AE algorithm is inactive.
- - name: AeStateSearching
- value: 1
- description: The AE algorithm has not converged yet.
- - name: AeStateConverged
- value: 2
- description: The AE algorithm has converged.
- - name: AeStateLocked
- value: 3
- description: The AE algorithm is locked.
- - name: AeStateFlashRequired
+ Multiple frames all at a single exposure will be used to create HDR
+ images. These images should be reported as all corresponding to the
+ HDR short channel.
+ - name: HdrModeNight
value: 4
- description: The AE algorithm would need a flash for good results
- - name: AeStatePrecapture
- value: 5
description: |
- The AE algorithm has started a pre-capture metering session.
- \sa AePrecaptureTrigger
-
- - AwbState:
- type: int32_t
- draft: true
- description: |
- Control to report the current AWB algorithm state. Currently identical
- to ANDROID_CONTROL_AWB_STATE.
-
- Current state of the AWB algorithm.
- enum:
- - name: AwbStateInactive
- value: 0
- description: The AWB algorithm is inactive.
- - name: AwbStateSearching
- value: 1
- description: The AWB algorithm has not converged yet.
- - name: AwbConverged
- value: 2
- description: The AWB algorithm has converged.
- - name: AwbLocked
- value: 3
- description: The AWB algorithm is locked.
-
- - SensorRollingShutterSkew:
- type: int64_t
- draft: true
- description: |
- Control to report the time between the start of exposure of the first
- row and the start of exposure of the last row. Currently identical to
- ANDROID_SENSOR_ROLLING_SHUTTER_SKEW
-
- - LensShadingMapMode:
- type: int32_t
- draft: true
- description: |
- Control to report if the lens shading map is available. Currently
- identical to ANDROID_STATISTICS_LENS_SHADING_MAP_MODE.
- enum:
- - name: LensShadingMapModeOff
- value: 0
- description: No lens shading map mode is available.
- - name: LensShadingMapModeOn
- value: 1
- description: The lens shading map mode is available.
-
- - SceneFlicker:
- type: int32_t
- draft: true
- description: |
- Control to report the detected scene light frequency. Currently
- identical to ANDROID_STATISTICS_SCENE_FLICKER.
- enum:
- - name: SceneFickerOff
- value: 0
- description: No flickering detected.
- - name: SceneFicker50Hz
- value: 1
- description: 50Hz flickering detected.
- - name: SceneFicker60Hz
- value: 2
- description: 60Hz flickering detected.
+ Multiple frames will be combined to produce "night mode" images. It
+ is up to the implementation exactly which HDR channels it uses, and
+ the images will all be tagged accordingly with the correct HDR
+ channel information.
- - PipelineDepth:
+ - HdrChannel:
type: int32_t
- draft: true
description: |
- Specifies the number of pipeline stages the frame went through from when
- it was exposed to when the final completed result was available to the
- framework. Always less than or equal to PipelineMaxDepth. Currently
- identical to ANDROID_REQUEST_PIPELINE_DEPTH.
+ This value is reported back to the application so that it can discover
+ whether this capture corresponds to the short or long exposure image (or
+ any other image used by the HDR procedure). An application can monitor
+ the HDR channel to discover when the differently exposed images have
+ arrived.
- The typical value for this control is 3 as a frame is first exposed,
- captured and then processed in a single pass through the ISP. Any
- additional processing step performed after the ISP pass (in example face
- detection, additional format conversions etc) count as an additional
- pipeline stage.
+ This metadata is only available when an HDR mode has been enabled.
- - MaxLatency:
- type: int32_t
- draft: true
- description: |
- The maximum number of frames that can occur after a request (different
- than the previous) has been submitted, and before the result's state
- becomes synchronized. A value of -1 indicates unknown latency, and 0
- indicates per-frame control. Currently identical to
- ANDROID_SYNC_MAX_LATENCY.
+ \sa HdrMode
- - TestPatternMode:
- type: int32_t
- draft: true
- description: |
- Control to select the test pattern mode. Currently identical to
- ANDROID_SENSOR_TEST_PATTERN_MODE.
enum:
- - name: TestPatternModeOff
+ - name: HdrChannelNone
value: 0
description: |
- No test pattern mode is used. The camera device returns frames from
- the image sensor.
- - name: TestPatternModeSolidColor
+ This image does not correspond to any of the captures used to create
+ an HDR image.
+ - name: HdrChannelShort
value: 1
description: |
- Each pixel in [R, G_even, G_odd, B] is replaced by its respective
- color channel provided in test pattern data.
- \todo Add control for test pattern data.
- - name: TestPatternModeColorBars
+ This is a short exposure image.
+ - name: HdrChannelMedium
value: 2
description: |
- All pixel data is replaced with an 8-bar color pattern. The vertical
- bars (left-to-right) are as follows; white, yellow, cyan, green,
- magenta, red, blue and black. Each bar should take up 1/8 of the
- sensor pixel array width. When this is not possible, the bar size
- should be rounded down to the nearest integer and the pattern can
- repeat on the right side. Each bar's height must always take up the
- full sensor pixel array height.
- - name: TestPatternModeColorBarsFadeToGray
+ This is a medium exposure image.
+ - name: HdrChannelLong
value: 3
description: |
- The test pattern is similar to TestPatternModeColorBars,
- except that each bar should start at its specified color at the top
- and fade to gray at the bottom. Furthermore each bar is further
- subdevided into a left and right half. The left half should have a
- smooth gradient, and the right half should have a quantized
- gradient. In particular, the right half's should consist of blocks
- of the same color for 1/16th active sensor pixel array width. The
- least significant bits in the quantized gradient should be copied
- from the most significant bits of the smooth gradient. The height of
- each bar should always be a multiple of 128. When this is not the
- case, the pattern should repeat at the bottom of the image.
- - name: TestPatternModePn9
- value: 4
- description: |
- All pixel data is replaced by a pseudo-random sequence generated
- from a PN9 512-bit sequence (typically implemented in hardware with
- a linear feedback shift register). The generator should be reset at
- the beginning of each frame, and thus each subsequent raw frame with
- this test pattern should be exactly the same as the last.
- - name: TestPatternModeCustom1
- value: 256
- description: |
- The first custom test pattern. All custom patterns that are
- available only on this camera device are at least this numeric
- value. All of the custom test patterns will be static (that is the
- raw image must not vary from frame to frame).
+ This is a long exposure image.
...
diff --git a/src/libcamera/control_ids_draft.yaml b/src/libcamera/control_ids_draft.yaml
new file mode 100644
index 00000000..9bef5bf1
--- /dev/null
+++ b/src/libcamera/control_ids_draft.yaml
@@ -0,0 +1,230 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Copyright (C) 2019, Google Inc.
+#
+%YAML 1.1
+---
+# Unless otherwise stated, all controls are bi-directional, i.e. they can be
+# set through Request::controls() and returned out through Request::metadata().
+vendor: draft
+controls:
+ - AePrecaptureTrigger:
+ type: int32_t
+ description: |
+ Control for AE metering trigger. Currently identical to
+ ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER.
+
+ Whether the camera device will trigger a precapture metering sequence
+ when it processes this request.
+ enum:
+ - name: AePrecaptureTriggerIdle
+ value: 0
+ description: The trigger is idle.
+ - name: AePrecaptureTriggerStart
+ value: 1
+ description: The pre-capture AE metering is started by the camera.
+ - name: AePrecaptureTriggerCancel
+ value: 2
+ description: |
+ The camera will cancel any active or completed metering sequence.
+ The AE algorithm is reset to its initial state.
+
+ - NoiseReductionMode:
+ type: int32_t
+ description: |
+ Control to select the noise reduction algorithm mode. Currently
+ identical to ANDROID_NOISE_REDUCTION_MODE.
+
+ Mode of operation for the noise reduction algorithm.
+ enum:
+ - name: NoiseReductionModeOff
+ value: 0
+ description: No noise reduction is applied
+ - name: NoiseReductionModeFast
+ value: 1
+ description: |
+ Noise reduction is applied without reducing the frame rate.
+ - name: NoiseReductionModeHighQuality
+ value: 2
+ description: |
+ High quality noise reduction at the expense of frame rate.
+ - name: NoiseReductionModeMinimal
+ value: 3
+ description: |
+ Minimal noise reduction is applied without reducing the frame rate.
+ - name: NoiseReductionModeZSL
+ value: 4
+ description: |
+ Noise reduction is applied at different levels to different streams.
+
+ - ColorCorrectionAberrationMode:
+ type: int32_t
+ description: |
+ Control to select the color correction aberration mode. Currently
+ identical to ANDROID_COLOR_CORRECTION_ABERRATION_MODE.
+
+ Mode of operation for the chromatic aberration correction algorithm.
+ enum:
+ - name: ColorCorrectionAberrationOff
+ value: 0
+ description: No aberration correction is applied.
+ - name: ColorCorrectionAberrationFast
+ value: 1
+ description: Aberration correction will not slow down the frame rate.
+ - name: ColorCorrectionAberrationHighQuality
+ value: 2
+ description: |
+ High quality aberration correction which might reduce the frame
+ rate.
+
+ - AeState:
+ type: int32_t
+ description: |
+ Control to report the current AE algorithm state. Currently identical to
+ ANDROID_CONTROL_AE_STATE.
+
+ Current state of the AE algorithm.
+ enum:
+ - name: AeStateInactive
+ value: 0
+ description: The AE algorithm is inactive.
+ - name: AeStateSearching
+ value: 1
+ description: The AE algorithm has not converged yet.
+ - name: AeStateConverged
+ value: 2
+ description: The AE algorithm has converged.
+ - name: AeStateLocked
+ value: 3
+ description: The AE algorithm is locked.
+ - name: AeStateFlashRequired
+ value: 4
+ description: The AE algorithm would need a flash for good results
+ - name: AeStatePrecapture
+ value: 5
+ description: |
+ The AE algorithm has started a pre-capture metering session.
+ \sa AePrecaptureTrigger
+
+ - AwbState:
+ type: int32_t
+ description: |
+ Control to report the current AWB algorithm state. Currently identical
+ to ANDROID_CONTROL_AWB_STATE.
+
+ Current state of the AWB algorithm.
+ enum:
+ - name: AwbStateInactive
+ value: 0
+ description: The AWB algorithm is inactive.
+ - name: AwbStateSearching
+ value: 1
+ description: The AWB algorithm has not converged yet.
+ - name: AwbConverged
+ value: 2
+ description: The AWB algorithm has converged.
+ - name: AwbLocked
+ value: 3
+ description: The AWB algorithm is locked.
+
+ - SensorRollingShutterSkew:
+ type: int64_t
+ description: |
+ Control to report the time between the start of exposure of the first
+ row and the start of exposure of the last row. Currently identical to
+ ANDROID_SENSOR_ROLLING_SHUTTER_SKEW
+
+ - LensShadingMapMode:
+ type: int32_t
+ description: |
+ Control to report if the lens shading map is available. Currently
+ identical to ANDROID_STATISTICS_LENS_SHADING_MAP_MODE.
+ enum:
+ - name: LensShadingMapModeOff
+ value: 0
+ description: No lens shading map mode is available.
+ - name: LensShadingMapModeOn
+ value: 1
+ description: The lens shading map mode is available.
+
+ - PipelineDepth:
+ type: int32_t
+ description: |
+ Specifies the number of pipeline stages the frame went through from when
+ it was exposed to when the final completed result was available to the
+ framework. Always less than or equal to PipelineMaxDepth. Currently
+ identical to ANDROID_REQUEST_PIPELINE_DEPTH.
+
+ The typical value for this control is 3 as a frame is first exposed,
+ captured and then processed in a single pass through the ISP. Any
+ additional processing step performed after the ISP pass (in example face
+ detection, additional format conversions etc) count as an additional
+ pipeline stage.
+
+ - MaxLatency:
+ type: int32_t
+ description: |
+ The maximum number of frames that can occur after a request (different
+ than the previous) has been submitted, and before the result's state
+ becomes synchronized. A value of -1 indicates unknown latency, and 0
+ indicates per-frame control. Currently identical to
+ ANDROID_SYNC_MAX_LATENCY.
+
+ - TestPatternMode:
+ type: int32_t
+ description: |
+ Control to select the test pattern mode. Currently identical to
+ ANDROID_SENSOR_TEST_PATTERN_MODE.
+ enum:
+ - name: TestPatternModeOff
+ value: 0
+ description: |
+ No test pattern mode is used. The camera device returns frames from
+ the image sensor.
+ - name: TestPatternModeSolidColor
+ value: 1
+ description: |
+ Each pixel in [R, G_even, G_odd, B] is replaced by its respective
+ color channel provided in test pattern data.
+ \todo Add control for test pattern data.
+ - name: TestPatternModeColorBars
+ value: 2
+ description: |
+ All pixel data is replaced with an 8-bar color pattern. The vertical
+ bars (left-to-right) are as follows; white, yellow, cyan, green,
+ magenta, red, blue and black. Each bar should take up 1/8 of the
+ sensor pixel array width. When this is not possible, the bar size
+ should be rounded down to the nearest integer and the pattern can
+ repeat on the right side. Each bar's height must always take up the
+ full sensor pixel array height.
+ - name: TestPatternModeColorBarsFadeToGray
+ value: 3
+ description: |
+ The test pattern is similar to TestPatternModeColorBars,
+ except that each bar should start at its specified color at the top
+ and fade to gray at the bottom. Furthermore each bar is further
+ subdevided into a left and right half. The left half should have a
+ smooth gradient, and the right half should have a quantized
+ gradient. In particular, the right half's should consist of blocks
+ of the same color for 1/16th active sensor pixel array width. The
+ least significant bits in the quantized gradient should be copied
+ from the most significant bits of the smooth gradient. The height of
+ each bar should always be a multiple of 128. When this is not the
+ case, the pattern should repeat at the bottom of the image.
+ - name: TestPatternModePn9
+ value: 4
+ description: |
+ All pixel data is replaced by a pseudo-random sequence generated
+ from a PN9 512-bit sequence (typically implemented in hardware with
+ a linear feedback shift register). The generator should be reset at
+ the beginning of each frame, and thus each subsequent raw frame with
+ this test pattern should be exactly the same as the last.
+ - name: TestPatternModeCustom1
+ value: 256
+ description: |
+ The first custom test pattern. All custom patterns that are
+ available only on this camera device are at least this numeric
+ value. All of the custom test patterns will be static (that is the
+ raw image must not vary from frame to frame).
+
+...
diff --git a/src/libcamera/control_ids_rpi.yaml b/src/libcamera/control_ids_rpi.yaml
new file mode 100644
index 00000000..cb097f88
--- /dev/null
+++ b/src/libcamera/control_ids_rpi.yaml
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Copyright (C) 2023, Raspberry Pi Ltd
+#
+%YAML 1.1
+---
+# Raspberry Pi (VC4 and PiSP) specific vendor controls
+vendor: rpi
+controls:
+ - StatsOutputEnable:
+ type: bool
+ description: |
+ Toggles the Raspberry Pi IPA to output a binary dump of the hardware
+ generated statistics through the Request metadata in the Bcm2835StatsOutput
+ control.
+
+ \sa Bcm2835StatsOutput
+
+ - Bcm2835StatsOutput:
+ type: uint8_t
+ size: [n]
+ description: |
+ Span of the BCM2835 ISP generated statistics for the current frame. This
+ is sent in the Request metadata if the StatsOutputEnable is set to true.
+ The statistics struct definition can be found in include/linux/bcm2835-isp.h.
+
+ \sa StatsOutputEnable
+
+...
diff --git a/src/libcamera/control_ranges.yaml b/src/libcamera/control_ranges.yaml
new file mode 100644
index 00000000..d42447d0
--- /dev/null
+++ b/src/libcamera/control_ranges.yaml
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Copyright (C) 2023, Raspberry Pi Ltd
+#
+%YAML 1.1
+---
+# Specifies the control id ranges/offsets for core/draft libcamera and vendor
+# controls and properties.
+ranges:
+ # Core libcamera controls
+ libcamera: 0
+ # Draft designated libcamera controls
+ draft: 10000
+ # Raspberry Pi vendor controls
+ rpi: 20000
+ # Next range starts at 30000
+
+...
diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp
index e87d2362..0cf719bd 100644
--- a/src/libcamera/control_serializer.cpp
+++ b/src/libcamera/control_serializer.cpp
@@ -144,7 +144,7 @@ void ControlSerializer::reset()
size_t ControlSerializer::binarySize(const ControlValue &value)
{
- return value.data().size_bytes();
+ return sizeof(ControlType) + value.data().size_bytes();
}
size_t ControlSerializer::binarySize(const ControlInfo &info)
@@ -195,6 +195,8 @@ size_t ControlSerializer::binarySize(const ControlList &list)
void ControlSerializer::store(const ControlValue &value,
ByteStreamBuffer &buffer)
{
+ const ControlType type = value.type();
+ buffer.write(&type);
buffer.write(value.data());
}
@@ -379,11 +381,13 @@ int ControlSerializer::serialize(const ControlList &list,
return 0;
}
-ControlValue ControlSerializer::loadControlValue(ControlType type,
- ByteStreamBuffer &buffer,
+ControlValue ControlSerializer::loadControlValue(ByteStreamBuffer &buffer,
bool isArray,
unsigned int count)
{
+ ControlType type;
+ buffer.read(&type);
+
ControlValue value;
value.reserve(type, isArray, count);
@@ -392,15 +396,11 @@ ControlValue ControlSerializer::loadControlValue(ControlType type,
return value;
}
-ControlInfo ControlSerializer::loadControlInfo(ControlType type,
- ByteStreamBuffer &b)
+ControlInfo ControlSerializer::loadControlInfo(ByteStreamBuffer &b)
{
- if (type == ControlTypeString)
- type = ControlTypeInteger32;
-
- ControlValue min = loadControlValue(type, b);
- ControlValue max = loadControlValue(type, b);
- ControlValue def = loadControlValue(type, b);
+ ControlValue min = loadControlValue(b);
+ ControlValue max = loadControlValue(b);
+ ControlValue def = loadControlValue(b);
return ControlInfo(min, max, def);
}
@@ -513,7 +513,7 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &
}
/* Create and store the ControlInfo. */
- ctrls.emplace(controlId, loadControlInfo(type, values));
+ ctrls.emplace(controlId, loadControlInfo(values));
}
/*
@@ -624,10 +624,8 @@ ControlList ControlSerializer::deserialize<ControlList>(ByteStreamBuffer &buffer
return {};
}
- ControlType type = static_cast<ControlType>(entry->type);
ctrls.set(entry->id,
- loadControlValue(type, values, entry->is_array,
- entry->count));
+ loadControlValue(values, entry->is_array, entry->count));
}
return ctrls;
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index 03ac6345..16d3547c 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -677,6 +677,9 @@ ControlInfoMap::ControlInfoMap(Map &&info, const ControlIdMap &idmap)
bool ControlInfoMap::validate()
{
+ if (!idmap_)
+ return false;
+
for (const auto &ctrl : *this) {
const ControlId *id = ctrl.first;
auto it = idmap_->find(id->id());
@@ -719,6 +722,8 @@ bool ControlInfoMap::validate()
*/
ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id)
{
+ ASSERT(idmap_);
+
return at(idmap_->at(id));
}
@@ -729,6 +734,8 @@ ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id)
*/
const ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id) const
{
+ ASSERT(idmap_);
+
return at(idmap_->at(id));
}
@@ -739,6 +746,9 @@ const ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id) const
*/
ControlInfoMap::size_type ControlInfoMap::count(unsigned int id) const
{
+ if (!idmap_)
+ return 0;
+
/*
* The ControlInfoMap and its idmap have a 1:1 mapping between their
* entries, we can thus just count the matching entries in idmap to
@@ -755,6 +765,9 @@ ControlInfoMap::size_type ControlInfoMap::count(unsigned int id) const
*/
ControlInfoMap::iterator ControlInfoMap::find(unsigned int id)
{
+ if (!idmap_)
+ return end();
+
auto iter = idmap_->find(id);
if (iter == idmap_->end())
return end();
@@ -770,6 +783,9 @@ ControlInfoMap::iterator ControlInfoMap::find(unsigned int id)
*/
ControlInfoMap::const_iterator ControlInfoMap::find(unsigned int id) const
{
+ if (!idmap_)
+ return end();
+
auto iter = idmap_->find(id);
if (iter == idmap_->end())
return end();
@@ -892,12 +908,26 @@ ControlList::ControlList(const ControlInfoMap &infoMap,
*/
/**
+ * \enum ControlList::MergePolicy
+ * \brief The policy used by the merge function
+ *
+ * \var ControlList::MergePolicy::KeepExisting
+ * \brief Existing controls in the target list are kept
+ *
+ * \var ControlList::MergePolicy::OverwriteExisting
+ * \brief Existing controls in the target list are updated
+ */
+
+/**
* \brief Merge the \a source into the ControlList
* \param[in] source The ControlList to merge into this object
+ * \param[in] policy Controls if existing elements in *this shall be
+ * overwritten
*
* Merging two control lists copies elements from the \a source and inserts
* them in *this. If the \a source contains elements whose key is already
- * present in *this, then those elements are not overwritten.
+ * present in *this, then those elements are only overwritten if
+ * \a policy is MergePolicy::OverwriteExisting.
*
* Only control lists created from the same ControlIdMap or ControlInfoMap may
* be merged. Attempting to do otherwise results in undefined behaviour.
@@ -905,10 +935,10 @@ ControlList::ControlList(const ControlInfoMap &infoMap,
* \todo Reimplement or implement an overloaded version which internally uses
* std::unordered_map::merge() and accepts a non-const argument.
*/
-void ControlList::merge(const ControlList &source)
+void ControlList::merge(const ControlList &source, MergePolicy policy)
{
/**
- * \todo: ASSERT that the current and source ControlList are derived
+ * \todo ASSERT that the current and source ControlList are derived
* from a compatible ControlIdMap, to prevent undefined behaviour due to
* id collisions.
*
@@ -920,7 +950,7 @@ void ControlList::merge(const ControlList &source)
*/
for (const auto &ctrl : source) {
- if (contains(ctrl.first)) {
+ if (policy == MergePolicy::KeepExisting && contains(ctrl.first)) {
const ControlId *id = idmap_->at(ctrl.first);
LOG(Controls, Warning)
<< "Control " << id->name() << " not overwritten";
@@ -933,17 +963,6 @@ void ControlList::merge(const ControlList &source)
/**
* \brief Check if the list contains a control with the specified \a id
- * \param[in] id The control ID
- *
- * \return True if the list contains a matching control, false otherwise
- */
-bool ControlList::contains(const ControlId &id) const
-{
- return controls_.find(id.id()) != controls_.end();
-}
-
-/**
- * \brief Check if the list contains a control with the specified \a id
* \param[in] id The control numerical ID
*
* \return True if the list contains a matching control, false otherwise
@@ -954,22 +973,20 @@ bool ControlList::contains(unsigned int id) const
}
/**
- * \fn template<typename T> T ControlList::get(const Control<T> &ctrl) const
+ * \fn ControlList::get(const Control<T> &ctrl) const
* \brief Get the value of control \a ctrl
* \param[in] ctrl The control
*
- * The behaviour is undefined if the control \a ctrl is not present in the
- * list. Use ControlList::contains() to test for the presence of a control in
- * the list before retrieving its value.
- *
- * The control value type shall match the type T, otherwise the behaviour is
- * undefined.
+ * Beside getting the value of a control, this function can also be used to
+ * check if a control is present in the ControlList by converting the returned
+ * std::optional<T> to bool (or calling its has_value() function).
*
- * \return The control value
+ * \return A std::optional<T> containing the control value, or std::nullopt if
+ * the control \a ctrl is not present in the list
*/
/**
- * \fn template<typename T, typename V> void ControlList::set(const Control<T> &ctrl, const V &value)
+ * \fn ControlList::set(const Control<T> &ctrl, const V &value)
* \brief Set the control \a ctrl value to \a value
* \param[in] ctrl The control
* \param[in] value The control value
@@ -983,8 +1000,7 @@ bool ControlList::contains(unsigned int id) const
*/
/**
- * \fn template<typename T, typename V> \
- * void ControlList::set(const Control<T> &ctrl, const std::initializer_list<V> &value)
+ * \fn ControlList::set(const Control<Span<T, Size>> &ctrl, const std::initializer_list<V> &value)
* \copydoc ControlList::set(const Control<T> &ctrl, const V &value)
*/
diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp
new file mode 100644
index 00000000..9f64eb51
--- /dev/null
+++ b/src/libcamera/converter.cpp
@@ -0,0 +1,335 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright 2022 NXP
+ *
+ * converter.cpp - Generic format converter interface
+ */
+
+#include "libcamera/internal/converter.h"
+
+#include <algorithm>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/media_device.h"
+
+/**
+ * \file internal/converter.h
+ * \brief Abstract converter
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Converter)
+
+/**
+ * \class Converter
+ * \brief Abstract Base Class for converter
+ *
+ * The Converter class is an Abstract Base Class defining the interfaces of
+ * converter implementations.
+ *
+ * Converters offer scaling and pixel format conversion services on an input
+ * stream. The converter can output multiple streams with individual conversion
+ * parameters from the same input stream.
+ */
+
+/**
+ * \brief Construct a Converter instance
+ * \param[in] media The media device implementing the converter
+ *
+ * This searches for the entity implementing the data streaming function in the
+ * media graph entities and use its device node as the converter device node.
+ */
+Converter::Converter(MediaDevice *media)
+{
+ const std::vector<MediaEntity *> &entities = media->entities();
+ auto it = std::find_if(entities.begin(), entities.end(),
+ [](MediaEntity *entity) {
+ return entity->function() == MEDIA_ENT_F_IO_V4L;
+ });
+ if (it == entities.end()) {
+ LOG(Converter, Error)
+ << "No entity suitable for implementing a converter in "
+ << media->driver() << " entities list.";
+ return;
+ }
+
+ deviceNode_ = (*it)->deviceNode();
+}
+
+Converter::~Converter()
+{
+}
+
+/**
+ * \fn Converter::loadConfiguration()
+ * \brief Load converter configuration from file
+ * \param[in] filename The file name path
+ *
+ * Load converter dependent configuration parameters to apply on the hardware.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
+ * \fn Converter::isValid()
+ * \brief Check if the converter configuration is valid
+ * \return True is the converter is valid, false otherwise
+ */
+
+/**
+ * \fn Converter::formats()
+ * \brief Retrieve the list of supported pixel formats for an input pixel format
+ * \param[in] input Input pixel format to retrieve output pixel format list for
+ * \return The list of supported output pixel formats
+ */
+
+/**
+ * \fn Converter::sizes()
+ * \brief Retrieve the range of minimum and maximum output sizes for an input size
+ * \param[in] input Input stream size to retrieve range for
+ * \return A range of output image sizes
+ */
+
+/**
+ * \fn Converter::strideAndFrameSize()
+ * \brief Retrieve the output stride and frame size for an input configutation
+ * \param[in] pixelFormat Input stream pixel format
+ * \param[in] size Input stream size
+ * \return A tuple indicating the stride and frame size or an empty tuple on error
+ */
+
+/**
+ * \fn Converter::configure()
+ * \brief Configure a set of output stream conversion from an input stream
+ * \param[in] inputCfg Input stream configuration
+ * \param[out] outputCfgs A list of output stream configurations
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
+ * \fn Converter::exportBuffers()
+ * \brief Export buffers from the converter device
+ * \param[in] output Output stream index exporting the buffers
+ * \param[in] count Number of buffers to allocate
+ * \param[out] buffers Vector to store allocated buffers
+ *
+ * This function operates similarly to V4L2VideoDevice::exportBuffers() on the
+ * output stream indicated by the \a output index.
+ *
+ * \return The number of allocated buffers on success or a negative error code
+ * otherwise
+ */
+
+/**
+ * \fn Converter::start()
+ * \brief Start the converter streaming operation
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
+ * \fn Converter::stop()
+ * \brief Stop the converter streaming operation
+ */
+
+/**
+ * \fn Converter::queueBuffers()
+ * \brief Queue buffers to converter device
+ * \param[in] input The frame buffer to apply the conversion
+ * \param[out] outputs The container holding the output stream indexes and
+ * their respective frame buffer outputs.
+ *
+ * This function queues the \a input frame buffer on the output streams of the
+ * \a outputs map key and retrieve the output frame buffer indicated by the
+ * buffer map value.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
+ * \var Converter::inputBufferReady
+ * \brief A signal emitted when the input frame buffer completes
+ */
+
+/**
+ * \var Converter::outputBufferReady
+ * \brief A signal emitted on each frame buffer completion of the output queue
+ */
+
+/**
+ * \fn Converter::deviceNode()
+ * \brief The converter device node attribute accessor
+ * \return The converter device node string
+ */
+
+/**
+ * \class ConverterFactoryBase
+ * \brief Base class for converter factories
+ *
+ * The ConverterFactoryBase class is the base of all specializations of the
+ * ConverterFactory class template. It implements the factory registration,
+ * maintains a registry of factories, and provides access to the registered
+ * factories.
+ */
+
+/**
+ * \brief Construct a converter factory base
+ * \param[in] name Name of the converter class
+ * \param[in] compatibles Name aliases of the converter class
+ *
+ * Creating an instance of the factory base registers it with the global list of
+ * factories, accessible through the factories() function.
+ *
+ * The factory \a name is used as unique identifier. If the converter
+ * implementation fully relies on a generic framework, the name should be the
+ * same as the framework. Otherwise, if the implementation is specialized, the
+ * factory name should match the driver name implementing the function.
+ *
+ * The factory \a compatibles holds a list of driver names implementing a generic
+ * subsystem without any personalizations.
+ */
+ConverterFactoryBase::ConverterFactoryBase(const std::string name, std::initializer_list<std::string> compatibles)
+ : name_(name), compatibles_(compatibles)
+{
+ registerType(this);
+}
+
+/**
+ * \fn ConverterFactoryBase::compatibles()
+ * \return The list of compatible name aliases of the converter
+ */
+
+/**
+ * \brief Create an instance of the converter corresponding to the media device
+ * \param[in] media The media device to create the converter for
+ *
+ * The converter is created by matching the factory name or any of its
+ * compatible aliases with the media device driver name.
+ *
+ * \return A new instance of the converter subclass corresponding to the media
+ * device, or null if the media device driver name doesn't match anything
+ */
+std::unique_ptr<Converter> ConverterFactoryBase::create(MediaDevice *media)
+{
+ const std::vector<ConverterFactoryBase *> &factories =
+ ConverterFactoryBase::factories();
+
+ for (const ConverterFactoryBase *factory : factories) {
+ const std::vector<std::string> &compatibles = factory->compatibles();
+ auto it = std::find(compatibles.begin(), compatibles.end(), media->driver());
+
+ if (it == compatibles.end() && media->driver() != factory->name_)
+ continue;
+
+ LOG(Converter, Debug)
+ << "Creating converter from "
+ << factory->name_ << " factory with "
+ << (it == compatibles.end() ? "no" : media->driver()) << " alias.";
+
+ std::unique_ptr<Converter> converter = factory->createInstance(media);
+ if (converter->isValid())
+ return converter;
+ }
+
+ return nullptr;
+}
+
+/**
+ * \brief Add a converter factory to the registry
+ * \param[in] factory Factory to use to construct the converter class
+ *
+ * The caller is responsible to guarantee the uniqueness of the converter
+ * factory name.
+ */
+void ConverterFactoryBase::registerType(ConverterFactoryBase *factory)
+{
+ std::vector<ConverterFactoryBase *> &factories =
+ ConverterFactoryBase::factories();
+
+ factories.push_back(factory);
+}
+
+/**
+ * \brief Retrieve the list of all converter factory names
+ * \return The list of all converter factory names
+ */
+std::vector<std::string> ConverterFactoryBase::names()
+{
+ std::vector<std::string> list;
+
+ std::vector<ConverterFactoryBase *> &factories =
+ ConverterFactoryBase::factories();
+
+ for (ConverterFactoryBase *factory : factories) {
+ list.push_back(factory->name_);
+ for (auto alias : factory->compatibles())
+ list.push_back(alias);
+ }
+
+ return list;
+}
+
+/**
+ * \brief Retrieve the list of all converter factories
+ * \return The list of converter factories
+ */
+std::vector<ConverterFactoryBase *> &ConverterFactoryBase::factories()
+{
+ /*
+ * The static factories map is defined inside the function to ensure
+ * it gets initialized on first use, without any dependency on link
+ * order.
+ */
+ static std::vector<ConverterFactoryBase *> factories;
+ return factories;
+}
+
+/**
+ * \var ConverterFactoryBase::name_
+ * \brief The name of the factory
+ */
+
+/**
+ * \var ConverterFactoryBase::compatibles_
+ * \brief The list holding the factory compatibles
+ */
+
+/**
+ * \class ConverterFactory
+ * \brief Registration of ConverterFactory classes and creation of instances
+ * \param _Converter The converter class type for this factory
+ *
+ * To facilitate discovery and instantiation of Converter classes, the
+ * ConverterFactory class implements auto-registration of converter helpers.
+ * Each Converter subclass shall register itself using the REGISTER_CONVERTER()
+ * macro, which will create a corresponding instance of a ConverterFactory
+ * subclass and register it with the static list of factories.
+ */
+
+/**
+ * \fn ConverterFactory::ConverterFactory(const char *name, std::initializer_list<std::string> compatibles)
+ * \brief Construct a converter factory
+ * \details \copydetails ConverterFactoryBase::ConverterFactoryBase
+ */
+
+/**
+ * \fn ConverterFactory::createInstance() const
+ * \brief Create an instance of the Converter corresponding to the factory
+ * \param[in] media Media device pointer
+ * \return A unique pointer to a newly constructed instance of the Converter
+ * subclass corresponding to the factory
+ */
+
+/**
+ * \def REGISTER_CONVERTER
+ * \brief Register a converter with the Converter factory
+ * \param[in] name Converter name used to register the class
+ * \param[in] converter Class name of Converter derived class to register
+ * \param[in] compatibles List of compatible names
+ *
+ * Register a Converter subclass with the factory and make it available to try
+ * and match converters.
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
index 77c44fc8..a5fc979b 100644
--- a/src/libcamera/pipeline/simple/converter.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -1,11 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
*
- * converter.cpp - Format converter for simple pipeline handler
+ * converter_v4l2_m2m.cpp - V4L2 M2M Format converter
*/
-#include "converter.h"
+#include "libcamera/internal/converter/converter_v4l2_m2m.h"
#include <algorithm>
#include <limits.h>
@@ -21,18 +22,23 @@
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_videodevice.h"
+/**
+ * \file internal/converter/converter_v4l2_m2m.h
+ * \brief V4L2 M2M based converter
+ */
+
namespace libcamera {
-LOG_DECLARE_CATEGORY(SimplePipeline)
+LOG_DECLARE_CATEGORY(Converter)
/* -----------------------------------------------------------------------------
- * SimpleConverter::Stream
+ * V4L2M2MConverter::Stream
*/
-SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
+V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
: converter_(converter), index_(index)
{
- m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);
+ m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
@@ -42,11 +48,11 @@ SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
m2m_.reset();
}
-int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
- const StreamConfiguration &outputCfg)
+int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
+ const StreamConfiguration &outputCfg)
{
V4L2PixelFormat videoFormat =
- V4L2PixelFormat::fromPixelFormat(inputCfg.pixelFormat);
+ m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
V4L2DeviceFormat format;
format.fourcc = videoFormat;
@@ -56,14 +62,14 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
int ret = m2m_->output()->setFormat(&format);
if (ret < 0) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to set input format: " << strerror(-ret);
return ret;
}
if (format.fourcc != videoFormat || format.size != inputCfg.size ||
format.planes[0].bpl != inputCfg.stride) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Input format not supported (requested "
<< inputCfg.size << "-" << videoFormat
<< ", got " << format << ")";
@@ -71,20 +77,20 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
}
/* Set the pixel format and size on the output. */
- videoFormat = V4L2PixelFormat::fromPixelFormat(outputCfg.pixelFormat);
+ videoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);
format = {};
format.fourcc = videoFormat;
format.size = outputCfg.size;
ret = m2m_->capture()->setFormat(&format);
if (ret < 0) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to set output format: " << strerror(-ret);
return ret;
}
if (format.fourcc != videoFormat || format.size != outputCfg.size) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Output format not supported";
return -EINVAL;
}
@@ -95,13 +101,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
return 0;
}
-int SimpleConverter::Stream::exportBuffers(unsigned int count,
- std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
return m2m_->capture()->exportBuffers(count, buffers);
}
-int SimpleConverter::Stream::start()
+int V4L2M2MConverter::Stream::start()
{
int ret = m2m_->output()->importBuffers(inputBufferCount_);
if (ret < 0)
@@ -128,7 +134,7 @@ int SimpleConverter::Stream::start()
return 0;
}
-void SimpleConverter::Stream::stop()
+void V4L2M2MConverter::Stream::stop()
{
m2m_->capture()->streamOff();
m2m_->output()->streamOff();
@@ -136,8 +142,7 @@ void SimpleConverter::Stream::stop()
m2m_->output()->releaseBuffers();
}
-int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
- FrameBuffer *output)
+int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
{
int ret = m2m_->output()->queueBuffer(input);
if (ret < 0)
@@ -150,12 +155,12 @@ int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
return 0;
}
-std::string SimpleConverter::Stream::logPrefix() const
+std::string V4L2M2MConverter::Stream::logPrefix() const
{
return "stream" + std::to_string(index_);
}
-void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
{
auto it = converter_->queue_.find(buffer);
if (it == converter_->queue_.end())
@@ -167,32 +172,34 @@ void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
}
}
-void SimpleConverter::Stream::captureBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
{
converter_->outputBufferReady.emit(buffer);
}
/* -----------------------------------------------------------------------------
- * SimpleConverter
+ * V4L2M2MConverter
*/
-SimpleConverter::SimpleConverter(MediaDevice *media)
+/**
+ * \class libcamera::V4L2M2MConverter
+ * \brief The V4L2 M2M converter implements the converter interface based on
+ * V4L2 M2M device.
+*/
+
+/**
+ * \fn V4L2M2MConverter::V4L2M2MConverter
+ * \brief Construct a V4L2M2MConverter instance
+ * \param[in] media The media device implementing the converter
+ */
+
+V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
+ : Converter(media)
{
- /*
- * Locate the video node. There's no need to validate the pipeline
- * further, the caller guarantees that this is a V4L2 mem2mem device.
- */
- const std::vector<MediaEntity *> &entities = media->entities();
- auto it = std::find_if(entities.begin(), entities.end(),
- [](MediaEntity *entity) {
- return entity->function() == MEDIA_ENT_F_IO_V4L;
- });
- if (it == entities.end())
+ if (deviceNode().empty())
return;
- deviceNode_ = (*it)->deviceNode();
-
- m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);
+ m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode());
int ret = m2m_->open();
if (ret < 0) {
m2m_.reset();
@@ -200,7 +207,21 @@ SimpleConverter::SimpleConverter(MediaDevice *media)
}
}
-std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
+/**
+ * \fn libcamera::V4L2M2MConverter::loadConfiguration
+ * \details \copydetails libcamera::Converter::loadConfiguration
+ */
+
+/**
+ * \fn libcamera::V4L2M2MConverter::isValid
+ * \details \copydetails libcamera::Converter::isValid
+ */
+
+/**
+ * \fn libcamera::V4L2M2MConverter::formats
+ * \details \copydetails libcamera::Converter::formats
+ */
+std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)
{
if (!m2m_)
return {};
@@ -210,16 +231,22 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
* enumerate the conversion capabilities on its output (V4L2 capture).
*/
V4L2DeviceFormat v4l2Format;
- v4l2Format.fourcc = V4L2PixelFormat::fromPixelFormat(input);
+ v4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);
v4l2Format.size = { 1, 1 };
int ret = m2m_->output()->setFormat(&v4l2Format);
if (ret < 0) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to set format: " << strerror(-ret);
return {};
}
+ if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
+ LOG(Converter, Debug)
+ << "Input format " << input << " not supported.";
+ return {};
+ }
+
std::vector<PixelFormat> pixelFormats;
for (const auto &format : m2m_->capture()->formats()) {
@@ -231,7 +258,10 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
return pixelFormats;
}
-SizeRange SimpleConverter::sizes(const Size &input)
+/**
+ * \copydoc libcamera::Converter::sizes
+ */
+SizeRange V4L2M2MConverter::sizes(const Size &input)
{
if (!m2m_)
return {};
@@ -246,7 +276,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
int ret = m2m_->output()->setFormat(&format);
if (ret < 0) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to set format: " << strerror(-ret);
return {};
}
@@ -256,7 +286,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
format.size = { 1, 1 };
ret = m2m_->capture()->setFormat(&format);
if (ret < 0) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to set format: " << strerror(-ret);
return {};
}
@@ -266,7 +296,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
format.size = { UINT_MAX, UINT_MAX };
ret = m2m_->capture()->setFormat(&format);
if (ret < 0) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to set format: " << strerror(-ret);
return {};
}
@@ -276,12 +306,15 @@ SizeRange SimpleConverter::sizes(const Size &input)
return sizes;
}
+/**
+ * \copydoc libcamera::Converter::strideAndFrameSize
+ */
std::tuple<unsigned int, unsigned int>
-SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
- const Size &size)
+V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
+ const Size &size)
{
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(pixelFormat);
+ format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
format.size = size;
int ret = m2m_->capture()->tryFormat(&format);
@@ -291,8 +324,11 @@ SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
}
-int SimpleConverter::configure(const StreamConfiguration &inputCfg,
- const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+/**
+ * \copydoc libcamera::Converter::configure
+ */
+int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
{
int ret = 0;
@@ -303,7 +339,7 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
Stream &stream = streams_.emplace_back(this, i);
if (!stream.isValid()) {
- LOG(SimplePipeline, Error)
+ LOG(Converter, Error)
<< "Failed to create stream " << i;
ret = -EINVAL;
break;
@@ -322,8 +358,11 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
return 0;
}
-int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
- std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+/**
+ * \copydoc libcamera::Converter::exportBuffers
+ */
+int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
if (output >= streams_.size())
return -EINVAL;
@@ -331,7 +370,10 @@ int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
return streams_[output].exportBuffers(count, buffers);
}
-int SimpleConverter::start()
+/**
+ * \copydoc libcamera::Converter::start
+ */
+int V4L2M2MConverter::start()
{
int ret;
@@ -346,14 +388,20 @@ int SimpleConverter::start()
return 0;
}
-void SimpleConverter::stop()
+/**
+ * \copydoc libcamera::Converter::stop
+ */
+void V4L2M2MConverter::stop()
{
for (Stream &stream : utils::reverse(streams_))
stream.stop();
}
-int SimpleConverter::queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs)
+/**
+ * \copydoc libcamera::Converter::queueBuffers
+ */
+int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
+ const std::map<unsigned int, FrameBuffer *> &outputs)
{
unsigned int mask = 0;
int ret;
@@ -396,4 +444,11 @@ int SimpleConverter::queueBuffers(FrameBuffer *input,
return 0;
}
+static std::initializer_list<std::string> compatibles = {
+ "mtk-mdp",
+ "pxp",
+};
+
+REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, compatibles)
+
} /* namespace libcamera */
diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
new file mode 100644
index 00000000..2aa72fe4
--- /dev/null
+++ b/src/libcamera/converter/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+ 'converter_v4l2_m2m.cpp'
+])
diff --git a/src/libcamera/delayed_controls.cpp b/src/libcamera/delayed_controls.cpp
index 9667187e..777441e8 100644
--- a/src/libcamera/delayed_controls.cpp
+++ b/src/libcamera/delayed_controls.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* delayed_controls.h - Helper to deal with controls that take effect with a delay
*/
@@ -115,8 +115,6 @@ DelayedControls::DelayedControls(V4L2Device *device,
*/
void DelayedControls::reset()
{
- running_ = false;
- firstSequence_ = 0;
queueCount_ = 1;
writeCount_ = 0;
@@ -204,8 +202,7 @@ bool DelayedControls::push(const ControlList &controls)
*/
ControlList DelayedControls::get(uint32_t sequence)
{
- uint32_t adjustedSeq = sequence - firstSequence_;
- unsigned int index = std::max<int>(0, adjustedSeq - maxDelay_);
+ unsigned int index = std::max<int>(0, sequence - maxDelay_);
ControlList out(device_->controls());
for (const auto &ctrl : values_) {
@@ -236,11 +233,6 @@ void DelayedControls::applyControls(uint32_t sequence)
{
LOG(DelayedControls, Debug) << "frame " << sequence << " started";
- if (!running_) {
- firstSequence_ = sequence;
- running_ = true;
- }
-
/*
* Create control list peeking ahead in the value queue to ensure
* values are set in time to satisfy the sensor delay.
@@ -279,7 +271,7 @@ void DelayedControls::applyControls(uint32_t sequence)
}
}
- writeCount_ = sequence - firstSequence_ + 1;
+ writeCount_ = sequence + 1;
while (writeCount_ > queueCount_) {
LOG(DelayedControls, Debug)
diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
index d1258050..fbbf0559 100644
--- a/src/libcamera/device_enumerator.cpp
+++ b/src/libcamera/device_enumerator.cpp
@@ -56,7 +56,7 @@ LOG_DEFINE_CATEGORY(DeviceEnumerator)
* names can be added as match criteria.
*
* Pipeline handlers are recommended to add entities to DeviceMatch as
- * appropriare to ensure that the media device they need can be uniquely
+ * appropriate to ensure that the media device they need can be uniquely
* identified. This is useful when the corresponding kernel driver can produce
* different graphs, for instance as a result of different driver versions or
* hardware configurations, and not all those graphs are suitable for a pipeline
@@ -101,8 +101,14 @@ bool DeviceMatch::match(const MediaDevice *device) const
for (const MediaEntity *entity : device->entities()) {
if (name == entity->name()) {
- found = true;
- break;
+ if (!entity->deviceNode().empty()) {
+ found = true;
+ break;
+ } else {
+ LOG(DeviceEnumerator, Debug)
+ << "Skip " << entity->name()
+ << ": no device node";
+ }
}
}
@@ -161,7 +167,7 @@ std::unique_ptr<DeviceEnumerator> DeviceEnumerator::create()
DeviceEnumerator::~DeviceEnumerator()
{
- for (std::shared_ptr<MediaDevice> media : devices_) {
+ for (const std::shared_ptr<MediaDevice> &media : devices_) {
if (media->busy())
LOG(DeviceEnumerator, Error)
<< "Removing media device " << media->deviceNode()
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp
index 5317afbd..0abc1248 100644
--- a/src/libcamera/device_enumerator_udev.cpp
+++ b/src/libcamera/device_enumerator_udev.cpp
@@ -13,6 +13,7 @@
#include <list>
#include <map>
#include <string.h>
+#include <string_view>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>
#include <unistd.h>
@@ -315,6 +316,7 @@ int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum)
* enumerator.
*/
deps->deps_.erase(devnum);
+ devMap_.erase(it);
if (deps->deps_.empty()) {
LOG(DeviceEnumerator, Debug)
@@ -330,18 +332,18 @@ int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum)
void DeviceEnumeratorUdev::udevNotify()
{
struct udev_device *dev = udev_monitor_receive_device(monitor_);
- std::string action(udev_device_get_action(dev));
- std::string deviceNode(udev_device_get_devnode(dev));
+ std::string_view action(udev_device_get_action(dev));
+ std::string_view deviceNode(udev_device_get_devnode(dev));
LOG(DeviceEnumerator, Debug)
- << action << " device " << udev_device_get_devnode(dev);
+ << action << " device " << deviceNode;
if (action == "add") {
addUdevDevice(dev);
} else if (action == "remove") {
const char *subsystem = udev_device_get_subsystem(dev);
if (subsystem && !strcmp(subsystem, "media"))
- removeDevice(deviceNode);
+ removeDevice(std::string(deviceNode));
}
udev_device_unref(dev);
diff --git a/src/libcamera/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp
new file mode 100644
index 00000000..b4509e72
--- /dev/null
+++ b/src/libcamera/dma_heaps.cpp
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * dma_heaps.cpp - Helper class for dma-heap allocations.
+ */
+
+#include "libcamera/internal/dma_heaps.h"
+
+#include <array>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file dma_heaps.cpp
+ * \brief dma-heap allocator
+ */
+
+namespace libcamera {
+
+/*
+ * /dev/dma_heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
+ * to only have to worry about importing.
+ *
+ * Annoyingly, should the cma heap size be specified on the kernel command line
+ * instead of DT, the heap gets named "reserved" instead.
+ */
+
+#ifndef __DOXYGEN__
+struct DmaHeapInfo {
+ DmaHeap::DmaHeapFlag type;
+ const char *deviceNodeName;
+};
+#endif
+
+static constexpr std::array<DmaHeapInfo, 3> heapInfos = { {
+ { DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/linux,cma" },
+ { DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/reserved" },
+ { DmaHeap::DmaHeapFlag::System, "/dev/dma_heap/system" },
+} };
+
+LOG_DEFINE_CATEGORY(DmaHeap)
+
+/**
+ * \class DmaHeap
+ * \brief Helper class for dma-heap allocations
+ *
+ * DMA heaps are kernel devices that provide an API to allocate memory from
+ * different pools called "heaps", wrap each allocated piece of memory in a
+ * dmabuf object, and return the dmabuf file descriptor to userspace. Multiple
+ * heaps can be provided by the system, with different properties for the
+ * underlying memory.
+ *
+ * This class wraps a DMA heap selected at construction time, and exposes
+ * functions to manage memory allocation.
+ */
+
+/**
+ * \enum DmaHeap::DmaHeapFlag
+ * \brief Type of the dma-heap
+ * \var DmaHeap::Cma
+ * \brief Allocate from a CMA dma-heap, providing physically-contiguous memory
+ * \var DmaHeap::System
+ * \brief Allocate from the system dma-heap, using the page allocator
+ */
+
+/**
+ * \typedef DmaHeap::DmaHeapFlags
+ * \brief A bitwise combination of DmaHeap::DmaHeapFlag values
+ */
+
+/**
+ * \brief Construct a DmaHeap of a given type
+ * \param[in] type The type(s) of the dma-heap(s) to allocate from
+ *
+ * The DMA heap type is selected with the \a type parameter, which defaults to
+ * the CMA heap. If no heap of the given type can be accessed, the constructed
+ * DmaHeap instance is invalid as indicated by the isValid() function.
+ *
+ * Multiple types can be selected by combining type flags, in which case the
+ * constructed DmaHeap will match one of the types. If the system provides
+ * multiple heaps that match the requested types, which heap is used is
+ * undefined.
+ */
+DmaHeap::DmaHeap(DmaHeapFlags type)
+{
+ for (const auto &info : heapInfos) {
+ if (!(type & info.type))
+ continue;
+
+ int ret = ::open(info.deviceNodeName, O_RDWR | O_CLOEXEC, 0);
+ if (ret < 0) {
+ ret = errno;
+ LOG(DmaHeap, Debug)
+ << "Failed to open " << info.deviceNodeName << ": "
+ << strerror(ret);
+ continue;
+ }
+
+ LOG(DmaHeap, Debug) << "Using " << info.deviceNodeName;
+ dmaHeapHandle_ = UniqueFD(ret);
+ break;
+ }
+
+ if (!dmaHeapHandle_.isValid())
+ LOG(DmaHeap, Error) << "Could not open any dmaHeap device";
+}
+
+/**
+ * \brief Destroy the DmaHeap instance
+ */
+DmaHeap::~DmaHeap() = default;
+
+/**
+ * \fn DmaHeap::isValid()
+ * \brief Check if the DmaHeap instance is valid
+ * \return True if the DmaHeap is valid, false otherwise
+ */
+
+/**
+ * \brief Allocate a dma-buf from the DmaHeap
+ * \param [in] name The name to set for the allocated buffer
+ * \param [in] size The size of the buffer to allocate
+ *
+ * Allocates a dma-buf with read/write access.
+ *
+ * If the allocation fails, return an invalid UniqueFD.
+ *
+ * \return The UniqueFD of the allocated buffer
+ */
+UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
+{
+ int ret;
+
+ if (!name)
+ return {};
+
+ struct dma_heap_allocation_data alloc = {};
+
+ alloc.len = size;
+ alloc.fd_flags = O_CLOEXEC | O_RDWR;
+
+ ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
+ if (ret < 0) {
+ LOG(DmaHeap, Error) << "dmaHeap allocation failure for " << name;
+ return {};
+ }
+
+ UniqueFD allocFd(alloc.fd);
+ ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
+ if (ret < 0) {
+ LOG(DmaHeap, Error) << "dmaHeap naming failure for " << name;
+ return {};
+ }
+
+ return allocFd;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/formats.cpp b/src/libcamera/formats.cpp
index 283ecb3d..955c3fba 100644
--- a/src/libcamera/formats.cpp
+++ b/src/libcamera/formats.cpp
@@ -33,7 +33,7 @@ LOG_DEFINE_CATEGORY(Formats)
* used in pipeline handlers.
*
* \var PixelFormatInfo::name
- * \brief The format name as a human-readable string, used as the test
+ * \brief The format name as a human-readable string, used as the text
* representation of the PixelFormat
*
* \var PixelFormatInfo::format
@@ -42,19 +42,16 @@ LOG_DEFINE_CATEGORY(Formats)
* \var PixelFormatInfo::v4l2Formats
* \brief The V4L2 pixel formats corresponding to the PixelFormat
*
- * Multiple V4L2 formats may exist for one PixelFormat when the format uses
- * multiple planes, as V4L2 defines separate 4CCs for contiguous and separate
- * planes formats. The two entries in the array store the contiguous and
- * non-contiguous V4L2 formats respectively. If the PixelFormat isn't a
- * multiplanar format, or if no corresponding non-contiguous V4L2 format
- * exists, the second entry is invalid.
+ * Multiple V4L2 formats may exist for one PixelFormat, as V4L2 defines
+ * separate 4CCs for contiguous and non-contiguous versions of the same image
+ * format.
*
* \var PixelFormatInfo::bitsPerPixel
* \brief The average number of bits per pixel
*
- * The number per pixel averages the total number of bits for all colour
- * components over the whole image, excluding any padding bits or padding
- * pixels.
+ * The number of bits per pixel averages the total number of bits for all
+ * colour components over the whole image, excluding any padding bits or
+ * padding pixels.
*
* For formats that store pixels with bit padding within words, only the
* effective bits are taken into account. For instance, 12-bit Bayer data
@@ -156,10 +153,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::RGB565, {
.name = "RGB565",
.format = formats::RGB565,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_RGB565),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_RGB565), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -169,10 +163,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::RGB565_BE, {
.name = "RGB565_BE",
.format = formats::RGB565_BE,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_RGB565X),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_RGB565X), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -182,10 +173,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::BGR888, {
.name = "BGR888",
.format = formats::BGR888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_RGB24),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_RGB24), },
.bitsPerPixel = 24,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -195,10 +183,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::RGB888, {
.name = "RGB888",
.format = formats::RGB888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_BGR24),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_BGR24), },
.bitsPerPixel = 24,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -208,10 +193,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::XRGB8888, {
.name = "XRGB8888",
.format = formats::XRGB8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_XBGR32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_XBGR32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -221,10 +203,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::XBGR8888, {
.name = "XBGR8888",
.format = formats::XBGR8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_RGBX32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_RGBX32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -234,10 +213,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::RGBX8888, {
.name = "RGBX8888",
.format = formats::RGBX8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_BGRX32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_BGRX32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -247,10 +223,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::BGRX8888, {
.name = "BGRX8888",
.format = formats::BGRX8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_XRGB32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_XRGB32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -260,10 +233,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::ABGR8888, {
.name = "ABGR8888",
.format = formats::ABGR8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_RGBA32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_RGBA32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -273,10 +243,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::ARGB8888, {
.name = "ARGB8888",
.format = formats::ARGB8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_ABGR32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_ABGR32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -286,10 +253,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::BGRA8888, {
.name = "BGRA8888",
.format = formats::BGRA8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_ARGB32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_ARGB32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -299,10 +263,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::RGBA8888, {
.name = "RGBA8888",
.format = formats::RGBA8888,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_BGRA32),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_BGRA32), },
.bitsPerPixel = 32,
.colourEncoding = PixelFormatInfo::ColourEncodingRGB,
.packed = false,
@@ -314,10 +275,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::YUYV, {
.name = "YUYV",
.format = formats::YUYV,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_YUYV),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YUYV), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -327,10 +285,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::YVYU, {
.name = "YVYU",
.format = formats::YVYU,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_YVYU),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YVYU), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -340,10 +295,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::UYVY, {
.name = "UYVY",
.format = formats::UYVY,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_UYVY),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_UYVY), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -353,24 +305,41 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::VYUY, {
.name = "VYUY",
.format = formats::VYUY,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_VYUY),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_VYUY), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
.pixelsPerGroup = 2,
.planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
} },
+ { formats::AVUY8888, {
+ .name = "AVUY8888",
+ .format = formats::AVUY8888,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YUVA32), },
+ .bitsPerPixel = 32,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ .packed = false,
+ .pixelsPerGroup = 1,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::XVUY8888, {
+ .name = "XVUY8888",
+ .format = formats::XVUY8888,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YUVX32), },
+ .bitsPerPixel = 32,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ .packed = false,
+ .pixelsPerGroup = 1,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
/* YUV planar formats. */
{ formats::NV12, {
.name = "NV12",
.format = formats::NV12,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_NV12),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_NV12M),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV12),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV12M),
},
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -382,8 +351,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "NV21",
.format = formats::NV21,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_NV21),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_NV21M),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV21),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV21M),
},
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -395,8 +364,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "NV16",
.format = formats::NV16,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_NV16),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_NV16M),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV16),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV16M),
},
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -408,8 +377,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "NV61",
.format = formats::NV61,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_NV61),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_NV61M),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV61),
+ V4L2PixelFormat(V4L2_PIX_FMT_NV61M),
},
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -420,10 +389,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::NV24, {
.name = "NV24",
.format = formats::NV24,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_NV24),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_NV24), },
.bitsPerPixel = 24,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -433,10 +399,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::NV42, {
.name = "NV42",
.format = formats::NV42,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_NV42),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_NV42), },
.bitsPerPixel = 24,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -447,8 +410,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "YUV420",
.format = formats::YUV420,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_YUV420),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_YUV420M),
+ V4L2PixelFormat(V4L2_PIX_FMT_YUV420),
+ V4L2PixelFormat(V4L2_PIX_FMT_YUV420M),
},
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -460,8 +423,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "YVU420",
.format = formats::YVU420,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_YVU420),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_YVU420M),
+ V4L2PixelFormat(V4L2_PIX_FMT_YVU420),
+ V4L2PixelFormat(V4L2_PIX_FMT_YVU420M),
},
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -473,8 +436,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "YUV422",
.format = formats::YUV422,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_YUV422P),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_YUV422M),
+ V4L2PixelFormat(V4L2_PIX_FMT_YUV422P),
+ V4L2PixelFormat(V4L2_PIX_FMT_YUV422M),
},
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -485,10 +448,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::YVU422, {
.name = "YVU422",
.format = formats::YVU422,
- .v4l2Formats = {
- .single = V4L2PixelFormat(),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_YVU422M),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YVU422M), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -498,10 +458,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::YUV444, {
.name = "YUV444",
.format = formats::YUV444,
- .v4l2Formats = {
- .single = V4L2PixelFormat(),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_YUV444M),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YUV444M), },
.bitsPerPixel = 24,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -511,10 +468,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::YVU444, {
.name = "YVU444",
.format = formats::YVU444,
- .v4l2Formats = {
- .single = V4L2PixelFormat(),
- .multi = V4L2PixelFormat(V4L2_PIX_FMT_YVU444M),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_YVU444M), },
.bitsPerPixel = 24,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -526,10 +480,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::R8, {
.name = "R8",
.format = formats::R8,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_GREY),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_GREY), },
.bitsPerPixel = 8,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
@@ -539,51 +490,49 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::R10, {
.name = "R10",
.format = formats::R10,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_Y10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
.pixelsPerGroup = 1,
.planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }},
} },
+ { formats::R10_CSI2P, {
+ .name = "R10_CSI2P",
+ .format = formats::R10_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y10P), },
+ .bitsPerPixel = 10,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ .packed = true,
+ .pixelsPerGroup = 4,
+ .planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
{ formats::R12, {
.name = "R12",
.format = formats::R12,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_Y12),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y12), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
.packed = false,
.pixelsPerGroup = 1,
.planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }},
} },
- { formats::R10_CSI2P, {
- .name = "R10_CSI2P",
- .format = formats::R10,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_Y10P),
- .multi = V4L2PixelFormat(),
- },
- .bitsPerPixel = 10,
+ { formats::R16, {
+ .name = "R16",
+ .format = formats::R16,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y16), },
+ .bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
- .packed = true,
- .pixelsPerGroup = 4,
- .planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }},
+ .packed = false,
+ .pixelsPerGroup = 1,
+ .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }},
} },
/* Bayer formats. */
{ formats::SBGGR8, {
.name = "SBGGR8",
.format = formats::SBGGR8,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8), },
.bitsPerPixel = 8,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -593,10 +542,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG8, {
.name = "SGBRG8",
.format = formats::SGBRG8,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGBRG8),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG8), },
.bitsPerPixel = 8,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -606,10 +552,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG8, {
.name = "SGRBG8",
.format = formats::SGRBG8,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8), },
.bitsPerPixel = 8,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -619,10 +562,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB8, {
.name = "SRGGB8",
.format = formats::SRGGB8,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SRGGB8),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB8), },
.bitsPerPixel = 8,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -632,10 +572,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SBGGR10, {
.name = "SBGGR10",
.format = formats::SBGGR10,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -645,10 +582,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG10, {
.name = "SGBRG10",
.format = formats::SGBRG10,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -658,10 +592,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG10, {
.name = "SGRBG10",
.format = formats::SGRBG10,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -671,10 +602,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB10, {
.name = "SRGGB10",
.format = formats::SRGGB10,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -684,10 +612,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SBGGR10_CSI2P, {
.name = "SBGGR10_CSI2P",
.format = formats::SBGGR10_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -697,10 +622,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG10_CSI2P, {
.name = "SGBRG10_CSI2P",
.format = formats::SGBRG10_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10P), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -710,10 +632,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG10_CSI2P, {
.name = "SGRBG10_CSI2P",
.format = formats::SGRBG10_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10P), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -723,10 +642,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB10_CSI2P, {
.name = "SRGGB10_CSI2P",
.format = formats::SRGGB10_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -736,10 +652,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SBGGR12, {
.name = "SBGGR12",
.format = formats::SBGGR12,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -749,10 +662,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG12, {
.name = "SGBRG12",
.format = formats::SGBRG12,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -762,10 +672,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG12, {
.name = "SGRBG12",
.format = formats::SGRBG12,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -775,10 +682,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB12, {
.name = "SRGGB12",
.format = formats::SRGGB12,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -788,10 +692,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SBGGR12_CSI2P, {
.name = "SBGGR12_CSI2P",
.format = formats::SBGGR12_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12P), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -801,10 +702,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG12_CSI2P, {
.name = "SGBRG12_CSI2P",
.format = formats::SGBRG12_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12P), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -814,10 +712,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG12_CSI2P, {
.name = "SGRBG12_CSI2P",
.format = formats::SGRBG12_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12P), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -827,23 +722,97 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB12_CSI2P, {
.name = "SRGGB12_CSI2P",
.format = formats::SRGGB12_CSI2P,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12P),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12P), },
.bitsPerPixel = 12,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
.pixelsPerGroup = 2,
.planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }},
} },
+ { formats::SBGGR14, {
+ .name = "SBGGR14",
+ .format = formats::SBGGR14,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR14), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = false,
+ .pixelsPerGroup = 2,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SGBRG14, {
+ .name = "SGBRG14",
+ .format = formats::SGBRG14,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG14), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = false,
+ .pixelsPerGroup = 2,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SGRBG14, {
+ .name = "SGRBG14",
+ .format = formats::SGRBG14,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG14), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = false,
+ .pixelsPerGroup = 2,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SRGGB14, {
+ .name = "SRGGB14",
+ .format = formats::SRGGB14,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB14), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = false,
+ .pixelsPerGroup = 2,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SBGGR14_CSI2P, {
+ .name = "SBGGR14_CSI2P",
+ .format = formats::SBGGR14_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR14P), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = true,
+ .pixelsPerGroup = 4,
+ .planes = {{ { 7, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SGBRG14_CSI2P, {
+ .name = "SGBRG14_CSI2P",
+ .format = formats::SGBRG14_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG14P), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = true,
+ .pixelsPerGroup = 4,
+ .planes = {{ { 7, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SGRBG14_CSI2P, {
+ .name = "SGRBG14_CSI2P",
+ .format = formats::SGRBG14_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG14P), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = true,
+ .pixelsPerGroup = 4,
+ .planes = {{ { 7, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
+ { formats::SRGGB14_CSI2P, {
+ .name = "SRGGB14_CSI2P",
+ .format = formats::SRGGB14_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB14P), },
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ .packed = true,
+ .pixelsPerGroup = 4,
+ .planes = {{ { 7, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
{ formats::SBGGR16, {
.name = "SBGGR16",
.format = formats::SBGGR16,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR16),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR16), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -853,10 +822,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG16, {
.name = "SGBRG16",
.format = formats::SGBRG16,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGBRG16),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG16), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -866,10 +832,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG16, {
.name = "SGRBG16",
.format = formats::SGRBG16,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG16),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG16), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -879,10 +842,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB16, {
.name = "SRGGB16",
.format = formats::SRGGB16,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16), },
.bitsPerPixel = 16,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = false,
@@ -892,10 +852,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SBGGR10_IPU3, {
.name = "SBGGR10_IPU3",
.format = formats::SBGGR10_IPU3,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SBGGR10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SBGGR10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -906,10 +863,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGBRG10_IPU3, {
.name = "SGBRG10_IPU3",
.format = formats::SGBRG10_IPU3,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SGBRG10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SGBRG10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -919,10 +873,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SGRBG10_IPU3, {
.name = "SGRBG10_IPU3",
.format = formats::SGRBG10_IPU3,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SGRBG10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SGRBG10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -932,10 +883,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
{ formats::SRGGB10_IPU3, {
.name = "SRGGB10_IPU3",
.format = formats::SRGGB10_IPU3,
- .v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SRGGB10),
- .multi = V4L2PixelFormat(),
- },
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SRGGB10), },
.bitsPerPixel = 10,
.colourEncoding = PixelFormatInfo::ColourEncodingRAW,
.packed = true,
@@ -948,8 +896,8 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.name = "MJPEG",
.format = formats::MJPEG,
.v4l2Formats = {
- .single = V4L2PixelFormat(V4L2_PIX_FMT_MJPEG),
- .multi = V4L2PixelFormat(),
+ V4L2PixelFormat(V4L2_PIX_FMT_MJPEG),
+ V4L2PixelFormat(V4L2_PIX_FMT_JPEG),
},
.bitsPerPixel = 0,
.colourEncoding = PixelFormatInfo::ColourEncodingYUV,
@@ -987,22 +935,22 @@ const PixelFormatInfo &PixelFormatInfo::info(const PixelFormat &format)
}
/**
- * \brief Retrieve information about a pixel format
+ * \brief Retrieve information about a V4L2 pixel format
* \param[in] format The V4L2 pixel format
* \return The PixelFormatInfo describing the V4L2 \a format if known, or an
* invalid PixelFormatInfo otherwise
*/
const PixelFormatInfo &PixelFormatInfo::info(const V4L2PixelFormat &format)
{
- const auto &info = std::find_if(pixelFormatInfo.begin(), pixelFormatInfo.end(),
- [format](auto pair) {
- return pair.second.v4l2Formats.single == format ||
- pair.second.v4l2Formats.multi == format;
- });
- if (info == pixelFormatInfo.end())
+ PixelFormat pixelFormat = format.toPixelFormat(false);
+ if (!pixelFormat.isValid())
return pixelFormatInfoInvalid;
- return info->second;
+ const auto iter = pixelFormatInfo.find(pixelFormat);
+ if (iter == pixelFormatInfo.end())
+ return pixelFormatInfoInvalid;
+
+ return iter->second;
}
/**
@@ -1049,7 +997,7 @@ unsigned int PixelFormatInfo::stride(unsigned int width, unsigned int plane,
return 0;
}
- if (plane > planes.size() || !planes[plane].bytesPerGroup) {
+ if (plane >= planes.size() || !planes[plane].bytesPerGroup) {
LOG(Formats, Warning) << "Invalid plane index, stride is zero";
return 0;
}
diff --git a/src/libcamera/formats.yaml b/src/libcamera/formats.yaml
index 7dda0132..d8a37992 100644
--- a/src/libcamera/formats.yaml
+++ b/src/libcamera/formats.yaml
@@ -2,7 +2,7 @@
#
# Copyright (C) 2020, Google Inc.
#
-%YAML 1.2
+%YAML 1.1
---
formats:
- R8:
@@ -11,6 +11,8 @@ formats:
fourcc: DRM_FORMAT_R10
- R12:
fourcc: DRM_FORMAT_R12
+ - R16:
+ fourcc: DRM_FORMAT_R16
- RGB565:
fourcc: DRM_FORMAT_RGB565
@@ -49,6 +51,10 @@ formats:
fourcc: DRM_FORMAT_UYVY
- VYUY:
fourcc: DRM_FORMAT_VYUY
+ - AVUY8888:
+ fourcc: DRM_FORMAT_AVUY8888
+ - XVUY8888:
+ fourcc: DRM_FORMAT_XVUY8888
- NV12:
fourcc: DRM_FORMAT_NV12
@@ -106,6 +112,15 @@ formats:
- SBGGR12:
fourcc: DRM_FORMAT_SBGGR12
+ - SRGGB14:
+ fourcc: DRM_FORMAT_SRGGB14
+ - SGRBG14:
+ fourcc: DRM_FORMAT_SGRBG14
+ - SGBRG14:
+ fourcc: DRM_FORMAT_SGBRG14
+ - SBGGR14:
+ fourcc: DRM_FORMAT_SBGGR14
+
- SRGGB16:
fourcc: DRM_FORMAT_SRGGB16
- SGRBG16:
@@ -145,6 +160,19 @@ formats:
fourcc: DRM_FORMAT_SBGGR12
mod: MIPI_FORMAT_MOD_CSI2_PACKED
+ - SRGGB14_CSI2P:
+ fourcc: DRM_FORMAT_SRGGB14
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
+ - SGRBG14_CSI2P:
+ fourcc: DRM_FORMAT_SGRBG14
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
+ - SGBRG14_CSI2P:
+ fourcc: DRM_FORMAT_SGBRG14
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
+ - SBGGR14_CSI2P:
+ fourcc: DRM_FORMAT_SBGGR14
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
+
- SRGGB10_IPU3:
fourcc: DRM_FORMAT_SRGGB10
mod: IPU3_FORMAT_MOD_PACKED
diff --git a/src/libcamera/framebuffer.cpp b/src/libcamera/framebuffer.cpp
index 7be18560..5a7f3c0b 100644
--- a/src/libcamera/framebuffer.cpp
+++ b/src/libcamera/framebuffer.cpp
@@ -114,9 +114,16 @@ LOG_DEFINE_CATEGORY(Buffer)
* pipeline handlers.
*/
-FrameBuffer::Private::Private()
- : request_(nullptr), isContiguous_(true)
+/**
+ * \brief Construct a FrameBuffer::Private instance
+ * \param[in] planes The frame memory planes
+ * \param[in] cookie Cookie
+ */
+FrameBuffer::Private::Private(const std::vector<Plane> &planes, uint64_t cookie)
+ : planes_(planes), cookie_(cookie), request_(nullptr),
+ isContiguous_(true)
{
+ metadata_.planes_.resize(planes_.size());
}
/**
@@ -195,6 +202,12 @@ FrameBuffer::Private::~Private()
*/
/**
+ * \fn FrameBuffer::Private::metadata()
+ * \brief Retrieve the dynamic metadata
+ * \return Dynamic metadata for the frame contained in the buffer
+ */
+
+/**
* \class FrameBuffer
* \brief Frame buffer data and its associated dynamic metadata
*
@@ -291,29 +304,22 @@ ino_t fileDescriptorInode(const SharedFD &fd)
* \param[in] cookie Cookie
*/
FrameBuffer::FrameBuffer(const std::vector<Plane> &planes, unsigned int cookie)
- : FrameBuffer(std::make_unique<Private>(), planes, cookie)
+ : FrameBuffer(std::make_unique<Private>(planes, cookie))
{
}
/**
- * \brief Construct a FrameBuffer with an extensible private class and an array
- * of planes
+ * \brief Construct a FrameBuffer with an extensible private class
* \param[in] d The extensible private class
- * \param[in] planes The frame memory planes
- * \param[in] cookie Cookie
*/
-FrameBuffer::FrameBuffer(std::unique_ptr<Private> d,
- const std::vector<Plane> &planes,
- unsigned int cookie)
- : Extensible(std::move(d)), planes_(planes), cookie_(cookie)
+FrameBuffer::FrameBuffer(std::unique_ptr<Private> d)
+ : Extensible(std::move(d))
{
- metadata_.planes_.resize(planes_.size());
-
unsigned int offset = 0;
bool isContiguous = true;
ino_t inode = 0;
- for (const auto &plane : planes_) {
+ for (const auto &plane : _d()->planes_) {
ASSERT(plane.offset != Plane::kInvalidOffset);
if (plane.offset != offset) {
@@ -325,9 +331,9 @@ FrameBuffer::FrameBuffer(std::unique_ptr<Private> d,
* Two different dmabuf file descriptors may still refer to the
* same dmabuf instance. Check this using inodes.
*/
- if (plane.fd != planes_[0].fd) {
+ if (plane.fd != _d()->planes_[0].fd) {
if (!inode)
- inode = fileDescriptorInode(planes_[0].fd);
+ inode = fileDescriptorInode(_d()->planes_[0].fd);
if (fileDescriptorInode(plane.fd) != inode) {
isContiguous = false;
break;
@@ -344,10 +350,13 @@ FrameBuffer::FrameBuffer(std::unique_ptr<Private> d,
}
/**
- * \fn FrameBuffer::planes()
* \brief Retrieve the static plane descriptors
* \return Array of plane descriptors
*/
+const std::vector<FrameBuffer::Plane> &FrameBuffer::planes() const
+{
+ return _d()->planes_;
+}
/**
* \brief Retrieve the request this buffer belongs to
@@ -368,13 +377,15 @@ Request *FrameBuffer::request() const
}
/**
- * \fn FrameBuffer::metadata()
* \brief Retrieve the dynamic metadata
* \return Dynamic metadata for the frame contained in the buffer
*/
+const FrameMetadata &FrameBuffer::metadata() const
+{
+ return _d()->metadata_;
+}
/**
- * \fn FrameBuffer::cookie()
* \brief Retrieve the cookie
*
* The cookie belongs to the creator of the FrameBuffer, which controls its
@@ -384,9 +395,12 @@ Request *FrameBuffer::request() const
*
* \return The cookie
*/
+uint64_t FrameBuffer::cookie() const
+{
+ return _d()->cookie_;
+}
/**
- * \fn FrameBuffer::setCookie()
* \brief Set the cookie
* \param[in] cookie Cookie to set
*
@@ -395,6 +409,10 @@ Request *FrameBuffer::request() const
* modify the cookie value of buffers they haven't created themselves. The
* libcamera core never modifies the buffer cookie.
*/
+void FrameBuffer::setCookie(uint64_t cookie)
+{
+ _d()->cookie_ = cookie;
+}
/**
* \brief Extract the Fence associated with this Framebuffer
diff --git a/src/libcamera/framebuffer_allocator.cpp b/src/libcamera/framebuffer_allocator.cpp
index 4df27cac..dbd0db19 100644
--- a/src/libcamera/framebuffer_allocator.cpp
+++ b/src/libcamera/framebuffer_allocator.cpp
@@ -59,14 +59,11 @@ LOG_DEFINE_CATEGORY(Allocator)
* \param[in] camera The camera
*/
FrameBufferAllocator::FrameBufferAllocator(std::shared_ptr<Camera> camera)
- : camera_(camera)
+ : camera_(std::move(camera))
{
}
-FrameBufferAllocator::~FrameBufferAllocator()
-{
- buffers_.clear();
-}
+FrameBufferAllocator::~FrameBufferAllocator() = default;
/**
* \brief Allocate buffers for a configured stream
@@ -88,16 +85,22 @@ FrameBufferAllocator::~FrameBufferAllocator()
*/
int FrameBufferAllocator::allocate(Stream *stream)
{
- if (buffers_.count(stream)) {
+ const auto &[it, inserted] = buffers_.try_emplace(stream);
+
+ if (!inserted) {
LOG(Allocator, Error) << "Buffers already allocated for stream";
return -EBUSY;
}
- int ret = camera_->exportFrameBuffers(stream, &buffers_[stream]);
+ int ret = camera_->exportFrameBuffers(stream, &it->second);
if (ret == -EINVAL)
LOG(Allocator, Error)
<< "Stream is not part of " << camera_->id()
<< " active configuration";
+
+ if (ret < 0)
+ buffers_.erase(it);
+
return ret;
}
@@ -119,8 +122,6 @@ int FrameBufferAllocator::free(Stream *stream)
if (iter == buffers_.end())
return -EINVAL;
- std::vector<std::unique_ptr<FrameBuffer>> &buffers = iter->second;
- buffers.clear();
buffers_.erase(iter);
return 0;
diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp
index e50b46c5..8d85b758 100644
--- a/src/libcamera/geometry.cpp
+++ b/src/libcamera/geometry.cpp
@@ -95,10 +95,10 @@ std::ostream &operator<<(std::ostream &out, const Point &p)
}
/**
- * \struct Size
+ * \class Size
* \brief Describe a two-dimensional size
*
- * The Size structure defines a two-dimensional size with integer precision.
+ * The Size class defines a two-dimensional size with integer precision.
*/
/**
@@ -455,7 +455,7 @@ std::ostream &operator<<(std::ostream &out, const Size &s)
}
/**
- * \struct SizeRange
+ * \class SizeRange
* \brief Describe a range of sizes
*
* A SizeRange describes a range of sizes included in the [min, max] interval
@@ -589,7 +589,7 @@ std::ostream &operator<<(std::ostream &out, const SizeRange &sr)
}
/**
- * \struct Rectangle
+ * \class Rectangle
* \brief Describe a rectangle's position and dimensions
*
* Rectangles are used to identify an area of an image. They are specified by
diff --git a/src/libcamera/ipa/meson.build b/src/libcamera/ipa/meson.build
index 44695240..ef73b3f9 100644
--- a/src/libcamera/ipa/meson.build
+++ b/src/libcamera/ipa/meson.build
@@ -3,13 +3,10 @@
libcamera_ipa_interfaces = []
foreach file : ipa_mojom_files
- name = '@0@'.format(file).split('/')[-1].split('.')[0]
-
# {pipeline}_ipa_interface.cpp
libcamera_ipa_interfaces += \
- custom_target(name + '_ipa_interface_cpp',
- input : file,
- output : name + '_ipa_interface.cpp',
+ custom_target(input : file,
+ output : '@BASENAME@_ipa_interface.cpp',
command : [
mojom_docs_extractor,
'-o', '@OUTPUT@', '@INPUT@'
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index ec966045..7a4515d9 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -109,6 +109,11 @@ IPAManager::IPAManager()
LOG(IPAManager, Fatal)
<< "Multiple IPAManager objects are not allowed";
+#if HAVE_IPA_PUBKEY
+ if (!pubKey_.isValid())
+ LOG(IPAManager, Warning) << "Public key not valid";
+#endif
+
unsigned int ipaCount = 0;
/* User-specified paths take precedence. */
@@ -133,7 +138,7 @@ IPAManager::IPAManager()
std::string root = utils::libcameraBuildPath();
if (!root.empty()) {
std::string ipaBuildPath = root + "src/ipa";
- constexpr int maxDepth = 1;
+ constexpr int maxDepth = 2;
LOG(IPAManager, Info)
<< "libcamera is not installed. Adding '"
@@ -274,6 +279,19 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
* found or if the IPA proxy fails to initialize
*/
+#if HAVE_IPA_PUBKEY
+/**
+ * \fn IPAManager::pubKey()
+ * \brief Retrieve the IPA module signing public key
+ *
+ * IPA module signature verification is normally handled internally by the
+ * IPAManager class. This function is meant to be used by utilities that need to
+ * verify signatures externally.
+ *
+ * \return The IPA module signing public key
+ */
+#endif
+
bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
{
#if HAVE_IPA_PUBKEY
diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp
index c9ff7de3..f2dd87e5 100644
--- a/src/libcamera/ipa_module.cpp
+++ b/src/libcamera/ipa_module.cpp
@@ -225,9 +225,9 @@ Span<const uint8_t> elfLoadSymbol(Span<const uint8_t> elf, const char *symbol)
* \brief The name of the IPA module
*
* The name may be used to build file system paths to IPA-specific resources.
- * It shall only contain printable characters, and may not contain '/', '*',
- * '?' or '\'. For IPA modules included in libcamera, it shall match the
- * directory of the IPA module in the source tree.
+ * It shall only contain printable characters, and may not contain '*', '?' or
+ * '\'. For IPA modules included in libcamera, it shall match the directory of
+ * the IPA module in the source tree.
*
* \todo Allow user to choose to isolate open source IPAs
*/
@@ -288,25 +288,30 @@ int IPAModule::loadIPAModuleInfo()
}
Span<const uint8_t> info = elfLoadSymbol(data, "ipaModuleInfo");
- if (info.size() != sizeof(info_)) {
+ if (info.size() < sizeof(info_)) {
LOG(IPAModule, Error) << "IPA module has no valid info";
return -EINVAL;
}
- memcpy(&info_, info.data(), info.size());
+ memcpy(&info_, info.data(), sizeof(info_));
if (info_.moduleAPIVersion != IPA_MODULE_API_VERSION) {
LOG(IPAModule, Error) << "IPA module API version mismatch";
return -EINVAL;
}
- /* Validate the IPA module name. */
+ /*
+ * Validate the IPA module name.
+ *
+ * \todo Consider module naming restrictions to avoid escaping from a
+ * base directory. Forbidding ".." may be enough, but this may be best
+ * implemented in a different layer.
+ */
std::string ipaName = info_.name;
auto iter = std::find_if_not(ipaName.begin(), ipaName.end(),
[](unsigned char c) -> bool {
- return isprint(c) && c != '/' &&
- c != '?' && c != '*' &&
- c != '\\';
+ return isprint(c) && c != '?' &&
+ c != '*' && c != '\\';
});
if (iter != ipaName.end()) {
LOG(IPAModule, Error)
diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp
index 7c94da9e..2949816b 100644
--- a/src/libcamera/media_device.cpp
+++ b/src/libcamera/media_device.cpp
@@ -352,8 +352,9 @@ MediaEntity *MediaDevice::getEntityByName(const std::string &name) const
* entity with name \a sourceName, to the pad at index \a sinkIdx of the
* sink entity with name \a sinkName, if any.
*
- * \sa MediaDevice::link(const MediaEntity *source, unsigned int sourceIdx, const MediaEntity *sink, unsigned int sinkIdx) const
- * \sa MediaDevice::link(const MediaPad *source, const MediaPad *sink) const
+ * \sa link(const MediaEntity *source, unsigned int sourceIdx,
+ * const MediaEntity *sink, unsigned int sinkIdx)
+ * \sa link(const MediaPad *source, const MediaPad *sink)
*
* \return The link that connects the two pads, or nullptr if no such a link
* exists
@@ -381,8 +382,9 @@ MediaLink *MediaDevice::link(const std::string &sourceName, unsigned int sourceI
* entity \a source, to the pad at index \a sinkIdx of the sink entity \a
* sink, if any.
*
- * \sa MediaDevice::link(const std::string &sourceName, unsigned int sourceIdx, const std::string &sinkName, unsigned int sinkIdx) const
- * \sa MediaDevice::link(const MediaPad *source, const MediaPad *sink) const
+ * \sa link(const std::string &sourceName, unsigned int sourceIdx,
+ * const std::string &sinkName, unsigned int sinkIdx)
+ * \sa link(const MediaPad *source, const MediaPad *sink)
*
* \return The link that connects the two pads, or nullptr if no such a link
* exists
@@ -404,8 +406,10 @@ MediaLink *MediaDevice::link(const MediaEntity *source, unsigned int sourceIdx,
* \param[in] source The source pad
* \param[in] sink The sink pad
*
- * \sa MediaDevice::link(const std::string &sourceName, unsigned int sourceIdx, const std::string &sinkName, unsigned int sinkIdx) const
- * \sa MediaDevice::link(const MediaEntity *source, unsigned int sourceIdx, const MediaEntity *sink, unsigned int sinkIdx) const
+ * \sa link(const std::string &sourceName, unsigned int sourceIdx,
+ * const std::string &sinkName, unsigned int sinkIdx)
+ * \sa link(const MediaEntity *source, unsigned int sourceIdx,
+ * const MediaEntity *sink, unsigned int sinkIdx)
*
* \return The link that connects the two pads, or nullptr if no such a link
* exists
@@ -473,7 +477,7 @@ int MediaDevice::open()
return -EBUSY;
}
- fd_ = UniqueFD(::open(deviceNode_.c_str(), O_RDWR));
+ fd_ = UniqueFD(::open(deviceNode_.c_str(), O_RDWR | O_CLOEXEC));
if (!fd_.isValid()) {
int ret = -errno;
LOG(MediaDevice, Error)
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index b57bee7e..a3b12bc1 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -7,15 +7,15 @@ libcamera_sources = files([
'camera_controls.cpp',
'camera_lens.cpp',
'camera_manager.cpp',
- 'camera_sensor.cpp',
- 'camera_sensor_properties.cpp',
'color_space.cpp',
'controls.cpp',
'control_serializer.cpp',
'control_validator.cpp',
+ 'converter.cpp',
'delayed_controls.cpp',
'device_enumerator.cpp',
'device_enumerator_sysfs.cpp',
+ 'dma_heaps.cpp',
'fence.cpp',
'formats.cpp',
'framebuffer.cpp',
@@ -33,11 +33,13 @@ libcamera_sources = files([
'mapped_framebuffer.cpp',
'media_device.cpp',
'media_object.cpp',
+ 'orientation.cpp',
'pipeline_handler.cpp',
'pixel_format.cpp',
'process.cpp',
'pub_key.cpp',
'request.cpp',
+ 'shared_mem_object.cpp',
'source_paths.cpp',
'stream.cpp',
'sysfs.cpp',
@@ -57,20 +59,46 @@ includes = [
libcamera_includes,
]
+libcamera_deps = []
+
libatomic = cc.find_library('atomic', required : false)
+libthreads = dependency('threads')
subdir('base')
+subdir('converter')
subdir('ipa')
subdir('pipeline')
subdir('proxy')
+subdir('sensor')
+subdir('software_isp')
+
+null_dep = dependency('', required : false)
-libdl = cc.find_library('dl')
-libgnutls = cc.find_library('gnutls', required : true)
-libudev = dependency('libudev', required : false)
+# TODO: Use dependency('dl') when updating to meson 0.62.0 or newer.
+libdl = null_dep
+if not cc.has_function('dlopen')
+ libdl = cc.find_library('dl')
+endif
+libudev = dependency('libudev', required : get_option('udev'))
libyaml = dependency('yaml-0.1', required : false)
-if libgnutls.found()
+# Use one of gnutls or libcrypto (provided by OpenSSL), trying gnutls first.
+libcrypto = dependency('gnutls', required : false)
+if libcrypto.found()
config_h.set('HAVE_GNUTLS', 1)
+else
+ libcrypto = dependency('libcrypto', required : false)
+ if libcrypto.found()
+ config_h.set('HAVE_CRYPTO', 1)
+ endif
+endif
+
+if not libcrypto.found()
+ warning('Neither gnutls nor libcrypto found, all IPA modules will be isolated')
+ summary({'IPA modules signed with': 'None (modules will run isolated)'},
+ section : 'Configuration')
+else
+ summary({'IPA modules signed with' : libcrypto.name()}, section : 'Configuration')
endif
if liblttng.found()
@@ -101,12 +129,27 @@ endif
control_sources = []
-foreach source : control_source_files
- input_files = files(source +'.yaml', source + '.cpp.in')
- control_sources += custom_target(source + '_cpp',
+controls_mode_files = {
+ 'controls' : controls_files,
+ 'properties' : properties_files,
+}
+
+foreach mode, input_files : controls_mode_files
+ input_files = files(input_files)
+
+ if mode == 'controls'
+ template_file = files('control_ids.cpp.in')
+ else
+ template_file = files('property_ids.cpp.in')
+ endif
+
+ ranges_file = files('control_ranges.yaml')
+ control_sources += custom_target(mode + '_cpp',
input : input_files,
- output : source + '.cpp',
- command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'])
+ output : mode + '_ids.cpp',
+ command : [gen_controls, '-o', '@OUTPUT@',
+ '--mode', mode, '-t', template_file,
+ '-r', ranges_file, '@INPUT@'])
endforeach
libcamera_sources += control_sources
@@ -131,12 +174,12 @@ if ipa_sign_module
libcamera_sources += ipa_pub_key_cpp
endif
-libcamera_deps = [
+libcamera_deps += [
libatomic,
libcamera_base,
libcamera_base_private,
+ libcrypto,
libdl,
- libgnutls,
liblttng,
libudev,
libyaml,
@@ -150,6 +193,7 @@ libcamera_deps = [
libcamera = shared_library('libcamera',
libcamera_sources,
version : libcamera_version,
+ soversion : libcamera_soversion,
name_prefix : '',
install : true,
include_directories : includes,
@@ -179,4 +223,6 @@ pkg_mod.generate(libcamera,
description : 'Complex Camera Support Library',
subdirs : 'libcamera')
+meson.override_dependency('libcamera', libcamera_public)
+
subdir('proxy/worker')
diff --git a/src/libcamera/orientation.cpp b/src/libcamera/orientation.cpp
new file mode 100644
index 00000000..965f5a8b
--- /dev/null
+++ b/src/libcamera/orientation.cpp
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Ideas On Board Oy
+ *
+ * orientation.cpp - Image orientation
+ */
+
+#include <libcamera/orientation.h>
+
+#include <array>
+#include <string>
+
+/**
+ * \file libcamera/orientation.h
+ * \brief Image orientation definition
+ */
+
+namespace libcamera {
+
+/**
+ * \enum Orientation
+ * \brief The image orientation in a memory buffer
+ *
+ * The Orientation enumeration describes the orientation of the images
+ * produced by the camera pipeline as they get received by the application
+ * inside memory buffers.
+ *
+ * The image orientation expressed using the Orientation enumeration can be then
+ * inferred by applying to a naturally oriented image a multiple of a 90 degrees
+ * rotation in the clockwise direction from the origin and then by applying an
+ * optional horizontal mirroring.
+ *
+ * The enumeration numerical values follow the ones defined by the EXIF
+ * Specification version 2.32, Tag 274 "Orientation", while the names of the
+ * enumerated values report the rotation and mirroring operations performed.
+ *
+ * For example, Orientation::Rotate90Mirror describes the orientation obtained
+ * by rotating the image 90 degrees clockwise first and then applying a
+ * horizontal mirroring.
+ *
+ * \var CameraConfiguration::Rotate0
+ * \image html rotation/rotate0.svg
+ * \var CameraConfiguration::Rotate0Mirror
+ * \image html rotation/rotate0Mirror.svg
+ * \var CameraConfiguration::Rotate180
+ * \image html rotation/rotate180.svg
+ * \var CameraConfiguration::Rotate180Mirror
+ * \image html rotation/rotate180Mirror.svg
+ * \var CameraConfiguration::Rotate90Mirror
+ * \image html rotation/rotate90Mirror.svg
+ * \var CameraConfiguration::Rotate270
+ * \image html rotation/rotate270.svg
+ * \var CameraConfiguration::Rotate270Mirror
+ * \image html rotation/rotate270Mirror.svg
+ * \var CameraConfiguration::Rotate90
+ * \image html rotation/rotate90.svg
+ */
+
+/**
+ * \brief Return the orientation representing a rotation of the given angle
+ * clockwise
+ * \param[in] angle The angle of rotation in a clockwise sense. Negative values
+ * can be used to represent anticlockwise rotations
+ * \param[out] success Set to `true` if the angle is a multiple of 90 degrees,
+ * otherwise `false`
+ * \return The orientation corresponding to the rotation if \a success was set
+ * to `true`, otherwise the `Rotate0` orientation
+ */
+Orientation orientationFromRotation(int angle, bool *success)
+{
+ angle = angle % 360;
+ if (angle < 0)
+ angle += 360;
+
+ if (success != nullptr)
+ *success = true;
+
+ switch (angle) {
+ case 0:
+ return Orientation::Rotate0;
+ case 90:
+ return Orientation::Rotate90;
+ case 180:
+ return Orientation::Rotate180;
+ case 270:
+ return Orientation::Rotate270;
+ }
+
+ if (success != nullptr)
+ *success = false;
+
+ return Orientation::Rotate0;
+}
+
+/**
+ * \brief Prints human-friendly names for Orientation items
+ * \param[in] out The output stream
+ * \param[in] orientation The Orientation item
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const Orientation &orientation)
+{
+ constexpr std::array<const char *, 9> orientationNames = {
+ "", /* Orientation starts counting from 1. */
+ "Rotate0", "Rotate0Mirror",
+ "Rotate180", "Rotate180Mirror",
+ "Rotate90Mirror", "Rotate270",
+ "Rotate270Mirror", "Rotate90",
+ };
+
+ out << orientationNames[static_cast<unsigned int>(orientation)];
+ return out;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
new file mode 100644
index 00000000..63082cea
--- /dev/null
+++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
@@ -0,0 +1,1117 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022 - Jacopo Mondi <jacopo@jmondi.org>
+ *
+ * imx8-isi.cpp - Pipeline handler for ISI interface found on NXP i.MX8 SoC
+ */
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/formats.h>
+#include <libcamera/geometry.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+#include "linux/media-bus-format.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(ISI)
+
+class PipelineHandlerISI;
+
+class ISICameraData : public Camera::Private
+{
+public:
+ ISICameraData(PipelineHandler *ph)
+ : Camera::Private(ph)
+ {
+ /*
+ * \todo Assume 2 channels only for now, as that's the number of
+ * available channels on i.MX8MP.
+ */
+ streams_.resize(2);
+ }
+
+ PipelineHandlerISI *pipe();
+
+ int init();
+
+ unsigned int pipeIndex(const Stream *stream)
+ {
+ return stream - &*streams_.begin();
+ }
+
+ unsigned int getRawMediaBusFormat(PixelFormat *pixelFormat) const;
+ unsigned int getYuvMediaBusFormat(const PixelFormat &pixelFormat) const;
+ unsigned int getMediaBusFormat(PixelFormat *pixelFormat) const;
+
+ std::unique_ptr<CameraSensor> sensor_;
+ std::unique_ptr<V4L2Subdevice> csis_;
+
+ std::vector<Stream> streams_;
+
+ std::vector<Stream *> enabledStreams_;
+
+ unsigned int xbarSink_;
+};
+
+class ISICameraConfiguration : public CameraConfiguration
+{
+public:
+ ISICameraConfiguration(ISICameraData *data)
+ : data_(data)
+ {
+ }
+
+ Status validate() override;
+
+ static const std::map<PixelFormat, unsigned int> formatsMap_;
+
+ V4L2SubdeviceFormat sensorFormat_;
+
+private:
+ CameraConfiguration::Status
+ validateRaw(std::set<Stream *> &availableStreams, const Size &maxResolution);
+ CameraConfiguration::Status
+ validateYuv(std::set<Stream *> &availableStreams, const Size &maxResolution);
+
+ const ISICameraData *data_;
+};
+
+class PipelineHandlerISI : public PipelineHandler
+{
+public:
+ PipelineHandlerISI(CameraManager *manager);
+
+ bool match(DeviceEnumerator *enumerator) override;
+
+ std::unique_ptr<CameraConfiguration>
+ generateConfiguration(Camera *camera, Span<const StreamRole> roles) override;
+ int configure(Camera *camera, CameraConfiguration *config) override;
+
+ int exportFrameBuffers(Camera *camera, Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
+
+ int start(Camera *camera, const ControlList *controls) override;
+
+protected:
+ void stopDevice(Camera *camera) override;
+
+ int queueRequestDevice(Camera *camera, Request *request) override;
+
+private:
+ static constexpr Size kPreviewSize = { 1920, 1080 };
+ static constexpr Size kMinISISize = { 1, 1 };
+
+ struct Pipe {
+ std::unique_ptr<V4L2Subdevice> isi;
+ std::unique_ptr<V4L2VideoDevice> capture;
+ };
+
+ ISICameraData *cameraData(Camera *camera)
+ {
+ return static_cast<ISICameraData *>(camera->_d());
+ }
+
+ Pipe *pipeFromStream(Camera *camera, const Stream *stream);
+
+ StreamConfiguration generateYUVConfiguration(Camera *camera,
+ const Size &size);
+ StreamConfiguration generateRawConfiguration(Camera *camera);
+
+ void bufferReady(FrameBuffer *buffer);
+
+ MediaDevice *isiDev_;
+
+ std::unique_ptr<V4L2Subdevice> crossbar_;
+ std::vector<Pipe> pipes_;
+};
+
+/* -----------------------------------------------------------------------------
+ * Camera Data
+ */
+
+PipelineHandlerISI *ISICameraData::pipe()
+{
+ return static_cast<PipelineHandlerISI *>(Camera::Private::pipe());
+}
+
+/* Open and initialize pipe components. */
+int ISICameraData::init()
+{
+ int ret = sensor_->init();
+ if (ret)
+ return ret;
+
+ ret = csis_->open();
+ if (ret)
+ return ret;
+
+ properties_ = sensor_->properties();
+
+ return 0;
+}
+
+/*
+ * Get a RAW Bayer media bus format compatible with the requested pixelFormat.
+ *
+ * If the requested pixelFormat cannot be produced by the sensor adjust it to
+ * the one corresponding to the media bus format with the largest bit-depth.
+ */
+unsigned int ISICameraData::getRawMediaBusFormat(PixelFormat *pixelFormat) const
+{
+ std::vector<unsigned int> mbusCodes = sensor_->mbusCodes();
+
+ static const std::map<PixelFormat, unsigned int> rawFormats = {
+ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
+ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
+ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
+ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
+ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
+ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
+ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
+ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
+ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
+ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
+ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
+ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
+ { formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 },
+ { formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 },
+ { formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 },
+ { formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 },
+ };
+
+ /*
+ * Make sure the requested PixelFormat is supported in the above
+ * map and the sensor can produce the compatible mbus code.
+ */
+ auto it = rawFormats.find(*pixelFormat);
+ if (it != rawFormats.end() &&
+ std::count(mbusCodes.begin(), mbusCodes.end(), it->second))
+ return it->second;
+
+ if (it == rawFormats.end())
+ LOG(ISI, Warning) << pixelFormat
+ << " not supported in ISI formats map.";
+
+ /*
+ * The desired pixel format cannot be produced. Adjust it to the one
+ * corresponding to the raw media bus format with the largest bit-depth
+ * the sensor provides.
+ */
+ unsigned int sensorCode = 0;
+ unsigned int maxDepth = 0;
+ *pixelFormat = {};
+
+ for (unsigned int code : mbusCodes) {
+ /* Make sure the media bus format is RAW Bayer. */
+ const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(code);
+ if (!bayerFormat.isValid())
+ continue;
+
+ /* Make sure the media format is supported. */
+ it = std::find_if(rawFormats.begin(), rawFormats.end(),
+ [code](auto &rawFormat) {
+ return rawFormat.second == code;
+ });
+
+ if (it == rawFormats.end()) {
+ LOG(ISI, Warning) << bayerFormat
+ << " not supported in ISI formats map.";
+ continue;
+ }
+
+ /* Pick the one with the largest bit depth. */
+ if (bayerFormat.bitDepth > maxDepth) {
+ maxDepth = bayerFormat.bitDepth;
+ *pixelFormat = it->first;
+ sensorCode = code;
+ }
+ }
+
+ if (!pixelFormat->isValid())
+ LOG(ISI, Error) << "Cannot find a supported RAW format";
+
+ return sensorCode;
+}
+
+/*
+ * Get a YUV/RGB media bus format from which the ISI can produce a processed
+ * stream, preferring codes with the same colour encoding as the requested
+ * pixelformat.
+ *
+ * If the sensor does not provide any YUV/RGB media bus format the ISI cannot
+ * generate any processed pixel format as it cannot debayer.
+ */
+unsigned int ISICameraData::getYuvMediaBusFormat(const PixelFormat &pixelFormat) const
+{
+ std::vector<unsigned int> mbusCodes = sensor_->mbusCodes();
+
+ /*
+ * The ISI can produce YUV/RGB pixel formats from any non-RAW Bayer
+ * media bus formats.
+ *
+ * Keep the list in sync with the mxc_isi_bus_formats[] array in
+ * the ISI driver.
+ */
+ std::vector<unsigned int> yuvCodes = {
+ MEDIA_BUS_FMT_UYVY8_1X16,
+ MEDIA_BUS_FMT_YUV8_1X24,
+ MEDIA_BUS_FMT_RGB565_1X16,
+ MEDIA_BUS_FMT_RGB888_1X24,
+ };
+
+ std::sort(mbusCodes.begin(), mbusCodes.end());
+ std::sort(yuvCodes.begin(), yuvCodes.end());
+
+ std::vector<unsigned int> supportedCodes;
+ std::set_intersection(mbusCodes.begin(), mbusCodes.end(),
+ yuvCodes.begin(), yuvCodes.end(),
+ std::back_inserter(supportedCodes));
+
+ if (supportedCodes.empty()) {
+ LOG(ISI, Warning) << "Cannot find a supported YUV/RGB format";
+
+ return 0;
+ }
+
+ /* Prefer codes with the same encoding as the requested pixel format. */
+ const PixelFormatInfo &info = PixelFormatInfo::info(pixelFormat);
+ for (unsigned int code : supportedCodes) {
+ if (info.colourEncoding == PixelFormatInfo::ColourEncodingYUV &&
+ (code == MEDIA_BUS_FMT_UYVY8_1X16 ||
+ code == MEDIA_BUS_FMT_YUV8_1X24))
+ return code;
+
+ if (info.colourEncoding == PixelFormatInfo::ColourEncodingRGB &&
+ (code == MEDIA_BUS_FMT_RGB565_1X16 ||
+ code == MEDIA_BUS_FMT_RGB888_1X24))
+ return code;
+ }
+
+ /* Otherwise return the first found code. */
+ return supportedCodes[0];
+}
+
+unsigned int ISICameraData::getMediaBusFormat(PixelFormat *pixelFormat) const
+{
+ if (PixelFormatInfo::info(*pixelFormat).colourEncoding ==
+ PixelFormatInfo::ColourEncodingRAW)
+ return getRawMediaBusFormat(pixelFormat);
+
+ return getYuvMediaBusFormat(*pixelFormat);
+}
+
+/* -----------------------------------------------------------------------------
+ * Camera Configuration
+ */
+
+/*
+ * ISICameraConfiguration::formatsMap_ records the association between an output
+ * pixel format and the ISI source pixel format to be applied to the pipeline.
+ */
+const std::map<PixelFormat, unsigned int> ISICameraConfiguration::formatsMap_ = {
+ { formats::YUYV, MEDIA_BUS_FMT_YUV8_1X24 },
+ { formats::AVUY8888, MEDIA_BUS_FMT_YUV8_1X24 },
+ { formats::NV12, MEDIA_BUS_FMT_YUV8_1X24 },
+ { formats::NV16, MEDIA_BUS_FMT_YUV8_1X24 },
+ { formats::YUV444, MEDIA_BUS_FMT_YUV8_1X24 },
+ { formats::RGB565, MEDIA_BUS_FMT_RGB888_1X24 },
+ { formats::BGR888, MEDIA_BUS_FMT_RGB888_1X24 },
+ { formats::RGB888, MEDIA_BUS_FMT_RGB888_1X24 },
+ { formats::XRGB8888, MEDIA_BUS_FMT_RGB888_1X24 },
+ { formats::ABGR8888, MEDIA_BUS_FMT_RGB888_1X24 },
+ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
+ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
+ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
+ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
+ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
+ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
+ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
+ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
+ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
+ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
+ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
+ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
+};
+
+/*
+ * Adjust stream configuration when the first requested stream is RAW: all the
+ * streams will have the same RAW pixelformat and size.
+ */
+CameraConfiguration::Status
+ISICameraConfiguration::validateRaw(std::set<Stream *> &availableStreams,
+ const Size &maxResolution)
+{
+ CameraConfiguration::Status status = Valid;
+
+ /*
+ * Make sure the requested RAW format is supported by the
+ * pipeline, otherwise adjust it.
+ */
+ std::vector<unsigned int> mbusCodes = data_->sensor_->mbusCodes();
+ StreamConfiguration &rawConfig = config_[0];
+ PixelFormat rawFormat = rawConfig.pixelFormat;
+
+ unsigned int sensorCode = data_->getRawMediaBusFormat(&rawFormat);
+ if (!sensorCode) {
+ LOG(ISI, Error) << "Cannot adjust RAW pixelformat "
+ << rawConfig.pixelFormat;
+ return Invalid;
+ }
+
+ if (rawFormat != rawConfig.pixelFormat) {
+ LOG(ISI, Debug) << "RAW pixelformat adjusted to "
+ << rawFormat;
+ rawConfig.pixelFormat = rawFormat;
+ status = Adjusted;
+ }
+
+ /* Cap the RAW stream size to the maximum resolution. */
+ const Size configSize = rawConfig.size;
+ rawConfig.size.boundTo(maxResolution);
+ if (rawConfig.size != configSize) {
+ LOG(ISI, Debug) << "RAW size adjusted to "
+ << rawConfig.size;
+ status = Adjusted;
+ }
+
+ /* Adjust all other streams to RAW. */
+ for (const auto &[i, cfg] : utils::enumerate(config_)) {
+
+ LOG(ISI, Debug) << "Stream " << i << ": " << cfg.toString();
+ const PixelFormat pixFmt = cfg.pixelFormat;
+ const Size size = cfg.size;
+
+ cfg.pixelFormat = rawConfig.pixelFormat;
+ cfg.size = rawConfig.size;
+
+ if (cfg.pixelFormat != pixFmt || cfg.size != size) {
+ LOG(ISI, Debug) << "Stream " << i << " adjusted to "
+ << cfg.toString();
+ status = Adjusted;
+ }
+
+ const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
+ cfg.stride = info.stride(cfg.size.width, 0);
+ cfg.frameSize = info.frameSize(cfg.size, info.bitsPerPixel);
+
+ /* Assign streams in the order they are presented. */
+ auto stream = availableStreams.extract(availableStreams.begin());
+ cfg.setStream(stream.value());
+ }
+
+ return status;
+}
+
+/*
+ * Adjust stream configuration when the first requested stream is not RAW: all
+ * the streams will be either YUV or RGB processed formats.
+ */
+CameraConfiguration::Status
+ISICameraConfiguration::validateYuv(std::set<Stream *> &availableStreams,
+ const Size &maxResolution)
+{
+ CameraConfiguration::Status status = Valid;
+
+ StreamConfiguration &yuvConfig = config_[0];
+ PixelFormat yuvPixelFormat = yuvConfig.pixelFormat;
+
+ /*
+ * Make sure the sensor can produce a compatible YUV/RGB media bus
+ * format. If the sensor can only produce RAW Bayer we can only fail
+ * here as we can't adjust to anything but RAW.
+ */
+ unsigned int yuvMediaBusCode = data_->getYuvMediaBusFormat(yuvPixelFormat);
+ if (!yuvMediaBusCode) {
+ LOG(ISI, Error) << "Cannot adjust pixelformat "
+ << yuvConfig.pixelFormat;
+ return Invalid;
+ }
+
+ /* Adjust all the other streams. */
+ for (const auto &[i, cfg] : utils::enumerate(config_)) {
+
+ LOG(ISI, Debug) << "Stream " << i << ": " << cfg.toString();
+
+ /* If the stream is RAW or not supported default it to YUYV. */
+ const PixelFormatInfo &cfgInfo = PixelFormatInfo::info(cfg.pixelFormat);
+ if (cfgInfo.colourEncoding == PixelFormatInfo::ColourEncodingRAW ||
+ !formatsMap_.count(cfg.pixelFormat)) {
+
+ LOG(ISI, Debug) << "Stream " << i << " format: "
+ << cfg.pixelFormat << " adjusted to YUYV";
+
+ cfg.pixelFormat = formats::YUYV;
+ status = Adjusted;
+ }
+
+ /* Cap the streams size to the maximum accepted resolution. */
+ Size configSize = cfg.size;
+ cfg.size.boundTo(maxResolution);
+ if (cfg.size != configSize) {
+ LOG(ISI, Debug)
+ << "Stream " << i << " adjusted to " << cfg.size;
+ status = Adjusted;
+ }
+
+ /* Re-fetch the pixel format info in case it has been adjusted. */
+ const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
+
+ /* \todo Multiplane ? */
+ cfg.stride = info.stride(cfg.size.width, 0);
+ cfg.frameSize = info.frameSize(cfg.size, info.bitsPerPixel);
+
+ /* Assign streams in the order they are presented. */
+ auto stream = availableStreams.extract(availableStreams.begin());
+ cfg.setStream(stream.value());
+ }
+
+ return status;
+}
+
+CameraConfiguration::Status ISICameraConfiguration::validate()
+{
+ Status status = Valid;
+
+ std::set<Stream *> availableStreams;
+ std::transform(data_->streams_.begin(), data_->streams_.end(),
+ std::inserter(availableStreams, availableStreams.end()),
+ [](const Stream &s) { return const_cast<Stream *>(&s); });
+
+ if (config_.empty())
+ return Invalid;
+
+ /* Cap the number of streams to the number of available ISI pipes. */
+ if (config_.size() > availableStreams.size()) {
+ config_.resize(availableStreams.size());
+ status = Adjusted;
+ }
+
+ /*
+ * If more than a single stream is requested, the maximum allowed input
+ * image width is 2048. Cap the maximum image size accordingly.
+ *
+ * \todo The (size > 1) check only applies to i.MX8MP which has 2 ISI
+ * channels. SoCs with more channels than the i.MX8MP are capable of
+ * supporting more streams with input width > 2048 by chaining
+ * successive channels together. Define a policy for channels allocation
+ * to fully support other SoCs.
+ */
+ CameraSensor *sensor = data_->sensor_.get();
+ Size maxResolution = sensor->resolution();
+ if (config_.size() > 1)
+ maxResolution.width = std::min(2048U, maxResolution.width);
+
+ /* Validate streams according to the format of the first one. */
+ const PixelFormatInfo info = PixelFormatInfo::info(config_[0].pixelFormat);
+
+ Status validationStatus;
+ if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)
+ validationStatus = validateRaw(availableStreams, maxResolution);
+ else
+ validationStatus = validateYuv(availableStreams, maxResolution);
+
+ if (validationStatus == Invalid)
+ return Invalid;
+
+ if (validationStatus == Adjusted)
+ status = Adjusted;
+
+ /*
+ * Sensor format selection policy: the first stream selects the media
+ * bus code to use, the largest stream selects the size.
+ *
+ * \todo The sensor format selection policy could be changed to
+ * prefer operating the sensor at full resolution to prioritize
+ * image quality in exchange of a usually slower frame rate.
+ * Usage of the STILL_CAPTURE role could be consider for this.
+ */
+ Size maxSize;
+ for (const auto &cfg : config_) {
+ if (cfg.size > maxSize)
+ maxSize = cfg.size;
+ }
+
+ PixelFormat pixelFormat = config_[0].pixelFormat;
+
+ V4L2SubdeviceFormat sensorFormat{};
+ sensorFormat.code = data_->getMediaBusFormat(&pixelFormat);
+ sensorFormat.size = maxSize;
+
+ LOG(ISI, Debug) << "Computed sensor configuration: " << sensorFormat;
+
+ /*
+ * We can't use CameraSensor::getFormat() as it might return a
+ * format larger than our strict width limit, as that function
+ * prioritizes formats with the same aspect ratio over formats with less
+ * difference in size.
+ *
+ * Manually walk all the sensor supported sizes searching for
+ * the smallest larger format without considering the aspect ratio
+ * as the ISI can freely scale.
+ */
+ auto sizes = sensor->sizes(sensorFormat.code);
+ Size bestSize;
+
+ for (const Size &s : sizes) {
+ /* Ignore smaller sizes. */
+ if (s.width < sensorFormat.size.width ||
+ s.height < sensorFormat.size.height)
+ continue;
+
+ /* Make sure the width stays in the limits. */
+ if (s.width > maxResolution.width)
+ continue;
+
+ bestSize = s;
+ break;
+ }
+
+ /*
+ * This should happen only if the sensor can only produce formats that
+ * exceed the maximum allowed input width.
+ */
+ if (bestSize.isNull()) {
+ LOG(ISI, Error) << "Unable to find a suitable sensor format";
+ return Invalid;
+ }
+
+ sensorFormat_.code = sensorFormat.code;
+ sensorFormat_.size = bestSize;
+
+ LOG(ISI, Debug) << "Selected sensor format: " << sensorFormat_;
+
+ return status;
+}
+
+/* -----------------------------------------------------------------------------
+ * Pipeline Handler
+ */
+
+PipelineHandlerISI::PipelineHandlerISI(CameraManager *manager)
+ : PipelineHandler(manager)
+{
+}
+
+/*
+ * Generate a StreamConfiguration for YUV/RGB use case.
+ *
+ * Verify it the sensor can produce a YUV/RGB media bus format and collect
+ * all the processed pixel formats the ISI can generate as supported stream
+ * configurations.
+ */
+StreamConfiguration PipelineHandlerISI::generateYUVConfiguration(Camera *camera,
+ const Size &size)
+{
+ ISICameraData *data = cameraData(camera);
+ PixelFormat pixelFormat = formats::YUYV;
+ unsigned int mbusCode;
+
+ mbusCode = data->getYuvMediaBusFormat(pixelFormat);
+ if (!mbusCode)
+ return {};
+
+ /* Adjust the requested size to the sensor's capabilities. */
+ V4L2SubdeviceFormat sensorFmt;
+ sensorFmt.code = mbusCode;
+ sensorFmt.size = size;
+
+ int ret = data->sensor_->tryFormat(&sensorFmt);
+ if (ret) {
+ LOG(ISI, Error) << "Failed to try sensor format.";
+ return {};
+ }
+
+ Size sensorSize = sensorFmt.size;
+
+ /*
+ * Populate the StreamConfiguration.
+ *
+ * As the sensor supports at least one YUV/RGB media bus format all the
+ * processed ones in formatsMap_ can be generated from it.
+ */
+ std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
+
+ for (const auto &[pixFmt, pipeFmt] : ISICameraConfiguration::formatsMap_) {
+ const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
+ if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)
+ continue;
+
+ streamFormats[pixFmt] = { { kMinISISize, sensorSize } };
+ }
+
+ StreamFormats formats(streamFormats);
+
+ StreamConfiguration cfg(formats);
+ cfg.pixelFormat = pixelFormat;
+ cfg.size = sensorSize;
+ cfg.bufferCount = 4;
+
+ return cfg;
+}
+
+/*
+ * Generate a StreamConfiguration for Raw Bayer use case. Verify if the sensor
+ * can produce the requested RAW bayer format and eventually adjust it to
+ * the one with the largest bit-depth the sensor can produce.
+ */
+StreamConfiguration PipelineHandlerISI::generateRawConfiguration(Camera *camera)
+{
+ static const std::map<unsigned int, PixelFormat> rawFormats = {
+ { MEDIA_BUS_FMT_SBGGR8_1X8, formats::SBGGR8 },
+ { MEDIA_BUS_FMT_SGBRG8_1X8, formats::SGBRG8 },
+ { MEDIA_BUS_FMT_SGRBG8_1X8, formats::SGRBG8 },
+ { MEDIA_BUS_FMT_SRGGB8_1X8, formats::SRGGB8 },
+ { MEDIA_BUS_FMT_SBGGR10_1X10, formats::SBGGR10 },
+ { MEDIA_BUS_FMT_SGBRG10_1X10, formats::SGBRG10 },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, formats::SGRBG10 },
+ { MEDIA_BUS_FMT_SRGGB10_1X10, formats::SRGGB10 },
+ { MEDIA_BUS_FMT_SBGGR12_1X12, formats::SBGGR12 },
+ { MEDIA_BUS_FMT_SGBRG12_1X12, formats::SGBRG12 },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, formats::SGRBG12 },
+ { MEDIA_BUS_FMT_SRGGB12_1X12, formats::SRGGB12 },
+ { MEDIA_BUS_FMT_SBGGR14_1X14, formats::SBGGR14 },
+ { MEDIA_BUS_FMT_SGBRG14_1X14, formats::SGBRG14 },
+ { MEDIA_BUS_FMT_SGRBG14_1X14, formats::SGRBG14 },
+ { MEDIA_BUS_FMT_SRGGB14_1X14, formats::SRGGB14 },
+ };
+
+ ISICameraData *data = cameraData(camera);
+ PixelFormat pixelFormat = formats::SBGGR10;
+ unsigned int mbusCode;
+
+ /* pixelFormat will be adjusted, if the sensor can produce RAW. */
+ mbusCode = data->getRawMediaBusFormat(&pixelFormat);
+ if (!mbusCode)
+ return {};
+
+ /*
+ * Populate the StreamConfiguration with all the supported Bayer
+ * formats the sensor can produce.
+ */
+ std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
+ const CameraSensor *sensor = data->sensor_.get();
+
+ for (unsigned int code : sensor->mbusCodes()) {
+ /* Find a Bayer media bus code from the sensor. */
+ const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(code);
+ if (!bayerFormat.isValid())
+ continue;
+
+ auto it = rawFormats.find(code);
+ if (it == rawFormats.end()) {
+ LOG(ISI, Warning) << bayerFormat
+ << " not supported in ISI formats map.";
+ continue;
+ }
+
+ streamFormats[it->second] = { { sensor->resolution(), sensor->resolution() } };
+ }
+
+ StreamFormats formats(streamFormats);
+
+ StreamConfiguration cfg(formats);
+ cfg.size = sensor->resolution();
+ cfg.pixelFormat = pixelFormat;
+ cfg.bufferCount = 4;
+
+ return cfg;
+}
+
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerISI::generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles)
+{
+ ISICameraData *data = cameraData(camera);
+ std::unique_ptr<ISICameraConfiguration> config =
+ std::make_unique<ISICameraConfiguration>(data);
+
+ if (roles.empty())
+ return config;
+
+ if (roles.size() > data->streams_.size()) {
+ LOG(ISI, Error) << "Only up to " << data->streams_.size()
+ << " streams are supported";
+ return nullptr;
+ }
+
+ for (const auto &role : roles) {
+ /*
+ * Prefer the following formats:
+ * - Still Capture: Full resolution YUYV
+ * - ViewFinder/VideoRecording: 1080p YUYV
+ * - RAW: Full resolution Bayer
+ */
+ StreamConfiguration cfg;
+
+ switch (role) {
+ case StreamRole::StillCapture:
+ case StreamRole::Viewfinder:
+ case StreamRole::VideoRecording: {
+ Size size = role == StreamRole::StillCapture
+ ? data->sensor_->resolution()
+ : PipelineHandlerISI::kPreviewSize;
+ cfg = generateYUVConfiguration(camera, size);
+ if (cfg.pixelFormat.isValid())
+ break;
+
+
+ /*
+ * Fallback to use a Bayer format if that's what the
+ * sensor supports.
+ */
+ [[fallthrough]];
+
+ }
+
+ case StreamRole::Raw: {
+ cfg = generateRawConfiguration(camera);
+ break;
+ }
+
+ default:
+ LOG(ISI, Error) << "Requested stream role not supported: " << role;
+ return nullptr;
+ }
+
+ if (!cfg.pixelFormat.isValid()) {
+ LOG(ISI, Error)
+ << "Cannot generate configuration for role: " << role;
+ return nullptr;
+ }
+
+ config->addConfiguration(cfg);
+ }
+
+ config->validate();
+
+ return config;
+}
+
+int PipelineHandlerISI::configure(Camera *camera, CameraConfiguration *c)
+{
+ ISICameraConfiguration *camConfig = static_cast<ISICameraConfiguration *>(c);
+ ISICameraData *data = cameraData(camera);
+
+ /* All links are immutable except the sensor -> csis link. */
+ const MediaPad *sensorSrc = data->sensor_->entity()->getPadByIndex(0);
+ sensorSrc->links()[0]->setEnabled(true);
+
+ /*
+ * Reset the crossbar switch routing and enable one route for each
+ * requested stream configuration.
+ *
+ * \todo Handle concurrent usage of multiple cameras by adjusting the
+ * routing table instead of resetting it.
+ */
+ V4L2Subdevice::Routing routing = {};
+ unsigned int xbarFirstSource = crossbar_->entity()->pads().size() / 2 + 1;
+
+ for (const auto &[idx, config] : utils::enumerate(*c)) {
+ uint32_t sourcePad = xbarFirstSource + idx;
+ routing.emplace_back(V4L2Subdevice::Stream{ data->xbarSink_, 0 },
+ V4L2Subdevice::Stream{ sourcePad, 0 },
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE);
+ }
+
+ int ret = crossbar_->setRouting(&routing, V4L2Subdevice::ActiveFormat);
+ if (ret)
+ return ret;
+
+ /* Apply format to the sensor and CSIS receiver. */
+ V4L2SubdeviceFormat format = camConfig->sensorFormat_;
+ ret = data->sensor_->setFormat(&format);
+ if (ret)
+ return ret;
+
+ ret = data->csis_->setFormat(0, &format);
+ if (ret)
+ return ret;
+
+ ret = crossbar_->setFormat(data->xbarSink_, &format);
+ if (ret)
+ return ret;
+
+ /* Now configure the ISI and video node instances, one per stream. */
+ data->enabledStreams_.clear();
+ for (const auto &config : *c) {
+ Pipe *pipe = pipeFromStream(camera, config.stream());
+
+ /*
+ * Set the format on the ISI sink pad: it must match what is
+ * received by the CSIS.
+ */
+ ret = pipe->isi->setFormat(0, &format);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure the ISI sink compose rectangle to downscale the
+ * image.
+ *
+ * \todo Additional cropping could be applied on the ISI source
+ * pad to further reduce the output image size.
+ */
+ Rectangle isiScale(config.size);
+ ret = pipe->isi->setSelection(0, V4L2_SEL_TGT_COMPOSE, &isiScale);
+ if (ret)
+ return ret;
+
+ /*
+ * Set the format on ISI source pad: only the media bus code
+ * is relevant as it configures format conversion, while the
+ * size is taken from the sink's COMPOSE (or source's CROP,
+ * if any) rectangles.
+ */
+ unsigned int isiCode = ISICameraConfiguration::formatsMap_.at(config.pixelFormat);
+
+ V4L2SubdeviceFormat isiFormat{};
+ isiFormat.code = isiCode;
+ isiFormat.size = config.size;
+
+ ret = pipe->isi->setFormat(1, &isiFormat);
+ if (ret)
+ return ret;
+
+ V4L2DeviceFormat captureFmt{};
+ captureFmt.fourcc = pipe->capture->toV4L2PixelFormat(config.pixelFormat);
+ captureFmt.size = config.size;
+
+ /* \todo Set stride and format. */
+ ret = pipe->capture->setFormat(&captureFmt);
+ if (ret)
+ return ret;
+
+ /* Store the list of enabled streams for later use. */
+ data->enabledStreams_.push_back(config.stream());
+ }
+
+ return 0;
+}
+
+int PipelineHandlerISI::exportFrameBuffers(Camera *camera, Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+ unsigned int count = stream->configuration().bufferCount;
+ Pipe *pipe = pipeFromStream(camera, stream);
+
+ return pipe->capture->exportBuffers(count, buffers);
+}
+
+int PipelineHandlerISI::start(Camera *camera,
+ [[maybe_unused]] const ControlList *controls)
+{
+ ISICameraData *data = cameraData(camera);
+
+ for (const auto &stream : data->enabledStreams_) {
+ Pipe *pipe = pipeFromStream(camera, stream);
+ const StreamConfiguration &config = stream->configuration();
+
+ int ret = pipe->capture->importBuffers(config.bufferCount);
+ if (ret)
+ return ret;
+
+ ret = pipe->capture->streamOn();
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void PipelineHandlerISI::stopDevice(Camera *camera)
+{
+ ISICameraData *data = cameraData(camera);
+
+ for (const auto &stream : data->enabledStreams_) {
+ Pipe *pipe = pipeFromStream(camera, stream);
+
+ pipe->capture->streamOff();
+ pipe->capture->releaseBuffers();
+ }
+}
+
+int PipelineHandlerISI::queueRequestDevice(Camera *camera, Request *request)
+{
+ for (auto &[stream, buffer] : request->buffers()) {
+ Pipe *pipe = pipeFromStream(camera, stream);
+
+ int ret = pipe->capture->queueBuffer(buffer);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+bool PipelineHandlerISI::match(DeviceEnumerator *enumerator)
+{
+ DeviceMatch dm("mxc-isi");
+ dm.add("crossbar");
+ dm.add("mxc_isi.0");
+ dm.add("mxc_isi.0.capture");
+
+ isiDev_ = acquireMediaDevice(enumerator, dm);
+ if (!isiDev_)
+ return false;
+
+ /*
+ * Acquire the subdevs and video nodes for the crossbar switch and the
+ * processing pipelines.
+ */
+ crossbar_ = V4L2Subdevice::fromEntityName(isiDev_, "crossbar");
+ if (!crossbar_)
+ return false;
+
+ int ret = crossbar_->open();
+ if (ret)
+ return false;
+
+ for (unsigned int i = 0; ; ++i) {
+ std::string entityName = "mxc_isi." + std::to_string(i);
+ std::unique_ptr<V4L2Subdevice> isi =
+ V4L2Subdevice::fromEntityName(isiDev_, entityName);
+ if (!isi)
+ break;
+
+ ret = isi->open();
+ if (ret)
+ return false;
+
+ entityName += ".capture";
+ std::unique_ptr<V4L2VideoDevice> capture =
+ V4L2VideoDevice::fromEntityName(isiDev_, entityName);
+ if (!capture)
+ return false;
+
+ capture->bufferReady.connect(this, &PipelineHandlerISI::bufferReady);
+
+ ret = capture->open();
+ if (ret)
+ return ret;
+
+ pipes_.push_back({ std::move(isi), std::move(capture) });
+ }
+
+ if (pipes_.empty()) {
+ LOG(ISI, Error) << "Unable to enumerate pipes";
+ return false;
+ }
+
+ /*
+ * Loop over all the crossbar switch sink pads to find connected CSI-2
+ * receivers and camera sensors.
+ */
+ unsigned int numCameras = 0;
+ unsigned int numSinks = 0;
+ for (MediaPad *pad : crossbar_->entity()->pads()) {
+ unsigned int sink = numSinks;
+
+ if (!(pad->flags() & MEDIA_PAD_FL_SINK) || pad->links().empty())
+ continue;
+
+ /*
+ * Count each crossbar sink pad to correctly configure
+ * routing and format for this camera.
+ */
+ numSinks++;
+
+ MediaEntity *csi = pad->links()[0]->source()->entity();
+ if (csi->pads().size() != 2) {
+ LOG(ISI, Debug) << "Skip unsupported CSI-2 receiver "
+ << csi->name();
+ continue;
+ }
+
+ pad = csi->pads()[0];
+ if (!(pad->flags() & MEDIA_PAD_FL_SINK) || pad->links().empty())
+ continue;
+
+ MediaEntity *sensor = pad->links()[0]->source()->entity();
+ if (sensor->function() != MEDIA_ENT_F_CAM_SENSOR) {
+ LOG(ISI, Debug) << "Skip unsupported subdevice "
+ << sensor->name();
+ continue;
+ }
+
+ /* Create the camera data. */
+ std::unique_ptr<ISICameraData> data =
+ std::make_unique<ISICameraData>(this);
+
+ data->sensor_ = std::make_unique<CameraSensor>(sensor);
+ data->csis_ = std::make_unique<V4L2Subdevice>(csi);
+ data->xbarSink_ = sink;
+
+ ret = data->init();
+ if (ret) {
+ LOG(ISI, Error) << "Failed to initialize camera data";
+ return false;
+ }
+
+ /* Register the camera. */
+ const std::string &id = data->sensor_->id();
+ std::set<Stream *> streams;
+ std::transform(data->streams_.begin(), data->streams_.end(),
+ std::inserter(streams, streams.end()),
+ [](Stream &s) { return &s; });
+
+ std::shared_ptr<Camera> camera =
+ Camera::create(std::move(data), id, streams);
+
+ registerCamera(std::move(camera));
+ numCameras++;
+ }
+
+ return numCameras > 0;
+}
+
+PipelineHandlerISI::Pipe *PipelineHandlerISI::pipeFromStream(Camera *camera,
+ const Stream *stream)
+{
+ ISICameraData *data = cameraData(camera);
+ unsigned int pipeIndex = data->pipeIndex(stream);
+
+ ASSERT(pipeIndex < pipes_.size());
+
+ return &pipes_[pipeIndex];
+}
+
+void PipelineHandlerISI::bufferReady(FrameBuffer *buffer)
+{
+ Request *request = buffer->request();
+
+ /* Record the sensor's timestamp in the request metadata. */
+ ControlList &metadata = request->metadata();
+ if (!metadata.contains(controls::SensorTimestamp.id()))
+ metadata.set(controls::SensorTimestamp,
+ buffer->metadata().timestamp);
+
+ completeBuffer(request, buffer);
+ if (request->hasPendingBuffers())
+ return;
+
+ completeRequest(request);
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerISI)
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build
new file mode 100644
index 00000000..ffd0ce54
--- /dev/null
+++ b/src/libcamera/pipeline/imx8-isi/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+ 'imx8-isi.cpp'
+])
diff --git a/src/libcamera/pipeline/ipu3/cio2.cpp b/src/libcamera/pipeline/ipu3/cio2.cpp
index 08e254f7..43c816ba 100644
--- a/src/libcamera/pipeline/ipu3/cio2.cpp
+++ b/src/libcamera/pipeline/ipu3/cio2.cpp
@@ -15,6 +15,7 @@
#include <libcamera/formats.h>
#include <libcamera/geometry.h>
#include <libcamera/stream.h>
+#include <libcamera/transform.h>
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/framebuffer.h"
@@ -177,10 +178,12 @@ int CIO2Device::init(const MediaDevice *media, unsigned int index)
/**
* \brief Configure the CIO2 unit
* \param[in] size The requested CIO2 output frame size
+ * \param[in] transform The transformation to be applied on the image sensor
* \param[out] outputFormat The CIO2 unit output image format
* \return 0 on success or a negative error code otherwise
*/
-int CIO2Device::configure(const Size &size, V4L2DeviceFormat *outputFormat)
+int CIO2Device::configure(const Size &size, const Transform &transform,
+ V4L2DeviceFormat *outputFormat)
{
V4L2SubdeviceFormat sensorFormat;
int ret;
@@ -191,7 +194,7 @@ int CIO2Device::configure(const Size &size, V4L2DeviceFormat *outputFormat)
*/
std::vector<unsigned int> mbusCodes = utils::map_keys(mbusCodesToPixelFormat);
sensorFormat = getSensorFormat(mbusCodes, size);
- ret = sensor_->setFormat(&sensorFormat);
+ ret = sensor_->setFormat(&sensorFormat, transform);
if (ret)
return ret;
@@ -199,11 +202,11 @@ int CIO2Device::configure(const Size &size, V4L2DeviceFormat *outputFormat)
if (ret)
return ret;
- const auto &itInfo = mbusCodesToPixelFormat.find(sensorFormat.mbus_code);
+ const auto &itInfo = mbusCodesToPixelFormat.find(sensorFormat.code);
if (itInfo == mbusCodesToPixelFormat.end())
return -EINVAL;
- outputFormat->fourcc = V4L2PixelFormat::fromPixelFormat(itInfo->second);
+ outputFormat->fourcc = output_->toV4L2PixelFormat(itInfo->second);
outputFormat->size = sensorFormat.size;
outputFormat->planesCount = 1;
@@ -227,13 +230,13 @@ StreamConfiguration CIO2Device::generateConfiguration(Size size) const
/* Query the sensor static information for closest match. */
std::vector<unsigned int> mbusCodes = utils::map_keys(mbusCodesToPixelFormat);
V4L2SubdeviceFormat sensorFormat = getSensorFormat(mbusCodes, size);
- if (!sensorFormat.mbus_code) {
+ if (!sensorFormat.code) {
LOG(IPU3, Error) << "Sensor does not support mbus code";
return {};
}
cfg.size = sensorFormat.size;
- cfg.pixelFormat = mbusCodesToPixelFormat.at(sensorFormat.mbus_code);
+ cfg.pixelFormat = mbusCodesToPixelFormat.at(sensorFormat.code);
cfg.bufferCount = kBufferCount;
return cfg;
@@ -323,7 +326,7 @@ V4L2SubdeviceFormat CIO2Device::getSensorFormat(const std::vector<unsigned int>
}
V4L2SubdeviceFormat format{};
- format.mbus_code = bestCode;
+ format.code = bestCode;
format.size = bestSize;
return format;
diff --git a/src/libcamera/pipeline/ipu3/cio2.h b/src/libcamera/pipeline/ipu3/cio2.h
index 68504a2d..bbd87eb8 100644
--- a/src/libcamera/pipeline/ipu3/cio2.h
+++ b/src/libcamera/pipeline/ipu3/cio2.h
@@ -26,6 +26,7 @@ class Request;
class Size;
class SizeRange;
struct StreamConfiguration;
+enum class Transform;
class CIO2Device
{
@@ -38,7 +39,8 @@ public:
std::vector<SizeRange> sizes(const PixelFormat &format) const;
int init(const MediaDevice *media, unsigned int index);
- int configure(const Size &size, V4L2DeviceFormat *outputFormat);
+ int configure(const Size &size, const Transform &transform,
+ V4L2DeviceFormat *outputFormat);
StreamConfiguration generateConfiguration(Size size) const;
diff --git a/src/libcamera/pipeline/ipu3/imgu.cpp b/src/libcamera/pipeline/ipu3/imgu.cpp
index 59305f85..2202438a 100644
--- a/src/libcamera/pipeline/ipu3/imgu.cpp
+++ b/src/libcamera/pipeline/ipu3/imgu.cpp
@@ -504,7 +504,7 @@ int ImgUDevice::configure(const PipeConfig &pipeConfig, V4L2DeviceFormat *inputF
LOG(IPU3, Debug) << "ImgU BDS rectangle = " << bds;
V4L2SubdeviceFormat gdcFormat = {};
- gdcFormat.mbus_code = MEDIA_BUS_FMT_FIXED;
+ gdcFormat.code = MEDIA_BUS_FMT_FIXED;
gdcFormat.size = pipeConfig.gdc;
ret = imgu_->setFormat(PAD_INPUT, &gdcFormat);
@@ -543,7 +543,7 @@ int ImgUDevice::configureVideoDevice(V4L2VideoDevice *dev, unsigned int pad,
V4L2DeviceFormat *outputFormat)
{
V4L2SubdeviceFormat imguFormat = {};
- imguFormat.mbus_code = MEDIA_BUS_FMT_FIXED;
+ imguFormat.code = MEDIA_BUS_FMT_FIXED;
imguFormat.size = cfg.size;
int ret = imgu_->setFormat(pad, &imguFormat);
@@ -558,7 +558,7 @@ int ImgUDevice::configureVideoDevice(V4L2VideoDevice *dev, unsigned int pad,
return 0;
*outputFormat = {};
- outputFormat->fourcc = V4L2PixelFormat::fromPixelFormat(formats::NV12);
+ outputFormat->fourcc = dev->toV4L2PixelFormat(formats::NV12);
outputFormat->size = cfg.size;
outputFormat->planesCount = 2;
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index b7dda282..fa4bd0bb 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -11,6 +11,8 @@
#include <queue>
#include <vector>
+#include <linux/intel-ipu3.h>
+
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -49,7 +51,7 @@ class IPU3CameraData : public Camera::Private
{
public:
IPU3CameraData(PipelineHandler *pipe)
- : Camera::Private(pipe), supportsFlips_(false)
+ : Camera::Private(pipe)
{
}
@@ -71,8 +73,6 @@ public:
Stream rawStream_;
Rectangle cropRegion_;
- bool supportsFlips_;
- Transform rotationTransform_;
std::unique_ptr<DelayedControls> delayedCtrls_;
IPU3Frames frameInfos_;
@@ -134,8 +134,8 @@ public:
PipelineHandlerIPU3(CameraManager *manager);
- CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) override;
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -182,48 +182,15 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate()
if (config_.empty())
return Invalid;
- Transform combined = transform * data_->rotationTransform_;
-
- /*
- * We combine the platform and user transform, but must "adjust away"
- * any combined result that includes a transposition, as we can't do
- * those. In this case, flipping only the transpose bit is helpful to
- * applications - they either get the transform they requested, or have
- * to do a simple transpose themselves (they don't have to worry about
- * the other possible cases).
- */
- if (!!(combined & Transform::Transpose)) {
- /*
- * Flipping the transpose bit in "transform" flips it in the
- * combined result too (as it's the last thing that happens),
- * which is of course clearing it.
- */
- transform ^= Transform::Transpose;
- combined &= ~Transform::Transpose;
- status = Adjusted;
- }
-
/*
- * We also check if the sensor doesn't do h/vflips at all, in which
- * case we clear them, and the application will have to do everything.
+ * Validate the requested transform against the sensor capabilities and
+ * rotation and store the final combined transform that configure() will
+ * need to apply to the sensor to save us working it out again.
*/
- if (!data_->supportsFlips_ && !!combined) {
- /*
- * If the sensor can do no transforms, then combined must be
- * changed to the identity. The only user transform that gives
- * rise to this is the inverse of the rotation. (Recall that
- * combined = transform * rotationTransform.)
- */
- transform = -data_->rotationTransform_;
- combined = Transform::Identity;
+ Orientation requestedOrientation = orientation;
+ combinedTransform_ = data_->cio2_.sensor()->computeTransform(&orientation);
+ if (orientation != requestedOrientation)
status = Adjusted;
- }
-
- /*
- * Store the final combined transform that configure() will need to
- * apply to the sensor to save us working it out again.
- */
- combinedTransform_ = combined;
/* Cap the number of entries to the available streams. */
if (config_.size() > kMaxStreams) {
@@ -243,6 +210,7 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate()
*/
unsigned int rawCount = 0;
unsigned int yuvCount = 0;
+ Size rawRequirement;
Size maxYuvSize;
Size rawSize;
@@ -251,10 +219,11 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate()
if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {
rawCount++;
- rawSize.expandTo(cfg.size);
+ rawSize = std::max(rawSize, cfg.size);
} else {
yuvCount++;
- maxYuvSize.expandTo(cfg.size);
+ maxYuvSize = std::max(maxYuvSize, cfg.size);
+ rawRequirement.expandTo(cfg.size);
}
}
@@ -283,17 +252,17 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate()
* The output YUV streams will be limited in size to the maximum frame
* size requested for the RAW stream, if present.
*
- * If no raw stream is requested generate a size as large as the maximum
- * requested YUV size aligned to the ImgU constraints and bound by the
- * sensor's maximum resolution. See
+ * If no raw stream is requested, generate a size from the largest YUV
+ * stream, aligned to the ImgU constraints and bound
+ * by the sensor's maximum resolution. See
* https://bugs.libcamera.org/show_bug.cgi?id=32
*/
if (rawSize.isNull())
- rawSize = maxYuvSize.expandedTo({ ImgUDevice::kIFMaxCropWidth,
- ImgUDevice::kIFMaxCropHeight })
- .grownBy({ ImgUDevice::kOutputMarginWidth,
- ImgUDevice::kOutputMarginHeight })
- .boundedTo(data_->cio2_.sensor()->resolution());
+ rawSize = rawRequirement.expandedTo({ ImgUDevice::kIFMaxCropWidth,
+ ImgUDevice::kIFMaxCropHeight })
+ .grownBy({ ImgUDevice::kOutputMarginWidth,
+ ImgUDevice::kOutputMarginHeight })
+ .boundedTo(data_->cio2_.sensor()->resolution());
cio2Configuration_ = data_->cio2_.generateConfiguration(rawSize);
if (!cio2Configuration_.pixelFormat.isValid())
@@ -420,11 +389,12 @@ PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)
{
}
-CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerIPU3::generateConfiguration(Camera *camera, Span<const StreamRole> roles)
{
IPU3CameraData *data = cameraData(camera);
- IPU3CameraConfiguration *config = new IPU3CameraConfiguration(data);
+ std::unique_ptr<IPU3CameraConfiguration> config =
+ std::make_unique<IPU3CameraConfiguration>(data);
if (roles.empty())
return config;
@@ -490,7 +460,6 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,
default:
LOG(IPU3, Error)
<< "Requested stream role not supported: " << role;
- delete config;
return nullptr;
}
@@ -552,7 +521,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
return ret;
/*
- * \todo: Enable links selectively based on the requested streams.
+ * \todo Enable links selectively based on the requested streams.
* As of now, enable all links unconditionally.
* \todo Don't configure the ImgU at all if we only have a single
* stream which is for raw capture, in which case no buffers will
@@ -568,7 +537,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
*/
const Size &sensorSize = config->cio2Format().size;
V4L2DeviceFormat cio2Format;
- ret = cio2->configure(sensorSize, &cio2Format);
+ ret = cio2->configure(sensorSize, config->combinedTransform_, &cio2Format);
if (ret)
return ret;
@@ -577,24 +546,6 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
data->cropRegion_ = sensorInfo.analogCrop;
/*
- * Configure the H/V flip controls based on the combination of
- * the sensor and user transform.
- */
- if (data->supportsFlips_) {
- ControlList sensorCtrls(cio2->sensor()->controls());
- sensorCtrls.set(V4L2_CID_HFLIP,
- static_cast<int32_t>(!!(config->combinedTransform_
- & Transform::HFlip)));
- sensorCtrls.set(V4L2_CID_VFLIP,
- static_cast<int32_t>(!!(config->combinedTransform_
- & Transform::VFlip)));
-
- ret = cio2->sensor()->setControls(&sensorCtrls);
- if (ret)
- return ret;
- }
-
- /*
* If the ImgU gets configured, its driver seems to expect that
* buffers will be queued to its outputs, as otherwise the next
* capture session that uses the ImgU fails when queueing
@@ -1143,25 +1094,12 @@ int PipelineHandlerIPU3::registerCameras()
&IPU3CameraData::frameStart);
/* Convert the sensor rotation to a transformation */
- int32_t rotation = 0;
- if (data->properties_.contains(properties::Rotation))
- rotation = data->properties_.get(properties::Rotation);
- else
+ const auto &rotation = data->properties_.get(properties::Rotation);
+ if (!rotation)
LOG(IPU3, Warning) << "Rotation control not exposed by "
<< cio2->sensor()->id()
<< ". Assume rotation 0";
- bool success;
- data->rotationTransform_ = transformFromRotation(rotation, &success);
- if (!success)
- LOG(IPU3, Warning) << "Invalid rotation of " << rotation
- << " degrees: ignoring";
-
- ControlList ctrls = cio2->sensor()->getControls({ V4L2_CID_HFLIP });
- if (!ctrls.empty())
- /* We assume the sensor supports VFLIP too. */
- data->supportsFlips_ = true;
-
/**
* \todo Dynamically assign ImgU and output devices to each
* stream and camera; as of now, limit support to two cameras
@@ -1244,8 +1182,16 @@ int IPU3CameraData::loadIPA()
if (ret)
return ret;
- ret = ipa_->init(IPASettings{ "", sensor->model() }, sensorInfo,
- sensor->controls(), &ipaControls_);
+ /*
+ * The API tuning file is made from the sensor name. If the tuning file
+ * isn't found, fall back to the 'uncalibrated' file.
+ */
+ std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml");
+ if (ipaTuningFile.empty())
+ ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+
+ ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
+ sensorInfo, sensor->controls(), &ipaControls_);
if (ret) {
LOG(IPU3, Error) << "Failed to initialise the IPU3 IPA";
return ret;
@@ -1289,6 +1235,8 @@ void IPU3CameraData::paramsBufferReady(unsigned int id)
imgu_->viewfinder_->queueBuffer(outbuffer);
}
+ info->paramBuffer->_d()->metadata().planes()[0].bytesused =
+ sizeof(struct ipu3_uapi_params);
imgu_->param_->queueBuffer(info->paramBuffer);
imgu_->stat_->queueBuffer(info->statBuffer);
imgu_->input_->queueBuffer(info->rawBuffer);
@@ -1330,8 +1278,9 @@ void IPU3CameraData::imguOutputBufferReady(FrameBuffer *buffer)
request->metadata().set(controls::draft::PipelineDepth, 3);
/* \todo Actually apply the scaler crop region to the ImgU. */
- if (request->controls().contains(controls::ScalerCrop))
- cropRegion_ = request->controls().get(controls::ScalerCrop);
+ const auto &scalerCrop = request->controls().get(controls::ScalerCrop);
+ if (scalerCrop)
+ cropRegion_ = *scalerCrop;
request->metadata().set(controls::ScalerCrop, cropRegion_);
if (frameInfos_.tryComplete(info))
@@ -1424,7 +1373,7 @@ void IPU3CameraData::statBufferReady(FrameBuffer *buffer)
return;
}
- ipa_->processStatsBuffer(info->id, request->metadata().get(controls::SensorTimestamp),
+ ipa_->processStatsBuffer(info->id, request->metadata().get(controls::SensorTimestamp).value_or(0),
info->statBuffer->cookie(), info->effectiveSensorControls);
}
@@ -1455,14 +1404,12 @@ void IPU3CameraData::frameStart(uint32_t sequence)
Request *request = processingRequests_.front();
processingRequests_.pop();
- if (!request->controls().contains(controls::draft::TestPatternMode))
+ const auto &testPatternMode = request->controls().get(controls::draft::TestPatternMode);
+ if (!testPatternMode)
return;
- const int32_t testPatternMode = request->controls().get(
- controls::draft::TestPatternMode);
-
int ret = cio2_.sensor()->setTestPatternMode(
- static_cast<controls::draft::TestPatternModeEnum>(testPatternMode));
+ static_cast<controls::draft::TestPatternModeEnum>(*testPatternMode));
if (ret) {
LOG(IPU3, Error) << "Failed to set test pattern mode: "
<< ret;
@@ -1470,7 +1417,7 @@ void IPU3CameraData::frameStart(uint32_t sequence)
}
request->metadata().set(controls::draft::TestPatternMode,
- testPatternMode);
+ *testPatternMode);
}
REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3)
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
new file mode 100644
index 00000000..78343553
--- /dev/null
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -0,0 +1,1066 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP
+ */
+
+#include <algorithm>
+#include <array>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include <linux/media-bus-format.h>
+#include <linux/media.h>
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/formats.h>
+#include <libcamera/geometry.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+namespace {
+
+bool isFormatRaw(const libcamera::PixelFormat &pixFmt)
+{
+ return libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==
+ libcamera::PixelFormatInfo::ColourEncodingRAW;
+}
+
+} /* namespace */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(MaliC55)
+
+const std::map<libcamera::PixelFormat, unsigned int> maliC55FmtToCode = {
+ /* \todo Support all formats supported by the driver in libcamera. */
+
+ { formats::RGB565, MEDIA_BUS_FMT_RGB121212_1X36 },
+ { formats::RGB888, MEDIA_BUS_FMT_RGB121212_1X36 },
+ { formats::YUYV, MEDIA_BUS_FMT_YUV10_1X30 },
+ { formats::UYVY, MEDIA_BUS_FMT_YUV10_1X30 },
+ { formats::R8, MEDIA_BUS_FMT_YUV10_1X30 },
+ { formats::NV12, MEDIA_BUS_FMT_YUV10_1X30 },
+ { formats::NV21, MEDIA_BUS_FMT_YUV10_1X30 },
+
+ /* RAW formats, FR pipe only. */
+ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
+ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
+ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
+ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
+ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
+ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
+ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
+ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
+ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
+ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
+ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
+ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
+ { formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 },
+ { formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 },
+ { formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 },
+ { formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 },
+ { formats::SGBRG16, MEDIA_BUS_FMT_SGBRG16_1X16 },
+ { formats::SRGGB16, MEDIA_BUS_FMT_SRGGB16_1X16 },
+ { formats::SBGGR16, MEDIA_BUS_FMT_SBGGR16_1X16 },
+ { formats::SGRBG16, MEDIA_BUS_FMT_SGRBG16_1X16 },
+};
+
+constexpr Size kMaliC55MinSize = { 128, 128 };
+constexpr Size kMaliC55MaxSize = { 8192, 8192 };
+constexpr unsigned int kMaliC55ISPInternalFormat = MEDIA_BUS_FMT_RGB121212_1X36;
+
+class MaliC55CameraData : public Camera::Private
+{
+public:
+ MaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity)
+ : Camera::Private(pipe), entity_(entity)
+ {
+ }
+
+ int init();
+
+ /* Deflect these functionalities to either TPG or CameraSensor. */
+ const std::vector<unsigned int> mbusCodes() const;
+ const std::vector<Size> sizes(unsigned int mbusCode) const;
+ const Size resolution() const;
+
+ PixelFormat bestRawFormat() const;
+
+ PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;
+ Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;
+
+ std::unique_ptr<CameraSensor> sensor_;
+
+ MediaEntity *entity_;
+ std::unique_ptr<V4L2Subdevice> csi_;
+ std::unique_ptr<V4L2Subdevice> sd_;
+ Stream frStream_;
+ Stream dsStream_;
+
+private:
+ void initTPGData();
+
+ std::string id_;
+ std::vector<unsigned int> tpgCodes_;
+ std::vector<Size> tpgSizes_;
+ Size tpgResolution_;
+};
+
+int MaliC55CameraData::init()
+{
+ int ret;
+
+ sd_ = std::make_unique<V4L2Subdevice>(entity_);
+ ret = sd_->open();
+ if (ret) {
+ LOG(MaliC55, Error) << "Failed to open sensor subdevice";
+ return ret;
+ }
+
+ /* If this camera is created from TPG, we return here. */
+ if (entity_->name() == "mali-c55 tpg") {
+ initTPGData();
+ return 0;
+ }
+
+ /*
+ * Register a CameraSensor if we connect to a sensor and create
+ * an entity for the connected CSI-2 receiver.
+ */
+ sensor_ = std::make_unique<CameraSensor>(entity_);
+ ret = sensor_->init();
+ if (ret)
+ return ret;
+
+ const MediaPad *sourcePad = entity_->getPadByIndex(0);
+ MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity();
+
+ csi_ = std::make_unique<V4L2Subdevice>(csiEntity);
+ if (csi_->open()) {
+ LOG(MaliC55, Error) << "Failed to open CSI-2 subdevice";
+ return false;
+ }
+
+ return 0;
+}
+
+void MaliC55CameraData::initTPGData()
+{
+ /* Replicate the CameraSensor implementation for TPG. */
+ V4L2Subdevice::Formats formats = sd_->formats(0);
+ if (formats.empty())
+ return;
+
+ tpgCodes_ = utils::map_keys(formats);
+ std::sort(tpgCodes_.begin(), tpgCodes_.end());
+
+ for (const auto &format : formats) {
+ const std::vector<SizeRange> &ranges = format.second;
+ std::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes_),
+ [](const SizeRange &range) { return range.max; });
+ }
+
+ tpgResolution_ = tpgSizes_.back();
+}
+
+const std::vector<unsigned int> MaliC55CameraData::mbusCodes() const
+{
+ if (sensor_)
+ return sensor_->mbusCodes();
+
+ return tpgCodes_;
+}
+
+const std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const
+{
+ if (sensor_)
+ return sensor_->sizes(mbusCode);
+
+ V4L2Subdevice::Formats formats = sd_->formats(0);
+ if (formats.empty())
+ return {};
+
+ std::vector<Size> sizes;
+ const auto &format = formats.find(mbusCode);
+ if (format == formats.end())
+ return {};
+
+ const std::vector<SizeRange> &ranges = format->second;
+ std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes),
+ [](const SizeRange &range) { return range.max; });
+
+ std::sort(sizes.begin(), sizes.end());
+
+ return sizes;
+}
+
+const Size MaliC55CameraData::resolution() const
+{
+ if (sensor_)
+ return sensor_->resolution();
+
+ return tpgResolution_;
+}
+
+PixelFormat MaliC55CameraData::bestRawFormat() const
+{
+ unsigned int bitDepth = 0;
+ PixelFormat rawFormat;
+
+ /*
+ * Iterate over all the supported PixelFormat and find the one
+ * supported by the camera with the largest bitdepth.
+ */
+ for (const auto &maliFormat : maliC55FmtToCode) {
+ PixelFormat pixFmt = maliFormat.first;
+ if (!isFormatRaw(pixFmt))
+ continue;
+
+ unsigned int rawCode = maliFormat.second;
+ const auto rawSizes = sizes(rawCode);
+ if (rawSizes.empty())
+ continue;
+
+ BayerFormat bayer = BayerFormat::fromMbusCode(rawCode);
+ if (bayer.bitDepth > bitDepth) {
+ bitDepth = bayer.bitDepth;
+ rawFormat = pixFmt;
+ }
+ }
+
+ return rawFormat;
+}
+
+/*
+ * Make sure the provided raw pixel format is supported and adjust it to
+ * one of the supported ones if it's not.
+ */
+PixelFormat MaliC55CameraData::adjustRawFormat(const PixelFormat &rawFmt) const
+{
+ /* Make sure the provided raw format is supported by the pipeline. */
+ auto it = maliC55FmtToCode.find(rawFmt);
+ if (it == maliC55FmtToCode.end())
+ return bestRawFormat();
+
+ /* Now make sure the RAW mbus code is supported by the image source. */
+ unsigned int rawCode = it->second;
+ const auto rawSizes = sizes(rawCode);
+ if (rawSizes.empty())
+ return bestRawFormat();
+
+ return rawFmt;
+}
+
+Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &rawSize) const
+{
+ /* Just make sure the format is supported. */
+ auto it = maliC55FmtToCode.find(rawFmt);
+ if (it == maliC55FmtToCode.end())
+ return {};
+
+ /* Check if the size is natively supported. */
+ unsigned int rawCode = it->second;
+ const auto rawSizes = sizes(rawCode);
+ auto sizeIt = std::find(rawSizes.begin(), rawSizes.end(), rawSize);
+ if (sizeIt != rawSizes.end())
+ return rawSize;
+
+ /* Or adjust it to the closest supported size. */
+ uint16_t distance = std::numeric_limits<uint16_t>::max();
+ Size bestSize;
+ for (const Size &size : rawSizes) {
+ uint16_t dist = std::abs(static_cast<int>(rawSize.width) -
+ static_cast<int>(size.width)) +
+ std::abs(static_cast<int>(rawSize.height) -
+ static_cast<int>(size.height));
+ if (dist < distance) {
+ dist = distance;
+ bestSize = size;
+ }
+ }
+
+ return bestSize;
+}
+
+class MaliC55CameraConfiguration : public CameraConfiguration
+{
+public:
+ MaliC55CameraConfiguration(MaliC55CameraData *data)
+ : CameraConfiguration(), data_(data)
+ {
+ }
+
+ Status validate() override;
+
+ V4L2SubdeviceFormat sensorFormat_;
+
+private:
+ static constexpr unsigned int kMaxStreams = 2;
+
+ const MaliC55CameraData *data_;
+};
+
+CameraConfiguration::Status MaliC55CameraConfiguration::validate()
+{
+ Status status = Valid;
+
+ if (config_.empty())
+ return Invalid;
+
+ /* Only 2 streams available. */
+ if (config_.size() > kMaxStreams) {
+ config_.resize(kMaxStreams);
+ status = Adjusted;
+ }
+
+ bool frPipeAvailable = true;
+ StreamConfiguration *rawConfig = nullptr;
+ for (StreamConfiguration &config : config_) {
+ if (!isFormatRaw(config.pixelFormat))
+ continue;
+
+ if (rawConfig) {
+ LOG(MaliC55, Error)
+ << "Only a single RAW stream is supported";
+ return Invalid;
+ }
+
+ rawConfig = &config;
+ }
+
+ Size maxSize = kMaliC55MaxSize;
+ if (rawConfig) {
+ /*
+ * \todo Take into account the Bayer components ordering once
+ * we support rotations.
+ */
+ PixelFormat rawFormat =
+ data_->adjustRawFormat(rawConfig->pixelFormat);
+ if (rawFormat != rawConfig->pixelFormat) {
+ LOG(MaliC55, Debug)
+ << "RAW format adjusted to " << rawFormat;
+ rawConfig->pixelFormat = rawFormat;
+ status = Adjusted;
+ }
+
+ Size rawSize =
+ data_->adjustRawSizes(rawFormat, rawConfig->size);
+ if (rawSize != rawConfig->size) {
+ LOG(MaliC55, Debug)
+ << "RAW sizes adjusted to " << rawSize;
+ rawConfig->size = rawSize;
+ status = Adjusted;
+ }
+
+ maxSize = rawSize;
+
+ rawConfig->setStream(const_cast<Stream *>(&data_->frStream_));
+ frPipeAvailable = false;
+ }
+
+ /* Adjust processed streams. */
+ Size maxYuvSize;
+ for (StreamConfiguration &config : config_) {
+ if (isFormatRaw(config.pixelFormat))
+ continue;
+
+ /* Adjust format and size for processed streams. */
+ const auto it = maliC55FmtToCode.find(config.pixelFormat);
+ if (it == maliC55FmtToCode.end()) {
+ LOG(MaliC55, Debug)
+ << "Format adjusted to " << formats::RGB565;
+ config.pixelFormat = formats::RGB565;
+ status = Adjusted;
+ }
+
+ Size size = std::clamp(config.size, kMaliC55MinSize, maxSize);
+ if (size != config.size) {
+ LOG(MaliC55, Debug)
+ << "Size adjusted to " << size;
+ config.size = size;
+ status = Adjusted;
+ }
+
+ if (maxYuvSize < size)
+ maxYuvSize = size;
+
+ if (frPipeAvailable) {
+ config.setStream(const_cast<Stream *>(&data_->frStream_));
+ frPipeAvailable = false;
+ } else {
+ config.setStream(const_cast<Stream *>(&data_->dsStream_));
+ }
+ }
+
+ /* Compute the sensor format. */
+
+ /* If there's a RAW config, sensor configuration follows it. */
+ if (rawConfig) {
+ const auto it = maliC55FmtToCode.find(rawConfig->pixelFormat);
+ sensorFormat_.code = it->second;
+ sensorFormat_.size = rawConfig->size;
+
+ return status;
+ }
+
+ /* If there's no RAW config, compute the sensor configuration here. */
+ PixelFormat rawFormat = data_->bestRawFormat();
+ const auto it = maliC55FmtToCode.find(rawFormat);
+ sensorFormat_.code = it->second;
+
+ uint16_t distance = std::numeric_limits<uint16_t>::max();
+ const auto sizes = data_->sizes(it->second);
+ Size bestSize;
+ for (const auto &size : sizes) {
+ /* Skip sensor sizes that are smaller than the max YUV size. */
+ if (maxYuvSize.width > size.width ||
+ maxYuvSize.height > size.height)
+ continue;
+
+ uint16_t dist = std::abs(static_cast<int>(maxYuvSize.width) -
+ static_cast<int>(size.width)) +
+ std::abs(static_cast<int>(maxYuvSize.height) -
+ static_cast<int>(size.height));
+ if (dist < distance) {
+ dist = distance;
+ bestSize = size;
+ }
+ }
+ sensorFormat_.size = bestSize;
+
+ LOG(MaliC55, Debug) << "Computed sensor configuration " << sensorFormat_;
+
+ return status;
+}
+
+class PipelineHandlerMaliC55 : public PipelineHandler
+{
+public:
+ PipelineHandlerMaliC55(CameraManager *manager);
+
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) override;
+ int configure(Camera *camera, CameraConfiguration *config) override;
+
+ int exportFrameBuffers(Camera *camera, Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
+
+ int start(Camera *camera, const ControlList *controls) override;
+ void stopDevice(Camera *camera) override;
+
+ int queueRequestDevice(Camera *camera, Request *request) override;
+
+ void bufferReady(FrameBuffer *buffer);
+
+ bool match(DeviceEnumerator *enumerator) override;
+
+private:
+ struct MaliC55Pipe {
+ std::unique_ptr<V4L2Subdevice> resizer;
+ std::unique_ptr<V4L2VideoDevice> cap;
+ Stream *stream;
+ };
+
+ enum {
+ MaliC55FR,
+ MaliC55DS,
+ MaliC55NumPipes,
+ };
+
+ MaliC55CameraData *cameraData(Camera *camera)
+ {
+ return static_cast<MaliC55CameraData *>(camera->_d());
+ }
+
+ MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, Stream *stream)
+ {
+ if (stream == &data->frStream_)
+ return &pipes_[MaliC55FR];
+ else if (stream == &data->dsStream_)
+ return &pipes_[MaliC55DS];
+ else
+ LOG(MaliC55, Fatal) << "Stream " << stream << " not valid";
+ return nullptr;
+ }
+
+ MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, const Stream *stream)
+ {
+ return pipeFromStream(data, const_cast<Stream *>(stream));
+ }
+
+ void resetPipes()
+ {
+ for (MaliC55Pipe &pipe : pipes_)
+ pipe.stream = nullptr;
+ }
+
+ int configureRawStream(MaliC55CameraData *data,
+ const StreamConfiguration &config,
+ V4L2SubdeviceFormat &subdevFormat);
+ int configureProcessedStream(MaliC55CameraData *data,
+ const StreamConfiguration &config,
+ V4L2SubdeviceFormat &subdevFormat);
+
+ void registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,
+ const std::string &name);
+ bool registerTPGCamera(MediaLink *link);
+ bool registerSensorCamera(MediaLink *link);
+
+ MediaDevice *media_;
+ std::unique_ptr<V4L2Subdevice> isp_;
+
+ std::array<MaliC55Pipe, MaliC55NumPipes> pipes_;
+
+ bool dsFitted_;
+};
+
+PipelineHandlerMaliC55::PipelineHandlerMaliC55(CameraManager *manager)
+ : PipelineHandler(manager), dsFitted_(true)
+{
+}
+
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerMaliC55::generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles)
+{
+ MaliC55CameraData *data = cameraData(camera);
+ std::unique_ptr<CameraConfiguration> config =
+ std::make_unique<MaliC55CameraConfiguration>(data);
+ bool frPipeAvailable = true;
+
+ if (roles.empty())
+ return config;
+
+ /* Check if one stream is RAW to reserve the FR pipe for it. */
+ if (std::find(roles.begin(), roles.end(), StreamRole::Raw) != roles.end())
+ frPipeAvailable = false;
+
+ for (const StreamRole &role : roles) {
+ struct MaliC55Pipe *pipe;
+
+ /* Assign pipe for this role. */
+ if (role == StreamRole::Raw) {
+ pipe = &pipes_[MaliC55FR];
+ } else {
+ if (frPipeAvailable) {
+ pipe = &pipes_[MaliC55FR];
+ frPipeAvailable = false;
+ } else {
+ pipe = &pipes_[MaliC55DS];
+ }
+ }
+
+ Size size = std::min(Size{ 1920, 1080 }, data->resolution());
+ PixelFormat pixelFormat;
+
+ switch (role) {
+ case StreamRole::StillCapture:
+ size = data->resolution();
+ [[fallthrough]];
+ case StreamRole::VideoRecording:
+ pixelFormat = formats::NV12;
+ break;
+
+ case StreamRole::Viewfinder:
+ pixelFormat = formats::RGB565;
+ break;
+
+ case StreamRole::Raw:
+ pixelFormat = data->bestRawFormat();
+ if (!pixelFormat.isValid()) {
+ LOG(MaliC55, Error)
+ << "Camera does not support RAW formats";
+ return nullptr;
+ }
+
+ size = data->resolution();
+ break;
+
+ default:
+ LOG(MaliC55, Error)
+ << "Requested stream role not supported: " << role;
+ return nullptr;
+ }
+
+ std::map<PixelFormat, std::vector<SizeRange>> formats;
+ for (const auto &maliFormat : maliC55FmtToCode) {
+ PixelFormat pixFmt = maliFormat.first;
+ bool isRaw = isFormatRaw(pixFmt);
+
+ /* RAW formats are only supported on the FR pipe. */
+ if (pipe != &pipes_[MaliC55FR] && isRaw)
+ continue;
+
+ if (isRaw) {
+ /* Make sure the mbus code is supported. */
+ unsigned int rawCode = maliFormat.second;
+ const auto sizes = data->sizes(rawCode);
+ if (sizes.empty())
+ continue;
+
+ /* And list all sizes the sensor can produce. */
+ std::vector<SizeRange> sizeRanges;
+ std::transform(sizes.begin(), sizes.end(),
+ std::back_inserter(sizeRanges),
+ [](const Size &s) {
+ return SizeRange(s);
+ });
+
+ formats[pixFmt] = sizeRanges;
+ } else {
+ /* Processed formats are always available. */
+ Size maxSize = std::min(kMaliC55MaxSize,
+ data->resolution());
+ formats[pixFmt] = { kMaliC55MinSize, maxSize };
+ }
+ }
+
+ StreamFormats streamFormats(formats);
+ StreamConfiguration cfg(streamFormats);
+ cfg.pixelFormat = pixelFormat;
+ cfg.bufferCount = 4;
+ cfg.size = size;
+
+ config->addConfiguration(cfg);
+ }
+
+ if (config->validate() == CameraConfiguration::Invalid)
+ return nullptr;
+
+ return config;
+}
+
+int PipelineHandlerMaliC55::configureRawStream(MaliC55CameraData *data,
+ const StreamConfiguration &config,
+ V4L2SubdeviceFormat &subdevFormat)
+{
+ Stream *stream = config.stream();
+ MaliC55Pipe *pipe = pipeFromStream(data, stream);
+
+ if (pipe != &pipes_[MaliC55FR]) {
+ LOG(MaliC55, Fatal) << "Only the FR pipe supports RAW capture.";
+ return -EINVAL;
+ }
+
+ /* Enable the debayer route to set fixed internal format on pad #0. */
+ V4L2Subdevice::Routing routing = {};
+ routing.emplace_back(V4L2Subdevice::Stream{ 0, 0 },
+ V4L2Subdevice::Stream{ 1, 0 },
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE);
+
+ int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);
+ if (ret)
+ return ret;
+
+ unsigned int rawCode = subdevFormat.code;
+ subdevFormat.code = kMaliC55ISPInternalFormat;
+ ret = pipe->resizer->setFormat(0, &subdevFormat);
+ if (ret)
+ return ret;
+
+ /* Enable the bypass route and apply RAW formats there. */
+ routing.clear();
+ routing.emplace_back(V4L2Subdevice::Stream{ 2, 0 },
+ V4L2Subdevice::Stream{ 1, 0 },
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE);
+ ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);
+ if (ret)
+ return ret;
+
+ subdevFormat.code = rawCode;
+ ret = pipe->resizer->setFormat(2, &subdevFormat);
+ if (ret)
+ return ret;
+
+ ret = pipe->resizer->setFormat(1, &subdevFormat);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int PipelineHandlerMaliC55::configureProcessedStream(MaliC55CameraData *data,
+ const StreamConfiguration &config,
+ V4L2SubdeviceFormat &subdevFormat)
+{
+ Stream *stream = config.stream();
+ MaliC55Pipe *pipe = pipeFromStream(data, stream);
+
+ /* Enable the debayer route on the resizer pipe. */
+ V4L2Subdevice::Routing routing = {};
+ routing.emplace_back(V4L2Subdevice::Stream{ 0, 0 },
+ V4L2Subdevice::Stream{ 1, 0 },
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE);
+
+ int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);
+ if (ret)
+ return ret;
+
+ subdevFormat.code = kMaliC55ISPInternalFormat;
+ ret = pipe->resizer->setFormat(0, &subdevFormat);
+ if (ret)
+ return ret;
+
+ /* \todo Configure the resizer crop/compose rectangles. */
+ Rectangle ispCrop = { 0, 0, config.size };
+ ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);
+ if (ret)
+ return ret;
+
+ ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, &ispCrop);
+ if (ret)
+ return ret;
+
+ subdevFormat.code = maliC55FmtToCode.find(config.pixelFormat)->second;
+ return pipe->resizer->setFormat(1, &subdevFormat);
+}
+
+int PipelineHandlerMaliC55::configure(Camera *camera,
+ CameraConfiguration *config)
+{
+ resetPipes();
+
+ int ret = media_->disableLinks();
+ if (ret)
+ return ret;
+
+ /* Link the graph depending if we are operating the TPG or a sensor. */
+ MaliC55CameraData *data = cameraData(camera);
+ if (data->csi_) {
+ const MediaEntity *csiEntity = data->csi_->entity();
+ ret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true);
+ } else {
+ ret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true);
+ }
+ if (ret)
+ return ret;
+
+ MaliC55CameraConfiguration *maliConfig =
+ static_cast<MaliC55CameraConfiguration *>(config);
+ V4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_;
+ ret = data->sd_->getFormat(0, &subdevFormat);
+ if (ret)
+ return ret;
+
+ if (data->csi_) {
+ ret = data->csi_->setFormat(0, &subdevFormat);
+ if (ret)
+ return ret;
+
+ ret = data->csi_->setFormat(1, &subdevFormat);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Propagate the format to the ISP sink pad and configure the input
+ * crop rectangle (no crop at the moment).
+ *
+ * \todo Configure the CSI-2 receiver.
+ */
+ ret = isp_->setFormat(0, &subdevFormat);
+ if (ret)
+ return ret;
+
+ Rectangle ispCrop(0, 0, subdevFormat.size);
+ ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure the resizer: fixed format the sink pad; use the media
+ * bus code associated with the desired capture format on the source
+ * pad.
+ *
+ * Configure the crop and compose rectangles to match the desired
+ * stream output size
+ *
+ * \todo Make the crop/scaler configurable
+ */
+ for (const StreamConfiguration &streamConfig : *config) {
+ Stream *stream = streamConfig.stream();
+ MaliC55Pipe *pipe = pipeFromStream(data, stream);
+
+ if (isFormatRaw(streamConfig.pixelFormat))
+ ret = configureRawStream(data, streamConfig, subdevFormat);
+ else
+ ret = configureProcessedStream(data, streamConfig, subdevFormat);
+ if (ret) {
+ LOG(MaliC55, Error) << "Failed to configure pipeline";
+ return ret;
+ }
+
+ /* Now apply the pixel format and size to the capture device. */
+ V4L2DeviceFormat captureFormat;
+ captureFormat.fourcc = pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat);
+ captureFormat.size = streamConfig.size;
+
+ ret = pipe->cap->setFormat(&captureFormat);
+ if (ret)
+ return ret;
+
+ pipe->stream = stream;
+ }
+
+ return 0;
+}
+
+int PipelineHandlerMaliC55::exportFrameBuffers(Camera *camera, Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+ MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);
+ unsigned int count = stream->configuration().bufferCount;
+
+ return pipe->cap->exportBuffers(count, buffers);
+}
+
+int PipelineHandlerMaliC55::start([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList *controls)
+{
+ for (MaliC55Pipe &pipe : pipes_) {
+ if (!pipe.stream)
+ continue;
+
+ Stream *stream = pipe.stream;
+
+ int ret = pipe.cap->importBuffers(stream->configuration().bufferCount);
+ if (ret) {
+ LOG(MaliC55, Error) << "Failed to import buffers";
+ return ret;
+ }
+
+ ret = pipe.cap->streamOn();
+ if (ret) {
+ LOG(MaliC55, Error) << "Failed to start stream";
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)
+{
+ for (MaliC55Pipe &pipe : pipes_) {
+ if (!pipe.stream)
+ continue;
+
+ pipe.cap->streamOff();
+ pipe.cap->releaseBuffers();
+ }
+}
+
+int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request)
+{
+ int ret;
+
+ for (auto &[stream, buffer] : request->buffers()) {
+ MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);
+
+ ret = pipe->cap->queueBuffer(buffer);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void PipelineHandlerMaliC55::bufferReady(FrameBuffer *buffer)
+{
+ Request *request = buffer->request();
+
+ completeBuffer(request, buffer);
+
+ if (request->hasPendingBuffers())
+ return;
+
+ completeRequest(request);
+}
+
+void PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,
+ const std::string &name)
+{
+ std::set<Stream *> streams{ &data->frStream_ };
+ if (dsFitted_)
+ streams.insert(&data->dsStream_);
+
+ std::shared_ptr<Camera> camera = Camera::create(std::move(data),
+ name, streams);
+ registerCamera(std::move(camera));
+}
+
+/*
+ * The only camera we support through direct connection to the ISP is the
+ * Mali-C55 TPG. Check we have that and warn if not.
+ */
+bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)
+{
+ const std::string &name = link->source()->entity()->name();
+ if (name != "mali-c55 tpg") {
+ LOG(MaliC55, Warning) << "Unsupported direct connection to "
+ << link->source()->entity()->name();
+ /*
+ * Return true and just skip registering a camera for this
+ * entity.
+ */
+ return true;
+ }
+
+ std::unique_ptr<MaliC55CameraData> data =
+ std::make_unique<MaliC55CameraData>(this, link->source()->entity());
+
+ if (data->init())
+ return false;
+
+ registerMaliCamera(std::move(data), name);
+
+ return true;
+}
+
+/*
+ * Register a Camera for each sensor connected to the ISP through a CSI-2
+ * receiver.
+ *
+ * \todo Support more complex topologies, such as video muxes.
+ */
+bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
+{
+ MediaEntity *csi2 = ispLink->source()->entity();
+ const MediaPad *csi2Sink = csi2->getPadByIndex(0);
+
+ for (MediaLink *link : csi2Sink->links()) {
+ MediaEntity *sensor = link->source()->entity();
+ unsigned int function = sensor->function();
+
+ if (function != MEDIA_ENT_F_CAM_SENSOR)
+ continue;
+
+ std::unique_ptr<MaliC55CameraData> data =
+ std::make_unique<MaliC55CameraData>(this, sensor);
+ if (data->init())
+ return false;
+
+ /* \todo: Init properties and controls. */
+
+ registerMaliCamera(std::move(data), sensor->name());
+ }
+
+ return true;
+}
+
+bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
+{
+ const MediaPad *ispSink;
+
+ /*
+ * We search for just the ISP subdevice and the full resolution pipe.
+ * The TPG and the downscale pipe are both optional blocks and may not
+ * be fitted.
+ */
+ DeviceMatch dm("mali-c55");
+ dm.add("mali-c55 isp");
+ dm.add("mali-c55 resizer fr");
+ dm.add("mali-c55 fr");
+
+ media_ = acquireMediaDevice(enumerator, dm);
+ if (!media_)
+ return false;
+
+ isp_ = V4L2Subdevice::fromEntityName(media_, "mali-c55 isp");
+ if (isp_->open() < 0)
+ return false;
+
+ MaliC55Pipe *frPipe = &pipes_[MaliC55FR];
+ frPipe->resizer = V4L2Subdevice::fromEntityName(media_, "mali-c55 resizer fr");
+ if (frPipe->resizer->open() < 0)
+ return false;
+
+ frPipe->cap = V4L2VideoDevice::fromEntityName(media_, "mali-c55 fr");
+ if (frPipe->cap->open() < 0)
+ return false;
+
+ frPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);
+
+ dsFitted_ = !!media_->getEntityByName("mali-c55 ds");
+ if (dsFitted_) {
+ LOG(MaliC55, Debug) << "Downscaler pipe is fitted";
+
+ MaliC55Pipe *dsPipe = &pipes_[MaliC55DS];
+
+ dsPipe->resizer = V4L2Subdevice::fromEntityName(media_, "mali-c55 resizer ds");
+ if (dsPipe->resizer->open() < 0)
+ return false;
+
+ dsPipe->cap = V4L2VideoDevice::fromEntityName(media_, "mali-c55 ds");
+ if (dsPipe->cap->open() < 0)
+ return false;
+
+ dsPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);
+ }
+
+ ispSink = isp_->entity()->getPadByIndex(0);
+ if (!ispSink || ispSink->links().empty()) {
+ LOG(MaliC55, Error) << "ISP sink pad error";
+ return false;
+ }
+
+ /*
+ * We could have several links pointing to the ISP's sink pad, which
+ * will be from entities with one of the following functions:
+ *
+ * MEDIA_ENT_F_CAM_SENSOR - The test pattern generator
+ * MEDIA_ENT_F_VID_IF_BRIDGE - A CSI-2 receiver
+ * MEDIA_ENT_F_IO_V4L - An input device
+ *
+ * The last one will be unsupported for now. The TPG is relatively easy,
+ * we just register a Camera for it. If we have a CSI-2 receiver we need
+ * to check its sink pad and register Cameras for anything connected to
+ * it (probably...there are some complex situations in which that might
+ * not be true but let's pretend they don't exist until we come across
+ * them)
+ */
+ bool registered;
+ for (MediaLink *link : ispSink->links()) {
+ unsigned int function = link->source()->entity()->function();
+
+ switch (function) {
+ case MEDIA_ENT_F_CAM_SENSOR:
+ registered = registerTPGCamera(link);
+ if (!registered)
+ return registered;
+
+ break;
+ case MEDIA_ENT_F_VID_IF_BRIDGE:
+ registered = registerSensorCamera(link);
+ if (!registered)
+ return registered;
+
+ break;
+ case MEDIA_ENT_F_IO_V4L:
+ LOG(MaliC55, Warning) << "Memory input not yet supported";
+ break;
+ default:
+ LOG(MaliC55, Error) << "Unsupported entity function";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerMaliC55)
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build
new file mode 100644
index 00000000..30fd29b9
--- /dev/null
+++ b/src/libcamera/pipeline/mali-c55/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+ 'mali-c55.cpp'
+])
diff --git a/src/libcamera/pipeline/meson.build b/src/libcamera/pipeline/meson.build
index 30dc5b97..8a61991c 100644
--- a/src/libcamera/pipeline/meson.build
+++ b/src/libcamera/pipeline/meson.build
@@ -1,5 +1,20 @@
# SPDX-License-Identifier: CC0-1.0
+# Location of pipeline specific configuration files
+pipeline_data_dir = libcamera_datadir / 'pipeline'
+
+# Allow multi-level directory structuring for the pipeline handlers if needed.
+subdirs = []
+
foreach pipeline : pipelines
+ pipeline = pipeline.split('/')[0]
+ if pipeline in subdirs
+ continue
+ endif
+
+ subdirs += pipeline
subdir(pipeline)
+
+ # Don't reuse the pipeline variable below, the subdirectory may have
+ # overwritten it.
endforeach
diff --git a/src/libcamera/pipeline/raspberrypi/dma_heaps.cpp b/src/libcamera/pipeline/raspberrypi/dma_heaps.cpp
deleted file mode 100644
index 69831dab..00000000
--- a/src/libcamera/pipeline/raspberrypi/dma_heaps.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
- *
- * dma_heaps.h - Helper class for dma-heap allocations.
- */
-
-#include "dma_heaps.h"
-
-#include <array>
-#include <fcntl.h>
-#include <linux/dma-buf.h>
-#include <linux/dma-heap.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include <libcamera/base/log.h>
-
-/*
- * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
- * to only have to worry about importing.
- *
- * Annoyingly, should the cma heap size be specified on the kernel command line
- * instead of DT, the heap gets named "reserved" instead.
- */
-static constexpr std::array<const char *, 2> heapNames = {
- "/dev/dma_heap/linux,cma",
- "/dev/dma_heap/reserved"
-};
-
-namespace libcamera {
-
-LOG_DECLARE_CATEGORY(RPI)
-
-namespace RPi {
-
-DmaHeap::DmaHeap()
-{
- for (const char *name : heapNames) {
- int ret = ::open(name, O_RDWR, 0);
- if (ret < 0) {
- ret = errno;
- LOG(RPI, Debug) << "Failed to open " << name << ": "
- << strerror(ret);
- continue;
- }
-
- dmaHeapHandle_ = UniqueFD(ret);
- break;
- }
-
- if (!dmaHeapHandle_.isValid())
- LOG(RPI, Error) << "Could not open any dmaHeap device";
-}
-
-DmaHeap::~DmaHeap() = default;
-
-UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
-{
- int ret;
-
- if (!name)
- return {};
-
- struct dma_heap_allocation_data alloc = {};
-
- alloc.len = size;
- alloc.fd_flags = O_CLOEXEC | O_RDWR;
-
- ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
- if (ret < 0) {
- LOG(RPI, Error) << "dmaHeap allocation failure for "
- << name;
- return {};
- }
-
- UniqueFD allocFd(alloc.fd);
- ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
- if (ret < 0) {
- LOG(RPI, Error) << "dmaHeap naming failure for "
- << name;
- return {};
- }
-
- return allocFd;
-}
-
-} /* namespace RPi */
-
-} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
deleted file mode 100644
index 66a84b1d..00000000
--- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
+++ /dev/null
@@ -1,2200 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi (Trading) Ltd.
- *
- * raspberrypi.cpp - Pipeline handler for Raspberry Pi devices
- */
-#include <algorithm>
-#include <assert.h>
-#include <cmath>
-#include <fcntl.h>
-#include <memory>
-#include <mutex>
-#include <queue>
-#include <unordered_set>
-#include <utility>
-
-#include <libcamera/base/shared_fd.h>
-#include <libcamera/base/utils.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/control_ids.h>
-#include <libcamera/formats.h>
-#include <libcamera/ipa/raspberrypi_ipa_interface.h>
-#include <libcamera/ipa/raspberrypi_ipa_proxy.h>
-#include <libcamera/logging.h>
-#include <libcamera/property_ids.h>
-#include <libcamera/request.h>
-
-#include <linux/bcm2835-isp.h>
-#include <linux/media-bus-format.h>
-#include <linux/videodev2.h>
-
-#include "libcamera/internal/bayer_format.h"
-#include "libcamera/internal/camera.h"
-#include "libcamera/internal/camera_sensor.h"
-#include "libcamera/internal/delayed_controls.h"
-#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/framebuffer.h"
-#include "libcamera/internal/ipa_manager.h"
-#include "libcamera/internal/media_device.h"
-#include "libcamera/internal/pipeline_handler.h"
-#include "libcamera/internal/v4l2_videodevice.h"
-
-#include "dma_heaps.h"
-#include "rpi_stream.h"
-
-using namespace std::chrono_literals;
-
-namespace libcamera {
-
-LOG_DEFINE_CATEGORY(RPI)
-
-namespace {
-
-constexpr unsigned int defaultRawBitDepth = 12;
-
-/* Map of mbus codes to supported sizes reported by the sensor. */
-using SensorFormats = std::map<unsigned int, std::vector<Size>>;
-
-SensorFormats populateSensorFormats(std::unique_ptr<CameraSensor> &sensor)
-{
- SensorFormats formats;
-
- for (auto const mbusCode : sensor->mbusCodes())
- formats.emplace(mbusCode, sensor->sizes(mbusCode));
-
- return formats;
-}
-
-PixelFormat mbusCodeToPixelFormat(unsigned int mbus_code,
- BayerFormat::Packing packingReq)
-{
- BayerFormat bayer = BayerFormat::fromMbusCode(mbus_code);
-
- ASSERT(bayer.isValid());
-
- bayer.packing = packingReq;
- PixelFormat pix = bayer.toPixelFormat();
-
- /*
- * Not all formats (e.g. 8-bit or 16-bit Bayer formats) can have packed
- * variants. So if the PixelFormat returns as invalid, use the non-packed
- * conversion instead.
- */
- if (!pix.isValid()) {
- bayer.packing = BayerFormat::Packing::None;
- pix = bayer.toPixelFormat();
- }
-
- return pix;
-}
-
-V4L2DeviceFormat toV4L2DeviceFormat(const V4L2SubdeviceFormat &format,
- BayerFormat::Packing packingReq)
-{
- const PixelFormat pix = mbusCodeToPixelFormat(format.mbus_code, packingReq);
- V4L2DeviceFormat deviceFormat;
-
- deviceFormat.fourcc = V4L2PixelFormat::fromPixelFormat(pix);
- deviceFormat.size = format.size;
- deviceFormat.colorSpace = format.colorSpace;
- return deviceFormat;
-}
-
-bool isRaw(const PixelFormat &pixFmt)
-{
- /*
- * The isRaw test might be redundant right now the pipeline handler only
- * supports RAW sensors. Leave it in for now, just as a sanity check.
- */
- if (!pixFmt.isValid())
- return false;
-
- const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
- if (!info.isValid())
- return false;
-
- return info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
-}
-
-double scoreFormat(double desired, double actual)
-{
- double score = desired - actual;
- /* Smaller desired dimensions are preferred. */
- if (score < 0.0)
- score = (-score) / 8;
- /* Penalise non-exact matches. */
- if (actual != desired)
- score *= 2;
-
- return score;
-}
-
-V4L2SubdeviceFormat findBestFormat(const SensorFormats &formatsMap, const Size &req, unsigned int bitDepth)
-{
- double bestScore = std::numeric_limits<double>::max(), score;
- V4L2SubdeviceFormat bestFormat;
- bestFormat.colorSpace = ColorSpace::Raw;
-
- constexpr float penaltyAr = 1500.0;
- constexpr float penaltyBitDepth = 500.0;
-
- /* Calculate the closest/best mode from the user requested size. */
- for (const auto &iter : formatsMap) {
- const unsigned int mbusCode = iter.first;
- const PixelFormat format = mbusCodeToPixelFormat(mbusCode,
- BayerFormat::Packing::None);
- const PixelFormatInfo &info = PixelFormatInfo::info(format);
-
- for (const Size &size : iter.second) {
- double reqAr = static_cast<double>(req.width) / req.height;
- double fmtAr = static_cast<double>(size.width) / size.height;
-
- /* Score the dimensions for closeness. */
- score = scoreFormat(req.width, size.width);
- score += scoreFormat(req.height, size.height);
- score += penaltyAr * scoreFormat(reqAr, fmtAr);
-
- /* Add any penalties... this is not an exact science! */
- score += utils::abs_diff(info.bitsPerPixel, bitDepth) * penaltyBitDepth;
-
- if (score <= bestScore) {
- bestScore = score;
- bestFormat.mbus_code = mbusCode;
- bestFormat.size = size;
- }
-
- LOG(RPI, Debug) << "Format: " << size
- << " fmt " << format
- << " Score: " << score
- << " (best " << bestScore << ")";
- }
- }
-
- return bestFormat;
-}
-
-enum class Unicam : unsigned int { Image, Embedded };
-enum class Isp : unsigned int { Input, Output0, Output1, Stats };
-
-} /* namespace */
-
-class RPiCameraData : public Camera::Private
-{
-public:
- RPiCameraData(PipelineHandler *pipe)
- : Camera::Private(pipe), state_(State::Stopped),
- supportsFlips_(false), flipsAlterBayerOrder_(false),
- dropFrameCount_(0), buffersAllocated_(false), ispOutputCount_(0)
- {
- }
-
- ~RPiCameraData()
- {
- freeBuffers();
- }
-
- void freeBuffers();
- void frameStarted(uint32_t sequence);
-
- int loadIPA(ipa::RPi::IPAInitResult *result);
- int configureIPA(const CameraConfiguration *config, ipa::RPi::IPAConfigResult *result);
-
- void enumerateVideoDevices(MediaLink *link);
-
- void statsMetadataComplete(uint32_t bufferId, const ControlList &controls);
- void runIsp(uint32_t bufferId);
- void embeddedComplete(uint32_t bufferId);
- void setIspControls(const ControlList &controls);
- void setDelayedControls(const ControlList &controls);
- void setSensorControls(ControlList &controls);
- void unicamTimeout();
-
- /* bufferComplete signal handlers. */
- void unicamBufferDequeue(FrameBuffer *buffer);
- void ispInputDequeue(FrameBuffer *buffer);
- void ispOutputDequeue(FrameBuffer *buffer);
-
- void clearIncompleteRequests();
- void handleStreamBuffer(FrameBuffer *buffer, RPi::Stream *stream);
- void handleExternalBuffer(FrameBuffer *buffer, RPi::Stream *stream);
- void handleState();
- Rectangle scaleIspCrop(const Rectangle &ispCrop) const;
- void applyScalerCrop(const ControlList &controls);
-
- std::unique_ptr<ipa::RPi::IPAProxyRPi> ipa_;
-
- std::unique_ptr<CameraSensor> sensor_;
- SensorFormats sensorFormats_;
- /* Array of Unicam and ISP device streams and associated buffers/streams. */
- RPi::Device<Unicam, 2> unicam_;
- RPi::Device<Isp, 4> isp_;
- /* The vector below is just for convenience when iterating over all streams. */
- std::vector<RPi::Stream *> streams_;
- /* Stores the ids of the buffers mapped in the IPA. */
- std::unordered_set<unsigned int> ipaBuffers_;
- /*
- * Stores a cascade of Video Mux or Bridge devices between the sensor and
- * Unicam together with media link across the entities.
- */
- std::vector<std::pair<std::unique_ptr<V4L2Subdevice>, MediaLink *>> bridgeDevices_;
-
- /* DMAHEAP allocation helper. */
- RPi::DmaHeap dmaHeap_;
- SharedFD lsTable_;
-
- std::unique_ptr<DelayedControls> delayedCtrls_;
- bool sensorMetadata_;
-
- /*
- * All the functions in this class are called from a single calling
- * thread. So, we do not need to have any mutex to protect access to any
- * of the variables below.
- */
- enum class State { Stopped, Idle, Busy, IpaComplete };
- State state_;
-
- struct BayerFrame {
- FrameBuffer *buffer;
- ControlList controls;
- };
-
- std::queue<BayerFrame> bayerQueue_;
- std::queue<FrameBuffer *> embeddedQueue_;
- std::deque<Request *> requestQueue_;
-
- /*
- * Manage horizontal and vertical flips supported (or not) by the
- * sensor. Also store the "native" Bayer order (that is, with no
- * transforms applied).
- */
- bool supportsFlips_;
- bool flipsAlterBayerOrder_;
- BayerFormat::Order nativeBayerOrder_;
-
- /* For handling digital zoom. */
- IPACameraSensorInfo sensorInfo_;
- Rectangle ispCrop_; /* crop in ISP (camera mode) pixels */
- Rectangle scalerCrop_; /* crop in sensor native pixels */
- Size ispMinCropSize_;
-
- unsigned int dropFrameCount_;
-
- /*
- * If set, this stores the value that represets a gain of one for
- * the V4L2_CID_NOTIFY_GAINS control.
- */
- std::optional<int32_t> notifyGainsUnity_;
-
- /* Have internal buffers been allocated? */
- bool buffersAllocated_;
-
-private:
- void checkRequestCompleted();
- void fillRequestMetadata(const ControlList &bufferControls,
- Request *request);
- void tryRunPipeline();
- bool findMatchingBuffers(BayerFrame &bayerFrame, FrameBuffer *&embeddedBuffer);
-
- unsigned int ispOutputCount_;
-};
-
-class RPiCameraConfiguration : public CameraConfiguration
-{
-public:
- RPiCameraConfiguration(const RPiCameraData *data);
-
- Status validate() override;
-
- /* Cache the combinedTransform_ that will be applied to the sensor */
- Transform combinedTransform_;
-
-private:
- const RPiCameraData *data_;
-};
-
-class PipelineHandlerRPi : public PipelineHandler
-{
-public:
- PipelineHandlerRPi(CameraManager *manager);
-
- CameraConfiguration *generateConfiguration(Camera *camera, const StreamRoles &roles) override;
- int configure(Camera *camera, CameraConfiguration *config) override;
-
- int exportFrameBuffers(Camera *camera, Stream *stream,
- std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
-
- int start(Camera *camera, const ControlList *controls) override;
- void stopDevice(Camera *camera) override;
-
- int queueRequestDevice(Camera *camera, Request *request) override;
-
- bool match(DeviceEnumerator *enumerator) override;
-
-private:
- RPiCameraData *cameraData(Camera *camera)
- {
- return static_cast<RPiCameraData *>(camera->_d());
- }
-
- int registerCamera(MediaDevice *unicam, MediaDevice *isp, MediaEntity *sensorEntity);
- int queueAllBuffers(Camera *camera);
- int prepareBuffers(Camera *camera);
- void mapBuffers(Camera *camera, const RPi::BufferMap &buffers, unsigned int mask);
-};
-
-RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
- : CameraConfiguration(), data_(data)
-{
-}
-
-CameraConfiguration::Status RPiCameraConfiguration::validate()
-{
- Status status = Valid;
-
- if (config_.empty())
- return Invalid;
-
- status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
-
- /*
- * What if the platform has a non-90 degree rotation? We can't even
- * "adjust" the configuration and carry on. Alternatively, raising an
- * error means the platform can never run. Let's just print a warning
- * and continue regardless; the rotation is effectively set to zero.
- */
- int32_t rotation = data_->sensor_->properties().get(properties::Rotation);
- bool success;
- Transform rotationTransform = transformFromRotation(rotation, &success);
- if (!success)
- LOG(RPI, Warning) << "Invalid rotation of " << rotation
- << " degrees - ignoring";
- Transform combined = transform * rotationTransform;
-
- /*
- * We combine the platform and user transform, but must "adjust away"
- * any combined result that includes a transform, as we can't do those.
- * In this case, flipping only the transpose bit is helpful to
- * applications - they either get the transform they requested, or have
- * to do a simple transpose themselves (they don't have to worry about
- * the other possible cases).
- */
- if (!!(combined & Transform::Transpose)) {
- /*
- * Flipping the transpose bit in "transform" flips it in the
- * combined result too (as it's the last thing that happens),
- * which is of course clearing it.
- */
- transform ^= Transform::Transpose;
- combined &= ~Transform::Transpose;
- status = Adjusted;
- }
-
- /*
- * We also check if the sensor doesn't do h/vflips at all, in which
- * case we clear them, and the application will have to do everything.
- */
- if (!data_->supportsFlips_ && !!combined) {
- /*
- * If the sensor can do no transforms, then combined must be
- * changed to the identity. The only user transform that gives
- * rise to this the inverse of the rotation. (Recall that
- * combined = transform * rotationTransform.)
- */
- transform = -rotationTransform;
- combined = Transform::Identity;
- status = Adjusted;
- }
-
- /*
- * Store the final combined transform that configure() will need to
- * apply to the sensor to save us working it out again.
- */
- combinedTransform_ = combined;
-
- unsigned int rawCount = 0, outCount = 0, count = 0, maxIndex = 0;
- std::pair<int, Size> outSize[2];
- Size maxSize;
- for (StreamConfiguration &cfg : config_) {
- if (isRaw(cfg.pixelFormat)) {
- /*
- * Calculate the best sensor mode we can use based on
- * the user request.
- */
- const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
- unsigned int bitDepth = info.isValid() ? info.bitsPerPixel : defaultRawBitDepth;
- V4L2SubdeviceFormat sensorFormat = findBestFormat(data_->sensorFormats_, cfg.size, bitDepth);
- BayerFormat::Packing packing = BayerFormat::Packing::CSI2;
- if (info.isValid() && !info.packed)
- packing = BayerFormat::Packing::None;
- V4L2DeviceFormat unicamFormat = toV4L2DeviceFormat(sensorFormat,
- packing);
- int ret = data_->unicam_[Unicam::Image].dev()->tryFormat(&unicamFormat);
- if (ret)
- return Invalid;
-
- /*
- * Some sensors change their Bayer order when they are
- * h-flipped or v-flipped, according to the transform.
- * If this one does, we must advertise the transformed
- * Bayer order in the raw stream. Note how we must
- * fetch the "native" (i.e. untransformed) Bayer order,
- * because the sensor may currently be flipped!
- */
- V4L2PixelFormat fourcc = unicamFormat.fourcc;
- if (data_->flipsAlterBayerOrder_) {
- BayerFormat bayer = BayerFormat::fromV4L2PixelFormat(fourcc);
- bayer.order = data_->nativeBayerOrder_;
- bayer = bayer.transform(combined);
- fourcc = bayer.toV4L2PixelFormat();
- }
-
- PixelFormat unicamPixFormat = fourcc.toPixelFormat();
- if (cfg.size != unicamFormat.size ||
- cfg.pixelFormat != unicamPixFormat) {
- cfg.size = unicamFormat.size;
- cfg.pixelFormat = unicamPixFormat;
- status = Adjusted;
- }
-
- cfg.stride = unicamFormat.planes[0].bpl;
- cfg.frameSize = unicamFormat.planes[0].size;
-
- rawCount++;
- } else {
- outSize[outCount] = std::make_pair(count, cfg.size);
- /* Record the largest resolution for fixups later. */
- if (maxSize < cfg.size) {
- maxSize = cfg.size;
- maxIndex = outCount;
- }
- outCount++;
- }
-
- count++;
-
- /* Can only output 1 RAW stream, or 2 YUV/RGB streams. */
- if (rawCount > 1 || outCount > 2) {
- LOG(RPI, Error) << "Invalid number of streams requested";
- return Invalid;
- }
- }
-
- /*
- * Now do any fixups needed. For the two ISP outputs, one stream must be
- * equal or smaller than the other in all dimensions.
- */
- for (unsigned int i = 0; i < outCount; i++) {
- outSize[i].second.width = std::min(outSize[i].second.width,
- maxSize.width);
- outSize[i].second.height = std::min(outSize[i].second.height,
- maxSize.height);
-
- if (config_.at(outSize[i].first).size != outSize[i].second) {
- config_.at(outSize[i].first).size = outSize[i].second;
- status = Adjusted;
- }
-
- /*
- * Also validate the correct pixel formats here.
- * Note that Output0 and Output1 support a different
- * set of formats.
- *
- * Output 0 must be for the largest resolution. We will
- * have that fixed up in the code above.
- *
- */
- StreamConfiguration &cfg = config_.at(outSize[i].first);
- PixelFormat &cfgPixFmt = cfg.pixelFormat;
- V4L2VideoDevice *dev;
-
- if (i == maxIndex)
- dev = data_->isp_[Isp::Output0].dev();
- else
- dev = data_->isp_[Isp::Output1].dev();
-
- V4L2VideoDevice::Formats fmts = dev->formats();
-
- if (fmts.find(V4L2PixelFormat::fromPixelFormat(cfgPixFmt)) == fmts.end()) {
- /* If we cannot find a native format, use a default one. */
- cfgPixFmt = formats::NV12;
- status = Adjusted;
- }
-
- V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
- format.size = cfg.size;
- format.colorSpace = cfg.colorSpace;
-
- LOG(RPI, Debug)
- << "Try color space " << ColorSpace::toString(cfg.colorSpace);
-
- int ret = dev->tryFormat(&format);
- if (ret)
- return Invalid;
-
- if (cfg.colorSpace != format.colorSpace) {
- status = Adjusted;
- LOG(RPI, Debug)
- << "Color space changed from "
- << ColorSpace::toString(cfg.colorSpace) << " to "
- << ColorSpace::toString(format.colorSpace);
- }
-
- cfg.colorSpace = format.colorSpace;
-
- cfg.stride = format.planes[0].bpl;
- cfg.frameSize = format.planes[0].size;
-
- }
-
- return status;
-}
-
-PipelineHandlerRPi::PipelineHandlerRPi(CameraManager *manager)
- : PipelineHandler(manager)
-{
-}
-
-CameraConfiguration *PipelineHandlerRPi::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
-{
- RPiCameraData *data = cameraData(camera);
- CameraConfiguration *config = new RPiCameraConfiguration(data);
- V4L2SubdeviceFormat sensorFormat;
- unsigned int bufferCount;
- PixelFormat pixelFormat;
- V4L2VideoDevice::Formats fmts;
- Size size;
- std::optional<ColorSpace> colorSpace;
-
- if (roles.empty())
- return config;
-
- unsigned int rawCount = 0;
- unsigned int outCount = 0;
- Size sensorSize = data->sensor_->resolution();
- for (const StreamRole role : roles) {
- switch (role) {
- case StreamRole::Raw:
- size = sensorSize;
- sensorFormat = findBestFormat(data->sensorFormats_, size, defaultRawBitDepth);
- pixelFormat = mbusCodeToPixelFormat(sensorFormat.mbus_code,
- BayerFormat::Packing::CSI2);
- ASSERT(pixelFormat.isValid());
- colorSpace = ColorSpace::Raw;
- bufferCount = 2;
- rawCount++;
- break;
-
- case StreamRole::StillCapture:
- fmts = data->isp_[Isp::Output0].dev()->formats();
- pixelFormat = formats::NV12;
- /*
- * Still image codecs usually expect the JPEG color space.
- * Even RGB codecs will be fine as the RGB we get with the
- * JPEG color space is the same as sRGB.
- */
- colorSpace = ColorSpace::Jpeg;
- /* Return the largest sensor resolution. */
- size = sensorSize;
- bufferCount = 1;
- outCount++;
- break;
-
- case StreamRole::VideoRecording:
- /*
- * The colour denoise algorithm requires the analysis
- * image, produced by the second ISP output, to be in
- * YUV420 format. Select this format as the default, to
- * maximize chances that it will be picked by
- * applications and enable usage of the colour denoise
- * algorithm.
- */
- fmts = data->isp_[Isp::Output0].dev()->formats();
- pixelFormat = formats::YUV420;
- /*
- * Choose a color space appropriate for video recording.
- * Rec.709 will be a good default for HD resolutions.
- */
- colorSpace = ColorSpace::Rec709;
- size = { 1920, 1080 };
- bufferCount = 4;
- outCount++;
- break;
-
- case StreamRole::Viewfinder:
- fmts = data->isp_[Isp::Output0].dev()->formats();
- pixelFormat = formats::ARGB8888;
- colorSpace = ColorSpace::Jpeg;
- size = { 800, 600 };
- bufferCount = 4;
- outCount++;
- break;
-
- default:
- LOG(RPI, Error) << "Requested stream role not supported: "
- << role;
- delete config;
- return nullptr;
- }
-
- if (rawCount > 1 || outCount > 2) {
- LOG(RPI, Error) << "Invalid stream roles requested";
- delete config;
- return nullptr;
- }
-
- std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
- if (role == StreamRole::Raw) {
- /* Translate the MBUS codes to a PixelFormat. */
- for (const auto &format : data->sensorFormats_) {
- PixelFormat pf = mbusCodeToPixelFormat(format.first,
- BayerFormat::Packing::CSI2);
- if (pf.isValid())
- deviceFormats.emplace(std::piecewise_construct, std::forward_as_tuple(pf),
- std::forward_as_tuple(format.second.begin(), format.second.end()));
- }
- } else {
- /*
- * Translate the V4L2PixelFormat to PixelFormat. Note that we
- * limit the recommended largest ISP output size to match the
- * sensor resolution.
- */
- for (const auto &format : fmts) {
- PixelFormat pf = format.first.toPixelFormat();
- if (pf.isValid()) {
- const SizeRange &ispSizes = format.second[0];
- deviceFormats[pf].emplace_back(ispSizes.min, sensorSize,
- ispSizes.hStep, ispSizes.vStep);
- }
- }
- }
-
- /* Add the stream format based on the device node used for the use case. */
- StreamFormats formats(deviceFormats);
- StreamConfiguration cfg(formats);
- cfg.size = size;
- cfg.pixelFormat = pixelFormat;
- cfg.colorSpace = colorSpace;
- cfg.bufferCount = bufferCount;
- config->addConfiguration(cfg);
- }
-
- config->validate();
-
- return config;
-}
-
-int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)
-{
- RPiCameraData *data = cameraData(camera);
- int ret;
-
- /* Start by freeing all buffers and reset the Unicam and ISP stream states. */
- data->freeBuffers();
- for (auto const stream : data->streams_)
- stream->setExternal(false);
-
- BayerFormat::Packing packing = BayerFormat::Packing::CSI2;
- Size maxSize, sensorSize;
- unsigned int maxIndex = 0;
- bool rawStream = false;
- unsigned int bitDepth = defaultRawBitDepth;
-
- /*
- * Look for the RAW stream (if given) size as well as the largest
- * ISP output size.
- */
- for (unsigned i = 0; i < config->size(); i++) {
- StreamConfiguration &cfg = config->at(i);
-
- if (isRaw(cfg.pixelFormat)) {
- /*
- * If we have been given a RAW stream, use that size
- * for setting up the sensor.
- */
- sensorSize = cfg.size;
- rawStream = true;
- /* Check if the user has explicitly set an unpacked format. */
- BayerFormat bayerFormat = BayerFormat::fromPixelFormat(cfg.pixelFormat);
- packing = bayerFormat.packing;
- bitDepth = bayerFormat.bitDepth;
- } else {
- if (cfg.size > maxSize) {
- maxSize = config->at(i).size;
- maxIndex = i;
- }
- }
- }
-
- /*
- * Configure the H/V flip controls based on the combination of
- * the sensor and user transform.
- */
- if (data->supportsFlips_) {
- const RPiCameraConfiguration *rpiConfig =
- static_cast<const RPiCameraConfiguration *>(config);
- ControlList controls;
-
- controls.set(V4L2_CID_HFLIP,
- static_cast<int32_t>(!!(rpiConfig->combinedTransform_ & Transform::HFlip)));
- controls.set(V4L2_CID_VFLIP,
- static_cast<int32_t>(!!(rpiConfig->combinedTransform_ & Transform::VFlip)));
- data->setSensorControls(controls);
- }
-
- /* First calculate the best sensor mode we can use based on the user request. */
- V4L2SubdeviceFormat sensorFormat = findBestFormat(data->sensorFormats_, rawStream ? sensorSize : maxSize, bitDepth);
- ret = data->sensor_->setFormat(&sensorFormat);
- if (ret)
- return ret;
-
- V4L2DeviceFormat unicamFormat = toV4L2DeviceFormat(sensorFormat, packing);
- ret = data->unicam_[Unicam::Image].dev()->setFormat(&unicamFormat);
- if (ret)
- return ret;
-
- LOG(RPI, Info) << "Sensor: " << camera->id()
- << " - Selected sensor format: " << sensorFormat
- << " - Selected unicam format: " << unicamFormat;
-
- ret = data->isp_[Isp::Input].dev()->setFormat(&unicamFormat);
- if (ret)
- return ret;
-
- /*
- * See which streams are requested, and route the user
- * StreamConfiguration appropriately.
- */
- V4L2DeviceFormat format;
- bool output0Set = false, output1Set = false;
- for (unsigned i = 0; i < config->size(); i++) {
- StreamConfiguration &cfg = config->at(i);
-
- if (isRaw(cfg.pixelFormat)) {
- cfg.setStream(&data->unicam_[Unicam::Image]);
- data->unicam_[Unicam::Image].setExternal(true);
- continue;
- }
-
- /* The largest resolution gets routed to the ISP Output 0 node. */
- RPi::Stream *stream = i == maxIndex ? &data->isp_[Isp::Output0]
- : &data->isp_[Isp::Output1];
-
- V4L2PixelFormat fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
- format.size = cfg.size;
- format.fourcc = fourcc;
- format.colorSpace = cfg.colorSpace;
-
- LOG(RPI, Debug) << "Setting " << stream->name() << " to "
- << format;
-
- ret = stream->dev()->setFormat(&format);
- if (ret)
- return -EINVAL;
-
- if (format.size != cfg.size || format.fourcc != fourcc) {
- LOG(RPI, Error)
- << "Failed to set requested format on " << stream->name()
- << ", returned " << format;
- return -EINVAL;
- }
-
- LOG(RPI, Debug)
- << "Stream " << stream->name() << " has color space "
- << ColorSpace::toString(cfg.colorSpace);
-
- cfg.setStream(stream);
- stream->setExternal(true);
-
- if (i != maxIndex)
- output1Set = true;
- else
- output0Set = true;
- }
-
- /*
- * If ISP::Output0 stream has not been configured by the application,
- * we must allow the hardware to generate an output so that the data
- * flow in the pipeline handler remains consistent, and we still generate
- * statistics for the IPA to use. So enable the output at a very low
- * resolution for internal use.
- *
- * \todo Allow the pipeline to work correctly without Output0 and only
- * statistics coming from the hardware.
- */
- if (!output0Set) {
- maxSize = Size(320, 240);
- format = {};
- format.size = maxSize;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(formats::YUV420);
- /* No one asked for output, so the color space doesn't matter. */
- format.colorSpace = ColorSpace::Jpeg;
- ret = data->isp_[Isp::Output0].dev()->setFormat(&format);
- if (ret) {
- LOG(RPI, Error)
- << "Failed to set default format on ISP Output0: "
- << ret;
- return -EINVAL;
- }
-
- LOG(RPI, Debug) << "Defaulting ISP Output0 format to "
- << format;
- }
-
- /*
- * If ISP::Output1 stream has not been requested by the application, we
- * set it up for internal use now. This second stream will be used for
- * fast colour denoise, and must be a quarter resolution of the ISP::Output0
- * stream. However, also limit the maximum size to 1200 pixels in the
- * larger dimension, just to avoid being wasteful with buffer allocations
- * and memory bandwidth.
- *
- * \todo If Output 1 format is not YUV420, Output 1 ought to be disabled as
- * colour denoise will not run.
- */
- if (!output1Set) {
- V4L2DeviceFormat output1Format;
- constexpr Size maxDimensions(1200, 1200);
- const Size limit = maxDimensions.boundedToAspectRatio(format.size);
-
- output1Format.size = (format.size / 2).boundedTo(limit).alignedDownTo(2, 2);
- output1Format.colorSpace = format.colorSpace;
- output1Format.fourcc = V4L2PixelFormat::fromPixelFormat(formats::YUV420);
-
- LOG(RPI, Debug) << "Setting ISP Output1 (internal) to "
- << output1Format;
-
- ret = data->isp_[Isp::Output1].dev()->setFormat(&output1Format);
- if (ret) {
- LOG(RPI, Error) << "Failed to set format on ISP Output1: "
- << ret;
- return -EINVAL;
- }
- }
-
- /* ISP statistics output format. */
- format = {};
- format.fourcc = V4L2PixelFormat(V4L2_META_FMT_BCM2835_ISP_STATS);
- ret = data->isp_[Isp::Stats].dev()->setFormat(&format);
- if (ret) {
- LOG(RPI, Error) << "Failed to set format on ISP stats stream: "
- << format;
- return ret;
- }
-
- /* Figure out the smallest selection the ISP will allow. */
- Rectangle testCrop(0, 0, 1, 1);
- data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop);
- data->ispMinCropSize_ = testCrop.size();
-
- /* Adjust aspect ratio by providing crops on the input image. */
- Size size = unicamFormat.size.boundedToAspectRatio(maxSize);
- Rectangle crop = size.centeredTo(Rectangle(unicamFormat.size).center());
- data->ispCrop_ = crop;
-
- data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop);
-
- ipa::RPi::IPAConfigResult result;
- ret = data->configureIPA(config, &result);
- if (ret)
- LOG(RPI, Error) << "Failed to configure the IPA: " << ret;
-
- /*
- * Set the scaler crop to the value we are using (scaled to native sensor
- * coordinates).
- */
- data->scalerCrop_ = data->scaleIspCrop(data->ispCrop_);
-
- /*
- * Configure the Unicam embedded data output format only if the sensor
- * supports it.
- */
- if (data->sensorMetadata_) {
- V4L2SubdeviceFormat embeddedFormat;
-
- data->sensor_->device()->getFormat(1, &embeddedFormat);
- format.fourcc = V4L2PixelFormat(V4L2_META_FMT_SENSOR_DATA);
- format.planes[0].size = embeddedFormat.size.width * embeddedFormat.size.height;
-
- LOG(RPI, Debug) << "Setting embedded data format.";
- ret = data->unicam_[Unicam::Embedded].dev()->setFormat(&format);
- if (ret) {
- LOG(RPI, Error) << "Failed to set format on Unicam embedded: "
- << format;
- return ret;
- }
- }
-
- /*
- * Update the ScalerCropMaximum to the correct value for this camera mode.
- * For us, it's the same as the "analogue crop".
- *
- * \todo Make this property the ScalerCrop maximum value when dynamic
- * controls are available and set it at validate() time
- */
- data->properties_.set(properties::ScalerCropMaximum, data->sensorInfo_.analogCrop);
-
- /* Store the mode sensitivity for the application. */
- data->properties_.set(properties::SensorSensitivity, result.modeSensitivity);
-
- /* Update the controls that the Raspberry Pi IPA can handle. */
- ControlInfoMap::Map ctrlMap;
- for (auto const &c : result.controlInfo)
- ctrlMap.emplace(c.first, c.second);
-
- /* Add the ScalerCrop control limits based on the current mode. */
- ctrlMap.emplace(&controls::ScalerCrop,
- ControlInfo(Rectangle(data->ispMinCropSize_), Rectangle(data->sensorInfo_.outputSize)));
-
- data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap());
-
- /* Setup the Video Mux/Bridge entities. */
- for (auto &[device, link] : data->bridgeDevices_) {
- /*
- * Start by disabling all the sink pad links on the devices in the
- * cascade, with the exception of the link connecting the device.
- */
- for (const MediaPad *p : device->entity()->pads()) {
- if (!(p->flags() & MEDIA_PAD_FL_SINK))
- continue;
-
- for (MediaLink *l : p->links()) {
- if (l != link)
- l->setEnabled(false);
- }
- }
-
- /*
- * Next, enable the entity -> entity links, and setup the pad format.
- *
- * \todo Some bridge devices may chainge the media bus code, so we
- * ought to read the source pad format and propagate it to the sink pad.
- */
- link->setEnabled(true);
- const MediaPad *sinkPad = link->sink();
- ret = device->setFormat(sinkPad->index(), &sensorFormat);
- if (ret) {
- LOG(RPI, Error) << "Failed to set format on " << device->entity()->name()
- << " pad " << sinkPad->index()
- << " with format " << format
- << ": " << ret;
- return ret;
- }
-
- LOG(RPI, Debug) << "Configured media link on device " << device->entity()->name()
- << " on pad " << sinkPad->index();
- }
-
- return ret;
-}
-
-int PipelineHandlerRPi::exportFrameBuffers([[maybe_unused]] Camera *camera, Stream *stream,
- std::vector<std::unique_ptr<FrameBuffer>> *buffers)
-{
- RPi::Stream *s = static_cast<RPi::Stream *>(stream);
- unsigned int count = stream->configuration().bufferCount;
- int ret = s->dev()->exportBuffers(count, buffers);
-
- s->setExportedBuffers(buffers);
-
- return ret;
-}
-
-int PipelineHandlerRPi::start(Camera *camera, const ControlList *controls)
-{
- RPiCameraData *data = cameraData(camera);
- int ret;
-
- for (auto const stream : data->streams_)
- stream->resetBuffers();
-
- if (!data->buffersAllocated_) {
- /* Allocate buffers for internal pipeline usage. */
- ret = prepareBuffers(camera);
- if (ret) {
- LOG(RPI, Error) << "Failed to allocate buffers";
- data->freeBuffers();
- stop(camera);
- return ret;
- }
- data->buffersAllocated_ = true;
- }
-
- /* Check if a ScalerCrop control was specified. */
- if (controls)
- data->applyScalerCrop(*controls);
-
- /* Start the IPA. */
- ipa::RPi::StartConfig startConfig;
- data->ipa_->start(controls ? *controls : ControlList{ controls::controls },
- &startConfig);
-
- /* Apply any gain/exposure settings that the IPA may have passed back. */
- if (!startConfig.controls.empty())
- data->setSensorControls(startConfig.controls);
-
- /* Configure the number of dropped frames required on startup. */
- data->dropFrameCount_ = startConfig.dropFrameCount;
-
- /* We need to set the dropFrameCount_ before queueing buffers. */
- ret = queueAllBuffers(camera);
- if (ret) {
- LOG(RPI, Error) << "Failed to queue buffers";
- stop(camera);
- return ret;
- }
-
- /* Enable SOF event generation. */
- data->unicam_[Unicam::Image].dev()->setFrameStartEnabled(true);
-
- /*
- * Reset the delayed controls with the gain and exposure values set by
- * the IPA.
- */
- data->delayedCtrls_->reset();
-
- data->state_ = RPiCameraData::State::Idle;
-
- /* Start all streams. */
- for (auto const stream : data->streams_) {
- ret = stream->dev()->streamOn();
- if (ret) {
- stop(camera);
- return ret;
- }
- }
-
- /*
- * Set the dequeue timeout to the larger of 2x the maximum possible
- * frame duration or 1 second.
- */
- utils::Duration timeout =
- std::max<utils::Duration>(1s, 2 * startConfig.maxSensorFrameLengthMs * 1ms);
- data->unicam_[Unicam::Image].dev()->setDequeueTimeout(timeout);
-
- return 0;
-}
-
-void PipelineHandlerRPi::stopDevice(Camera *camera)
-{
- RPiCameraData *data = cameraData(camera);
-
- data->state_ = RPiCameraData::State::Stopped;
-
- /* Disable SOF event generation. */
- data->unicam_[Unicam::Image].dev()->setFrameStartEnabled(false);
-
- for (auto const stream : data->streams_)
- stream->dev()->streamOff();
-
- data->clearIncompleteRequests();
- data->bayerQueue_ = {};
- data->embeddedQueue_ = {};
-
- /* Stop the IPA. */
- data->ipa_->stop();
-}
-
-int PipelineHandlerRPi::queueRequestDevice(Camera *camera, Request *request)
-{
- RPiCameraData *data = cameraData(camera);
-
- if (data->state_ == RPiCameraData::State::Stopped)
- return -EINVAL;
-
- LOG(RPI, Debug) << "queueRequestDevice: New request.";
-
- /* Push all buffers supplied in the Request to the respective streams. */
- for (auto stream : data->streams_) {
- if (!stream->isExternal())
- continue;
-
- FrameBuffer *buffer = request->findBuffer(stream);
- if (buffer && stream->getBufferId(buffer) == -1) {
- /*
- * This buffer is not recognised, so it must have been allocated
- * outside the v4l2 device. Store it in the stream buffer list
- * so we can track it.
- */
- stream->setExternalBuffer(buffer);
- }
- /*
- * If no buffer is provided by the request for this stream, we
- * queue a nullptr to the stream to signify that it must use an
- * internally allocated buffer for this capture request. This
- * buffer will not be given back to the application, but is used
- * to support the internal pipeline flow.
- *
- * The below queueBuffer() call will do nothing if there are not
- * enough internal buffers allocated, but this will be handled by
- * queuing the request for buffers in the RPiStream object.
- */
- int ret = stream->queueBuffer(buffer);
- if (ret)
- return ret;
- }
-
- /* Push the request to the back of the queue. */
- data->requestQueue_.push_back(request);
- data->handleState();
-
- return 0;
-}
-
-bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)
-{
- DeviceMatch unicam("unicam");
- MediaDevice *unicamDevice = acquireMediaDevice(enumerator, unicam);
-
- if (!unicamDevice) {
- LOG(RPI, Debug) << "Unable to acquire a Unicam instance";
- return false;
- }
-
- DeviceMatch isp("bcm2835-isp");
- MediaDevice *ispDevice = acquireMediaDevice(enumerator, isp);
-
- if (!ispDevice) {
- LOG(RPI, Debug) << "Unable to acquire ISP instance";
- return false;
- }
-
- /*
- * The loop below is used to register multiple cameras behind one or more
- * video mux devices that are attached to a particular Unicam instance.
- * Obviously these cameras cannot be used simultaneously.
- */
- unsigned int numCameras = 0;
- for (MediaEntity *entity : unicamDevice->entities()) {
- if (entity->function() != MEDIA_ENT_F_CAM_SENSOR)
- continue;
-
- int ret = registerCamera(unicamDevice, ispDevice, entity);
- if (ret)
- LOG(RPI, Error) << "Failed to register camera "
- << entity->name() << ": " << ret;
- else
- numCameras++;
- }
-
- return !!numCameras;
-}
-
-int PipelineHandlerRPi::registerCamera(MediaDevice *unicam, MediaDevice *isp, MediaEntity *sensorEntity)
-{
- std::unique_ptr<RPiCameraData> data = std::make_unique<RPiCameraData>(this);
-
- if (!data->dmaHeap_.isValid())
- return -ENOMEM;
-
- MediaEntity *unicamImage = unicam->getEntityByName("unicam-image");
- MediaEntity *ispOutput0 = isp->getEntityByName("bcm2835-isp0-output0");
- MediaEntity *ispCapture1 = isp->getEntityByName("bcm2835-isp0-capture1");
- MediaEntity *ispCapture2 = isp->getEntityByName("bcm2835-isp0-capture2");
- MediaEntity *ispCapture3 = isp->getEntityByName("bcm2835-isp0-capture3");
-
- if (!unicamImage || !ispOutput0 || !ispCapture1 || !ispCapture2 || !ispCapture3)
- return -ENOENT;
-
- /* Locate and open the unicam video streams. */
- data->unicam_[Unicam::Image] = RPi::Stream("Unicam Image", unicamImage);
-
- /* An embedded data node will not be present if the sensor does not support it. */
- MediaEntity *unicamEmbedded = unicam->getEntityByName("unicam-embedded");
- if (unicamEmbedded) {
- data->unicam_[Unicam::Embedded] = RPi::Stream("Unicam Embedded", unicamEmbedded);
- data->unicam_[Unicam::Embedded].dev()->bufferReady.connect(data.get(),
- &RPiCameraData::unicamBufferDequeue);
- }
-
- /* Tag the ISP input stream as an import stream. */
- data->isp_[Isp::Input] = RPi::Stream("ISP Input", ispOutput0, true);
- data->isp_[Isp::Output0] = RPi::Stream("ISP Output0", ispCapture1);
- data->isp_[Isp::Output1] = RPi::Stream("ISP Output1", ispCapture2);
- data->isp_[Isp::Stats] = RPi::Stream("ISP Stats", ispCapture3);
-
- /* Wire up all the buffer connections. */
- data->unicam_[Unicam::Image].dev()->dequeueTimeout.connect(data.get(), &RPiCameraData::unicamTimeout);
- data->unicam_[Unicam::Image].dev()->frameStart.connect(data.get(), &RPiCameraData::frameStarted);
- data->unicam_[Unicam::Image].dev()->bufferReady.connect(data.get(), &RPiCameraData::unicamBufferDequeue);
- data->isp_[Isp::Input].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispInputDequeue);
- data->isp_[Isp::Output0].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputDequeue);
- data->isp_[Isp::Output1].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputDequeue);
- data->isp_[Isp::Stats].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputDequeue);
-
- data->sensor_ = std::make_unique<CameraSensor>(sensorEntity);
- if (!data->sensor_)
- return -EINVAL;
-
- if (data->sensor_->init())
- return -EINVAL;
-
- /*
- * Enumerate all the Video Mux/Bridge devices across the sensor -> unicam
- * chain. There may be a cascade of devices in this chain!
- */
- MediaLink *link = sensorEntity->getPadByIndex(0)->links()[0];
- data->enumerateVideoDevices(link);
-
- data->sensorFormats_ = populateSensorFormats(data->sensor_);
-
- ipa::RPi::IPAInitResult result;
- if (data->loadIPA(&result)) {
- LOG(RPI, Error) << "Failed to load a suitable IPA library";
- return -EINVAL;
- }
-
- if (result.sensorConfig.sensorMetadata ^ !!unicamEmbedded) {
- LOG(RPI, Warning) << "Mismatch between Unicam and CamHelper for embedded data usage!";
- result.sensorConfig.sensorMetadata = false;
- if (unicamEmbedded)
- data->unicam_[Unicam::Embedded].dev()->bufferReady.disconnect();
- }
-
- /*
- * Open all Unicam and ISP streams. The exception is the embedded data
- * stream, which only gets opened below if the IPA reports that the sensor
- * supports embedded data.
- *
- * The below grouping is just for convenience so that we can easily
- * iterate over all streams in one go.
- */
- data->streams_.push_back(&data->unicam_[Unicam::Image]);
- if (result.sensorConfig.sensorMetadata)
- data->streams_.push_back(&data->unicam_[Unicam::Embedded]);
-
- for (auto &stream : data->isp_)
- data->streams_.push_back(&stream);
-
- for (auto stream : data->streams_) {
- int ret = stream->dev()->open();
- if (ret)
- return ret;
- }
-
- if (!data->unicam_[Unicam::Image].dev()->caps().hasMediaController()) {
- LOG(RPI, Error) << "Unicam driver does not use the MediaController, please update your kernel!";
- return -EINVAL;
- }
-
- /*
- * Setup our delayed control writer with the sensor default
- * gain and exposure delays. Mark VBLANK for priority write.
- */
- std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
- { V4L2_CID_ANALOGUE_GAIN, { result.sensorConfig.gainDelay, false } },
- { V4L2_CID_EXPOSURE, { result.sensorConfig.exposureDelay, false } },
- { V4L2_CID_VBLANK, { result.sensorConfig.vblankDelay, true } }
- };
- data->delayedCtrls_ = std::make_unique<DelayedControls>(data->sensor_->device(), params);
- data->sensorMetadata_ = result.sensorConfig.sensorMetadata;
-
- /* Register initial controls that the Raspberry Pi IPA can handle. */
- data->controlInfo_ = std::move(result.controlInfo);
-
- /* Initialize the camera properties. */
- data->properties_ = data->sensor_->properties();
-
- /*
- * The V4L2_CID_NOTIFY_GAINS control, if present, is used to inform the
- * sensor of the colour gains. It is defined to be a linear gain where
- * the default value represents a gain of exactly one.
- */
- auto it = data->sensor_->controls().find(V4L2_CID_NOTIFY_GAINS);
- if (it != data->sensor_->controls().end())
- data->notifyGainsUnity_ = it->second.def().get<int32_t>();
-
- /*
- * Set a default value for the ScalerCropMaximum property to show
- * that we support its use, however, initialise it to zero because
- * it's not meaningful until a camera mode has been chosen.
- */
- data->properties_.set(properties::ScalerCropMaximum, Rectangle{});
-
- /*
- * We cache three things about the sensor in relation to transforms
- * (meaning horizontal and vertical flips).
- *
- * Firstly, does it support them?
- * Secondly, if you use them does it affect the Bayer ordering?
- * Thirdly, what is the "native" Bayer order, when no transforms are
- * applied?
- *
- * We note that the sensor's cached list of supported formats is
- * already in the "native" order, with any flips having been undone.
- */
- const V4L2Subdevice *sensor = data->sensor_->device();
- const struct v4l2_query_ext_ctrl *hflipCtrl = sensor->controlInfo(V4L2_CID_HFLIP);
- if (hflipCtrl) {
- /* We assume it will support vflips too... */
- data->supportsFlips_ = true;
- data->flipsAlterBayerOrder_ = hflipCtrl->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT;
- }
-
- /* Look for a valid Bayer format. */
- BayerFormat bayerFormat;
- for (const auto &iter : data->sensorFormats_) {
- bayerFormat = BayerFormat::fromMbusCode(iter.first);
- if (bayerFormat.isValid())
- break;
- }
-
- if (!bayerFormat.isValid()) {
- LOG(RPI, Error) << "No Bayer format found";
- return -EINVAL;
- }
- data->nativeBayerOrder_ = bayerFormat.order;
-
- /*
- * List the available streams an application may request. At present, we
- * do not advertise Unicam Embedded and ISP Statistics streams, as there
- * is no mechanism for the application to request non-image buffer formats.
- */
- std::set<Stream *> streams;
- streams.insert(&data->unicam_[Unicam::Image]);
- streams.insert(&data->isp_[Isp::Output0]);
- streams.insert(&data->isp_[Isp::Output1]);
-
- /* Create and register the camera. */
- const std::string &id = data->sensor_->id();
- std::shared_ptr<Camera> camera =
- Camera::create(std::move(data), id, streams);
- PipelineHandler::registerCamera(std::move(camera));
-
- LOG(RPI, Info) << "Registered camera " << id
- << " to Unicam device " << unicam->deviceNode()
- << " and ISP device " << isp->deviceNode();
- return 0;
-}
-
-int PipelineHandlerRPi::queueAllBuffers(Camera *camera)
-{
- RPiCameraData *data = cameraData(camera);
- int ret;
-
- for (auto const stream : data->streams_) {
- if (!stream->isExternal()) {
- ret = stream->queueAllBuffers();
- if (ret < 0)
- return ret;
- } else {
- /*
- * For external streams, we must queue up a set of internal
- * buffers to handle the number of drop frames requested by
- * the IPA. This is done by passing nullptr in queueBuffer().
- *
- * The below queueBuffer() call will do nothing if there
- * are not enough internal buffers allocated, but this will
- * be handled by queuing the request for buffers in the
- * RPiStream object.
- */
- unsigned int i;
- for (i = 0; i < data->dropFrameCount_; i++) {
- ret = stream->queueBuffer(nullptr);
- if (ret)
- return ret;
- }
- }
- }
-
- return 0;
-}
-
-int PipelineHandlerRPi::prepareBuffers(Camera *camera)
-{
- RPiCameraData *data = cameraData(camera);
- unsigned int numRawBuffers = 0;
- int ret;
-
- for (Stream *s : camera->streams()) {
- if (isRaw(s->configuration().pixelFormat)) {
- numRawBuffers = s->configuration().bufferCount;
- break;
- }
- }
-
- /* Decide how many internal buffers to allocate. */
- for (auto const stream : data->streams_) {
- unsigned int numBuffers;
- /*
- * For Unicam, allocate a minimum of 4 buffers as we want
- * to avoid any frame drops.
- */
- constexpr unsigned int minBuffers = 4;
- if (stream == &data->unicam_[Unicam::Image]) {
- /*
- * If an application has configured a RAW stream, allocate
- * additional buffers to make up the minimum, but ensure
- * we have at least 2 sets of internal buffers to use to
- * minimise frame drops.
- */
- numBuffers = std::max<int>(2, minBuffers - numRawBuffers);
- } else if (stream == &data->isp_[Isp::Input]) {
- /*
- * ISP input buffers are imported from Unicam, so follow
- * similar logic as above to count all the RAW buffers
- * available.
- */
- numBuffers = numRawBuffers + std::max<int>(2, minBuffers - numRawBuffers);
-
- } else if (stream == &data->unicam_[Unicam::Embedded]) {
- /*
- * Embedded data buffers are (currently) for internal use,
- * so allocate the minimum required to avoid frame drops.
- */
- numBuffers = minBuffers;
- } else {
- /*
- * Since the ISP runs synchronous with the IPA and requests,
- * we only ever need one set of internal buffers. Any buffers
- * the application wants to hold onto will already be exported
- * through PipelineHandlerRPi::exportFrameBuffers().
- */
- numBuffers = 1;
- }
-
- ret = stream->prepareBuffers(numBuffers);
- if (ret < 0)
- return ret;
- }
-
- /*
- * Pass the stats and embedded data buffers to the IPA. No other
- * buffers need to be passed.
- */
- mapBuffers(camera, data->isp_[Isp::Stats].getBuffers(), ipa::RPi::MaskStats);
- if (data->sensorMetadata_)
- mapBuffers(camera, data->unicam_[Unicam::Embedded].getBuffers(),
- ipa::RPi::MaskEmbeddedData);
-
- return 0;
-}
-
-void PipelineHandlerRPi::mapBuffers(Camera *camera, const RPi::BufferMap &buffers, unsigned int mask)
-{
- RPiCameraData *data = cameraData(camera);
- std::vector<IPABuffer> ipaBuffers;
- /*
- * Link the FrameBuffers with the id (key value) in the map stored in
- * the RPi stream object - along with an identifier mask.
- *
- * This will allow us to identify buffers passed between the pipeline
- * handler and the IPA.
- */
- for (auto const &it : buffers) {
- ipaBuffers.push_back(IPABuffer(mask | it.first,
- it.second->planes()));
- data->ipaBuffers_.insert(mask | it.first);
- }
-
- data->ipa_->mapBuffers(ipaBuffers);
-}
-
-void RPiCameraData::freeBuffers()
-{
- if (ipa_) {
- /*
- * Copy the buffer ids from the unordered_set to a vector to
- * pass to the IPA.
- */
- std::vector<unsigned int> ipaBuffers(ipaBuffers_.begin(),
- ipaBuffers_.end());
- ipa_->unmapBuffers(ipaBuffers);
- ipaBuffers_.clear();
- }
-
- for (auto const stream : streams_)
- stream->releaseBuffers();
-
- buffersAllocated_ = false;
-}
-
-void RPiCameraData::frameStarted(uint32_t sequence)
-{
- LOG(RPI, Debug) << "frame start " << sequence;
-
- /* Write any controls for the next frame as soon as we can. */
- delayedCtrls_->applyControls(sequence);
-}
-
-int RPiCameraData::loadIPA(ipa::RPi::IPAInitResult *result)
-{
- ipa_ = IPAManager::createIPA<ipa::RPi::IPAProxyRPi>(pipe(), 1, 1);
-
- if (!ipa_)
- return -ENOENT;
-
- ipa_->statsMetadataComplete.connect(this, &RPiCameraData::statsMetadataComplete);
- ipa_->runIsp.connect(this, &RPiCameraData::runIsp);
- ipa_->embeddedComplete.connect(this, &RPiCameraData::embeddedComplete);
- ipa_->setIspControls.connect(this, &RPiCameraData::setIspControls);
- ipa_->setDelayedControls.connect(this, &RPiCameraData::setDelayedControls);
-
- /*
- * The configuration (tuning file) is made from the sensor name unless
- * the environment variable overrides it.
- */
- std::string configurationFile;
- char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RPI_TUNING_FILE");
- if (!configFromEnv || *configFromEnv == '\0')
- configurationFile = ipa_->configurationFile(sensor_->model() + ".json");
- else
- configurationFile = std::string(configFromEnv);
-
- IPASettings settings(configurationFile, sensor_->model());
-
- return ipa_->init(settings, result);
-}
-
-int RPiCameraData::configureIPA(const CameraConfiguration *config, ipa::RPi::IPAConfigResult *result)
-{
- std::map<unsigned int, IPAStream> streamConfig;
- std::map<unsigned int, ControlInfoMap> entityControls;
- ipa::RPi::IPAConfig ipaConfig;
-
- /* Inform IPA of stream configuration and sensor controls. */
- unsigned int i = 0;
- for (auto const &stream : isp_) {
- if (stream.isExternal()) {
- streamConfig[i++] = IPAStream(
- stream.configuration().pixelFormat,
- stream.configuration().size);
- }
- }
-
- entityControls.emplace(0, sensor_->controls());
- entityControls.emplace(1, isp_[Isp::Input].dev()->controls());
-
- /* Always send the user transform to the IPA. */
- ipaConfig.transform = static_cast<unsigned int>(config->transform);
-
- /* Allocate the lens shading table via dmaHeap and pass to the IPA. */
- if (!lsTable_.isValid()) {
- lsTable_ = SharedFD(dmaHeap_.alloc("ls_grid", ipa::RPi::MaxLsGridSize));
- if (!lsTable_.isValid())
- return -ENOMEM;
-
- /* Allow the IPA to mmap the LS table via the file descriptor. */
- /*
- * \todo Investigate if mapping the lens shading table buffer
- * could be handled with mapBuffers().
- */
- ipaConfig.lsTableHandle = lsTable_;
- }
-
- /* We store the IPACameraSensorInfo for digital zoom calculations. */
- int ret = sensor_->sensorInfo(&sensorInfo_);
- if (ret) {
- LOG(RPI, Error) << "Failed to retrieve camera sensor info";
- return ret;
- }
-
- /* Ready the IPA - it must know about the sensor resolution. */
- ControlList controls;
- ret = ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig,
- &controls, result);
- if (ret < 0) {
- LOG(RPI, Error) << "IPA configuration failed!";
- return -EPIPE;
- }
-
- if (!controls.empty())
- setSensorControls(controls);
-
- return 0;
-}
-
-/*
- * enumerateVideoDevices() iterates over the Media Controller topology, starting
- * at the sensor and finishing at Unicam. For each sensor, RPiCameraData stores
- * a unique list of any intermediate video mux or bridge devices connected in a
- * cascade, together with the entity to entity link.
- *
- * Entity pad configuration and link enabling happens at the end of configure().
- * We first disable all pad links on each entity device in the chain, and then
- * selectively enabling the specific links to link sensor to Unicam across all
- * intermediate muxes and bridges.
- *
- * In the cascaded topology below, if Sensor1 is used, the Mux2 -> Mux1 link
- * will be disabled, and Sensor1 -> Mux1 -> Unicam links enabled. Alternatively,
- * if Sensor3 is used, the Sensor2 -> Mux2 and Sensor1 -> Mux1 links are disabled,
- * and Sensor3 -> Mux2 -> Mux1 -> Unicam links are enabled. All other links will
- * remain unchanged.
- *
- * +----------+
- * | Unicam |
- * +-----^----+
- * |
- * +---+---+
- * | Mux1 <-------+
- * +--^----+ |
- * | |
- * +-----+---+ +---+---+
- * | Sensor1 | | Mux2 |<--+
- * +---------+ +-^-----+ |
- * | |
- * +-------+-+ +---+-----+
- * | Sensor2 | | Sensor3 |
- * +---------+ +---------+
- */
-void RPiCameraData::enumerateVideoDevices(MediaLink *link)
-{
- const MediaPad *sinkPad = link->sink();
- const MediaEntity *entity = sinkPad->entity();
- bool unicamFound = false;
-
- /* We only deal with Video Mux and Bridge devices in cascade. */
- if (entity->function() != MEDIA_ENT_F_VID_MUX &&
- entity->function() != MEDIA_ENT_F_VID_IF_BRIDGE)
- return;
-
- /* Find the source pad for this Video Mux or Bridge device. */
- const MediaPad *sourcePad = nullptr;
- for (const MediaPad *pad : entity->pads()) {
- if (pad->flags() & MEDIA_PAD_FL_SOURCE) {
- /*
- * We can only deal with devices that have a single source
- * pad. If this device has multiple source pads, ignore it
- * and this branch in the cascade.
- */
- if (sourcePad)
- return;
-
- sourcePad = pad;
- }
- }
-
- LOG(RPI, Debug) << "Found video mux device " << entity->name()
- << " linked to sink pad " << sinkPad->index();
-
- bridgeDevices_.emplace_back(std::make_unique<V4L2Subdevice>(entity), link);
- bridgeDevices_.back().first->open();
-
- /*
- * Iterate through all the sink pad links down the cascade to find any
- * other Video Mux and Bridge devices.
- */
- for (MediaLink *l : sourcePad->links()) {
- enumerateVideoDevices(l);
- /* Once we reach the Unicam entity, we are done. */
- if (l->sink()->entity()->name() == "unicam-image") {
- unicamFound = true;
- break;
- }
- }
-
- /* This identifies the end of our entity enumeration recursion. */
- if (link->source()->entity()->function() == MEDIA_ENT_F_CAM_SENSOR) {
- /*
- * If Unicam is not at the end of this cascade, we cannot configure
- * this topology automatically, so remove all entity references.
- */
- if (!unicamFound) {
- LOG(RPI, Warning) << "Cannot automatically configure this MC topology!";
- bridgeDevices_.clear();
- }
- }
-}
-
-void RPiCameraData::statsMetadataComplete(uint32_t bufferId, const ControlList &controls)
-{
- if (state_ == State::Stopped)
- return;
-
- FrameBuffer *buffer = isp_[Isp::Stats].getBuffers().at(bufferId);
-
- handleStreamBuffer(buffer, &isp_[Isp::Stats]);
-
- /* Add to the Request metadata buffer what the IPA has provided. */
- Request *request = requestQueue_.front();
- request->metadata().merge(controls);
-
- /*
- * Inform the sensor of the latest colour gains if it has the
- * V4L2_CID_NOTIFY_GAINS control (which means notifyGainsUnity_ is set).
- */
- if (notifyGainsUnity_ && controls.contains(libcamera::controls::ColourGains)) {
- libcamera::Span<const float> colourGains = controls.get(libcamera::controls::ColourGains);
- /* The control wants linear gains in the order B, Gb, Gr, R. */
- ControlList ctrls(sensor_->controls());
- std::array<int32_t, 4> gains{
- static_cast<int32_t>(colourGains[1] * *notifyGainsUnity_),
- *notifyGainsUnity_,
- *notifyGainsUnity_,
- static_cast<int32_t>(colourGains[0] * *notifyGainsUnity_)
- };
- ctrls.set(V4L2_CID_NOTIFY_GAINS, Span<const int32_t>{ gains });
-
- sensor_->setControls(&ctrls);
- }
-
- state_ = State::IpaComplete;
- handleState();
-}
-
-void RPiCameraData::runIsp(uint32_t bufferId)
-{
- if (state_ == State::Stopped)
- return;
-
- FrameBuffer *buffer = unicam_[Unicam::Image].getBuffers().at(bufferId);
-
- LOG(RPI, Debug) << "Input re-queue to ISP, buffer id " << bufferId
- << ", timestamp: " << buffer->metadata().timestamp;
-
- isp_[Isp::Input].queueBuffer(buffer);
- ispOutputCount_ = 0;
- handleState();
-}
-
-void RPiCameraData::embeddedComplete(uint32_t bufferId)
-{
- if (state_ == State::Stopped)
- return;
-
- FrameBuffer *buffer = unicam_[Unicam::Embedded].getBuffers().at(bufferId);
- handleStreamBuffer(buffer, &unicam_[Unicam::Embedded]);
- handleState();
-}
-
-void RPiCameraData::setIspControls(const ControlList &controls)
-{
- ControlList ctrls = controls;
-
- if (ctrls.contains(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING)) {
- ControlValue &value =
- const_cast<ControlValue &>(ctrls.get(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING));
- Span<uint8_t> s = value.data();
- bcm2835_isp_lens_shading *ls =
- reinterpret_cast<bcm2835_isp_lens_shading *>(s.data());
- ls->dmabuf = lsTable_.get();
- }
-
- isp_[Isp::Input].dev()->setControls(&ctrls);
- handleState();
-}
-
-void RPiCameraData::setDelayedControls(const ControlList &controls)
-{
- if (!delayedCtrls_->push(controls))
- LOG(RPI, Error) << "V4L2 DelayedControl set failed";
- handleState();
-}
-
-void RPiCameraData::setSensorControls(ControlList &controls)
-{
- /*
- * We need to ensure that if both VBLANK and EXPOSURE are present, the
- * former must be written ahead of, and separately from EXPOSURE to avoid
- * V4L2 rejecting the latter. This is identical to what DelayedControls
- * does with the priority write flag.
- *
- * As a consequence of the below logic, VBLANK gets set twice, and we
- * rely on the v4l2 framework to not pass the second control set to the
- * driver as the actual control value has not changed.
- */
- if (controls.contains(V4L2_CID_EXPOSURE) && controls.contains(V4L2_CID_VBLANK)) {
- ControlList vblank_ctrl;
-
- vblank_ctrl.set(V4L2_CID_VBLANK, controls.get(V4L2_CID_VBLANK));
- sensor_->setControls(&vblank_ctrl);
- }
-
- sensor_->setControls(&controls);
-}
-
-void RPiCameraData::unicamTimeout()
-{
- LOG(RPI, Error) << "Unicam has timed out!";
- LOG(RPI, Error) << "Please check that your camera sensor connector is attached securely.";
- LOG(RPI, Error) << "Alternatively, try another cable and/or sensor.";
-}
-
-void RPiCameraData::unicamBufferDequeue(FrameBuffer *buffer)
-{
- RPi::Stream *stream = nullptr;
- int index;
-
- if (state_ == State::Stopped)
- return;
-
- for (RPi::Stream &s : unicam_) {
- index = s.getBufferId(buffer);
- if (index != -1) {
- stream = &s;
- break;
- }
- }
-
- /* The buffer must belong to one of our streams. */
- ASSERT(stream);
-
- LOG(RPI, Debug) << "Stream " << stream->name() << " buffer dequeue"
- << ", buffer id " << index
- << ", timestamp: " << buffer->metadata().timestamp;
-
- if (stream == &unicam_[Unicam::Image]) {
- /*
- * Lookup the sensor controls used for this frame sequence from
- * DelayedControl and queue them along with the frame buffer.
- */
- ControlList ctrl = delayedCtrls_->get(buffer->metadata().sequence);
- /*
- * Add the frame timestamp to the ControlList for the IPA to use
- * as it does not receive the FrameBuffer object.
- */
- ctrl.set(controls::SensorTimestamp, buffer->metadata().timestamp);
- bayerQueue_.push({ buffer, std::move(ctrl) });
- } else {
- embeddedQueue_.push(buffer);
- }
-
- handleState();
-}
-
-void RPiCameraData::ispInputDequeue(FrameBuffer *buffer)
-{
- if (state_ == State::Stopped)
- return;
-
- LOG(RPI, Debug) << "Stream ISP Input buffer complete"
- << ", buffer id " << unicam_[Unicam::Image].getBufferId(buffer)
- << ", timestamp: " << buffer->metadata().timestamp;
-
- /* The ISP input buffer gets re-queued into Unicam. */
- handleStreamBuffer(buffer, &unicam_[Unicam::Image]);
- handleState();
-}
-
-void RPiCameraData::ispOutputDequeue(FrameBuffer *buffer)
-{
- RPi::Stream *stream = nullptr;
- int index;
-
- if (state_ == State::Stopped)
- return;
-
- for (RPi::Stream &s : isp_) {
- index = s.getBufferId(buffer);
- if (index != -1) {
- stream = &s;
- break;
- }
- }
-
- /* The buffer must belong to one of our ISP output streams. */
- ASSERT(stream);
-
- LOG(RPI, Debug) << "Stream " << stream->name() << " buffer complete"
- << ", buffer id " << index
- << ", timestamp: " << buffer->metadata().timestamp;
-
- /*
- * ISP statistics buffer must not be re-queued or sent back to the
- * application until after the IPA signals so.
- */
- if (stream == &isp_[Isp::Stats]) {
- ipa_->signalStatReady(ipa::RPi::MaskStats | static_cast<unsigned int>(index));
- } else {
- /* Any other ISP output can be handed back to the application now. */
- handleStreamBuffer(buffer, stream);
- }
-
- /*
- * Increment the number of ISP outputs generated.
- * This is needed to track dropped frames.
- */
- ispOutputCount_++;
-
- handleState();
-}
-
-void RPiCameraData::clearIncompleteRequests()
-{
- /*
- * All outstanding requests (and associated buffers) must be returned
- * back to the application.
- */
- while (!requestQueue_.empty()) {
- Request *request = requestQueue_.front();
-
- for (auto &b : request->buffers()) {
- FrameBuffer *buffer = b.second;
- /*
- * Has the buffer already been handed back to the
- * request? If not, do so now.
- */
- if (buffer->request()) {
- buffer->_d()->cancel();
- pipe()->completeBuffer(request, buffer);
- }
- }
-
- pipe()->completeRequest(request);
- requestQueue_.pop_front();
- }
-}
-
-void RPiCameraData::handleStreamBuffer(FrameBuffer *buffer, RPi::Stream *stream)
-{
- /*
- * It is possible to be here without a pending request, so check
- * that we actually have one to action, otherwise we just return
- * buffer back to the stream.
- */
- Request *request = requestQueue_.empty() ? nullptr : requestQueue_.front();
- if (!dropFrameCount_ && request && request->findBuffer(stream) == buffer) {
- /*
- * Check if this is an externally provided buffer, and if
- * so, we must stop tracking it in the pipeline handler.
- */
- handleExternalBuffer(buffer, stream);
- /*
- * Tag the buffer as completed, returning it to the
- * application.
- */
- pipe()->completeBuffer(request, buffer);
- } else {
- /*
- * This buffer was not part of the Request (which happens if an
- * internal buffer was used for an external stream, or
- * unconditionally for internal streams), or there is no pending
- * request, so we can recycle it.
- */
- stream->returnBuffer(buffer);
- }
-}
-
-void RPiCameraData::handleExternalBuffer(FrameBuffer *buffer, RPi::Stream *stream)
-{
- unsigned int id = stream->getBufferId(buffer);
-
- if (!(id & ipa::RPi::MaskExternalBuffer))
- return;
-
- /* Stop the Stream object from tracking the buffer. */
- stream->removeExternalBuffer(buffer);
-}
-
-void RPiCameraData::handleState()
-{
- switch (state_) {
- case State::Stopped:
- case State::Busy:
- break;
-
- case State::IpaComplete:
- /* If the request is completed, we will switch to Idle state. */
- checkRequestCompleted();
- /*
- * No break here, we want to try running the pipeline again.
- * The fallthrough clause below suppresses compiler warnings.
- */
- [[fallthrough]];
-
- case State::Idle:
- tryRunPipeline();
- break;
- }
-}
-
-void RPiCameraData::checkRequestCompleted()
-{
- bool requestCompleted = false;
- /*
- * If we are dropping this frame, do not touch the request, simply
- * change the state to IDLE when ready.
- */
- if (!dropFrameCount_) {
- Request *request = requestQueue_.front();
- if (request->hasPendingBuffers())
- return;
-
- /* Must wait for metadata to be filled in before completing. */
- if (state_ != State::IpaComplete)
- return;
-
- pipe()->completeRequest(request);
- requestQueue_.pop_front();
- requestCompleted = true;
- }
-
- /*
- * Make sure we have three outputs completed in the case of a dropped
- * frame.
- */
- if (state_ == State::IpaComplete &&
- ((ispOutputCount_ == 3 && dropFrameCount_) || requestCompleted)) {
- state_ = State::Idle;
- if (dropFrameCount_) {
- dropFrameCount_--;
- LOG(RPI, Debug) << "Dropping frame at the request of the IPA ("
- << dropFrameCount_ << " left)";
- }
- }
-}
-
-Rectangle RPiCameraData::scaleIspCrop(const Rectangle &ispCrop) const
-{
- /*
- * Scale a crop rectangle defined in the ISP's coordinates into native sensor
- * coordinates.
- */
- Rectangle nativeCrop = ispCrop.scaledBy(sensorInfo_.analogCrop.size(),
- sensorInfo_.outputSize);
- nativeCrop.translateBy(sensorInfo_.analogCrop.topLeft());
- return nativeCrop;
-}
-
-void RPiCameraData::applyScalerCrop(const ControlList &controls)
-{
- if (controls.contains(controls::ScalerCrop)) {
- Rectangle nativeCrop = controls.get<Rectangle>(controls::ScalerCrop);
-
- if (!nativeCrop.width || !nativeCrop.height)
- nativeCrop = { 0, 0, 1, 1 };
-
- /* Create a version of the crop scaled to ISP (camera mode) pixels. */
- Rectangle ispCrop = nativeCrop.translatedBy(-sensorInfo_.analogCrop.topLeft());
- ispCrop.scaleBy(sensorInfo_.outputSize, sensorInfo_.analogCrop.size());
-
- /*
- * The crop that we set must be:
- * 1. At least as big as ispMinCropSize_, once that's been
- * enlarged to the same aspect ratio.
- * 2. With the same mid-point, if possible.
- * 3. But it can't go outside the sensor area.
- */
- Size minSize = ispMinCropSize_.expandedToAspectRatio(nativeCrop.size());
- Size size = ispCrop.size().expandedTo(minSize);
- ispCrop = size.centeredTo(ispCrop.center()).enclosedIn(Rectangle(sensorInfo_.outputSize));
-
- if (ispCrop != ispCrop_) {
- isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &ispCrop);
- ispCrop_ = ispCrop;
-
- /*
- * Also update the ScalerCrop in the metadata with what we actually
- * used. But we must first rescale that from ISP (camera mode) pixels
- * back into sensor native pixels.
- */
- scalerCrop_ = scaleIspCrop(ispCrop_);
- }
- }
-}
-
-void RPiCameraData::fillRequestMetadata(const ControlList &bufferControls,
- Request *request)
-{
- request->metadata().set(controls::SensorTimestamp,
- bufferControls.get(controls::SensorTimestamp));
-
- request->metadata().set(controls::ScalerCrop, scalerCrop_);
-}
-
-void RPiCameraData::tryRunPipeline()
-{
- FrameBuffer *embeddedBuffer;
- BayerFrame bayerFrame;
-
- /* If any of our request or buffer queues are empty, we cannot proceed. */
- if (state_ != State::Idle || requestQueue_.empty() ||
- bayerQueue_.empty() || (embeddedQueue_.empty() && sensorMetadata_))
- return;
-
- if (!findMatchingBuffers(bayerFrame, embeddedBuffer))
- return;
-
- /* Take the first request from the queue and action the IPA. */
- Request *request = requestQueue_.front();
-
- /* See if a new ScalerCrop value needs to be applied. */
- applyScalerCrop(request->controls());
-
- /*
- * Clear the request metadata and fill it with some initial non-IPA
- * related controls. We clear it first because the request metadata
- * may have been populated if we have dropped the previous frame.
- */
- request->metadata().clear();
- fillRequestMetadata(bayerFrame.controls, request);
-
- /*
- * Process all the user controls by the IPA. Once this is complete, we
- * queue the ISP output buffer listed in the request to start the HW
- * pipeline.
- */
- ipa_->signalQueueRequest(request->controls());
-
- /* Set our state to say the pipeline is active. */
- state_ = State::Busy;
-
- unsigned int bayerId = unicam_[Unicam::Image].getBufferId(bayerFrame.buffer);
-
- LOG(RPI, Debug) << "Signalling signalIspPrepare:"
- << " Bayer buffer id: " << bayerId;
-
- ipa::RPi::ISPConfig ispPrepare;
- ispPrepare.bayerBufferId = ipa::RPi::MaskBayerData | bayerId;
- ispPrepare.controls = std::move(bayerFrame.controls);
-
- if (embeddedBuffer) {
- unsigned int embeddedId = unicam_[Unicam::Embedded].getBufferId(embeddedBuffer);
-
- ispPrepare.embeddedBufferId = ipa::RPi::MaskEmbeddedData | embeddedId;
- ispPrepare.embeddedBufferPresent = true;
-
- LOG(RPI, Debug) << "Signalling signalIspPrepare:"
- << " Embedded buffer id: " << embeddedId;
- }
-
- ipa_->signalIspPrepare(ispPrepare);
-}
-
-bool RPiCameraData::findMatchingBuffers(BayerFrame &bayerFrame, FrameBuffer *&embeddedBuffer)
-{
- if (bayerQueue_.empty())
- return false;
-
- /* Start with the front of the bayer queue. */
- bayerFrame = std::move(bayerQueue_.front());
- bayerQueue_.pop();
-
- /*
- * Find the embedded data buffer with a matching timestamp to pass to
- * the IPA. Any embedded buffers with a timestamp lower than the
- * current bayer buffer will be removed and re-queued to the driver.
- */
- uint64_t ts = bayerFrame.buffer->metadata().timestamp;
- embeddedBuffer = nullptr;
- while (!embeddedQueue_.empty()) {
- FrameBuffer *b = embeddedQueue_.front();
- if (b->metadata().timestamp < ts) {
- embeddedQueue_.pop();
- unicam_[Unicam::Embedded].returnBuffer(b);
- LOG(RPI, Debug) << "Dropping unmatched input frame in stream "
- << unicam_[Unicam::Embedded].name();
- } else if (b->metadata().timestamp == ts) {
- /* Found a match! */
- embeddedBuffer = b;
- embeddedQueue_.pop();
- break;
- } else {
- break; /* Only higher timestamps from here. */
- }
- }
-
- if (!embeddedBuffer && sensorMetadata_) {
- /* Log if there is no matching embedded data buffer found. */
- LOG(RPI, Debug) << "Returning bayer frame without a matching embedded buffer.";
- }
-
- return true;
-}
-
-REGISTER_PIPELINE_HANDLER(PipelineHandlerRPi)
-
-} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/raspberrypi/rpi_stream.h b/src/libcamera/pipeline/raspberrypi/rpi_stream.h
deleted file mode 100644
index fe011100..00000000
--- a/src/libcamera/pipeline/raspberrypi/rpi_stream.h
+++ /dev/null
@@ -1,178 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
- *
- * rpi_stream.h - Raspberry Pi device stream abstraction class.
- */
-
-#pragma once
-
-#include <queue>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <libcamera/ipa/raspberrypi_ipa_interface.h>
-#include <libcamera/stream.h>
-
-#include "libcamera/internal/v4l2_videodevice.h"
-
-namespace libcamera {
-
-namespace RPi {
-
-using BufferMap = std::unordered_map<unsigned int, FrameBuffer *>;
-
-/*
- * Device stream abstraction for either an internal or external stream.
- * Used for both Unicam and the ISP.
- */
-class Stream : public libcamera::Stream
-{
-public:
- Stream()
- : id_(ipa::RPi::MaskID)
- {
- }
-
- Stream(const char *name, MediaEntity *dev, bool importOnly = false)
- : external_(false), importOnly_(importOnly), name_(name),
- dev_(std::make_unique<V4L2VideoDevice>(dev)), id_(ipa::RPi::MaskID)
- {
- }
-
- V4L2VideoDevice *dev() const;
- std::string name() const;
- bool isImporter() const;
- void resetBuffers();
-
- void setExternal(bool external);
- bool isExternal() const;
-
- void setExportedBuffers(std::vector<std::unique_ptr<FrameBuffer>> *buffers);
- const BufferMap &getBuffers() const;
- int getBufferId(FrameBuffer *buffer) const;
-
- void setExternalBuffer(FrameBuffer *buffer);
- void removeExternalBuffer(FrameBuffer *buffer);
-
- int prepareBuffers(unsigned int count);
- int queueBuffer(FrameBuffer *buffer);
- void returnBuffer(FrameBuffer *buffer);
-
- int queueAllBuffers();
- void releaseBuffers();
-
-private:
- class IdGenerator
- {
- public:
- IdGenerator(int max)
- : max_(max), id_(0)
- {
- }
-
- int get()
- {
- int id;
- if (!recycle_.empty()) {
- id = recycle_.front();
- recycle_.pop();
- } else {
- id = id_++;
- ASSERT(id_ <= max_);
- }
- return id;
- }
-
- void release(int id)
- {
- recycle_.push(id);
- }
-
- void reset()
- {
- id_ = 0;
- recycle_ = {};
- }
-
- private:
- int max_;
- int id_;
- std::queue<int> recycle_;
- };
-
- void clearBuffers();
- int queueToDevice(FrameBuffer *buffer);
-
- /*
- * Indicates that this stream is active externally, i.e. the buffers
- * might be provided by (and returned to) the application.
- */
- bool external_;
-
- /* Indicates that this stream only imports buffers, e.g. ISP input. */
- bool importOnly_;
-
- /* Stream name identifier. */
- std::string name_;
-
- /* The actual device stream. */
- std::unique_ptr<V4L2VideoDevice> dev_;
-
- /* Tracks a unique id key for the bufferMap_ */
- IdGenerator id_;
-
- /* All frame buffers associated with this device stream. */
- BufferMap bufferMap_;
-
- /*
- * List of frame buffers that we can use if none have been provided by
- * the application for external streams. This is populated by the
- * buffers exported internally.
- */
- std::queue<FrameBuffer *> availableBuffers_;
-
- /*
- * List of frame buffers that are to be queued into the device from a Request.
- * A nullptr indicates any internal buffer can be used (from availableBuffers_),
- * whereas a valid pointer indicates an external buffer to be queued.
- *
- * Ordering buffers to be queued is important here as it must match the
- * requests coming from the application.
- */
- std::queue<FrameBuffer *> requestBuffers_;
-
- /*
- * This is a list of buffers exported internally. Need to keep this around
- * as the stream needs to maintain ownership of these buffers.
- */
- std::vector<std::unique_ptr<FrameBuffer>> internalBuffers_;
-};
-
-/*
- * The following class is just a convenient (and typesafe) array of device
- * streams indexed with an enum class.
- */
-template<typename E, std::size_t N>
-class Device : public std::array<class Stream, N>
-{
-private:
- constexpr auto index(E e) const noexcept
- {
- return static_cast<std::underlying_type_t<E>>(e);
- }
-public:
- Stream &operator[](E e)
- {
- return std::array<class Stream, N>::operator[](index(e));
- }
- const Stream &operator[](E e) const
- {
- return std::array<class Stream, N>::operator[](index(e));
- }
-};
-
-} /* namespace RPi */
-
-} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 3dc0850c..abb21968 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -13,24 +13,29 @@
#include <queue>
#include <linux/media-bus-format.h>
+#include <linux/rkisp1-config.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
+#include <libcamera/color_space.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
#include <libcamera/framebuffer.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.h>
+#include <libcamera/transform.h>
+
#include <libcamera/ipa/core_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_proxy.h>
-#include <libcamera/request.h>
-#include <libcamera/stream.h>
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/pipeline_handler.h"
@@ -64,7 +69,8 @@ class RkISP1Frames
public:
RkISP1Frames(PipelineHandler *pipe);
- RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request);
+ RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request,
+ bool isRaw);
int destroy(unsigned int frame);
void clear();
@@ -119,6 +125,7 @@ public:
Status validate() override;
const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }
+ const Transform &combinedTransform() { return combinedTransform_; }
private:
bool fitsAllPaths(const StreamConfiguration &cfg);
@@ -132,6 +139,7 @@ private:
const RkISP1CameraData *data_;
V4L2SubdeviceFormat sensorFormat_;
+ Transform combinedTransform_;
};
class PipelineHandlerRkISP1 : public PipelineHandler
@@ -139,8 +147,8 @@ class PipelineHandlerRkISP1 : public PipelineHandler
public:
PipelineHandlerRkISP1(CameraManager *manager);
- CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) override;
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -154,6 +162,8 @@ public:
bool match(DeviceEnumerator *enumerator) override;
private:
+ static constexpr Size kRkISP1PreviewSize = { 1920, 1080 };
+
RkISP1CameraData *cameraData(Camera *camera)
{
return static_cast<RkISP1CameraData *>(camera->_d());
@@ -165,7 +175,7 @@ private:
int initLinks(Camera *camera, const CameraSensor *sensor,
const RkISP1CameraConfiguration &config);
int createCamera(MediaEntity *sensor);
- void tryCompleteRequest(Request *request);
+ void tryCompleteRequest(RkISP1FrameInfo *info);
void bufferReady(FrameBuffer *buffer);
void paramReady(FrameBuffer *buffer);
void statReady(FrameBuffer *buffer);
@@ -178,6 +188,10 @@ private:
std::unique_ptr<V4L2Subdevice> isp_;
std::unique_ptr<V4L2VideoDevice> param_;
std::unique_ptr<V4L2VideoDevice> stat_;
+ std::unique_ptr<V4L2Subdevice> csi_;
+
+ bool hasSelfPath_;
+ bool isRaw_;
RkISP1MainPath mainPath_;
RkISP1SelfPath selfPath_;
@@ -188,6 +202,8 @@ private:
std::queue<FrameBuffer *> availableStatBuffers_;
Camera *activeCamera_;
+
+ const MediaPad *ispSink_;
};
RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)
@@ -195,28 +211,35 @@ RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)
{
}
-RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request)
+RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request,
+ bool isRaw)
{
unsigned int frame = data->frame_;
- if (pipe_->availableParamBuffers_.empty()) {
- LOG(RkISP1, Error) << "Parameters buffer underrun";
- return nullptr;
- }
- FrameBuffer *paramBuffer = pipe_->availableParamBuffers_.front();
+ FrameBuffer *paramBuffer = nullptr;
+ FrameBuffer *statBuffer = nullptr;
- if (pipe_->availableStatBuffers_.empty()) {
- LOG(RkISP1, Error) << "Statisitc buffer underrun";
- return nullptr;
+ if (!isRaw) {
+ if (pipe_->availableParamBuffers_.empty()) {
+ LOG(RkISP1, Error) << "Parameters buffer underrun";
+ return nullptr;
+ }
+
+ if (pipe_->availableStatBuffers_.empty()) {
+ LOG(RkISP1, Error) << "Statistic buffer underrun";
+ return nullptr;
+ }
+
+ paramBuffer = pipe_->availableParamBuffers_.front();
+ pipe_->availableParamBuffers_.pop();
+
+ statBuffer = pipe_->availableStatBuffers_.front();
+ pipe_->availableStatBuffers_.pop();
}
- FrameBuffer *statBuffer = pipe_->availableStatBuffers_.front();
FrameBuffer *mainPathBuffer = request->findBuffer(&data->mainPathStream_);
FrameBuffer *selfPathBuffer = request->findBuffer(&data->selfPathStream_);
- pipe_->availableParamBuffers_.pop();
- pipe_->availableStatBuffers_.pop();
-
RkISP1FrameInfo *info = new RkISP1FrameInfo;
info->frame = frame;
@@ -323,7 +346,7 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
/*
* The API tuning file is made from the sensor name unless the
- * environment variable overrides it. If
+ * environment variable overrides it.
*/
std::string ipaTuningFile;
char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RKISP1_TUNING_FILE");
@@ -339,7 +362,15 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
ipaTuningFile = std::string(configFromEnv);
}
- int ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision);
+ IPACameraSensorInfo sensorInfo{};
+ int ret = sensor_->sensorInfo(&sensorInfo);
+ if (ret) {
+ LOG(RkISP1, Error) << "Camera sensor information not available";
+ return ret;
+ }
+
+ ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,
+ sensorInfo, sensor_->controls(), &controlInfo_);
if (ret < 0) {
LOG(RkISP1, Error) << "IPA initialization failure";
return ret;
@@ -355,13 +386,15 @@ void RkISP1CameraData::paramFilled(unsigned int frame)
if (!info)
return;
+ info->paramBuffer->_d()->metadata().planes()[0].bytesused =
+ sizeof(struct rkisp1_params_cfg);
pipe->param_->queueBuffer(info->paramBuffer);
pipe->stat_->queueBuffer(info->statBuffer);
if (info->mainPathBuffer)
mainPath_->queueBuffer(info->mainPathBuffer);
- if (info->selfPathBuffer)
+ if (selfPath_ && info->selfPathBuffer)
selfPath_->queueBuffer(info->selfPathBuffer);
}
@@ -380,9 +413,33 @@ void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &meta
info->request->metadata().merge(metadata);
info->metadataProcessed = true;
- pipe()->tryCompleteRequest(info->request);
+ pipe()->tryCompleteRequest(info);
}
+/* -----------------------------------------------------------------------------
+ * Camera Configuration
+ */
+
+namespace {
+
+/* Keep in sync with the supported raw formats in rkisp1_path.cpp. */
+const std::map<PixelFormat, uint32_t> rawFormats = {
+ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
+ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
+ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
+ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
+ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
+ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
+ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
+ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
+ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
+ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
+ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
+ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
+};
+
+} /* namespace */
+
RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,
RkISP1CameraData *data)
: CameraConfiguration()
@@ -393,14 +450,15 @@ RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,
bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
{
+ const CameraSensor *sensor = data_->sensor_.get();
StreamConfiguration config;
config = cfg;
- if (data_->mainPath_->validate(&config) != Valid)
+ if (data_->mainPath_->validate(sensor, &config) != Valid)
return false;
config = cfg;
- if (data_->selfPath_->validate(&config) != Valid)
+ if (data_->selfPath_ && data_->selfPath_->validate(sensor, &config) != Valid)
return false;
return true;
@@ -409,20 +467,38 @@ bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
CameraConfiguration::Status RkISP1CameraConfiguration::validate()
{
const CameraSensor *sensor = data_->sensor_.get();
- Status status = Valid;
+ unsigned int pathCount = data_->selfPath_ ? 2 : 1;
+ Status status;
if (config_.empty())
return Invalid;
- if (transform != Transform::Identity) {
- transform = Transform::Identity;
+ status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
+
+ /* Cap the number of entries to the available streams. */
+ if (config_.size() > pathCount) {
+ config_.resize(pathCount);
status = Adjusted;
}
- /* Cap the number of entries to the available streams. */
- if (config_.size() > 2) {
- config_.resize(2);
+ Orientation requestedOrientation = orientation;
+ combinedTransform_ = data_->sensor_->computeTransform(&orientation);
+ if (orientation != requestedOrientation)
status = Adjusted;
+
+ /*
+ * Simultaneous capture of raw and processed streams isn't possible. If
+ * there is any raw stream, cap the number of streams to one.
+ */
+ if (config_.size() > 1) {
+ for (const auto &cfg : config_) {
+ if (PixelFormatInfo::info(cfg.pixelFormat).colourEncoding ==
+ PixelFormatInfo::ColourEncodingRAW) {
+ config_.resize(1);
+ status = Adjusted;
+ break;
+ }
+ }
}
/*
@@ -438,14 +514,14 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
std::reverse(order.begin(), order.end());
bool mainPathAvailable = true;
- bool selfPathAvailable = true;
+ bool selfPathAvailable = data_->selfPath_;
for (unsigned int index : order) {
StreamConfiguration &cfg = config_[index];
/* Try to match stream without adjusting configuration. */
if (mainPathAvailable) {
StreamConfiguration tryCfg = cfg;
- if (data_->mainPath_->validate(&tryCfg) == Valid) {
+ if (data_->mainPath_->validate(sensor, &tryCfg) == Valid) {
mainPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
@@ -455,7 +531,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
if (selfPathAvailable) {
StreamConfiguration tryCfg = cfg;
- if (data_->selfPath_->validate(&tryCfg) == Valid) {
+ if (data_->selfPath_->validate(sensor, &tryCfg) == Valid) {
selfPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
@@ -466,7 +542,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
/* Try to match stream allowing adjusting configuration. */
if (mainPathAvailable) {
StreamConfiguration tryCfg = cfg;
- if (data_->mainPath_->validate(&tryCfg) == Adjusted) {
+ if (data_->mainPath_->validate(sensor, &tryCfg) == Adjusted) {
mainPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
@@ -477,7 +553,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
if (selfPathAvailable) {
StreamConfiguration tryCfg = cfg;
- if (data_->selfPath_->validate(&tryCfg) == Adjusted) {
+ if (data_->selfPath_->validate(sensor, &tryCfg) == Adjusted) {
selfPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
@@ -486,86 +562,147 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
}
}
- /* All paths rejected configuraiton. */
+ /* All paths rejected configuration. */
LOG(RkISP1, Debug) << "Camera configuration not supported "
<< cfg.toString();
return Invalid;
}
/* Select the sensor format. */
+ PixelFormat rawFormat;
Size maxSize;
- for (const StreamConfiguration &cfg : config_)
+
+ for (const StreamConfiguration &cfg : config_) {
+ const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
+ if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)
+ rawFormat = cfg.pixelFormat;
+
maxSize = std::max(maxSize, cfg.size);
+ }
+
+ std::vector<unsigned int> mbusCodes;
+
+ if (rawFormat.isValid()) {
+ mbusCodes = { rawFormats.at(rawFormat) };
+ } else {
+ std::transform(rawFormats.begin(), rawFormats.end(),
+ std::back_inserter(mbusCodes),
+ [](const auto &value) { return value.second; });
+ }
+
+ sensorFormat_ = sensor->getFormat(mbusCodes, maxSize);
- sensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,
- MEDIA_BUS_FMT_SGBRG12_1X12,
- MEDIA_BUS_FMT_SGRBG12_1X12,
- MEDIA_BUS_FMT_SRGGB12_1X12,
- MEDIA_BUS_FMT_SBGGR10_1X10,
- MEDIA_BUS_FMT_SGBRG10_1X10,
- MEDIA_BUS_FMT_SGRBG10_1X10,
- MEDIA_BUS_FMT_SRGGB10_1X10,
- MEDIA_BUS_FMT_SBGGR8_1X8,
- MEDIA_BUS_FMT_SGBRG8_1X8,
- MEDIA_BUS_FMT_SGRBG8_1X8,
- MEDIA_BUS_FMT_SRGGB8_1X8 },
- maxSize);
if (sensorFormat_.size.isNull())
sensorFormat_.size = sensor->resolution();
return status;
}
-PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)
- : PipelineHandler(manager)
-{
-}
-
/* -----------------------------------------------------------------------------
* Pipeline Operations
*/
-CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
+PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)
+ : PipelineHandler(manager), hasSelfPath_(true)
+{
+}
+
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles)
{
RkISP1CameraData *data = cameraData(camera);
- CameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);
+
+ unsigned int pathCount = data->selfPath_ ? 2 : 1;
+ if (roles.size() > pathCount) {
+ LOG(RkISP1, Error) << "Too many stream roles requested";
+ return nullptr;
+ }
+
+ std::unique_ptr<CameraConfiguration> config =
+ std::make_unique<RkISP1CameraConfiguration>(camera, data);
if (roles.empty())
return config;
+ /*
+ * As the ISP can't output different color spaces for the main and self
+ * path, pick a sensible default color space based on the role of the
+ * first stream and use it for all streams.
+ */
+ std::optional<ColorSpace> colorSpace;
bool mainPathAvailable = true;
- bool selfPathAvailable = true;
+
for (const StreamRole role : roles) {
- bool useMainPath;
+ Size size;
switch (role) {
- case StreamRole::StillCapture: {
- useMainPath = mainPathAvailable;
+ case StreamRole::StillCapture:
+ /* JPEG encoders typically expect sYCC. */
+ if (!colorSpace)
+ colorSpace = ColorSpace::Sycc;
+
+ size = data->sensor_->resolution();
break;
- }
+
case StreamRole::Viewfinder:
- case StreamRole::VideoRecording: {
- useMainPath = !selfPathAvailable;
+ /*
+ * sYCC is the YCbCr encoding of sRGB, which is commonly
+ * used by displays.
+ */
+ if (!colorSpace)
+ colorSpace = ColorSpace::Sycc;
+
+ size = kRkISP1PreviewSize;
break;
- }
+
+ case StreamRole::VideoRecording:
+ /* Rec. 709 is a good default for HD video recording. */
+ if (!colorSpace)
+ colorSpace = ColorSpace::Rec709;
+
+ size = kRkISP1PreviewSize;
+ break;
+
+ case StreamRole::Raw:
+ if (roles.size() > 1) {
+ LOG(RkISP1, Error)
+ << "Can't capture both raw and processed streams";
+ return nullptr;
+ }
+
+ colorSpace = ColorSpace::Raw;
+ size = data->sensor_->resolution();
+ break;
+
default:
LOG(RkISP1, Warning)
<< "Requested stream role not supported: " << role;
- delete config;
return nullptr;
}
- StreamConfiguration cfg;
- if (useMainPath) {
- cfg = data->mainPath_->generateConfiguration(
- data->sensor_->resolution());
+ /*
+ * Prefer the main path if available, as it supports higher
+ * resolutions.
+ *
+ * \todo Using the main path unconditionally hides support for
+ * RGB (only available on the self path) in the streams formats
+ * exposed to applications. This likely calls for a better API
+ * to expose streams capabilities.
+ */
+ RkISP1Path *path;
+ if (mainPathAvailable) {
+ path = data->mainPath_;
mainPathAvailable = false;
} else {
- cfg = data->selfPath_->generateConfiguration(
- data->sensor_->resolution());
- selfPathAvailable = false;
+ path = data->selfPath_;
}
+ StreamConfiguration cfg =
+ path->generateConfiguration(data->sensor_.get(), size, role);
+ if (!cfg.pixelFormat.isValid())
+ return nullptr;
+
+ cfg.colorSpace = colorSpace;
config->addConfiguration(cfg);
}
@@ -593,12 +730,18 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
V4L2SubdeviceFormat format = config->sensorFormat();
LOG(RkISP1, Debug) << "Configuring sensor with " << format;
- ret = sensor->setFormat(&format);
+ ret = sensor->setFormat(&format, config->combinedTransform());
if (ret < 0)
return ret;
LOG(RkISP1, Debug) << "Sensor configured with " << format;
+ if (csi_) {
+ ret = csi_->setFormat(0, &format);
+ if (ret < 0)
+ return ret;
+ }
+
ret = isp_->setFormat(0, &format);
if (ret < 0)
return ret;
@@ -612,8 +755,14 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
<< "ISP input pad configured with " << format
<< " crop " << rect;
+ const PixelFormat &streamFormat = config->at(0).pixelFormat;
+ const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
+ isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
+
/* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
- format.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8;
+ if (!isRaw_)
+ format.code = MEDIA_BUS_FMT_YUYV8_2X8;
+
LOG(RkISP1, Debug)
<< "Configuring ISP output pad with " << format
<< " crop " << rect;
@@ -622,6 +771,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
if (ret < 0)
return ret;
+ format.colorSpace = config->at(0).colorSpace;
ret = isp_->setFormat(2, &format);
if (ret < 0)
return ret;
@@ -637,10 +787,12 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
ret = mainPath_.configure(cfg, format);
streamConfig[0] = IPAStream(cfg.pixelFormat,
cfg.size);
- } else {
+ } else if (hasSelfPath_) {
ret = selfPath_.configure(cfg, format);
streamConfig[1] = IPAStream(cfg.pixelFormat,
cfg.size);
+ } else {
+ return -ENODEV;
}
if (ret)
@@ -660,19 +812,15 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
return ret;
/* Inform IPA of stream configuration and sensor controls. */
- IPACameraSensorInfo sensorInfo = {};
- ret = data->sensor_->sensorInfo(&sensorInfo);
- if (ret) {
- /* \todo Turn this into a hard failure. */
- LOG(RkISP1, Warning) << "Camera sensor information not available";
- sensorInfo = {};
- ret = 0;
- }
+ ipa::rkisp1::IPAConfigInfo ipaConfig{};
+
+ ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo);
+ if (ret)
+ return ret;
- std::map<uint32_t, ControlInfoMap> entityControls;
- entityControls.emplace(0, data->sensor_->controls());
+ ipaConfig.sensorControls = data->sensor_->controls();
- ret = data->ipa_->configure(sensorInfo, streamConfig, entityControls);
+ ret = data->ipa_->configure(ipaConfig, streamConfig, &data->controlInfo_);
if (ret) {
LOG(RkISP1, Error) << "failed configuring IPA (" << ret << ")";
return ret;
@@ -688,7 +836,7 @@ int PipelineHandlerRkISP1::exportFrameBuffers([[maybe_unused]] Camera *camera, S
if (stream == &data->mainPathStream_)
return mainPath_.exportBuffers(count, buffers);
- else if (stream == &data->selfPathStream_)
+ else if (hasSelfPath_ && stream == &data->selfPathStream_)
return selfPath_.exportBuffers(count, buffers);
return -EINVAL;
@@ -705,13 +853,15 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera)
data->selfPathStream_.configuration().bufferCount,
});
- ret = param_->allocateBuffers(maxCount, &paramBuffers_);
- if (ret < 0)
- goto error;
+ if (!isRaw_) {
+ ret = param_->allocateBuffers(maxCount, &paramBuffers_);
+ if (ret < 0)
+ goto error;
- ret = stat_->allocateBuffers(maxCount, &statBuffers_);
- if (ret < 0)
- goto error;
+ ret = stat_->allocateBuffers(maxCount, &statBuffers_);
+ if (ret < 0)
+ goto error;
+ }
for (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) {
buffer->setCookie(ipaBufferId++);
@@ -787,23 +937,25 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL
data->frame_ = 0;
- ret = param_->streamOn();
- if (ret) {
- data->ipa_->stop();
- freeBuffers(camera);
- LOG(RkISP1, Error)
- << "Failed to start parameters " << camera->id();
- return ret;
- }
+ if (!isRaw_) {
+ ret = param_->streamOn();
+ if (ret) {
+ data->ipa_->stop();
+ freeBuffers(camera);
+ LOG(RkISP1, Error)
+ << "Failed to start parameters " << camera->id();
+ return ret;
+ }
- ret = stat_->streamOn();
- if (ret) {
- param_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
- LOG(RkISP1, Error)
- << "Failed to start statistics " << camera->id();
- return ret;
+ ret = stat_->streamOn();
+ if (ret) {
+ param_->streamOff();
+ data->ipa_->stop();
+ freeBuffers(camera);
+ LOG(RkISP1, Error)
+ << "Failed to start statistics " << camera->id();
+ return ret;
+ }
}
if (data->mainPath_->isEnabled()) {
@@ -817,7 +969,7 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL
}
}
- if (data->selfPath_->isEnabled()) {
+ if (hasSelfPath_ && data->selfPath_->isEnabled()) {
ret = selfPath_.start();
if (ret) {
mainPath_.stop();
@@ -844,18 +996,21 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera)
data->ipa_->stop();
- selfPath_.stop();
+ if (hasSelfPath_)
+ selfPath_.stop();
mainPath_.stop();
- ret = stat_->streamOff();
- if (ret)
- LOG(RkISP1, Warning)
- << "Failed to stop statistics for " << camera->id();
+ if (!isRaw_) {
+ ret = stat_->streamOff();
+ if (ret)
+ LOG(RkISP1, Warning)
+ << "Failed to stop statistics for " << camera->id();
- ret = param_->streamOff();
- if (ret)
- LOG(RkISP1, Warning)
- << "Failed to stop parameters for " << camera->id();
+ ret = param_->streamOff();
+ if (ret)
+ LOG(RkISP1, Warning)
+ << "Failed to stop parameters for " << camera->id();
+ }
ASSERT(data->queuedRequests_.empty());
data->frameInfo_.clear();
@@ -869,12 +1024,21 @@ int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request)
{
RkISP1CameraData *data = cameraData(camera);
- RkISP1FrameInfo *info = data->frameInfo_.create(data, request);
+ RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_);
if (!info)
return -ENOENT;
data->ipa_->queueRequest(data->frame_, request->controls());
- data->ipa_->fillParamsBuffer(data->frame_, info->paramBuffer->cookie());
+ if (isRaw_) {
+ if (info->mainPathBuffer)
+ data->mainPath_->queueBuffer(info->mainPathBuffer);
+
+ if (data->selfPath_ && info->selfPathBuffer)
+ data->selfPath_->queueBuffer(info->selfPathBuffer);
+ } else {
+ data->ipa_->fillParamsBuffer(data->frame_,
+ info->paramBuffer->cookie());
+ }
data->frame_++;
@@ -900,8 +1064,7 @@ int PipelineHandlerRkISP1::initLinks(Camera *camera,
* Configure the sensor links: enable the link corresponding to this
* camera.
*/
- const MediaPad *pad = isp_->entity()->getPadByIndex(0);
- for (MediaLink *link : pad->links()) {
+ for (MediaLink *link : ispSink_->links()) {
if (link->source()->entity() != sensor->entity())
continue;
@@ -915,10 +1078,18 @@ int PipelineHandlerRkISP1::initLinks(Camera *camera,
return ret;
}
+ if (csi_) {
+ MediaLink *link = isp_->entity()->getPadByIndex(0)->links().at(0);
+
+ ret = link->setEnabled(true);
+ if (ret < 0)
+ return ret;
+ }
+
for (const StreamConfiguration &cfg : config) {
if (cfg.stream() == &data->mainPathStream_)
ret = data->mainPath_->setEnabled(true);
- else if (cfg.stream() == &data->selfPathStream_)
+ else if (hasSelfPath_ && cfg.stream() == &data->selfPathStream_)
ret = data->selfPath_->setEnabled(true);
else
return -EINVAL;
@@ -935,15 +1106,8 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
int ret;
std::unique_ptr<RkISP1CameraData> data =
- std::make_unique<RkISP1CameraData>(this, &mainPath_, &selfPath_);
-
- ControlInfoMap::Map ctrls;
- ctrls.emplace(std::piecewise_construct,
- std::forward_as_tuple(&controls::AeEnable),
- std::forward_as_tuple(false, true));
-
- data->controlInfo_ = ControlInfoMap(std::move(ctrls),
- controls::controls);
+ std::make_unique<RkISP1CameraData>(this, &mainPath_,
+ hasSelfPath_ ? &selfPath_ : nullptr);
data->sensor_ = std::make_unique<CameraSensor>(sensor);
ret = data->sensor_->init();
@@ -991,9 +1155,7 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
DeviceMatch dm("rkisp1");
dm.add("rkisp1_isp");
- dm.add("rkisp1_resizer_selfpath");
dm.add("rkisp1_resizer_mainpath");
- dm.add("rkisp1_selfpath");
dm.add("rkisp1_mainpath");
dm.add("rkisp1_stats");
dm.add("rkisp1_params");
@@ -1008,11 +1170,29 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
return false;
}
+ hasSelfPath_ = !!media_->getEntityByName("rkisp1_selfpath");
+
/* Create the V4L2 subdevices we will need. */
isp_ = V4L2Subdevice::fromEntityName(media_, "rkisp1_isp");
if (isp_->open() < 0)
return false;
+ /* Locate and open the optional CSI-2 receiver. */
+ ispSink_ = isp_->entity()->getPadByIndex(0);
+ if (!ispSink_ || ispSink_->links().empty())
+ return false;
+
+ pad = ispSink_->links().at(0)->source();
+ if (pad->entity()->function() == MEDIA_ENT_F_VID_IF_BRIDGE) {
+ csi_ = std::make_unique<V4L2Subdevice>(pad->entity());
+ if (csi_->open() < 0)
+ return false;
+
+ ispSink_ = csi_->entity()->getPadByIndex(0);
+ if (!ispSink_)
+ return false;
+ }
+
/* Locate and open the stats and params video nodes. */
stat_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1_stats");
if (stat_->open() < 0)
@@ -1026,11 +1206,12 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
if (!mainPath_.init(media_))
return false;
- if (!selfPath_.init(media_))
+ if (hasSelfPath_ && !selfPath_.init(media_))
return false;
mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);
- selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);
+ if (hasSelfPath_)
+ selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);
stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statReady);
param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramReady);
@@ -1038,12 +1219,8 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
* Enumerate all sensors connected to the ISP and create one
* camera instance for each of them.
*/
- pad = isp_->entity()->getPadByIndex(0);
- if (!pad)
- return false;
-
bool registered = false;
- for (MediaLink *link : pad->links()) {
+ for (MediaLink *link : ispSink_->links()) {
if (!createCamera(link->source()->entity()))
registered = true;
}
@@ -1055,12 +1232,10 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
* Buffer Handling
*/
-void PipelineHandlerRkISP1::tryCompleteRequest(Request *request)
+void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info)
{
RkISP1CameraData *data = cameraData(activeCamera_);
- RkISP1FrameInfo *info = data->frameInfo_.find(request);
- if (!info)
- return;
+ Request *request = info->request;
if (request->hasPendingBuffers())
return;
@@ -1068,7 +1243,7 @@ void PipelineHandlerRkISP1::tryCompleteRequest(Request *request)
if (!info->metadataProcessed)
return;
- if (!info->paramDequeued)
+ if (!isRaw_ && !info->paramDequeued)
return;
data->frameInfo_.destroy(info->frame);
@@ -1078,19 +1253,38 @@ void PipelineHandlerRkISP1::tryCompleteRequest(Request *request)
void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer)
{
+ ASSERT(activeCamera_);
+ RkISP1CameraData *data = cameraData(activeCamera_);
+
+ RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
+ if (!info)
+ return;
+
+ const FrameMetadata &metadata = buffer->metadata();
Request *request = buffer->request();
- /*
- * Record the sensor's timestamp in the request metadata.
- *
- * \todo The sensor timestamp should be better estimated by connecting
- * to the V4L2Device::frameStart signal.
- */
- request->metadata().set(controls::SensorTimestamp,
- buffer->metadata().timestamp);
+ if (metadata.status != FrameMetadata::FrameCancelled) {
+ /*
+ * Record the sensor's timestamp in the request metadata.
+ *
+ * \todo The sensor timestamp should be better estimated by connecting
+ * to the V4L2Device::frameStart signal.
+ */
+ request->metadata().set(controls::SensorTimestamp,
+ metadata.timestamp);
+
+ if (isRaw_) {
+ const ControlList &ctrls =
+ data->delayedCtrls_->get(metadata.sequence);
+ data->ipa_->processStatsBuffer(info->frame, 0, ctrls);
+ }
+ } else {
+ if (isRaw_)
+ info->metadataProcessed = true;
+ }
completeBuffer(request, buffer);
- tryCompleteRequest(request);
+ tryCompleteRequest(info);
}
void PipelineHandlerRkISP1::paramReady(FrameBuffer *buffer)
@@ -1103,7 +1297,7 @@ void PipelineHandlerRkISP1::paramReady(FrameBuffer *buffer)
return;
info->paramDequeued = true;
- tryCompleteRequest(info->request);
+ tryCompleteRequest(info);
}
void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer)
@@ -1117,7 +1311,7 @@ void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer)
if (buffer->metadata().status == FrameMetadata::FrameCancelled) {
info->metadataProcessed = true;
- tryCompleteRequest(info->request);
+ tryCompleteRequest(info);
return;
}
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
index 6f175758..9195aad2 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
@@ -12,6 +12,7 @@
#include <libcamera/formats.h>
#include <libcamera/stream.h>
+#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.h"
@@ -20,6 +21,39 @@ namespace libcamera {
LOG_DECLARE_CATEGORY(RkISP1)
+namespace {
+
+/* Keep in sync with the supported raw formats in rkisp1.cpp. */
+const std::map<PixelFormat, uint32_t> formatToMediaBus = {
+ { formats::UYVY, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::YUYV, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::NV12, MEDIA_BUS_FMT_YUYV8_1_5X8 },
+ { formats::NV21, MEDIA_BUS_FMT_YUYV8_1_5X8 },
+ { formats::NV16, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::NV61, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::YUV420, MEDIA_BUS_FMT_YUYV8_1_5X8 },
+ { formats::YVU420, MEDIA_BUS_FMT_YUYV8_1_5X8 },
+ { formats::YUV422, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::YVU422, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::R8, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::RGB565, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::XRGB8888, MEDIA_BUS_FMT_YUYV8_2X8 },
+ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
+ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
+ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
+ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
+ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
+ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
+ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
+ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
+ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
+ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
+ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
+ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
+};
+
+} /* namespace */
+
RkISP1Path::RkISP1Path(const char *name, const Span<const PixelFormat> &formats,
const Size &minResolution, const Size &maxResolution)
: name_(name), running_(false), formats_(formats),
@@ -41,6 +75,8 @@ bool RkISP1Path::init(MediaDevice *media)
if (video_->open() < 0)
return false;
+ populateFormats();
+
link_ = media->link("rkisp1_isp", 2, resizer, 0);
if (!link_)
return false;
@@ -48,40 +84,227 @@ bool RkISP1Path::init(MediaDevice *media)
return true;
}
-StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)
+void RkISP1Path::populateFormats()
{
+ V4L2VideoDevice::Formats v4l2Formats = video_->formats();
+ if (v4l2Formats.empty()) {
+ LOG(RkISP1, Info)
+ << "Failed to enumerate supported formats and sizes, using defaults";
+
+ for (const PixelFormat &format : formats_)
+ streamFormats_.insert(format);
+ return;
+ }
+
+ minResolution_ = { 65535, 65535 };
+ maxResolution_ = { 0, 0 };
+
+ std::vector<PixelFormat> formats;
+ for (const auto &[format, sizes] : v4l2Formats) {
+ const PixelFormat pixelFormat = format.toPixelFormat();
+
+ /*
+ * As a defensive measure, skip any pixel format exposed by the
+ * driver that we don't know about. This ensures that looking up
+ * formats in formatToMediaBus using a key from streamFormats_
+ * will never fail in any of the other functions.
+ */
+ if (!formatToMediaBus.count(pixelFormat)) {
+ LOG(RkISP1, Warning)
+ << "Unsupported pixel format " << pixelFormat;
+ continue;
+ }
+
+ streamFormats_.insert(pixelFormat);
+
+ for (const auto &size : sizes) {
+ if (minResolution_ > size.min)
+ minResolution_ = size.min;
+ if (maxResolution_ < size.max)
+ maxResolution_ = size.max;
+ }
+ }
+}
+
+StreamConfiguration
+RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size,
+ StreamRole role)
+{
+ const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes();
+ const Size &resolution = sensor->resolution();
+
+ /* Min and max resolutions to populate the available stream formats. */
Size maxResolution = maxResolution_.boundedToAspectRatio(resolution)
.boundedTo(resolution);
Size minResolution = minResolution_.expandedToAspectRatio(resolution);
+ /* The desired stream size, bound to the max resolution. */
+ Size streamSize = size.boundedTo(maxResolution);
+
+ /* Create the list of supported stream formats. */
std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
- for (const PixelFormat &format : formats_)
- streamFormats[format] = { { minResolution, maxResolution } };
+ unsigned int rawBitsPerPixel = 0;
+ PixelFormat rawFormat;
+
+ for (const auto &format : streamFormats_) {
+ const PixelFormatInfo &info = PixelFormatInfo::info(format);
+
+ /* Populate stream formats for non-RAW configurations. */
+ if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW) {
+ if (role == StreamRole::Raw)
+ continue;
+
+ streamFormats[format] = { { minResolution, maxResolution } };
+ continue;
+ }
+
+ /* Skip RAW formats for non-raw roles. */
+ if (role != StreamRole::Raw)
+ continue;
+
+ /* Populate stream formats for RAW configurations. */
+ uint32_t mbusCode = formatToMediaBus.at(format);
+ if (std::find(mbusCodes.begin(), mbusCodes.end(), mbusCode) ==
+ mbusCodes.end())
+ /* Skip formats not supported by sensor. */
+ continue;
+
+ /* Add all the RAW sizes the sensor can produce for this code. */
+ for (const auto &rawSize : sensor->sizes(mbusCode)) {
+ if (rawSize.width > maxResolution_.width ||
+ rawSize.height > maxResolution_.height)
+ continue;
+
+ streamFormats[format].push_back({ rawSize, rawSize });
+ }
+
+ /*
+ * Store the raw format with the highest bits per pixel for
+ * later usage.
+ */
+ if (info.bitsPerPixel > rawBitsPerPixel) {
+ rawBitsPerPixel = info.bitsPerPixel;
+ rawFormat = format;
+ }
+ }
+
+ /*
+ * Pick a suitable pixel format for the role. Raw streams need to use a
+ * raw format, processed streams use NV12 by default.
+ */
+ PixelFormat format;
+
+ if (role == StreamRole::Raw) {
+ if (!rawFormat.isValid()) {
+ LOG(RkISP1, Error)
+ << "Sensor " << sensor->model()
+ << " doesn't support raw capture";
+ return {};
+ }
+
+ format = rawFormat;
+ } else {
+ format = formats::NV12;
+ }
StreamFormats formats(streamFormats);
StreamConfiguration cfg(formats);
- cfg.pixelFormat = formats::NV12;
- cfg.size = maxResolution;
+ cfg.pixelFormat = format;
+ cfg.size = streamSize;
cfg.bufferCount = RKISP1_BUFFER_COUNT;
return cfg;
}
-CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)
+CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor,
+ StreamConfiguration *cfg)
{
+ const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes();
+ const Size &resolution = sensor->resolution();
+
const StreamConfiguration reqCfg = *cfg;
CameraConfiguration::Status status = CameraConfiguration::Valid;
- if (std::find(formats_.begin(), formats_.end(), cfg->pixelFormat) ==
- formats_.end())
- cfg->pixelFormat = formats::NV12;
+ /*
+ * Validate the pixel format. If the requested format isn't supported,
+ * default to either NV12 (all versions of the ISP are guaranteed to
+ * support NV12 on both the main and self paths) if the requested format
+ * is not a raw format, or to the supported raw format with the highest
+ * bits per pixel otherwise.
+ */
+ unsigned int rawBitsPerPixel = 0;
+ PixelFormat rawFormat;
+ bool found = false;
+
+ for (const auto &format : streamFormats_) {
+ const PixelFormatInfo &info = PixelFormatInfo::info(format);
+
+ if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {
+ /* Skip raw formats not supported by the sensor. */
+ uint32_t mbusCode = formatToMediaBus.at(format);
+ if (std::find(mbusCodes.begin(), mbusCodes.end(), mbusCode) ==
+ mbusCodes.end())
+ continue;
+
+ /*
+ * Store the raw format with the highest bits per pixel
+ * for later usage.
+ */
+ if (info.bitsPerPixel > rawBitsPerPixel) {
+ rawBitsPerPixel = info.bitsPerPixel;
+ rawFormat = format;
+ }
+ }
+
+ if (cfg->pixelFormat == format) {
+ found = true;
+ break;
+ }
+ }
+
+ bool isRaw = PixelFormatInfo::info(cfg->pixelFormat).colourEncoding ==
+ PixelFormatInfo::ColourEncodingRAW;
+
+ /*
+ * If no raw format supported by the sensor has been found, use a
+ * processed format.
+ */
+ if (!rawFormat.isValid())
+ isRaw = false;
+
+ if (!found)
+ cfg->pixelFormat = isRaw ? rawFormat : formats::NV12;
+
+ Size minResolution;
+ Size maxResolution;
+
+ if (isRaw) {
+ /*
+ * Use the sensor output size closest to the requested stream
+ * size.
+ */
+ uint32_t mbusCode = formatToMediaBus.at(cfg->pixelFormat);
+ V4L2SubdeviceFormat sensorFormat =
+ sensor->getFormat({ mbusCode }, cfg->size);
+
+ minResolution = sensorFormat.size;
+ maxResolution = sensorFormat.size;
+ } else {
+ /*
+ * Adjust the size based on the sensor resolution and absolute
+ * limits of the ISP.
+ */
+ minResolution = minResolution_.expandedToAspectRatio(resolution);
+ maxResolution = maxResolution_.boundedToAspectRatio(resolution)
+ .boundedTo(resolution);
+ }
- cfg->size.boundTo(maxResolution_);
- cfg->size.expandTo(minResolution_);
+ cfg->size.boundTo(maxResolution);
+ cfg->size.expandTo(minResolution);
cfg->bufferCount = RKISP1_BUFFER_COUNT;
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg->pixelFormat);
+ format.fourcc = video_->toV4L2PixelFormat(cfg->pixelFormat);
format.size = cfg->size;
int ret = video_->tryFormat(&format);
@@ -112,7 +335,18 @@ int RkISP1Path::configure(const StreamConfiguration &config,
if (ret < 0)
return ret;
- Rectangle rect(0, 0, ispFormat.size);
+ /*
+ * Crop on the resizer input to maintain FOV before downscaling.
+ *
+ * \todo The alignment to a multiple of 2 pixels is required but may
+ * change the aspect ratio very slightly. A more advanced algorithm to
+ * compute the resizer input crop rectangle is needed, and it should
+ * also take into account the need to crop away the edge pixels affected
+ * by the ISP processing blocks.
+ */
+ Size ispCrop = inputFormat.size.boundedToAspectRatio(config.size)
+ .alignedUpTo(2, 2);
+ Rectangle rect = ispCrop.centeredTo(Rectangle(inputFormat.size).center());
ret = resizer_->setSelection(0, V4L2_SEL_TGT_CROP, &rect);
if (ret < 0)
return ret;
@@ -127,15 +361,11 @@ int RkISP1Path::configure(const StreamConfiguration &config,
<< "Configuring " << name_ << " resizer output pad with "
<< ispFormat;
- switch (config.pixelFormat) {
- case formats::NV12:
- case formats::NV21:
- ispFormat.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8;
- break;
- default:
- ispFormat.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8;
- break;
- }
+ /*
+ * The configuration has been validated, the pixel format is guaranteed
+ * to be supported and thus found in formatToMediaBus.
+ */
+ ispFormat.code = formatToMediaBus.at(config.pixelFormat);
ret = resizer_->setFormat(1, &ispFormat);
if (ret < 0)
@@ -147,7 +377,7 @@ int RkISP1Path::configure(const StreamConfiguration &config,
const PixelFormatInfo &info = PixelFormatInfo::info(config.pixelFormat);
V4L2DeviceFormat outputFormat;
- outputFormat.fourcc = V4L2PixelFormat::fromPixelFormat(config.pixelFormat);
+ outputFormat.fourcc = video_->toV4L2PixelFormat(config.pixelFormat);
outputFormat.size = config.size;
outputFormat.planesCount = info.numPlanes();
@@ -156,7 +386,7 @@ int RkISP1Path::configure(const StreamConfiguration &config,
return ret;
if (outputFormat.size != config.size ||
- outputFormat.fourcc != V4L2PixelFormat::fromPixelFormat(config.pixelFormat)) {
+ outputFormat.fourcc != video_->toV4L2PixelFormat(config.pixelFormat)) {
LOG(RkISP1, Error)
<< "Unable to configure capture in " << config.toString();
return -EINVAL;
@@ -204,17 +434,32 @@ void RkISP1Path::stop()
running_ = false;
}
+/*
+ * \todo Remove the hardcoded resolutions and formats once all users will have
+ * migrated to a recent enough kernel.
+ */
namespace {
constexpr Size RKISP1_RSZ_MP_SRC_MIN{ 32, 16 };
constexpr Size RKISP1_RSZ_MP_SRC_MAX{ 4416, 3312 };
-constexpr std::array<PixelFormat, 6> RKISP1_RSZ_MP_FORMATS{
+constexpr std::array<PixelFormat, 18> RKISP1_RSZ_MP_FORMATS{
formats::YUYV,
formats::NV16,
formats::NV61,
formats::NV21,
formats::NV12,
formats::R8,
- /* \todo Add support for RAW formats. */
+ formats::SBGGR8,
+ formats::SGBRG8,
+ formats::SGRBG8,
+ formats::SRGGB8,
+ formats::SBGGR10,
+ formats::SGBRG10,
+ formats::SGRBG10,
+ formats::SRGGB10,
+ formats::SBGGR12,
+ formats::SGBRG12,
+ formats::SGRBG12,
+ formats::SRGGB12,
};
constexpr Size RKISP1_RSZ_SP_SRC_MIN{ 32, 16 };
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
index f3f1ae39..cd77957e 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h
+++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
@@ -8,6 +8,7 @@
#pragma once
#include <memory>
+#include <set>
#include <vector>
#include <libcamera/base/signal.h>
@@ -22,6 +23,7 @@
namespace libcamera {
+class CameraSensor;
class MediaDevice;
class V4L2Subdevice;
struct StreamConfiguration;
@@ -38,8 +40,11 @@ public:
int setEnabled(bool enable) { return link_->setEnabled(enable); }
bool isEnabled() const { return link_->flags() & MEDIA_LNK_FL_ENABLED; }
- StreamConfiguration generateConfiguration(const Size &resolution);
- CameraConfiguration::Status validate(StreamConfiguration *cfg);
+ StreamConfiguration generateConfiguration(const CameraSensor *sensor,
+ const Size &resolution,
+ StreamRole role);
+ CameraConfiguration::Status validate(const CameraSensor *sensor,
+ StreamConfiguration *cfg);
int configure(const StreamConfiguration &config,
const V4L2SubdeviceFormat &inputFormat);
@@ -57,14 +62,17 @@ public:
Signal<FrameBuffer *> &bufferReady() { return video_->bufferReady; }
private:
+ void populateFormats();
+
static constexpr unsigned int RKISP1_BUFFER_COUNT = 4;
const char *name_;
bool running_;
const Span<const PixelFormat> formats_;
- const Size minResolution_;
- const Size maxResolution_;
+ std::set<PixelFormat> streamFormats_;
+ Size minResolution_;
+ Size maxResolution_;
std::unique_ptr<V4L2Subdevice> resizer_;
std::unique_ptr<V4L2VideoDevice> video_;
diff --git a/src/libcamera/pipeline/rpi/common/delayed_controls.cpp b/src/libcamera/pipeline/rpi/common/delayed_controls.cpp
new file mode 100644
index 00000000..3db92e7d
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/common/delayed_controls.cpp
@@ -0,0 +1,293 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * delayed_controls.cpp - Helper to deal with controls that take effect with a delay
+ *
+ * Note: This has been forked from the libcamera core implementation.
+ */
+
+#include "delayed_controls.h"
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/controls.h>
+
+#include "libcamera/internal/v4l2_device.h"
+
+/**
+ * \file delayed_controls.h
+ * \brief Helper to deal with controls that take effect with a delay
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(RPiDelayedControls)
+
+namespace RPi {
+
+/**
+ * \class DelayedControls
+ * \brief Helper to deal with controls that take effect with a delay
+ *
+ * Some sensor controls take effect with a delay as the sensor needs time to
+ * adjust, for example exposure and analog gain. This is a helper class to deal
+ * with such controls and the intended users are pipeline handlers.
+ *
+ * The idea is to extend the concept of the buffer depth of a pipeline the
+ * application needs to maintain to also cover controls. Just as with buffer
+ * depth if the application keeps the number of requests queued above the
+ * control depth the controls are guaranteed to take effect for the correct
+ * request. The control depth is determined by the control with the greatest
+ * delay.
+ */
+
+/**
+ * \struct DelayedControls::ControlParams
+ * \brief Parameters associated with controls handled by the \a DelayedControls
+ * helper class
+ *
+ * \var ControlParams::delay
+ * \brief Frame delay from setting the control on a sensor device to when it is
+ * consumed during framing.
+ *
+ * \var ControlParams::priorityWrite
+ * \brief Flag to indicate that this control must be applied ahead of, and
+ * separately from the other controls.
+ *
+ * Typically set for the \a V4L2_CID_VBLANK control so that the device driver
+ * does not reject \a V4L2_CID_EXPOSURE control values that may be outside of
+ * the existing vertical blanking specified bounds, but are within the new
+ * blanking bounds.
+ */
+
+/**
+ * \brief Construct a DelayedControls instance
+ * \param[in] device The V4L2 device the controls have to be applied to
+ * \param[in] controlParams Map of the numerical V4L2 control ids to their
+ * associated control parameters.
+ *
+ * The control parameters comprise of delays (in frames) and a priority write
+ * flag. If this flag is set, the relevant control is written separately from,
+ * and ahead of the rest of the batched controls.
+ *
+ * Only controls specified in \a controlParams are handled. If it's desired to
+ * mix delayed controls and controls that take effect immediately the immediate
+ * controls must be listed in the \a controlParams map with a delay value of 0.
+ */
+DelayedControls::DelayedControls(V4L2Device *device,
+ const std::unordered_map<uint32_t, ControlParams> &controlParams)
+ : device_(device), maxDelay_(0)
+{
+ const ControlInfoMap &controls = device_->controls();
+
+ /*
+ * Create a map of control ids to delays for controls exposed by the
+ * device.
+ */
+ for (auto const &param : controlParams) {
+ auto it = controls.find(param.first);
+ if (it == controls.end()) {
+ LOG(RPiDelayedControls, Error)
+ << "Delay request for control id "
+ << utils::hex(param.first)
+ << " but control is not exposed by device "
+ << device_->deviceNode();
+ continue;
+ }
+
+ const ControlId *id = it->first;
+
+ controlParams_[id] = param.second;
+
+ LOG(RPiDelayedControls, Debug)
+ << "Set a delay of " << controlParams_[id].delay
+ << " and priority write flag " << controlParams_[id].priorityWrite
+ << " for " << id->name();
+
+ maxDelay_ = std::max(maxDelay_, controlParams_[id].delay);
+ }
+
+ reset(0);
+}
+
+/**
+ * \brief Reset state machine
+ *
+ * Resets the state machine to a starting position based on control values
+ * retrieved from the device.
+ */
+void DelayedControls::reset(unsigned int cookie)
+{
+ queueCount_ = 1;
+ writeCount_ = 0;
+ cookies_[0] = cookie;
+
+ /* Retrieve control as reported by the device. */
+ std::vector<uint32_t> ids;
+ for (auto const &param : controlParams_)
+ ids.push_back(param.first->id());
+
+ ControlList controls = device_->getControls(ids);
+
+ /* Seed the control queue with the controls reported by the device. */
+ values_.clear();
+ for (const auto &ctrl : controls) {
+ const ControlId *id = device_->controls().idmap().at(ctrl.first);
+ /*
+ * Do not mark this control value as updated, it does not need
+ * to be written to to device on startup.
+ */
+ values_[id][0] = Info(ctrl.second, false);
+ }
+}
+
+/**
+ * \brief Push a set of controls on the queue
+ * \param[in] controls List of controls to add to the device queue
+ *
+ * Push a set of controls to the control queue. This increases the control queue
+ * depth by one.
+ *
+ * \returns true if \a controls are accepted, or false otherwise
+ */
+bool DelayedControls::push(const ControlList &controls, const unsigned int cookie)
+{
+ /* Copy state from previous frame. */
+ for (auto &ctrl : values_) {
+ Info &info = ctrl.second[queueCount_];
+ info = values_[ctrl.first][queueCount_ - 1];
+ info.updated = false;
+ }
+
+ /* Update with new controls. */
+ const ControlIdMap &idmap = device_->controls().idmap();
+ for (const auto &control : controls) {
+ const auto &it = idmap.find(control.first);
+ if (it == idmap.end()) {
+ LOG(RPiDelayedControls, Warning)
+ << "Unknown control " << control.first;
+ return false;
+ }
+
+ const ControlId *id = it->second;
+
+ if (controlParams_.find(id) == controlParams_.end())
+ return false;
+
+ Info &info = values_[id][queueCount_];
+
+ info = Info(control.second);
+
+ LOG(RPiDelayedControls, Debug)
+ << "Queuing " << id->name()
+ << " to " << info.toString()
+ << " at index " << queueCount_;
+ }
+
+ cookies_[queueCount_] = cookie;
+ queueCount_++;
+
+ return true;
+}
+
+/**
+ * \brief Read back controls in effect at a sequence number
+ * \param[in] sequence The sequence number to get controls for
+ *
+ * Read back what controls where in effect at a specific sequence number. The
+ * history is a ring buffer of 16 entries where new and old values coexist. It's
+ * the callers responsibility to not read too old sequence numbers that have been
+ * pushed out of the history.
+ *
+ * Historic values are evicted by pushing new values onto the queue using
+ * push(). The max history from the current sequence number that yields valid
+ * values are thus 16 minus number of controls pushed.
+ *
+ * \return The controls at \a sequence number
+ */
+std::pair<ControlList, unsigned int> DelayedControls::get(uint32_t sequence)
+{
+ unsigned int index = std::max<int>(0, sequence - maxDelay_);
+
+ ControlList out(device_->controls());
+ for (const auto &ctrl : values_) {
+ const ControlId *id = ctrl.first;
+ const Info &info = ctrl.second[index];
+
+ out.set(id->id(), info);
+
+ LOG(RPiDelayedControls, Debug)
+ << "Reading " << id->name()
+ << " to " << info.toString()
+ << " at index " << index;
+ }
+
+ return { out, cookies_[index] };
+}
+
+/**
+ * \brief Inform DelayedControls of the start of a new frame
+ * \param[in] sequence Sequence number of the frame that started
+ *
+ * Inform the state machine that a new frame has started and of its sequence
+ * number. Any user of these helpers is responsible to inform the helper about
+ * the start of any frame. This can be connected with ease to the start of a
+ * exposure (SOE) V4L2 event.
+ */
+void DelayedControls::applyControls(uint32_t sequence)
+{
+ LOG(RPiDelayedControls, Debug) << "frame " << sequence << " started";
+
+ /*
+ * Create control list peeking ahead in the value queue to ensure
+ * values are set in time to satisfy the sensor delay.
+ */
+ ControlList out(device_->controls());
+ for (auto &ctrl : values_) {
+ const ControlId *id = ctrl.first;
+ unsigned int delayDiff = maxDelay_ - controlParams_[id].delay;
+ unsigned int index = std::max<int>(0, writeCount_ - delayDiff);
+ Info &info = ctrl.second[index];
+
+ if (info.updated) {
+ if (controlParams_[id].priorityWrite) {
+ /*
+ * This control must be written now, it could
+ * affect validity of the other controls.
+ */
+ ControlList priority(device_->controls());
+ priority.set(id->id(), info);
+ device_->setControls(&priority);
+ } else {
+ /*
+ * Batch up the list of controls and write them
+ * at the end of the function.
+ */
+ out.set(id->id(), info);
+ }
+
+ LOG(RPiDelayedControls, Debug)
+ << "Setting " << id->name()
+ << " to " << info.toString()
+ << " at index " << index;
+
+ /* Done with this update, so mark as completed. */
+ info.updated = false;
+ }
+ }
+
+ writeCount_ = sequence + 1;
+
+ while (writeCount_ > queueCount_) {
+ LOG(RPiDelayedControls, Debug)
+ << "Queue is empty, auto queue no-op.";
+ push({}, cookies_[queueCount_ - 1]);
+ }
+
+ device_->setControls(&out);
+}
+
+} /* namespace RPi */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/rpi/common/delayed_controls.h b/src/libcamera/pipeline/rpi/common/delayed_controls.h
new file mode 100644
index 00000000..61f755f0
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/common/delayed_controls.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * delayed_controls.h - Helper to deal with controls that take effect with a delay
+ *
+ * Note: This has been forked from the libcamera core implementation.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <unordered_map>
+#include <utility>
+
+#include <libcamera/controls.h>
+
+namespace libcamera {
+
+class V4L2Device;
+
+namespace RPi {
+
+class DelayedControls
+{
+public:
+ struct ControlParams {
+ unsigned int delay;
+ bool priorityWrite;
+ };
+
+ DelayedControls(V4L2Device *device,
+ const std::unordered_map<uint32_t, ControlParams> &controlParams);
+
+ void reset(unsigned int cookie);
+
+ bool push(const ControlList &controls, unsigned int cookie);
+ std::pair<ControlList, unsigned int> get(uint32_t sequence);
+
+ void applyControls(uint32_t sequence);
+
+private:
+ class Info : public ControlValue
+ {
+ public:
+ Info()
+ : updated(false)
+ {
+ }
+
+ Info(const ControlValue &v, bool updated_ = true)
+ : ControlValue(v), updated(updated_)
+ {
+ }
+
+ bool updated;
+ };
+
+ static constexpr int listSize = 16;
+ template<typename T>
+ class RingBuffer : public std::array<T, listSize>
+ {
+ public:
+ T &operator[](unsigned int index)
+ {
+ return std::array<T, listSize>::operator[](index % listSize);
+ }
+
+ const T &operator[](unsigned int index) const
+ {
+ return std::array<T, listSize>::operator[](index % listSize);
+ }
+ };
+
+ V4L2Device *device_;
+ std::unordered_map<const ControlId *, ControlParams> controlParams_;
+ unsigned int maxDelay_;
+
+ uint32_t queueCount_;
+ uint32_t writeCount_;
+ std::unordered_map<const ControlId *, RingBuffer<Info>> values_;
+ RingBuffer<unsigned int> cookies_;
+};
+
+} /* namespace RPi */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/raspberrypi/meson.build b/src/libcamera/pipeline/rpi/common/meson.build
index f1a2f5ee..8fb7e823 100644
--- a/src/libcamera/pipeline/raspberrypi/meson.build
+++ b/src/libcamera/pipeline/rpi/common/meson.build
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: CC0-1.0
libcamera_sources += files([
- 'dma_heaps.cpp',
- 'raspberrypi.cpp',
+ 'delayed_controls.cpp',
+ 'pipeline_base.cpp',
'rpi_stream.cpp',
])
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
new file mode 100644
index 00000000..7e420b3f
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -0,0 +1,1487 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019-2023, Raspberry Pi Ltd
+ *
+ * pipeline_base.cpp - Pipeline handler base class for Raspberry Pi devices
+ */
+
+#include "pipeline_base.h"
+
+#include <chrono>
+
+#include <linux/media-bus-format.h>
+#include <linux/videodev2.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/formats.h>
+#include <libcamera/logging.h>
+#include <libcamera/property_ids.h>
+
+#include "libcamera/internal/camera_lens.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+
+using namespace std::chrono_literals;
+
+namespace libcamera {
+
+using namespace RPi;
+
+LOG_DEFINE_CATEGORY(RPI)
+
+using StreamFlag = RPi::Stream::StreamFlag;
+
+namespace {
+
+constexpr unsigned int defaultRawBitDepth = 12;
+
+PixelFormat mbusCodeToPixelFormat(unsigned int code,
+ BayerFormat::Packing packingReq)
+{
+ BayerFormat bayer = BayerFormat::fromMbusCode(code);
+
+ ASSERT(bayer.isValid());
+
+ bayer.packing = packingReq;
+ PixelFormat pix = bayer.toPixelFormat();
+
+ /*
+ * Not all formats (e.g. 8-bit or 16-bit Bayer formats) can have packed
+ * variants. So if the PixelFormat returns as invalid, use the non-packed
+ * conversion instead.
+ */
+ if (!pix.isValid()) {
+ bayer.packing = BayerFormat::Packing::None;
+ pix = bayer.toPixelFormat();
+ }
+
+ return pix;
+}
+
+bool isMonoSensor(std::unique_ptr<CameraSensor> &sensor)
+{
+ unsigned int mbusCode = sensor->mbusCodes()[0];
+ const BayerFormat &bayer = BayerFormat::fromMbusCode(mbusCode);
+
+ return bayer.order == BayerFormat::Order::MONO;
+}
+
+const std::vector<ColorSpace> validColorSpaces = {
+ ColorSpace::Sycc,
+ ColorSpace::Smpte170m,
+ ColorSpace::Rec709
+};
+
+std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
+{
+ for (auto cs : validColorSpaces) {
+ if (colourSpace.primaries == cs.primaries &&
+ colourSpace.transferFunction == cs.transferFunction)
+ return cs;
+ }
+
+ return std::nullopt;
+}
+
+} /* namespace */
+
+/*
+ * Raspberry Pi drivers expect the following colour spaces:
+ * - V4L2_COLORSPACE_RAW for raw streams.
+ * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
+ * non-raw streams. Other fields such as transfer function, YCbCr encoding and
+ * quantisation are not used.
+ *
+ * The libcamera colour spaces that we wish to use corresponding to these are therefore:
+ * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
+ * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
+ * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
+ * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
+ */
+CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
+{
+ Status status = Valid;
+ yuvColorSpace_.reset();
+
+ for (auto cfg : config_) {
+ /* First fix up raw streams to have the "raw" colour space. */
+ if (PipelineHandlerBase::isRaw(cfg.pixelFormat)) {
+ /* If there was no value here, that doesn't count as "adjusted". */
+ if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw)
+ status = Adjusted;
+ cfg.colorSpace = ColorSpace::Raw;
+ continue;
+ }
+
+ /* Next we need to find our shared colour space. The first valid one will do. */
+ if (cfg.colorSpace && !yuvColorSpace_)
+ yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
+ }
+
+ /* If no colour space was given anywhere, choose sYCC. */
+ if (!yuvColorSpace_)
+ yuvColorSpace_ = ColorSpace::Sycc;
+
+ /* Note the version of this that any RGB streams will have to use. */
+ rgbColorSpace_ = yuvColorSpace_;
+ rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
+ rgbColorSpace_->range = ColorSpace::Range::Full;
+
+ /* Go through the streams again and force everyone to the same colour space. */
+ for (auto cfg : config_) {
+ if (cfg.colorSpace == ColorSpace::Raw)
+ continue;
+
+ if (PipelineHandlerBase::isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
+ /* Again, no value means "not adjusted". */
+ if (cfg.colorSpace)
+ status = Adjusted;
+ cfg.colorSpace = yuvColorSpace_;
+ }
+ if (PipelineHandlerBase::isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
+ /* Be nice, and let the YUV version count as non-adjusted too. */
+ if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
+ status = Adjusted;
+ cfg.colorSpace = rgbColorSpace_;
+ }
+ }
+
+ return status;
+}
+
+CameraConfiguration::Status RPiCameraConfiguration::validate()
+{
+ Status status = Valid;
+
+ if (config_.empty())
+ return Invalid;
+
+ /*
+ * Make sure that if a sensor configuration has been requested it
+ * is valid.
+ */
+ if (sensorConfig && !sensorConfig->isValid()) {
+ LOG(RPI, Error) << "Invalid sensor configuration request";
+ return Invalid;
+ }
+
+ status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
+
+ /*
+ * Validate the requested transform against the sensor capabilities and
+ * rotation and store the final combined transform that configure() will
+ * need to apply to the sensor to save us working it out again.
+ */
+ Orientation requestedOrientation = orientation;
+ combinedTransform_ = data_->sensor_->computeTransform(&orientation);
+ if (orientation != requestedOrientation)
+ status = Adjusted;
+
+ rawStreams_.clear();
+ outStreams_.clear();
+
+ for (const auto &[index, cfg] : utils::enumerate(config_)) {
+ if (PipelineHandlerBase::isRaw(cfg.pixelFormat))
+ rawStreams_.emplace_back(index, &cfg);
+ else
+ outStreams_.emplace_back(index, &cfg);
+ }
+
+ /* Sort the streams so the highest resolution is first. */
+ std::sort(rawStreams_.begin(), rawStreams_.end(),
+ [](auto &l, auto &r) { return l.cfg->size > r.cfg->size; });
+
+ std::sort(outStreams_.begin(), outStreams_.end(),
+ [](auto &l, auto &r) { return l.cfg->size > r.cfg->size; });
+
+ /* Compute the sensor's format then do any platform specific fixups. */
+ unsigned int bitDepth;
+ Size sensorSize;
+
+ if (sensorConfig) {
+ /* Use the application provided sensor configuration. */
+ bitDepth = sensorConfig->bitDepth;
+ sensorSize = sensorConfig->outputSize;
+ } else if (!rawStreams_.empty()) {
+ /* Use the RAW stream format and size. */
+ BayerFormat bayerFormat = BayerFormat::fromPixelFormat(rawStreams_[0].cfg->pixelFormat);
+ bitDepth = bayerFormat.bitDepth;
+ sensorSize = rawStreams_[0].cfg->size;
+ } else {
+ bitDepth = defaultRawBitDepth;
+ sensorSize = outStreams_[0].cfg->size;
+ }
+
+ sensorFormat_ = data_->findBestFormat(sensorSize, bitDepth);
+
+ /*
+ * If a sensor configuration has been requested, it should apply
+ * without modifications.
+ */
+ if (sensorConfig) {
+ BayerFormat bayer = BayerFormat::fromMbusCode(sensorFormat_.code);
+
+ if (bayer.bitDepth != sensorConfig->bitDepth ||
+ sensorFormat_.size != sensorConfig->outputSize) {
+ LOG(RPI, Error) << "Invalid sensor configuration: "
+ << "bitDepth/size mismatch";
+ return Invalid;
+ }
+ }
+
+ /* Start with some initial generic RAW stream adjustments. */
+ for (auto &raw : rawStreams_) {
+ StreamConfiguration *rawStream = raw.cfg;
+
+ /*
+ * Some sensors change their Bayer order when they are
+ * h-flipped or v-flipped, according to the transform. Adjust
+ * the RAW stream to match the computed sensor format by
+ * applying the sensor Bayer order resulting from the transform
+ * to the user request.
+ */
+
+ BayerFormat cfgBayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat);
+ cfgBayer.order = data_->sensor_->bayerOrder(combinedTransform_);
+
+ if (rawStream->pixelFormat != cfgBayer.toPixelFormat()) {
+ rawStream->pixelFormat = cfgBayer.toPixelFormat();
+ status = Adjusted;
+ }
+ }
+
+ /* Do any platform specific fixups. */
+ Status st = data_->platformValidate(this);
+ if (st == Invalid)
+ return Invalid;
+ else if (st == Adjusted)
+ status = Adjusted;
+
+ /* Further fixups on the RAW streams. */
+ for (auto &raw : rawStreams_) {
+ int ret = raw.dev->tryFormat(&raw.format);
+ if (ret)
+ return Invalid;
+
+ if (RPi::PipelineHandlerBase::updateStreamConfig(raw.cfg, raw.format))
+ status = Adjusted;
+ }
+
+ /* Further fixups on the ISP output streams. */
+ for (auto &out : outStreams_) {
+
+ /*
+ * We want to send the associated YCbCr info through to the driver.
+ *
+ * But for RGB streams, the YCbCr info gets overwritten on the way back
+ * so we must check against what the stream cfg says, not what we actually
+ * requested (which carefully included the YCbCr info)!
+ */
+ out.format.colorSpace = yuvColorSpace_;
+
+ LOG(RPI, Debug)
+ << "Try color space " << ColorSpace::toString(out.cfg->colorSpace);
+
+ int ret = out.dev->tryFormat(&out.format);
+ if (ret)
+ return Invalid;
+
+ if (RPi::PipelineHandlerBase::updateStreamConfig(out.cfg, out.format))
+ status = Adjusted;
+ }
+
+ return status;
+}
+
+bool PipelineHandlerBase::isRgb(const PixelFormat &pixFmt)
+{
+ const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
+ return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
+}
+
+bool PipelineHandlerBase::isYuv(const PixelFormat &pixFmt)
+{
+ /* The code below would return true for raw mono streams, so weed those out first. */
+ if (PipelineHandlerBase::isRaw(pixFmt))
+ return false;
+
+ const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
+ return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
+}
+
+bool PipelineHandlerBase::isRaw(const PixelFormat &pixFmt)
+{
+ /* This test works for both Bayer and raw mono formats. */
+ return BayerFormat::fromPixelFormat(pixFmt).isValid();
+}
+
+/*
+ * Adjust a StreamConfiguration fields to match a video device format.
+ * Returns true if the StreamConfiguration has been adjusted.
+ */
+bool PipelineHandlerBase::updateStreamConfig(StreamConfiguration *stream,
+ const V4L2DeviceFormat &format)
+{
+ const PixelFormat &pixFormat = format.fourcc.toPixelFormat();
+ bool adjusted = false;
+
+ if (stream->pixelFormat != pixFormat || stream->size != format.size) {
+ stream->pixelFormat = pixFormat;
+ stream->size = format.size;
+ adjusted = true;
+ }
+
+ if (stream->colorSpace != format.colorSpace) {
+ stream->colorSpace = format.colorSpace;
+ adjusted = true;
+ LOG(RPI, Debug)
+ << "Color space changed from "
+ << ColorSpace::toString(stream->colorSpace) << " to "
+ << ColorSpace::toString(format.colorSpace);
+ }
+
+ stream->stride = format.planes[0].bpl;
+ stream->frameSize = format.planes[0].size;
+
+ return adjusted;
+}
+
+/*
+ * Populate and return a video device format using a StreamConfiguration. */
+V4L2DeviceFormat PipelineHandlerBase::toV4L2DeviceFormat(const V4L2VideoDevice *dev,
+ const StreamConfiguration *stream)
+{
+ V4L2DeviceFormat deviceFormat;
+
+ const PixelFormatInfo &info = PixelFormatInfo::info(stream->pixelFormat);
+ deviceFormat.planesCount = info.numPlanes();
+ deviceFormat.fourcc = dev->toV4L2PixelFormat(stream->pixelFormat);
+ deviceFormat.size = stream->size;
+ deviceFormat.planes[0].bpl = stream->stride;
+ deviceFormat.colorSpace = stream->colorSpace;
+
+ return deviceFormat;
+}
+
+V4L2DeviceFormat PipelineHandlerBase::toV4L2DeviceFormat(const V4L2VideoDevice *dev,
+ const V4L2SubdeviceFormat &format,
+ BayerFormat::Packing packingReq)
+{
+ unsigned int code = format.code;
+ const PixelFormat pix = mbusCodeToPixelFormat(code, packingReq);
+ V4L2DeviceFormat deviceFormat;
+
+ deviceFormat.fourcc = dev->toV4L2PixelFormat(pix);
+ deviceFormat.size = format.size;
+ deviceFormat.colorSpace = format.colorSpace;
+ return deviceFormat;
+}
+
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerBase::generateConfiguration(Camera *camera, Span<const StreamRole> roles)
+{
+ CameraData *data = cameraData(camera);
+ std::unique_ptr<CameraConfiguration> config =
+ std::make_unique<RPiCameraConfiguration>(data);
+ V4L2SubdeviceFormat sensorFormat;
+ unsigned int bufferCount;
+ PixelFormat pixelFormat;
+ V4L2VideoDevice::Formats fmts;
+ Size size;
+ std::optional<ColorSpace> colorSpace;
+
+ if (roles.empty())
+ return config;
+
+ Size sensorSize = data->sensor_->resolution();
+ for (const StreamRole role : roles) {
+ switch (role) {
+ case StreamRole::Raw:
+ size = sensorSize;
+ sensorFormat = data->findBestFormat(size, defaultRawBitDepth);
+ pixelFormat = mbusCodeToPixelFormat(sensorFormat.code,
+ BayerFormat::Packing::CSI2);
+ ASSERT(pixelFormat.isValid());
+ colorSpace = ColorSpace::Raw;
+ bufferCount = 2;
+ break;
+
+ case StreamRole::StillCapture:
+ fmts = data->ispFormats();
+ pixelFormat = formats::YUV420;
+ /*
+ * Still image codecs usually expect the sYCC color space.
+ * Even RGB codecs will be fine as the RGB we get with the
+ * sYCC color space is the same as sRGB.
+ */
+ colorSpace = ColorSpace::Sycc;
+ /* Return the largest sensor resolution. */
+ size = sensorSize;
+ bufferCount = 1;
+ break;
+
+ case StreamRole::VideoRecording:
+ /*
+ * The colour denoise algorithm requires the analysis
+ * image, produced by the second ISP output, to be in
+ * YUV420 format. Select this format as the default, to
+ * maximize chances that it will be picked by
+ * applications and enable usage of the colour denoise
+ * algorithm.
+ */
+ fmts = data->ispFormats();
+ pixelFormat = formats::YUV420;
+ /*
+ * Choose a color space appropriate for video recording.
+ * Rec.709 will be a good default for HD resolutions.
+ */
+ colorSpace = ColorSpace::Rec709;
+ size = { 1920, 1080 };
+ bufferCount = 4;
+ break;
+
+ case StreamRole::Viewfinder:
+ fmts = data->ispFormats();
+ pixelFormat = formats::XRGB8888;
+ colorSpace = ColorSpace::Sycc;
+ size = { 800, 600 };
+ bufferCount = 4;
+ break;
+
+ default:
+ LOG(RPI, Error) << "Requested stream role not supported: "
+ << role;
+ return nullptr;
+ }
+
+ std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
+ if (role == StreamRole::Raw) {
+ /* Translate the MBUS codes to a PixelFormat. */
+ for (const auto &format : data->sensorFormats_) {
+ PixelFormat pf = mbusCodeToPixelFormat(format.first,
+ BayerFormat::Packing::CSI2);
+ if (pf.isValid())
+ deviceFormats.emplace(std::piecewise_construct, std::forward_as_tuple(pf),
+ std::forward_as_tuple(format.second.begin(), format.second.end()));
+ }
+ } else {
+ /*
+ * Translate the V4L2PixelFormat to PixelFormat. Note that we
+ * limit the recommended largest ISP output size to match the
+ * sensor resolution.
+ */
+ for (const auto &format : fmts) {
+ PixelFormat pf = format.first.toPixelFormat();
+ if (pf.isValid()) {
+ const SizeRange &ispSizes = format.second[0];
+ deviceFormats[pf].emplace_back(ispSizes.min, sensorSize,
+ ispSizes.hStep, ispSizes.vStep);
+ }
+ }
+ }
+
+ /* Add the stream format based on the device node used for the use case. */
+ StreamFormats formats(deviceFormats);
+ StreamConfiguration cfg(formats);
+ cfg.size = size;
+ cfg.pixelFormat = pixelFormat;
+ cfg.colorSpace = colorSpace;
+ cfg.bufferCount = bufferCount;
+ config->addConfiguration(cfg);
+ }
+
+ config->validate();
+
+ return config;
+}
+
+int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config)
+{
+ CameraData *data = cameraData(camera);
+ int ret;
+
+ /* Start by freeing all buffers and reset the stream states. */
+ data->freeBuffers();
+ for (auto const stream : data->streams_)
+ stream->clearFlags(StreamFlag::External);
+
+ /*
+ * Apply the format on the sensor with any cached transform.
+ *
+ * If the application has provided a sensor configuration apply it
+ * instead of just applying a format.
+ */
+ RPiCameraConfiguration *rpiConfig = static_cast<RPiCameraConfiguration *>(config);
+ V4L2SubdeviceFormat *sensorFormat = &rpiConfig->sensorFormat_;
+
+ if (rpiConfig->sensorConfig) {
+ ret = data->sensor_->applyConfiguration(*rpiConfig->sensorConfig,
+ rpiConfig->combinedTransform_,
+ sensorFormat);
+ } else {
+ ret = data->sensor_->setFormat(sensorFormat,
+ rpiConfig->combinedTransform_);
+ }
+ if (ret)
+ return ret;
+
+ /*
+ * Platform specific internal stream configuration. This also assigns
+ * external streams which get configured below.
+ */
+ ret = data->platformConfigure(rpiConfig);
+ if (ret)
+ return ret;
+
+ ipa::RPi::ConfigResult result;
+ ret = data->configureIPA(config, &result);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to configure the IPA: " << ret;
+ return ret;
+ }
+
+ /*
+ * Set the scaler crop to the value we are using (scaled to native sensor
+ * coordinates).
+ */
+ data->scalerCrop_ = data->scaleIspCrop(data->ispCrop_);
+
+ /*
+ * Update the ScalerCropMaximum to the correct value for this camera mode.
+ * For us, it's the same as the "analogue crop".
+ *
+ * \todo Make this property the ScalerCrop maximum value when dynamic
+ * controls are available and set it at validate() time
+ */
+ data->properties_.set(properties::ScalerCropMaximum, data->sensorInfo_.analogCrop);
+
+ /* Store the mode sensitivity for the application. */
+ data->properties_.set(properties::SensorSensitivity, result.modeSensitivity);
+
+ /* Update the controls that the Raspberry Pi IPA can handle. */
+ ControlInfoMap::Map ctrlMap;
+ for (auto const &c : result.controlInfo)
+ ctrlMap.emplace(c.first, c.second);
+
+ /* Add the ScalerCrop control limits based on the current mode. */
+ Rectangle ispMinCrop = data->scaleIspCrop(Rectangle(data->ispMinCropSize_));
+ ctrlMap[&controls::ScalerCrop] = ControlInfo(ispMinCrop, data->sensorInfo_.analogCrop, data->scalerCrop_);
+
+ data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap());
+
+ /* Setup the Video Mux/Bridge entities. */
+ for (auto &[device, link] : data->bridgeDevices_) {
+ /*
+ * Start by disabling all the sink pad links on the devices in the
+ * cascade, with the exception of the link connecting the device.
+ */
+ for (const MediaPad *p : device->entity()->pads()) {
+ if (!(p->flags() & MEDIA_PAD_FL_SINK))
+ continue;
+
+ for (MediaLink *l : p->links()) {
+ if (l != link)
+ l->setEnabled(false);
+ }
+ }
+
+ /*
+ * Next, enable the entity -> entity links, and setup the pad format.
+ *
+ * \todo Some bridge devices may chainge the media bus code, so we
+ * ought to read the source pad format and propagate it to the sink pad.
+ */
+ link->setEnabled(true);
+ const MediaPad *sinkPad = link->sink();
+ ret = device->setFormat(sinkPad->index(), sensorFormat);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to set format on " << device->entity()->name()
+ << " pad " << sinkPad->index()
+ << " with format " << *sensorFormat
+ << ": " << ret;
+ return ret;
+ }
+
+ LOG(RPI, Debug) << "Configured media link on device " << device->entity()->name()
+ << " on pad " << sinkPad->index();
+ }
+
+ return 0;
+}
+
+int PipelineHandlerBase::exportFrameBuffers([[maybe_unused]] Camera *camera, libcamera::Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+ RPi::Stream *s = static_cast<RPi::Stream *>(stream);
+ unsigned int count = stream->configuration().bufferCount;
+ int ret = s->dev()->exportBuffers(count, buffers);
+
+ s->setExportedBuffers(buffers);
+
+ return ret;
+}
+
+int PipelineHandlerBase::start(Camera *camera, const ControlList *controls)
+{
+ CameraData *data = cameraData(camera);
+ int ret;
+
+ /* Check if a ScalerCrop control was specified. */
+ if (controls)
+ data->applyScalerCrop(*controls);
+
+ /* Start the IPA. */
+ ipa::RPi::StartResult result;
+ data->ipa_->start(controls ? *controls : ControlList{ controls::controls },
+ &result);
+
+ /* Apply any gain/exposure settings that the IPA may have passed back. */
+ if (!result.controls.empty())
+ data->setSensorControls(result.controls);
+
+ /* Configure the number of dropped frames required on startup. */
+ data->dropFrameCount_ = data->config_.disableStartupFrameDrops
+ ? 0 : result.dropFrameCount;
+
+ for (auto const stream : data->streams_)
+ stream->resetBuffers();
+
+ if (!data->buffersAllocated_) {
+ /* Allocate buffers for internal pipeline usage. */
+ ret = prepareBuffers(camera);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to allocate buffers";
+ data->freeBuffers();
+ stop(camera);
+ return ret;
+ }
+ data->buffersAllocated_ = true;
+ }
+
+ /* We need to set the dropFrameCount_ before queueing buffers. */
+ ret = queueAllBuffers(camera);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to queue buffers";
+ stop(camera);
+ return ret;
+ }
+
+ /*
+ * Reset the delayed controls with the gain and exposure values set by
+ * the IPA.
+ */
+ data->delayedCtrls_->reset(0);
+ data->state_ = CameraData::State::Idle;
+
+ /* Enable SOF event generation. */
+ data->frontendDevice()->setFrameStartEnabled(true);
+
+ data->platformStart();
+
+ /* Start all streams. */
+ for (auto const stream : data->streams_) {
+ ret = stream->dev()->streamOn();
+ if (ret) {
+ stop(camera);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void PipelineHandlerBase::stopDevice(Camera *camera)
+{
+ CameraData *data = cameraData(camera);
+
+ data->state_ = CameraData::State::Stopped;
+ data->platformStop();
+
+ for (auto const stream : data->streams_)
+ stream->dev()->streamOff();
+
+ /* Disable SOF event generation. */
+ data->frontendDevice()->setFrameStartEnabled(false);
+
+ data->clearIncompleteRequests();
+
+ /* Stop the IPA. */
+ data->ipa_->stop();
+}
+
+void PipelineHandlerBase::releaseDevice(Camera *camera)
+{
+ CameraData *data = cameraData(camera);
+ data->freeBuffers();
+}
+
+int PipelineHandlerBase::queueRequestDevice(Camera *camera, Request *request)
+{
+ CameraData *data = cameraData(camera);
+
+ if (!data->isRunning())
+ return -EINVAL;
+
+ LOG(RPI, Debug) << "queueRequestDevice: New request sequence: "
+ << request->sequence();
+
+ /* Push all buffers supplied in the Request to the respective streams. */
+ for (auto stream : data->streams_) {
+ if (!(stream->getFlags() & StreamFlag::External))
+ continue;
+
+ FrameBuffer *buffer = request->findBuffer(stream);
+ if (buffer && !stream->getBufferId(buffer)) {
+ /*
+ * This buffer is not recognised, so it must have been allocated
+ * outside the v4l2 device. Store it in the stream buffer list
+ * so we can track it.
+ */
+ stream->setExportedBuffer(buffer);
+ }
+
+ /*
+ * If no buffer is provided by the request for this stream, we
+ * queue a nullptr to the stream to signify that it must use an
+ * internally allocated buffer for this capture request. This
+ * buffer will not be given back to the application, but is used
+ * to support the internal pipeline flow.
+ *
+ * The below queueBuffer() call will do nothing if there are not
+ * enough internal buffers allocated, but this will be handled by
+ * queuing the request for buffers in the RPiStream object.
+ */
+ int ret = stream->queueBuffer(buffer);
+ if (ret)
+ return ret;
+ }
+
+ /* Push the request to the back of the queue. */
+ data->requestQueue_.push(request);
+ data->handleState();
+
+ return 0;
+}
+
+int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &cameraData,
+ MediaDevice *frontend, const std::string &frontendName,
+ MediaDevice *backend, MediaEntity *sensorEntity)
+{
+ CameraData *data = cameraData.get();
+ int ret;
+
+ data->sensor_ = std::make_unique<CameraSensor>(sensorEntity);
+ if (!data->sensor_)
+ return -EINVAL;
+
+ if (data->sensor_->init())
+ return -EINVAL;
+
+ /* Populate the map of sensor supported formats and sizes. */
+ for (auto const mbusCode : data->sensor_->mbusCodes())
+ data->sensorFormats_.emplace(mbusCode,
+ data->sensor_->sizes(mbusCode));
+
+ /*
+ * Enumerate all the Video Mux/Bridge devices across the sensor -> Fr
+ * chain. There may be a cascade of devices in this chain!
+ */
+ MediaLink *link = sensorEntity->getPadByIndex(0)->links()[0];
+ data->enumerateVideoDevices(link, frontendName);
+
+ ipa::RPi::InitResult result;
+ if (data->loadIPA(&result)) {
+ LOG(RPI, Error) << "Failed to load a suitable IPA library";
+ return -EINVAL;
+ }
+
+ /*
+ * Setup our delayed control writer with the sensor default
+ * gain and exposure delays. Mark VBLANK for priority write.
+ */
+ std::unordered_map<uint32_t, RPi::DelayedControls::ControlParams> params = {
+ { V4L2_CID_ANALOGUE_GAIN, { result.sensorConfig.gainDelay, false } },
+ { V4L2_CID_EXPOSURE, { result.sensorConfig.exposureDelay, false } },
+ { V4L2_CID_HBLANK, { result.sensorConfig.hblankDelay, false } },
+ { V4L2_CID_VBLANK, { result.sensorConfig.vblankDelay, true } }
+ };
+ data->delayedCtrls_ = std::make_unique<RPi::DelayedControls>(data->sensor_->device(), params);
+ data->sensorMetadata_ = result.sensorConfig.sensorMetadata;
+
+ /* Register initial controls that the Raspberry Pi IPA can handle. */
+ data->controlInfo_ = std::move(result.controlInfo);
+
+ /* Initialize the camera properties. */
+ data->properties_ = data->sensor_->properties();
+
+ /*
+ * The V4L2_CID_NOTIFY_GAINS control, if present, is used to inform the
+ * sensor of the colour gains. It is defined to be a linear gain where
+ * the default value represents a gain of exactly one.
+ */
+ auto it = data->sensor_->controls().find(V4L2_CID_NOTIFY_GAINS);
+ if (it != data->sensor_->controls().end())
+ data->notifyGainsUnity_ = it->second.def().get<int32_t>();
+
+ /*
+ * Set a default value for the ScalerCropMaximum property to show
+ * that we support its use, however, initialise it to zero because
+ * it's not meaningful until a camera mode has been chosen.
+ */
+ data->properties_.set(properties::ScalerCropMaximum, Rectangle{});
+
+ ret = platformRegister(cameraData, frontend, backend);
+ if (ret)
+ return ret;
+
+ ret = data->loadPipelineConfiguration();
+ if (ret) {
+ LOG(RPI, Error) << "Unable to load pipeline configuration";
+ return ret;
+ }
+
+ /* Setup the general IPA signal handlers. */
+ data->frontendDevice()->dequeueTimeout.connect(data, &RPi::CameraData::cameraTimeout);
+ data->frontendDevice()->frameStart.connect(data, &RPi::CameraData::frameStarted);
+ data->ipa_->setDelayedControls.connect(data, &CameraData::setDelayedControls);
+ data->ipa_->setLensControls.connect(data, &CameraData::setLensControls);
+ data->ipa_->metadataReady.connect(data, &CameraData::metadataReady);
+
+ return 0;
+}
+
+void PipelineHandlerBase::mapBuffers(Camera *camera, const BufferMap &buffers, unsigned int mask)
+{
+ CameraData *data = cameraData(camera);
+ std::vector<IPABuffer> bufferIds;
+ /*
+ * Link the FrameBuffers with the id (key value) in the map stored in
+ * the RPi stream object - along with an identifier mask.
+ *
+ * This will allow us to identify buffers passed between the pipeline
+ * handler and the IPA.
+ */
+ for (auto const &it : buffers) {
+ bufferIds.push_back(IPABuffer(mask | it.first,
+ it.second.buffer->planes()));
+ data->bufferIds_.insert(mask | it.first);
+ }
+
+ data->ipa_->mapBuffers(bufferIds);
+}
+
+int PipelineHandlerBase::queueAllBuffers(Camera *camera)
+{
+ CameraData *data = cameraData(camera);
+ int ret;
+
+ for (auto const stream : data->streams_) {
+ if (!(stream->getFlags() & StreamFlag::External)) {
+ ret = stream->queueAllBuffers();
+ if (ret < 0)
+ return ret;
+ } else {
+ /*
+ * For external streams, we must queue up a set of internal
+ * buffers to handle the number of drop frames requested by
+ * the IPA. This is done by passing nullptr in queueBuffer().
+ *
+ * The below queueBuffer() call will do nothing if there
+ * are not enough internal buffers allocated, but this will
+ * be handled by queuing the request for buffers in the
+ * RPiStream object.
+ */
+ unsigned int i;
+ for (i = 0; i < data->dropFrameCount_; i++) {
+ ret = stream->queueBuffer(nullptr);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+double CameraData::scoreFormat(double desired, double actual) const
+{
+ double score = desired - actual;
+ /* Smaller desired dimensions are preferred. */
+ if (score < 0.0)
+ score = (-score) / 8;
+ /* Penalise non-exact matches. */
+ if (actual != desired)
+ score *= 2;
+
+ return score;
+}
+
+V4L2SubdeviceFormat CameraData::findBestFormat(const Size &req, unsigned int bitDepth) const
+{
+ double bestScore = std::numeric_limits<double>::max(), score;
+ V4L2SubdeviceFormat bestFormat;
+ bestFormat.colorSpace = ColorSpace::Raw;
+
+ constexpr float penaltyAr = 1500.0;
+ constexpr float penaltyBitDepth = 500.0;
+
+ /* Calculate the closest/best mode from the user requested size. */
+ for (const auto &iter : sensorFormats_) {
+ const unsigned int mbusCode = iter.first;
+ const PixelFormat format = mbusCodeToPixelFormat(mbusCode,
+ BayerFormat::Packing::None);
+ const PixelFormatInfo &info = PixelFormatInfo::info(format);
+
+ for (const Size &size : iter.second) {
+ double reqAr = static_cast<double>(req.width) / req.height;
+ double fmtAr = static_cast<double>(size.width) / size.height;
+
+ /* Score the dimensions for closeness. */
+ score = scoreFormat(req.width, size.width);
+ score += scoreFormat(req.height, size.height);
+ score += penaltyAr * scoreFormat(reqAr, fmtAr);
+
+ /* Add any penalties... this is not an exact science! */
+ score += utils::abs_diff(info.bitsPerPixel, bitDepth) * penaltyBitDepth;
+
+ if (score <= bestScore) {
+ bestScore = score;
+ bestFormat.code = mbusCode;
+ bestFormat.size = size;
+ }
+
+ LOG(RPI, Debug) << "Format: " << size
+ << " fmt " << format
+ << " Score: " << score
+ << " (best " << bestScore << ")";
+ }
+ }
+
+ return bestFormat;
+}
+
+void CameraData::freeBuffers()
+{
+ if (ipa_) {
+ /*
+ * Copy the buffer ids from the unordered_set to a vector to
+ * pass to the IPA.
+ */
+ std::vector<unsigned int> bufferIds(bufferIds_.begin(),
+ bufferIds_.end());
+ ipa_->unmapBuffers(bufferIds);
+ bufferIds_.clear();
+ }
+
+ for (auto const stream : streams_)
+ stream->releaseBuffers();
+
+ platformFreeBuffers();
+
+ buffersAllocated_ = false;
+}
+
+/*
+ * enumerateVideoDevices() iterates over the Media Controller topology, starting
+ * at the sensor and finishing at the frontend. For each sensor, CameraData stores
+ * a unique list of any intermediate video mux or bridge devices connected in a
+ * cascade, together with the entity to entity link.
+ *
+ * Entity pad configuration and link enabling happens at the end of configure().
+ * We first disable all pad links on each entity device in the chain, and then
+ * selectively enabling the specific links to link sensor to the frontend across
+ * all intermediate muxes and bridges.
+ *
+ * In the cascaded topology below, if Sensor1 is used, the Mux2 -> Mux1 link
+ * will be disabled, and Sensor1 -> Mux1 -> Frontend links enabled. Alternatively,
+ * if Sensor3 is used, the Sensor2 -> Mux2 and Sensor1 -> Mux1 links are disabled,
+ * and Sensor3 -> Mux2 -> Mux1 -> Frontend links are enabled. All other links will
+ * remain unchanged.
+ *
+ * +----------+
+ * | FE |
+ * +-----^----+
+ * |
+ * +---+---+
+ * | Mux1 |<------+
+ * +--^---- |
+ * | |
+ * +-----+---+ +---+---+
+ * | Sensor1 | | Mux2 |<--+
+ * +---------+ +-^-----+ |
+ * | |
+ * +-------+-+ +---+-----+
+ * | Sensor2 | | Sensor3 |
+ * +---------+ +---------+
+ */
+void CameraData::enumerateVideoDevices(MediaLink *link, const std::string &frontend)
+{
+ const MediaPad *sinkPad = link->sink();
+ const MediaEntity *entity = sinkPad->entity();
+ bool frontendFound = false;
+
+ /* We only deal with Video Mux and Bridge devices in cascade. */
+ if (entity->function() != MEDIA_ENT_F_VID_MUX &&
+ entity->function() != MEDIA_ENT_F_VID_IF_BRIDGE)
+ return;
+
+ /* Find the source pad for this Video Mux or Bridge device. */
+ const MediaPad *sourcePad = nullptr;
+ for (const MediaPad *pad : entity->pads()) {
+ if (pad->flags() & MEDIA_PAD_FL_SOURCE) {
+ /*
+ * We can only deal with devices that have a single source
+ * pad. If this device has multiple source pads, ignore it
+ * and this branch in the cascade.
+ */
+ if (sourcePad)
+ return;
+
+ sourcePad = pad;
+ }
+ }
+
+ LOG(RPI, Debug) << "Found video mux device " << entity->name()
+ << " linked to sink pad " << sinkPad->index();
+
+ bridgeDevices_.emplace_back(std::make_unique<V4L2Subdevice>(entity), link);
+ bridgeDevices_.back().first->open();
+
+ /*
+ * Iterate through all the sink pad links down the cascade to find any
+ * other Video Mux and Bridge devices.
+ */
+ for (MediaLink *l : sourcePad->links()) {
+ enumerateVideoDevices(l, frontend);
+ /* Once we reach the Frontend entity, we are done. */
+ if (l->sink()->entity()->name() == frontend) {
+ frontendFound = true;
+ break;
+ }
+ }
+
+ /* This identifies the end of our entity enumeration recursion. */
+ if (link->source()->entity()->function() == MEDIA_ENT_F_CAM_SENSOR) {
+ /*
+ * If the frontend is not at the end of this cascade, we cannot
+ * configure this topology automatically, so remove all entity
+ * references.
+ */
+ if (!frontendFound) {
+ LOG(RPI, Warning) << "Cannot automatically configure this MC topology!";
+ bridgeDevices_.clear();
+ }
+ }
+}
+
+int CameraData::loadPipelineConfiguration()
+{
+ config_ = {
+ .disableStartupFrameDrops = false,
+ .cameraTimeoutValue = 0,
+ };
+
+ /* Initial configuration of the platform, in case no config file is present */
+ platformPipelineConfigure({});
+
+ char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RPI_CONFIG_FILE");
+ if (!configFromEnv || *configFromEnv == '\0')
+ return 0;
+
+ std::string filename = std::string(configFromEnv);
+ File file(filename);
+
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ LOG(RPI, Warning) << "Failed to open configuration file '" << filename << "'"
+ << ", using defaults";
+ return 0;
+ }
+
+ LOG(RPI, Info) << "Using configuration file '" << filename << "'";
+
+ std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+ if (!root) {
+ LOG(RPI, Warning) << "Failed to parse configuration file, using defaults";
+ return 0;
+ }
+
+ std::optional<double> ver = (*root)["version"].get<double>();
+ if (!ver || *ver != 1.0) {
+ LOG(RPI, Warning) << "Unexpected configuration file version reported: "
+ << *ver;
+ return 0;
+ }
+
+ const YamlObject &phConfig = (*root)["pipeline_handler"];
+
+ config_.disableStartupFrameDrops =
+ phConfig["disable_startup_frame_drops"].get<bool>(config_.disableStartupFrameDrops);
+
+ config_.cameraTimeoutValue =
+ phConfig["camera_timeout_value_ms"].get<unsigned int>(config_.cameraTimeoutValue);
+
+ if (config_.cameraTimeoutValue) {
+ /* Disable the IPA signal to control timeout and set the user requested value. */
+ ipa_->setCameraTimeout.disconnect();
+ frontendDevice()->setDequeueTimeout(config_.cameraTimeoutValue * 1ms);
+ }
+
+ return platformPipelineConfigure(root);
+}
+
+int CameraData::loadIPA(ipa::RPi::InitResult *result)
+{
+ int ret;
+
+ ipa_ = IPAManager::createIPA<ipa::RPi::IPAProxyRPi>(pipe(), 1, 1);
+
+ if (!ipa_)
+ return -ENOENT;
+
+ /*
+ * The configuration (tuning file) is made from the sensor name unless
+ * the environment variable overrides it.
+ */
+ std::string configurationFile;
+ char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RPI_TUNING_FILE");
+ if (!configFromEnv || *configFromEnv == '\0') {
+ std::string model = sensor_->model();
+ if (isMonoSensor(sensor_))
+ model += "_mono";
+ configurationFile = ipa_->configurationFile(model + ".json");
+ } else {
+ configurationFile = std::string(configFromEnv);
+ }
+
+ IPASettings settings(configurationFile, sensor_->model());
+ ipa::RPi::InitParams params;
+
+ ret = sensor_->sensorInfo(&params.sensorInfo);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to retrieve camera sensor info";
+ return ret;
+ }
+
+ params.lensPresent = !!sensor_->focusLens();
+ ret = platformInitIpa(params);
+ if (ret)
+ return ret;
+
+ return ipa_->init(settings, params, result);
+}
+
+int CameraData::configureIPA(const CameraConfiguration *config, ipa::RPi::ConfigResult *result)
+{
+ ipa::RPi::ConfigParams params;
+ int ret;
+
+ params.sensorControls = sensor_->controls();
+ if (sensor_->focusLens())
+ params.lensControls = sensor_->focusLens()->controls();
+
+ ret = platformConfigureIpa(params);
+ if (ret)
+ return ret;
+
+ /* We store the IPACameraSensorInfo for digital zoom calculations. */
+ ret = sensor_->sensorInfo(&sensorInfo_);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to retrieve camera sensor info";
+ return ret;
+ }
+
+ /* Always send the user transform to the IPA. */
+ Transform transform = config->orientation / Orientation::Rotate0;
+ params.transform = static_cast<unsigned int>(transform);
+
+ /* Ready the IPA - it must know about the sensor resolution. */
+ ret = ipa_->configure(sensorInfo_, params, result);
+ if (ret < 0) {
+ LOG(RPI, Error) << "IPA configuration failed!";
+ return -EPIPE;
+ }
+
+ if (!result->sensorControls.empty())
+ setSensorControls(result->sensorControls);
+ if (!result->lensControls.empty())
+ setLensControls(result->lensControls);
+
+ return 0;
+}
+
+void CameraData::metadataReady(const ControlList &metadata)
+{
+ if (!isRunning())
+ return;
+
+ /* Add to the Request metadata buffer what the IPA has provided. */
+ /* Last thing to do is to fill up the request metadata. */
+ Request *request = requestQueue_.front();
+ request->metadata().merge(metadata);
+
+ /*
+ * Inform the sensor of the latest colour gains if it has the
+ * V4L2_CID_NOTIFY_GAINS control (which means notifyGainsUnity_ is set).
+ */
+ const auto &colourGains = metadata.get(libcamera::controls::ColourGains);
+ if (notifyGainsUnity_ && colourGains) {
+ /* The control wants linear gains in the order B, Gb, Gr, R. */
+ ControlList ctrls(sensor_->controls());
+ std::array<int32_t, 4> gains{
+ static_cast<int32_t>((*colourGains)[1] * *notifyGainsUnity_),
+ *notifyGainsUnity_,
+ *notifyGainsUnity_,
+ static_cast<int32_t>((*colourGains)[0] * *notifyGainsUnity_)
+ };
+ ctrls.set(V4L2_CID_NOTIFY_GAINS, Span<const int32_t>{ gains });
+
+ sensor_->setControls(&ctrls);
+ }
+}
+
+void CameraData::setDelayedControls(const ControlList &controls, uint32_t delayContext)
+{
+ if (!delayedCtrls_->push(controls, delayContext))
+ LOG(RPI, Error) << "V4L2 DelayedControl set failed";
+}
+
+void CameraData::setLensControls(const ControlList &controls)
+{
+ CameraLens *lens = sensor_->focusLens();
+
+ if (lens && controls.contains(V4L2_CID_FOCUS_ABSOLUTE)) {
+ ControlValue const &focusValue = controls.get(V4L2_CID_FOCUS_ABSOLUTE);
+ lens->setFocusPosition(focusValue.get<int32_t>());
+ }
+}
+
+void CameraData::setSensorControls(ControlList &controls)
+{
+ /*
+ * We need to ensure that if both VBLANK and EXPOSURE are present, the
+ * former must be written ahead of, and separately from EXPOSURE to avoid
+ * V4L2 rejecting the latter. This is identical to what DelayedControls
+ * does with the priority write flag.
+ *
+ * As a consequence of the below logic, VBLANK gets set twice, and we
+ * rely on the v4l2 framework to not pass the second control set to the
+ * driver as the actual control value has not changed.
+ */
+ if (controls.contains(V4L2_CID_EXPOSURE) && controls.contains(V4L2_CID_VBLANK)) {
+ ControlList vblank_ctrl;
+
+ vblank_ctrl.set(V4L2_CID_VBLANK, controls.get(V4L2_CID_VBLANK));
+ sensor_->setControls(&vblank_ctrl);
+ }
+
+ sensor_->setControls(&controls);
+}
+
+Rectangle CameraData::scaleIspCrop(const Rectangle &ispCrop) const
+{
+ /*
+ * Scale a crop rectangle defined in the ISP's coordinates into native sensor
+ * coordinates.
+ */
+ Rectangle nativeCrop = ispCrop.scaledBy(sensorInfo_.analogCrop.size(),
+ sensorInfo_.outputSize);
+ nativeCrop.translateBy(sensorInfo_.analogCrop.topLeft());
+ return nativeCrop;
+}
+
+void CameraData::applyScalerCrop(const ControlList &controls)
+{
+ const auto &scalerCrop = controls.get<Rectangle>(controls::ScalerCrop);
+ if (scalerCrop) {
+ Rectangle nativeCrop = *scalerCrop;
+
+ if (!nativeCrop.width || !nativeCrop.height)
+ nativeCrop = { 0, 0, 1, 1 };
+
+ /* Create a version of the crop scaled to ISP (camera mode) pixels. */
+ Rectangle ispCrop = nativeCrop.translatedBy(-sensorInfo_.analogCrop.topLeft());
+ ispCrop.scaleBy(sensorInfo_.outputSize, sensorInfo_.analogCrop.size());
+
+ /*
+ * The crop that we set must be:
+ * 1. At least as big as ispMinCropSize_, once that's been
+ * enlarged to the same aspect ratio.
+ * 2. With the same mid-point, if possible.
+ * 3. But it can't go outside the sensor area.
+ */
+ Size minSize = ispMinCropSize_.expandedToAspectRatio(nativeCrop.size());
+ Size size = ispCrop.size().expandedTo(minSize);
+ ispCrop = size.centeredTo(ispCrop.center()).enclosedIn(Rectangle(sensorInfo_.outputSize));
+
+ if (ispCrop != ispCrop_) {
+ ispCrop_ = ispCrop;
+ platformSetIspCrop();
+
+ /*
+ * Also update the ScalerCrop in the metadata with what we actually
+ * used. But we must first rescale that from ISP (camera mode) pixels
+ * back into sensor native pixels.
+ */
+ scalerCrop_ = scaleIspCrop(ispCrop_);
+ }
+ }
+}
+
+void CameraData::cameraTimeout()
+{
+ LOG(RPI, Error) << "Camera frontend has timed out!";
+ LOG(RPI, Error) << "Please check that your camera sensor connector is attached securely.";
+ LOG(RPI, Error) << "Alternatively, try another cable and/or sensor.";
+
+ state_ = CameraData::State::Error;
+ platformStop();
+
+ /*
+ * To allow the application to attempt a recovery from this timeout,
+ * stop all devices streaming, and return any outstanding requests as
+ * incomplete and cancelled.
+ */
+ for (auto const stream : streams_)
+ stream->dev()->streamOff();
+
+ clearIncompleteRequests();
+}
+
+void CameraData::frameStarted(uint32_t sequence)
+{
+ LOG(RPI, Debug) << "Frame start " << sequence;
+
+ /* Write any controls for the next frame as soon as we can. */
+ delayedCtrls_->applyControls(sequence);
+}
+
+void CameraData::clearIncompleteRequests()
+{
+ /*
+ * All outstanding requests (and associated buffers) must be returned
+ * back to the application.
+ */
+ while (!requestQueue_.empty()) {
+ Request *request = requestQueue_.front();
+
+ for (auto &b : request->buffers()) {
+ FrameBuffer *buffer = b.second;
+ /*
+ * Has the buffer already been handed back to the
+ * request? If not, do so now.
+ */
+ if (buffer->request()) {
+ buffer->_d()->cancel();
+ pipe()->completeBuffer(request, buffer);
+ }
+ }
+
+ pipe()->completeRequest(request);
+ requestQueue_.pop();
+ }
+}
+
+void CameraData::handleStreamBuffer(FrameBuffer *buffer, RPi::Stream *stream)
+{
+ /*
+ * It is possible to be here without a pending request, so check
+ * that we actually have one to action, otherwise we just return
+ * buffer back to the stream.
+ */
+ Request *request = requestQueue_.empty() ? nullptr : requestQueue_.front();
+ if (!dropFrameCount_ && request && request->findBuffer(stream) == buffer) {
+ /*
+ * Tag the buffer as completed, returning it to the
+ * application.
+ */
+ LOG(RPI, Debug) << "Completing request buffer for stream "
+ << stream->name();
+ pipe()->completeBuffer(request, buffer);
+ } else {
+ /*
+ * This buffer was not part of the Request (which happens if an
+ * internal buffer was used for an external stream, or
+ * unconditionally for internal streams), or there is no pending
+ * request, so we can recycle it.
+ */
+ LOG(RPI, Debug) << "Returning buffer to stream "
+ << stream->name();
+ stream->returnBuffer(buffer);
+ }
+}
+
+void CameraData::handleState()
+{
+ switch (state_) {
+ case State::Stopped:
+ case State::Busy:
+ case State::Error:
+ break;
+
+ case State::IpaComplete:
+ /* If the request is completed, we will switch to Idle state. */
+ checkRequestCompleted();
+ /*
+ * No break here, we want to try running the pipeline again.
+ * The fallthrough clause below suppresses compiler warnings.
+ */
+ [[fallthrough]];
+
+ case State::Idle:
+ tryRunPipeline();
+ break;
+ }
+}
+
+void CameraData::checkRequestCompleted()
+{
+ bool requestCompleted = false;
+ /*
+ * If we are dropping this frame, do not touch the request, simply
+ * change the state to IDLE when ready.
+ */
+ if (!dropFrameCount_) {
+ Request *request = requestQueue_.front();
+ if (request->hasPendingBuffers())
+ return;
+
+ /* Must wait for metadata to be filled in before completing. */
+ if (state_ != State::IpaComplete)
+ return;
+
+ LOG(RPI, Debug) << "Completing request sequence: "
+ << request->sequence();
+
+ pipe()->completeRequest(request);
+ requestQueue_.pop();
+ requestCompleted = true;
+ }
+
+ /*
+ * Make sure we have three outputs completed in the case of a dropped
+ * frame.
+ */
+ if (state_ == State::IpaComplete &&
+ ((ispOutputCount_ == ispOutputTotal_ && dropFrameCount_) ||
+ requestCompleted)) {
+ LOG(RPI, Debug) << "Going into Idle state";
+ state_ = State::Idle;
+ if (dropFrameCount_) {
+ dropFrameCount_--;
+ LOG(RPI, Debug) << "Dropping frame at the request of the IPA ("
+ << dropFrameCount_ << " left)";
+ }
+ }
+}
+
+void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request *request)
+{
+ request->metadata().set(controls::SensorTimestamp,
+ bufferControls.get(controls::SensorTimestamp).value_or(0));
+
+ request->metadata().set(controls::ScalerCrop, scalerCrop_);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h
new file mode 100644
index 00000000..0608bbe5
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h
@@ -0,0 +1,286 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019-2023, Raspberry Pi Ltd
+ *
+ * pipeline_base.h - Pipeline handler base class for Raspberry Pi devices
+ */
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <queue>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <libcamera/controls.h>
+#include <libcamera/request.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/media_object.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+#include "libcamera/internal/yaml_parser.h"
+
+#include <libcamera/ipa/raspberrypi_ipa_interface.h>
+#include <libcamera/ipa/raspberrypi_ipa_proxy.h>
+
+#include "delayed_controls.h"
+#include "rpi_stream.h"
+
+using namespace std::chrono_literals;
+
+namespace libcamera {
+
+namespace RPi {
+
+/* Map of mbus codes to supported sizes reported by the sensor. */
+using SensorFormats = std::map<unsigned int, std::vector<Size>>;
+
+class RPiCameraConfiguration;
+class CameraData : public Camera::Private
+{
+public:
+ CameraData(PipelineHandler *pipe)
+ : Camera::Private(pipe), state_(State::Stopped),
+ dropFrameCount_(0), buffersAllocated_(false),
+ ispOutputCount_(0), ispOutputTotal_(0)
+ {
+ }
+
+ virtual ~CameraData()
+ {
+ }
+
+ virtual CameraConfiguration::Status platformValidate(RPiCameraConfiguration *rpiConfig) const = 0;
+ virtual int platformConfigure(const RPiCameraConfiguration *rpiConfig) = 0;
+ virtual void platformStart() = 0;
+ virtual void platformStop() = 0;
+
+ double scoreFormat(double desired, double actual) const;
+ V4L2SubdeviceFormat findBestFormat(const Size &req, unsigned int bitDepth) const;
+
+ void freeBuffers();
+ virtual void platformFreeBuffers() = 0;
+
+ void enumerateVideoDevices(MediaLink *link, const std::string &frontend);
+
+ int loadPipelineConfiguration();
+ int loadIPA(ipa::RPi::InitResult *result);
+ int configureIPA(const CameraConfiguration *config, ipa::RPi::ConfigResult *result);
+ virtual int platformInitIpa(ipa::RPi::InitParams &params) = 0;
+ virtual int platformConfigureIpa(ipa::RPi::ConfigParams &params) = 0;
+
+ void metadataReady(const ControlList &metadata);
+ void setDelayedControls(const ControlList &controls, uint32_t delayContext);
+ void setLensControls(const ControlList &controls);
+ void setSensorControls(ControlList &controls);
+
+ Rectangle scaleIspCrop(const Rectangle &ispCrop) const;
+ void applyScalerCrop(const ControlList &controls);
+ virtual void platformSetIspCrop() = 0;
+
+ void cameraTimeout();
+ void frameStarted(uint32_t sequence);
+
+ void clearIncompleteRequests();
+ void handleStreamBuffer(FrameBuffer *buffer, Stream *stream);
+ void handleState();
+
+ virtual V4L2VideoDevice::Formats ispFormats() const = 0;
+ virtual V4L2VideoDevice::Formats rawFormats() const = 0;
+ virtual V4L2VideoDevice *frontendDevice() = 0;
+
+ virtual int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) = 0;
+
+ std::unique_ptr<ipa::RPi::IPAProxyRPi> ipa_;
+
+ std::unique_ptr<CameraSensor> sensor_;
+ SensorFormats sensorFormats_;
+
+ /* The vector below is just for convenience when iterating over all streams. */
+ std::vector<Stream *> streams_;
+ /* Stores the ids of the buffers mapped in the IPA. */
+ std::unordered_set<unsigned int> bufferIds_;
+ /*
+ * Stores a cascade of Video Mux or Bridge devices between the sensor and
+ * Unicam together with media link across the entities.
+ */
+ std::vector<std::pair<std::unique_ptr<V4L2Subdevice>, MediaLink *>> bridgeDevices_;
+
+ std::unique_ptr<DelayedControls> delayedCtrls_;
+ bool sensorMetadata_;
+
+ /*
+ * All the functions in this class are called from a single calling
+ * thread. So, we do not need to have any mutex to protect access to any
+ * of the variables below.
+ */
+ enum class State { Stopped, Idle, Busy, IpaComplete, Error };
+ State state_;
+
+ bool isRunning()
+ {
+ return state_ != State::Stopped && state_ != State::Error;
+ }
+
+ std::queue<Request *> requestQueue_;
+
+ /* For handling digital zoom. */
+ IPACameraSensorInfo sensorInfo_;
+ Rectangle ispCrop_; /* crop in ISP (camera mode) pixels */
+ Rectangle scalerCrop_; /* crop in sensor native pixels */
+ Size ispMinCropSize_;
+
+ unsigned int dropFrameCount_;
+
+ /*
+ * If set, this stores the value that represets a gain of one for
+ * the V4L2_CID_NOTIFY_GAINS control.
+ */
+ std::optional<int32_t> notifyGainsUnity_;
+
+ /* Have internal buffers been allocated? */
+ bool buffersAllocated_;
+
+ struct Config {
+ /*
+ * Override any request from the IPA to drop a number of startup
+ * frames.
+ */
+ bool disableStartupFrameDrops;
+ /*
+ * Override the camera timeout value calculated by the IPA based
+ * on frame durations.
+ */
+ unsigned int cameraTimeoutValue;
+ };
+
+ Config config_;
+
+protected:
+ void fillRequestMetadata(const ControlList &bufferControls,
+ Request *request);
+
+ virtual void tryRunPipeline() = 0;
+
+ unsigned int ispOutputCount_;
+ unsigned int ispOutputTotal_;
+
+private:
+ void checkRequestCompleted();
+};
+
+class PipelineHandlerBase : public PipelineHandler
+{
+public:
+ PipelineHandlerBase(CameraManager *manager)
+ : PipelineHandler(manager)
+ {
+ }
+
+ virtual ~PipelineHandlerBase()
+ {
+ }
+
+ static bool isRgb(const PixelFormat &pixFmt);
+ static bool isYuv(const PixelFormat &pixFmt);
+ static bool isRaw(const PixelFormat &pixFmt);
+
+ static bool updateStreamConfig(StreamConfiguration *stream,
+ const V4L2DeviceFormat &format);
+ static V4L2DeviceFormat toV4L2DeviceFormat(const V4L2VideoDevice *dev,
+ const StreamConfiguration *stream);
+ static V4L2DeviceFormat toV4L2DeviceFormat(const V4L2VideoDevice *dev,
+ const V4L2SubdeviceFormat &format,
+ BayerFormat::Packing packingReq);
+
+ std::unique_ptr<CameraConfiguration>
+ generateConfiguration(Camera *camera, Span<const StreamRole> roles) override;
+ int configure(Camera *camera, CameraConfiguration *config) override;
+
+ int exportFrameBuffers(Camera *camera, libcamera::Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
+
+ int start(Camera *camera, const ControlList *controls) override;
+ void stopDevice(Camera *camera) override;
+ void releaseDevice(Camera *camera) override;
+
+ int queueRequestDevice(Camera *camera, Request *request) override;
+
+protected:
+ int registerCamera(std::unique_ptr<RPi::CameraData> &cameraData,
+ MediaDevice *frontent, const std::string &frontendName,
+ MediaDevice *backend, MediaEntity *sensorEntity);
+
+ void mapBuffers(Camera *camera, const BufferMap &buffers, unsigned int mask);
+
+ virtual int platformRegister(std::unique_ptr<CameraData> &cameraData,
+ MediaDevice *unicam, MediaDevice *isp) = 0;
+
+private:
+ CameraData *cameraData(Camera *camera)
+ {
+ return static_cast<CameraData *>(camera->_d());
+ }
+
+ int queueAllBuffers(Camera *camera);
+ virtual int prepareBuffers(Camera *camera) = 0;
+};
+
+class RPiCameraConfiguration final : public CameraConfiguration
+{
+public:
+ RPiCameraConfiguration(const CameraData *data)
+ : CameraConfiguration(), data_(data)
+ {
+ }
+
+ CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
+ Status validate() override;
+
+ /* Cache the combinedTransform_ that will be applied to the sensor */
+ Transform combinedTransform_;
+ /* The sensor format computed in validate() */
+ V4L2SubdeviceFormat sensorFormat_;
+
+ struct StreamParams {
+ StreamParams()
+ : index(0), cfg(nullptr), dev(nullptr)
+ {
+ }
+
+ StreamParams(unsigned int index_, StreamConfiguration *cfg_)
+ : index(index_), cfg(cfg_), dev(nullptr)
+ {
+ }
+
+ unsigned int index;
+ StreamConfiguration *cfg;
+ V4L2VideoDevice *dev;
+ V4L2DeviceFormat format;
+ };
+
+ std::vector<StreamParams> rawStreams_;
+ std::vector<StreamParams> outStreams_;
+
+ /*
+ * Store the colour spaces that all our streams will have. RGB format streams
+ * will have the same colorspace as YUV streams, with YCbCr field cleared and
+ * range set to full.
+ */
+ std::optional<ColorSpace> yuvColorSpace_;
+ std::optional<ColorSpace> rgbColorSpace_;
+
+private:
+ const CameraData *data_;
+};
+
+} /* namespace RPi */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/raspberrypi/rpi_stream.cpp b/src/libcamera/pipeline/rpi/common/rpi_stream.cpp
index 7a93efaa..70f115f1 100644
--- a/src/libcamera/pipeline/raspberrypi/rpi_stream.cpp
+++ b/src/libcamera/pipeline/rpi/common/rpi_stream.cpp
@@ -1,14 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* rpi_stream.cpp - Raspberry Pi device stream abstraction class.
*/
#include "rpi_stream.h"
+#include <algorithm>
+#include <tuple>
+#include <utility>
+
#include <libcamera/base/log.h>
-#include <libcamera/ipa/raspberrypi_ipa_interface.h>
+/* Maximum number of buffer slots to allocate in the V4L2 device driver. */
+static constexpr unsigned int maxV4L2BufferCount = 32;
namespace libcamera {
@@ -16,40 +21,64 @@ LOG_DEFINE_CATEGORY(RPISTREAM)
namespace RPi {
+const BufferObject Stream::errorBufferObject{ nullptr, false };
+
+void Stream::setFlags(StreamFlags flags)
+{
+ /* We don't want dynamic mmapping. */
+ ASSERT(!(flags & StreamFlag::RequiresMmap));
+
+ flags_ |= flags;
+
+ /* Import streams cannot be external. */
+ ASSERT(!(flags_ & StreamFlag::External) || !(flags_ & StreamFlag::ImportOnly));
+}
+
+void Stream::clearFlags(StreamFlags flags)
+{
+ /* We don't want dynamic mmapping. */
+ ASSERT(!(flags & StreamFlag::RequiresMmap));
+
+ flags_ &= ~flags;
+}
+
+RPi::Stream::StreamFlags Stream::getFlags() const
+{
+ return flags_;
+}
+
V4L2VideoDevice *Stream::dev() const
{
return dev_.get();
}
-std::string Stream::name() const
+const std::string &Stream::name() const
{
return name_;
}
-void Stream::resetBuffers()
+unsigned int Stream::swDownscale() const
{
- /* Add all internal buffers to the queue of usable buffers. */
- availableBuffers_ = {};
- for (auto const &buffer : internalBuffers_)
- availableBuffers_.push(buffer.get());
+ return swDownscale_;
}
-void Stream::setExternal(bool external)
+void Stream::setSwDownscale(unsigned int swDownscale)
{
- /* Import streams cannot be external. */
- ASSERT(!external || !importOnly_);
- external_ = external;
+ swDownscale_ = swDownscale;
}
-bool Stream::isExternal() const
+void Stream::resetBuffers()
{
- return external_;
+ /* Add all internal buffers to the queue of usable buffers. */
+ availableBuffers_ = {};
+ for (auto const &buffer : internalBuffers_)
+ availableBuffers_.push(buffer.get());
}
void Stream::setExportedBuffers(std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
for (auto const &buffer : *buffers)
- bufferMap_.emplace(id_.get(), buffer.get());
+ bufferEmplace(++id_, buffer.get());
}
const BufferMap &Stream::getBuffers() const
@@ -57,68 +86,42 @@ const BufferMap &Stream::getBuffers() const
return bufferMap_;
}
-int Stream::getBufferId(FrameBuffer *buffer) const
+unsigned int Stream::getBufferId(FrameBuffer *buffer) const
{
- if (importOnly_)
- return -1;
+ if (flags_ & StreamFlag::ImportOnly)
+ return 0;
/* Find the buffer in the map, and return the buffer id. */
auto it = std::find_if(bufferMap_.begin(), bufferMap_.end(),
- [&buffer](auto const &p) { return p.second == buffer; });
+ [&buffer](auto const &p) { return p.second.buffer == buffer; });
if (it == bufferMap_.end())
- return -1;
+ return 0;
return it->first;
}
-void Stream::setExternalBuffer(FrameBuffer *buffer)
+void Stream::setExportedBuffer(FrameBuffer *buffer)
{
- bufferMap_.emplace(ipa::RPi::MaskExternalBuffer | id_.get(), buffer);
-}
-
-void Stream::removeExternalBuffer(FrameBuffer *buffer)
-{
- int id = getBufferId(buffer);
-
- /* Ensure we have this buffer in the stream, and it is marked external. */
- ASSERT(id != -1 && (id & ipa::RPi::MaskExternalBuffer));
- bufferMap_.erase(id);
+ bufferEmplace(++id_, buffer);
}
int Stream::prepareBuffers(unsigned int count)
{
int ret;
- if (!importOnly_) {
- if (count) {
- /* Export some frame buffers for internal use. */
- ret = dev_->exportBuffers(count, &internalBuffers_);
- if (ret < 0)
- return ret;
-
- /* Add these exported buffers to the internal/external buffer list. */
- setExportedBuffers(&internalBuffers_);
- resetBuffers();
- }
+ if (!(flags_ & StreamFlag::ImportOnly)) {
+ /* Export some frame buffers for internal use. */
+ ret = dev_->exportBuffers(count, &internalBuffers_);
+ if (ret < 0)
+ return ret;
- /* We must import all internal/external exported buffers. */
- count = bufferMap_.size();
+ /* Add these exported buffers to the internal/external buffer list. */
+ setExportedBuffers(&internalBuffers_);
+ resetBuffers();
}
- /*
- * If this is an external stream, we must allocate slots for buffers that
- * might be externally allocated. We have no indication of how many buffers
- * may be used, so this might overallocate slots in the buffer cache.
- * Similarly, if this stream is only importing buffers, we do the same.
- *
- * \todo Find a better heuristic, or, even better, an exact solution to
- * this issue.
- */
- if (isExternal() || importOnly_)
- count = count * 2;
-
- return dev_->importBuffers(count);
+ return dev_->importBuffers(maxV4L2BufferCount);
}
int Stream::queueBuffer(FrameBuffer *buffer)
@@ -162,7 +165,7 @@ int Stream::queueBuffer(FrameBuffer *buffer)
void Stream::returnBuffer(FrameBuffer *buffer)
{
- if (!external_) {
+ if (!(flags_ & StreamFlag::External) && !(flags_ & StreamFlag::Recurrent)) {
/* For internal buffers, simply requeue back to the device. */
queueToDevice(buffer);
return;
@@ -171,9 +174,6 @@ void Stream::returnBuffer(FrameBuffer *buffer)
/* Push this buffer back into the queue to be used again. */
availableBuffers_.push(buffer);
- /* Allow the buffer id to be reused. */
- id_.release(getBufferId(buffer));
-
/*
* Do we have any Request buffers that are waiting to be queued?
* If so, do it now as availableBuffers_ will not be empty.
@@ -202,11 +202,32 @@ void Stream::returnBuffer(FrameBuffer *buffer)
}
}
+const BufferObject &Stream::getBuffer(unsigned int id)
+{
+ auto const &it = bufferMap_.find(id);
+ if (it == bufferMap_.end())
+ return errorBufferObject;
+
+ return it->second;
+}
+
+const BufferObject &Stream::acquireBuffer()
+{
+ /* No id provided, so pick up the next available buffer if possible. */
+ if (availableBuffers_.empty())
+ return errorBufferObject;
+
+ unsigned int id = getBufferId(availableBuffers_.front());
+ availableBuffers_.pop();
+
+ return getBuffer(id);
+}
+
int Stream::queueAllBuffers()
{
int ret;
- if (external_)
+ if ((flags_ & StreamFlag::External) || (flags_ & StreamFlag::Recurrent))
return 0;
while (!availableBuffers_.empty()) {
@@ -226,13 +247,23 @@ void Stream::releaseBuffers()
clearBuffers();
}
+void Stream::bufferEmplace(unsigned int id, FrameBuffer *buffer)
+{
+ if (flags_ & StreamFlag::RequiresMmap)
+ bufferMap_.emplace(std::piecewise_construct, std::forward_as_tuple(id),
+ std::forward_as_tuple(buffer, true));
+ else
+ bufferMap_.emplace(std::piecewise_construct, std::forward_as_tuple(id),
+ std::forward_as_tuple(buffer, false));
+}
+
void Stream::clearBuffers()
{
availableBuffers_ = std::queue<FrameBuffer *>{};
requestBuffers_ = std::queue<FrameBuffer *>{};
internalBuffers_.clear();
bufferMap_.clear();
- id_.reset();
+ id_ = 0;
}
int Stream::queueToDevice(FrameBuffer *buffer)
diff --git a/src/libcamera/pipeline/rpi/common/rpi_stream.h b/src/libcamera/pipeline/rpi/common/rpi_stream.h
new file mode 100644
index 00000000..48ed41ab
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/common/rpi_stream.h
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * rpi_stream.h - Raspberry Pi device stream abstraction class.
+ */
+
+#pragma once
+
+#include <optional>
+#include <queue>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <libcamera/base/flags.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/mapped_framebuffer.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+namespace libcamera {
+
+namespace RPi {
+
+enum BufferMask {
+ MaskID = 0x00ffff,
+ MaskStats = 0x010000,
+ MaskEmbeddedData = 0x020000,
+ MaskBayerData = 0x040000,
+};
+
+struct BufferObject {
+ BufferObject(FrameBuffer *b, bool requiresMmap)
+ : buffer(b), mapped(std::nullopt)
+ {
+ if (requiresMmap)
+ mapped = std::make_optional<MappedFrameBuffer>
+ (b, MappedFrameBuffer::MapFlag::ReadWrite);
+ }
+
+ FrameBuffer *buffer;
+ std::optional<MappedFrameBuffer> mapped;
+};
+
+using BufferMap = std::unordered_map<unsigned int, BufferObject>;
+
+/*
+ * Device stream abstraction for either an internal or external stream.
+ * Used for both Unicam and the ISP.
+ */
+class Stream : public libcamera::Stream
+{
+public:
+ enum class StreamFlag {
+ None = 0,
+ /*
+ * Indicates that this stream only imports buffers, e.g. the ISP
+ * input stream.
+ */
+ ImportOnly = (1 << 0),
+ /*
+ * Indicates that this stream is active externally, i.e. the
+ * buffers might be provided by (and returned to) the application.
+ */
+ External = (1 << 1),
+ /*
+ * Indicates that the stream buffers need to be mmaped and returned
+ * to the pipeline handler when requested.
+ */
+ RequiresMmap = (1 << 2),
+ /*
+ * Indicates a stream that needs buffers recycled every frame internally
+ * in the pipeline handler, e.g. stitch, TDN, config. All buffer
+ * management will be handled by the pipeline handler.
+ */
+ Recurrent = (1 << 3),
+ /*
+ * Indicates that the output stream needs a software format conversion
+ * to be applied after ISP processing.
+ */
+ Needs32bitConv = (1 << 4),
+ };
+
+ using StreamFlags = Flags<StreamFlag>;
+
+ Stream()
+ : flags_(StreamFlag::None), id_(0), swDownscale_(0)
+ {
+ }
+
+ Stream(const char *name, MediaEntity *dev, StreamFlags flags = StreamFlag::None)
+ : flags_(flags), name_(name),
+ dev_(std::make_unique<V4L2VideoDevice>(dev)), id_(0),
+ swDownscale_(0)
+ {
+ }
+
+ void setFlags(StreamFlags flags);
+ void clearFlags(StreamFlags flags);
+ StreamFlags getFlags() const;
+
+ V4L2VideoDevice *dev() const;
+ const std::string &name() const;
+ void resetBuffers();
+
+ unsigned int swDownscale() const;
+ void setSwDownscale(unsigned int swDownscale);
+
+ void setExportedBuffers(std::vector<std::unique_ptr<FrameBuffer>> *buffers);
+ const BufferMap &getBuffers() const;
+ unsigned int getBufferId(FrameBuffer *buffer) const;
+
+ void setExportedBuffer(FrameBuffer *buffer);
+
+ int prepareBuffers(unsigned int count);
+ int queueBuffer(FrameBuffer *buffer);
+ void returnBuffer(FrameBuffer *buffer);
+
+ const BufferObject &getBuffer(unsigned int id);
+ const BufferObject &acquireBuffer();
+
+ int queueAllBuffers();
+ void releaseBuffers();
+
+ /* For error handling. */
+ static const BufferObject errorBufferObject;
+
+private:
+ void bufferEmplace(unsigned int id, FrameBuffer *buffer);
+ void clearBuffers();
+ int queueToDevice(FrameBuffer *buffer);
+
+ StreamFlags flags_;
+
+ /* Stream name identifier. */
+ std::string name_;
+
+ /* The actual device stream. */
+ std::unique_ptr<V4L2VideoDevice> dev_;
+
+ /* Tracks a unique id key for the bufferMap_ */
+ unsigned int id_;
+
+ /* Power of 2 greater than one if software downscaling will be required. */
+ unsigned int swDownscale_;
+
+ /* All frame buffers associated with this device stream. */
+ BufferMap bufferMap_;
+
+ /*
+ * List of frame buffers that we can use if none have been provided by
+ * the application for external streams. This is populated by the
+ * buffers exported internally.
+ */
+ std::queue<FrameBuffer *> availableBuffers_;
+
+ /*
+ * List of frame buffers that are to be queued into the device from a Request.
+ * A nullptr indicates any internal buffer can be used (from availableBuffers_),
+ * whereas a valid pointer indicates an external buffer to be queued.
+ *
+ * Ordering buffers to be queued is important here as it must match the
+ * requests coming from the application.
+ */
+ std::queue<FrameBuffer *> requestBuffers_;
+
+ /*
+ * This is a list of buffers exported internally. Need to keep this around
+ * as the stream needs to maintain ownership of these buffers.
+ */
+ std::vector<std::unique_ptr<FrameBuffer>> internalBuffers_;
+};
+
+/*
+ * The following class is just a convenient (and typesafe) array of device
+ * streams indexed with an enum class.
+ */
+template<typename E, std::size_t N>
+class Device : public std::array<class Stream, N>
+{
+public:
+ Stream &operator[](E e)
+ {
+ return std::array<class Stream, N>::operator[](utils::to_underlying(e));
+ }
+ const Stream &operator[](E e) const
+ {
+ return std::array<class Stream, N>::operator[](utils::to_underlying(e));
+ }
+};
+
+} /* namespace RPi */
+
+LIBCAMERA_FLAGS_ENABLE_OPERATORS(RPi::Stream::StreamFlag)
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/rpi/meson.build b/src/libcamera/pipeline/rpi/meson.build
new file mode 100644
index 00000000..2391b6a9
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/meson.build
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('common')
+
+foreach pipeline : pipelines
+ pipeline = pipeline.split('/')
+ if pipeline.length() < 2 or pipeline[0] != 'rpi'
+ continue
+ endif
+
+ subdir(pipeline[1])
+endforeach
diff --git a/src/libcamera/pipeline/rpi/vc4/data/example.yaml b/src/libcamera/pipeline/rpi/vc4/data/example.yaml
new file mode 100644
index 00000000..b8e01ade
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/vc4/data/example.yaml
@@ -0,0 +1,46 @@
+{
+ "version": 1.0,
+ "target": "bcm2835",
+
+ "pipeline_handler":
+ {
+ # The minimum number of internal buffers to be allocated for
+ # Unicam. This value must be greater than 0, but less than or
+ # equal to min_total_unicam_buffers.
+ #
+ # A larger number of internal buffers can reduce the occurrence
+ # of frame drops during high CPU loads, but might also cause
+ # additional latency in the system.
+ #
+ # Note that the pipeline handler might override this value and
+ # not allocate any internal buffers if it knows they will never
+ # be used. For example if the RAW stream is marked as mandatory
+ # and there are no dropped frames signalled for algorithm
+ # convergence.
+ #
+ # "min_unicam_buffers": 2,
+
+ # The minimum total (internal + external) buffer count used for
+ # Unicam. The number of internal buffers allocated for Unicam is
+ # given by:
+ #
+ # internal buffer count = max(min_unicam_buffers,
+ # min_total_unicam_buffers - external buffer count)
+ #
+ # "min_total_unicam_buffers": 4,
+
+ # Override any request from the IPA to drop a number of startup
+ # frames.
+ #
+ # "disable_startup_frame_drops": false,
+
+ # Custom timeout value (in ms) for camera to use. This overrides
+ # the value computed by the pipeline handler based on frame
+ # durations.
+ #
+ # Set this value to 0 to use the pipeline handler computed
+ # timeout value.
+ #
+ # "camera_timeout_value_ms": 0,
+ }
+}
diff --git a/src/libcamera/pipeline/rpi/vc4/data/meson.build b/src/libcamera/pipeline/rpi/vc4/data/meson.build
new file mode 100644
index 00000000..179feebc
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/vc4/data/meson.build
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: CC0-1.0
+
+conf_files = files([
+ 'example.yaml',
+])
+
+install_data(conf_files,
+ install_dir : pipeline_data_dir / 'rpi' / 'vc4',
+ install_tag : 'runtime')
diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build
new file mode 100644
index 00000000..386e2296
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/vc4/meson.build
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+ 'vc4.cpp',
+])
+
+subdir('data')
diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
new file mode 100644
index 00000000..947b1e73
--- /dev/null
+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
@@ -0,0 +1,1023 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019-2023, Raspberry Pi Ltd
+ *
+ * vc4.cpp - Pipeline handler for VC4-based Raspberry Pi devices
+ */
+
+#include <linux/bcm2835-isp.h>
+#include <linux/v4l2-controls.h>
+#include <linux/videodev2.h>
+
+#include <libcamera/formats.h>
+
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/dma_heaps.h"
+
+#include "../common/pipeline_base.h"
+#include "../common/rpi_stream.h"
+
+using namespace std::chrono_literals;
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(RPI)
+
+using StreamFlag = RPi::Stream::StreamFlag;
+using StreamParams = RPi::RPiCameraConfiguration::StreamParams;
+
+namespace {
+
+enum class Unicam : unsigned int { Image, Embedded };
+enum class Isp : unsigned int { Input, Output0, Output1, Stats };
+
+} /* namespace */
+
+class Vc4CameraData final : public RPi::CameraData
+{
+public:
+ Vc4CameraData(PipelineHandler *pipe)
+ : RPi::CameraData(pipe)
+ {
+ }
+
+ ~Vc4CameraData()
+ {
+ freeBuffers();
+ }
+
+ V4L2VideoDevice::Formats ispFormats() const override
+ {
+ return isp_[Isp::Output0].dev()->formats();
+ }
+
+ V4L2VideoDevice::Formats rawFormats() const override
+ {
+ return unicam_[Unicam::Image].dev()->formats();
+ }
+
+ V4L2VideoDevice *frontendDevice() override
+ {
+ return unicam_[Unicam::Image].dev();
+ }
+
+ void platformFreeBuffers() override
+ {
+ }
+
+ CameraConfiguration::Status platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override;
+
+ int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override;
+
+ void platformStart() override;
+ void platformStop() override;
+
+ void unicamBufferDequeue(FrameBuffer *buffer);
+ void ispInputDequeue(FrameBuffer *buffer);
+ void ispOutputDequeue(FrameBuffer *buffer);
+
+ void processStatsComplete(const ipa::RPi::BufferIds &buffers);
+ void prepareIspComplete(const ipa::RPi::BufferIds &buffers, bool stitchSwapBuffers);
+ void setIspControls(const ControlList &controls);
+ void setCameraTimeout(uint32_t maxFrameLengthMs);
+
+ /* Array of Unicam and ISP device streams and associated buffers/streams. */
+ RPi::Device<Unicam, 2> unicam_;
+ RPi::Device<Isp, 4> isp_;
+
+ /* DMAHEAP allocation helper. */
+ DmaHeap dmaHeap_;
+ SharedFD lsTable_;
+
+ struct Config {
+ /*
+ * The minimum number of internal buffers to be allocated for
+ * the Unicam Image stream.
+ */
+ unsigned int minUnicamBuffers;
+ /*
+ * The minimum total (internal + external) buffer count used for
+ * the Unicam Image stream.
+ *
+ * Note that:
+ * minTotalUnicamBuffers must be >= 1, and
+ * minTotalUnicamBuffers >= minUnicamBuffers
+ */
+ unsigned int minTotalUnicamBuffers;
+ };
+
+ Config config_;
+
+private:
+ void platformSetIspCrop() override
+ {
+ isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &ispCrop_);
+ }
+
+ int platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig) override;
+ int platformConfigureIpa(ipa::RPi::ConfigParams &params) override;
+
+ int platformInitIpa([[maybe_unused]] ipa::RPi::InitParams &params) override
+ {
+ return 0;
+ }
+
+ struct BayerFrame {
+ FrameBuffer *buffer;
+ ControlList controls;
+ unsigned int delayContext;
+ };
+
+ void tryRunPipeline() override;
+ bool findMatchingBuffers(BayerFrame &bayerFrame, FrameBuffer *&embeddedBuffer);
+
+ std::queue<BayerFrame> bayerQueue_;
+ std::queue<FrameBuffer *> embeddedQueue_;
+};
+
+class PipelineHandlerVc4 : public RPi::PipelineHandlerBase
+{
+public:
+ PipelineHandlerVc4(CameraManager *manager)
+ : RPi::PipelineHandlerBase(manager)
+ {
+ }
+
+ ~PipelineHandlerVc4()
+ {
+ }
+
+ bool match(DeviceEnumerator *enumerator) override;
+
+private:
+ Vc4CameraData *cameraData(Camera *camera)
+ {
+ return static_cast<Vc4CameraData *>(camera->_d());
+ }
+
+ int prepareBuffers(Camera *camera) override;
+ int platformRegister(std::unique_ptr<RPi::CameraData> &cameraData,
+ MediaDevice *unicam, MediaDevice *isp) override;
+};
+
+bool PipelineHandlerVc4::match(DeviceEnumerator *enumerator)
+{
+ constexpr unsigned int numUnicamDevices = 2;
+
+ /*
+ * Loop over all Unicam instances, but return out once a match is found.
+ * This is to ensure we correctly enumrate the camera when an instance
+ * of Unicam has registered with media controller, but has not registered
+ * device nodes due to a sensor subdevice failure.
+ */
+ for (unsigned int i = 0; i < numUnicamDevices; i++) {
+ DeviceMatch unicam("unicam");
+ MediaDevice *unicamDevice = acquireMediaDevice(enumerator, unicam);
+
+ if (!unicamDevice) {
+ LOG(RPI, Debug) << "Unable to acquire a Unicam instance";
+ continue;
+ }
+
+ DeviceMatch isp("bcm2835-isp");
+ MediaDevice *ispDevice = acquireMediaDevice(enumerator, isp);
+
+ if (!ispDevice) {
+ LOG(RPI, Debug) << "Unable to acquire ISP instance";
+ continue;
+ }
+
+ /*
+ * The loop below is used to register multiple cameras behind one or more
+ * video mux devices that are attached to a particular Unicam instance.
+ * Obviously these cameras cannot be used simultaneously.
+ */
+ unsigned int numCameras = 0;
+ for (MediaEntity *entity : unicamDevice->entities()) {
+ if (entity->function() != MEDIA_ENT_F_CAM_SENSOR)
+ continue;
+
+ std::unique_ptr<RPi::CameraData> cameraData = std::make_unique<Vc4CameraData>(this);
+ int ret = RPi::PipelineHandlerBase::registerCamera(cameraData,
+ unicamDevice, "unicam-image",
+ ispDevice, entity);
+ if (ret)
+ LOG(RPI, Error) << "Failed to register camera "
+ << entity->name() << ": " << ret;
+ else
+ numCameras++;
+ }
+
+ if (numCameras)
+ return true;
+ }
+
+ return false;
+}
+
+int PipelineHandlerVc4::prepareBuffers(Camera *camera)
+{
+ Vc4CameraData *data = cameraData(camera);
+ unsigned int numRawBuffers = 0;
+ int ret;
+
+ for (Stream *s : camera->streams()) {
+ if (BayerFormat::fromPixelFormat(s->configuration().pixelFormat).isValid()) {
+ numRawBuffers = s->configuration().bufferCount;
+ break;
+ }
+ }
+
+ /* Decide how many internal buffers to allocate. */
+ for (auto const stream : data->streams_) {
+ unsigned int numBuffers;
+ /*
+ * For Unicam, allocate a minimum number of buffers for internal
+ * use as we want to avoid any frame drops.
+ */
+ const unsigned int minBuffers = data->config_.minTotalUnicamBuffers;
+ if (stream == &data->unicam_[Unicam::Image]) {
+ /*
+ * If an application has configured a RAW stream, allocate
+ * additional buffers to make up the minimum, but ensure
+ * we have at least minUnicamBuffers of internal buffers
+ * to use to minimise frame drops.
+ */
+ numBuffers = std::max<int>(data->config_.minUnicamBuffers,
+ minBuffers - numRawBuffers);
+ } else if (stream == &data->isp_[Isp::Input]) {
+ /*
+ * ISP input buffers are imported from Unicam, so follow
+ * similar logic as above to count all the RAW buffers
+ * available.
+ */
+ numBuffers = numRawBuffers +
+ std::max<int>(data->config_.minUnicamBuffers,
+ minBuffers - numRawBuffers);
+
+ } else if (stream == &data->unicam_[Unicam::Embedded]) {
+ /*
+ * Embedded data buffers are (currently) for internal use, and
+ * are small enough (typically 1-2KB) that we can
+ * allocate them generously to avoid causing problems in the
+ * IPA when we cannot supply the metadata.
+ *
+ * 12 are allocated as a typical application will have 8-10
+ * input buffers, so allocating more embedded buffers than that
+ * is a sensible choice.
+ *
+ * The lifetimes of these buffers are smaller than those of the
+ * raw buffers, so allocating a fixed number will still suffice
+ * if the application requests a greater number of raw
+ * buffers, as these will be recycled quicker.
+ */
+ numBuffers = 12;
+ } else {
+ /*
+ * Since the ISP runs synchronous with the IPA and requests,
+ * we only ever need one set of internal buffers. Any buffers
+ * the application wants to hold onto will already be exported
+ * through PipelineHandlerRPi::exportFrameBuffers().
+ */
+ numBuffers = 1;
+ }
+
+ LOG(RPI, Debug) << "Preparing " << numBuffers
+ << " buffers for stream " << stream->name();
+
+ ret = stream->prepareBuffers(numBuffers);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * Pass the stats and embedded data buffers to the IPA. No other
+ * buffers need to be passed.
+ */
+ mapBuffers(camera, data->isp_[Isp::Stats].getBuffers(), RPi::MaskStats);
+ if (data->sensorMetadata_)
+ mapBuffers(camera, data->unicam_[Unicam::Embedded].getBuffers(),
+ RPi::MaskEmbeddedData);
+
+ return 0;
+}
+
+int PipelineHandlerVc4::platformRegister(std::unique_ptr<RPi::CameraData> &cameraData, MediaDevice *unicam, MediaDevice *isp)
+{
+ Vc4CameraData *data = static_cast<Vc4CameraData *>(cameraData.get());
+
+ if (!data->dmaHeap_.isValid())
+ return -ENOMEM;
+
+ MediaEntity *unicamImage = unicam->getEntityByName("unicam-image");
+ MediaEntity *ispOutput0 = isp->getEntityByName("bcm2835-isp0-output0");
+ MediaEntity *ispCapture1 = isp->getEntityByName("bcm2835-isp0-capture1");
+ MediaEntity *ispCapture2 = isp->getEntityByName("bcm2835-isp0-capture2");
+ MediaEntity *ispCapture3 = isp->getEntityByName("bcm2835-isp0-capture3");
+
+ if (!unicamImage || !ispOutput0 || !ispCapture1 || !ispCapture2 || !ispCapture3)
+ return -ENOENT;
+
+ /* Locate and open the unicam video streams. */
+ data->unicam_[Unicam::Image] = RPi::Stream("Unicam Image", unicamImage);
+
+ /* An embedded data node will not be present if the sensor does not support it. */
+ MediaEntity *unicamEmbedded = unicam->getEntityByName("unicam-embedded");
+ if (unicamEmbedded) {
+ data->unicam_[Unicam::Embedded] = RPi::Stream("Unicam Embedded", unicamEmbedded);
+ data->unicam_[Unicam::Embedded].dev()->bufferReady.connect(data,
+ &Vc4CameraData::unicamBufferDequeue);
+ }
+
+ /* Tag the ISP input stream as an import stream. */
+ data->isp_[Isp::Input] = RPi::Stream("ISP Input", ispOutput0, StreamFlag::ImportOnly);
+ data->isp_[Isp::Output0] = RPi::Stream("ISP Output0", ispCapture1);
+ data->isp_[Isp::Output1] = RPi::Stream("ISP Output1", ispCapture2);
+ data->isp_[Isp::Stats] = RPi::Stream("ISP Stats", ispCapture3);
+
+ /* Wire up all the buffer connections. */
+ data->unicam_[Unicam::Image].dev()->bufferReady.connect(data, &Vc4CameraData::unicamBufferDequeue);
+ data->isp_[Isp::Input].dev()->bufferReady.connect(data, &Vc4CameraData::ispInputDequeue);
+ data->isp_[Isp::Output0].dev()->bufferReady.connect(data, &Vc4CameraData::ispOutputDequeue);
+ data->isp_[Isp::Output1].dev()->bufferReady.connect(data, &Vc4CameraData::ispOutputDequeue);
+ data->isp_[Isp::Stats].dev()->bufferReady.connect(data, &Vc4CameraData::ispOutputDequeue);
+
+ if (data->sensorMetadata_ ^ !!data->unicam_[Unicam::Embedded].dev()) {
+ LOG(RPI, Warning) << "Mismatch between Unicam and CamHelper for embedded data usage!";
+ data->sensorMetadata_ = false;
+ if (data->unicam_[Unicam::Embedded].dev())
+ data->unicam_[Unicam::Embedded].dev()->bufferReady.disconnect();
+ }
+
+ /*
+ * Open all Unicam and ISP streams. The exception is the embedded data
+ * stream, which only gets opened below if the IPA reports that the sensor
+ * supports embedded data.
+ *
+ * The below grouping is just for convenience so that we can easily
+ * iterate over all streams in one go.
+ */
+ data->streams_.push_back(&data->unicam_[Unicam::Image]);
+ if (data->sensorMetadata_)
+ data->streams_.push_back(&data->unicam_[Unicam::Embedded]);
+
+ for (auto &stream : data->isp_)
+ data->streams_.push_back(&stream);
+
+ for (auto stream : data->streams_) {
+ int ret = stream->dev()->open();
+ if (ret)
+ return ret;
+ }
+
+ if (!data->unicam_[Unicam::Image].dev()->caps().hasMediaController()) {
+ LOG(RPI, Error) << "Unicam driver does not use the MediaController, please update your kernel!";
+ return -EINVAL;
+ }
+
+ /* Write up all the IPA connections. */
+ data->ipa_->processStatsComplete.connect(data, &Vc4CameraData::processStatsComplete);
+ data->ipa_->prepareIspComplete.connect(data, &Vc4CameraData::prepareIspComplete);
+ data->ipa_->setIspControls.connect(data, &Vc4CameraData::setIspControls);
+ data->ipa_->setCameraTimeout.connect(data, &Vc4CameraData::setCameraTimeout);
+
+ /*
+ * List the available streams an application may request. At present, we
+ * do not advertise Unicam Embedded and ISP Statistics streams, as there
+ * is no mechanism for the application to request non-image buffer formats.
+ */
+ std::set<Stream *> streams;
+ streams.insert(&data->unicam_[Unicam::Image]);
+ streams.insert(&data->isp_[Isp::Output0]);
+ streams.insert(&data->isp_[Isp::Output1]);
+
+ /* Create and register the camera. */
+ const std::string &id = data->sensor_->id();
+ std::shared_ptr<Camera> camera =
+ Camera::create(std::move(cameraData), id, streams);
+ PipelineHandler::registerCamera(std::move(camera));
+
+ LOG(RPI, Info) << "Registered camera " << id
+ << " to Unicam device " << unicam->deviceNode()
+ << " and ISP device " << isp->deviceNode();
+
+ return 0;
+}
+
+CameraConfiguration::Status Vc4CameraData::platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const
+{
+ std::vector<StreamParams> &rawStreams = rpiConfig->rawStreams_;
+ std::vector<StreamParams> &outStreams = rpiConfig->outStreams_;
+
+ CameraConfiguration::Status status = CameraConfiguration::Status::Valid;
+
+ /* Can only output 1 RAW stream, or 2 YUV/RGB streams. */
+ if (rawStreams.size() > 1 || outStreams.size() > 2) {
+ LOG(RPI, Error) << "Invalid number of streams requested";
+ return CameraConfiguration::Status::Invalid;
+ }
+
+ if (!rawStreams.empty()) {
+ rawStreams[0].dev = unicam_[Unicam::Image].dev();
+
+ /* Adjust the RAW stream to match the computed sensor format. */
+ StreamConfiguration *rawStream = rawStreams[0].cfg;
+ BayerFormat rawBayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat);
+
+ /* Apply the sensor bitdepth. */
+ rawBayer.bitDepth = BayerFormat::fromMbusCode(rpiConfig->sensorFormat_.code).bitDepth;
+
+ /* Default to CSI2 packing if the user request is unsupported. */
+ if (rawBayer.packing != BayerFormat::Packing::CSI2 &&
+ rawBayer.packing != BayerFormat::Packing::None)
+ rawBayer.packing = BayerFormat::Packing::CSI2;
+
+ PixelFormat rawFormat = rawBayer.toPixelFormat();
+
+ /*
+ * Try for an unpacked format if a packed one wasn't available.
+ * This catches 8 (and 16) bit formats which would otherwise
+ * fail.
+ */
+ if (!rawFormat.isValid() && rawBayer.packing != BayerFormat::Packing::None) {
+ rawBayer.packing = BayerFormat::Packing::None;
+ rawFormat = rawBayer.toPixelFormat();
+ }
+
+ if (rawStream->pixelFormat != rawFormat ||
+ rawStream->size != rpiConfig->sensorFormat_.size) {
+ rawStream->pixelFormat = rawFormat;
+ rawStream->size = rpiConfig->sensorFormat_.size;
+
+ status = CameraConfiguration::Adjusted;
+ }
+
+ rawStreams[0].format =
+ RPi::PipelineHandlerBase::toV4L2DeviceFormat(unicam_[Unicam::Image].dev(), rawStream);
+ }
+
+ /*
+ * For the two ISP outputs, one stream must be equal or smaller than the
+ * other in all dimensions.
+ *
+ * Index 0 contains the largest requested resolution.
+ */
+ for (unsigned int i = 0; i < outStreams.size(); i++) {
+ Size size;
+
+ /*
+ * \todo Should we warn if upscaling, as it reduces the image
+ * quality and is usually undesired ?
+ */
+
+ size.width = std::min(outStreams[i].cfg->size.width,
+ outStreams[0].cfg->size.width);
+ size.height = std::min(outStreams[i].cfg->size.height,
+ outStreams[0].cfg->size.height);
+
+ if (outStreams[i].cfg->size != size) {
+ outStreams[i].cfg->size = size;
+ status = CameraConfiguration::Status::Adjusted;
+ }
+
+ /*
+ * Output 0 must be for the largest resolution. We will
+ * have that fixed up in the code above.
+ */
+ outStreams[i].dev = isp_[i == 0 ? Isp::Output0 : Isp::Output1].dev();
+
+ outStreams[i].format = RPi::PipelineHandlerBase::toV4L2DeviceFormat(outStreams[i].dev, outStreams[i].cfg);
+ }
+
+ return status;
+}
+
+int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root)
+{
+ config_ = {
+ .minUnicamBuffers = 2,
+ .minTotalUnicamBuffers = 4,
+ };
+
+ if (!root)
+ return 0;
+
+ std::optional<double> ver = (*root)["version"].get<double>();
+ if (!ver || *ver != 1.0) {
+ LOG(RPI, Error) << "Unexpected configuration file version reported";
+ return -EINVAL;
+ }
+
+ std::optional<std::string> target = (*root)["target"].get<std::string>();
+ if (!target || *target != "bcm2835") {
+ LOG(RPI, Error) << "Unexpected target reported: expected \"bcm2835\", got "
+ << *target;
+ return -EINVAL;
+ }
+
+ const YamlObject &phConfig = (*root)["pipeline_handler"];
+ config_.minUnicamBuffers =
+ phConfig["min_unicam_buffers"].get<unsigned int>(config_.minUnicamBuffers);
+ config_.minTotalUnicamBuffers =
+ phConfig["min_total_unicam_buffers"].get<unsigned int>(config_.minTotalUnicamBuffers);
+
+ if (config_.minTotalUnicamBuffers < config_.minUnicamBuffers) {
+ LOG(RPI, Error) << "Invalid configuration: min_total_unicam_buffers must be >= min_unicam_buffers";
+ return -EINVAL;
+ }
+
+ if (config_.minTotalUnicamBuffers < 1) {
+ LOG(RPI, Error) << "Invalid configuration: min_total_unicam_buffers must be >= 1";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int Vc4CameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig)
+{
+ const std::vector<StreamParams> &rawStreams = rpiConfig->rawStreams_;
+ const std::vector<StreamParams> &outStreams = rpiConfig->outStreams_;
+ int ret;
+
+ V4L2VideoDevice *unicam = unicam_[Unicam::Image].dev();
+ V4L2DeviceFormat unicamFormat;
+
+ /*
+ * See which streams are requested, and route the user
+ * StreamConfiguration appropriately.
+ */
+ if (!rawStreams.empty()) {
+ rawStreams[0].cfg->setStream(&unicam_[Unicam::Image]);
+ unicam_[Unicam::Image].setFlags(StreamFlag::External);
+ unicamFormat = rawStreams[0].format;
+ } else {
+ unicamFormat =
+ RPi::PipelineHandlerBase::toV4L2DeviceFormat(unicam,
+ rpiConfig->sensorFormat_,
+ BayerFormat::Packing::CSI2);
+ }
+
+ ret = unicam->setFormat(&unicamFormat);
+ if (ret)
+ return ret;
+
+ ret = isp_[Isp::Input].dev()->setFormat(&unicamFormat);
+ if (ret)
+ return ret;
+
+ LOG(RPI, Info) << "Sensor: " << sensor_->id()
+ << " - Selected sensor format: " << rpiConfig->sensorFormat_
+ << " - Selected unicam format: " << unicamFormat;
+
+ /* Use a sensible small default size if no output streams are configured. */
+ Size maxSize = outStreams.empty() ? Size(320, 240) : outStreams[0].cfg->size;
+ V4L2DeviceFormat format;
+
+ for (unsigned int i = 0; i < outStreams.size(); i++) {
+ StreamConfiguration *cfg = outStreams[i].cfg;
+
+ /* The largest resolution gets routed to the ISP Output 0 node. */
+ RPi::Stream *stream = i == 0 ? &isp_[Isp::Output0] : &isp_[Isp::Output1];
+ format = outStreams[i].format;
+
+ LOG(RPI, Debug) << "Setting " << stream->name() << " to "
+ << format;
+
+ ret = stream->dev()->setFormat(&format);
+ if (ret)
+ return -EINVAL;
+
+ LOG(RPI, Debug)
+ << "Stream " << stream->name() << " has color space "
+ << ColorSpace::toString(cfg->colorSpace);
+
+ cfg->setStream(stream);
+ stream->setFlags(StreamFlag::External);
+ }
+
+ ispOutputTotal_ = outStreams.size();
+
+ /*
+ * If ISP::Output0 stream has not been configured by the application,
+ * we must allow the hardware to generate an output so that the data
+ * flow in the pipeline handler remains consistent, and we still generate
+ * statistics for the IPA to use. So enable the output at a very low
+ * resolution for internal use.
+ *
+ * \todo Allow the pipeline to work correctly without Output0 and only
+ * statistics coming from the hardware.
+ */
+ if (outStreams.empty()) {
+ V4L2VideoDevice *dev = isp_[Isp::Output0].dev();
+
+ format = {};
+ format.size = maxSize;
+ format.fourcc = dev->toV4L2PixelFormat(formats::YUV420);
+ /* No one asked for output, so the color space doesn't matter. */
+ format.colorSpace = ColorSpace::Sycc;
+ ret = dev->setFormat(&format);
+ if (ret) {
+ LOG(RPI, Error)
+ << "Failed to set default format on ISP Output0: "
+ << ret;
+ return -EINVAL;
+ }
+
+ ispOutputTotal_++;
+
+ LOG(RPI, Debug) << "Defaulting ISP Output0 format to "
+ << format;
+ }
+
+ /*
+ * If ISP::Output1 stream has not been requested by the application, we
+ * set it up for internal use now. This second stream will be used for
+ * fast colour denoise, and must be a quarter resolution of the ISP::Output0
+ * stream. However, also limit the maximum size to 1200 pixels in the
+ * larger dimension, just to avoid being wasteful with buffer allocations
+ * and memory bandwidth.
+ *
+ * \todo If Output 1 format is not YUV420, Output 1 ought to be disabled as
+ * colour denoise will not run.
+ */
+ if (outStreams.size() <= 1) {
+ V4L2VideoDevice *dev = isp_[Isp::Output1].dev();
+
+ V4L2DeviceFormat output1Format;
+ constexpr Size maxDimensions(1200, 1200);
+ const Size limit = maxDimensions.boundedToAspectRatio(format.size);
+
+ output1Format.size = (format.size / 2).boundedTo(limit).alignedDownTo(2, 2);
+ output1Format.colorSpace = format.colorSpace;
+ output1Format.fourcc = dev->toV4L2PixelFormat(formats::YUV420);
+
+ LOG(RPI, Debug) << "Setting ISP Output1 (internal) to "
+ << output1Format;
+
+ ret = dev->setFormat(&output1Format);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to set format on ISP Output1: "
+ << ret;
+ return -EINVAL;
+ }
+
+ ispOutputTotal_++;
+ }
+
+ /* ISP statistics output format. */
+ format = {};
+ format.fourcc = V4L2PixelFormat(V4L2_META_FMT_BCM2835_ISP_STATS);
+ ret = isp_[Isp::Stats].dev()->setFormat(&format);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to set format on ISP stats stream: "
+ << format;
+ return ret;
+ }
+
+ ispOutputTotal_++;
+
+ /*
+ * Configure the Unicam embedded data output format only if the sensor
+ * supports it.
+ */
+ if (sensorMetadata_) {
+ V4L2SubdeviceFormat embeddedFormat;
+
+ sensor_->device()->getFormat(1, &embeddedFormat);
+ format = {};
+ format.fourcc = V4L2PixelFormat(V4L2_META_FMT_SENSOR_DATA);
+ format.planes[0].size = embeddedFormat.size.width * embeddedFormat.size.height;
+
+ LOG(RPI, Debug) << "Setting embedded data format " << format.toString();
+ ret = unicam_[Unicam::Embedded].dev()->setFormat(&format);
+ if (ret) {
+ LOG(RPI, Error) << "Failed to set format on Unicam embedded: "
+ << format;
+ return ret;
+ }
+ }
+
+ /* Figure out the smallest selection the ISP will allow. */
+ Rectangle testCrop(0, 0, 1, 1);
+ isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop);
+ ispMinCropSize_ = testCrop.size();
+
+ /* Adjust aspect ratio by providing crops on the input image. */
+ Size size = unicamFormat.size.boundedToAspectRatio(maxSize);
+ ispCrop_ = size.centeredTo(Rectangle(unicamFormat.size).center());
+
+ platformSetIspCrop();
+
+ return 0;
+}
+
+int Vc4CameraData::platformConfigureIpa(ipa::RPi::ConfigParams &params)
+{
+ params.ispControls = isp_[Isp::Input].dev()->controls();
+
+ /* Allocate the lens shading table via dmaHeap and pass to the IPA. */
+ if (!lsTable_.isValid()) {
+ lsTable_ = SharedFD(dmaHeap_.alloc("ls_grid", ipa::RPi::MaxLsGridSize));
+ if (!lsTable_.isValid())
+ return -ENOMEM;
+
+ /* Allow the IPA to mmap the LS table via the file descriptor. */
+ /*
+ * \todo Investigate if mapping the lens shading table buffer
+ * could be handled with mapBuffers().
+ */
+ params.lsTableHandle = lsTable_;
+ }
+
+ return 0;
+}
+
+void Vc4CameraData::platformStart()
+{
+}
+
+void Vc4CameraData::platformStop()
+{
+ bayerQueue_ = {};
+ embeddedQueue_ = {};
+}
+
+void Vc4CameraData::unicamBufferDequeue(FrameBuffer *buffer)
+{
+ RPi::Stream *stream = nullptr;
+ unsigned int index;
+
+ if (!isRunning())
+ return;
+
+ for (RPi::Stream &s : unicam_) {
+ index = s.getBufferId(buffer);
+ if (index) {
+ stream = &s;
+ break;
+ }
+ }
+
+ /* The buffer must belong to one of our streams. */
+ ASSERT(stream);
+
+ LOG(RPI, Debug) << "Stream " << stream->name() << " buffer dequeue"
+ << ", buffer id " << index
+ << ", timestamp: " << buffer->metadata().timestamp;
+
+ if (stream == &unicam_[Unicam::Image]) {
+ /*
+ * Lookup the sensor controls used for this frame sequence from
+ * DelayedControl and queue them along with the frame buffer.
+ */
+ auto [ctrl, delayContext] = delayedCtrls_->get(buffer->metadata().sequence);
+ /*
+ * Add the frame timestamp to the ControlList for the IPA to use
+ * as it does not receive the FrameBuffer object.
+ */
+ ctrl.set(controls::SensorTimestamp, buffer->metadata().timestamp);
+ bayerQueue_.push({ buffer, std::move(ctrl), delayContext });
+ } else {
+ embeddedQueue_.push(buffer);
+ }
+
+ handleState();
+}
+
+void Vc4CameraData::ispInputDequeue(FrameBuffer *buffer)
+{
+ if (!isRunning())
+ return;
+
+ LOG(RPI, Debug) << "Stream ISP Input buffer complete"
+ << ", buffer id " << unicam_[Unicam::Image].getBufferId(buffer)
+ << ", timestamp: " << buffer->metadata().timestamp;
+
+ /* The ISP input buffer gets re-queued into Unicam. */
+ handleStreamBuffer(buffer, &unicam_[Unicam::Image]);
+ handleState();
+}
+
+void Vc4CameraData::ispOutputDequeue(FrameBuffer *buffer)
+{
+ RPi::Stream *stream = nullptr;
+ unsigned int index;
+
+ if (!isRunning())
+ return;
+
+ for (RPi::Stream &s : isp_) {
+ index = s.getBufferId(buffer);
+ if (index) {
+ stream = &s;
+ break;
+ }
+ }
+
+ /* The buffer must belong to one of our ISP output streams. */
+ ASSERT(stream);
+
+ LOG(RPI, Debug) << "Stream " << stream->name() << " buffer complete"
+ << ", buffer id " << index
+ << ", timestamp: " << buffer->metadata().timestamp;
+
+ /*
+ * ISP statistics buffer must not be re-queued or sent back to the
+ * application until after the IPA signals so.
+ */
+ if (stream == &isp_[Isp::Stats]) {
+ ipa::RPi::ProcessParams params;
+ params.buffers.stats = index | RPi::MaskStats;
+ params.ipaContext = requestQueue_.front()->sequence();
+ ipa_->processStats(params);
+ } else {
+ /* Any other ISP output can be handed back to the application now. */
+ handleStreamBuffer(buffer, stream);
+ }
+
+ /*
+ * Increment the number of ISP outputs generated.
+ * This is needed to track dropped frames.
+ */
+ ispOutputCount_++;
+
+ handleState();
+}
+
+void Vc4CameraData::processStatsComplete(const ipa::RPi::BufferIds &buffers)
+{
+ if (!isRunning())
+ return;
+
+ FrameBuffer *buffer = isp_[Isp::Stats].getBuffers().at(buffers.stats & RPi::MaskID).buffer;
+
+ handleStreamBuffer(buffer, &isp_[Isp::Stats]);
+
+ state_ = State::IpaComplete;
+ handleState();
+}
+
+void Vc4CameraData::prepareIspComplete(const ipa::RPi::BufferIds &buffers,
+ [[maybe_unused]] bool stitchSwapBuffers)
+{
+ unsigned int embeddedId = buffers.embedded & RPi::MaskID;
+ unsigned int bayer = buffers.bayer & RPi::MaskID;
+ FrameBuffer *buffer;
+
+ if (!isRunning())
+ return;
+
+ buffer = unicam_[Unicam::Image].getBuffers().at(bayer & RPi::MaskID).buffer;
+ LOG(RPI, Debug) << "Input re-queue to ISP, buffer id " << (bayer & RPi::MaskID)
+ << ", timestamp: " << buffer->metadata().timestamp;
+
+ isp_[Isp::Input].queueBuffer(buffer);
+ ispOutputCount_ = 0;
+
+ if (sensorMetadata_ && embeddedId) {
+ buffer = unicam_[Unicam::Embedded].getBuffers().at(embeddedId & RPi::MaskID).buffer;
+ handleStreamBuffer(buffer, &unicam_[Unicam::Embedded]);
+ }
+
+ handleState();
+}
+
+void Vc4CameraData::setIspControls(const ControlList &controls)
+{
+ ControlList ctrls = controls;
+
+ if (ctrls.contains(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING)) {
+ ControlValue &value =
+ const_cast<ControlValue &>(ctrls.get(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING));
+ Span<uint8_t> s = value.data();
+ bcm2835_isp_lens_shading *ls =
+ reinterpret_cast<bcm2835_isp_lens_shading *>(s.data());
+ ls->dmabuf = lsTable_.get();
+ }
+
+ isp_[Isp::Input].dev()->setControls(&ctrls);
+ handleState();
+}
+
+void Vc4CameraData::setCameraTimeout(uint32_t maxFrameLengthMs)
+{
+ /*
+ * Set the dequeue timeout to the larger of 5x the maximum reported
+ * frame length advertised by the IPA over a number of frames. Allow
+ * a minimum timeout value of 1s.
+ */
+ utils::Duration timeout =
+ std::max<utils::Duration>(1s, 5 * maxFrameLengthMs * 1ms);
+
+ LOG(RPI, Debug) << "Setting Unicam timeout to " << timeout;
+ unicam_[Unicam::Image].dev()->setDequeueTimeout(timeout);
+}
+
+void Vc4CameraData::tryRunPipeline()
+{
+ FrameBuffer *embeddedBuffer;
+ BayerFrame bayerFrame;
+
+ /* If any of our request or buffer queues are empty, we cannot proceed. */
+ if (state_ != State::Idle || requestQueue_.empty() ||
+ bayerQueue_.empty() || (embeddedQueue_.empty() && sensorMetadata_))
+ return;
+
+ if (!findMatchingBuffers(bayerFrame, embeddedBuffer))
+ return;
+
+ /* Take the first request from the queue and action the IPA. */
+ Request *request = requestQueue_.front();
+
+ /* See if a new ScalerCrop value needs to be applied. */
+ applyScalerCrop(request->controls());
+
+ /*
+ * Clear the request metadata and fill it with some initial non-IPA
+ * related controls. We clear it first because the request metadata
+ * may have been populated if we have dropped the previous frame.
+ */
+ request->metadata().clear();
+ fillRequestMetadata(bayerFrame.controls, request);
+
+ /* Set our state to say the pipeline is active. */
+ state_ = State::Busy;
+
+ unsigned int bayer = unicam_[Unicam::Image].getBufferId(bayerFrame.buffer);
+
+ LOG(RPI, Debug) << "Signalling prepareIsp:"
+ << " Bayer buffer id: " << bayer;
+
+ ipa::RPi::PrepareParams params;
+ params.buffers.bayer = RPi::MaskBayerData | bayer;
+ params.sensorControls = std::move(bayerFrame.controls);
+ params.requestControls = request->controls();
+ params.ipaContext = request->sequence();
+ params.delayContext = bayerFrame.delayContext;
+ params.buffers.embedded = 0;
+
+ if (embeddedBuffer) {
+ unsigned int embeddedId = unicam_[Unicam::Embedded].getBufferId(embeddedBuffer);
+
+ params.buffers.embedded = RPi::MaskEmbeddedData | embeddedId;
+ LOG(RPI, Debug) << "Signalling prepareIsp:"
+ << " Embedded buffer id: " << embeddedId;
+ }
+
+ ipa_->prepareIsp(params);
+}
+
+bool Vc4CameraData::findMatchingBuffers(BayerFrame &bayerFrame, FrameBuffer *&embeddedBuffer)
+{
+ if (bayerQueue_.empty())
+ return false;
+
+ /*
+ * Find the embedded data buffer with a matching timestamp to pass to
+ * the IPA. Any embedded buffers with a timestamp lower than the
+ * current bayer buffer will be removed and re-queued to the driver.
+ */
+ uint64_t ts = bayerQueue_.front().buffer->metadata().timestamp;
+ embeddedBuffer = nullptr;
+ while (!embeddedQueue_.empty()) {
+ FrameBuffer *b = embeddedQueue_.front();
+ if (b->metadata().timestamp < ts) {
+ embeddedQueue_.pop();
+ unicam_[Unicam::Embedded].returnBuffer(b);
+ LOG(RPI, Debug) << "Dropping unmatched input frame in stream "
+ << unicam_[Unicam::Embedded].name();
+ } else if (b->metadata().timestamp == ts) {
+ /* Found a match! */
+ embeddedBuffer = b;
+ embeddedQueue_.pop();
+ break;
+ } else {
+ break; /* Only higher timestamps from here. */
+ }
+ }
+
+ if (!embeddedBuffer && sensorMetadata_) {
+ if (embeddedQueue_.empty()) {
+ /*
+ * If the embedded buffer queue is empty, wait for the next
+ * buffer to arrive - dequeue ordering may send the image
+ * buffer first.
+ */
+ LOG(RPI, Debug) << "Waiting for next embedded buffer.";
+ return false;
+ }
+
+ /* Log if there is no matching embedded data buffer found. */
+ LOG(RPI, Debug) << "Returning bayer frame without a matching embedded buffer.";
+ }
+
+ bayerFrame = std::move(bayerQueue_.front());
+ bayerQueue_.pop();
+
+ return true;
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerVc4)
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
index 9c99b32f..42b0896d 100644
--- a/src/libcamera/pipeline/simple/meson.build
+++ b/src/libcamera/pipeline/simple/meson.build
@@ -1,6 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
libcamera_sources += files([
- 'converter.cpp',
'simple.cpp',
])
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index bc0cb1a0..61a59926 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -30,13 +30,14 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/converter.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/software_isp/software_isp.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.h"
-#include "converter.h"
namespace libcamera {
@@ -100,8 +101,14 @@ LOG_DEFINE_CATEGORY(SimplePipeline)
*
* During the breadth-first search, the pipeline is traversed from entity to
* entity, by following media graph links from source to sink, starting at the
- * camera sensor. When reaching an entity (on its sink side), all its source
- * pads are considered to continue the graph traversal.
+ * camera sensor.
+ *
+ * When reaching an entity (on its sink side), if the entity is a V4L2 subdev
+ * that supports the streams API, the subdev internal routes are followed to
+ * find the connected source pads. Otherwise all of the entity's source pads
+ * are considered to continue the graph traversal. The pipeline handler
+ * currently considers the default internal routes only and doesn't attempt to
+ * setup custom routes. This can be extended if needed.
*
* The shortest path between the camera sensor and a video node is stored in
* SimpleCameraData::entities_ as a list of SimpleCameraData::Entity structures,
@@ -179,14 +186,24 @@ struct SimplePipelineInfo {
* and the number of streams it supports.
*/
std::vector<std::pair<const char *, unsigned int>> converters;
+ /*
+ * Using Software ISP is to be enabled per driver.
+ *
+ * The Software ISP can't be used together with the converters.
+ */
+ bool swIspEnabled;
};
namespace {
static const SimplePipelineInfo supportedDevices[] = {
- { "imx7-csi", { { "pxp", 1 } } },
- { "qcom-camss", {} },
- { "sun6i-csi", {} },
+ { "dcmipp", {}, false },
+ { "imx7-csi", { { "pxp", 1 } }, false },
+ { "j721e-csi2rx", {}, false },
+ { "mtk-seninf", { { "mtk-mdp", 3 } }, false },
+ { "mxc-isi", {}, false },
+ { "qcom-camss", {}, true },
+ { "sun6i-csi", {}, false },
};
} /* namespace */
@@ -204,7 +221,8 @@ public:
int init();
int setupLinks();
int setupFormats(V4L2SubdeviceFormat *format,
- V4L2Subdevice::Whence whence);
+ V4L2Subdevice::Whence whence,
+ Transform transform = Transform::Identity);
void bufferReady(FrameBuffer *buffer);
unsigned int streamIndex(const Stream *stream) const
@@ -216,6 +234,11 @@ public:
/* The media entity, always valid. */
MediaEntity *entity;
/*
+ * Whether or not the entity is a subdev that supports the
+ * routing API.
+ */
+ bool supportsRouting;
+ /*
* The local sink pad connected to the upstream entity, null for
* the camera sensor at the beginning of the pipeline.
*/
@@ -254,16 +277,22 @@ public:
std::vector<Configuration> configs_;
std::map<PixelFormat, std::vector<const Configuration *>> formats_;
- std::unique_ptr<SimpleConverter> converter_;
- std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
- bool useConverter_;
- std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
+ std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_;
+ std::queue<std::map<unsigned int, FrameBuffer *>> conversionQueue_;
+ bool useConversion_;
+
+ std::unique_ptr<Converter> converter_;
+ std::unique_ptr<SoftwareIsp> swIsp_;
private:
void tryPipeline(unsigned int code, const Size &size);
+ static std::vector<const MediaPad *> routedSourcePads(MediaPad *sink);
- void converterInputDone(FrameBuffer *buffer);
- void converterOutputDone(FrameBuffer *buffer);
+ void conversionInputDone(FrameBuffer *buffer);
+ void conversionOutputDone(FrameBuffer *buffer);
+
+ void ispStatsReady();
+ void setSensorControls(const ControlList &sensorControls);
};
class SimpleCameraConfiguration : public CameraConfiguration
@@ -279,6 +308,7 @@ public:
}
bool needConversion() const { return needConversion_; }
+ const Transform &combinedTransform() const { return combinedTransform_; }
private:
/*
@@ -291,6 +321,7 @@ private:
const SimpleCameraData::Configuration *pipeConfig_;
bool needConversion_;
+ Transform combinedTransform_;
};
class SimplePipelineHandler : public PipelineHandler
@@ -298,8 +329,8 @@ class SimplePipelineHandler : public PipelineHandler
public:
SimplePipelineHandler(CameraManager *manager);
- CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) override;
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -313,6 +344,7 @@ public:
V4L2VideoDevice *video(const MediaEntity *entity);
V4L2Subdevice *subdev(const MediaEntity *entity);
MediaDevice *converter() { return converter_; }
+ bool swIspEnabled() const { return swIspEnabled_; }
protected:
int queueRequestDevice(Camera *camera, Request *request) override;
@@ -332,6 +364,7 @@ private:
}
std::vector<MediaEntity *> locateSensors();
+ static int resetRoutingTable(V4L2Subdevice *subdev);
const MediaPad *acquirePipeline(SimpleCameraData *data);
void releasePipeline(SimpleCameraData *data);
@@ -340,6 +373,7 @@ private:
std::map<const MediaEntity *, EntityData> entities_;
MediaDevice *converter_;
+ bool swIspEnabled_;
};
/* -----------------------------------------------------------------------------
@@ -386,17 +420,40 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
break;
}
- /* The actual breadth-first search algorithm. */
visited.insert(entity);
- for (MediaPad *pad : entity->pads()) {
- if (!(pad->flags() & MEDIA_PAD_FL_SOURCE))
- continue;
+ /*
+ * Add direct downstream entities to the search queue. If the
+ * current entity supports the subdev internal routing API,
+ * restrict the search to downstream entities reachable through
+ * active routes.
+ */
+
+ std::vector<const MediaPad *> pads;
+ bool supportsRouting = false;
+
+ if (sinkPad) {
+ pads = routedSourcePads(sinkPad);
+ if (!pads.empty())
+ supportsRouting = true;
+ }
+
+ if (pads.empty()) {
+ for (const MediaPad *pad : entity->pads()) {
+ if (!(pad->flags() & MEDIA_PAD_FL_SOURCE))
+ continue;
+ pads.push_back(pad);
+ }
+ }
+
+ for (const MediaPad *pad : pads) {
for (MediaLink *link : pad->links()) {
MediaEntity *next = link->sink()->entity();
if (visited.find(next) == visited.end()) {
queue.push({ next, link->sink() });
- parents.insert({ next, { entity, sinkPad, pad, link } });
+
+ Entity e{ entity, supportsRouting, sinkPad, pad, link };
+ parents.insert({ next, e });
}
}
}
@@ -410,7 +467,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
* to the sensor. Store all the entities in the pipeline, from the
* camera sensor to the video node, in entities_.
*/
- entities_.push_front({ entity, sinkPad, nullptr, nullptr });
+ entities_.push_front({ entity, false, sinkPad, nullptr, nullptr });
for (auto it = parents.find(entity); it != parents.end();
it = parents.find(entity)) {
@@ -455,14 +512,45 @@ int SimpleCameraData::init()
/* Open the converter, if any. */
MediaDevice *converter = pipe->converter();
if (converter) {
- converter_ = std::make_unique<SimpleConverter>(converter);
- if (!converter_->isValid()) {
+ converter_ = ConverterFactoryBase::create(converter);
+ if (!converter_) {
LOG(SimplePipeline, Warning)
<< "Failed to create converter, disabling format conversion";
converter_.reset();
} else {
- converter_->inputBufferReady.connect(this, &SimpleCameraData::converterInputDone);
- converter_->outputBufferReady.connect(this, &SimpleCameraData::converterOutputDone);
+ converter_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone);
+ converter_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
+ }
+ }
+
+ /*
+ * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
+ */
+ if (!converter_ && pipe->swIspEnabled()) {
+ swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get());
+ if (!swIsp_->isValid()) {
+ LOG(SimplePipeline, Warning)
+ << "Failed to create software ISP, disabling software debayering";
+ swIsp_.reset();
+ } else {
+ /*
+ * The inputBufferReady signal is emitted from the soft ISP thread,
+ * and needs to be handled in the pipeline handler thread. Signals
+ * implement queued delivery, but this works transparently only if
+ * the receiver is bound to the target thread. As the
+ * SimpleCameraData class doesn't inherit from the Object class, it
+ * is not bound to any thread, and the signal would be delivered
+ * synchronously. Instead, connect the signal to a lambda function
+ * bound explicitly to the pipe, which is bound to the pipeline
+ * handler thread. The function then simply forwards the call to
+ * conversionInputDone().
+ */
+ swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
+ this->conversionInputDone(buffer);
+ });
+ swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
+ swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
+ swIsp_->setSensorControls.connect(this, &SimpleCameraData::setSensorControls);
}
}
@@ -520,13 +608,13 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size)
* corresponding possible V4L2 pixel formats on the video node.
*/
V4L2SubdeviceFormat format{};
- format.mbus_code = code;
+ format.code = code;
format.size = size;
int ret = setupFormats(&format, V4L2Subdevice::TryFormat);
if (ret < 0) {
/* Pipeline configuration failed, skip this configuration. */
- format.mbus_code = code;
+ format.code = code;
format.size = size;
LOG(SimplePipeline, Debug)
<< "Sensor format " << format
@@ -534,7 +622,7 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size)
return;
}
- V4L2VideoDevice::Formats videoFormats = video_->formats(format.mbus_code);
+ V4L2VideoDevice::Formats videoFormats = video_->formats(format.code);
LOG(SimplePipeline, Debug)
<< "Adding configuration for " << format.size
@@ -556,12 +644,20 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size)
config.captureFormat = pixelFormat;
config.captureSize = format.size;
- if (!converter_) {
- config.outputFormats = { pixelFormat };
- config.outputSizes = config.captureSize;
- } else {
+ if (converter_) {
config.outputFormats = converter_->formats(pixelFormat);
config.outputSizes = converter_->sizes(format.size);
+ } else if (swIsp_) {
+ config.outputFormats = swIsp_->formats(pixelFormat);
+ config.outputSizes = swIsp_->sizes(pixelFormat, format.size);
+ if (config.outputFormats.empty()) {
+ /* Do not use swIsp for unsupported pixelFormat's. */
+ config.outputFormats = { pixelFormat };
+ config.outputSizes = config.captureSize;
+ }
+ } else {
+ config.outputFormats = { pixelFormat };
+ config.outputSizes = config.captureSize;
}
configs_.push_back(config);
@@ -577,15 +673,32 @@ int SimpleCameraData::setupLinks()
* multiple sink links to be enabled together, even on different sink
* pads. We must thus start by disabling all sink links (but the one we
* want to enable) before enabling the pipeline link.
+ *
+ * The entities_ list stores entities along with their source link. We
+ * need to process the link in the context of the sink entity, so
+ * record the source link of the current entity as the sink link of the
+ * next entity, and skip the first entity in the loop.
*/
+ MediaLink *sinkLink = nullptr;
+
for (SimpleCameraData::Entity &e : entities_) {
- if (!e.sourceLink)
- break;
+ if (!sinkLink) {
+ sinkLink = e.sourceLink;
+ continue;
+ }
+
+ for (MediaPad *pad : e.entity->pads()) {
+ /*
+ * If the entity supports the V4L2 internal routing API,
+ * assume that it may carry multiple independent streams
+ * concurrently, and only disable links on the sink and
+ * source pads used by the pipeline.
+ */
+ if (e.supportsRouting && pad != e.sink && pad != e.source)
+ continue;
- MediaEntity *remote = e.sourceLink->sink()->entity();
- for (MediaPad *pad : remote->pads()) {
for (MediaLink *link : pad->links()) {
- if (link == e.sourceLink)
+ if (link == sinkLink)
continue;
if ((link->flags() & MEDIA_LNK_FL_ENABLED) &&
@@ -597,18 +710,21 @@ int SimpleCameraData::setupLinks()
}
}
- if (!(e.sourceLink->flags() & MEDIA_LNK_FL_ENABLED)) {
- ret = e.sourceLink->setEnabled(true);
+ if (!(sinkLink->flags() & MEDIA_LNK_FL_ENABLED)) {
+ ret = sinkLink->setEnabled(true);
if (ret < 0)
return ret;
}
+
+ sinkLink = e.sourceLink;
}
return 0;
}
int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
- V4L2Subdevice::Whence whence)
+ V4L2Subdevice::Whence whence,
+ Transform transform)
{
SimplePipelineHandler *pipe = SimpleCameraData::pipe();
int ret;
@@ -617,7 +733,7 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
* Configure the format on the sensor output and propagate it through
* the pipeline.
*/
- ret = sensor_->setFormat(format);
+ ret = sensor_->setFormat(format, transform);
if (ret < 0)
return ret;
@@ -644,7 +760,7 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
if (ret < 0)
return ret;
- if (format->mbus_code != sourceFormat.mbus_code ||
+ if (format->code != sourceFormat.code ||
format->size != sourceFormat.size) {
LOG(SimplePipeline, Debug)
<< "Source '" << source->entity()->name()
@@ -678,7 +794,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
* point converting an erroneous buffer.
*/
if (buffer->metadata().status != FrameMetadata::FrameSuccess) {
- if (!useConverter_) {
+ if (!useConversion_) {
/* No conversion, just complete the request. */
Request *request = buffer->request();
pipe->completeBuffer(request, buffer);
@@ -687,23 +803,23 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
}
/*
- * The converter is in use. Requeue the internal buffer for
- * capture (unless the stream is being stopped), and complete
- * the request with all the user-facing buffers.
+ * The converter or Software ISP is in use. Requeue the internal
+ * buffer for capture (unless the stream is being stopped), and
+ * complete the request with all the user-facing buffers.
*/
if (buffer->metadata().status != FrameMetadata::FrameCancelled)
video_->queueBuffer(buffer);
- if (converterQueue_.empty())
+ if (conversionQueue_.empty())
return;
Request *request = nullptr;
- for (auto &item : converterQueue_.front()) {
+ for (auto &item : conversionQueue_.front()) {
FrameBuffer *outputBuffer = item.second;
request = outputBuffer->request();
pipe->completeBuffer(request, outputBuffer);
}
- converterQueue_.pop();
+ conversionQueue_.pop();
if (request)
pipe->completeRequest(request);
@@ -720,9 +836,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
*/
Request *request = buffer->request();
- if (useConverter_ && !converterQueue_.empty()) {
+ if (useConversion_ && !conversionQueue_.empty()) {
const std::map<unsigned int, FrameBuffer *> &outputs =
- converterQueue_.front();
+ conversionQueue_.front();
if (!outputs.empty()) {
FrameBuffer *outputBuffer = outputs.begin()->second;
if (outputBuffer)
@@ -735,18 +851,22 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
buffer->metadata().timestamp);
/*
- * Queue the captured and the request buffer to the converter if format
- * conversion is needed. If there's no queued request, just requeue the
- * captured buffer for capture.
+ * Queue the captured and the request buffer to the converter or Software
+ * ISP if format conversion is needed. If there's no queued request, just
+ * requeue the captured buffer for capture.
*/
- if (useConverter_) {
- if (converterQueue_.empty()) {
+ if (useConversion_) {
+ if (conversionQueue_.empty()) {
video_->queueBuffer(buffer);
return;
}
- converter_->queueBuffers(buffer, converterQueue_.front());
- converterQueue_.pop();
+ if (converter_)
+ converter_->queueBuffers(buffer, conversionQueue_.front());
+ else
+ swIsp_->queueBuffers(buffer, conversionQueue_.front());
+
+ conversionQueue_.pop();
return;
}
@@ -755,13 +875,13 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
pipe->completeRequest(request);
}
-void SimpleCameraData::converterInputDone(FrameBuffer *buffer)
+void SimpleCameraData::conversionInputDone(FrameBuffer *buffer)
{
/* Queue the input buffer back for capture. */
video_->queueBuffer(buffer);
}
-void SimpleCameraData::converterOutputDone(FrameBuffer *buffer)
+void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
{
SimplePipelineHandler *pipe = SimpleCameraData::pipe();
@@ -771,6 +891,56 @@ void SimpleCameraData::converterOutputDone(FrameBuffer *buffer)
pipe->completeRequest(request);
}
+void SimpleCameraData::ispStatsReady()
+{
+ /* \todo Use the DelayedControls class */
+ swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN,
+ V4L2_CID_EXPOSURE }));
+}
+
+void SimpleCameraData::setSensorControls(const ControlList &sensorControls)
+{
+ ControlList ctrls(sensorControls);
+ sensor_->setControls(&ctrls);
+}
+
+/* Retrieve all source pads connected to a sink pad through active routes. */
+std::vector<const MediaPad *> SimpleCameraData::routedSourcePads(MediaPad *sink)
+{
+ MediaEntity *entity = sink->entity();
+ std::unique_ptr<V4L2Subdevice> subdev =
+ std::make_unique<V4L2Subdevice>(entity);
+
+ int ret = subdev->open();
+ if (ret < 0)
+ return {};
+
+ V4L2Subdevice::Routing routing = {};
+ ret = subdev->getRouting(&routing, V4L2Subdevice::ActiveFormat);
+ if (ret < 0)
+ return {};
+
+ std::vector<const MediaPad *> pads;
+
+ for (const V4L2Subdevice::Route &route : routing) {
+ if (sink->index() != route.sink.pad ||
+ !(route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
+ continue;
+
+ const MediaPad *pad = entity->getPadByIndex(route.source.pad);
+ if (!pad) {
+ LOG(SimplePipeline, Warning)
+ << "Entity " << entity->name()
+ << " has invalid route source pad "
+ << route.source.pad;
+ }
+
+ pads.push_back(pad);
+ }
+
+ return pads;
+}
+
/* -----------------------------------------------------------------------------
* Camera Configuration
*/
@@ -782,17 +952,45 @@ SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera,
{
}
+namespace {
+
+static Size adjustSize(const Size &requestedSize, const SizeRange &supportedSizes)
+{
+ ASSERT(supportedSizes.min <= supportedSizes.max);
+
+ if (supportedSizes.min == supportedSizes.max)
+ return supportedSizes.max;
+
+ unsigned int hStep = supportedSizes.hStep;
+ unsigned int vStep = supportedSizes.vStep;
+
+ if (hStep == 0)
+ hStep = supportedSizes.max.width - supportedSizes.min.width;
+ if (vStep == 0)
+ vStep = supportedSizes.max.height - supportedSizes.min.height;
+
+ Size adjusted = requestedSize.boundedTo(supportedSizes.max)
+ .expandedTo(supportedSizes.min);
+
+ return adjusted.shrunkBy(supportedSizes.min)
+ .alignedDownTo(hStep, vStep)
+ .grownBy(supportedSizes.min);
+}
+
+} /* namespace */
+
CameraConfiguration::Status SimpleCameraConfiguration::validate()
{
+ const CameraSensor *sensor = data_->sensor_.get();
Status status = Valid;
if (config_.empty())
return Invalid;
- if (transform != Transform::Identity) {
- transform = Transform::Identity;
+ Orientation requestedOrientation = orientation;
+ combinedTransform_ = sensor->computeTransform(&orientation);
+ if (orientation != requestedOrientation)
status = Adjusted;
- }
/* Cap the number of entries to the available streams. */
if (config_.size() > data_->streams_.size()) {
@@ -897,10 +1095,19 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
}
if (!pipeConfig_->outputSizes.contains(cfg.size)) {
+ Size adjustedSize = pipeConfig_->captureSize;
+ /*
+ * The converter (when present) may not be able to output
+ * a size identical to its input size. The capture size is thus
+ * not guaranteed to be a valid output size. In such cases, use
+ * the smaller valid output size closest to the requested.
+ */
+ if (!pipeConfig_->outputSizes.contains(adjustedSize))
+ adjustedSize = adjustSize(cfg.size, pipeConfig_->outputSizes);
LOG(SimplePipeline, Debug)
<< "Adjusting size from " << cfg.size
- << " to " << pipeConfig_->captureSize;
- cfg.size = pipeConfig_->captureSize;
+ << " to " << adjustedSize;
+ cfg.size = adjustedSize;
status = Adjusted;
}
@@ -912,13 +1119,16 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
/* Set the stride, frameSize and bufferCount. */
if (needConversion_) {
std::tie(cfg.stride, cfg.frameSize) =
- data_->converter_->strideAndFrameSize(cfg.pixelFormat,
- cfg.size);
+ data_->converter_
+ ? data_->converter_->strideAndFrameSize(cfg.pixelFormat,
+ cfg.size)
+ : data_->swIsp_->strideAndFrameSize(cfg.pixelFormat,
+ cfg.size);
if (cfg.stride == 0)
return Invalid;
} else {
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
+ format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
int ret = data_->video_->tryFormat(&format);
@@ -944,12 +1154,12 @@ SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)
{
}
-CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
+std::unique_ptr<CameraConfiguration>
+SimplePipelineHandler::generateConfiguration(Camera *camera, Span<const StreamRole> roles)
{
SimpleCameraData *data = cameraData(camera);
- CameraConfiguration *config =
- new SimpleCameraConfiguration(camera, data);
+ std::unique_ptr<CameraConfiguration> config =
+ std::make_unique<SimpleCameraConfiguration>(camera, data);
if (roles.empty())
return config;
@@ -1020,15 +1230,16 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
const SimpleCameraData::Configuration *pipeConfig = config->pipeConfig();
V4L2SubdeviceFormat format{};
- format.mbus_code = pipeConfig->code;
+ format.code = pipeConfig->code;
format.size = pipeConfig->sensorSize;
- ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat);
+ ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat,
+ config->combinedTransform());
if (ret < 0)
return ret;
/* Configure the video node. */
- V4L2PixelFormat videoFormat = V4L2PixelFormat::fromPixelFormat(pipeConfig->captureFormat);
+ V4L2PixelFormat videoFormat = video->toV4L2PixelFormat(pipeConfig->captureFormat);
V4L2DeviceFormat captureFormat;
captureFormat.fourcc = videoFormat;
@@ -1055,14 +1266,14 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
/* Configure the converter if needed. */
std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs;
- data->useConverter_ = config->needConversion();
+ data->useConversion_ = config->needConversion();
for (unsigned int i = 0; i < config->size(); ++i) {
StreamConfiguration &cfg = config->at(i);
cfg.setStream(&data->streams_[i]);
- if (data->useConverter_)
+ if (data->useConversion_)
outputCfgs.push_back(cfg);
}
@@ -1075,7 +1286,10 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
inputCfg.stride = captureFormat.planes[0].bpl;
inputCfg.bufferCount = kNumInternalBuffers;
- return data->converter_->configure(inputCfg, outputCfgs);
+ return data->converter_
+ ? data->converter_->configure(inputCfg, outputCfgs)
+ : data->swIsp_->configure(inputCfg, outputCfgs,
+ data->sensor_->controls());
}
int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
@@ -1088,9 +1302,12 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
* Export buffers on the converter or capture video node, depending on
* whether the converter is used or not.
*/
- if (data->useConverter_)
- return data->converter_->exportBuffers(data->streamIndex(stream),
- count, buffers);
+ if (data->useConversion_)
+ return data->converter_
+ ? data->converter_->exportBuffers(data->streamIndex(stream),
+ count, buffers)
+ : data->swIsp_->exportBuffers(data->streamIndex(stream),
+ count, buffers);
else
return data->video_->exportBuffers(count, buffers);
}
@@ -1109,13 +1326,13 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
return -EBUSY;
}
- if (data->useConverter_) {
+ if (data->useConversion_) {
/*
* When using the converter allocate a fixed number of internal
* buffers.
*/
ret = video->allocateBuffers(kNumInternalBuffers,
- &data->converterBuffers_);
+ &data->conversionBuffers_);
} else {
/* Otherwise, prepare for using buffers from the only stream. */
Stream *stream = &data->streams_[0];
@@ -1134,15 +1351,21 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
return ret;
}
- if (data->useConverter_) {
- ret = data->converter_->start();
+ if (data->useConversion_) {
+ if (data->converter_)
+ ret = data->converter_->start();
+ else if (data->swIsp_)
+ ret = data->swIsp_->start();
+ else
+ ret = 0;
+
if (ret < 0) {
stop(camera);
return ret;
}
/* Queue all internal buffers for capture. */
- for (std::unique_ptr<FrameBuffer> &buffer : data->converterBuffers_)
+ for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_)
video->queueBuffer(buffer.get());
}
@@ -1154,15 +1377,19 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
SimpleCameraData *data = cameraData(camera);
V4L2VideoDevice *video = data->video_;
- if (data->useConverter_)
- data->converter_->stop();
+ if (data->useConversion_) {
+ if (data->converter_)
+ data->converter_->stop();
+ else if (data->swIsp_)
+ data->swIsp_->stop();
+ }
video->streamOff();
video->releaseBuffers();
video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady);
- data->converterBuffers_.clear();
+ data->conversionBuffers_.clear();
releasePipeline(data);
}
@@ -1180,7 +1407,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
* queue, it will be handed to the converter in the capture
* completion handler.
*/
- if (data->useConverter_) {
+ if (data->useConversion_) {
buffers.emplace(data->streamIndex(stream), buffer);
} else {
ret = data->video_->queueBuffer(buffer);
@@ -1189,8 +1416,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
}
}
- if (data->useConverter_)
- data->converterQueue_.push(std::move(buffers));
+ if (data->useConversion_)
+ data->conversionQueue_.push(std::move(buffers));
return 0;
}
@@ -1260,6 +1487,37 @@ std::vector<MediaEntity *> SimplePipelineHandler::locateSensors()
return sensors;
}
+int SimplePipelineHandler::resetRoutingTable(V4L2Subdevice *subdev)
+{
+ /* Reset the media entity routing table to its default state. */
+ V4L2Subdevice::Routing routing = {};
+
+ int ret = subdev->getRouting(&routing, V4L2Subdevice::TryFormat);
+ if (ret)
+ return ret;
+
+ ret = subdev->setRouting(&routing, V4L2Subdevice::ActiveFormat);
+ if (ret)
+ return ret;
+
+ /*
+ * If the routing table is empty we won't be able to meaningfully use
+ * the subdev.
+ */
+ if (routing.empty()) {
+ LOG(SimplePipeline, Error)
+ << "Default routing table of " << subdev->deviceNode()
+ << " is empty";
+ return -EINVAL;
+ }
+
+ LOG(SimplePipeline, Debug)
+ << "Routing table of " << subdev->deviceNode()
+ << " reset to " << routing;
+
+ return 0;
+}
+
bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
{
const SimplePipelineInfo *info = nullptr;
@@ -1286,6 +1544,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
}
}
+ swIspEnabled_ = info->swIspEnabled;
+
/* Locate the sensors. */
std::vector<MediaEntity *> sensors = locateSensors();
if (sensors.empty()) {
@@ -1352,6 +1612,23 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
<< ": " << strerror(-ret);
return false;
}
+
+ if (subdev->caps().hasStreams()) {
+ /*
+ * Reset the routing table to its default state
+ * to make sure entities are enumerate according
+ * to the defaul routing configuration.
+ */
+ ret = resetRoutingTable(subdev.get());
+ if (ret) {
+ LOG(SimplePipeline, Error)
+ << "Failed to reset routes for "
+ << subdev->deviceNode() << ": "
+ << strerror(-ret);
+ return false;
+ }
+ }
+
break;
default:
diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
index 53b2f23a..ed9c7f88 100644
--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -46,8 +46,16 @@ public:
ControlInfoMap::Map *ctrls);
void bufferReady(FrameBuffer *buffer);
+ const std::string &id() const { return id_; }
+
std::unique_ptr<V4L2VideoDevice> video_;
Stream stream_;
+ std::map<PixelFormat, std::vector<SizeRange>> formats_;
+
+private:
+ bool generateId();
+
+ std::string id_;
};
class UVCCameraConfiguration : public CameraConfiguration
@@ -66,8 +74,8 @@ class PipelineHandlerUVC : public PipelineHandler
public:
PipelineHandlerUVC(CameraManager *manager);
- CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) override;
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -81,8 +89,6 @@ public:
bool match(DeviceEnumerator *enumerator) override;
private:
- std::string generateId(const UVCCameraData *data);
-
int processControl(ControlList *controls, unsigned int id,
const ControlValue &value);
int processControls(UVCCameraData *data, Request *request);
@@ -105,8 +111,8 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
if (config_.empty())
return Invalid;
- if (transform != Transform::Identity) {
- transform = Transform::Identity;
+ if (orientation != Orientation::Rotate0) {
+ orientation = Orientation::Rotate0;
status = Adjusted;
}
@@ -149,7 +155,7 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
cfg.bufferCount = 4;
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
+ format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
int ret = data_->video_->tryFormat(&format);
@@ -159,6 +165,11 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
cfg.stride = format.planes[0].bpl;
cfg.frameSize = format.planes[0].size;
+ if (cfg.colorSpace != format.colorSpace) {
+ cfg.colorSpace = format.colorSpace;
+ status = Adjusted;
+ }
+
return status;
}
@@ -167,24 +178,18 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)
{
}
-CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerUVC::generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles)
{
UVCCameraData *data = cameraData(camera);
- CameraConfiguration *config = new UVCCameraConfiguration(data);
+ std::unique_ptr<CameraConfiguration> config =
+ std::make_unique<UVCCameraConfiguration>(data);
if (roles.empty())
return config;
- V4L2VideoDevice::Formats v4l2Formats = data->video_->formats();
- std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
- for (const auto &format : v4l2Formats) {
- PixelFormat pixelFormat = format.first.toPixelFormat();
- if (pixelFormat.isValid())
- deviceFormats[pixelFormat] = format.second;
- }
-
- StreamFormats formats(deviceFormats);
+ StreamFormats formats(data->formats_);
StreamConfiguration cfg(formats);
cfg.pixelFormat = formats.pixelformats().front();
@@ -205,7 +210,7 @@ int PipelineHandlerUVC::configure(Camera *camera, CameraConfiguration *config)
int ret;
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
+ format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
ret = data->video_->setFormat(&format);
@@ -213,7 +218,7 @@ int PipelineHandlerUVC::configure(Camera *camera, CameraConfiguration *config)
return ret;
if (format.size != cfg.size ||
- format.fourcc != V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat))
+ format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
return -EINVAL;
cfg.setStream(&data->stream_);
@@ -340,12 +345,8 @@ int PipelineHandlerUVC::processControls(UVCCameraData *data, Request *request)
{
ControlList controls(data->video_->controls());
- for (auto it : request->controls()) {
- unsigned int id = it.first;
- ControlValue &value = it.second;
-
+ for (const auto &[id, value] : request->controls())
processControl(&controls, id, value);
- }
for (const auto &ctrl : controls)
LOG(UVC, Debug)
@@ -383,69 +384,6 @@ int PipelineHandlerUVC::queueRequestDevice(Camera *camera, Request *request)
return 0;
}
-std::string PipelineHandlerUVC::generateId(const UVCCameraData *data)
-{
- const std::string path = data->video_->devicePath();
-
- /* Create a controller ID from first device described in firmware. */
- std::string controllerId;
- std::string searchPath = path;
- while (true) {
- std::string::size_type pos = searchPath.rfind('/');
- if (pos <= 1) {
- LOG(UVC, Error) << "Can not find controller ID";
- return {};
- }
-
- searchPath = searchPath.substr(0, pos);
-
- controllerId = sysfs::firmwareNodePath(searchPath);
- if (!controllerId.empty())
- break;
- }
-
- /*
- * Create a USB ID from the device path which has the known format:
- *
- * path = bus, "-", ports, ":", config, ".", interface ;
- * bus = number ;
- * ports = port, [ ".", ports ] ;
- * port = number ;
- * config = number ;
- * interface = number ;
- *
- * Example: 3-2.4:1.0
- *
- * The bus is not guaranteed to be stable and needs to be stripped from
- * the USB ID. The final USB ID is built up of the ports, config and
- * interface properties.
- *
- * Example 2.4:1.0.
- */
- std::string usbId = utils::basename(path.c_str());
- usbId = usbId.substr(usbId.find('-') + 1);
-
- /* Creata a device ID from the USB devices vendor and product ID. */
- std::string deviceId;
- for (const char *name : { "idVendor", "idProduct" }) {
- std::ifstream file(path + "/../" + name);
-
- if (!file.is_open())
- return {};
-
- std::string value;
- std::getline(file, value);
- file.close();
-
- if (!deviceId.empty())
- deviceId += ":";
-
- deviceId += value;
- }
-
- return controllerId + "-" + usbId + "-" + deviceId;
-}
-
bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
{
MediaDevice *media;
@@ -461,12 +399,7 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
return false;
/* Create and register the camera. */
- std::string id = generateId(data.get());
- if (id.empty()) {
- LOG(UVC, Error) << "Failed to generate camera ID";
- return false;
- }
-
+ std::string id = data->id();
std::set<Stream *> streams{ &data->stream_ };
std::shared_ptr<Camera> camera =
Camera::create(std::move(data), id, streams);
@@ -501,6 +434,39 @@ int UVCCameraData::init(MediaDevice *media)
video_->bufferReady.connect(this, &UVCCameraData::bufferReady);
+ /* Generate the camera ID. */
+ if (!generateId()) {
+ LOG(UVC, Error) << "Failed to generate camera ID";
+ return -EINVAL;
+ }
+
+ /*
+ * Populate the map of supported formats, and infer the camera sensor
+ * resolution from the largest size it advertises.
+ */
+ Size resolution;
+ for (const auto &format : video_->formats()) {
+ PixelFormat pixelFormat = format.first.toPixelFormat();
+ if (!pixelFormat.isValid())
+ continue;
+
+ formats_[pixelFormat] = format.second;
+
+ const std::vector<SizeRange> &sizeRanges = format.second;
+ for (const SizeRange &sizeRange : sizeRanges) {
+ if (sizeRange.max > resolution)
+ resolution = sizeRange.max;
+ }
+ }
+
+ if (formats_.empty()) {
+ LOG(UVC, Error)
+ << "Camera " << id_ << " (" << media->model()
+ << ") doesn't expose any supported format";
+ return -EINVAL;
+ }
+
+ /* Populate the camera properties. */
properties_.set(properties::Model, utils::toAscii(media->model()));
/*
@@ -531,19 +497,6 @@ int UVCCameraData::init(MediaDevice *media)
properties_.set(properties::Location, location);
- /*
- * Get the current format in order to initialize the sensor array
- * properties.
- */
- Size resolution;
- for (const auto &it : video_->formats()) {
- const std::vector<SizeRange> &sizeRanges = it.second;
- for (const SizeRange &sizeRange : sizeRanges) {
- if (sizeRange.max > resolution)
- resolution = sizeRange.max;
- }
- }
-
properties_.set(properties::PixelArraySize, resolution);
properties_.set(properties::PixelArrayActiveAreas, { Rectangle(resolution) });
@@ -562,6 +515,70 @@ int UVCCameraData::init(MediaDevice *media)
return 0;
}
+bool UVCCameraData::generateId()
+{
+ const std::string path = video_->devicePath();
+
+ /* Create a controller ID from first device described in firmware. */
+ std::string controllerId;
+ std::string searchPath = path;
+ while (true) {
+ std::string::size_type pos = searchPath.rfind('/');
+ if (pos <= 1) {
+ LOG(UVC, Error) << "Can not find controller ID";
+ return false;
+ }
+
+ searchPath = searchPath.substr(0, pos);
+
+ controllerId = sysfs::firmwareNodePath(searchPath);
+ if (!controllerId.empty())
+ break;
+ }
+
+ /*
+ * Create a USB ID from the device path which has the known format:
+ *
+ * path = bus, "-", ports, ":", config, ".", interface ;
+ * bus = number ;
+ * ports = port, [ ".", ports ] ;
+ * port = number ;
+ * config = number ;
+ * interface = number ;
+ *
+ * Example: 3-2.4:1.0
+ *
+ * The bus is not guaranteed to be stable and needs to be stripped from
+ * the USB ID. The final USB ID is built up of the ports, config and
+ * interface properties.
+ *
+ * Example 2.4:1.0.
+ */
+ std::string usbId = utils::basename(path.c_str());
+ usbId = usbId.substr(usbId.find('-') + 1);
+
+ /* Creata a device ID from the USB devices vendor and product ID. */
+ std::string deviceId;
+ for (const char *name : { "idVendor", "idProduct" }) {
+ std::ifstream file(path + "/../" + name);
+
+ if (!file.is_open())
+ return false;
+
+ std::string value;
+ std::getline(file, value);
+ file.close();
+
+ if (!deviceId.empty())
+ deviceId += ":";
+
+ deviceId += value;
+ }
+
+ id_ = controllerId + "-" + usbId + "-" + deviceId;
+ return true;
+}
+
void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info,
ControlInfoMap::Map *ctrls)
{
diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
index 3379ac5c..5e66ee1d 100644
--- a/src/libcamera/pipeline/vimc/vimc.cpp
+++ b/src/libcamera/pipeline/vimc/vimc.cpp
@@ -54,7 +54,7 @@ public:
int init();
int allocateMockIPABuffers();
void bufferReady(FrameBuffer *buffer);
- void paramsBufferReady(unsigned int id);
+ void paramsBufferReady(unsigned int id, const Flags<ipa::vimc::TestFlag> flags);
MediaDevice *media_;
std::unique_ptr<CameraSensor> sensor_;
@@ -84,8 +84,8 @@ class PipelineHandlerVimc : public PipelineHandler
public:
PipelineHandlerVimc(CameraManager *manager);
- CameraConfiguration *generateConfiguration(Camera *camera,
- const StreamRoles &roles) override;
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
@@ -128,8 +128,8 @@ CameraConfiguration::Status VimcCameraConfiguration::validate()
if (config_.empty())
return Invalid;
- if (transform != Transform::Identity) {
- transform = Transform::Identity;
+ if (orientation != Orientation::Rotate0) {
+ orientation = Orientation::Rotate0;
status = Adjusted;
}
@@ -171,7 +171,7 @@ CameraConfiguration::Status VimcCameraConfiguration::validate()
cfg.bufferCount = 4;
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
+ format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
int ret = data_->video_->tryFormat(&format);
@@ -189,11 +189,13 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)
{
}
-CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,
- const StreamRoles &roles)
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerVimc::generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles)
{
VimcCameraData *data = cameraData(camera);
- CameraConfiguration *config = new VimcCameraConfiguration(data);
+ std::unique_ptr<CameraConfiguration> config =
+ std::make_unique<VimcCameraConfiguration>(data);
if (roles.empty())
return config;
@@ -242,7 +244,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
/* The scaler hardcodes a x3 scale-up ratio. */
V4L2SubdeviceFormat subformat = {};
- subformat.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8;
+ subformat.code = MEDIA_BUS_FMT_SGRBG8_1X8;
subformat.size = { cfg.size.width / 3, cfg.size.height / 3 };
ret = data->sensor_->setFormat(&subformat);
@@ -253,7 +255,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
if (ret)
return ret;
- subformat.mbus_code = pixelformats.find(cfg.pixelFormat)->second;
+ subformat.code = pixelformats.find(cfg.pixelFormat)->second;
ret = data->debayer_->setFormat(1, &subformat);
if (ret)
return ret;
@@ -275,7 +277,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
return ret;
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
+ format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
ret = data->video_->setFormat(&format);
@@ -283,7 +285,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
return ret;
if (format.size != cfg.size ||
- format.fourcc != V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat))
+ format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
return -EINVAL;
/*
@@ -378,7 +380,7 @@ int PipelineHandlerVimc::processControls(VimcCameraData *data, Request *request)
{
ControlList controls(data->sensor_->controls());
- for (auto it : request->controls()) {
+ for (const auto &it : request->controls()) {
unsigned int id = it.first;
unsigned int offset;
uint32_t cid;
@@ -471,7 +473,15 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
data->ipa_->paramsBufferReady.connect(data.get(), &VimcCameraData::paramsBufferReady);
std::string conf = data->ipa_->configurationFile("vimc.conf");
- data->ipa_->init(IPASettings{ conf, data->sensor_->model() });
+ Flags<ipa::vimc::TestFlag> inFlags = ipa::vimc::TestFlag::Flag2;
+ Flags<ipa::vimc::TestFlag> outFlags;
+ data->ipa_->init(IPASettings{ conf, data->sensor_->model() },
+ ipa::vimc::IPAOperationInit, inFlags, &outFlags);
+
+ LOG(VIMC, Debug)
+ << "Flag 1 was "
+ << (outFlags & ipa::vimc::TestFlag::Flag1 ? "" : "not ")
+ << "set";
/* Create and register the camera. */
std::set<Stream *> streams{ &data->stream_ };
@@ -598,7 +608,7 @@ int VimcCameraData::allocateMockIPABuffers()
constexpr unsigned int kBufCount = 2;
V4L2DeviceFormat format;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(formats::BGR888);
+ format.fourcc = video_->toV4L2PixelFormat(formats::BGR888);
format.size = Size (160, 120);
int ret = video_->setFormat(&format);
@@ -608,7 +618,8 @@ int VimcCameraData::allocateMockIPABuffers()
return video_->exportBuffers(kBufCount, &mockIPABufs_);
}
-void VimcCameraData::paramsBufferReady([[maybe_unused]] unsigned int id)
+void VimcCameraData::paramsBufferReady([[maybe_unused]] unsigned int id,
+ [[maybe_unused]] const Flags<ipa::vimc::TestFlag> flags)
{
}
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 7ebd76ad..29e0c98a 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -8,6 +8,7 @@
#include "libcamera/internal/pipeline_handler.h"
#include <chrono>
+#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <libcamera/base/log.h>
@@ -15,10 +16,11 @@
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
#include <libcamera/framebuffer.h>
+#include <libcamera/property_ids.h>
#include "libcamera/internal/camera.h"
+#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/media_device.h"
@@ -64,11 +66,10 @@ LOG_DEFINE_CATEGORY(Pipeline)
*
* In order to honour the std::enable_shared_from_this<> contract,
* PipelineHandler instances shall never be constructed manually, but always
- * through the PipelineHandlerFactory::create() function implemented by the
- * respective factories.
+ * through the PipelineHandlerFactoryBase::create() function.
*/
PipelineHandler::PipelineHandler(CameraManager *manager)
- : manager_(manager), lockOwner_(false)
+ : manager_(manager), useCount_(0)
{
}
@@ -143,58 +144,89 @@ MediaDevice *PipelineHandler::acquireMediaDevice(DeviceEnumerator *enumerator,
}
/**
- * \brief Lock all media devices acquired by the pipeline
+ * \brief Acquire exclusive access to the pipeline handler for the process
*
- * This function shall not be called from pipeline handler implementation, as
- * the Camera class handles locking directly.
+ * This function locks all the media devices used by the pipeline to ensure
+ * that no other process can access them concurrently.
+ *
+ * Access to a pipeline handler may be acquired recursively from within the
+ * same process. Every successful acquire() call shall be matched with a
+ * release() call. This allows concurrent access to the same pipeline handler
+ * from different cameras within the same process.
+ *
+ * Pipeline handlers shall not call this function directly as the Camera class
+ * handles access internally.
*
* \context This function is \threadsafe.
*
- * \return True if the devices could be locked, false otherwise
- * \sa unlock()
- * \sa MediaDevice::lock()
+ * \return True if the pipeline handler was acquired, false if another process
+ * has already acquired it
+ * \sa release()
*/
-bool PipelineHandler::lock()
+bool PipelineHandler::acquire()
{
MutexLocker locker(lock_);
- /* Do not allow nested locking in the same libcamera instance. */
- if (lockOwner_)
- return false;
+ if (useCount_) {
+ ++useCount_;
+ return true;
+ }
for (std::shared_ptr<MediaDevice> &media : mediaDevices_) {
if (!media->lock()) {
- unlock();
+ unlockMediaDevices();
return false;
}
}
- lockOwner_ = true;
-
+ ++useCount_;
return true;
}
/**
- * \brief Unlock all media devices acquired by the pipeline
+ * \brief Release exclusive access to the pipeline handler
+ * \param[in] camera The camera for which to release data
*
- * This function shall not be called from pipeline handler implementation, as
- * the Camera class handles locking directly.
+ * This function releases access to the pipeline handler previously acquired by
+ * a call to acquire(). Every release() call shall match a previous successful
+ * acquire() call. Calling this function on a pipeline handler that hasn't been
+ * acquired results in undefined behaviour.
+ *
+ * Pipeline handlers shall not call this function directly as the Camera class
+ * handles access internally.
*
* \context This function is \threadsafe.
*
- * \sa lock()
+ * \sa acquire()
*/
-void PipelineHandler::unlock()
+void PipelineHandler::release(Camera *camera)
{
MutexLocker locker(lock_);
- if (!lockOwner_)
- return;
+ ASSERT(useCount_);
+
+ unlockMediaDevices();
+
+ releaseDevice(camera);
+
+ --useCount_;
+}
+
+/**
+ * \brief Release resources associated with this camera
+ * \param[in] camera The camera for which to release resources
+ *
+ * Pipeline handlers may override this in order to perform cleanup operations
+ * when a camera is released, such as freeing memory.
+ */
+void PipelineHandler::releaseDevice([[maybe_unused]] Camera *camera)
+{
+}
+void PipelineHandler::unlockMediaDevices()
+{
for (std::shared_ptr<MediaDevice> &media : mediaDevices_)
media->unlock();
-
- lockOwner_ = false;
}
/**
@@ -217,8 +249,7 @@ void PipelineHandler::unlock()
* handler.
*
* \return A valid CameraConfiguration if the requested roles can be satisfied,
- * or a null pointer otherwise. The ownership of the returned configuration is
- * passed to the caller.
+ * or a null pointer otherwise.
*/
/**
@@ -312,6 +343,8 @@ void PipelineHandler::stop(Camera *camera)
/* Make sure no requests are pending. */
Camera::Private *data = camera->_d();
ASSERT(data->queuedRequests_.empty());
+
+ data->requestSequence_ = 0;
}
/**
@@ -504,6 +537,62 @@ void PipelineHandler::completeRequest(Request *request)
}
/**
+ * \brief Retrieve the absolute path to a platform configuration file
+ * \param[in] subdir The pipeline handler specific subdirectory name
+ * \param[in] name The configuration file name
+ *
+ * This function locates a named platform configuration file and returns
+ * its absolute path to the pipeline handler. It searches the following
+ * directories, in order:
+ *
+ * - If libcamera is not installed, the src/libcamera/pipeline/\<subdir\>/data/
+ * directory within the source tree ; otherwise
+ * - The system data (share/libcamera/pipeline/\<subdir\>) directory.
+ *
+ * The system directories are not searched if libcamera is not installed.
+ *
+ * \return The full path to the pipeline handler configuration file, or an empty
+ * string if no configuration file can be found
+ */
+std::string PipelineHandler::configurationFile(const std::string &subdir,
+ const std::string &name) const
+{
+ std::string confPath;
+ struct stat statbuf;
+ int ret;
+
+ std::string root = utils::libcameraSourcePath();
+ if (!root.empty()) {
+ /*
+ * When libcamera is used before it is installed, load
+ * configuration files from the source directory. The
+ * configuration files are then located in the 'data'
+ * subdirectory of the corresponding pipeline handler.
+ */
+ std::string confDir = root + "src/libcamera/pipeline/";
+ confPath = confDir + subdir + "/data/" + name;
+
+ LOG(Pipeline, Info)
+ << "libcamera is not installed. Loading platform configuration file from '"
+ << confPath << "'";
+ } else {
+ /* Else look in the system locations. */
+ confPath = std::string(LIBCAMERA_DATA_DIR)
+ + "/pipeline/" + subdir + '/' + name;
+ }
+
+ ret = stat(confPath.c_str(), &statbuf);
+ if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
+ return confPath;
+
+ LOG(Pipeline, Error)
+ << "Configuration file '" << confPath
+ << "' not found for pipeline handler '" << PipelineHandler::name() << "'";
+
+ return std::string();
+}
+
+/**
* \brief Register a camera to the camera manager and pipeline handler
* \param[in] camera The camera to be added
*
@@ -524,7 +613,7 @@ void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera)
* Walk the entity list and map the devnums of all capture video nodes
* to the camera.
*/
- std::vector<dev_t> devnums;
+ std::vector<int64_t> devnums;
for (const std::shared_ptr<MediaDevice> &media : mediaDevices_) {
for (const MediaEntity *entity : media->entities()) {
if (entity->pads().size() == 1 &&
@@ -536,7 +625,14 @@ void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera)
}
}
- manager_->addCamera(std::move(camera), devnums);
+ /*
+ * Store the associated devices as a property of the camera to allow
+ * systems to identify which devices are managed by libcamera.
+ */
+ Camera::Private *data = camera->_d();
+ data->properties_.set(properties::SystemDevices, devnums);
+
+ manager_->_d()->addCamera(std::move(camera));
}
/**
@@ -553,7 +649,7 @@ void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera)
*/
void PipelineHandler::hotplugMediaDevice(MediaDevice *media)
{
- media->disconnected.connect(this, [=]() { mediaDeviceDisconnected(media); });
+ media->disconnected.connect(this, [this, media] { mediaDeviceDisconnected(media); });
}
/**
@@ -597,13 +693,13 @@ void PipelineHandler::disconnect()
*/
std::vector<std::weak_ptr<Camera>> cameras{ std::move(cameras_) };
- for (std::weak_ptr<Camera> ptr : cameras) {
+ for (const std::weak_ptr<Camera> &ptr : cameras) {
std::shared_ptr<Camera> camera = ptr.lock();
if (!camera)
continue;
camera->disconnect();
- manager_->removeCamera(camera);
+ manager_->_d()->removeCamera(camera);
}
}
@@ -624,27 +720,25 @@ void PipelineHandler::disconnect()
*/
/**
- * \class PipelineHandlerFactory
- * \brief Registration of PipelineHandler classes and creation of instances
+ * \class PipelineHandlerFactoryBase
+ * \brief Base class for pipeline handler factories
*
- * To facilitate discovery and instantiation of PipelineHandler classes, the
- * PipelineHandlerFactory class maintains a registry of pipeline handler
- * classes. Each PipelineHandler subclass shall register itself using the
- * REGISTER_PIPELINE_HANDLER() macro, which will create a corresponding
- * instance of a PipelineHandlerFactory subclass and register it with the
- * static list of factories.
+ * The PipelineHandlerFactoryBase class is the base of all specializations of
+ * the PipelineHandlerFactory class template. It implements the factory
+ * registration, maintains a registry of factories, and provides access to the
+ * registered factories.
*/
/**
- * \brief Construct a pipeline handler factory
+ * \brief Construct a pipeline handler factory base
* \param[in] name Name of the pipeline handler class
*
- * Creating an instance of the factory registers is with the global list of
+ * Creating an instance of the factory base registers it with the global list of
* factories, accessible through the factories() function.
*
* The factory \a name is used for debug purpose and shall be unique.
*/
-PipelineHandlerFactory::PipelineHandlerFactory(const char *name)
+PipelineHandlerFactoryBase::PipelineHandlerFactoryBase(const char *name)
: name_(name)
{
registerType(this);
@@ -657,15 +751,15 @@ PipelineHandlerFactory::PipelineHandlerFactory(const char *name)
* \return A shared pointer to a new instance of the PipelineHandler subclass
* corresponding to the factory
*/
-std::shared_ptr<PipelineHandler> PipelineHandlerFactory::create(CameraManager *manager)
+std::shared_ptr<PipelineHandler> PipelineHandlerFactoryBase::create(CameraManager *manager) const
{
- PipelineHandler *handler = createInstance(manager);
+ std::unique_ptr<PipelineHandler> handler = createInstance(manager);
handler->name_ = name_.c_str();
- return std::shared_ptr<PipelineHandler>(handler);
+ return std::shared_ptr<PipelineHandler>(std::move(handler));
}
/**
- * \fn PipelineHandlerFactory::name()
+ * \fn PipelineHandlerFactoryBase::name()
* \brief Retrieve the factory name
* \return The factory name
*/
@@ -677,9 +771,10 @@ std::shared_ptr<PipelineHandler> PipelineHandlerFactory::create(CameraManager *m
* The caller is responsible to guarantee the uniqueness of the pipeline handler
* name.
*/
-void PipelineHandlerFactory::registerType(PipelineHandlerFactory *factory)
+void PipelineHandlerFactoryBase::registerType(PipelineHandlerFactoryBase *factory)
{
- std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
+ std::vector<PipelineHandlerFactoryBase *> &factories =
+ PipelineHandlerFactoryBase::factories();
factories.push_back(factory);
}
@@ -688,28 +783,47 @@ void PipelineHandlerFactory::registerType(PipelineHandlerFactory *factory)
* \brief Retrieve the list of all pipeline handler factories
* \return the list of pipeline handler factories
*/
-std::vector<PipelineHandlerFactory *> &PipelineHandlerFactory::factories()
+std::vector<PipelineHandlerFactoryBase *> &PipelineHandlerFactoryBase::factories()
{
/*
* The static factories map is defined inside the function to ensure
* it gets initialized on first use, without any dependency on
* link order.
*/
- static std::vector<PipelineHandlerFactory *> factories;
+ static std::vector<PipelineHandlerFactoryBase *> factories;
return factories;
}
/**
- * \fn PipelineHandlerFactory::createInstance()
- * \brief Create an instance of the PipelineHandler corresponding to the factory
- * \param[in] manager The camera manager
+ * \class PipelineHandlerFactory
+ * \brief Registration of PipelineHandler classes and creation of instances
+ * \tparam _PipelineHandler The pipeline handler class type for this factory
*
- * This virtual function is implemented by the REGISTER_PIPELINE_HANDLER()
- * macro. It creates a pipeline handler instance associated with the camera
- * \a manager.
+ * To facilitate discovery and instantiation of PipelineHandler classes, the
+ * PipelineHandlerFactory class implements auto-registration of pipeline
+ * handlers. Each PipelineHandler subclass shall register itself using the
+ * REGISTER_PIPELINE_HANDLER() macro, which will create a corresponding
+ * instance of a PipelineHandlerFactory and register it with the static list of
+ * factories.
+ */
+
+/**
+ * \fn PipelineHandlerFactory::PipelineHandlerFactory(const char *name)
+ * \brief Construct a pipeline handler factory
+ * \param[in] name Name of the pipeline handler class
*
- * \return a pointer to a newly constructed instance of the PipelineHandler
- * subclass corresponding to the factory
+ * Creating an instance of the factory registers it with the global list of
+ * factories, accessible through the factories() function.
+ *
+ * The factory \a name is used for debug purpose and shall be unique.
+ */
+
+/**
+ * \fn PipelineHandlerFactory::createInstance() const
+ * \brief Create an instance of the PipelineHandler corresponding to the factory
+ * \param[in] manager The camera manager
+ * \return A unique pointer to a newly constructed instance of the
+ * PipelineHandler subclass corresponding to the factory
*/
/**
diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
index 0e6b4e1d..86a382fb 100644
--- a/src/libcamera/process.cpp
+++ b/src/libcamera/process.cpp
@@ -263,7 +263,9 @@ int Process::start(const std::string &path,
closeAllFdsExcept(fds);
- unsetenv("LIBCAMERA_LOG_FILE");
+ const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE");
+ if (file && strcmp(file, "syslog"))
+ unsetenv("LIBCAMERA_LOG_FILE");
const char **argv = new const char *[args.size() + 2];
unsigned int len = args.size();
diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in
index f917e334..8b274c38 100644
--- a/src/libcamera/property_ids.cpp.in
+++ b/src/libcamera/property_ids.cpp.in
@@ -23,14 +23,7 @@ namespace properties {
${controls_doc}
-/**
- * \brief Namespace for libcamera draft properties
- */
-namespace draft {
-
-${draft_controls_doc}
-
-} /* namespace draft */
+${vendor_controls_doc}
#ifndef __DOXYGEN__
/*
@@ -39,11 +32,8 @@ ${draft_controls_doc}
*/
${controls_def}
-namespace draft {
-
-${draft_controls_def}
+${vendor_controls_def}
-} /* namespace draft */
#endif
/**
diff --git a/src/libcamera/property_ids.yaml b/src/libcamera/property_ids_core.yaml
index 11b7ebdc..834454a4 100644
--- a/src/libcamera/property_ids.yaml
+++ b/src/libcamera/property_ids_core.yaml
@@ -2,8 +2,9 @@
#
# Copyright (C) 2019, Google Inc.
#
-%YAML 1.2
+%YAML 1.1
---
+vendor: libcamera
controls:
- Location:
type: int32_t
@@ -29,10 +30,10 @@ controls:
- Rotation:
type: int32_t
description: |
- The camera rotation is expressed as the angular difference in degrees
- between two reference systems, one relative to the camera module, and
- one defined on the external world scene to be captured when projected
- on the image sensor pixel array.
+ The camera physical mounting rotation. It is expressed as the angular
+ difference in degrees between two reference systems, one relative to the
+ camera module, and one defined on the external world scene to be
+ captured when projected on the image sensor pixel array.
A camera sensor has a 2-dimensional reference system 'Rc' defined by
its pixel array read-out order. The origin is set to the first pixel
@@ -690,37 +691,14 @@ controls:
that is twice that of the full resolution mode. This value will be valid
after the configure method has returned successfully.
- # ----------------------------------------------------------------------------
- # Draft properties section
-
- - ColorFilterArrangement:
- type: int32_t
- draft: true
+ - SystemDevices:
+ type: int64_t
+ size: [n]
description: |
- The arrangement of color filters on sensor; represents the colors in the
- top-left 2x2 section of the sensor, in reading order. Currently
- identical to ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT.
- enum:
- - name: RGGB
- value: 0
- description: RGGB Bayer pattern
- - name: GRBG
- value: 1
- description: GRBG Bayer pattern
- - name: GBRG
- value: 2
- description: GBRG Bayer pattern
- - name: BGGR
- value: 3
- description: BGGR Bayer pattern
- - name: RGB
- value: 4
- description: |
- Sensor is not Bayer; output has 3 16-bit values for each pixel,
- instead of just 1 16-bit value per pixel.
- - name: MONO
- value: 5
- description: |
- Sensor is not Bayer; output consists of a single colour channel.
+ A list of integer values of type dev_t denoting the major and minor
+ device numbers of the underlying devices used in the operation of this
+ camera.
+
+ Different cameras may report identical devices.
...
diff --git a/src/libcamera/property_ids_draft.yaml b/src/libcamera/property_ids_draft.yaml
new file mode 100644
index 00000000..62f0e242
--- /dev/null
+++ b/src/libcamera/property_ids_draft.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Copyright (C) 2019, Google Inc.
+#
+%YAML 1.1
+---
+vendor: draft
+controls:
+ - ColorFilterArrangement:
+ type: int32_t
+ vendor: draft
+ description: |
+ The arrangement of color filters on sensor; represents the colors in the
+ top-left 2x2 section of the sensor, in reading order. Currently
+ identical to ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT.
+ enum:
+ - name: RGGB
+ value: 0
+ description: RGGB Bayer pattern
+ - name: GRBG
+ value: 1
+ description: GRBG Bayer pattern
+ - name: GBRG
+ value: 2
+ description: GBRG Bayer pattern
+ - name: BGGR
+ value: 3
+ description: BGGR Bayer pattern
+ - name: RGB
+ value: 4
+ description: |
+ Sensor is not Bayer; output has 3 16-bit values for each pixel,
+ instead of just 1 16-bit value per pixel.
+ - name: MONO
+ value: 5
+ description: |
+ Sensor is not Bayer; output consists of a single colour channel.
+
+...
diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build
index 70c8760a..aa4d9cd7 100644
--- a/src/libcamera/proxy/worker/meson.build
+++ b/src/libcamera/proxy/worker/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-proxy_install_dir = get_option('libexecdir') / 'libcamera'
+proxy_install_dir = libcamera_libexecdir
# generate {pipeline}_ipa_proxy_worker.cpp
foreach mojom : ipa_mojoms
diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp
index 9bb08fda..64dfa234 100644
--- a/src/libcamera/pub_key.cpp
+++ b/src/libcamera/pub_key.cpp
@@ -7,7 +7,12 @@
#include "libcamera/internal/pub_key.h"
-#if HAVE_GNUTLS
+#if HAVE_CRYPTO
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+#elif HAVE_GNUTLS
#include <gnutls/abstract.h>
#endif
@@ -33,7 +38,14 @@ namespace libcamera {
PubKey::PubKey([[maybe_unused]] Span<const uint8_t> key)
: valid_(false)
{
-#if HAVE_GNUTLS
+#if HAVE_CRYPTO
+ const uint8_t *data = key.data();
+ pubkey_ = d2i_PUBKEY(nullptr, &data, key.size());
+ if (!pubkey_)
+ return;
+
+ valid_ = true;
+#elif HAVE_GNUTLS
int ret = gnutls_pubkey_init(&pubkey_);
if (ret < 0)
return;
@@ -52,7 +64,9 @@ PubKey::PubKey([[maybe_unused]] Span<const uint8_t> key)
PubKey::~PubKey()
{
-#if HAVE_GNUTLS
+#if HAVE_CRYPTO
+ EVP_PKEY_free(pubkey_);
+#elif HAVE_GNUTLS
gnutls_pubkey_deinit(pubkey_);
#endif
}
@@ -76,7 +90,35 @@ PubKey::~PubKey()
bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
[[maybe_unused]] Span<const uint8_t> sig) const
{
-#if HAVE_GNUTLS
+ if (!valid_)
+ return false;
+
+#if HAVE_CRYPTO
+ /*
+ * Create and initialize a public key algorithm context for signature
+ * verification.
+ */
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pubkey_, nullptr);
+ if (!ctx)
+ return false;
+
+ if (EVP_PKEY_verify_init(ctx) <= 0 ||
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0 ||
+ EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) {
+ EVP_PKEY_CTX_free(ctx);
+ return false;
+ }
+
+ /* Calculate the SHA256 digest of the data. */
+ uint8_t digest[SHA256_DIGEST_LENGTH];
+ SHA256(data.data(), data.size(), digest);
+
+ /* Decrypt the signature and verify it matches the digest. */
+ int ret = EVP_PKEY_verify(ctx, sig.data(), sig.size(), digest,
+ SHA256_DIGEST_LENGTH);
+ EVP_PKEY_CTX_free(ctx);
+ return ret == 1;
+#elif HAVE_GNUTLS
const gnutls_datum_t gnuTlsData{
const_cast<unsigned char *>(data.data()),
static_cast<unsigned int>(data.size())
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index 51d74b29..949c556f 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -158,9 +158,12 @@ void Request::Private::cancel()
}
/**
- * \copydoc Request::reuse()
+ * \brief Reset the request internal data to default values
+ *
+ * After calling this function, all request internal data will have default
+ * values as if the Request::Private instance had just been constructed.
*/
-void Request::Private::reuse()
+void Request::Private::reset()
{
sequence_ = 0;
cancelled_ = false;
@@ -349,7 +352,7 @@ Request::Request(Camera *camera, uint64_t cookie)
camera->_d()->validator());
/**
- * \todo: Add a validator for metadata controls.
+ * \todo Add a validator for metadata controls.
*/
metadata_ = new ControlList(controls::controls);
@@ -380,7 +383,7 @@ void Request::reuse(ReuseFlag flags)
{
LIBCAMERA_TRACEPOINT(request_reuse, this);
- _d()->reuse();
+ _d()->reset();
if (flags & ReuseBuffers) {
for (auto pair : bufferMap_) {
@@ -526,8 +529,8 @@ FrameBuffer *Request::findBuffer(const Stream *stream) const
*
* When requests are queued, they are given a sequential number to track the
* order in which requests are queued to a camera. This number counts all
- * requests given to a camera through its lifetime, and is not reset to zero
- * between camera stop/start sequences.
+ * requests given to a camera and is reset to zero between camera stop/start
+ * sequences.
*
* It can be used to support debugging and identifying the flow of requests
* through a pipeline, but does not guarantee to represent the sequence number
diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/sensor/camera_sensor.cpp
index d055c16a..5c4f3532 100644
--- a/src/libcamera/camera_sensor.cpp
+++ b/src/libcamera/sensor/camera_sensor.cpp
@@ -15,6 +15,8 @@
#include <math.h>
#include <string.h>
+#include <libcamera/camera.h>
+#include <libcamera/orientation.h>
#include <libcamera/property_ids.h>
#include <libcamera/base/utils.h>
@@ -55,7 +57,8 @@ LOG_DEFINE_CATEGORY(CameraSensor)
*/
CameraSensor::CameraSensor(const MediaEntity *entity)
: entity_(entity), pad_(UINT_MAX), staticProps_(nullptr),
- bayerFormat_(nullptr), properties_(properties::properties)
+ bayerFormat_(nullptr), supportFlips_(false),
+ flipsAlterBayerOrder_(false), properties_(properties::properties)
{
}
@@ -152,7 +155,12 @@ int CameraSensor::init()
*/
if (entity_->device()->driver() == "vimc") {
initVimcDefaultProperties();
- return initProperties();
+
+ ret = initProperties();
+ if (ret)
+ return ret;
+
+ return discoverAncillaryDevices();
}
/* Get the color filter array pattern (only for RAW sensors). */
@@ -176,9 +184,48 @@ int CameraSensor::init()
if (ret)
return ret;
+ /*
+ * Set HBLANK to the minimum to start with a well-defined line length,
+ * allowing IPA modules that do not modify HBLANK to use the sensor
+ * minimum line length in their calculations.
+ */
+ const struct v4l2_query_ext_ctrl *hblankInfo = subdev_->controlInfo(V4L2_CID_HBLANK);
+ if (hblankInfo && !(hblankInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) {
+ ControlList ctrl(subdev_->controls());
+
+ ctrl.set(V4L2_CID_HBLANK, static_cast<int32_t>(hblankInfo->minimum));
+ ret = subdev_->setControls(&ctrl);
+ if (ret)
+ return ret;
+ }
+
return applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);
}
+int CameraSensor::generateId()
+{
+ const std::string devPath = subdev_->devicePath();
+
+ /* Try to get ID from firmware description. */
+ id_ = sysfs::firmwareNodePath(devPath);
+ if (!id_.empty())
+ return 0;
+
+ /*
+ * Virtual sensors not described in firmware
+ *
+ * Verify it's a platform device and construct ID from the device path
+ * and model of sensor.
+ */
+ if (devPath.find("/sys/devices/platform/", 0) == 0) {
+ id_ = devPath.substr(strlen("/sys/devices/")) + " " + model();
+ return 0;
+ }
+
+ LOG(CameraSensor, Error) << "Can't generate sensor ID";
+ return -EINVAL;
+}
+
int CameraSensor::validateSensorDriver()
{
int err = 0;
@@ -217,6 +264,26 @@ int CameraSensor::validateSensorDriver()
}
/*
+ * Verify if sensor supports horizontal/vertical flips
+ *
+ * \todo Handle horizontal and vertical flips independently.
+ */
+ const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);
+ const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);
+ if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&
+ vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) {
+ supportFlips_ = true;
+
+ if (hflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT ||
+ vflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT)
+ flipsAlterBayerOrder_ = true;
+ }
+
+ if (!supportFlips_)
+ LOG(CameraSensor, Debug)
+ << "Camera sensor does not support horizontal/vertical flip";
+
+ /*
* Make sure the required selection targets are supported.
*
* Failures in reading any of the targets are not deemed to be fatal,
@@ -275,6 +342,7 @@ int CameraSensor::validateSensorDriver()
* required by the CameraSensor class.
*/
static constexpr uint32_t mandatoryControls[] = {
+ V4L2_CID_ANALOGUE_GAIN,
V4L2_CID_EXPOSURE,
V4L2_CID_HBLANK,
V4L2_CID_PIXEL_RATE,
@@ -384,18 +452,18 @@ int CameraSensor::initProperties()
/* Retrieve and register properties from the kernel interface. */
const ControlInfoMap &controls = subdev_->controls();
- int32_t propertyValue;
const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);
if (orientation != controls.end()) {
int32_t v4l2Orientation = orientation->second.def().get<int32_t>();
+ int32_t propertyValue;
switch (v4l2Orientation) {
default:
LOG(CameraSensor, Warning)
<< "Unsupported camera location "
<< v4l2Orientation << ", setting to External";
- /* Fall-through */
+ [[fallthrough]];
case V4L2_CAMERA_ORIENTATION_EXTERNAL:
propertyValue = properties::CameraLocationExternal;
break;
@@ -413,8 +481,27 @@ int CameraSensor::initProperties()
const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);
if (rotationControl != controls.end()) {
- propertyValue = rotationControl->second.def().get<int32_t>();
+ int32_t propertyValue = rotationControl->second.def().get<int32_t>();
+
+ /*
+ * Cache the Transform associated with the camera mounting
+ * rotation for later use in computeTransform().
+ */
+ bool success;
+ mountingOrientation_ = orientationFromRotation(propertyValue, &success);
+ if (!success) {
+ LOG(CameraSensor, Warning)
+ << "Invalid rotation of " << propertyValue
+ << " degrees - ignoring";
+ mountingOrientation_ = Orientation::Rotate0;
+ }
+
properties_.set(properties::Rotation, propertyValue);
+ } else {
+ LOG(CameraSensor, Warning)
+ << "Rotation control not available, default to 0 degrees";
+ properties_.set(properties::Rotation, 0);
+ mountingOrientation_ = Orientation::Rotate0;
}
properties_.set(properties::PixelArraySize, pixelArraySize_);
@@ -467,8 +554,8 @@ int CameraSensor::discoverAncillaryDevices()
ret = focusLens_->init();
if (ret) {
LOG(CameraSensor, Error)
- << "CameraLens initialisation failed";
- return ret;
+ << "Lens initialisation failed, lens disabled";
+ focusLens_.reset();
}
break;
@@ -510,6 +597,21 @@ int CameraSensor::discoverAncillaryDevices()
*/
/**
+ * \fn CameraSensor::device()
+ * \brief Retrieve the camera sensor device
+ * \todo Remove this function by integrating DelayedControl with CameraSensor
+ * \return The camera sensor device
+ */
+
+/**
+ * \fn CameraSensor::focusLens()
+ * \brief Retrieve the focus lens controller
+ *
+ * \return The focus lens controller. nullptr if no focus lens controller is
+ * connected to the sensor
+ */
+
+/**
* \fn CameraSensor::mbusCodes()
* \brief Retrieve the media bus codes supported by the camera sensor
*
@@ -562,64 +664,6 @@ Size CameraSensor::resolution() const
}
/**
- * \fn CameraSensor::testPatternModes()
- * \brief Retrieve all the supported test pattern modes of the camera sensor
- * The test pattern mode values correspond to the controls::TestPattern control.
- *
- * \return The list of test pattern modes
- */
-
-/**
- * \brief Set the test pattern mode for the camera sensor
- * \param[in] mode The test pattern mode
- *
- * The new \a mode is applied to the sensor if it differs from the active test
- * pattern mode. Otherwise, this function is a no-op. Setting the same test
- * pattern mode for every frame thus incurs no performance penalty.
- */
-int CameraSensor::setTestPatternMode(controls::draft::TestPatternModeEnum mode)
-{
- if (testPatternMode_ == mode)
- return 0;
-
- if (testPatternModes_.empty()) {
- LOG(CameraSensor, Error)
- << "Camera sensor does not support test pattern modes.";
- return -EINVAL;
- }
-
- return applyTestPatternMode(mode);
-}
-
-int CameraSensor::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)
-{
- if (testPatternModes_.empty())
- return 0;
-
- auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),
- mode);
- if (it == testPatternModes_.end()) {
- LOG(CameraSensor, Error) << "Unsupported test pattern mode "
- << mode;
- return -EINVAL;
- }
-
- LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode;
-
- int32_t index = staticProps_->testPatternModes.at(mode);
- ControlList ctrls{ controls() };
- ctrls.set(V4L2_CID_TEST_PATTERN, index);
-
- int ret = setControls(&ctrls);
- if (ret)
- return ret;
-
- testPatternMode_ = mode;
-
- return 0;
-}
-
-/**
* \brief Retrieve the best sensor format for a desired output
* \param[in] mbusCodes The list of acceptable media bus codes
* \param[in] size The desired size
@@ -699,7 +743,7 @@ V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbu
}
V4L2SubdeviceFormat format{
- .mbus_code = bestCode,
+ .code = bestCode,
.size = *bestSize,
.colorSpace = ColorSpace::Raw,
};
@@ -710,96 +754,143 @@ V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbu
/**
* \brief Set the sensor output format
* \param[in] format The desired sensor output format
+ * \param[in] transform The transform to be applied on the sensor.
+ * Defaults to Identity.
+ *
+ * If flips are writable they are configured according to the desired Transform.
+ * Transform::Identity always corresponds to H/V flip being disabled if the
+ * controls are writable. Flips are set before the new format is applied as
+ * they can effectively change the Bayer pattern ordering.
*
* The ranges of any controls associated with the sensor are also updated.
*
* \return 0 on success or a negative error code otherwise
*/
-int CameraSensor::setFormat(V4L2SubdeviceFormat *format)
+int CameraSensor::setFormat(V4L2SubdeviceFormat *format, Transform transform)
{
+ /* Configure flips if the sensor supports that. */
+ if (supportFlips_) {
+ ControlList flipCtrls(subdev_->controls());
+
+ flipCtrls.set(V4L2_CID_HFLIP,
+ static_cast<int32_t>(!!(transform & Transform::HFlip)));
+ flipCtrls.set(V4L2_CID_VFLIP,
+ static_cast<int32_t>(!!(transform & Transform::VFlip)));
+
+ int ret = subdev_->setControls(&flipCtrls);
+ if (ret)
+ return ret;
+ }
+
+ /* Apply format on the subdev. */
int ret = subdev_->setFormat(pad_, format);
if (ret)
return ret;
- updateControlInfo();
+ subdev_->updateControlInfo();
return 0;
}
/**
- * \brief Retrieve the supported V4L2 controls and their information
- *
- * Control information is updated automatically to reflect the current sensor
- * configuration when the setFormat() function is called, without invalidating
- * any iterator on the ControlInfoMap. A manual update can also be forced by
- * calling the updateControlInfo() function for pipeline handlers that change
- * the sensor configuration wihtout using setFormat().
- *
- * \return A map of the V4L2 controls supported by the sensor
- */
-const ControlInfoMap &CameraSensor::controls() const
-{
- return subdev_->controls();
-}
-
-/**
- * \brief Read V4L2 controls from the sensor
- * \param[in] ids The list of controls to read, specified by their ID
- *
- * This function reads the value of all controls contained in \a ids, and
- * returns their values as a ControlList. The control identifiers are defined by
- * the V4L2 specification (V4L2_CID_*).
+ * \brief Try the sensor output format
+ * \param[in] format The desired sensor output format
*
- * If any control in \a ids is not supported by the device, is disabled (i.e.
- * has the V4L2_CTRL_FLAG_DISABLED flag set), or if any other error occurs
- * during validation of the requested controls, no control is read and this
- * function returns an empty control list.
+ * The ranges of any controls associated with the sensor are not updated.
*
- * \sa V4L2Device::getControls()
+ * \todo Add support for Transform by changing the format's Bayer ordering
+ * before calling subdev_->setFormat().
*
- * \return The control values in a ControlList on success, or an empty list on
- * error
+ * \return 0 on success or a negative error code otherwise
*/
-ControlList CameraSensor::getControls(const std::vector<uint32_t> &ids)
+int CameraSensor::tryFormat(V4L2SubdeviceFormat *format) const
{
- return subdev_->getControls(ids);
+ return subdev_->setFormat(pad_, format,
+ V4L2Subdevice::Whence::TryFormat);
}
/**
- * \brief Write V4L2 controls to the sensor
- * \param[in] ctrls The list of controls to write
+ * \brief Apply a sensor configuration to the camera sensor
+ * \param[in] config The sensor configuration
+ * \param[in] transform The transform to be applied on the sensor.
+ * Defaults to Identity
+ * \param[out] sensorFormat Format applied to the sensor (optional)
*
- * This function writes the value of all controls contained in \a ctrls, and
- * stores the values actually applied to the device in the corresponding \a
- * ctrls entry. The control identifiers are defined by the V4L2 specification
- * (V4L2_CID_*).
+ * Apply to the camera sensor the configuration \a config.
*
- * If any control in \a ctrls is not supported by the device, is disabled (i.e.
- * has the V4L2_CTRL_FLAG_DISABLED flag set), is read-only, or if any other
- * error occurs during validation of the requested controls, no control is
- * written and this function returns -EINVAL.
+ * \todo The configuration shall be fully populated and if any of the fields
+ * specified cannot be applied exactly, an error code is returned.
*
- * If an error occurs while writing the controls, the index of the first
- * control that couldn't be written is returned. All controls below that index
- * are written and their values are updated in \a ctrls, while all other
- * controls are not written and their values are not changed.
- *
- * \sa V4L2Device::setControls()
- *
- * \return 0 on success or an error code otherwise
- * \retval -EINVAL One of the control is not supported or not accessible
- * \retval i The index of the control that failed
+ * \return 0 if \a config is applied correctly to the camera sensor, a negative
+ * error code otherwise
*/
-int CameraSensor::setControls(ControlList *ctrls)
+int CameraSensor::applyConfiguration(const SensorConfiguration &config,
+ Transform transform,
+ V4L2SubdeviceFormat *sensorFormat)
{
- return subdev_->setControls(ctrls);
-}
+ if (!config.isValid()) {
+ LOG(CameraSensor, Error) << "Invalid sensor configuration";
+ return -EINVAL;
+ }
-/**
- * \fn CameraSensor::device()
- * \brief Retrieve the camera sensor device
- * \todo Remove this function by integrating DelayedControl with CameraSensor
- * \return The camera sensor device
- */
+ std::vector<unsigned int> filteredCodes;
+ std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),
+ std::back_inserter(filteredCodes),
+ [&config](unsigned int mbusCode) {
+ BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);
+ if (bayer.bitDepth == config.bitDepth)
+ return true;
+ return false;
+ });
+ if (filteredCodes.empty()) {
+ LOG(CameraSensor, Error)
+ << "Cannot find any format with bit depth "
+ << config.bitDepth;
+ return -EINVAL;
+ }
+
+ /*
+ * Compute the sensor's data frame size by applying the cropping
+ * rectangle, subsampling and output crop to the sensor's pixel array
+ * size.
+ *
+ * \todo The actual size computation is for now ignored and only the
+ * output size is considered. This implies that resolutions obtained
+ * with two different cropping/subsampling will look identical and
+ * only the first found one will be considered.
+ */
+ V4L2SubdeviceFormat subdevFormat = {};
+ for (unsigned int code : filteredCodes) {
+ for (const Size &size : sizes(code)) {
+ if (size.width != config.outputSize.width ||
+ size.height != config.outputSize.height)
+ continue;
+
+ subdevFormat.code = code;
+ subdevFormat.size = size;
+ break;
+ }
+ }
+ if (!subdevFormat.code) {
+ LOG(CameraSensor, Error) << "Invalid output size in sensor configuration";
+ return -EINVAL;
+ }
+
+ int ret = setFormat(&subdevFormat, transform);
+ if (ret)
+ return ret;
+
+ /*
+ * Return to the caller the format actually applied to the sensor.
+ * This is relevant if transform has changed the bayer pattern order.
+ */
+ if (sensorFormat)
+ *sensorFormat = subdevFormat;
+
+ /* \todo Handle AnalogCrop. Most sensors do not support set_selection */
+ /* \todo Handle scaling in the digital domain. */
+
+ return 0;
+}
/**
* \fn CameraSensor::properties()
@@ -819,10 +910,6 @@ int CameraSensor::setControls(ControlList *ctrls)
* Sensor information is only available for raw sensors. When called for a YUV
* sensor, this function returns -EINVAL.
*
- * Pipeline handlers that do not change the sensor format using the setFormat()
- * function may need to call updateControlInfo() beforehand, to ensure all the
- * control ranges are up to date.
- *
* \return 0 on success, a negative error code otherwise
*/
int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const
@@ -866,9 +953,13 @@ int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const
ret = subdev_->getFormat(pad_, &format);
if (ret)
return ret;
- info->bitsPerPixel = format.bitsPerPixel();
+
+ info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;
info->outputSize = format.size;
+ std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);
+ info->cfaPattern = cfa ? *cfa : properties::draft::RGB;
+
/*
* Retrieve the pixel rate, line length and minimum/maximum frame
* duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,
@@ -883,10 +974,12 @@ int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const
return -EINVAL;
}
- int32_t hblank = ctrls.get(V4L2_CID_HBLANK).get<int32_t>();
- info->lineLength = info->outputSize.width + hblank;
info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();
+ const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);
+ info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();
+ info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();
+
const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);
info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();
info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();
@@ -895,50 +988,220 @@ int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const
}
/**
- * \fn void CameraSensor::updateControlInfo()
- * \brief Update the sensor's ControlInfoMap in case they have changed
- * \sa V4L2Device::updateControlInfo()
+ * \brief Compute the Transform that gives the requested \a orientation
+ * \param[inout] orientation The desired image orientation
+ *
+ * This function computes the Transform that the pipeline handler should apply
+ * to the CameraSensor to obtain the requested \a orientation.
+ *
+ * The intended caller of this function is the validate() implementation of
+ * pipeline handlers, that pass in the application requested
+ * CameraConfiguration::orientation and obtain a Transform to apply to the
+ * camera sensor, likely at configure() time.
+ *
+ * If the requested \a orientation cannot be obtained, the \a orientation
+ * parameter is adjusted to report the current image orientation and
+ * Transform::Identity is returned.
+ *
+ * If the requested \a orientation can be obtained, the function computes a
+ * Transform and does not adjust \a orientation.
+ *
+ * Pipeline handlers are expected to verify if \a orientation has been
+ * adjusted by this function and set the CameraConfiguration::status to
+ * Adjusted accordingly.
+ *
+ * \return A Transform instance that applied to the CameraSensor produces images
+ * with \a orientation
*/
-void CameraSensor::updateControlInfo()
+Transform CameraSensor::computeTransform(Orientation *orientation) const
{
- subdev_->updateControlInfo();
+ /*
+ * If we cannot do any flips we cannot change the native camera mounting
+ * orientation.
+ */
+ if (!supportFlips_) {
+ *orientation = mountingOrientation_;
+ return Transform::Identity;
+ }
+
+ /*
+ * Now compute the required transform to obtain 'orientation' starting
+ * from the mounting rotation.
+ *
+ * As a note:
+ * orientation / mountingOrientation_ = transform
+ * mountingOrientation_ * transform = orientation
+ */
+ Transform transform = *orientation / mountingOrientation_;
+
+ /*
+ * If transform contains any Transpose we cannot do it, so adjust
+ * 'orientation' to report the image native orientation and return Identity.
+ */
+ if (!!(transform & Transform::Transpose)) {
+ *orientation = mountingOrientation_;
+ return Transform::Identity;
+ }
+
+ return transform;
}
/**
- * \fn CameraSensor::focusLens()
- * \brief Retrieve the focus lens controller
+ * \brief Compute the Bayer order that results from the given Transform
+ * \param[in] t The Transform to apply to the sensor
*
- * \return The focus lens controller. nullptr if no focus lens controller is
- * connected to the sensor
+ * Some sensors change their Bayer order when they are h-flipped or v-flipped.
+ * This function computes and returns the Bayer order that would result from the
+ * given transform applied to the sensor.
+ *
+ * This function is valid only when the sensor produces raw Bayer formats.
+ *
+ * \return The Bayer order produced by the sensor when the Transform is applied
*/
+BayerFormat::Order CameraSensor::bayerOrder(Transform t) const
+{
+ /* Return a defined by meaningless value for non-Bayer sensors. */
+ if (!bayerFormat_)
+ return BayerFormat::Order::BGGR;
-std::string CameraSensor::logPrefix() const
+ if (!flipsAlterBayerOrder_)
+ return bayerFormat_->order;
+
+ /*
+ * Apply the transform to the native (i.e. untransformed) Bayer order,
+ * using the rest of the Bayer format supplied by the caller.
+ */
+ return bayerFormat_->transform(t).order;
+}
+
+/**
+ * \brief Retrieve the supported V4L2 controls and their information
+ *
+ * Control information is updated automatically to reflect the current sensor
+ * configuration when the setFormat() function is called, without invalidating
+ * any iterator on the ControlInfoMap.
+ *
+ * \return A map of the V4L2 controls supported by the sensor
+ */
+const ControlInfoMap &CameraSensor::controls() const
{
- return "'" + entity_->name() + "'";
+ return subdev_->controls();
}
-int CameraSensor::generateId()
+/**
+ * \brief Read V4L2 controls from the sensor
+ * \param[in] ids The list of controls to read, specified by their ID
+ *
+ * This function reads the value of all controls contained in \a ids, and
+ * returns their values as a ControlList. The control identifiers are defined by
+ * the V4L2 specification (V4L2_CID_*).
+ *
+ * If any control in \a ids is not supported by the device, is disabled (i.e.
+ * has the V4L2_CTRL_FLAG_DISABLED flag set), or if any other error occurs
+ * during validation of the requested controls, no control is read and this
+ * function returns an empty control list.
+ *
+ * \sa V4L2Device::getControls()
+ *
+ * \return The control values in a ControlList on success, or an empty list on
+ * error
+ */
+ControlList CameraSensor::getControls(const std::vector<uint32_t> &ids)
{
- const std::string devPath = subdev_->devicePath();
+ return subdev_->getControls(ids);
+}
- /* Try to get ID from firmware description. */
- id_ = sysfs::firmwareNodePath(devPath);
- if (!id_.empty())
+/**
+ * \brief Write V4L2 controls to the sensor
+ * \param[in] ctrls The list of controls to write
+ *
+ * This function writes the value of all controls contained in \a ctrls, and
+ * stores the values actually applied to the device in the corresponding \a
+ * ctrls entry. The control identifiers are defined by the V4L2 specification
+ * (V4L2_CID_*).
+ *
+ * If any control in \a ctrls is not supported by the device, is disabled (i.e.
+ * has the V4L2_CTRL_FLAG_DISABLED flag set), is read-only, or if any other
+ * error occurs during validation of the requested controls, no control is
+ * written and this function returns -EINVAL.
+ *
+ * If an error occurs while writing the controls, the index of the first
+ * control that couldn't be written is returned. All controls below that index
+ * are written and their values are updated in \a ctrls, while all other
+ * controls are not written and their values are not changed.
+ *
+ * \sa V4L2Device::setControls()
+ *
+ * \return 0 on success or an error code otherwise
+ * \retval -EINVAL One of the control is not supported or not accessible
+ * \retval i The index of the control that failed
+ */
+int CameraSensor::setControls(ControlList *ctrls)
+{
+ return subdev_->setControls(ctrls);
+}
+
+/**
+ * \fn CameraSensor::testPatternModes()
+ * \brief Retrieve all the supported test pattern modes of the camera sensor
+ * The test pattern mode values correspond to the controls::TestPattern control.
+ *
+ * \return The list of test pattern modes
+ */
+
+/**
+ * \brief Set the test pattern mode for the camera sensor
+ * \param[in] mode The test pattern mode
+ *
+ * The new \a mode is applied to the sensor if it differs from the active test
+ * pattern mode. Otherwise, this function is a no-op. Setting the same test
+ * pattern mode for every frame thus incurs no performance penalty.
+ */
+int CameraSensor::setTestPatternMode(controls::draft::TestPatternModeEnum mode)
+{
+ if (testPatternMode_ == mode)
return 0;
- /*
- * Virtual sensors not described in firmware
- *
- * Verify it's a platform device and construct ID from the device path
- * and model of sensor.
- */
- if (devPath.find("/sys/devices/platform/", 0) == 0) {
- id_ = devPath.substr(strlen("/sys/devices/")) + " " + model();
+ if (testPatternModes_.empty()) {
+ LOG(CameraSensor, Error)
+ << "Camera sensor does not support test pattern modes.";
+ return -EINVAL;
+ }
+
+ return applyTestPatternMode(mode);
+}
+
+int CameraSensor::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)
+{
+ if (testPatternModes_.empty())
return 0;
+
+ auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),
+ mode);
+ if (it == testPatternModes_.end()) {
+ LOG(CameraSensor, Error) << "Unsupported test pattern mode "
+ << mode;
+ return -EINVAL;
}
- LOG(CameraSensor, Error) << "Can't generate sensor ID";
- return -EINVAL;
+ LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode;
+
+ int32_t index = staticProps_->testPatternModes.at(mode);
+ ControlList ctrls{ controls() };
+ ctrls.set(V4L2_CID_TEST_PATTERN, index);
+
+ int ret = setControls(&ctrls);
+ if (ret)
+ return ret;
+
+ testPatternMode_ = mode;
+
+ return 0;
+}
+
+std::string CameraSensor::logPrefix() const
+{
+ return "'" + entity_->name() + "'";
}
} /* namespace libcamera */
diff --git a/src/libcamera/camera_sensor_properties.cpp b/src/libcamera/sensor/camera_sensor_properties.cpp
index e5f27f06..6e28b09e 100644
--- a/src/libcamera/camera_sensor_properties.cpp
+++ b/src/libcamera/sensor/camera_sensor_properties.cpp
@@ -52,6 +52,15 @@ LOG_DEFINE_CATEGORY(CameraSensorProperties)
const CameraSensorProperties *CameraSensorProperties::get(const std::string &sensor)
{
static const std::map<std::string, const CameraSensorProperties> sensorProps = {
+ { "ar0521", {
+ .unitCellSize = { 2200, 2200 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeSolidColor, 1 },
+ { controls::draft::TestPatternModeColorBars, 2 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 3 },
+ },
+ } },
{ "hi846", {
.unitCellSize = { 1120, 1120 },
.testPatternModes = {
@@ -98,10 +107,50 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen
.unitCellSize = { 3450, 3450 },
.testPatternModes = {},
} },
+ { "imx327", {
+ .unitCellSize = { 2900, 2900 },
+ .testPatternModes = {},
+ } },
{ "imx477", {
.unitCellSize = { 1550, 1550 },
.testPatternModes = {},
} },
+ { "imx519", {
+ .unitCellSize = { 1220, 1220 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeSolidColor, 2 },
+ { controls::draft::TestPatternModePn9, 4 },
+ /*
+ * The driver reports ColorBars and ColorBarsFadeToGray as well but
+ * these two patterns do not comply with MIPI CCS v1.1 (Section 10.1).
+ */
+ },
+ } },
+ { "imx708", {
+ .unitCellSize = { 1400, 1400 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1 },
+ { controls::draft::TestPatternModeSolidColor, 2 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 3 },
+ { controls::draft::TestPatternModePn9, 4 },
+ },
+ } },
+ { "ov2685", {
+ .unitCellSize = { 1750, 1750 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1},
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 2 },
+ /*
+ * No corresponding test pattern mode for:
+ * 3: "Random Data"
+ * 4: "Black White Square"
+ * 5: "Color Square"
+ */
+ },
+ } },
{ "ov2740", {
.unitCellSize = { 1400, 1400 },
.testPatternModes = {
@@ -109,6 +158,19 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen
{ controls::draft::TestPatternModeColorBars, 1},
},
} },
+ { "ov4689", {
+ .unitCellSize = { 2000, 2000 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1},
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 2},
+ /*
+ * No corresponding test patterns in
+ * MIPI CCS specification for sensor's
+ * colorBarType2 and colorBarType3.
+ */
+ },
+ } },
{ "ov5640", {
.unitCellSize = { 1400, 1400 },
.testPatternModes = {
@@ -146,6 +208,32 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen
*/
},
} },
+ { "ov64a40", {
+ .unitCellSize = { 1008, 1008 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 2 },
+ /*
+ * No corresponding test patter mode
+ * 3: "Vertical Color Bar Type 3",
+ * 4: "Vertical Color Bar Type 4"
+ */
+ },
+ } },
+ { "ov8858", {
+ .unitCellSize = { 1120, 1120 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 2 },
+ /*
+ * No corresponding test patter mode
+ * 3: "Vertical Color Bar Type 3",
+ * 4: "Vertical Color Bar Type 4"
+ */
+ },
+ } },
{ "ov8865", {
.unitCellSize = { 1400, 1400 },
.testPatternModes = {
diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build
new file mode 100644
index 00000000..bf4b131a
--- /dev/null
+++ b/src/libcamera/sensor/meson.build
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+ 'camera_sensor.cpp',
+ 'camera_sensor_properties.cpp',
+])
diff --git a/src/libcamera/shared_mem_object.cpp b/src/libcamera/shared_mem_object.cpp
new file mode 100644
index 00000000..b018fb3b
--- /dev/null
+++ b/src/libcamera/shared_mem_object.cpp
@@ -0,0 +1,236 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd
+ * Copyright (C) 2024 Andrei Konovalov
+ * Copyright (C) 2024 Dennis Bonke
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * shared_mem_object.cpp - Helpers for shared memory allocations
+ */
+
+#include "libcamera/internal/shared_mem_object.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/**
+ * \file shared_mem_object.cpp
+ * \brief Helpers for shared memory allocations
+ */
+
+namespace libcamera {
+
+/**
+ * \class SharedMem
+ * \brief Helper class to allocate and manage memory shareable between processes
+ *
+ * SharedMem manages memory suitable for sharing between processes. When an
+ * instance is constructed, it allocates a memory buffer of the requested size
+ * backed by an anonymous file, using the memfd API.
+ *
+ * The allocated memory is exposed by the mem() function. If memory allocation
+ * fails, the function returns an empty Span. This can be also checked using the
+ * bool() operator.
+ *
+ * The file descriptor for the backing file is exposed as a SharedFD by the fd()
+ * function. It can be shared with other processes across IPC boundaries, which
+ * can then map the memory with mmap().
+ *
+ * A single memfd is created for every SharedMem. If there is a need to allocate
+ * a large number of objects in shared memory, these objects should be grouped
+ * together and use the shared memory allocated by a single SharedMem object if
+ * possible. This will help to minimize the number of created memfd's.
+ */
+
+SharedMem::SharedMem() = default;
+
+/**
+ * \brief Construct a SharedMem with memory of the given \a size
+ * \param[in] name Name of the SharedMem
+ * \param[in] size Size of the shared memory to allocate and map
+ *
+ * The \a name is used for debugging purpose only. Multiple SharedMem instances
+ * can have the same name.
+ */
+SharedMem::SharedMem(const std::string &name, std::size_t size)
+{
+ int fd = memfd_create(name.c_str(), MFD_CLOEXEC);
+ if (fd < 0)
+ return;
+
+ fd_ = SharedFD(std::move(fd));
+ if (!fd_.isValid())
+ return;
+
+ if (ftruncate(fd_.get(), size) < 0) {
+ fd_ = SharedFD();
+ return;
+ }
+
+ void *mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd_.get(), 0);
+ if (mem == MAP_FAILED) {
+ fd_ = SharedFD();
+ return;
+ }
+
+ mem_ = { static_cast<uint8_t *>(mem), size };
+}
+
+/**
+ * \brief Move constructor for SharedMem
+ * \param[in] rhs The object to move
+ */
+SharedMem::SharedMem(SharedMem &&rhs)
+{
+ this->fd_ = std::move(rhs.fd_);
+ this->mem_ = rhs.mem_;
+ rhs.mem_ = {};
+}
+
+/**
+ * \brief Destroy the SharedMem instance
+ *
+ * Destroying an instance invalidates the memory mapping exposed with mem().
+ * Other mappings of the backing file, created in this or other processes with
+ * mmap(), remain valid.
+ *
+ * Similarly, other references to the backing file descriptor created by copying
+ * the SharedFD returned by fd() remain valid. The underlying memory will be
+ * freed only when all file descriptors that reference the anonymous file get
+ * closed.
+ */
+SharedMem::~SharedMem()
+{
+ if (!mem_.empty())
+ munmap(mem_.data(), mem_.size_bytes());
+}
+
+/**
+ * \brief Move assignment operator for SharedMem
+ * \param[in] rhs The object to move
+ */
+SharedMem &SharedMem::operator=(SharedMem &&rhs)
+{
+ this->fd_ = std::move(rhs.fd_);
+ this->mem_ = rhs.mem_;
+ rhs.mem_ = {};
+ return *this;
+}
+
+/**
+ * \fn const SharedFD &SharedMem::fd() const
+ * \brief Retrieve the file descriptor for the underlying shared memory
+ * \return The file descriptor, or an invalid SharedFD if allocation failed
+ */
+
+/**
+ * \fn Span<uint8_t> SharedMem::mem() const
+ * \brief Retrieve the underlying shared memory
+ * \return The memory buffer, or an empty Span if allocation failed
+ */
+
+/**
+ * \fn SharedMem::operator bool()
+ * \brief Check if the shared memory allocation succeeded
+ * \return True if allocation of the shared memory succeeded, false otherwise
+ */
+
+/**
+ * \class SharedMemObject
+ * \brief Helper class to allocate an object in shareable memory
+ * \tparam The object type
+ *
+ * The SharedMemObject class is a specialization of the SharedMem class that
+ * wraps an object of type \a T and constructs it in shareable memory. It uses
+ * the same underlying memory allocation and sharing mechanism as the SharedMem
+ * class.
+ *
+ * The wrapped object is constructed at the same time as the SharedMemObject
+ * instance, by forwarding the arguments passed to the SharedMemObject
+ * constructor. The underlying memory allocation is sized to the object \a T
+ * size. The bool() operator should be used to check the allocation was
+ * successful. The object can be accessed using the dereference operators
+ * operator*() and operator->().
+ *
+ * While no restriction on the type \a T is enforced, not all types are suitable
+ * for sharing between multiple processes. Most notably, any object type that
+ * contains pointer or reference members will likely cause issues. Even if those
+ * members refer to other members of the same object, the shared memory will be
+ * mapped at different addresses in different processes, and the pointers will
+ * not be valid.
+ *
+ * A new anonymous file is created for every SharedMemObject instance. If there
+ * is a need to share a large number of small objects, these objects should be
+ * grouped into a single larger object to limit the number of file descriptors.
+ *
+ * To share the object with other processes, see the SharedMem documentation.
+ */
+
+/**
+ * \var SharedMemObject::kSize
+ * \brief The size of the object stored in shared memory
+ */
+
+/**
+ * \fn SharedMemObject::SharedMemObject(const std::string &name, Args &&...args)
+ * \brief Construct a SharedMemObject
+ * \param[in] name Name of the SharedMemObject
+ * \param[in] args Arguments to pass to the constructor of the object T
+ *
+ * The \a name is used for debugging purpose only. Multiple SharedMem instances
+ * can have the same name.
+ */
+
+/**
+ * \fn SharedMemObject::SharedMemObject(SharedMemObject<T> &&rhs)
+ * \brief Move constructor for SharedMemObject
+ * \param[in] rhs The object to move
+ */
+
+/**
+ * \fn SharedMemObject::~SharedMemObject()
+ * \brief Destroy the SharedMemObject instance
+ *
+ * Destroying a SharedMemObject calls the wrapped T object's destructor. While
+ * the underlying memory may not be freed immediately if other mappings have
+ * been created manually (see SharedMem::~SharedMem() for more information), the
+ * stored object may be modified. Depending on the ~T() destructor, accessing
+ * the object after destruction of the SharedMemObject causes undefined
+ * behaviour. It is the responsibility of the user of this class to synchronize
+ * with other users who have access to the shared object.
+ */
+
+/**
+ * \fn SharedMemObject::operator=(SharedMemObject<T> &&rhs)
+ * \brief Move assignment operator for SharedMemObject
+ * \param[in] rhs The SharedMemObject object to take the data from
+ *
+ * Moving a SharedMemObject does not affect the stored object.
+ */
+
+/**
+ * \fn SharedMemObject::operator->()
+ * \brief Dereference the stored object
+ * \return Pointer to the stored object
+ */
+
+/**
+ * \fn const T *SharedMemObject::operator->() const
+ * \copydoc SharedMemObject::operator->
+ */
+
+/**
+ * \fn SharedMemObject::operator*()
+ * \brief Dereference the stored object
+ * \return Reference to the stored object
+ */
+
+/**
+ * \fn const T &SharedMemObject::operator*() const
+ * \copydoc SharedMemObject::operator*
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO
new file mode 100644
index 00000000..4fcee39b
--- /dev/null
+++ b/src/libcamera/software_isp/TODO
@@ -0,0 +1,279 @@
+1. Setting F_SEAL_SHRINK and F_SEAL_GROW after ftruncate()
+
+>> SharedMem::SharedMem(const std::string &name, std::size_t size)
+>> : name_(name), size_(size), mem_(nullptr)
+>>
+>> ...
+>>
+>> if (ftruncate(fd_.get(), size_) < 0)
+>> return;
+>
+> Should we set the GROW and SHRINK seals (in a separate patch) ?
+
+Yes, this can be done.
+Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch
+some potential errors related to improper access to the shared memory allocated by
+the SharedMemObject.
+
+---
+
+2. Reconsider stats sharing
+
+>>> +void SwStatsCpu::finishFrame(void)
+>>> +{
+>>> + *sharedStats_ = stats_;
+>>
+>> Is it more efficient to copy the stats instead of operating directly on
+>> the shared memory ?
+>
+> I inherited doing things this way from Andrey. I kept this because
+> we don't really have any synchronization with the IPA reading this.
+>
+> So the idea is to only touch this when the next set of statistics
+> is ready since we don't know when the IPA is done with accessing
+> the previous set of statistics ...
+>
+> This is both something which seems mostly a theoretic problem,
+> yet also definitely something which I think we need to fix.
+>
+> Maybe use a ringbuffer of stats buffers and pass the index into
+> the ringbuffer to the emit signal ?
+
+That would match how we deal with hardware ISPs, and I think that's a
+good idea. It will help decoupling the processing side from the IPA.
+
+---
+
+3. Remove statsReady signal
+
+> class SwStatsCpu
+> {
+> /**
+> * \brief Signals that the statistics are ready
+> */
+> Signal<> statsReady;
+
+But better, I wonder if the signal could be dropped completely. The
+SwStatsCpu class does not operate asynchronously. Shouldn't whoever
+calls the finishFrame() function then handle emitting the signal ?
+
+Now, the trouble is that this would be the DebayerCpu class, whose name
+doesn't indicate as a prime candidate to handle stats. However, it
+already exposes a getStatsFD() function, so we're already calling for
+trouble :-) Either that should be moved to somewhere else, or the class
+should be renamed. Considering that the class applies colour gains in
+addition to performing the interpolation, it may be more of a naming
+issue.
+
+Removing the signal and refactoring those classes doesn't have to be
+addressed now, I think it would be part of a larger refactoring
+(possibly also considering platforms that have no ISP but can produce
+stats in hardware, such as the i.MX7), but please keep it on your radar.
+
+---
+
+4. Hide internal representation of gains from callers
+
+> struct DebayerParams {
+> static constexpr unsigned int kGain10 = 256;
+
+Forcing the caller to deal with the internal representation of gains
+isn't nice, especially given that it precludes implementing gains of
+different precisions in different backend. Wouldn't it be better to pass
+the values as floating point numbers, and convert them to the internal
+representation in the implementation of process() before using them ?
+
+---
+
+5. Store ISP parameters in per-frame buffers
+
+> /**
+> * \fn void Debayer::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+> * \brief Process the bayer data into the requested format.
+> * \param[in] input The input buffer.
+> * \param[in] output The output buffer.
+> * \param[in] params The parameters to be used in debayering.
+> *
+> * \note DebayerParams is passed by value deliberately so that a copy is passed
+> * when this is run in another thread by invokeMethod().
+> */
+
+Possibly something to address later, by storing ISP parameters in
+per-frame buffers like we do for hardware ISPs.
+
+---
+
+6. Input buffer copying configuration
+
+> DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
+> : stats_(std::move(stats)), gammaCorrection_(1.0)
+> {
+> enableInputMemcpy_ = true;
+
+Set this appropriately and/or make it configurable.
+
+---
+
+7. Performance measurement configuration
+
+> void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+> /* Measure before emitting signals */
+> if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure &&
+> ++measuredFrames_ > DebayerCpu::kFramesToSkip) {
+> timespec frameEndTime = {};
+> clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime);
+> frameProcessTime_ += timeDiff(frameEndTime, frameStartTime);
+> if (measuredFrames_ == DebayerCpu::kLastFrameToMeasure) {
+> const unsigned int measuredFrames = DebayerCpu::kLastFrameToMeasure -
+> DebayerCpu::kFramesToSkip;
+> LOG(Debayer, Info)
+> << "Processed " << measuredFrames
+> << " frames in " << frameProcessTime_ / 1000 << "us, "
+> << frameProcessTime_ / (1000 * measuredFrames)
+> << " us/frame";
+> }
+> }
+
+I wonder if there would be a way to control at runtime when/how to
+perform those measurements. Maybe that's a bit overkill.
+
+---
+
+8. DebayerCpu cleanups
+
+> >> class DebayerCpu : public Debayer, public Object
+> >> const SharedFD &getStatsFD() { return stats_->getStatsFD(); }
+> >
+> > This,
+>
+> Note the statistics pass-through stuff is sort of a necessary evil
+> since we want one main loop going over the data line by line and
+> doing both debayering as well as stats while the line is still
+> hot in the l2 cache. And things like the process2() and process4()
+> loops are highly CPU debayering specific so I don't think we should
+> move those out of the CpuDebayer code.
+
+Yes, that I understood from the review. "necessary evil" is indeed the
+right term :-) I expect it will take quite some design skills to balance
+the need for performances and the need for a maintainable architecture.
+
+> > plus the fact that this class handles colour gains and gamma,
+> > makes me thing we have either a naming issue, or an architecture issue.
+>
+> I agree that this does a bit more then debayering, although
+> the debayering really is the main thing it does.
+>
+> I guess the calculation of the rgb lookup tables which do the
+> color gains and gamma could be moved outside of this class,
+> that might even be beneficial for GPU based debayering assuming
+> that that is going to use rgb lookup tables too (it could
+> implement actual color gains + gamma correction in some different
+> way).
+>
+> I think this falls under the lets wait until we have a GPU
+> based SoftISP MVP/POC and then do some refactoring to see which
+> bits should go where.
+
+---
+
+8. Decouple pipeline and IPA naming
+
+> The current src/ipa/meson.build assumes the IPA name to match the
+> pipeline name. For this reason "-Dipas=simple" is used for the
+> Soft IPA module.
+
+This should be addressed.
+
+---
+
+9. Doxyfile cleanup
+
+>> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
+>> index a86ea6c1..2be8d47b 100644
+>> --- a/Documentation/Doxyfile.in
+>> +++ b/Documentation/Doxyfile.in
+>> @@ -44,6 +44,7 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \
+>> @TOP_SRCDIR@/src/libcamera/pipeline/ \
+>> @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
+>> @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
+>> + @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \
+> Why is this needed ?
+>
+>> @TOP_BUILDDIR@/src/libcamera/proxy/
+>> EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
+>> diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
+>> index f3b4881c..3352d08f 100644
+>> --- a/include/libcamera/ipa/meson.build
+>> +++ b/include/libcamera/ipa/meson.build
+>> @@ -65,6 +65,7 @@ pipeline_ipa_mojom_mapping = {
+>> 'ipu3': 'ipu3.mojom',
+>> 'rkisp1': 'rkisp1.mojom',
+>> 'rpi/vc4': 'raspberrypi.mojom',
+>> + 'simple': 'soft.mojom',
+>> 'vimc': 'vimc.mojom',
+>> }
+>> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
+>> new file mode 100644
+>> index 00000000..c249bd75
+>> --- /dev/null
+>> +++ b/include/libcamera/ipa/soft.mojom
+>> @@ -0,0 +1,28 @@
+>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
+>> +
+>> +/*
+>> + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
+> Ah that's why.
+
+Yes, because, well... all the other IPAs were doing that...
+
+> It doesn't have to be done before merging, but could you
+> address this sooner than later ?
+
+---
+
+10. Switch to libipa/algorithm.h API in processStats
+
+>> void IPASoftSimple::processStats(const ControlList &sensorControls)
+>>
+> Do you envision switching to the libipa/algorithm.h API at some point ?
+
+At some point, yes.
+
+---
+
+11. Improve handling the sensor controls which take effect with a delay
+
+> void IPASoftSimple::processStats(const ControlList &sensorControls)
+> {
+> ...
+> /*
+> * AE / AGC, use 2 frames delay to make sure that the exposure and
+> * the gain set have applied to the camera sensor.
+> */
+> if (ignore_updates_ > 0) {
+> --ignore_updates_;
+> return;
+> }
+
+This could be handled better with DelayedControls.
+
+---
+
+12. Use DelayedControls class in ispStatsReady()
+
+> void SimpleCameraData::ispStatsReady()
+> {
+> swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN,
+> V4L2_CID_EXPOSURE }));
+
+You should use the DelayedControls class.
+
+---
+
+13. Improve black level and colour gains application
+
+I think the black level should eventually be moved before debayering, and
+ideally the colour gains as well. I understand the need for optimizations to
+lower the CPU consumption, but at the same time I don't feel comfortable
+building up on top of an implementation that may work a bit more by chance than
+by correctness, as that's not very maintainable.
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
new file mode 100644
index 00000000..1c035e9b
--- /dev/null
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * debayer.cpp - debayer base class
+ */
+
+#include "debayer.h"
+
+namespace libcamera {
+
+/**
+ * \struct DebayerParams
+ * \brief Struct to hold the debayer parameters.
+ */
+
+/**
+ * \var DebayerParams::kGain10
+ * \brief const value for 1.0 gain
+ */
+
+/**
+ * \var DebayerParams::gainR
+ * \brief Red gain
+ *
+ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
+ */
+
+/**
+ * \var DebayerParams::gainG
+ * \brief Green gain
+ *
+ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
+ */
+
+/**
+ * \var DebayerParams::gainB
+ * \brief Blue gain
+ *
+ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
+ */
+
+/**
+ * \var DebayerParams::gamma
+ * \brief Gamma correction, 1.0 is no correction
+ */
+
+/**
+ * \class Debayer
+ * \brief Base debayering class
+ *
+ * Base class that provides functions for setting up the debayering process.
+ */
+
+LOG_DEFINE_CATEGORY(Debayer)
+
+Debayer::~Debayer()
+{
+}
+
+/**
+ * \fn int Debayer::configure(const StreamConfiguration &inputCfg, const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+ * \brief Configure the debayer object according to the passed in parameters.
+ * \param[in] inputCfg The input configuration.
+ * \param[in] outputCfgs The output configurations.
+ *
+ * \return 0 on success, a negative errno on failure.
+ */
+
+/**
+ * \fn Size Debayer::patternSize(PixelFormat inputFormat)
+ * \brief Get the width and height at which the bayer pattern repeats.
+ * \param[in] inputFormat The input format.
+ *
+ * Valid sizes are: 2x2, 4x2 or 4x4.
+ *
+ * \return Pattern size or an empty size for unsupported inputFormats.
+ */
+
+/**
+ * \fn std::vector<PixelFormat> Debayer::formats(PixelFormat inputFormat)
+ * \brief Get the supported output formats.
+ * \param[in] inputFormat The input format.
+ *
+ * \return All supported output formats or an empty vector if there are none.
+ */
+
+/**
+ * \fn std::tuple<unsigned int, unsigned int> Debayer::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
+ * \brief Get the stride and the frame size.
+ * \param[in] outputFormat The output format.
+ * \param[in] size The output size.
+ *
+ * \return A tuple of the stride and the frame size, or a tuple with 0,0 if
+ * there is no valid output config.
+ */
+
+/**
+ * \fn void Debayer::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+ * \brief Process the bayer data into the requested format.
+ * \param[in] input The input buffer.
+ * \param[in] output The output buffer.
+ * \param[in] params The parameters to be used in debayering.
+ *
+ * \note DebayerParams is passed by value deliberately so that a copy is passed
+ * when this is run in another thread by invokeMethod().
+ */
+
+/**
+ * \fn virtual SizeRange Debayer::sizes(PixelFormat inputFormat, const Size &inputSize)
+ * \brief Get the supported output sizes for the given input format and size.
+ * \param[in] inputFormat The input format.
+ * \param[in] inputSize The input size.
+ *
+ * \return The valid size ranges or an empty range if there are none.
+ */
+
+/**
+ * \var Signal<FrameBuffer *> Debayer::inputBufferReady
+ * \brief Signals when the input buffer is ready.
+ */
+
+/**
+ * \var Signal<FrameBuffer *> Debayer::outputBufferReady
+ * \brief Signals when the output buffer is ready.
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h
new file mode 100644
index 00000000..42ae58ab
--- /dev/null
+++ b/src/libcamera/software_isp/debayer.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * debayer.h - debayering base class
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/software_isp/debayer_params.h"
+
+namespace libcamera {
+
+class FrameBuffer;
+
+LOG_DECLARE_CATEGORY(Debayer)
+
+class Debayer
+{
+public:
+ virtual ~Debayer() = 0;
+
+ virtual int configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0;
+
+ virtual std::vector<PixelFormat> formats(PixelFormat inputFormat) = 0;
+
+ virtual std::tuple<unsigned int, unsigned int>
+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0;
+
+ virtual void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0;
+
+ virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0;
+
+ Signal<FrameBuffer *> inputBufferReady;
+ Signal<FrameBuffer *> outputBufferReady;
+
+private:
+ virtual Size patternSize(PixelFormat inputFormat) = 0;
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
new file mode 100644
index 00000000..88d6578b
--- /dev/null
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -0,0 +1,807 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * debayer_cpu.cpp - CPU based debayering class
+ */
+
+#include "debayer_cpu.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <libcamera/formats.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/mapped_framebuffer.h"
+
+namespace libcamera {
+
+/**
+ * \class DebayerCpu
+ * \brief Class for debayering on the CPU
+ *
+ * Implementation for CPU based debayering
+ */
+
+/**
+ * \brief Constructs a DebayerCpu object
+ * \param[in] stats Pointer to the stats object to use
+ */
+DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
+ : stats_(std::move(stats)), gammaCorrection_(1.0), blackLevel_(0)
+{
+ /*
+ * Reading from uncached buffers may be very slow.
+ * In such a case, it's better to copy input buffer data to normal memory.
+ * But in case of cached buffers, copying the data is unnecessary overhead.
+ * enable_input_memcpy_ makes this behavior configurable. At the moment, we
+ * always set it to true as the safer choice but this should be changed in
+ * future.
+ */
+ enableInputMemcpy_ = true;
+
+ /* Initialize gamma to 1.0 curve */
+ for (unsigned int i = 0; i < kGammaLookupSize; i++)
+ gamma_[i] = i / (kGammaLookupSize / kRGBLookupSize);
+
+ for (unsigned int i = 0; i < kMaxLineBuffers; i++)
+ lineBuffers_[i] = nullptr;
+}
+
+DebayerCpu::~DebayerCpu()
+{
+ for (unsigned int i = 0; i < kMaxLineBuffers; i++)
+ free(lineBuffers_[i]);
+}
+
+#define DECLARE_SRC_POINTERS(pixel_t) \
+ const pixel_t *prev = (const pixel_t *)src[0] + xShift_; \
+ const pixel_t *curr = (const pixel_t *)src[1] + xShift_; \
+ const pixel_t *next = (const pixel_t *)src[2] + xShift_;
+
+/*
+ * RGR
+ * GBG
+ * RGR
+ */
+#define BGGR_BGR888(p, n, div) \
+ *dst++ = blue_[curr[x] / (div)]; \
+ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
+ *dst++ = red_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
+ x++;
+
+/*
+ * GBG
+ * RGR
+ * GBG
+ */
+#define GRBG_BGR888(p, n, div) \
+ *dst++ = blue_[(prev[x] + next[x]) / (2 * (div))]; \
+ *dst++ = green_[curr[x] / (div)]; \
+ *dst++ = red_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
+ x++;
+
+/*
+ * GRG
+ * BGB
+ * GRG
+ */
+#define GBRG_BGR888(p, n, div) \
+ *dst++ = blue_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
+ *dst++ = green_[curr[x] / (div)]; \
+ *dst++ = red_[(prev[x] + next[x]) / (2 * (div))]; \
+ x++;
+
+/*
+ * BGB
+ * GRG
+ * BGB
+ */
+#define RGGB_BGR888(p, n, div) \
+ *dst++ = blue_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
+ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
+ *dst++ = red_[curr[x] / (div)]; \
+ x++;
+
+void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ DECLARE_SRC_POINTERS(uint8_t)
+
+ for (int x = 0; x < (int)window_.width;) {
+ BGGR_BGR888(1, 1, 1)
+ GBRG_BGR888(1, 1, 1)
+ }
+}
+
+void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ DECLARE_SRC_POINTERS(uint8_t)
+
+ for (int x = 0; x < (int)window_.width;) {
+ GRBG_BGR888(1, 1, 1)
+ RGGB_BGR888(1, 1, 1)
+ }
+}
+
+void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ DECLARE_SRC_POINTERS(uint16_t)
+
+ for (int x = 0; x < (int)window_.width;) {
+ /* divide values by 4 for 10 -> 8 bpp value */
+ BGGR_BGR888(1, 1, 4)
+ GBRG_BGR888(1, 1, 4)
+ }
+}
+
+void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ DECLARE_SRC_POINTERS(uint16_t)
+
+ for (int x = 0; x < (int)window_.width;) {
+ /* divide values by 4 for 10 -> 8 bpp value */
+ GRBG_BGR888(1, 1, 4)
+ RGGB_BGR888(1, 1, 4)
+ }
+}
+
+void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ DECLARE_SRC_POINTERS(uint16_t)
+
+ for (int x = 0; x < (int)window_.width;) {
+ /* divide values by 16 for 12 -> 8 bpp value */
+ BGGR_BGR888(1, 1, 16)
+ GBRG_BGR888(1, 1, 16)
+ }
+}
+
+void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ DECLARE_SRC_POINTERS(uint16_t)
+
+ for (int x = 0; x < (int)window_.width;) {
+ /* divide values by 16 for 12 -> 8 bpp value */
+ GRBG_BGR888(1, 1, 16)
+ RGGB_BGR888(1, 1, 16)
+ }
+}
+
+void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ const int widthInBytes = window_.width * 5 / 4;
+ const uint8_t *prev = src[0];
+ const uint8_t *curr = src[1];
+ const uint8_t *next = src[2];
+
+ /*
+ * For the first pixel getting a pixel from the previous column uses
+ * x - 2 to skip the 5th byte with least-significant bits for 4 pixels.
+ * Same for last pixel (uses x + 2) and looking at the next column.
+ */
+ for (int x = 0; x < widthInBytes;) {
+ /* First pixel */
+ BGGR_BGR888(2, 1, 1)
+ /* Second pixel BGGR -> GBRG */
+ GBRG_BGR888(1, 1, 1)
+ /* Same thing for third and fourth pixels */
+ BGGR_BGR888(1, 1, 1)
+ GBRG_BGR888(1, 2, 1)
+ /* Skip 5th src byte with 4 x 2 least-significant-bits */
+ x++;
+ }
+}
+
+void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ const int widthInBytes = window_.width * 5 / 4;
+ const uint8_t *prev = src[0];
+ const uint8_t *curr = src[1];
+ const uint8_t *next = src[2];
+
+ for (int x = 0; x < widthInBytes;) {
+ /* First pixel */
+ GRBG_BGR888(2, 1, 1)
+ /* Second pixel GRBG -> RGGB */
+ RGGB_BGR888(1, 1, 1)
+ /* Same thing for third and fourth pixels */
+ GRBG_BGR888(1, 1, 1)
+ RGGB_BGR888(1, 2, 1)
+ /* Skip 5th src byte with 4 x 2 least-significant-bits */
+ x++;
+ }
+}
+
+void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ const int widthInBytes = window_.width * 5 / 4;
+ const uint8_t *prev = src[0];
+ const uint8_t *curr = src[1];
+ const uint8_t *next = src[2];
+
+ for (int x = 0; x < widthInBytes;) {
+ /* Even pixel */
+ GBRG_BGR888(2, 1, 1)
+ /* Odd pixel GBGR -> BGGR */
+ BGGR_BGR888(1, 1, 1)
+ /* Same thing for next 2 pixels */
+ GBRG_BGR888(1, 1, 1)
+ BGGR_BGR888(1, 2, 1)
+ /* Skip 5th src byte with 4 x 2 least-significant-bits */
+ x++;
+ }
+}
+
+void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+ const int widthInBytes = window_.width * 5 / 4;
+ const uint8_t *prev = src[0];
+ const uint8_t *curr = src[1];
+ const uint8_t *next = src[2];
+
+ for (int x = 0; x < widthInBytes;) {
+ /* Even pixel */
+ RGGB_BGR888(2, 1, 1)
+ /* Odd pixel RGGB -> GRBG */
+ GRBG_BGR888(1, 1, 1)
+ /* Same thing for next 2 pixels */
+ RGGB_BGR888(1, 1, 1)
+ GRBG_BGR888(1, 2, 1)
+ /* Skip 5th src byte with 4 x 2 least-significant-bits */
+ x++;
+ }
+}
+
+static bool isStandardBayerOrder(BayerFormat::Order order)
+{
+ return order == BayerFormat::BGGR || order == BayerFormat::GBRG ||
+ order == BayerFormat::GRBG || order == BayerFormat::RGGB;
+}
+
+/*
+ * Setup the Debayer object according to the passed in parameters.
+ * Return 0 on success, a negative errno value on failure
+ * (unsupported parameters).
+ */
+int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config)
+{
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
+ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
+ bayerFormat.packing == BayerFormat::Packing::None &&
+ isStandardBayerOrder(bayerFormat.order)) {
+ config.bpp = (bayerFormat.bitDepth + 7) & ~7;
+ config.patternSize.width = 2;
+ config.patternSize.height = 2;
+ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 });
+ return 0;
+ }
+
+ if (bayerFormat.bitDepth == 10 &&
+ bayerFormat.packing == BayerFormat::Packing::CSI2 &&
+ isStandardBayerOrder(bayerFormat.order)) {
+ config.bpp = 10;
+ config.patternSize.width = 4; /* 5 bytes per *4* pixels */
+ config.patternSize.height = 2;
+ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 });
+ return 0;
+ }
+
+ LOG(Debayer, Info)
+ << "Unsupported input format " << inputFormat.toString();
+ return -EINVAL;
+}
+
+int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config)
+{
+ if (outputFormat == formats::RGB888 || outputFormat == formats::BGR888) {
+ config.bpp = 24;
+ return 0;
+ }
+
+ LOG(Debayer, Info)
+ << "Unsupported output format " << outputFormat.toString();
+ return -EINVAL;
+}
+
+/*
+ * Check for standard Bayer orders and set xShift_ and swap debayer0/1, so that
+ * a single pair of BGGR debayer functions can be used for all 4 standard orders.
+ */
+int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order)
+{
+ switch (order) {
+ case BayerFormat::BGGR:
+ break;
+ case BayerFormat::GBRG:
+ xShift_ = 1; /* BGGR -> GBRG */
+ break;
+ case BayerFormat::GRBG:
+ std::swap(debayer0_, debayer1_); /* BGGR -> GRBG */
+ break;
+ case BayerFormat::RGGB:
+ xShift_ = 1; /* BGGR -> GBRG */
+ std::swap(debayer0_, debayer1_); /* GBRG -> RGGB */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat)
+{
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
+ xShift_ = 0;
+ swapRedBlueGains_ = false;
+
+ auto invalidFmt = []() -> int {
+ LOG(Debayer, Error) << "Unsupported input output format combination";
+ return -EINVAL;
+ };
+
+ switch (outputFormat) {
+ case formats::RGB888:
+ break;
+ case formats::BGR888:
+ /* Swap R and B in bayer order to generate BGR888 instead of RGB888 */
+ swapRedBlueGains_ = true;
+
+ switch (bayerFormat.order) {
+ case BayerFormat::BGGR:
+ bayerFormat.order = BayerFormat::RGGB;
+ break;
+ case BayerFormat::GBRG:
+ bayerFormat.order = BayerFormat::GRBG;
+ break;
+ case BayerFormat::GRBG:
+ bayerFormat.order = BayerFormat::GBRG;
+ break;
+ case BayerFormat::RGGB:
+ bayerFormat.order = BayerFormat::BGGR;
+ break;
+ default:
+ return invalidFmt();
+ }
+ break;
+ default:
+ return invalidFmt();
+ }
+
+ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
+ bayerFormat.packing == BayerFormat::Packing::None &&
+ isStandardBayerOrder(bayerFormat.order)) {
+ switch (bayerFormat.bitDepth) {
+ case 8:
+ debayer0_ = &DebayerCpu::debayer8_BGBG_BGR888;
+ debayer1_ = &DebayerCpu::debayer8_GRGR_BGR888;
+ break;
+ case 10:
+ debayer0_ = &DebayerCpu::debayer10_BGBG_BGR888;
+ debayer1_ = &DebayerCpu::debayer10_GRGR_BGR888;
+ break;
+ case 12:
+ debayer0_ = &DebayerCpu::debayer12_BGBG_BGR888;
+ debayer1_ = &DebayerCpu::debayer12_GRGR_BGR888;
+ break;
+ }
+ setupStandardBayerOrder(bayerFormat.order);
+ return 0;
+ }
+
+ if (bayerFormat.bitDepth == 10 &&
+ bayerFormat.packing == BayerFormat::Packing::CSI2) {
+ switch (bayerFormat.order) {
+ case BayerFormat::BGGR:
+ debayer0_ = &DebayerCpu::debayer10P_BGBG_BGR888;
+ debayer1_ = &DebayerCpu::debayer10P_GRGR_BGR888;
+ return 0;
+ case BayerFormat::GBRG:
+ debayer0_ = &DebayerCpu::debayer10P_GBGB_BGR888;
+ debayer1_ = &DebayerCpu::debayer10P_RGRG_BGR888;
+ return 0;
+ case BayerFormat::GRBG:
+ debayer0_ = &DebayerCpu::debayer10P_GRGR_BGR888;
+ debayer1_ = &DebayerCpu::debayer10P_BGBG_BGR888;
+ return 0;
+ case BayerFormat::RGGB:
+ debayer0_ = &DebayerCpu::debayer10P_RGRG_BGR888;
+ debayer1_ = &DebayerCpu::debayer10P_GBGB_BGR888;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return invalidFmt();
+}
+
+int DebayerCpu::configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+{
+ if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)
+ return -EINVAL;
+
+ if (stats_->configure(inputCfg) != 0)
+ return -EINVAL;
+
+ const Size &statsPatternSize = stats_->patternSize();
+ if (inputConfig_.patternSize.width != statsPatternSize.width ||
+ inputConfig_.patternSize.height != statsPatternSize.height) {
+ LOG(Debayer, Error)
+ << "mismatching stats and debayer pattern sizes for "
+ << inputCfg.pixelFormat.toString();
+ return -EINVAL;
+ }
+
+ inputConfig_.stride = inputCfg.stride;
+
+ if (outputCfgs.size() != 1) {
+ LOG(Debayer, Error)
+ << "Unsupported number of output streams: "
+ << outputCfgs.size();
+ return -EINVAL;
+ }
+
+ const StreamConfiguration &outputCfg = outputCfgs[0];
+ SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);
+ std::tie(outputConfig_.stride, outputConfig_.frameSize) =
+ strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size);
+
+ if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {
+ LOG(Debayer, Error)
+ << "Invalid output size/stride: "
+ << "\n " << outputCfg.size << " (" << outSizeRange << ")"
+ << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")";
+ return -EINVAL;
+ }
+
+ if (setDebayerFunctions(inputCfg.pixelFormat, outputCfg.pixelFormat) != 0)
+ return -EINVAL;
+
+ window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
+ ~(inputConfig_.patternSize.width - 1);
+ window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &
+ ~(inputConfig_.patternSize.height - 1);
+ window_.width = outputCfg.size.width;
+ window_.height = outputCfg.size.height;
+
+ /* Don't pass x,y since process() already adjusts src before passing it */
+ stats_->setWindow(Rectangle(window_.size()));
+
+ /* pad with patternSize.Width on both left and right side */
+ lineBufferPadding_ = inputConfig_.patternSize.width * inputConfig_.bpp / 8;
+ lineBufferLength_ = window_.width * inputConfig_.bpp / 8 +
+ 2 * lineBufferPadding_;
+ for (unsigned int i = 0;
+ i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_;
+ i++) {
+ free(lineBuffers_[i]);
+ lineBuffers_[i] = (uint8_t *)malloc(lineBufferLength_);
+ if (!lineBuffers_[i])
+ return -ENOMEM;
+ }
+
+ measuredFrames_ = 0;
+ frameProcessTime_ = 0;
+
+ return 0;
+}
+
+/*
+ * Get width and height at which the bayer-pattern repeats.
+ * Return pattern-size or an empty Size for an unsupported inputFormat.
+ */
+Size DebayerCpu::patternSize(PixelFormat inputFormat)
+{
+ DebayerCpu::DebayerInputConfig config;
+
+ if (getInputConfig(inputFormat, config) != 0)
+ return {};
+
+ return config.patternSize;
+}
+
+std::vector<PixelFormat> DebayerCpu::formats(PixelFormat inputFormat)
+{
+ DebayerCpu::DebayerInputConfig config;
+
+ if (getInputConfig(inputFormat, config) != 0)
+ return std::vector<PixelFormat>();
+
+ return config.outputFormats;
+}
+
+std::tuple<unsigned int, unsigned int>
+DebayerCpu::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
+{
+ DebayerCpu::DebayerOutputConfig config;
+
+ if (getOutputConfig(outputFormat, config) != 0)
+ return std::make_tuple(0, 0);
+
+ /* round up to multiple of 8 for 64 bits alignment */
+ unsigned int stride = (size.width * config.bpp / 8 + 7) & ~7;
+
+ return std::make_tuple(stride, stride * size.height);
+}
+
+void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[])
+{
+ const unsigned int patternHeight = inputConfig_.patternSize.height;
+
+ if (!enableInputMemcpy_)
+ return;
+
+ for (unsigned int i = 0; i < patternHeight; i++) {
+ memcpy(lineBuffers_[i], linePointers[i + 1] - lineBufferPadding_,
+ lineBufferLength_);
+ linePointers[i + 1] = lineBuffers_[i] + lineBufferPadding_;
+ }
+
+ /* Point lineBufferIndex_ to first unused lineBuffer */
+ lineBufferIndex_ = patternHeight;
+}
+
+void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src)
+{
+ const unsigned int patternHeight = inputConfig_.patternSize.height;
+
+ for (unsigned int i = 0; i < patternHeight; i++)
+ linePointers[i] = linePointers[i + 1];
+
+ linePointers[patternHeight] = src +
+ (patternHeight / 2) * (int)inputConfig_.stride;
+}
+
+void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[])
+{
+ const unsigned int patternHeight = inputConfig_.patternSize.height;
+
+ if (!enableInputMemcpy_)
+ return;
+
+ memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - lineBufferPadding_,
+ lineBufferLength_);
+ linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + lineBufferPadding_;
+
+ lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1);
+}
+
+void DebayerCpu::process2(const uint8_t *src, uint8_t *dst)
+{
+ unsigned int yEnd = window_.y + window_.height;
+ /* Holds [0] previous- [1] current- [2] next-line */
+ const uint8_t *linePointers[3];
+
+ /* Adjust src to top left corner of the window */
+ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;
+
+ /* [x] becomes [x - 1] after initial shiftLinePointers() call */
+ if (window_.y) {
+ linePointers[1] = src - inputConfig_.stride; /* previous-line */
+ linePointers[2] = src;
+ } else {
+ /* window_.y == 0, use the next line as prev line */
+ linePointers[1] = src + inputConfig_.stride;
+ linePointers[2] = src;
+ /* Last 2 lines also need special handling */
+ yEnd -= 2;
+ }
+
+ setupInputMemcpy(linePointers);
+
+ for (unsigned int y = window_.y; y < yEnd; y += 2) {
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ stats_->processLine0(y, linePointers);
+ (this->*debayer0_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ (this->*debayer1_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+ }
+
+ if (window_.y == 0) {
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ stats_->processLine0(yEnd, linePointers);
+ (this->*debayer0_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+
+ shiftLinePointers(linePointers, src);
+ /* next line may point outside of src, use prev. */
+ linePointers[2] = linePointers[0];
+ (this->*debayer1_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+ }
+}
+
+void DebayerCpu::process4(const uint8_t *src, uint8_t *dst)
+{
+ const unsigned int yEnd = window_.y + window_.height;
+ /*
+ * This holds pointers to [0] 2-lines-up [1] 1-line-up [2] current-line
+ * [3] 1-line-down [4] 2-lines-down.
+ */
+ const uint8_t *linePointers[5];
+
+ /* Adjust src to top left corner of the window */
+ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;
+
+ /* [x] becomes [x - 1] after initial shiftLinePointers() call */
+ linePointers[1] = src - 2 * inputConfig_.stride;
+ linePointers[2] = src - inputConfig_.stride;
+ linePointers[3] = src;
+ linePointers[4] = src + inputConfig_.stride;
+
+ setupInputMemcpy(linePointers);
+
+ for (unsigned int y = window_.y; y < yEnd; y += 4) {
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ stats_->processLine0(y, linePointers);
+ (this->*debayer0_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ (this->*debayer1_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ stats_->processLine2(y, linePointers);
+ (this->*debayer2_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+
+ shiftLinePointers(linePointers, src);
+ memcpyNextLine(linePointers);
+ (this->*debayer3_)(dst, linePointers);
+ src += inputConfig_.stride;
+ dst += outputConfig_.stride;
+ }
+}
+
+static inline int64_t timeDiff(timespec &after, timespec &before)
+{
+ return (after.tv_sec - before.tv_sec) * 1000000000LL +
+ (int64_t)after.tv_nsec - (int64_t)before.tv_nsec;
+}
+
+void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+{
+ timespec frameStartTime;
+
+ if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure) {
+ frameStartTime = {};
+ clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime);
+ }
+
+ /* Apply DebayerParams */
+ if (params.gamma != gammaCorrection_ || params.blackLevel != blackLevel_) {
+ const unsigned int blackIndex =
+ params.blackLevel * kGammaLookupSize / 256;
+ std::fill(gamma_.begin(), gamma_.begin() + blackIndex, 0);
+ const float divisor = kGammaLookupSize - blackIndex - 1.0;
+ for (unsigned int i = blackIndex; i < kGammaLookupSize; i++)
+ gamma_[i] = UINT8_MAX * powf((i - blackIndex) / divisor, params.gamma);
+
+ gammaCorrection_ = params.gamma;
+ blackLevel_ = params.blackLevel;
+ }
+
+ if (swapRedBlueGains_)
+ std::swap(params.gainR, params.gainB);
+
+ for (unsigned int i = 0; i < kRGBLookupSize; i++) {
+ constexpr unsigned int div =
+ kRGBLookupSize * DebayerParams::kGain10 / kGammaLookupSize;
+ unsigned int idx;
+
+ /* Apply gamma after gain! */
+ idx = std::min({ i * params.gainR / div, (kGammaLookupSize - 1) });
+ red_[i] = gamma_[idx];
+
+ idx = std::min({ i * params.gainG / div, (kGammaLookupSize - 1) });
+ green_[i] = gamma_[idx];
+
+ idx = std::min({ i * params.gainB / div, (kGammaLookupSize - 1) });
+ blue_[i] = gamma_[idx];
+ }
+
+ /* Copy metadata from the input buffer */
+ FrameMetadata &metadata = output->_d()->metadata();
+ metadata.status = input->metadata().status;
+ metadata.sequence = input->metadata().sequence;
+ metadata.timestamp = input->metadata().timestamp;
+
+ MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);
+ MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);
+ if (!in.isValid() || !out.isValid()) {
+ LOG(Debayer, Error) << "mmap-ing buffer(s) failed";
+ metadata.status = FrameMetadata::FrameError;
+ return;
+ }
+
+ stats_->startFrame();
+
+ if (inputConfig_.patternSize.height == 2)
+ process2(in.planes()[0].data(), out.planes()[0].data());
+ else
+ process4(in.planes()[0].data(), out.planes()[0].data());
+
+ metadata.planes()[0].bytesused = out.planes()[0].size();
+
+ /* Measure before emitting signals */
+ if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure &&
+ ++measuredFrames_ > DebayerCpu::kFramesToSkip) {
+ timespec frameEndTime = {};
+ clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime);
+ frameProcessTime_ += timeDiff(frameEndTime, frameStartTime);
+ if (measuredFrames_ == DebayerCpu::kLastFrameToMeasure) {
+ const unsigned int measuredFrames = DebayerCpu::kLastFrameToMeasure -
+ DebayerCpu::kFramesToSkip;
+ LOG(Debayer, Info)
+ << "Processed " << measuredFrames
+ << " frames in " << frameProcessTime_ / 1000 << "us, "
+ << frameProcessTime_ / (1000 * measuredFrames)
+ << " us/frame";
+ }
+ }
+
+ stats_->finishFrame();
+ outputBufferReady.emit(output);
+ inputBufferReady.emit(input);
+}
+
+SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize)
+{
+ Size patternSize = this->patternSize(inputFormat);
+ unsigned int borderHeight = patternSize.height;
+
+ if (patternSize.isNull())
+ return {};
+
+ /* No need for top/bottom border with a pattern height of 2 */
+ if (patternSize.height == 2)
+ borderHeight = 0;
+
+ /*
+ * For debayer interpolation a border is kept around the entire image
+ * and the minimum output size is pattern-height x pattern-width.
+ */
+ if (inputSize.width < (3 * patternSize.width) ||
+ inputSize.height < (2 * borderHeight + patternSize.height)) {
+ LOG(Debayer, Warning)
+ << "Input format size too small: " << inputSize.toString();
+ return {};
+ }
+
+ return SizeRange(Size(patternSize.width, patternSize.height),
+ Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1),
+ (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)),
+ patternSize.width, patternSize.height);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
new file mode 100644
index 00000000..689c1075
--- /dev/null
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * debayer_cpu.h - CPU based debayering header
+ */
+
+#pragma once
+
+#include <memory>
+#include <stdint.h>
+#include <vector>
+
+#include <libcamera/base/object.h>
+
+#include "libcamera/internal/bayer_format.h"
+
+#include "debayer.h"
+#include "swstats_cpu.h"
+
+namespace libcamera {
+
+class DebayerCpu : public Debayer, public Object
+{
+public:
+ DebayerCpu(std::unique_ptr<SwStatsCpu> stats);
+ ~DebayerCpu();
+
+ int configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs);
+ Size patternSize(PixelFormat inputFormat);
+ std::vector<PixelFormat> formats(PixelFormat input);
+ std::tuple<unsigned int, unsigned int>
+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
+ void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params);
+ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
+
+ /**
+ * \brief Get the file descriptor for the statistics
+ *
+ * \return the file descriptor pointing to the statistics
+ */
+ const SharedFD &getStatsFD() { return stats_->getStatsFD(); }
+
+ /**
+ * \brief Get the output frame size
+ *
+ * \return The output frame size
+ */
+ unsigned int frameSize() { return outputConfig_.frameSize; }
+
+private:
+ /**
+ * \brief Called to debayer 1 line of Bayer input data to output format
+ * \param[out] dst Pointer to the start of the output line to write
+ * \param[in] src The input data
+ *
+ * Input data is an array of (patternSize_.height + 1) src
+ * pointers each pointing to a line in the Bayer source. The middle
+ * element of the array will point to the actual line being processed.
+ * Earlier element(s) will point to the previous line(s) and later
+ * element(s) to the next line(s).
+ *
+ * These functions take an array of src pointers, rather than
+ * a single src pointer + a stride for the source, so that when the src
+ * is slow uncached memory it can be copied to faster memory before
+ * debayering. Debayering a standard 2x2 Bayer pattern requires access
+ * to the previous and next src lines for interpolating the missing
+ * colors. To allow copying the src lines only once 3 temporary buffers
+ * each holding a single line are used, re-using the oldest buffer for
+ * the next line and the pointers are swizzled so that:
+ * src[0] = previous-line, src[1] = currrent-line, src[2] = next-line.
+ * This way the 3 pointers passed to the debayer functions form
+ * a sliding window over the src avoiding the need to copy each
+ * line more than once.
+ *
+ * Similarly for bayer patterns which repeat every 4 lines, 5 src
+ * pointers are passed holding: src[0] = 2-lines-up, src[1] = 1-line-up
+ * src[2] = current-line, src[3] = 1-line-down, src[4] = 2-lines-down.
+ */
+ using debayerFn = void (DebayerCpu::*)(uint8_t *dst, const uint8_t *src[]);
+
+ /* 8-bit raw bayer format */
+ void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ /* unpacked 10-bit raw bayer format */
+ void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ /* unpacked 12-bit raw bayer format */
+ void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */
+ void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
+
+ struct DebayerInputConfig {
+ Size patternSize;
+ unsigned int bpp; /* Memory used per pixel, not precision */
+ unsigned int stride;
+ std::vector<PixelFormat> outputFormats;
+ };
+
+ struct DebayerOutputConfig {
+ unsigned int bpp; /* Memory used per pixel, not precision */
+ unsigned int stride;
+ unsigned int frameSize;
+ };
+
+ int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
+ int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
+ int setupStandardBayerOrder(BayerFormat::Order order);
+ int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat);
+ void setupInputMemcpy(const uint8_t *linePointers[]);
+ void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);
+ void memcpyNextLine(const uint8_t *linePointers[]);
+ void process2(const uint8_t *src, uint8_t *dst);
+ void process4(const uint8_t *src, uint8_t *dst);
+
+ static constexpr unsigned int kGammaLookupSize = 1024;
+ static constexpr unsigned int kRGBLookupSize = 256;
+ /* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */
+ static constexpr unsigned int kMaxLineBuffers = 5;
+
+ std::array<uint8_t, kGammaLookupSize> gamma_;
+ std::array<uint8_t, kRGBLookupSize> red_;
+ std::array<uint8_t, kRGBLookupSize> green_;
+ std::array<uint8_t, kRGBLookupSize> blue_;
+ debayerFn debayer0_;
+ debayerFn debayer1_;
+ debayerFn debayer2_;
+ debayerFn debayer3_;
+ Rectangle window_;
+ DebayerInputConfig inputConfig_;
+ DebayerOutputConfig outputConfig_;
+ std::unique_ptr<SwStatsCpu> stats_;
+ uint8_t *lineBuffers_[kMaxLineBuffers];
+ unsigned int lineBufferLength_;
+ unsigned int lineBufferPadding_;
+ unsigned int lineBufferIndex_;
+ unsigned int xShift_; /* Offset of 0/1 applied to window_.x */
+ bool enableInputMemcpy_;
+ bool swapRedBlueGains_;
+ float gammaCorrection_;
+ unsigned int blackLevel_;
+ unsigned int measuredFrames_;
+ int64_t frameProcessTime_;
+ /* Skip 30 frames for things to stabilize then measure 30 frames */
+ static constexpr unsigned int kFramesToSkip = 30;
+ static constexpr unsigned int kLastFrameToMeasure = 60;
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
new file mode 100644
index 00000000..f7c66e28
--- /dev/null
+++ b/src/libcamera/software_isp/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: CC0-1.0
+
+softisp_enabled = pipelines.contains('simple')
+summary({'SoftISP support' : softisp_enabled}, section : 'Configuration')
+
+if not softisp_enabled
+ subdir_done()
+endif
+
+libcamera_sources += files([
+ 'debayer.cpp',
+ 'debayer_cpu.cpp',
+ 'software_isp.cpp',
+ 'swstats_cpu.cpp',
+])
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
new file mode 100644
index 00000000..e4e56086
--- /dev/null
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -0,0 +1,357 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * software_isp.cpp - Simple software ISP implementation
+ */
+
+#include "libcamera/internal/software_isp/software_isp.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/mapped_framebuffer.h"
+
+#include "debayer_cpu.h"
+
+/**
+ * \file software_isp.cpp
+ * \brief Simple software ISP implementation
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(SoftwareIsp)
+
+/**
+ * \class SoftwareIsp
+ * \brief Class for the Software ISP
+ */
+
+/**
+ * \var SoftwareIsp::inputBufferReady
+ * \brief A signal emitted when the input frame buffer completes
+ */
+
+/**
+ * \var SoftwareIsp::outputBufferReady
+ * \brief A signal emitted when the output frame buffer completes
+ */
+
+/**
+ * \var SoftwareIsp::ispStatsReady
+ * \brief A signal emitted when the statistics for IPA are ready
+ */
+
+/**
+ * \var SoftwareIsp::setSensorControls
+ * \brief A signal emitted when the values to write to the sensor controls are
+ * ready
+ */
+
+/**
+ * \brief Constructs SoftwareIsp object
+ * \param[in] pipe The pipeline handler in use
+ * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline
+ * handler
+ */
+SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
+ : debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10,
+ DebayerParams::kGain10, 0.5f, 0 },
+ dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
+{
+ if (!dmaHeap_.isValid()) {
+ LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object";
+ return;
+ }
+
+ sharedParams_ = SharedMemObject<DebayerParams>("softIsp_params");
+ if (!sharedParams_) {
+ LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters";
+ return;
+ }
+
+ auto stats = std::make_unique<SwStatsCpu>();
+ if (!stats->isValid()) {
+ LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object";
+ return;
+ }
+ stats->statsReady.connect(this, &SoftwareIsp::statsReady);
+
+ debayer_ = std::make_unique<DebayerCpu>(std::move(stats));
+ debayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady);
+ debayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady);
+
+ ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0);
+ if (!ipa_) {
+ LOG(SoftwareIsp, Error)
+ << "Creating IPA for software ISP failed";
+ debayer_.reset();
+ return;
+ }
+
+ /*
+ * The API tuning file is made from the sensor name. If the tuning file
+ * isn't found, fall back to the 'uncalibrated' file.
+ */
+ std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml");
+ if (ipaTuningFile.empty())
+ ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+
+ int ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
+ debayer_->getStatsFD(),
+ sharedParams_.fd(),
+ sensor->controls());
+ if (ret) {
+ LOG(SoftwareIsp, Error) << "IPA init failed";
+ debayer_.reset();
+ return;
+ }
+
+ ipa_->setIspParams.connect(this, &SoftwareIsp::saveIspParams);
+ ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls);
+
+ debayer_->moveToThread(&ispWorkerThread_);
+}
+
+SoftwareIsp::~SoftwareIsp()
+{
+ /* make sure to destroy the DebayerCpu before the ispWorkerThread_ is gone */
+ debayer_.reset();
+}
+
+/**
+ * \fn int SoftwareIsp::loadConfiguration([[maybe_unused]] const std::string &filename)
+ * \brief Load a configuration from a file
+ * \param[in] filename The file to load the configuration data from
+ *
+ * Currently is a stub doing nothing and always returning "success".
+ *
+ * \return 0 on success
+ */
+
+/**
+ * \brief Process the statistics gathered
+ * \param[in] sensorControls The sensor controls
+ *
+ * Requests the IPA to calculate new parameters for ISP and new control
+ * values for the sensor.
+ */
+void SoftwareIsp::processStats(const ControlList &sensorControls)
+{
+ ASSERT(ipa_);
+ ipa_->processStats(sensorControls);
+}
+
+/**
+ * \brief Check the validity of Software Isp object
+ * \return True if Software Isp is valid, false otherwise
+ */
+bool SoftwareIsp::isValid() const
+{
+ return !!debayer_;
+}
+
+/**
+ * \brief Get the output formats supported for the given input format
+ * \param[in] inputFormat The input format
+ * \return All the supported output formats or an empty vector if there are none
+ */
+std::vector<PixelFormat> SoftwareIsp::formats(PixelFormat inputFormat)
+{
+ ASSERT(debayer_);
+
+ return debayer_->formats(inputFormat);
+}
+
+/**
+ * \brief Get the supported output sizes for the given input format and size
+ * \param[in] inputFormat The input format
+ * \param[in] inputSize The input frame size
+ * \return The valid size range or an empty range if there are none
+ */
+SizeRange SoftwareIsp::sizes(PixelFormat inputFormat, const Size &inputSize)
+{
+ ASSERT(debayer_);
+
+ return debayer_->sizes(inputFormat, inputSize);
+}
+
+/**
+ * Get the output stride and the frame size in bytes for the given output format and size
+ * \param[in] outputFormat The output format
+ * \param[in] size The output size (width and height in pixels)
+ * \return A tuple of the stride and the frame size in bytes, or a tuple of 0,0
+ * if there is no valid output config
+ */
+std::tuple<unsigned int, unsigned int>
+SoftwareIsp::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
+{
+ ASSERT(debayer_);
+
+ return debayer_->strideAndFrameSize(outputFormat, size);
+}
+
+/**
+ * \brief Configure the SoftwareIsp object according to the passed in parameters
+ * \param[in] inputCfg The input configuration
+ * \param[in] outputCfgs The output configurations
+ * \param[in] sensorControls ControlInfoMap of the controls supported by the sensor
+ * \return 0 on success, a negative errno on failure
+ */
+int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+ const ControlInfoMap &sensorControls)
+{
+ ASSERT(ipa_ && debayer_);
+
+ int ret = ipa_->configure(sensorControls);
+ if (ret < 0)
+ return ret;
+
+ return debayer_->configure(inputCfg, outputCfgs);
+}
+
+/**
+ * \brief Export the buffers from the Software ISP
+ * \param[in] output Output stream index exporting the buffers
+ * \param[in] count Number of buffers to allocate
+ * \param[out] buffers Vector to store the allocated buffers
+ * \return The number of allocated buffers on success or a negative error code
+ * otherwise
+ */
+int SoftwareIsp::exportBuffers(unsigned int output, unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+ ASSERT(debayer_ != nullptr);
+
+ /* single output for now */
+ if (output >= 1)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < count; i++) {
+ const std::string name = "frame-" + std::to_string(i);
+ const size_t frameSize = debayer_->frameSize();
+
+ FrameBuffer::Plane outPlane;
+ outPlane.fd = SharedFD(dmaHeap_.alloc(name.c_str(), frameSize));
+ if (!outPlane.fd.isValid()) {
+ LOG(SoftwareIsp, Error)
+ << "failed to allocate a dma_buf";
+ return -ENOMEM;
+ }
+ outPlane.offset = 0;
+ outPlane.length = frameSize;
+
+ std::vector<FrameBuffer::Plane> planes{ outPlane };
+ buffers->emplace_back(std::make_unique<FrameBuffer>(std::move(planes)));
+ }
+
+ return count;
+}
+
+/**
+ * \brief Queue buffers to Software ISP
+ * \param[in] input The input framebuffer
+ * \param[in] outputs The container holding the output stream indexes and
+ * their respective frame buffer outputs
+ * \return 0 on success, a negative errno on failure
+ */
+int SoftwareIsp::queueBuffers(FrameBuffer *input,
+ const std::map<unsigned int, FrameBuffer *> &outputs)
+{
+ unsigned int mask = 0;
+
+ /*
+ * Validate the outputs as a sanity check: at least one output is
+ * required, all outputs must reference a valid stream and no two
+ * outputs can reference the same stream.
+ */
+ if (outputs.empty())
+ return -EINVAL;
+
+ for (auto [index, buffer] : outputs) {
+ if (!buffer)
+ return -EINVAL;
+ if (index >= 1) /* only single stream atm */
+ return -EINVAL;
+ if (mask & (1 << index))
+ return -EINVAL;
+
+ mask |= 1 << index;
+ }
+
+ process(input, outputs.at(0));
+
+ return 0;
+}
+
+/**
+ * \brief Starts the Software ISP streaming operation
+ * \return 0 on success, any other value indicates an error
+ */
+int SoftwareIsp::start()
+{
+ int ret = ipa_->start();
+ if (ret)
+ return ret;
+
+ ispWorkerThread_.start();
+ return 0;
+}
+
+/**
+ * \brief Stops the Software ISP streaming operation
+ */
+void SoftwareIsp::stop()
+{
+ ispWorkerThread_.exit();
+ ispWorkerThread_.wait();
+
+ ipa_->stop();
+}
+
+/**
+ * \brief Passes the input framebuffer to the ISP worker to process
+ * \param[in] input The input framebuffer
+ * \param[out] output The framebuffer to write the processed frame to
+ */
+void SoftwareIsp::process(FrameBuffer *input, FrameBuffer *output)
+{
+ debayer_->invokeMethod(&DebayerCpu::process,
+ ConnectionTypeQueued, input, output, debayerParams_);
+}
+
+void SoftwareIsp::saveIspParams()
+{
+ debayerParams_ = *sharedParams_;
+}
+
+void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls)
+{
+ setSensorControls.emit(sensorControls);
+}
+
+void SoftwareIsp::statsReady()
+{
+ ispStatsReady.emit();
+}
+
+void SoftwareIsp::inputReady(FrameBuffer *input)
+{
+ inputBufferReady.emit(input);
+}
+
+void SoftwareIsp::outputReady(FrameBuffer *output)
+{
+ outputBufferReady.emit(output);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
new file mode 100644
index 00000000..a0c45b0c
--- /dev/null
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * swstats_cpu.cpp - CPU based software statistics implementation
+ */
+
+#include "swstats_cpu.h"
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+
+namespace libcamera {
+
+/**
+ * \class SwStatsCpu
+ * \brief Class for gathering statistics on the CPU
+ *
+ * CPU based software ISP statistics implementation.
+ *
+ * This class offers a configure function + functions to gather statistics on a
+ * line by line basis. This allows CPU based software debayering to interleave
+ * debayering and statistics gathering on a line by line basis while the input
+ * data is still hot in the cache.
+ *
+ * It is also possible to specify a window over which to gather statistics
+ * instead of processing the whole frame.
+ */
+
+/**
+ * \fn bool SwStatsCpu::isValid() const
+ * \brief Gets whether the statistics object is valid
+ *
+ * \return True if it's valid, false otherwise
+ */
+
+/**
+ * \fn const SharedFD &SwStatsCpu::getStatsFD()
+ * \brief Get the file descriptor for the statistics
+ *
+ * \return The file descriptor
+ */
+
+/**
+ * \fn const Size &SwStatsCpu::patternSize()
+ * \brief Get the pattern size
+ *
+ * For some input-formats, e.g. Bayer data, processing is done multiple lines
+ * and/or columns at a time. Get width and height at which the (bayer) pattern
+ * repeats. Window values are rounded down to a multiple of this and the height
+ * also indicates if processLine2() should be called or not.
+ * This may only be called after a successful configure() call.
+ *
+ * \return The pattern size
+ */
+
+/**
+ * \fn void SwStatsCpu::processLine0(unsigned int y, const uint8_t *src[])
+ * \brief Process line 0
+ * \param[in] y The y coordinate.
+ * \param[in] src The input data.
+ *
+ * This function processes line 0 for input formats with
+ * patternSize height == 1.
+ * It'll process line 0 and 1 for input formats with patternSize height >= 2.
+ * This function may only be called after a successful setWindow() call.
+ */
+
+/**
+ * \fn void SwStatsCpu::processLine2(unsigned int y, const uint8_t *src[])
+ * \brief Process line 2 and 3
+ * \param[in] y The y coordinate.
+ * \param[in] src The input data.
+ *
+ * This function processes line 2 and 3 for input formats with
+ * patternSize height == 4.
+ * This function may only be called after a successful setWindow() call.
+ */
+
+/**
+ * \var Signal<> SwStatsCpu::statsReady
+ * \brief Signals that the statistics are ready
+ */
+
+/**
+ * \typedef SwStatsCpu::statsProcessFn
+ * \brief Called when there is data to get statistics from
+ * \param[in] src The input data
+ *
+ * These functions take an array of (patternSize_.height + 1) src
+ * pointers each pointing to a line in the source image. The middle
+ * element of the array will point to the actual line being processed.
+ * Earlier element(s) will point to the previous line(s) and later
+ * element(s) to the next line(s).
+ *
+ * See the documentation of DebayerCpu::debayerFn for more details.
+ */
+
+/**
+ * \var unsigned int SwStatsCpu::ySkipMask_
+ * \brief Skip lines where this bitmask is set in y
+ */
+
+/**
+ * \var Rectangle SwStatsCpu::window_
+ * \brief Statistics window, set by setWindow(), used every line
+ */
+
+/**
+ * \var Size SwStatsCpu::patternSize_
+ * \brief The size of the bayer pattern
+ *
+ * Valid sizes are: 2x2, 4x2 or 4x4.
+ */
+
+/**
+ * \var unsigned int SwStatsCpu::xShift_
+ * \brief The offset of x, applied to window_.x for bayer variants
+ *
+ * This can either be 0 or 1.
+ */
+
+LOG_DEFINE_CATEGORY(SwStatsCpu)
+
+SwStatsCpu::SwStatsCpu()
+ : sharedStats_("softIsp_stats")
+{
+ if (!sharedStats_)
+ LOG(SwStatsCpu, Error)
+ << "Failed to create shared memory for statistics";
+}
+
+static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */
+static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */
+static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
+
+#define SWSTATS_START_LINE_STATS(pixel_t) \
+ pixel_t r, g, g2, b; \
+ uint64_t yVal; \
+ \
+ uint64_t sumR = 0; \
+ uint64_t sumG = 0; \
+ uint64_t sumB = 0;
+
+#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
+ sumR += r; \
+ sumG += g; \
+ sumB += b; \
+ \
+ yVal = r * kRedYMul; \
+ yVal += g * kGreenYMul; \
+ yVal += b * kBlueYMul; \
+ stats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;
+
+#define SWSTATS_FINISH_LINE_STATS() \
+ stats_.sumR_ += sumR; \
+ stats_.sumG_ += sumG; \
+ stats_.sumB_ += sumB;
+
+void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[])
+{
+ const uint8_t *src0 = src[1] + window_.x;
+ const uint8_t *src1 = src[2] + window_.x;
+
+ SWSTATS_START_LINE_STATS(uint8_t)
+
+ if (swapLines_)
+ std::swap(src0, src1);
+
+ /* x += 4 sample every other 2x2 block */
+ for (int x = 0; x < (int)window_.width; x += 4) {
+ b = src0[x];
+ g = src0[x + 1];
+ g2 = src1[x];
+ r = src1[x + 1];
+
+ g = (g + g2) / 2;
+
+ SWSTATS_ACCUMULATE_LINE_STATS(1)
+ }
+
+ SWSTATS_FINISH_LINE_STATS()
+}
+
+void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[])
+{
+ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
+ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
+
+ SWSTATS_START_LINE_STATS(uint16_t)
+
+ if (swapLines_)
+ std::swap(src0, src1);
+
+ /* x += 4 sample every other 2x2 block */
+ for (int x = 0; x < (int)window_.width; x += 4) {
+ b = src0[x];
+ g = src0[x + 1];
+ g2 = src1[x];
+ r = src1[x + 1];
+
+ g = (g + g2) / 2;
+
+ /* divide Y by 4 for 10 -> 8 bpp value */
+ SWSTATS_ACCUMULATE_LINE_STATS(4)
+ }
+
+ SWSTATS_FINISH_LINE_STATS()
+}
+
+void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[])
+{
+ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
+ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
+
+ SWSTATS_START_LINE_STATS(uint16_t)
+
+ if (swapLines_)
+ std::swap(src0, src1);
+
+ /* x += 4 sample every other 2x2 block */
+ for (int x = 0; x < (int)window_.width; x += 4) {
+ b = src0[x];
+ g = src0[x + 1];
+ g2 = src1[x];
+ r = src1[x + 1];
+
+ g = (g + g2) / 2;
+
+ /* divide Y by 16 for 12 -> 8 bpp value */
+ SWSTATS_ACCUMULATE_LINE_STATS(16)
+ }
+
+ SWSTATS_FINISH_LINE_STATS()
+}
+
+void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[])
+{
+ const uint8_t *src0 = src[1] + window_.x * 5 / 4;
+ const uint8_t *src1 = src[2] + window_.x * 5 / 4;
+ const int widthInBytes = window_.width * 5 / 4;
+
+ if (swapLines_)
+ std::swap(src0, src1);
+
+ SWSTATS_START_LINE_STATS(uint8_t)
+
+ /* x += 5 sample every other 2x2 block */
+ for (int x = 0; x < widthInBytes; x += 5) {
+ /* BGGR */
+ b = src0[x];
+ g = src0[x + 1];
+ g2 = src1[x];
+ r = src1[x + 1];
+ g = (g + g2) / 2;
+ /* Data is already 8 bits, divide by 1 */
+ SWSTATS_ACCUMULATE_LINE_STATS(1)
+ }
+
+ SWSTATS_FINISH_LINE_STATS()
+}
+
+void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])
+{
+ const uint8_t *src0 = src[1] + window_.x * 5 / 4;
+ const uint8_t *src1 = src[2] + window_.x * 5 / 4;
+ const int widthInBytes = window_.width * 5 / 4;
+
+ if (swapLines_)
+ std::swap(src0, src1);
+
+ SWSTATS_START_LINE_STATS(uint8_t)
+
+ /* x += 5 sample every other 2x2 block */
+ for (int x = 0; x < widthInBytes; x += 5) {
+ /* GBRG */
+ g = src0[x];
+ b = src0[x + 1];
+ r = src1[x];
+ g2 = src1[x + 1];
+ g = (g + g2) / 2;
+ /* Data is already 8 bits, divide by 1 */
+ SWSTATS_ACCUMULATE_LINE_STATS(1)
+ }
+
+ SWSTATS_FINISH_LINE_STATS()
+}
+
+/**
+ * \brief Reset state to start statistics gathering for a new frame
+ *
+ * This may only be called after a successful setWindow() call.
+ */
+void SwStatsCpu::startFrame(void)
+{
+ if (window_.width == 0)
+ LOG(SwStatsCpu, Error) << "Calling startFrame() without setWindow()";
+
+ stats_.sumR_ = 0;
+ stats_.sumB_ = 0;
+ stats_.sumG_ = 0;
+ stats_.yHistogram.fill(0);
+}
+
+/**
+ * \brief Finish statistics calculation for the current frame
+ *
+ * This may only be called after a successful setWindow() call.
+ */
+void SwStatsCpu::finishFrame(void)
+{
+ *sharedStats_ = stats_;
+ statsReady.emit();
+}
+
+/**
+ * \brief Setup SwStatsCpu object for standard Bayer orders
+ * \param[in] order The Bayer order
+ *
+ * Check if order is a standard Bayer order and setup xShift_ and swapLines_
+ * so that a single BGGR stats function can be used for all 4 standard orders.
+ */
+int SwStatsCpu::setupStandardBayerOrder(BayerFormat::Order order)
+{
+ switch (order) {
+ case BayerFormat::BGGR:
+ xShift_ = 0;
+ swapLines_ = false;
+ break;
+ case BayerFormat::GBRG:
+ xShift_ = 1; /* BGGR -> GBRG */
+ swapLines_ = false;
+ break;
+ case BayerFormat::GRBG:
+ xShift_ = 0;
+ swapLines_ = true; /* BGGR -> GRBG */
+ break;
+ case BayerFormat::RGGB:
+ xShift_ = 1; /* BGGR -> GBRG */
+ swapLines_ = true; /* GBRG -> RGGB */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ patternSize_.height = 2;
+ patternSize_.width = 2;
+ ySkipMask_ = 0x02; /* Skip every 3th and 4th line */
+ return 0;
+}
+
+/**
+ * \brief Configure the statistics object for the passed in input format
+ * \param[in] inputCfg The input format
+ *
+ * \return 0 on success, a negative errno value on failure
+ */
+int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+{
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
+
+ if (bayerFormat.packing == BayerFormat::Packing::None &&
+ setupStandardBayerOrder(bayerFormat.order) == 0) {
+ switch (bayerFormat.bitDepth) {
+ case 8:
+ stats0_ = &SwStatsCpu::statsBGGR8Line0;
+ return 0;
+ case 10:
+ stats0_ = &SwStatsCpu::statsBGGR10Line0;
+ return 0;
+ case 12:
+ stats0_ = &SwStatsCpu::statsBGGR12Line0;
+ return 0;
+ }
+ }
+
+ if (bayerFormat.bitDepth == 10 &&
+ bayerFormat.packing == BayerFormat::Packing::CSI2) {
+ patternSize_.height = 2;
+ patternSize_.width = 4; /* 5 bytes per *4* pixels */
+ /* Skip every 3th and 4th line, sample every other 2x2 block */
+ ySkipMask_ = 0x02;
+ xShift_ = 0;
+
+ switch (bayerFormat.order) {
+ case BayerFormat::BGGR:
+ case BayerFormat::GRBG:
+ stats0_ = &SwStatsCpu::statsBGGR10PLine0;
+ swapLines_ = bayerFormat.order == BayerFormat::GRBG;
+ return 0;
+ case BayerFormat::GBRG:
+ case BayerFormat::RGGB:
+ stats0_ = &SwStatsCpu::statsGBRG10PLine0;
+ swapLines_ = bayerFormat.order == BayerFormat::RGGB;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ LOG(SwStatsCpu, Info)
+ << "Unsupported input format " << inputCfg.pixelFormat.toString();
+ return -EINVAL;
+}
+
+/**
+ * \brief Specify window coordinates over which to gather statistics
+ * \param[in] window The window object.
+ */
+void SwStatsCpu::setWindow(const Rectangle &window)
+{
+ window_ = window;
+
+ window_.x &= ~(patternSize_.width - 1);
+ window_.x += xShift_;
+ window_.y &= ~(patternSize_.height - 1);
+
+ /* width_ - xShift_ to make sure the window fits */
+ window_.width -= xShift_;
+ window_.width &= ~(patternSize_.width - 1);
+ window_.height &= ~(patternSize_.height - 1);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
new file mode 100644
index 00000000..baec3951
--- /dev/null
+++ b/src/libcamera/software_isp/swstats_cpu.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * swstats_cpu.h - CPU based software statistics implementation
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/shared_mem_object.h"
+#include "libcamera/internal/software_isp/swisp_stats.h"
+
+namespace libcamera {
+
+class PixelFormat;
+struct StreamConfiguration;
+
+class SwStatsCpu
+{
+public:
+ SwStatsCpu();
+ ~SwStatsCpu() = default;
+
+ bool isValid() const { return sharedStats_.fd().isValid(); }
+
+ const SharedFD &getStatsFD() { return sharedStats_.fd(); }
+
+ const Size &patternSize() { return patternSize_; }
+
+ int configure(const StreamConfiguration &inputCfg);
+ void setWindow(const Rectangle &window);
+ void startFrame();
+ void finishFrame();
+
+ void processLine0(unsigned int y, const uint8_t *src[])
+ {
+ if ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) ||
+ y >= (window_.y + window_.height))
+ return;
+
+ (this->*stats0_)(src);
+ }
+
+ void processLine2(unsigned int y, const uint8_t *src[])
+ {
+ if ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) ||
+ y >= (window_.y + window_.height))
+ return;
+
+ (this->*stats2_)(src);
+ }
+
+ Signal<> statsReady;
+
+private:
+ using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]);
+
+ int setupStandardBayerOrder(BayerFormat::Order order);
+ /* Bayer 8 bpp unpacked */
+ void statsBGGR8Line0(const uint8_t *src[]);
+ /* Bayer 10 bpp unpacked */
+ void statsBGGR10Line0(const uint8_t *src[]);
+ /* Bayer 12 bpp unpacked */
+ void statsBGGR12Line0(const uint8_t *src[]);
+ /* Bayer 10 bpp packed */
+ void statsBGGR10PLine0(const uint8_t *src[]);
+ void statsGBRG10PLine0(const uint8_t *src[]);
+
+ /* Variables set by configure(), used every line */
+ statsProcessFn stats0_;
+ statsProcessFn stats2_;
+ bool swapLines_;
+
+ unsigned int ySkipMask_;
+
+ Rectangle window_;
+
+ Size patternSize_;
+
+ unsigned int xShift_;
+
+ SharedMemObject<SwIspStats> sharedStats_;
+ SwIspStats stats_;
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/stream.cpp b/src/libcamera/stream.cpp
index 686e693b..540a428e 100644
--- a/src/libcamera/stream.cpp
+++ b/src/libcamera/stream.cpp
@@ -311,7 +311,8 @@ StreamConfiguration::StreamConfiguration(const StreamFormats &formats)
* The stride value reports the number of bytes between the beginning of
* successive lines in an image buffer for this stream. The value is
* valid after successfully validating the configuration with a call to
- * CameraConfiguration::validate().
+ * CameraConfiguration::validate(). For compressed formats (such as MJPEG),
+ * this value will be zero.
*/
/**
@@ -418,9 +419,23 @@ std::string StreamConfiguration::toString() const
*/
/**
- * \typedef StreamRoles
- * \brief A vector of StreamRole
+ * \brief Insert a text representation of a StreamRole into an output stream
+ * \param[in] out The output stream
+ * \param[in] role The StreamRole
+ * \return The output stream \a out
*/
+std::ostream &operator<<(std::ostream &out, StreamRole role)
+{
+ static constexpr std::array<const char *, 4> names{
+ "Raw",
+ "StillCapture",
+ "VideoRecording",
+ "Viewfinder",
+ };
+
+ out << names[utils::to_underlying(role)];
+ return out;
+}
/**
* \class Stream
diff --git a/src/libcamera/transform.cpp b/src/libcamera/transform.cpp
index 99a043ba..fb2d55ac 100644
--- a/src/libcamera/transform.cpp
+++ b/src/libcamera/transform.cpp
@@ -1,12 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2020, Raspberry Pi (Trading) Limited
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* transform.cpp - 2D plane transforms.
*/
#include <libcamera/transform.h>
+#include <libcamera/orientation.h>
+
/**
* \file transform.h
* \brief Enum to represent and manipulate 2D plane transforms
@@ -187,24 +189,24 @@ Input image | | goes to output image | |
*/
/**
- * \brief Compose two transforms together
- * \param[in] t1 The second transform
- * \param[in] t0 The first transform
+ * \brief Compose two transforms by applying \a t0 first then \a t1
+ * \param[in] t0 The first transform to apply
+ * \param[in] t1 The second transform to apply
*
- * Composing transforms follows the usual mathematical convention for
- * composing functions. That is, when performing `t1 * t0`, \a t0 is applied
- * first, and then \a t1.
- * For example, `Transpose * HFlip` performs `HFlip` first and then the
- * `Transpose` yielding `Rot270`, as shown below.
+ * Compose two transforms into a transform that is equivalent to first applying
+ * \a t0 and then applying \a t1. For example, `HFlip * Transpose` performs
+ * `HFlip` first and then the `Transpose` yielding `Rot270`, as shown below.
~~~
A-B B-A B-D
Input image | | -> HFLip -> | | -> Transpose -> | | = Rot270
C-D D-C A-C
~~~
- * Note that composition is generally non-commutative for Transforms,
- * and not the same as XOR-ing the underlying bit representations.
+ * Note that composition is generally non-commutative for Transforms, and not
+ * the same as XOR-ing the underlying bit representations.
+ *
+ * \return A Transform equivalent to applying \a t0 and then \a t1
*/
-Transform operator*(Transform t1, Transform t0)
+Transform operator*(Transform t0, Transform t1)
{
/*
* Reorder the operations so that we imagine doing t0's transpose
@@ -299,6 +301,91 @@ Transform transformFromRotation(int angle, bool *success)
return Transform::Identity;
}
+namespace {
+
+/**
+ * \brief Return the transform representing \a orientation
+ * \param[in] orientation The orientation to convert
+ * \return The transform corresponding to \a orientation
+ */
+Transform transformFromOrientation(const Orientation &orientation)
+{
+ switch (orientation) {
+ case Orientation::Rotate0:
+ return Transform::Identity;
+ case Orientation::Rotate0Mirror:
+ return Transform::HFlip;
+ case Orientation::Rotate180:
+ return Transform::Rot180;
+ case Orientation::Rotate180Mirror:
+ return Transform::VFlip;
+ case Orientation::Rotate90Mirror:
+ return Transform::Transpose;
+ case Orientation::Rotate90:
+ return Transform::Rot90;
+ case Orientation::Rotate270Mirror:
+ return Transform::Rot180Transpose;
+ case Orientation::Rotate270:
+ return Transform::Rot270;
+ }
+
+ return Transform::Identity;
+}
+
+} /* namespace */
+
+/**
+ * \brief Return the Transform that applied to \a o2 gives \a o1
+ * \param o1 The Orientation to obtain
+ * \param o2 The base Orientation
+ *
+ * This operation can be used to easily compute the Transform to apply to a
+ * base orientation \a o2 to get the desired orientation \a o1.
+ *
+ * \return A Transform that applied to \a o2 gives \a o1
+ */
+Transform operator/(const Orientation &o1, const Orientation &o2)
+{
+ Transform t1 = transformFromOrientation(o1);
+ Transform t2 = transformFromOrientation(o2);
+
+ return -t2 * t1;
+}
+
+/**
+ * \brief Apply the Transform \a t on the orientation \a o
+ * \param o The orientation
+ * \param t The transform to apply on \a o
+ * \return The Orientation resulting from applying \a t on \a o
+ */
+Orientation operator*(const Orientation &o, const Transform &t)
+{
+ /*
+ * Apply a Transform corresponding to the orientation first and
+ * then apply \a t to it.
+ */
+ switch (transformFromOrientation(o) * t) {
+ case Transform::Identity:
+ return Orientation::Rotate0;
+ case Transform::HFlip:
+ return Orientation::Rotate0Mirror;
+ case Transform::VFlip:
+ return Orientation::Rotate180Mirror;
+ case Transform::Rot180:
+ return Orientation::Rotate180;
+ case Transform::Transpose:
+ return Orientation::Rotate90Mirror;
+ case Transform::Rot270:
+ return Orientation::Rotate270;
+ case Transform::Rot90:
+ return Orientation::Rotate90;
+ case Transform::Rot180Transpose:
+ return Orientation::Rotate270Mirror;
+ }
+
+ return Orientation::Rotate0;
+}
+
/**
* \brief Return a character string describing the transform
* \param[in] t The transform to be described.
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index 3fc8438f..24d208ef 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -24,6 +24,7 @@
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include "libcamera/internal/formats.h"
#include "libcamera/internal/sysfs.h"
/**
@@ -85,18 +86,18 @@ int V4L2Device::open(unsigned int flags)
return -EBUSY;
}
- UniqueFD fd(syscall(SYS_openat, AT_FDCWD, deviceNode_.c_str(), flags));
+ UniqueFD fd(syscall(SYS_openat, AT_FDCWD, deviceNode_.c_str(),
+ flags | O_CLOEXEC));
if (!fd.isValid()) {
int ret = -errno;
- LOG(V4L2, Error) << "Failed to open V4L2 device: "
+ LOG(V4L2, Error) << "Failed to open V4L2 device '"
+ << deviceNode_ << "': "
<< strerror(-ret);
return ret;
}
setFd(std::move(fd));
- listControls();
-
return 0;
}
@@ -127,6 +128,8 @@ int V4L2Device::setFd(UniqueFD fd)
fdEventNotifier_->activated.connect(this, &V4L2Device::eventAvailable);
fdEventNotifier_->setEnabled(false);
+ listControls();
+
return 0;
}
@@ -242,7 +245,8 @@ ControlList V4L2Device::getControls(const std::vector<uint32_t> &ids)
}
/* A specific control failed. */
- LOG(V4L2, Error) << "Unable to read control " << errorIdx
+ const unsigned int id = v4l2Ctrls[errorIdx].id;
+ LOG(V4L2, Error) << "Unable to read control " << utils::hex(id)
<< ": " << strerror(-ret);
v4l2Ctrls.resize(errorIdx);
@@ -352,7 +356,8 @@ int V4L2Device::setControls(ControlList *ctrls)
}
/* A specific control failed. */
- LOG(V4L2, Error) << "Unable to set control " << errorIdx
+ const unsigned int id = v4l2Ctrls[errorIdx].id;
+ LOG(V4L2, Error) << "Unable to set control " << utils::hex(id)
<< ": " << strerror(-ret);
v4l2Ctrls.resize(errorIdx);
@@ -525,7 +530,7 @@ std::unique_ptr<ControlId> V4L2Device::v4l2ControlId(const v4l2_query_ext_ctrl &
* \param[in] ctrl The v4l2_query_ext_ctrl that represents a V4L2 control
* \return A ControlInfo that represents \a ctrl
*/
-ControlInfo V4L2Device::v4l2ControlInfo(const v4l2_query_ext_ctrl &ctrl)
+std::optional<ControlInfo> V4L2Device::v4l2ControlInfo(const v4l2_query_ext_ctrl &ctrl)
{
switch (ctrl.type) {
case V4L2_CTRL_TYPE_U8:
@@ -562,14 +567,14 @@ ControlInfo V4L2Device::v4l2ControlInfo(const v4l2_query_ext_ctrl &ctrl)
*
* \return A ControlInfo that represents \a ctrl
*/
-ControlInfo V4L2Device::v4l2MenuControlInfo(const struct v4l2_query_ext_ctrl &ctrl)
+std::optional<ControlInfo> V4L2Device::v4l2MenuControlInfo(const struct v4l2_query_ext_ctrl &ctrl)
{
std::vector<ControlValue> indices;
struct v4l2_querymenu menu = {};
menu.id = ctrl.id;
if (ctrl.minimum < 0)
- return ControlInfo();
+ return std::nullopt;
for (int32_t index = ctrl.minimum; index <= ctrl.maximum; ++index) {
menu.index = index;
@@ -579,6 +584,14 @@ ControlInfo V4L2Device::v4l2MenuControlInfo(const struct v4l2_query_ext_ctrl &ct
indices.push_back(index);
}
+ /*
+ * Some faulty UVC devices are known to return an empty menu control.
+ * Controls without a menu option can not be set, or read, so they are
+ * not exposed.
+ */
+ if (indices.size() == 0)
+ return std::nullopt;
+
return ControlInfo(indices,
ControlValue(static_cast<int32_t>(ctrl.default_value)));
}
@@ -627,7 +640,17 @@ void V4L2Device::listControls()
controlIdMap_[ctrl.id] = controlIds_.back().get();
controlInfo_.emplace(ctrl.id, ctrl);
- ctrls.emplace(controlIds_.back().get(), v4l2ControlInfo(ctrl));
+ std::optional<ControlInfo> info = v4l2ControlInfo(ctrl);
+
+ if (!info) {
+ LOG(V4L2, Error)
+ << "Control " << ctrl.name
+ << " cannot be registered";
+
+ continue;
+ }
+
+ ctrls.emplace(controlIds_.back().get(), *info);
}
controls_ = ControlInfoMap(std::move(ctrls), controlIdMap_);
@@ -666,7 +689,7 @@ void V4L2Device::updateControlInfo()
continue;
}
- info = v4l2ControlInfo(ctrl);
+ info = *v4l2ControlInfo(ctrl);
}
}
@@ -745,8 +768,12 @@ void V4L2Device::eventAvailable()
static const std::map<uint32_t, ColorSpace> v4l2ToColorSpace = {
{ V4L2_COLORSPACE_RAW, ColorSpace::Raw },
- { V4L2_COLORSPACE_JPEG, ColorSpace::Jpeg },
- { V4L2_COLORSPACE_SRGB, ColorSpace::Srgb },
+ { V4L2_COLORSPACE_SRGB, {
+ ColorSpace::Primaries::Rec709,
+ ColorSpace::TransferFunction::Srgb,
+ ColorSpace::YcbcrEncoding::Rec601,
+ ColorSpace::Range::Limited } },
+ { V4L2_COLORSPACE_JPEG, ColorSpace::Sycc },
{ V4L2_COLORSPACE_SMPTE170M, ColorSpace::Smpte170m },
{ V4L2_COLORSPACE_REC709, ColorSpace::Rec709 },
{ V4L2_COLORSPACE_BT2020, ColorSpace::Rec2020 },
@@ -771,8 +798,7 @@ static const std::map<uint32_t, ColorSpace::Range> v4l2ToRange = {
static const std::vector<std::pair<ColorSpace, v4l2_colorspace>> colorSpaceToV4l2 = {
{ ColorSpace::Raw, V4L2_COLORSPACE_RAW },
- { ColorSpace::Jpeg, V4L2_COLORSPACE_JPEG },
- { ColorSpace::Srgb, V4L2_COLORSPACE_SRGB },
+ { ColorSpace::Sycc, V4L2_COLORSPACE_JPEG },
{ ColorSpace::Smpte170m, V4L2_COLORSPACE_SMPTE170M },
{ ColorSpace::Rec709, V4L2_COLORSPACE_REC709 },
{ ColorSpace::Rec2020, V4L2_COLORSPACE_BT2020 },
@@ -792,6 +818,8 @@ static const std::map<ColorSpace::TransferFunction, v4l2_xfer_func> transferFunc
};
static const std::map<ColorSpace::YcbcrEncoding, v4l2_ycbcr_encoding> ycbcrEncodingToV4l2 = {
+ /* V4L2 has no "none" encoding. */
+ { ColorSpace::YcbcrEncoding::None, V4L2_YCBCR_ENC_DEFAULT },
{ ColorSpace::YcbcrEncoding::Rec601, V4L2_YCBCR_ENC_601 },
{ ColorSpace::YcbcrEncoding::Rec709, V4L2_YCBCR_ENC_709 },
{ ColorSpace::YcbcrEncoding::Rec2020, V4L2_YCBCR_ENC_BT2020 },
@@ -805,6 +833,7 @@ static const std::map<ColorSpace::Range, v4l2_quantization> rangeToV4l2 = {
/**
* \brief Convert the color space fields in a V4L2 format to a ColorSpace
* \param[in] v4l2Format A V4L2 format containing color space information
+ * \param[in] colourEncoding Type of colour encoding
*
* The colorspace, ycbcr_enc, xfer_func and quantization fields within a
* V4L2 format structure are converted to a corresponding ColorSpace.
@@ -816,7 +845,8 @@ static const std::map<ColorSpace::Range, v4l2_quantization> rangeToV4l2 = {
* \retval std::nullopt One or more V4L2 color space fields were not recognised
*/
template<typename T>
-std::optional<ColorSpace> V4L2Device::toColorSpace(const T &v4l2Format)
+std::optional<ColorSpace> V4L2Device::toColorSpace(const T &v4l2Format,
+ PixelFormatInfo::ColourEncoding colourEncoding)
{
auto itColor = v4l2ToColorSpace.find(v4l2Format.colorspace);
if (itColor == v4l2ToColorSpace.end())
@@ -839,6 +869,14 @@ std::optional<ColorSpace> V4L2Device::toColorSpace(const T &v4l2Format)
return std::nullopt;
colorSpace.ycbcrEncoding = itYcbcrEncoding->second;
+
+ /*
+ * V4L2 has no "none" encoding, override the value returned by
+ * the kernel for non-YUV formats as YCbCr encoding isn't
+ * applicable in that case.
+ */
+ if (colourEncoding != PixelFormatInfo::ColourEncodingYUV)
+ colorSpace.ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
}
if (v4l2Format.quantization != V4L2_QUANTIZATION_DEFAULT) {
@@ -847,14 +885,24 @@ std::optional<ColorSpace> V4L2Device::toColorSpace(const T &v4l2Format)
return std::nullopt;
colorSpace.range = itRange->second;
+
+ /*
+ * "Limited" quantization range is only meant for YUV formats.
+ * Override the range to "Full" for all other formats.
+ */
+ if (colourEncoding != PixelFormatInfo::ColourEncodingYUV)
+ colorSpace.range = ColorSpace::Range::Full;
}
return colorSpace;
}
-template std::optional<ColorSpace> V4L2Device::toColorSpace(const struct v4l2_pix_format &);
-template std::optional<ColorSpace> V4L2Device::toColorSpace(const struct v4l2_pix_format_mplane &);
-template std::optional<ColorSpace> V4L2Device::toColorSpace(const struct v4l2_mbus_framefmt &);
+template std::optional<ColorSpace> V4L2Device::toColorSpace(const struct v4l2_pix_format &,
+ PixelFormatInfo::ColourEncoding);
+template std::optional<ColorSpace> V4L2Device::toColorSpace(const struct v4l2_pix_format_mplane &,
+ PixelFormatInfo::ColourEncoding);
+template std::optional<ColorSpace> V4L2Device::toColorSpace(const struct v4l2_mbus_framefmt &,
+ PixelFormatInfo::ColourEncoding);
/**
* \brief Fill in the color space fields of a V4L2 format from a ColorSpace
diff --git a/src/libcamera/v4l2_pixelformat.cpp b/src/libcamera/v4l2_pixelformat.cpp
index 58fc4e9d..731dc10f 100644
--- a/src/libcamera/v4l2_pixelformat.cpp
+++ b/src/libcamera/v4l2_pixelformat.cpp
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
- * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2020, Raspberry Pi Ltd
*
* v4l2_pixelformat.cpp - V4L2 Pixel Format
*/
@@ -81,6 +81,10 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
{ formats::UYVY, "UYVY 4:2:2" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_VYUY),
{ formats::VYUY, "VYUY 4:2:2" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_YUVA32),
+ { formats::AVUY8888, "32-bit YUVA 8-8-8-8" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_YUVX32),
+ { formats::XVUY8888, "32-bit YUVX 8-8-8-8" } },
/* YUV planar formats. */
{ V4L2PixelFormat(V4L2_PIX_FMT_NV16),
@@ -119,7 +123,7 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
{ formats::YVU422, "Planar YVU 4:2:2 (N-C)" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_YUV444M),
{ formats::YUV444, "Planar YUV 4:4:4 (N-C)" } },
- { V4L2PixelFormat(V4L2_PIX_FMT_YUV444M),
+ { V4L2PixelFormat(V4L2_PIX_FMT_YVU444M),
{ formats::YVU444, "Planar YVU 4:4:4 (N-C)" } },
/* Greyscale formats. */
@@ -127,8 +131,12 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
{ formats::R8, "8-bit Greyscale" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_Y10),
{ formats::R10, "10-bit Greyscale" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_Y10P),
+ { formats::R10_CSI2P, "10-bit Greyscale Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_Y12),
{ formats::R12, "12-bit Greyscale" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_Y16),
+ { formats::R16, "16-bit Greyscale" } },
/* Bayer formats. */
{ V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8),
@@ -171,6 +179,22 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
{ formats::SGRBG12_CSI2P, "12-bit Bayer GRGR/BGBG Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12P),
{ formats::SRGGB12_CSI2P, "12-bit Bayer RGRG/GBGB Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR14),
+ { formats::SBGGR14, "14-bit Bayer BGBG/GRGR" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG14),
+ { formats::SGBRG14, "14-bit Bayer GBGB/RGRG" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG14),
+ { formats::SGRBG14, "14-bit Bayer GRGR/BGBG" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB14),
+ { formats::SRGGB14, "14-bit Bayer RGRG/GBGB" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR14P),
+ { formats::SBGGR14_CSI2P, "14-bit Bayer BGBG/GRGR Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG14P),
+ { formats::SGBRG14_CSI2P, "14-bit Bayer GBGB/RGRG Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG14P),
+ { formats::SGRBG14_CSI2P, "14-bit Bayer GRGR/BGBG Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB14P),
+ { formats::SRGGB14_CSI2P, "14-bit Bayer RGRG/GBGB Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_SBGGR16),
{ formats::SBGGR16, "16-bit Bayer BGBG/GRGR" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_SGBRG16),
@@ -183,6 +207,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
/* Compressed formats. */
{ V4L2PixelFormat(V4L2_PIX_FMT_MJPEG),
{ formats::MJPEG, "Motion-JPEG" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_JPEG),
+ { formats::MJPEG, "JPEG JFIF" } },
};
} /* namespace */
@@ -286,15 +312,23 @@ const char *V4L2PixelFormat::description() const
/**
* \brief Convert the V4L2 pixel format to the corresponding PixelFormat
+ * \param[in] warn When true, log a warning message if the V4L2 pixel format
+ * isn't known
+ *
+ * Users of this function might try to convert a V4L2PixelFormat to a
+ * PixelFormat just to check if the format is supported or not. In that case,
+ * they can suppress the warning message by setting the \a warn argument to
+ * false to not pollute the log with unnecessary messages.
+ *
* \return The PixelFormat corresponding to the V4L2 pixel format
*/
-PixelFormat V4L2PixelFormat::toPixelFormat() const
+PixelFormat V4L2PixelFormat::toPixelFormat(bool warn) const
{
const auto iter = vpf2pf.find(*this);
if (iter == vpf2pf.end()) {
- LOG(V4L2, Warning)
- << "Unsupported V4L2 pixel format "
- << toString();
+ if (warn)
+ LOG(V4L2, Warning) << "Unsupported V4L2 pixel format "
+ << toString();
return PixelFormat();
}
@@ -302,26 +336,24 @@ PixelFormat V4L2PixelFormat::toPixelFormat() const
}
/**
- * \brief Convert \a pixelFormat to its corresponding V4L2PixelFormat
+ * \brief Retrieve the list of V4L2PixelFormat associated with \a pixelFormat
* \param[in] pixelFormat The PixelFormat to convert
- * \param[in] multiplanar V4L2 Multiplanar API support flag
*
- * Multiple V4L2 formats may exist for one PixelFormat when the format uses
- * multiple planes, as V4L2 defines separate 4CCs for contiguous and separate
- * planes formats. Set the \a multiplanar parameter to false to select a format
- * with contiguous planes, or to true to select a format with non-contiguous
- * planes.
+ * Multiple V4L2 formats may exist for one PixelFormat as V4L2 defines separate
+ * 4CCs for contiguous and non-contiguous versions of the same image format.
*
- * \return The V4L2PixelFormat corresponding to \a pixelFormat
+ * \return The list of V4L2PixelFormat corresponding to \a pixelFormat
*/
-V4L2PixelFormat V4L2PixelFormat::fromPixelFormat(const PixelFormat &pixelFormat,
- bool multiplanar)
+const std::vector<V4L2PixelFormat> &
+V4L2PixelFormat::fromPixelFormat(const PixelFormat &pixelFormat)
{
+ static const std::vector<V4L2PixelFormat> empty;
+
const PixelFormatInfo &info = PixelFormatInfo::info(pixelFormat);
if (!info.isValid())
- return V4L2PixelFormat();
+ return empty;
- return multiplanar ? info.v4l2Formats.multi : info.v4l2Formats.single;
+ return info.v4l2Formats;
}
/**
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index 98a3911a..1076b700 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -23,6 +23,7 @@
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include "libcamera/internal/formats.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/media_object.h"
@@ -35,105 +36,699 @@ namespace libcamera {
LOG_DECLARE_CATEGORY(V4L2)
-namespace {
-
-/*
- * \struct V4L2SubdeviceFormatInfo
+/**
+ * \class MediaBusFormatInfo
* \brief Information about media bus formats
- * \param bitsPerPixel Bits per pixel
- * \param name Name of MBUS format
+ *
+ * The MediaBusFormatInfo class groups together information describing a media
+ * bus format. It facilitates handling of media bus formats by providing data
+ * commonly used in pipeline handlers.
+ *
+ * \var MediaBusFormatInfo::name
+ * \brief The format name as a human-readable string, used as the text
+ * representation of the format
+ *
+ * \var MediaBusFormatInfo::code
+ * \brief The media bus format code described by this instance (MEDIA_BUS_FMT_*)
+ *
+ * \var MediaBusFormatInfo::type
+ * \brief The media bus format type
+ *
+ * \var MediaBusFormatInfo::bitsPerPixel
+ * \brief The average number of bits per pixel
+ *
+ * The number of bits per pixel averages the total number of bits for all
+ * colour components over the whole image, excluding any padding bits or
+ * padding pixels.
+ *
+ * For formats that transmit multiple or fractional pixels per sample, the
+ * value will differ from the bus width.
+ *
+ * Formats that don't have a fixed number of bits per pixel, such as compressed
+ * formats, or device-specific embedded data formats, report 0 in this field.
+ *
+ * \var MediaBusFormatInfo::colourEncoding
+ * \brief The colour encoding type
+ *
+ * This field is valid for Type::Image formats only.
*/
-struct V4L2SubdeviceFormatInfo {
- unsigned int bitsPerPixel;
- const char *name;
-};
-/*
- * \var formatInfoMap
- * \brief A map that associates V4L2SubdeviceFormatInfo struct to V4L2 media
- * bus codes
- */
-const std::map<uint32_t, V4L2SubdeviceFormatInfo> formatInfoMap = {
- { MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, { 16, "RGB444_2X8_PADHI_BE" } },
- { MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, { 16, "RGB444_2X8_PADHI_LE" } },
- { MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, { 16, "RGB555_2X8_PADHI_BE" } },
- { MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, { 16, "RGB555_2X8_PADHI_LE" } },
- { MEDIA_BUS_FMT_RGB565_1X16, { 16, "RGB565_1X16" } },
- { MEDIA_BUS_FMT_BGR565_2X8_BE, { 16, "BGR565_2X8_BE" } },
- { MEDIA_BUS_FMT_BGR565_2X8_LE, { 16, "BGR565_2X8_LE" } },
- { MEDIA_BUS_FMT_RGB565_2X8_BE, { 16, "RGB565_2X8_BE" } },
- { MEDIA_BUS_FMT_RGB565_2X8_LE, { 16, "RGB565_2X8_LE" } },
- { MEDIA_BUS_FMT_RGB666_1X18, { 18, "RGB666_1X18" } },
- { MEDIA_BUS_FMT_RGB888_1X24, { 24, "RGB888_1X24" } },
- { MEDIA_BUS_FMT_RGB888_2X12_BE, { 24, "RGB888_2X12_BE" } },
- { MEDIA_BUS_FMT_RGB888_2X12_LE, { 24, "RGB888_2X12_LE" } },
- { MEDIA_BUS_FMT_ARGB8888_1X32, { 32, "ARGB8888_1X32" } },
- { MEDIA_BUS_FMT_Y8_1X8, { 8, "Y8_1X8" } },
- { MEDIA_BUS_FMT_UV8_1X8, { 8, "UV8_1X8" } },
- { MEDIA_BUS_FMT_UYVY8_1_5X8, { 12, "UYVY8_1_5X8" } },
- { MEDIA_BUS_FMT_VYUY8_1_5X8, { 12, "VYUY8_1_5X8" } },
- { MEDIA_BUS_FMT_YUYV8_1_5X8, { 12, "YUYV8_1_5X8" } },
- { MEDIA_BUS_FMT_YVYU8_1_5X8, { 12, "YVYU8_1_5X8" } },
- { MEDIA_BUS_FMT_UYVY8_2X8, { 16, "UYVY8_2X8" } },
- { MEDIA_BUS_FMT_VYUY8_2X8, { 16, "VYUY8_2X8" } },
- { MEDIA_BUS_FMT_YUYV8_2X8, { 16, "YUYV8_2X8" } },
- { MEDIA_BUS_FMT_YVYU8_2X8, { 16, "YVYU8_2X8" } },
- { MEDIA_BUS_FMT_Y10_1X10, { 10, "Y10_1X10" } },
- { MEDIA_BUS_FMT_UYVY10_2X10, { 20, "UYVY10_2X10" } },
- { MEDIA_BUS_FMT_VYUY10_2X10, { 20, "VYUY10_2X10" } },
- { MEDIA_BUS_FMT_YUYV10_2X10, { 20, "YUYV10_2X10" } },
- { MEDIA_BUS_FMT_YVYU10_2X10, { 20, "YVYU10_2X10" } },
- { MEDIA_BUS_FMT_Y12_1X12, { 12, "Y12_1X12" } },
- { MEDIA_BUS_FMT_UYVY8_1X16, { 16, "UYVY8_1X16" } },
- { MEDIA_BUS_FMT_VYUY8_1X16, { 16, "VYUY8_1X16" } },
- { MEDIA_BUS_FMT_YUYV8_1X16, { 16, "YUYV8_1X16" } },
- { MEDIA_BUS_FMT_YVYU8_1X16, { 16, "YVYU8_1X16" } },
- { MEDIA_BUS_FMT_YDYUYDYV8_1X16, { 16, "YDYUYDYV8_1X16" } },
- { MEDIA_BUS_FMT_UYVY10_1X20, { 20, "UYVY10_1X20" } },
- { MEDIA_BUS_FMT_VYUY10_1X20, { 20, "VYUY10_1X20" } },
- { MEDIA_BUS_FMT_YUYV10_1X20, { 20, "YUYV10_1X20" } },
- { MEDIA_BUS_FMT_YVYU10_1X20, { 20, "YVYU10_1X20" } },
- { MEDIA_BUS_FMT_YUV8_1X24, { 24, "YUV8_1X24" } },
- { MEDIA_BUS_FMT_YUV10_1X30, { 30, "YUV10_1X30" } },
- { MEDIA_BUS_FMT_AYUV8_1X32, { 32, "AYUV8_1X32" } },
- { MEDIA_BUS_FMT_UYVY12_2X12, { 24, "UYVY12_2X12" } },
- { MEDIA_BUS_FMT_VYUY12_2X12, { 24, "VYUY12_2X12" } },
- { MEDIA_BUS_FMT_YUYV12_2X12, { 24, "YUYV12_2X12" } },
- { MEDIA_BUS_FMT_YVYU12_2X12, { 24, "YVYU12_2X12" } },
- { MEDIA_BUS_FMT_UYVY12_1X24, { 24, "UYVY12_1X24" } },
- { MEDIA_BUS_FMT_VYUY12_1X24, { 24, "VYUY12_1X24" } },
- { MEDIA_BUS_FMT_YUYV12_1X24, { 24, "YUYV12_1X24" } },
- { MEDIA_BUS_FMT_YVYU12_1X24, { 24, "YVYU12_1X24" } },
- { MEDIA_BUS_FMT_SBGGR8_1X8, { 8, "SBGGR8_1X8" } },
- { MEDIA_BUS_FMT_SGBRG8_1X8, { 8, "SGBRG8_1X8" } },
- { MEDIA_BUS_FMT_SGRBG8_1X8, { 8, "SGRBG8_1X8" } },
- { MEDIA_BUS_FMT_SRGGB8_1X8, { 8, "SRGGB8_1X8" } },
- { MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, { 8, "SBGGR10_ALAW8_1X8" } },
- { MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, { 8, "SGBRG10_ALAW8_1X8" } },
- { MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, { 8, "SGRBG10_ALAW8_1X8" } },
- { MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, { 8, "SRGGB10_ALAW8_1X8" } },
- { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, { 8, "SBGGR10_DPCM8_1X8" } },
- { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, { 8, "SGBRG10_DPCM8_1X8" } },
- { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, { 8, "SGRBG10_DPCM8_1X8" } },
- { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, { 8, "SRGGB10_DPCM8_1X8" } },
- { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, { 16, "SBGGR10_2X8_PADHI_BE" } },
- { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, { 16, "SBGGR10_2X8_PADHI_LE" } },
- { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE, { 16, "SBGGR10_2X8_PADLO_BE" } },
- { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE, { 16, "SBGGR10_2X8_PADLO_LE" } },
- { MEDIA_BUS_FMT_SBGGR10_1X10, { 10, "SBGGR10_1X10" } },
- { MEDIA_BUS_FMT_SGBRG10_1X10, { 10, "SGBRG10_1X10" } },
- { MEDIA_BUS_FMT_SGRBG10_1X10, { 10, "SGRBG10_1X10" } },
- { MEDIA_BUS_FMT_SRGGB10_1X10, { 10, "SRGGB10_1X10" } },
- { MEDIA_BUS_FMT_SBGGR12_1X12, { 12, "SBGGR12_1X12" } },
- { MEDIA_BUS_FMT_SGBRG12_1X12, { 12, "SGBRG12_1X12" } },
- { MEDIA_BUS_FMT_SGRBG12_1X12, { 12, "SGRBG12_1X12" } },
- { MEDIA_BUS_FMT_SRGGB12_1X12, { 12, "SRGGB12_1X12" } },
- { MEDIA_BUS_FMT_AHSV8888_1X32, { 32, "AHSV8888_1X32" } },
+/**
+ * \enum MediaBusFormatInfo::Type
+ * \brief The format type
+ *
+ * \var MediaBusFormatInfo::Type::Image
+ * \brief The format describes image data
+ *
+ * \var MediaBusFormatInfo::Type::Metadata
+ * \brief The format describes generic metadata
+ *
+ * \var MediaBusFormatInfo::Type::EmbeddedData
+ * \brief The format describes sensor embedded data
+ */
+
+namespace {
+
+const std::map<uint32_t, MediaBusFormatInfo> mediaBusFormatInfo{
+ /* This table is sorted to match the order in linux/media-bus-format.h */
+ { MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, {
+ .name = "RGB444_2X8_PADHI_BE",
+ .code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, {
+ .name = "RGB444_2X8_PADHI_LE",
+ .code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, {
+ .name = "RGB555_2X8_PADHI_BE",
+ .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, {
+ .name = "RGB555_2X8_PADHI_LE",
+ .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB565_1X16, {
+ .name = "RGB565_1X16",
+ .code = MEDIA_BUS_FMT_RGB565_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_BGR565_2X8_BE, {
+ .name = "BGR565_2X8_BE",
+ .code = MEDIA_BUS_FMT_BGR565_2X8_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_BGR565_2X8_LE, {
+ .name = "BGR565_2X8_LE",
+ .code = MEDIA_BUS_FMT_BGR565_2X8_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB565_2X8_BE, {
+ .name = "RGB565_2X8_BE",
+ .code = MEDIA_BUS_FMT_RGB565_2X8_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB565_2X8_LE, {
+ .name = "RGB565_2X8_LE",
+ .code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB666_1X18, {
+ .name = "RGB666_1X18",
+ .code = MEDIA_BUS_FMT_RGB666_1X18,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 18,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_BGR888_1X24, {
+ .name = "BGR888_1X24",
+ .code = MEDIA_BUS_FMT_BGR888_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB888_1X24, {
+ .name = "RGB888_1X24",
+ .code = MEDIA_BUS_FMT_RGB888_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB888_2X12_BE, {
+ .name = "RGB888_2X12_BE",
+ .code = MEDIA_BUS_FMT_RGB888_2X12_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_RGB888_2X12_LE, {
+ .name = "RGB888_2X12_LE",
+ .code = MEDIA_BUS_FMT_RGB888_2X12_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_ARGB8888_1X32, {
+ .name = "ARGB8888_1X32",
+ .code = MEDIA_BUS_FMT_ARGB8888_1X32,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 32,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_Y8_1X8, {
+ .name = "Y8_1X8",
+ .code = MEDIA_BUS_FMT_Y8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UV8_1X8, {
+ .name = "UV8_1X8",
+ .code = MEDIA_BUS_FMT_UV8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY8_1_5X8, {
+ .name = "UYVY8_1_5X8",
+ .code = MEDIA_BUS_FMT_UYVY8_1_5X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY8_1_5X8, {
+ .name = "VYUY8_1_5X8",
+ .code = MEDIA_BUS_FMT_VYUY8_1_5X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV8_1_5X8, {
+ .name = "YUYV8_1_5X8",
+ .code = MEDIA_BUS_FMT_YUYV8_1_5X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU8_1_5X8, {
+ .name = "YVYU8_1_5X8",
+ .code = MEDIA_BUS_FMT_YVYU8_1_5X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY8_2X8, {
+ .name = "UYVY8_2X8",
+ .code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY8_2X8, {
+ .name = "VYUY8_2X8",
+ .code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV8_2X8, {
+ .name = "YUYV8_2X8",
+ .code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU8_2X8, {
+ .name = "YVYU8_2X8",
+ .code = MEDIA_BUS_FMT_YVYU8_2X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_Y10_1X10, {
+ .name = "Y10_1X10",
+ .code = MEDIA_BUS_FMT_Y10_1X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 10,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY10_2X10, {
+ .name = "UYVY10_2X10",
+ .code = MEDIA_BUS_FMT_UYVY10_2X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY10_2X10, {
+ .name = "VYUY10_2X10",
+ .code = MEDIA_BUS_FMT_VYUY10_2X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV10_2X10, {
+ .name = "YUYV10_2X10",
+ .code = MEDIA_BUS_FMT_YUYV10_2X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU10_2X10, {
+ .name = "YVYU10_2X10",
+ .code = MEDIA_BUS_FMT_YVYU10_2X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_Y12_1X12, {
+ .name = "Y12_1X12",
+ .code = MEDIA_BUS_FMT_Y12_1X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_Y16_1X16, {
+ .name = "Y16_1X16",
+ .code = MEDIA_BUS_FMT_Y16_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY8_1X16, {
+ .name = "UYVY8_1X16",
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY8_1X16, {
+ .name = "VYUY8_1X16",
+ .code = MEDIA_BUS_FMT_VYUY8_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV8_1X16, {
+ .name = "YUYV8_1X16",
+ .code = MEDIA_BUS_FMT_YUYV8_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU8_1X16, {
+ .name = "YVYU8_1X16",
+ .code = MEDIA_BUS_FMT_YVYU8_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YDYUYDYV8_1X16, {
+ .name = "YDYUYDYV8_1X16",
+ .code = MEDIA_BUS_FMT_YDYUYDYV8_1X16,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY10_1X20, {
+ .name = "UYVY10_1X20",
+ .code = MEDIA_BUS_FMT_UYVY10_1X20,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY10_1X20, {
+ .name = "VYUY10_1X20",
+ .code = MEDIA_BUS_FMT_VYUY10_1X20,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV10_1X20, {
+ .name = "YUYV10_1X20",
+ .code = MEDIA_BUS_FMT_YUYV10_1X20,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU10_1X20, {
+ .name = "YVYU10_1X20",
+ .code = MEDIA_BUS_FMT_YVYU10_1X20,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 20,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUV8_1X24, {
+ .name = "YUV8_1X24",
+ .code = MEDIA_BUS_FMT_YUV8_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUV10_1X30, {
+ .name = "YUV10_1X30",
+ .code = MEDIA_BUS_FMT_YUV10_1X30,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 30,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_AYUV8_1X32, {
+ .name = "AYUV8_1X32",
+ .code = MEDIA_BUS_FMT_AYUV8_1X32,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 32,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY12_2X12, {
+ .name = "UYVY12_2X12",
+ .code = MEDIA_BUS_FMT_UYVY12_2X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY12_2X12, {
+ .name = "VYUY12_2X12",
+ .code = MEDIA_BUS_FMT_VYUY12_2X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV12_2X12, {
+ .name = "YUYV12_2X12",
+ .code = MEDIA_BUS_FMT_YUYV12_2X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU12_2X12, {
+ .name = "YVYU12_2X12",
+ .code = MEDIA_BUS_FMT_YVYU12_2X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_UYVY12_1X24, {
+ .name = "UYVY12_1X24",
+ .code = MEDIA_BUS_FMT_UYVY12_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_VYUY12_1X24, {
+ .name = "VYUY12_1X24",
+ .code = MEDIA_BUS_FMT_VYUY12_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YUYV12_1X24, {
+ .name = "YUYV12_1X24",
+ .code = MEDIA_BUS_FMT_YUYV12_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_YVYU12_1X24, {
+ .name = "YVYU12_1X24",
+ .code = MEDIA_BUS_FMT_YVYU12_1X24,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 24,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_SBGGR8_1X8, {
+ .name = "SBGGR8_1X8",
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGBRG8_1X8, {
+ .name = "SGBRG8_1X8",
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGRBG8_1X8, {
+ .name = "SGRBG8_1X8",
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SRGGB8_1X8, {
+ .name = "SRGGB8_1X8",
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, {
+ .name = "SBGGR10_ALAW8_1X8",
+ .code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, {
+ .name = "SGBRG10_ALAW8_1X8",
+ .code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, {
+ .name = "SGRBG10_ALAW8_1X8",
+ .code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, {
+ .name = "SRGGB10_ALAW8_1X8",
+ .code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, {
+ .name = "SBGGR10_DPCM8_1X8",
+ .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, {
+ .name = "SGBRG10_DPCM8_1X8",
+ .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, {
+ .name = "SGRBG10_DPCM8_1X8",
+ .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, {
+ .name = "SRGGB10_DPCM8_1X8",
+ .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, {
+ .name = "SBGGR10_2X8_PADHI_BE",
+ .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, {
+ .name = "SBGGR10_2X8_PADHI_LE",
+ .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE, {
+ .name = "SBGGR10_2X8_PADLO_BE",
+ .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE, {
+ .name = "SBGGR10_2X8_PADLO_LE",
+ .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 16,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR10_1X10, {
+ .name = "SBGGR10_1X10",
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 10,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGBRG10_1X10, {
+ .name = "SGBRG10_1X10",
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 10,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, {
+ .name = "SGRBG10_1X10",
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 10,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SRGGB10_1X10, {
+ .name = "SRGGB10_1X10",
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 10,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR12_1X12, {
+ .name = "SBGGR12_1X12",
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGBRG12_1X12, {
+ .name = "SGBRG12_1X12",
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, {
+ .name = "SGRBG12_1X12",
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SRGGB12_1X12, {
+ .name = "SRGGB12_1X12",
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SBGGR14_1X14, {
+ .name = "SBGGR14_1X14",
+ .code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGBRG14_1X14, {
+ .name = "SGBRG14_1X14",
+ .code = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SGRBG14_1X14, {
+ .name = "SGRBG14_1X14",
+ .code = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ { MEDIA_BUS_FMT_SRGGB14_1X14, {
+ .name = "SRGGB14_1X14",
+ .code = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 14,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
+ /* \todo Clarify colour encoding for HSV formats */
+ { MEDIA_BUS_FMT_AHSV8888_1X32, {
+ .name = "AHSV8888_1X32",
+ .code = MEDIA_BUS_FMT_AHSV8888_1X32,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 32,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRGB,
+ } },
+ { MEDIA_BUS_FMT_JPEG_1X8, {
+ .name = "JPEG_1X8",
+ .code = MEDIA_BUS_FMT_JPEG_1X8,
+ .type = MediaBusFormatInfo::Type::Image,
+ .bitsPerPixel = 8,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ } },
+ { MEDIA_BUS_FMT_METADATA_FIXED, {
+ .name = "METADATA_FIXED",
+ .code = MEDIA_BUS_FMT_METADATA_FIXED,
+ .type = MediaBusFormatInfo::Type::Metadata,
+ .bitsPerPixel = 0,
+ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
+ } },
};
} /* namespace */
/**
+ * \fn bool MediaBusFormatInfo::isValid() const
+ * \brief Check if the media bus format info is valid
+ * \return True if the media bus format info is valid, false otherwise
+ */
+
+/**
+ * \brief Retrieve information about a media bus format
+ * \param[in] code The media bus format code
+ * \return The MediaBusFormatInfo describing the \a code if known, or an invalid
+ * MediaBusFormatInfo otherwise
+ */
+const MediaBusFormatInfo &MediaBusFormatInfo::info(uint32_t code)
+{
+ static const MediaBusFormatInfo invalid{};
+
+ const auto it = mediaBusFormatInfo.find(code);
+ if (it == mediaBusFormatInfo.end()) {
+ LOG(V4L2, Warning)
+ << "Unsupported media bus format "
+ << utils::hex(code, 4);
+ return invalid;
+ }
+
+ return it->second;
+}
+
+/**
+ * \struct V4L2SubdeviceCapability
+ * \brief struct v4l2_subdev_capability object wrapper and helpers
+ *
+ * The V4L2SubdeviceCapability structure manages the information returned by the
+ * VIDIOC_SUBDEV_QUERYCAP ioctl.
+ */
+
+/**
+ * \fn V4L2SubdeviceCapability::isReadOnly()
+ * \brief Retrieve if a subdevice is registered as read-only
+ *
+ * A V4L2 subdevice is registered as read-only if V4L2_SUBDEV_CAP_RO_SUBDEV
+ * is listed as part of its capabilities.
+ *
+ * \return True if the subdevice is registered as read-only, false otherwise
+ */
+
+/**
+ * \fn V4L2SubdeviceCapability::hasStreams()
+ * \brief Retrieve if a subdevice supports the V4L2 streams API
+ * \return True if the subdevice supports the streams API, false otherwise
+ */
+
+/**
* \struct V4L2SubdeviceFormat
* \brief The V4L2 sub-device image format and sizes
*
@@ -162,7 +757,7 @@ const std::map<uint32_t, V4L2SubdeviceFormatInfo> formatInfoMap = {
*/
/**
- * \var V4L2SubdeviceFormat::mbus_code
+ * \var V4L2SubdeviceFormat::code
* \brief The image format bus code
*/
@@ -199,23 +794,6 @@ const std::string V4L2SubdeviceFormat::toString() const
}
/**
- * \brief Retrieve the number of bits per pixel for the V4L2 subdevice format
- * \return The number of bits per pixel for the format, or 0 if the format is
- * not supported
- */
-uint8_t V4L2SubdeviceFormat::bitsPerPixel() const
-{
- const auto it = formatInfoMap.find(mbus_code);
- if (it == formatInfoMap.end()) {
- LOG(V4L2, Error) << "No information available for format '"
- << *this << "'";
- return 0;
- }
-
- return it->second.bitsPerPixel;
-}
-
-/**
* \brief Insert a text representation of a V4L2SubdeviceFormat into an output
* stream
* \param[in] out The output stream
@@ -226,10 +804,10 @@ std::ostream &operator<<(std::ostream &out, const V4L2SubdeviceFormat &f)
{
out << f.size << "-";
- const auto it = formatInfoMap.find(f.mbus_code);
+ const auto it = mediaBusFormatInfo.find(f.code);
- if (it == formatInfoMap.end())
- out << utils::hex(f.mbus_code, 4);
+ if (it == mediaBusFormatInfo.end())
+ out << utils::hex(f.code, 4);
else
out << it->second.name;
@@ -265,6 +843,134 @@ std::ostream &operator<<(std::ostream &out, const V4L2SubdeviceFormat &f)
*/
/**
+ * \class V4L2Subdevice::Stream
+ * \brief V4L2 subdevice stream
+ *
+ * This class identifies a subdev stream, by bundling the pad number with the
+ * stream number. It is used in all stream-aware functions of the V4L2Subdevice
+ * class to identify the stream the functions operate on.
+ *
+ * \var V4L2Subdevice::Stream::pad
+ * \brief The 0-indexed pad number
+ *
+ * \var V4L2Subdevice::Stream::stream
+ * \brief The stream number
+ */
+
+/**
+ * \fn V4L2Subdevice::Stream::Stream()
+ * \brief Construct a Stream with pad and stream set to 0
+ */
+
+/**
+ * \fn V4L2Subdevice::Stream::Stream(unsigned int pad, unsigned int stream)
+ * \brief Construct a Stream with a given \a pad and \a stream number
+ * \param[in] pad The indexed pad number
+ * \param[in] stream The stream number
+ */
+
+/**
+ * \brief Compare streams for equality
+ * \return True if the two streams are equal, false otherwise
+ */
+bool operator==(const V4L2Subdevice::Stream &lhs, const V4L2Subdevice::Stream &rhs)
+{
+ return lhs.pad == rhs.pad && lhs.stream == rhs.stream;
+}
+
+/**
+ * \fn bool operator!=(const V4L2Subdevice::Stream &lhs, const V4L2Subdevice::Stream &rhs)
+ * \brief Compare streams for inequality
+ * \return True if the two streams are not equal, false otherwise
+ */
+
+/**
+ * \brief Insert a text representation of a V4L2Subdevice::Stream into an
+ * output stream
+ * \param[in] out The output stream
+ * \param[in] stream The V4L2Subdevice::Stream
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Stream &stream)
+{
+ out << stream.pad << "/" << stream.stream;
+
+ return out;
+}
+
+/**
+ * \class V4L2Subdevice::Route
+ * \brief V4L2 subdevice routing table entry
+ *
+ * This class models a route in the subdevice routing table. It is similar to
+ * the v4l2_subdev_route structure, but uses the V4L2Subdevice::Stream class
+ * for easier usage with the V4L2Subdevice stream-aware functions.
+ *
+ * \var V4L2Subdevice::Route::sink
+ * \brief The sink stream of the route
+ *
+ * \var V4L2Subdevice::Route::source
+ * \brief The source stream of the route
+ *
+ * \var V4L2Subdevice::Route::flags
+ * \brief The route flags (V4L2_SUBDEV_ROUTE_FL_*)
+ */
+
+/**
+ * \fn V4L2Subdevice::Route::Route()
+ * \brief Construct a Route with default streams
+ */
+
+/**
+ * \fn V4L2Subdevice::Route::Route(const Stream &sink, const Stream &source,
+ * uint32_t flags)
+ * \brief Construct a Route from \a sink to \a source
+ * \param[in] sink The sink stream
+ * \param[in] source The source stream
+ * \param[in] flags The route flags
+ */
+
+/**
+ * \brief Insert a text representation of a V4L2Subdevice::Route into an
+ * output stream
+ * \param[in] out The output stream
+ * \param[in] route The V4L2Subdevice::Route
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route)
+{
+ out << route.sink << " -> " << route.source
+ << " (" << utils::hex(route.flags) << ")";
+
+ return out;
+}
+
+/**
+ * \typedef V4L2Subdevice::Routing
+ * \brief V4L2 subdevice routing table
+ *
+ * This class stores a subdevice routing table as a vector of routes.
+ */
+
+/**
+ * \brief Insert a text representation of a V4L2Subdevice::Routing into an
+ * output stream
+ * \param[in] out The output stream
+ * \param[in] routing The V4L2Subdevice::Routing
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing)
+{
+ for (const auto &[i, route] : utils::enumerate(routing)) {
+ out << "[" << i << "] " << route;
+ if (i != routing.size() - 1)
+ out << ", ";
+ }
+
+ return out;
+}
+
+/**
* \brief Create a V4L2 subdevice from a MediaEntity using its device node
* path
*/
@@ -284,7 +990,40 @@ V4L2Subdevice::~V4L2Subdevice()
*/
int V4L2Subdevice::open()
{
- return V4L2Device::open(O_RDWR);
+ int ret = V4L2Device::open(O_RDWR);
+ if (ret)
+ return ret;
+
+ /*
+ * Try to query the subdev capabilities. The VIDIOC_SUBDEV_QUERYCAP API
+ * was introduced in kernel v5.8, ENOTTY errors must be ignored to
+ * support older kernels.
+ */
+ caps_ = {};
+ ret = ioctl(VIDIOC_SUBDEV_QUERYCAP, &caps_);
+ if (ret < 0 && errno != ENOTTY) {
+ ret = -errno;
+ LOG(V4L2, Error)
+ << "Unable to query capabilities: " << strerror(-ret);
+ return ret;
+ }
+
+ /* If the subdev supports streams, enable the streams API. */
+ if (caps_.hasStreams()) {
+ struct v4l2_subdev_client_capability clientCaps{};
+ clientCaps.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS;
+
+ ret = ioctl(VIDIOC_SUBDEV_S_CLIENT_CAP, &clientCaps);
+ if (ret < 0) {
+ ret = -errno;
+ LOG(V4L2, Error)
+ << "Unable to set client capabilities: "
+ << strerror(-ret);
+ return ret;
+ }
+ }
+
+ return 0;
}
/**
@@ -295,7 +1034,7 @@ int V4L2Subdevice::open()
/**
* \brief Get selection rectangle \a rect for \a target
- * \param[in] pad The 0-indexed pad number the rectangle is retrieved from
+ * \param[in] stream The stream the rectangle is retrieved from
* \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
* \param[out] rect The retrieved selection rectangle
*
@@ -303,13 +1042,14 @@ int V4L2Subdevice::open()
*
* \return 0 on success or a negative error code otherwise
*/
-int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target,
+int V4L2Subdevice::getSelection(const Stream &stream, unsigned int target,
Rectangle *rect)
{
struct v4l2_subdev_selection sel = {};
sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- sel.pad = pad;
+ sel.pad = stream.pad;
+ sel.stream = stream.stream;
sel.target = target;
sel.flags = 0;
@@ -317,7 +1057,7 @@ int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target,
if (ret < 0) {
LOG(V4L2, Error)
<< "Unable to get rectangle " << target << " on pad "
- << pad << ": " << strerror(-ret);
+ << stream << ": " << strerror(-ret);
return ret;
}
@@ -330,8 +1070,19 @@ int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target,
}
/**
+ * \fn V4L2Subdevice::getSelection(unsigned int pad, unsigned int target,
+ * Rectangle *rect)
+ * \brief Get selection rectangle \a rect for \a target
+ * \param[in] pad The 0-indexed pad number the rectangle is retrieved from
+ * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
+ * \param[out] rect The retrieved selection rectangle
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
* \brief Set selection rectangle \a rect for \a target
- * \param[in] pad The 0-indexed pad number the rectangle is to be applied to
+ * \param[in] stream The stream the rectangle is to be applied to
* \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
* \param[inout] rect The selection rectangle to be applied
*
@@ -339,13 +1090,14 @@ int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target,
*
* \return 0 on success or a negative error code otherwise
*/
-int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
+int V4L2Subdevice::setSelection(const Stream &stream, unsigned int target,
Rectangle *rect)
{
struct v4l2_subdev_selection sel = {};
sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- sel.pad = pad;
+ sel.pad = stream.pad;
+ sel.stream = stream.stream;
sel.target = target;
sel.flags = 0;
@@ -358,7 +1110,7 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
if (ret < 0) {
LOG(V4L2, Error)
<< "Unable to set rectangle " << target << " on pad "
- << pad << ": " << strerror(-ret);
+ << stream << ": " << strerror(-ret);
return ret;
}
@@ -369,26 +1121,40 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
return 0;
}
+
/**
- * \brief Enumerate all media bus codes and frame sizes on a \a pad
- * \param[in] pad The 0-indexed pad number to enumerate formats on
+ * \fn V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
+ * Rectangle *rect)
+ * \brief Set selection rectangle \a rect for \a target
+ * \param[in] pad The 0-indexed pad number the rectangle is to be applied to
+ * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
+ * \param[inout] rect The selection rectangle to be applied
+ *
+ * \todo Define a V4L2SelectionTarget enum for the selection target
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
+ * \brief Enumerate all media bus codes and frame sizes on a \a stream
+ * \param[in] stream The stream to enumerate formats for
*
* Enumerate all media bus codes and frame sizes supported by the subdevice on
- * a \a pad.
+ * a \a stream.
*
* \return A list of the supported device formats
*/
-V4L2Subdevice::Formats V4L2Subdevice::formats(unsigned int pad)
+V4L2Subdevice::Formats V4L2Subdevice::formats(const Stream &stream)
{
Formats formats;
- if (pad >= entity_->pads().size()) {
- LOG(V4L2, Error) << "Invalid pad: " << pad;
+ if (stream.pad >= entity_->pads().size()) {
+ LOG(V4L2, Error) << "Invalid pad: " << stream.pad;
return {};
}
- for (unsigned int code : enumPadCodes(pad)) {
- std::vector<SizeRange> sizes = enumPadSizes(pad, code);
+ for (unsigned int code : enumPadCodes(stream)) {
+ std::vector<SizeRange> sizes = enumPadSizes(stream, code);
if (sizes.empty())
return {};
@@ -396,7 +1162,7 @@ V4L2Subdevice::Formats V4L2Subdevice::formats(unsigned int pad)
if (!inserted.second) {
LOG(V4L2, Error)
<< "Could not add sizes for media bus code "
- << code << " on pad " << pad;
+ << code << " on pad " << stream.pad;
return {};
}
}
@@ -405,79 +1171,273 @@ V4L2Subdevice::Formats V4L2Subdevice::formats(unsigned int pad)
}
/**
- * \brief Retrieve the image format set on one of the V4L2 subdevice pads
- * \param[in] pad The 0-indexed pad number the format is to be retrieved from
+ * \fn V4L2Subdevice::formats(unsigned int pad)
+ * \brief Enumerate all media bus codes and frame sizes on a \a pad
+ * \param[in] pad The 0-indexed pad number to enumerate formats on
+ *
+ * Enumerate all media bus codes and frame sizes supported by the subdevice on
+ * a \a pad
+ *
+ * \return A list of the supported device formats
+ */
+
+std::optional<ColorSpace> V4L2Subdevice::toColorSpace(const v4l2_mbus_framefmt &format) const
+{
+ /*
+ * Only image formats have a color space, for other formats (such as
+ * metadata formats) the color space concept isn't applicable. V4L2
+ * subdev drivers return a colorspace set to V4L2_COLORSPACE_DEFAULT in
+ * that case (as well as for image formats when the driver hasn't
+ * bothered implementing color space support). Check the colorspace
+ * field here and return std::nullopt directly to avoid logging a
+ * warning.
+ */
+ if (format.colorspace == V4L2_COLORSPACE_DEFAULT)
+ return std::nullopt;
+
+ PixelFormatInfo::ColourEncoding colourEncoding;
+ const MediaBusFormatInfo &info = MediaBusFormatInfo::info(format.code);
+ if (info.isValid()) {
+ colourEncoding = info.colourEncoding;
+ } else {
+ LOG(V4L2, Warning)
+ << "Unknown subdev format "
+ << utils::hex(format.code, 4)
+ << ", defaulting to RGB encoding";
+
+ colourEncoding = PixelFormatInfo::ColourEncodingRGB;
+ }
+
+ return V4L2Device::toColorSpace(format, colourEncoding);
+}
+
+/**
+ * \brief Retrieve the image format set on one of the V4L2 subdevice streams
+ * \param[in] stream The stream the format is to be retrieved from
* \param[out] format The image bus format
* \param[in] whence The format to get, \ref V4L2Subdevice::ActiveFormat
* "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat"
* \return 0 on success or a negative error code otherwise
*/
-int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format,
+int V4L2Subdevice::getFormat(const Stream &stream, V4L2SubdeviceFormat *format,
Whence whence)
{
struct v4l2_subdev_format subdevFmt = {};
- subdevFmt.which = whence == ActiveFormat ? V4L2_SUBDEV_FORMAT_ACTIVE
- : V4L2_SUBDEV_FORMAT_TRY;
- subdevFmt.pad = pad;
+ subdevFmt.which = whence;
+ subdevFmt.pad = stream.pad;
+ subdevFmt.stream = stream.stream;
int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
if (ret) {
LOG(V4L2, Error)
- << "Unable to get format on pad " << pad
- << ": " << strerror(-ret);
+ << "Unable to get format on pad " << stream << ": "
+ << strerror(-ret);
return ret;
}
format->size.width = subdevFmt.format.width;
format->size.height = subdevFmt.format.height;
- format->mbus_code = subdevFmt.format.code;
+ format->code = subdevFmt.format.code;
format->colorSpace = toColorSpace(subdevFmt.format);
return 0;
}
/**
+ * \fn V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format,
+ * Whence whence)
+ * \brief Retrieve the image format set on one of the V4L2 subdevice pads
+ * \param[in] pad The 0-indexed pad number the format is to be retrieved from
+ * \param[out] format The image bus format
+ * \param[in] whence The format to get, \ref V4L2Subdevice::ActiveFormat
+ * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat"
+ * \return 0 on success or a negative error code otherwise
+ */
+
+/**
* \brief Set an image format on one of the V4L2 subdevice pads
- * \param[in] pad The 0-indexed pad number the format is to be applied to
- * \param[inout] format The image bus format to apply to the subdevice's pad
+ * \param[in] stream The stream the format is to be applied to
+ * \param[inout] format The image bus format to apply to the stream
* \param[in] whence The format to set, \ref V4L2Subdevice::ActiveFormat
* "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat"
*
- * Apply the requested image format to the desired media pad and return the
+ * Apply the requested image format to the desired stream and return the
* actually applied format parameters, as getFormat() would do.
*
* \return 0 on success or a negative error code otherwise
*/
-int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format,
+int V4L2Subdevice::setFormat(const Stream &stream, V4L2SubdeviceFormat *format,
Whence whence)
{
struct v4l2_subdev_format subdevFmt = {};
- subdevFmt.which = whence == ActiveFormat ? V4L2_SUBDEV_FORMAT_ACTIVE
- : V4L2_SUBDEV_FORMAT_TRY;
- subdevFmt.pad = pad;
+ subdevFmt.which = whence;
+ subdevFmt.pad = stream.pad;
+ subdevFmt.stream = stream.stream;
subdevFmt.format.width = format->size.width;
subdevFmt.format.height = format->size.height;
- subdevFmt.format.code = format->mbus_code;
+ subdevFmt.format.code = format->code;
subdevFmt.format.field = V4L2_FIELD_NONE;
- fromColorSpace(format->colorSpace, subdevFmt.format);
+ if (format->colorSpace) {
+ fromColorSpace(format->colorSpace, subdevFmt.format);
+
+ /* The CSC flag is only applicable to source pads. */
+ if (entity_->pads()[stream.pad]->flags() & MEDIA_PAD_FL_SOURCE)
+ subdevFmt.format.flags |= V4L2_MBUS_FRAMEFMT_SET_CSC;
+ }
int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
if (ret) {
LOG(V4L2, Error)
- << "Unable to set format on pad " << pad
- << ": " << strerror(-ret);
+ << "Unable to set format on pad " << stream << ": "
+ << strerror(-ret);
return ret;
}
format->size.width = subdevFmt.format.width;
format->size.height = subdevFmt.format.height;
- format->mbus_code = subdevFmt.format.code;
+ format->code = subdevFmt.format.code;
format->colorSpace = toColorSpace(subdevFmt.format);
return 0;
}
/**
+ * \fn V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format,
+ * Whence whence)
+ * \brief Set an image format on one of the V4L2 subdevice pads
+ * \param[in] pad The 0-indexed pad number the format is to be applied to
+ * \param[inout] format The image bus format to apply to the subdevice's pad
+ * \param[in] whence The format to set, \ref V4L2Subdevice::ActiveFormat
+ * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat"
+ *
+ * Apply the requested image format to the desired media pad and return the
+ * actually applied format parameters, as getFormat() would do.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+
+namespace {
+
+void routeFromKernel(V4L2Subdevice::Route &route,
+ const struct v4l2_subdev_route &kroute)
+{
+ route.sink.pad = kroute.sink_pad;
+ route.sink.stream = kroute.sink_stream;
+ route.source.pad = kroute.source_pad;
+ route.source.stream = kroute.source_stream;
+ route.flags = kroute.flags;
+}
+
+void routeToKernel(const V4L2Subdevice::Route &route,
+ struct v4l2_subdev_route &kroute)
+{
+ kroute.sink_pad = route.sink.pad;
+ kroute.sink_stream = route.sink.stream;
+ kroute.source_pad = route.source.pad;
+ kroute.source_stream = route.source.stream;
+ kroute.flags = route.flags;
+}
+
+} /* namespace */
+
+/**
+ * \brief Retrieve the subdevice's internal routing table
+ * \param[out] routing The routing table
+ * \param[in] whence The routing table to get, \ref V4L2Subdevice::ActiveFormat
+ * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat"
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
+{
+ routing->clear();
+
+ if (!caps_.hasStreams())
+ return 0;
+
+ struct v4l2_subdev_routing rt = {};
+
+ rt.which = whence;
+
+ int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
+ if (ret == 0 || ret == -ENOTTY)
+ return ret;
+
+ if (ret != -ENOSPC) {
+ LOG(V4L2, Error)
+ << "Failed to retrieve number of routes: "
+ << strerror(-ret);
+ return ret;
+ }
+
+ std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
+ rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+
+ ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
+ if (ret) {
+ LOG(V4L2, Error)
+ << "Failed to retrieve routes: " << strerror(-ret);
+ return ret;
+ }
+
+ if (rt.num_routes != routes.size()) {
+ LOG(V4L2, Error) << "Invalid number of routes";
+ return -EINVAL;
+ }
+
+ routing->resize(rt.num_routes);
+
+ for (const auto &[i, route] : utils::enumerate(routes))
+ routeFromKernel((*routing)[i], route);
+
+ return 0;
+}
+
+/**
+ * \brief Set a routing table on the V4L2 subdevice
+ * \param[inout] routing The routing table
+ * \param[in] whence The routing table to set, \ref V4L2Subdevice::ActiveFormat
+ * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat"
+ *
+ * Apply to the V4L2 subdevice the routing table \a routing and update its
+ * content to reflect the actually applied routing table as getRouting() would
+ * do.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Subdevice::setRouting(Routing *routing, Whence whence)
+{
+ if (!caps_.hasStreams()) {
+ routing->clear();
+ return 0;
+ }
+
+ std::vector<struct v4l2_subdev_route> routes{ routing->size() };
+
+ for (const auto &[i, route] : utils::enumerate(*routing))
+ routeToKernel(route, routes[i]);
+
+ struct v4l2_subdev_routing rt = {};
+ rt.which = whence;
+ rt.num_routes = routes.size();
+ rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+
+ int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING, &rt);
+ if (ret) {
+ LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
+ return ret;
+ }
+
+ routes.resize(rt.num_routes);
+ routing->resize(rt.num_routes);
+
+ for (const auto &[i, route] : utils::enumerate(routes))
+ routeFromKernel((*routing)[i], route);
+
+ return 0;
+}
+
+/**
* \brief Retrieve the model name of the device
*
* The model name allows identification of the specific device model. This can
@@ -530,6 +1490,12 @@ const std::string &V4L2Subdevice::model()
}
/**
+ * \fn V4L2Subdevice::caps()
+ * \brief Retrieve the subdevice V4L2 capabilities
+ * \return The subdevice V4L2 capabilities
+ */
+
+/**
* \brief Create a new video subdevice instance from \a entity in media device
* \a media
* \param[in] media The media device where the entity is registered
@@ -553,14 +1519,15 @@ std::string V4L2Subdevice::logPrefix() const
return "'" + entity_->name() + "'";
}
-std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
+std::vector<unsigned int> V4L2Subdevice::enumPadCodes(const Stream &stream)
{
std::vector<unsigned int> codes;
int ret;
for (unsigned int index = 0; ; index++) {
struct v4l2_subdev_mbus_code_enum mbusEnum = {};
- mbusEnum.pad = pad;
+ mbusEnum.pad = stream.pad;
+ mbusEnum.stream = stream.stream;
mbusEnum.index = index;
mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
@@ -573,7 +1540,7 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
if (ret < 0 && ret != -EINVAL) {
LOG(V4L2, Error)
- << "Unable to enumerate formats on pad " << pad
+ << "Unable to enumerate formats on pad " << stream
<< ": " << strerror(-ret);
return {};
}
@@ -581,7 +1548,7 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
return codes;
}
-std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
+std::vector<SizeRange> V4L2Subdevice::enumPadSizes(const Stream &stream,
unsigned int code)
{
std::vector<SizeRange> sizes;
@@ -590,7 +1557,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
for (unsigned int index = 0;; index++) {
struct v4l2_subdev_frame_size_enum sizeEnum = {};
sizeEnum.index = index;
- sizeEnum.pad = pad;
+ sizeEnum.pad = stream.pad;
+ sizeEnum.stream = stream.stream;
sizeEnum.code = code;
sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
@@ -604,7 +1572,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
LOG(V4L2, Error)
- << "Unable to enumerate sizes on pad " << pad
+ << "Unable to enumerate sizes on pad " << stream
<< ": " << strerror(-ret);
return {};
}
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 63911339..a72ef64d 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -633,13 +633,9 @@ int V4L2VideoDevice::open()
<< "Opened device " << caps_.bus_info() << ": "
<< caps_.driver() << ": " << caps_.card();
- ret = getFormat(&format_);
- if (ret) {
- LOG(V4L2, Error) << "Failed to get format";
+ ret = initFormats();
+ if (ret)
return ret;
- }
-
- formatInfo_ = &PixelFormatInfo::info(format_.fourcc);
return 0;
}
@@ -726,7 +722,24 @@ int V4L2VideoDevice::open(SharedFD handle, enum v4l2_buf_type type)
<< "Opened device " << caps_.bus_info() << ": "
<< caps_.driver() << ": " << caps_.card();
- ret = getFormat(&format_);
+ ret = initFormats();
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int V4L2VideoDevice::initFormats()
+{
+ const std::vector<V4L2PixelFormat> &deviceFormats = enumPixelformats(0);
+ if (deviceFormats.empty()) {
+ LOG(V4L2, Error) << "Failed to initialize device formats";
+ return -EINVAL;
+ }
+
+ pixelFormats_ = { deviceFormats.begin(), deviceFormats.end() };
+
+ int ret = getFormat(&format_);
if (ret) {
LOG(V4L2, Error) << "Failed to get format";
return ret;
@@ -901,6 +914,13 @@ int V4L2VideoDevice::trySetFormatMeta(V4L2DeviceFormat *format, bool set)
return 0;
}
+template<typename T>
+std::optional<ColorSpace> V4L2VideoDevice::toColorSpace(const T &v4l2Format)
+{
+ V4L2PixelFormat fourcc{ v4l2Format.pixelformat };
+ return V4L2Device::toColorSpace(v4l2Format, PixelFormatInfo::info(fourcc).colourEncoding);
+}
+
int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
{
struct v4l2_format v4l2Format = {};
@@ -940,7 +960,12 @@ int V4L2VideoDevice::trySetFormatMultiplane(V4L2DeviceFormat *format, bool set)
pix->pixelformat = format->fourcc;
pix->num_planes = format->planesCount;
pix->field = V4L2_FIELD_NONE;
- fromColorSpace(format->colorSpace, *pix);
+ if (format->colorSpace) {
+ fromColorSpace(format->colorSpace, *pix);
+
+ if (caps_.isVideoCapture())
+ pix->flags |= V4L2_PIX_FMT_FLAG_SET_CSC;
+ }
ASSERT(pix->num_planes <= std::size(pix->plane_fmt));
@@ -1010,7 +1035,12 @@ int V4L2VideoDevice::trySetFormatSingleplane(V4L2DeviceFormat *format, bool set)
pix->pixelformat = format->fourcc;
pix->bytesperline = format->planes[0].bpl;
pix->field = V4L2_FIELD_NONE;
- fromColorSpace(format->colorSpace, *pix);
+ if (format->colorSpace) {
+ fromColorSpace(format->colorSpace, *pix);
+
+ if (caps_.isVideoCapture())
+ pix->flags |= V4L2_PIX_FMT_FLAG_SET_CSC;
+ }
ret = ioctl(set ? VIDIOC_S_FMT : VIDIOC_TRY_FMT, &v4l2Format);
if (ret) {
@@ -1441,7 +1471,7 @@ UniqueFD V4L2VideoDevice::exportDmabufFd(unsigned int index,
expbuf.type = bufferType_;
expbuf.index = index;
expbuf.plane = plane;
- expbuf.flags = O_RDWR;
+ expbuf.flags = O_CLOEXEC | O_RDWR;
ret = ioctl(VIDIOC_EXPBUF, &expbuf);
if (ret < 0) {
@@ -1503,6 +1533,9 @@ int V4L2VideoDevice::importBuffers(unsigned int count)
*/
int V4L2VideoDevice::releaseBuffers()
{
+ if (!cache_)
+ return 0;
+
LOG(V4L2, Debug) << "Releasing buffers";
delete cache_;
@@ -1593,6 +1626,11 @@ int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer)
if (V4L2_TYPE_IS_OUTPUT(buf.type)) {
const FrameMetadata &metadata = buffer->metadata();
+ for (const auto &plane : metadata.planes()) {
+ if (!plane.bytesused)
+ LOG(V4L2, Warning) << "byteused == 0 is deprecated";
+ }
+
if (numV4l2Planes != planes.size()) {
/*
* If we have a multi-planar buffer with a V4L2
@@ -1761,12 +1799,14 @@ FrameBuffer *V4L2VideoDevice::dequeueBuffer()
watchdog_.start(std::chrono::duration_cast<std::chrono::milliseconds>(watchdogDuration_));
}
- buffer->metadata_.status = buf.flags & V4L2_BUF_FLAG_ERROR
- ? FrameMetadata::FrameError
- : FrameMetadata::FrameSuccess;
- buffer->metadata_.sequence = buf.sequence;
- buffer->metadata_.timestamp = buf.timestamp.tv_sec * 1000000000ULL
- + buf.timestamp.tv_usec * 1000ULL;
+ FrameMetadata &metadata = buffer->_d()->metadata();
+
+ metadata.status = buf.flags & V4L2_BUF_FLAG_ERROR
+ ? FrameMetadata::FrameError
+ : FrameMetadata::FrameSuccess;
+ metadata.sequence = buf.sequence;
+ metadata.timestamp = buf.timestamp.tv_sec * 1000000000ULL
+ + buf.timestamp.tv_usec * 1000ULL;
if (V4L2_TYPE_IS_OUTPUT(buf.type))
return buffer;
@@ -1777,15 +1817,14 @@ FrameBuffer *V4L2VideoDevice::dequeueBuffer()
*/
if (!firstFrame_) {
if (buf.sequence)
- LOG(V4L2, Warning)
+ LOG(V4L2, Info)
<< "Zero sequence expected for first frame (got "
<< buf.sequence << ")";
firstFrame_ = buf.sequence;
}
- buffer->metadata_.sequence -= firstFrame_.value();
+ metadata.sequence -= firstFrame_.value();
unsigned int numV4l2Planes = multiPlanar ? buf.length : 1;
- FrameMetadata &metadata = buffer->metadata_;
if (numV4l2Planes != buffer->planes().size()) {
/*
@@ -1911,9 +1950,10 @@ int V4L2VideoDevice::streamOff()
/* Send back all queued buffers. */
for (auto it : queuedBuffers_) {
FrameBuffer *buffer = it.second;
+ FrameMetadata &metadata = buffer->_d()->metadata();
cache_->put(it.first);
- buffer->metadata_.status = FrameMetadata::FrameCancelled;
+ metadata.status = FrameMetadata::FrameCancelled;
bufferReady.emit(buffer);
}
@@ -1990,6 +2030,40 @@ V4L2VideoDevice::fromEntityName(const MediaDevice *media,
}
/**
+ * \brief Convert \a PixelFormat to a V4L2PixelFormat supported by the device
+ * \param[in] pixelFormat The PixelFormat to convert
+ *
+ * Convert \a pixelformat to a V4L2 FourCC that is known to be supported by
+ * the video device.
+ *
+ * A V4L2VideoDevice may support different V4L2 pixel formats that map the same
+ * PixelFormat. This is the case of the contiguous and non-contiguous variants
+ * of multiplanar formats, and with the V4L2 MJPEG and JPEG pixel formats.
+ * Converting a PixelFormat to a V4L2PixelFormat may thus have multiple answers.
+ *
+ * This function converts the \a pixelFormat using the list of V4L2 pixel
+ * formats that the V4L2VideoDevice supports. This guarantees that the returned
+ * V4L2PixelFormat will be valid for the device. If multiple matches are still
+ * possible, contiguous variants are preferred. If the \a pixelFormat is not
+ * supported by the device, the function returns an invalid V4L2PixelFormat.
+ *
+ * \return The V4L2PixelFormat corresponding to \a pixelFormat if supported by
+ * the device, or an invalid V4L2PixelFormat otherwise
+ */
+V4L2PixelFormat V4L2VideoDevice::toV4L2PixelFormat(const PixelFormat &pixelFormat) const
+{
+ const std::vector<V4L2PixelFormat> &v4l2PixelFormats =
+ V4L2PixelFormat::fromPixelFormat(pixelFormat);
+
+ for (const V4L2PixelFormat &v4l2Format : v4l2PixelFormats) {
+ if (pixelFormats_.count(v4l2Format))
+ return v4l2Format;
+ }
+
+ return {};
+}
+
+/**
* \class V4L2M2MDevice
* \brief Memory-to-Memory video device
*
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index 5c45e44e..bf21141e 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -31,12 +31,6 @@ namespace {
/* Empty static YamlObject as a safe result for invalid operations */
static const YamlObject empty;
-void setOk(bool *ok, bool result)
-{
- if (ok)
- *ok = result;
-}
-
} /* namespace */
/**
@@ -91,7 +85,6 @@ std::size_t YamlObject::size() const
{
switch (type_) {
case Type::Dictionary:
- return dictionary_.size();
case Type::List:
return list_.size();
default:
@@ -100,232 +93,293 @@ std::size_t YamlObject::size() const
}
/**
- * \fn template<typename T> YamlObject::get<T>(
- * const T &defaultValue, bool *ok) const
+ * \fn template<typename T> YamlObject::get<T>() const
* \brief Parse the YamlObject as a \a T value
- * \param[in] defaultValue The default value when failing to parse
- * \param[out] ok The result of whether the parse succeeded
*
* This function parses the value of the YamlObject as a \a T object, and
* returns the value. If parsing fails (usually because the YamlObject doesn't
- * store a \a T value), the \a defaultValue is returned, and \a ok is set to
- * false. Otherwise, the YamlObject value is returned, and \a ok is set to true.
+ * store a \a T value), std::nullopt is returned.
+ *
+ * \return The YamlObject value, or std::nullopt if parsing failed
+ */
+
+/**
+ * \fn template<typename T> YamlObject::get<T>(const T &defaultValue) const
+ * \brief Parse the YamlObject as a \a T value
+ * \param[in] defaultValue The default value when failing to parse
*
- * The \a ok pointer is optional and can be a nullptr if the caller doesn't
- * need to know if parsing succeeded.
+ * This function parses the value of the YamlObject as a \a T object, and
+ * returns the value. If parsing fails (usually because the YamlObject doesn't
+ * store a \a T value), the \a defaultValue is returned.
*
- * \return Value as a bool type
+ * \return The YamlObject value, or \a defaultValue if parsing failed
*/
#ifndef __DOXYGEN__
template<>
-bool YamlObject::get(const bool &defaultValue, bool *ok) const
+std::optional<bool> YamlObject::get() const
{
- setOk(ok, false);
-
if (type_ != Type::Value)
- return defaultValue;
+ return std::nullopt;
- if (value_ == "true") {
- setOk(ok, true);
+ if (value_ == "true")
return true;
- } else if (value_ == "false") {
- setOk(ok, true);
+ else if (value_ == "false")
return false;
- }
- return defaultValue;
+ return std::nullopt;
}
-template<>
-int16_t YamlObject::get(const int16_t &defaultValue, bool *ok) const
-{
- setOk(ok, false);
-
- if (type_ != Type::Value)
- return defaultValue;
+namespace {
- if (value_ == "")
- return defaultValue;
+bool parseSignedInteger(const std::string &str, long min, long max,
+ long *result)
+{
+ if (str == "")
+ return false;
char *end;
errno = 0;
- int16_t value = std::strtol(value_.c_str(), &end, 10);
+ long value = std::strtol(str.c_str(), &end, 10);
- if ('\0' != *end || errno == ERANGE ||
- value < std::numeric_limits<int16_t>::min() ||
- value > std::numeric_limits<int16_t>::max())
- return defaultValue;
+ if ('\0' != *end || errno == ERANGE || value < min || value > max)
+ return false;
- setOk(ok, true);
- return value;
+ *result = value;
+ return true;
}
-template<>
-uint16_t YamlObject::get(const uint16_t &defaultValue, bool *ok) const
+bool parseUnsignedInteger(const std::string &str, unsigned long max,
+ unsigned long *result)
{
- setOk(ok, false);
-
- if (type_ != Type::Value)
- return defaultValue;
-
- if (value_ == "")
- return defaultValue;
+ if (str == "")
+ return false;
/*
- * libyaml parses all scalar values as strings. When a string has
- * leading spaces before a minus sign, for example " -10", strtoul
- * skips leading spaces, accepts the leading minus sign, and the
- * calculated digits are negated as if by unary minus. Rule it out in
- * case the user gets a large number when the value is negative.
+ * strtoul() accepts strings representing a negative number, in which
+ * case it negates the converted value. We don't want to silently accept
+ * negative values and return a large positive number, so check for a
+ * minus sign (after optional whitespace) and return an error.
*/
- std::size_t found = value_.find_first_not_of(" \t");
- if (found != std::string::npos && value_[found] == '-')
- return defaultValue;
+ std::size_t found = str.find_first_not_of(" \t");
+ if (found != std::string::npos && str[found] == '-')
+ return false;
char *end;
errno = 0;
- uint16_t value = std::strtoul(value_.c_str(), &end, 10);
+ unsigned long value = std::strtoul(str.c_str(), &end, 10);
- if ('\0' != *end || errno == ERANGE ||
- value < std::numeric_limits<uint16_t>::min() ||
- value > std::numeric_limits<uint16_t>::max())
- return defaultValue;
+ if ('\0' != *end || errno == ERANGE || value > max)
+ return false;
- setOk(ok, true);
- return value;
+ *result = value;
+ return true;
}
+} /* namespace */
+
template<>
-int32_t YamlObject::get(const int32_t &defaultValue, bool *ok) const
+std::optional<int8_t> YamlObject::get() const
{
- setOk(ok, false);
-
if (type_ != Type::Value)
- return defaultValue;
+ return std::nullopt;
- if (value_ == "")
- return defaultValue;
+ long value;
- char *end;
+ if (!parseSignedInteger(value_, std::numeric_limits<int8_t>::min(),
+ std::numeric_limits<int8_t>::max(), &value))
+ return std::nullopt;
- errno = 0;
- long value = std::strtol(value_.c_str(), &end, 10);
+ return value;
+}
+
+template<>
+std::optional<uint8_t> YamlObject::get() const
+{
+ if (type_ != Type::Value)
+ return std::nullopt;
- if ('\0' != *end || errno == ERANGE ||
- value < std::numeric_limits<int32_t>::min() ||
- value > std::numeric_limits<int32_t>::max())
- return defaultValue;
+ unsigned long value;
+
+ if (!parseUnsignedInteger(value_, std::numeric_limits<uint8_t>::max(),
+ &value))
+ return std::nullopt;
- setOk(ok, true);
return value;
}
template<>
-uint32_t YamlObject::get(const uint32_t &defaultValue, bool *ok) const
+std::optional<int16_t> YamlObject::get() const
{
- setOk(ok, false);
+ if (type_ != Type::Value)
+ return std::nullopt;
+
+ long value;
+
+ if (!parseSignedInteger(value_, std::numeric_limits<int16_t>::min(),
+ std::numeric_limits<int16_t>::max(), &value))
+ return std::nullopt;
+
+ return value;
+}
+template<>
+std::optional<uint16_t> YamlObject::get() const
+{
if (type_ != Type::Value)
- return defaultValue;
+ return std::nullopt;
- if (value_ == "")
- return defaultValue;
+ unsigned long value;
- /*
- * libyaml parses all scalar values as strings. When a string has
- * leading spaces before a minus sign, for example " -10", strtoul
- * skips leading spaces, accepts the leading minus sign, and the
- * calculated digits are negated as if by unary minus. Rule it out in
- * case the user gets a large number when the value is negative.
- */
- std::size_t found = value_.find_first_not_of(" \t");
- if (found != std::string::npos && value_[found] == '-')
- return defaultValue;
+ if (!parseUnsignedInteger(value_, std::numeric_limits<uint16_t>::max(),
+ &value))
+ return std::nullopt;
- char *end;
+ return value;
+}
- errno = 0;
- unsigned long value = std::strtoul(value_.c_str(), &end, 10);
+template<>
+std::optional<int32_t> YamlObject::get() const
+{
+ if (type_ != Type::Value)
+ return std::nullopt;
+
+ long value;
- if ('\0' != *end || errno == ERANGE ||
- value < std::numeric_limits<uint32_t>::min() ||
- value > std::numeric_limits<uint32_t>::max())
- return defaultValue;
+ if (!parseSignedInteger(value_, std::numeric_limits<int32_t>::min(),
+ std::numeric_limits<int32_t>::max(), &value))
+ return std::nullopt;
- setOk(ok, true);
return value;
}
template<>
-double YamlObject::get(const double &defaultValue, bool *ok) const
+std::optional<uint32_t> YamlObject::get() const
{
- setOk(ok, false);
+ if (type_ != Type::Value)
+ return std::nullopt;
+
+ unsigned long value;
+ if (!parseUnsignedInteger(value_, std::numeric_limits<uint32_t>::max(),
+ &value))
+ return std::nullopt;
+
+ return value;
+}
+
+template<>
+std::optional<double> YamlObject::get() const
+{
if (type_ != Type::Value)
- return defaultValue;
+ return std::nullopt;
if (value_ == "")
- return defaultValue;
+ return std::nullopt;
char *end;
errno = 0;
- double value = std::strtod(value_.c_str(), &end);
+ double value = utils::strtod(value_.c_str(), &end);
if ('\0' != *end || errno == ERANGE)
- return defaultValue;
+ return std::nullopt;
- setOk(ok, true);
return value;
}
template<>
-std::string YamlObject::get(const std::string &defaultValue, bool *ok) const
+std::optional<std::string> YamlObject::get() const
{
- setOk(ok, false);
-
if (type_ != Type::Value)
- return defaultValue;
+ return std::nullopt;
- setOk(ok, true);
return value_;
}
template<>
-Size YamlObject::get(const Size &defaultValue, bool *ok) const
+std::optional<Size> YamlObject::get() const
{
- setOk(ok, false);
-
if (type_ != Type::List)
- return defaultValue;
+ return std::nullopt;
if (list_.size() != 2)
- return defaultValue;
+ return std::nullopt;
- /*
- * Add a local variable to validate each dimension in case
- * that ok == nullptr.
- */
- bool valid;
- uint32_t width = list_[0]->get<uint32_t>(0, &valid);
- if (!valid)
- return defaultValue;
+ auto width = list_[0].value->get<uint32_t>();
+ if (!width)
+ return std::nullopt;
- uint32_t height = list_[1]->get<uint32_t>(0, &valid);
- if (!valid)
- return defaultValue;
+ auto height = list_[1].value->get<uint32_t>();
+ if (!height)
+ return std::nullopt;
- setOk(ok, true);
- return Size(width, height);
+ return Size(*width, *height);
}
#endif /* __DOXYGEN__ */
/**
+ * \fn template<typename T> YamlObject::getList<T>() const
+ * \brief Parse the YamlObject as a list of \a T
+ *
+ * This function parses the value of the YamlObject as a list of \a T objects,
+ * and returns the value as a \a std::vector<T>. If parsing fails, std::nullopt
+ * is returned.
+ *
+ * \return The YamlObject value as a std::vector<T>, or std::nullopt if parsing
+ * failed
+ */
+
+#ifndef __DOXYGEN__
+
+template<typename T,
+ std::enable_if_t<
+ std::is_same_v<bool, T> ||
+ std::is_same_v<double, T> ||
+ std::is_same_v<int8_t, T> ||
+ std::is_same_v<uint8_t, T> ||
+ std::is_same_v<int16_t, T> ||
+ std::is_same_v<uint16_t, T> ||
+ std::is_same_v<int32_t, T> ||
+ std::is_same_v<uint32_t, T> ||
+ std::is_same_v<std::string, T> ||
+ std::is_same_v<Size, T>> *>
+std::optional<std::vector<T>> YamlObject::getList() const
+{
+ if (type_ != Type::List)
+ return std::nullopt;
+
+ std::vector<T> values;
+ values.reserve(list_.size());
+
+ for (const YamlObject &entry : asList()) {
+ const auto value = entry.get<T>();
+ if (!value)
+ return std::nullopt;
+ values.emplace_back(*value);
+ }
+
+ return values;
+}
+
+template std::optional<std::vector<bool>> YamlObject::getList<bool>() const;
+template std::optional<std::vector<double>> YamlObject::getList<double>() const;
+template std::optional<std::vector<int8_t>> YamlObject::getList<int8_t>() const;
+template std::optional<std::vector<uint8_t>> YamlObject::getList<uint8_t>() const;
+template std::optional<std::vector<int16_t>> YamlObject::getList<int16_t>() const;
+template std::optional<std::vector<uint16_t>> YamlObject::getList<uint16_t>() const;
+template std::optional<std::vector<int32_t>> YamlObject::getList<int32_t>() const;
+template std::optional<std::vector<uint32_t>> YamlObject::getList<uint32_t>() const;
+template std::optional<std::vector<std::string>> YamlObject::getList<std::string>() const;
+template std::optional<std::vector<Size>> YamlObject::getList<Size>() const;
+
+#endif /* __DOXYGEN__ */
+
+/**
* \fn YamlObject::asDict() const
* \brief Wrap a dictionary YamlObject in an adapter that exposes iterators
*
@@ -379,7 +433,7 @@ const YamlObject &YamlObject::operator[](std::size_t index) const
if (type_ != Type::List || index >= size())
return empty;
- return *list_[index];
+ return *list_[index].value;
}
/**
@@ -395,7 +449,7 @@ const YamlObject &YamlObject::operator[](std::size_t index) const
*/
bool YamlObject::contains(const std::string &key) const
{
- if (dictionary_.find(key) == dictionary_.end())
+ if (dictionary_.find(std::ref(key)) == dictionary_.end())
return false;
return true;
@@ -622,7 +676,7 @@ int YamlParserContext::parseDictionaryOrList(YamlObject::Type type,
* Add a safety counter to make sure we don't loop indefinitely in case
* the YAML file is malformed.
*/
- for (unsigned int sentinel = 1000; sentinel; sentinel--) {
+ for (unsigned int sentinel = 2000; sentinel; sentinel--) {
auto evt = nextEvent();
if (!evt)
return -EINVAL;
@@ -667,16 +721,16 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
yamlObject.type_ = YamlObject::Type::List;
auto &list = yamlObject.list_;
auto handler = [this, &list](EventPtr evt) {
- list.emplace_back(new YamlObject());
- return parseNextYamlObject(*list.back(), std::move(evt));
+ list.emplace_back(std::string{}, std::make_unique<YamlObject>());
+ return parseNextYamlObject(*list.back().value, std::move(evt));
};
return parseDictionaryOrList(YamlObject::Type::List, handler);
}
case YAML_MAPPING_START_EVENT: {
yamlObject.type_ = YamlObject::Type::Dictionary;
- auto &dictionary = yamlObject.dictionary_;
- auto handler = [this, &dictionary](EventPtr evtKey) {
+ auto &list = yamlObject.list_;
+ auto handler = [this, &list](EventPtr evtKey) {
/* Parse key */
if (evtKey->type != YAML_SCALAR_EVENT) {
LOG(YamlParser, Error) << "Expect key at line: "
@@ -694,10 +748,19 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
if (!evtValue)
return -EINVAL;
- auto elem = dictionary.emplace(key, std::make_unique<YamlObject>());
- return parseNextYamlObject(*elem.first->second.get(), std::move(evtValue));
+ auto &elem = list.emplace_back(std::move(key),
+ std::make_unique<YamlObject>());
+ return parseNextYamlObject(*elem.value, std::move(evtValue));
};
- return parseDictionaryOrList(YamlObject::Type::Dictionary, handler);
+ int ret = parseDictionaryOrList(YamlObject::Type::Dictionary, handler);
+ if (ret)
+ return ret;
+
+ auto &dictionary = yamlObject.dictionary_;
+ for (const auto &elem : list)
+ dictionary.emplace(elem.key, elem.value.get());
+
+ return 0;
}
default:
@@ -753,6 +816,9 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
* The YamlParser::parse() function takes an open FILE, parses its contents, and
* returns a pointer to a YamlObject corresponding to the root node of the YAML
* document.
+ *
+ * The parser preserves the order of items in the YAML file, for both lists and
+ * dictionaries.
*/
/**
@@ -775,7 +841,9 @@ std::unique_ptr<YamlObject> YamlParser::parse(File &file)
std::unique_ptr<YamlObject> root(new YamlObject());
if (context.parseContent(*root)) {
- LOG(YamlParser, Error) << "Failed to parse YAML content";
+ LOG(YamlParser, Error)
+ << "Failed to parse YAML content from "
+ << file.fileName();
return nullptr;
}
diff --git a/src/meson.build b/src/meson.build
index 34663a6f..165a77bb 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,6 +3,7 @@
# Cache system paths
libcamera_datadir = get_option('datadir') / 'libcamera'
libcamera_libdir = get_option('libdir') / 'libcamera'
+libcamera_libexecdir = get_option('libexecdir') / 'libcamera'
libcamera_sysconfdir = get_option('sysconfdir') / 'libcamera'
config_h.set('LIBCAMERA_DATA_DIR', '"' + get_option('prefix') / libcamera_datadir + '"')
@@ -14,7 +15,7 @@ summary({
}, section : 'Paths')
# Module Signing
-openssl = find_program('openssl', required : true)
+openssl = find_program('openssl', required : false)
if openssl.found()
ipa_priv_key = custom_target('ipa-priv-key',
output : ['ipa-priv-key.pem'],
@@ -22,6 +23,7 @@ if openssl.found()
config_h.set('HAVE_IPA_PUBKEY', 1)
ipa_sign_module = true
else
+ warning('openssl not found, all IPA modules will be isolated')
ipa_sign_module = false
endif
@@ -31,10 +33,7 @@ subdir('libcamera')
subdir('android')
subdir('ipa')
-subdir('lc-compliance')
-
-subdir('cam')
-subdir('qcam')
+subdir('apps')
subdir('gstreamer')
subdir('py')
diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
index 2ae89fa8..ff4b7f66 100755
--- a/src/py/cam/cam.py
+++ b/src/py/cam/cam.py
@@ -3,9 +3,6 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
-# \todo Convert ctx and state dicts to proper classes, and move relevant
-# functions to those classes.
-
from typing import Any
import argparse
import binascii
@@ -26,6 +23,7 @@ class CameraContext:
opt_metadata: bool
opt_save_frames: bool
opt_capture: int
+ opt_orientation: str
stream_names: dict[libcam.Stream, str]
streams: list[libcam.Stream]
@@ -149,6 +147,21 @@ class CameraContext:
if 'pixelformat' in stream_opts:
stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat'])
+ if self.opt_orientation is not None:
+ orientation_map = {
+ 'rot0': libcam.Orientation.Rotate0,
+ 'rot180': libcam.Orientation.Rotate180,
+ 'mirror': libcam.Orientation.Rotate0Mirror,
+ 'flip': libcam.Orientation.Rotate180Mirror,
+ }
+
+ orient = orientation_map.get(self.opt_orientation, None)
+ if orient is None:
+ print('Bad orientation: ', self.opt_orientation)
+ sys.exit(-1)
+
+ camconfig.orientation = orient
+
stat = camconfig.validate()
if stat == libcam.CameraConfiguration.Status.Invalid:
@@ -161,9 +174,7 @@ class CameraContext:
print('Camera configuration adjusted')
- r = self.camera.configure(camconfig)
- if r != 0:
- raise Exception('Configure failed')
+ self.camera.configure(camconfig)
self.stream_names = {}
self.streams = []
@@ -178,12 +189,7 @@ class CameraContext:
allocator = libcam.FrameBufferAllocator(self.camera)
for stream in self.streams:
- ret = allocator.allocate(stream)
- if ret < 0:
- print('Cannot allocate buffers')
- exit(-1)
-
- allocated = len(allocator.buffers(stream))
+ allocated = allocator.allocate(stream)
print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated))
@@ -208,10 +214,7 @@ class CameraContext:
buffers = self.allocator.buffers(stream)
buffer = buffers[buf_num]
- ret = request.add_buffer(stream, buffer)
- if ret < 0:
- print('Can not set buffer for request')
- exit(-1)
+ request.add_buffer(stream, buffer)
requests.append(request)
@@ -269,6 +272,11 @@ class CaptureState:
ctx.last = ts
ctx.fps = fps
+ if ctx.opt_metadata:
+ reqmeta = req.metadata
+ for ctrl, val in reqmeta.items():
+ print(f'\t{ctrl} = {val}')
+
for stream, fb in buffers.items():
stream_name = ctx.stream_names[stream]
@@ -287,11 +295,6 @@ class CaptureState:
'/'.join([str(p.bytes_used) for p in meta.planes]),
crcs))
- if ctx.opt_metadata:
- reqmeta = req.metadata
- for ctrl, val in reqmeta.items():
- print(f'\t{ctrl} = {val}')
-
if ctx.opt_save_frames:
with libcamera.utils.MappedFrameBuffer(fb) as mfb:
filename = 'frame-{}-{}-{}.data'.format(ctx.id, stream_name, ctx.reqs_completed)
@@ -398,6 +401,7 @@ def main():
parser.add_argument('--metadata', nargs=0, type=bool, action=CustomAction, help='Print the metadata for completed requests')
parser.add_argument('--strict-formats', type=bool, nargs=0, action=CustomAction, help='Do not allow requested stream format(s) to be adjusted')
parser.add_argument('-s', '--stream', nargs='+', action=CustomAction)
+ parser.add_argument('-o', '--orientation', help='Desired image orientation (rot0, rot180, mirror, flip)')
args = parser.parse_args()
cm = libcam.CameraManager.singleton()
@@ -421,6 +425,7 @@ def main():
ctx.opt_metadata = args.metadata.get(cam_idx, False)
ctx.opt_strict_formats = args.strict_formats.get(cam_idx, False)
ctx.opt_stream = args.stream.get(cam_idx, ['role=viewfinder'])
+ ctx.opt_orientation = args.orientation
contexts.append(ctx)
for ctx in contexts:
@@ -434,7 +439,10 @@ def main():
if args.info:
ctx.do_cmd_info()
- if args.capture:
+ # Filter out capture contexts which are not marked for capture
+ contexts = [ctx for ctx in contexts if ctx.opt_capture > 0]
+
+ if contexts:
state = CaptureState(cm, contexts)
if args.renderer == 'null':
diff --git a/src/py/cam/helpers.py b/src/py/cam/helpers.py
index 6b32a134..2d906667 100644
--- a/src/py/cam/helpers.py
+++ b/src/py/cam/helpers.py
@@ -117,14 +117,12 @@ def to_rgb(fmt, size, data):
bayer_pattern = fmt[1:5]
bitspp = int(fmt[5:])
- # \todo shifting leaves the lowest bits 0
if bitspp == 8:
data = data.reshape((h, w))
- data = data.astype(np.uint16) << 8
+ data = data.astype(np.uint16)
elif bitspp in [10, 12]:
data = data.view(np.uint16)
data = data.reshape((h, w))
- data = data << (16 - bitspp)
else:
raise Exception('Bad bitspp:' + str(bitspp))
@@ -145,7 +143,7 @@ def to_rgb(fmt, size, data):
b0 = (idx % 2, idx // 2)
rgb = demosaic(data, r0, g0, g1, b0)
- rgb = (rgb >> 8).astype(np.uint8)
+ rgb = (rgb >> (bitspp - 8)).astype(np.uint8)
else:
rgb = None
diff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py
index 2b81bb65..1cd1019d 100755
--- a/src/py/examples/simple-cam.py
+++ b/src/py/examples/simple-cam.py
@@ -19,8 +19,9 @@ TIMEOUT_SEC = 3
def handle_camera_event(cm):
- # cm.get_ready_requests() will not block here, as we know there is an event
- # to read.
+ # cm.get_ready_requests() returns the ready requests, which in our case
+ # should almost always return a single Request, but in some cases there
+ # could be multiple or none.
reqs = cm.get_ready_requests()
@@ -258,12 +259,7 @@ def main():
allocator = libcam.FrameBufferAllocator(camera)
for cfg in config:
- ret = allocator.allocate(cfg.stream)
- if ret < 0:
- print('Can\'t allocate buffers')
- return -1
-
- allocated = len(allocator.buffers(cfg.stream))
+ allocated = allocator.allocate(cfg.stream)
print(f'Allocated {allocated} buffers for stream')
# --------------------------------------------------------------------
@@ -288,15 +284,9 @@ def main():
requests = []
for i in range(len(buffers)):
request = camera.create_request()
- if not request:
- print('Can\'t create request')
- return -1
buffer = buffers[i]
- ret = request.add_buffer(stream, buffer)
- if ret < 0:
- print('Can\'t set buffer for request')
- return -1
+ request.add_buffer(stream, buffer)
# Controls can be added to a request on a per frame basis.
request.set_control(libcam.controls.Brightness, 0.5)
diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py
index a6a9b33e..4b85408f 100755
--- a/src/py/examples/simple-capture.py
+++ b/src/py/examples/simple-capture.py
@@ -14,6 +14,7 @@
import argparse
import libcamera as libcam
+import selectors
import sys
# Number of frames to capture
@@ -42,8 +43,7 @@ def main():
# Acquire the camera for our use
- ret = cam.acquire()
- assert ret == 0
+ cam.acquire()
# Configure the camera
@@ -59,8 +59,7 @@ def main():
w, h = [int(v) for v in args.size.split('x')]
stream_config.size = libcam.Size(w, h)
- ret = cam.configure(cam_config)
- assert ret == 0
+ cam.configure(cam_config)
print(f'Capturing {TOTAL_FRAMES} frames with {stream_config}')
@@ -82,15 +81,13 @@ def main():
req = cam.create_request(i)
buffer = allocator.buffers(stream)[i]
- ret = req.add_buffer(stream, buffer)
- assert ret == 0
+ req.add_buffer(stream, buffer)
reqs.append(req)
# Start the camera
- ret = cam.start()
- assert ret == 0
+ cam.start()
# frames_queued and frames_done track the number of frames queued and done
@@ -100,18 +97,24 @@ def main():
# Queue the requests to the camera
for req in reqs:
- ret = cam.queue_request(req)
- assert ret == 0
+ cam.queue_request(req)
frames_queued += 1
# The main loop. Wait for the queued Requests to complete, process them,
# and re-queue them again.
+ sel = selectors.DefaultSelector()
+ sel.register(cm.event_fd, selectors.EVENT_READ)
+
while frames_done < TOTAL_FRAMES:
- # cm.get_ready_requests() blocks until there is an event and returns
- # all the ready requests. Here we should almost always get a single
+ # cm.get_ready_requests() does not block, so we use a Selector to wait
+ # for a camera event. Here we should almost always get a single
# Request, but in some cases there could be multiple or none.
+ events = sel.select()
+ if not events:
+ continue
+
reqs = cm.get_ready_requests()
for req in reqs:
@@ -147,13 +150,11 @@ def main():
# Stop the camera
- ret = cam.stop()
- assert ret == 0
+ cam.stop()
# Release the camera
- ret = cam.release()
- assert ret == 0
+ cam.release()
return 0
diff --git a/src/py/examples/simple-continuous-capture.py b/src/py/examples/simple-continuous-capture.py
index fe78a2dd..e1cb931e 100755
--- a/src/py/examples/simple-continuous-capture.py
+++ b/src/py/examples/simple-continuous-capture.py
@@ -28,8 +28,7 @@ class CameraCaptureContext:
# Acquire the camera for our use
- ret = cam.acquire()
- assert ret == 0
+ cam.acquire()
# Configure the camera
@@ -37,8 +36,7 @@ class CameraCaptureContext:
stream_config = cam_config.at(0)
- ret = cam.configure(cam_config)
- assert ret == 0
+ cam.configure(cam_config)
stream = stream_config.stream
@@ -62,8 +60,7 @@ class CameraCaptureContext:
req = cam.create_request(idx)
buffer = allocator.buffers(stream)[i]
- ret = req.add_buffer(stream, buffer)
- assert ret == 0
+ req.add_buffer(stream, buffer)
self.reqs.append(req)
@@ -73,13 +70,11 @@ class CameraCaptureContext:
def uninit_camera(self):
# Stop the camera
- ret = self.cam.stop()
- assert ret == 0
+ self.cam.stop()
# Release the camera
- ret = self.cam.release()
- assert ret == 0
+ self.cam.release()
# A container class for our state
@@ -88,8 +83,9 @@ class CaptureContext:
camera_contexts: list[CameraCaptureContext] = []
def handle_camera_event(self):
- # cm.get_ready_requests() will not block here, as we know there is an event
- # to read.
+ # cm.get_ready_requests() returns the ready requests, which in our case
+ # should almost always return a single Request, but in some cases there
+ # could be multiple or none.
reqs = self.cm.get_ready_requests()
@@ -144,8 +140,7 @@ class CaptureContext:
for cam_ctx in self.camera_contexts:
for req in cam_ctx.reqs:
- ret = cam_ctx.cam.queue_request(req)
- assert ret == 0
+ cam_ctx.cam.queue_request(req)
# Use Selector to wait for events from the camera and from the keyboard
@@ -176,8 +171,7 @@ def main():
# Start the cameras
for cam_ctx in ctx.camera_contexts:
- ret = cam_ctx.cam.start()
- assert ret == 0
+ cam_ctx.cam.start()
ctx.capture()
diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py
index 99f3bbcf..8efbf95b 100755
--- a/src/py/libcamera/gen-py-controls.py
+++ b/src/py/libcamera/gen-py-controls.py
@@ -24,48 +24,57 @@ def find_common_prefix(strings):
def generate_py(controls, mode):
out = ''
- for ctrl in controls:
- name, ctrl = ctrl.popitem()
-
- if ctrl.get('draft'):
- ns = 'libcamera::{}::draft::'.format(mode)
- container = 'draft'
- else:
- ns = 'libcamera::{}::'.format(mode)
- container = 'controls'
+ vendors_class_def = []
+ vendor_defs = []
+ vendors = []
+ for vendor, ctrl_list in controls.items():
+ for ctrls in ctrl_list:
+ name, ctrl = ctrls.popitem()
+
+ if vendor not in vendors and vendor != 'libcamera':
+ vendor_mode_str = f'{vendor.capitalize()}{mode.capitalize()}'
+ vendors_class_def.append('class Py{}\n{{\n}};\n'.format(vendor_mode_str))
+ vendor_defs.append('\tauto {} = py::class_<Py{}>(controls, \"{}\");'.format(vendor, vendor_mode_str, vendor))
+ vendors.append(vendor)
+
+ if vendor != 'libcamera':
+ ns = 'libcamera::{}::{}::'.format(mode, vendor)
+ container = vendor
+ else:
+ ns = 'libcamera::{}::'.format(mode)
+ container = 'controls'
- out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
+ out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
- enum = ctrl.get('enum')
- if not enum:
- continue
+ enum = ctrl.get('enum')
+ if not enum:
+ continue
- cpp_enum = name + 'Enum'
+ cpp_enum = name + 'Enum'
- out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
+ out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
- if mode == 'controls':
- # Adjustments for controls
- if name == 'LensShadingMapMode':
- prefix = 'LensShadingMapMode'
- elif name == 'SceneFlicker':
- # If we strip the prefix, we would get '50Hz', which is illegal name
- prefix = ''
+ if mode == 'controls':
+ # Adjustments for controls
+ if name == 'LensShadingMapMode':
+ prefix = 'LensShadingMapMode'
+ else:
+ prefix = find_common_prefix([e['name'] for e in enum])
else:
+ # Adjustments for properties
prefix = find_common_prefix([e['name'] for e in enum])
- else:
- # Adjustments for properties
- prefix = find_common_prefix([e['name'] for e in enum])
- for entry in enum:
- cpp_enum = entry['name']
- py_enum = entry['name'][len(prefix):]
+ for entry in enum:
+ cpp_enum = entry['name']
+ py_enum = entry['name'][len(prefix):]
- out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
+ out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
- out += '\t;\n\n'
+ out += '\t;\n\n'
- return {'controls': out}
+ return {'controls': out,
+ 'vendors_class_def': '\n'.join(vendors_class_def),
+ 'vendors_defs': '\n'.join(vendor_defs)}
def fill_template(template, data):
@@ -78,22 +87,25 @@ def fill_template(template, data):
def main(argv):
# Parse command line arguments
parser = argparse.ArgumentParser()
- parser.add_argument('-o', dest='output', metavar='file', type=str,
+ parser.add_argument('--mode', '-m', type=str, required=True,
+ help='Mode is either "controls" or "properties"')
+ parser.add_argument('--output', '-o', metavar='file', type=str,
help='Output file name. Defaults to standard output if not specified.')
- parser.add_argument('input', type=str,
- help='Input file name.')
- parser.add_argument('template', type=str,
+ parser.add_argument('--template', '-t', type=str, required=True,
help='Template file name.')
- parser.add_argument('--mode', type=str, required=True,
- help='Mode is either "controls" or "properties"')
+ parser.add_argument('input', type=str, nargs='+',
+ help='Input file name.')
args = parser.parse_args(argv[1:])
if args.mode not in ['controls', 'properties']:
print(f'Invalid mode option "{args.mode}"', file=sys.stderr)
return -1
- data = open(args.input, 'rb').read()
- controls = yaml.safe_load(data)['controls']
+ controls = {}
+ for input in args.input:
+ data = open(input, 'rb').read()
+ vendor = yaml.safe_load(data)['vendor']
+ controls[vendor] = yaml.safe_load(data)['controls']
data = generate_py(controls, args.mode)
diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build
index eb884538..4807ca7d 100644
--- a/src/py/libcamera/meson.build
+++ b/src/py/libcamera/meson.build
@@ -7,42 +7,56 @@ if not py3_dep.found()
subdir_done()
endif
-pycamera_enabled = true
+pybind11_dep = dependency('pybind11', required : get_option('pycamera'))
+
+if not pybind11_dep.found()
+ pycamera_enabled = false
+ subdir_done()
+endif
-pybind11_proj = subproject('pybind11')
-pybind11_dep = pybind11_proj.get_variable('pybind11_dep')
+pycamera_enabled = true
pycamera_sources = files([
+ 'py_camera_manager.cpp',
+ 'py_color_space.cpp',
'py_enums.cpp',
'py_geometry.cpp',
+ 'py_helpers.cpp',
'py_main.cpp',
+ 'py_transform.cpp',
])
# Generate controls
-gen_py_controls_input_files = files([
- '../../libcamera/control_ids.yaml',
- 'py_controls_generated.cpp.in',
-])
+gen_py_controls_input_files = []
+gen_py_controls_template = files('py_controls_generated.cpp.in')
gen_py_controls = files('gen-py-controls.py')
+foreach file : controls_files
+ gen_py_controls_input_files += files('../../libcamera/' + file)
+endforeach
+
pycamera_sources += custom_target('py_gen_controls',
input : gen_py_controls_input_files,
output : ['py_controls_generated.cpp'],
- command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', '@INPUT@'])
+ command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@',
+ '-t', gen_py_controls_template, '@INPUT@'])
# Generate properties
-gen_py_property_enums_input_files = files([
- '../../libcamera/property_ids.yaml',
- 'py_properties_generated.cpp.in',
-])
+gen_py_property_enums_input_files = []
+gen_py_properties_template = files('py_properties_generated.cpp.in')
+
+foreach file : properties_files
+ gen_py_property_enums_input_files += files('../../libcamera/' + file)
+endforeach
pycamera_sources += custom_target('py_gen_properties',
input : gen_py_property_enums_input_files,
output : ['py_properties_generated.cpp'],
- command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', '@INPUT@'])
+ command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@',
+ '-t', gen_py_properties_template, '@INPUT@'])
# Generate formats
@@ -59,7 +73,7 @@ pycamera_sources += custom_target('py_gen_formats',
command : [gen_py_formats, '-o', '@OUTPUT@', '@INPUT@'])
pycamera_deps = [
- libcamera_public,
+ libcamera_private,
py3_dep,
pybind11_dep,
]
@@ -68,7 +82,6 @@ pycamera_args = [
'-fvisibility=hidden',
'-Wno-shadow',
'-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT',
- '-DLIBCAMERA_BASE_PRIVATE',
]
destdir = get_option('libdir') / ('python' + py3_dep.version()) / 'site-packages' / 'libcamera'
@@ -77,6 +90,7 @@ pycamera = shared_module('_libcamera',
pycamera_sources,
install : true,
install_dir : destdir,
+ install_tag : 'python-runtime',
name_prefix : '',
dependencies : pycamera_deps,
cpp_args : pycamera_args)
@@ -86,13 +100,15 @@ pycamera = shared_module('_libcamera',
run_command('ln', '-fsrT', files('__init__.py'),
meson.current_build_dir() / '__init__.py',
- check: true)
+ check : true)
run_command('ln', '-fsrT', meson.current_source_dir() / 'utils',
meson.current_build_dir() / 'utils',
- check: true)
+ check : true)
-install_data(['__init__.py'], install_dir : destdir)
+install_data(['__init__.py'],
+ install_dir : destdir,
+ install_tag : 'python-runtime')
# \todo Generate stubs when building. See https://peps.python.org/pep-0484/#stub-files
# Note: Depends on pybind11-stubgen. To generate pylibcamera stubs:
diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp
new file mode 100644
index 00000000..9ccb7aad
--- /dev/null
+++ b/src/py/libcamera/py_camera_manager.cpp
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include "py_camera_manager.h"
+
+#include <errno.h>
+#include <memory>
+#include <sys/eventfd.h>
+#include <system_error>
+#include <unistd.h>
+#include <vector>
+
+#include "py_main.h"
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+PyCameraManager::PyCameraManager()
+{
+ LOG(Python, Debug) << "PyCameraManager()";
+
+ cameraManager_ = std::make_unique<CameraManager>();
+
+ int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+ if (fd == -1)
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to create eventfd");
+
+ eventFd_ = UniqueFD(fd);
+
+ int ret = cameraManager_->start();
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to start CameraManager");
+}
+
+PyCameraManager::~PyCameraManager()
+{
+ LOG(Python, Debug) << "~PyCameraManager()";
+}
+
+py::list PyCameraManager::cameras()
+{
+ /*
+ * Create a list of Cameras, where each camera has a keep-alive to
+ * CameraManager.
+ */
+ py::list l;
+
+ for (auto &camera : cameraManager_->cameras()) {
+ py::object py_cm = py::cast(this);
+ py::object py_cam = py::cast(camera);
+ py::detail::keep_alive_impl(py_cam, py_cm);
+ l.append(py_cam);
+ }
+
+ return l;
+}
+
+std::vector<py::object> PyCameraManager::getReadyRequests()
+{
+ int ret = readFd();
+
+ if (ret == -EAGAIN)
+ return std::vector<py::object>();
+
+ if (ret != 0)
+ throw std::system_error(-ret, std::generic_category());
+
+ std::vector<py::object> py_reqs;
+
+ for (Request *request : getCompletedRequests()) {
+ py::object o = py::cast(request);
+ /* Decrease the ref increased in Camera.queue_request() */
+ o.dec_ref();
+ py_reqs.push_back(o);
+ }
+
+ return py_reqs;
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleRequestCompleted(Request *req)
+{
+ pushRequest(req);
+ writeFd();
+}
+
+void PyCameraManager::writeFd()
+{
+ uint64_t v = 1;
+
+ size_t s = write(eventFd_.get(), &v, 8);
+ /*
+ * We should never fail, and have no simple means to manage the error,
+ * so let's log a fatal error.
+ */
+ if (s != 8)
+ LOG(Python, Fatal) << "Unable to write to eventfd";
+}
+
+int PyCameraManager::readFd()
+{
+ uint8_t buf[8];
+
+ ssize_t ret = read(eventFd_.get(), buf, 8);
+
+ if (ret == 8)
+ return 0;
+ else if (ret < 0)
+ return -errno;
+ else
+ return -EIO;
+}
+
+void PyCameraManager::pushRequest(Request *req)
+{
+ MutexLocker guard(completedRequestsMutex_);
+ completedRequests_.push_back(req);
+}
+
+std::vector<Request *> PyCameraManager::getCompletedRequests()
+{
+ std::vector<Request *> v;
+ MutexLocker guard(completedRequestsMutex_);
+ swap(v, completedRequests_);
+ return v;
+}
diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h
new file mode 100644
index 00000000..3574db23
--- /dev/null
+++ b/src/py/libcamera/py_camera_manager.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#pragma once
+
+#include <libcamera/base/mutex.h>
+
+#include <libcamera/libcamera.h>
+
+#include <pybind11/pybind11.h>
+
+using namespace libcamera;
+
+class PyCameraManager
+{
+public:
+ PyCameraManager();
+ ~PyCameraManager();
+
+ pybind11::list cameras();
+ std::shared_ptr<Camera> get(const std::string &name) { return cameraManager_->get(name); }
+
+ static const std::string &version() { return CameraManager::version(); }
+
+ int eventFd() const { return eventFd_.get(); }
+
+ std::vector<pybind11::object> getReadyRequests();
+
+ void handleRequestCompleted(Request *req);
+
+private:
+ std::unique_ptr<CameraManager> cameraManager_;
+
+ UniqueFD eventFd_;
+ libcamera::Mutex completedRequestsMutex_;
+ std::vector<Request *> completedRequests_
+ LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_);
+
+ void writeFd();
+ int readFd();
+ void pushRequest(Request *req);
+ std::vector<Request *> getCompletedRequests();
+};
diff --git a/src/py/libcamera/py_color_space.cpp b/src/py/libcamera/py_color_space.cpp
new file mode 100644
index 00000000..5201121a
--- /dev/null
+++ b/src/py/libcamera/py_color_space.cpp
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ *
+ * Python bindings - Color Space classes
+ */
+
+#include <libcamera/color_space.h>
+#include <libcamera/libcamera.h>
+
+#include <pybind11/operators.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+void init_py_color_space(py::module &m)
+{
+ auto pyColorSpace = py::class_<ColorSpace>(m, "ColorSpace");
+ auto pyColorSpacePrimaries = py::enum_<ColorSpace::Primaries>(pyColorSpace, "Primaries");
+ auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
+ auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
+ auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
+
+ pyColorSpace
+ .def(py::init([](ColorSpace::Primaries primaries,
+ ColorSpace::TransferFunction transferFunction,
+ ColorSpace::YcbcrEncoding ycbcrEncoding,
+ ColorSpace::Range range) {
+ return ColorSpace(primaries, transferFunction, ycbcrEncoding, range);
+ }), py::arg("primaries"), py::arg("transferFunction"),
+ py::arg("ycbcrEncoding"), py::arg("range"))
+ .def(py::init([](ColorSpace &other) { return other; }))
+ .def("__str__", [](ColorSpace &self) {
+ return "<libcamera.ColorSpace '" + self.toString() + "'>";
+ })
+ .def_readwrite("primaries", &ColorSpace::primaries)
+ .def_readwrite("transferFunction", &ColorSpace::transferFunction)
+ .def_readwrite("ycbcrEncoding", &ColorSpace::ycbcrEncoding)
+ .def_readwrite("range", &ColorSpace::range)
+ .def_static("Raw", []() { return ColorSpace::Raw; })
+ .def_static("Srgb", []() { return ColorSpace::Srgb; })
+ .def_static("Sycc", []() { return ColorSpace::Sycc; })
+ .def_static("Smpte170m", []() { return ColorSpace::Smpte170m; })
+ .def_static("Rec709", []() { return ColorSpace::Rec709; })
+ .def_static("Rec2020", []() { return ColorSpace::Rec2020; });
+
+ pyColorSpacePrimaries
+ .value("Raw", ColorSpace::Primaries::Raw)
+ .value("Smpte170m", ColorSpace::Primaries::Smpte170m)
+ .value("Rec709", ColorSpace::Primaries::Rec709)
+ .value("Rec2020", ColorSpace::Primaries::Rec2020);
+
+ pyColorSpaceTransferFunction
+ .value("Linear", ColorSpace::TransferFunction::Linear)
+ .value("Srgb", ColorSpace::TransferFunction::Srgb)
+ .value("Rec709", ColorSpace::TransferFunction::Rec709);
+
+ pyColorSpaceYcbcrEncoding
+ .value("Null", ColorSpace::YcbcrEncoding::None)
+ .value("Rec601", ColorSpace::YcbcrEncoding::Rec601)
+ .value("Rec709", ColorSpace::YcbcrEncoding::Rec709)
+ .value("Rec2020", ColorSpace::YcbcrEncoding::Rec2020);
+
+ pyColorSpaceRange
+ .value("Full", ColorSpace::Range::Full)
+ .value("Limited", ColorSpace::Range::Limited);
+}
diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in
index cb8442ba..8d282ce5 100644
--- a/src/py/libcamera/py_controls_generated.cpp.in
+++ b/src/py/libcamera/py_controls_generated.cpp.in
@@ -9,7 +9,7 @@
#include <libcamera/control_ids.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
@@ -17,14 +17,12 @@ class PyControls
{
};
-class PyDraftControls
-{
-};
+${vendors_class_def}
void init_py_controls_generated(py::module& m)
{
auto controls = py::class_<PyControls>(m, "controls");
- auto draft = py::class_<PyDraftControls>(controls, "draft");
+${vendors_defs}
${controls}
}
diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp
index 96d4beef..e25689c6 100644
--- a/src/py/libcamera/py_enums.cpp
+++ b/src/py/libcamera/py_enums.cpp
@@ -7,7 +7,7 @@
#include <libcamera/libcamera.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
@@ -31,4 +31,14 @@ void init_py_enums(py::module &m)
.value("String", ControlType::ControlTypeString)
.value("Rectangle", ControlType::ControlTypeRectangle)
.value("Size", ControlType::ControlTypeSize);
+
+ py::enum_<Orientation>(m, "Orientation")
+ .value("Rotate0", Orientation::Rotate0)
+ .value("Rotate0Mirror", Orientation::Rotate0Mirror)
+ .value("Rotate180", Orientation::Rotate180)
+ .value("Rotate180Mirror", Orientation::Rotate180Mirror)
+ .value("Rotate90Mirror", Orientation::Rotate90Mirror)
+ .value("Rotate270", Orientation::Rotate270)
+ .value("Rotate270Mirror", Orientation::Rotate270Mirror)
+ .value("Rotate90", Orientation::Rotate90);
}
diff --git a/src/py/libcamera/py_formats_generated.cpp.in b/src/py/libcamera/py_formats_generated.cpp.in
index b88807f3..a3f7f94d 100644
--- a/src/py/libcamera/py_formats_generated.cpp.in
+++ b/src/py/libcamera/py_formats_generated.cpp.in
@@ -9,7 +9,7 @@
#include <libcamera/formats.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
diff --git a/src/py/libcamera/py_geometry.cpp b/src/py/libcamera/py_geometry.cpp
index 84b0cb08..5c2aeac4 100644
--- a/src/py/libcamera/py_geometry.cpp
+++ b/src/py/libcamera/py_geometry.cpp
@@ -11,7 +11,7 @@
#include <libcamera/libcamera.h>
#include <pybind11/operators.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
new file mode 100644
index 00000000..79891ab6
--- /dev/null
+++ b/src/py/libcamera/py_helpers.cpp
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include "py_helpers.h"
+
+#include <libcamera/libcamera.h>
+
+#include <pybind11/functional.h>
+#include <pybind11/stl.h>
+#include <pybind11/stl_bind.h>
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+template<typename T>
+static py::object valueOrTuple(const ControlValue &cv)
+{
+ if (cv.isArray()) {
+ const T *v = reinterpret_cast<const T *>(cv.data().data());
+ auto t = py::tuple(cv.numElements());
+
+ for (size_t i = 0; i < cv.numElements(); ++i)
+ t[i] = v[i];
+
+ return std::move(t);
+ }
+
+ return py::cast(cv.get<T>());
+}
+
+py::object controlValueToPy(const ControlValue &cv)
+{
+ switch (cv.type()) {
+ case ControlTypeBool:
+ return valueOrTuple<bool>(cv);
+ case ControlTypeByte:
+ return valueOrTuple<uint8_t>(cv);
+ case ControlTypeInteger32:
+ return valueOrTuple<int32_t>(cv);
+ case ControlTypeInteger64:
+ return valueOrTuple<int64_t>(cv);
+ case ControlTypeFloat:
+ return valueOrTuple<float>(cv);
+ case ControlTypeString:
+ return py::cast(cv.get<std::string>());
+ case ControlTypeRectangle:
+ return valueOrTuple<Rectangle>(cv);
+ case ControlTypeSize: {
+ const Size *v = reinterpret_cast<const Size *>(cv.data().data());
+ return py::cast(v);
+ }
+ case ControlTypeNone:
+ return py::none();
+ default:
+ throw std::runtime_error("Unsupported ControlValue type");
+ }
+}
+
+template<typename T>
+static ControlValue controlValueMaybeArray(const py::object &ob)
+{
+ if (py::isinstance<py::list>(ob) || py::isinstance<py::tuple>(ob)) {
+ std::vector<T> vec = ob.cast<std::vector<T>>();
+ return ControlValue(Span<const T>(vec));
+ }
+
+ return ControlValue(ob.cast<T>());
+}
+
+ControlValue pyToControlValue(const py::object &ob, ControlType type)
+{
+ switch (type) {
+ case ControlTypeBool:
+ return ControlValue(ob.cast<bool>());
+ case ControlTypeByte:
+ return controlValueMaybeArray<uint8_t>(ob);
+ case ControlTypeInteger32:
+ return controlValueMaybeArray<int32_t>(ob);
+ case ControlTypeInteger64:
+ return controlValueMaybeArray<int64_t>(ob);
+ case ControlTypeFloat:
+ return controlValueMaybeArray<float>(ob);
+ case ControlTypeString:
+ return ControlValue(ob.cast<std::string>());
+ case ControlTypeRectangle:
+ return controlValueMaybeArray<Rectangle>(ob);
+ case ControlTypeSize:
+ return ControlValue(ob.cast<Size>());
+ case ControlTypeNone:
+ return ControlValue();
+ default:
+ throw std::runtime_error("Control type not implemented");
+ }
+}
diff --git a/src/py/libcamera/py_helpers.h b/src/py/libcamera/py_helpers.h
new file mode 100644
index 00000000..983969df
--- /dev/null
+++ b/src/py/libcamera/py_helpers.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#pragma once
+
+#include <libcamera/libcamera.h>
+
+#include <pybind11/pybind11.h>
+
+pybind11::object controlValueToPy(const libcamera::ControlValue &cv);
+libcamera::ControlValue pyToControlValue(const pybind11::object &ob, libcamera::ControlType type);
diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp
index 505cc3dc..bce08218 100644
--- a/src/py/libcamera/py_main.cpp
+++ b/src/py/libcamera/py_main.cpp
@@ -5,132 +5,93 @@
* Python bindings
*/
-#include <mutex>
+#include "py_main.h"
+
+#include <memory>
#include <stdexcept>
-#include <sys/eventfd.h>
-#include <unistd.h>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/libcamera.h>
#include <pybind11/functional.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
+#include "py_camera_manager.h"
+#include "py_helpers.h"
+
namespace py = pybind11;
using namespace libcamera;
-template<typename T>
-static py::object valueOrTuple(const ControlValue &cv)
-{
- if (cv.isArray()) {
- const T *v = reinterpret_cast<const T *>(cv.data().data());
- auto t = py::tuple(cv.numElements());
+namespace libcamera {
- for (size_t i = 0; i < cv.numElements(); ++i)
- t[i] = v[i];
+LOG_DEFINE_CATEGORY(Python)
- return std::move(t);
- }
-
- return py::cast(cv.get<T>());
}
-static py::object controlValueToPy(const ControlValue &cv)
+/*
+ * This is a holder class used only for the Camera class, for the sole purpose
+ * of avoiding the compilation issue with Camera's private destructor.
+ *
+ * pybind11 requires a public destructor for classes held with shared_ptrs, even
+ * in cases where the public destructor is not strictly needed. The current
+ * understanding is that there are the following options to solve the problem:
+ *
+ * - Use pybind11 'smart_holder' branch. The downside is that 'smart_holder'
+ * is not the mainline branch, and not available in distributions.
+ * - https://github.com/pybind/pybind11/pull/2067
+ * - Make the Camera destructor public
+ * - Something like the PyCameraSmartPtr here, which adds a layer, hiding the
+ * issue.
+ */
+template<typename T>
+class PyCameraSmartPtr
{
- switch (cv.type()) {
- case ControlTypeBool:
- return valueOrTuple<bool>(cv);
- case ControlTypeByte:
- return valueOrTuple<uint8_t>(cv);
- case ControlTypeInteger32:
- return valueOrTuple<int32_t>(cv);
- case ControlTypeInteger64:
- return valueOrTuple<int64_t>(cv);
- case ControlTypeFloat:
- return valueOrTuple<float>(cv);
- case ControlTypeString:
- return py::cast(cv.get<std::string>());
- case ControlTypeRectangle: {
- const Rectangle *v = reinterpret_cast<const Rectangle *>(cv.data().data());
- return py::cast(v);
- }
- case ControlTypeSize: {
- const Size *v = reinterpret_cast<const Size *>(cv.data().data());
- return py::cast(v);
+public:
+ using element_type = T;
+
+ PyCameraSmartPtr()
+ {
}
- case ControlTypeNone:
- default:
- throw std::runtime_error("Unsupported ControlValue type");
+
+ explicit PyCameraSmartPtr(T *)
+ {
+ throw std::runtime_error("invalid SmartPtr constructor call");
}
-}
-template<typename T>
-static ControlValue controlValueMaybeArray(const py::object &ob)
-{
- if (py::isinstance<py::list>(ob) || py::isinstance<py::tuple>(ob)) {
- std::vector<T> vec = ob.cast<std::vector<T>>();
- return ControlValue(Span<const T>(vec));
+ explicit PyCameraSmartPtr(std::shared_ptr<T> p)
+ : ptr_(p)
+ {
}
- return ControlValue(ob.cast<T>());
-}
+ T *get() const { return ptr_.get(); }
-static ControlValue pyToControlValue(const py::object &ob, ControlType type)
-{
- switch (type) {
- case ControlTypeBool:
- return ControlValue(ob.cast<bool>());
- case ControlTypeByte:
- return controlValueMaybeArray<uint8_t>(ob);
- case ControlTypeInteger32:
- return controlValueMaybeArray<int32_t>(ob);
- case ControlTypeInteger64:
- return controlValueMaybeArray<int64_t>(ob);
- case ControlTypeFloat:
- return controlValueMaybeArray<float>(ob);
- case ControlTypeString:
- return ControlValue(ob.cast<std::string>());
- case ControlTypeRectangle:
- return ControlValue(ob.cast<Rectangle>());
- case ControlTypeSize:
- return ControlValue(ob.cast<Size>());
- case ControlTypeNone:
- default:
- throw std::runtime_error("Control type not implemented");
- }
-}
+ operator std::shared_ptr<T>() const { return ptr_; }
-static std::weak_ptr<CameraManager> gCameraManager;
-static int gEventfd;
-static std::mutex gReqlistMutex;
-static std::vector<Request *> gReqList;
+private:
+ std::shared_ptr<T> ptr_;
+};
-static void handleRequestCompleted(Request *req)
-{
- {
- std::lock_guard guard(gReqlistMutex);
- gReqList.push_back(req);
- }
+PYBIND11_DECLARE_HOLDER_TYPE(T, PyCameraSmartPtr<T>)
- uint64_t v = 1;
- size_t s = write(gEventfd, &v, 8);
- /*
- * We should never fail, and have no simple means to manage the error,
- * so let's use LOG(Fatal).
- */
- if (s != 8)
- LOG(Fatal) << "Unable to write to eventfd";
-}
+/*
+ * Note: global C++ destructors can be ran on this before the py module is
+ * destructed.
+ */
+static std::weak_ptr<PyCameraManager> gCameraManager;
-void init_py_enums(py::module &m);
+void init_py_color_space(py::module &m);
void init_py_controls_generated(py::module &m);
+void init_py_enums(py::module &m);
void init_py_formats_generated(py::module &m);
void init_py_geometry(py::module &m);
void init_py_properties_generated(py::module &m);
+void init_py_transform(py::module &m);
PYBIND11_MODULE(_libcamera, m)
{
@@ -138,6 +99,8 @@ PYBIND11_MODULE(_libcamera, m)
init_py_controls_generated(m);
init_py_geometry(m);
init_py_properties_generated(m);
+ init_py_color_space(m);
+ init_py_transform(m);
/* Forward declarations */
@@ -147,8 +110,9 @@ PYBIND11_MODULE(_libcamera, m)
* https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings
*/
- auto pyCameraManager = py::class_<CameraManager>(m, "CameraManager");
- auto pyCamera = py::class_<Camera>(m, "Camera");
+ auto pyCameraManager = py::class_<PyCameraManager, std::shared_ptr<PyCameraManager>>(m, "CameraManager");
+ auto pyCamera = py::class_<Camera, PyCameraSmartPtr<Camera>>(m, "Camera");
+ auto pySensorConfiguration = py::class_<SensorConfiguration>(m, "SensorConfiguration");
auto pyCameraConfiguration = py::class_<CameraConfiguration>(m, "CameraConfiguration");
auto pyCameraConfigurationStatus = py::enum_<CameraConfiguration::Status>(pyCameraConfiguration, "Status");
auto pyStreamConfiguration = py::class_<StreamConfiguration>(m, "StreamConfiguration");
@@ -165,12 +129,6 @@ PYBIND11_MODULE(_libcamera, m)
auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
auto pyFrameMetadataStatus = py::enum_<FrameMetadata::Status>(pyFrameMetadata, "Status");
auto pyFrameMetadataPlane = py::class_<FrameMetadata::Plane>(pyFrameMetadata, "Plane");
- auto pyTransform = py::class_<Transform>(m, "Transform");
- auto pyColorSpace = py::class_<ColorSpace>(m, "ColorSpace");
- auto pyColorSpacePrimaries = py::enum_<ColorSpace::Primaries>(pyColorSpace, "Primaries");
- auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
- auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
- auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
auto pyPixelFormat = py::class_<PixelFormat>(m, "PixelFormat");
init_py_formats_generated(m);
@@ -181,113 +139,69 @@ PYBIND11_MODULE(_libcamera, m)
/* Classes */
pyCameraManager
.def_static("singleton", []() {
- std::shared_ptr<CameraManager> cm = gCameraManager.lock();
- if (cm)
- return cm;
-
- int fd = eventfd(0, 0);
- if (fd == -1)
- throw std::system_error(errno, std::generic_category(),
- "Failed to create eventfd");
-
- cm = std::shared_ptr<CameraManager>(new CameraManager, [](auto p) {
- close(gEventfd);
- gEventfd = -1;
- delete p;
- });
-
- gEventfd = fd;
- gCameraManager = cm;
-
- int ret = cm->start();
- if (ret)
- throw std::system_error(-ret, std::generic_category(),
- "Failed to start CameraManager");
-
- return cm;
- })
-
- .def_property_readonly("version", &CameraManager::version)
-
- .def_property_readonly("event_fd", [](CameraManager &) {
- return gEventfd;
- })
-
- .def("get_ready_requests", [](CameraManager &) {
- uint8_t buf[8];
-
- if (read(gEventfd, buf, 8) != 8)
- throw std::system_error(errno, std::generic_category());
-
- std::vector<Request *> v;
-
- {
- std::lock_guard guard(gReqlistMutex);
- swap(v, gReqList);
- }
-
- std::vector<py::object> ret;
+ std::shared_ptr<PyCameraManager> cm = gCameraManager.lock();
- for (Request *req : v) {
- py::object o = py::cast(req);
- /* Decrease the ref increased in Camera.queue_request() */
- o.dec_ref();
- ret.push_back(o);
+ if (!cm) {
+ cm = std::make_shared<PyCameraManager>();
+ gCameraManager = cm;
}
- return ret;
+ return cm;
})
- .def("get", py::overload_cast<const std::string &>(&CameraManager::get), py::keep_alive<0, 1>())
+ .def_property_readonly_static("version", [](py::object /* self */) { return PyCameraManager::version(); })
+ .def("get", &PyCameraManager::get, py::keep_alive<0, 1>())
+ .def_property_readonly("cameras", &PyCameraManager::cameras)
- /* Create a list of Cameras, where each camera has a keep-alive to CameraManager */
- .def_property_readonly("cameras", [](CameraManager &self) {
- py::list l;
-
- for (auto &c : self.cameras()) {
- py::object py_cm = py::cast(self);
- py::object py_cam = py::cast(c);
- py::detail::keep_alive_impl(py_cam, py_cm);
- l.append(py_cam);
- }
-
- return l;
- });
+ .def_property_readonly("event_fd", &PyCameraManager::eventFd)
+ .def("get_ready_requests", &PyCameraManager::getReadyRequests);
pyCamera
.def_property_readonly("id", &Camera::id)
- .def("acquire", &Camera::acquire)
- .def("release", &Camera::release)
+ .def("acquire", [](Camera &self) {
+ int ret = self.acquire();
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to acquire camera");
+ })
+ .def("release", [](Camera &self) {
+ int ret = self.release();
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to release camera");
+ })
.def("start", [](Camera &self,
const std::unordered_map<const ControlId *, py::object> &controls) {
/* \todo What happens if someone calls start() multiple times? */
- self.requestCompleted.connect(handleRequestCompleted);
+ auto cm = gCameraManager.lock();
+ ASSERT(cm);
+
+ self.requestCompleted.connect(cm.get(), &PyCameraManager::handleRequestCompleted);
ControlList controlList(self.controls());
- for (const auto& [id, obj]: controls) {
+ for (const auto &[id, obj] : controls) {
auto val = pyToControlValue(obj, id->type());
controlList.set(id->id(), val);
}
int ret = self.start(&controlList);
if (ret) {
- self.requestCompleted.disconnect(handleRequestCompleted);
- return ret;
+ self.requestCompleted.disconnect();
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to start camera");
}
-
- return 0;
}, py::arg("controls") = std::unordered_map<const ControlId *, py::object>())
.def("stop", [](Camera &self) {
int ret = self.stop();
- if (ret)
- return ret;
- self.requestCompleted.disconnect(handleRequestCompleted);
+ self.requestCompleted.disconnect();
- return 0;
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to stop camera");
})
.def("__str__", [](Camera &self) {
@@ -295,10 +209,24 @@ PYBIND11_MODULE(_libcamera, m)
})
/* Keep the camera alive, as StreamConfiguration contains a Stream* */
- .def("generate_configuration", &Camera::generateConfiguration, py::keep_alive<0, 1>())
- .def("configure", &Camera::configure)
+ .def("generate_configuration", [](Camera &self, const std::vector<StreamRole> &roles) {
+ return self.generateConfiguration(roles);
+ }, py::keep_alive<0, 1>())
+
+ .def("configure", [](Camera &self, CameraConfiguration *config) {
+ int ret = self.configure(config);
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to configure camera");
+ })
- .def("create_request", &Camera::createRequest, py::arg("cookie") = 0)
+ .def("create_request", [](Camera &self, uint64_t cookie) {
+ std::unique_ptr<Request> req = self.createRequest(cookie);
+ if (!req)
+ throw std::system_error(ENOMEM, std::generic_category(),
+ "Failed to create request");
+ return req;
+ }, py::arg("cookie") = 0)
.def("queue_request", [](Camera &self, Request *req) {
py::object py_req = py::cast(req);
@@ -311,10 +239,11 @@ PYBIND11_MODULE(_libcamera, m)
py_req.inc_ref();
int ret = self.queueRequest(req);
- if (ret)
+ if (ret) {
py_req.dec_ref();
-
- return ret;
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to queue request");
+ }
})
.def_property_readonly("streams", [](Camera &self) {
@@ -353,6 +282,40 @@ PYBIND11_MODULE(_libcamera, m)
return ret;
});
+ pySensorConfiguration
+ .def(py::init<>())
+ .def_readwrite("bit_depth", &SensorConfiguration::bitDepth)
+ .def_readwrite("analog_crop", &SensorConfiguration::analogCrop)
+ .def_property(
+ "binning",
+ [](SensorConfiguration &self) {
+ return py::make_tuple(self.binning.binX, self.binning.binY);
+ },
+ [](SensorConfiguration &self, py::object value) {
+ auto vec = value.cast<std::vector<unsigned int>>();
+ if (vec.size() != 2)
+ throw std::runtime_error("binning requires iterable of 2 values");
+ self.binning.binX = vec[0];
+ self.binning.binY = vec[1];
+ })
+ .def_property(
+ "skipping",
+ [](SensorConfiguration &self) {
+ return py::make_tuple(self.skipping.xOddInc, self.skipping.xEvenInc,
+ self.skipping.yOddInc, self.skipping.yEvenInc);
+ },
+ [](SensorConfiguration &self, py::object value) {
+ auto vec = value.cast<std::vector<unsigned int>>();
+ if (vec.size() != 4)
+ throw std::runtime_error("skipping requires iterable of 4 values");
+ self.skipping.xOddInc = vec[0];
+ self.skipping.xEvenInc = vec[1];
+ self.skipping.yOddInc = vec[2];
+ self.skipping.yEvenInc = vec[3];
+ })
+ .def_readwrite("output_size", &SensorConfiguration::outputSize)
+ .def("is_valid", &SensorConfiguration::isValid);
+
pyCameraConfiguration
.def("__iter__", [](CameraConfiguration &self) {
return py::make_iterator<py::return_value_policy::reference_internal>(self);
@@ -365,7 +328,8 @@ PYBIND11_MODULE(_libcamera, m)
py::return_value_policy::reference_internal)
.def_property_readonly("size", &CameraConfiguration::size)
.def_property_readonly("empty", &CameraConfiguration::empty)
- .def_readwrite("transform", &CameraConfiguration::transform);
+ .def_readwrite("sensor_config", &CameraConfiguration::sensorConfig)
+ .def_readwrite("orientation", &CameraConfiguration::orientation);
pyCameraConfigurationStatus
.value("Valid", CameraConfiguration::Valid)
@@ -391,8 +355,14 @@ PYBIND11_MODULE(_libcamera, m)
.def("range", &StreamFormats::range);
pyFrameBufferAllocator
- .def(py::init<std::shared_ptr<Camera>>(), py::keep_alive<1, 2>())
- .def("allocate", &FrameBufferAllocator::allocate)
+ .def(py::init<PyCameraSmartPtr<Camera>>(), py::keep_alive<1, 2>())
+ .def("allocate", [](FrameBufferAllocator &self, Stream *stream) {
+ int ret = self.allocate(stream);
+ if (ret < 0)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to allocate buffers");
+ return ret;
+ })
.def_property_readonly("allocated", &FrameBufferAllocator::allocated)
/* Create a list of FrameBuffers, where each FrameBuffer has a keep-alive to FrameBufferAllocator */
.def("buffers", [](FrameBufferAllocator &self, Stream *stream) {
@@ -469,11 +439,15 @@ PYBIND11_MODULE(_libcamera, m)
pyRequest
/* \todo Fence is not supported, so we cannot expose addBuffer() directly */
.def("add_buffer", [](Request &self, const Stream *stream, FrameBuffer *buffer) {
- return self.addBuffer(stream, buffer);
+ int ret = self.addBuffer(stream, buffer);
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to add buffer");
}, py::keep_alive<1, 3>()) /* Request keeps Framebuffer alive */
.def_property_readonly("status", &Request::status)
.def_property_readonly("buffers", &Request::buffers)
.def_property_readonly("cookie", &Request::cookie)
+ .def_property_readonly("sequence", &Request::sequence)
.def_property_readonly("has_pending_buffers", &Request::hasPendingBuffers)
.def("set_control", [](Request &self, const ControlId &id, py::object value) {
self.controls().set(id.id(), pyToControlValue(value, id.type()));
@@ -526,109 +500,6 @@ PYBIND11_MODULE(_libcamera, m)
pyFrameMetadataPlane
.def_readwrite("bytes_used", &FrameMetadata::Plane::bytesused);
- pyTransform
- .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
- bool ok;
-
- Transform t = transformFromRotation(rotation, &ok);
- if (!ok)
- throw std::invalid_argument("Invalid rotation");
-
- if (hflip)
- t ^= Transform::HFlip;
- if (vflip)
- t ^= Transform::VFlip;
- if (transpose)
- t ^= Transform::Transpose;
- return t;
- }), py::arg("rotation") = 0, py::arg("hflip") = false,
- py::arg("vflip") = false, py::arg("transpose") = false)
- .def(py::init([](Transform &other) { return other; }))
- .def("__str__", [](Transform &self) {
- return "<libcamera.Transform '" + std::string(transformToString(self)) + "'>";
- })
- .def_property("hflip",
- [](Transform &self) {
- return !!(self & Transform::HFlip);
- },
- [](Transform &self, bool hflip) {
- if (hflip)
- self |= Transform::HFlip;
- else
- self &= ~Transform::HFlip;
- })
- .def_property("vflip",
- [](Transform &self) {
- return !!(self & Transform::VFlip);
- },
- [](Transform &self, bool vflip) {
- if (vflip)
- self |= Transform::VFlip;
- else
- self &= ~Transform::VFlip;
- })
- .def_property("transpose",
- [](Transform &self) {
- return !!(self & Transform::Transpose);
- },
- [](Transform &self, bool transpose) {
- if (transpose)
- self |= Transform::Transpose;
- else
- self &= ~Transform::Transpose;
- })
- .def("inverse", [](Transform &self) { return -self; })
- .def("invert", [](Transform &self) {
- self = -self;
- })
- .def("compose", [](Transform &self, Transform &other) {
- self = self * other;
- });
-
- pyColorSpace
- .def(py::init([](ColorSpace::Primaries primaries,
- ColorSpace::TransferFunction transferFunction,
- ColorSpace::YcbcrEncoding ycbcrEncoding,
- ColorSpace::Range range) {
- return ColorSpace(primaries, transferFunction, ycbcrEncoding, range);
- }), py::arg("primaries"), py::arg("transferFunction"),
- py::arg("ycbcrEncoding"), py::arg("range"))
- .def(py::init([](ColorSpace &other) { return other; }))
- .def("__str__", [](ColorSpace &self) {
- return "<libcamera.ColorSpace '" + self.toString() + "'>";
- })
- .def_readwrite("primaries", &ColorSpace::primaries)
- .def_readwrite("transferFunction", &ColorSpace::transferFunction)
- .def_readwrite("ycbcrEncoding", &ColorSpace::ycbcrEncoding)
- .def_readwrite("range", &ColorSpace::range)
- .def_static("Raw", []() { return ColorSpace::Raw; })
- .def_static("Jpeg", []() { return ColorSpace::Jpeg; })
- .def_static("Srgb", []() { return ColorSpace::Srgb; })
- .def_static("Smpte170m", []() { return ColorSpace::Smpte170m; })
- .def_static("Rec709", []() { return ColorSpace::Rec709; })
- .def_static("Rec2020", []() { return ColorSpace::Rec2020; });
-
- pyColorSpacePrimaries
- .value("Raw", ColorSpace::Primaries::Raw)
- .value("Smpte170m", ColorSpace::Primaries::Smpte170m)
- .value("Rec709", ColorSpace::Primaries::Rec709)
- .value("Rec2020", ColorSpace::Primaries::Rec2020);
-
- pyColorSpaceTransferFunction
- .value("Linear", ColorSpace::TransferFunction::Linear)
- .value("Srgb", ColorSpace::TransferFunction::Srgb)
- .value("Rec709", ColorSpace::TransferFunction::Rec709);
-
- pyColorSpaceYcbcrEncoding
- .value("Null", ColorSpace::YcbcrEncoding::None)
- .value("Rec601", ColorSpace::YcbcrEncoding::Rec601)
- .value("Rec709", ColorSpace::YcbcrEncoding::Rec709)
- .value("Rec2020", ColorSpace::YcbcrEncoding::Rec2020);
-
- pyColorSpaceRange
- .value("Full", ColorSpace::Range::Full)
- .value("Limited", ColorSpace::Range::Limited);
-
pyPixelFormat
.def(py::init<>())
.def(py::init<uint32_t, uint64_t>())
diff --git a/src/py/libcamera/py_main.h b/src/py/libcamera/py_main.h
new file mode 100644
index 00000000..5bb5f2d1
--- /dev/null
+++ b/src/py/libcamera/py_main.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#pragma once
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Python)
+
+}
diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in
index 044b2b2a..e3802b81 100644
--- a/src/py/libcamera/py_properties_generated.cpp.in
+++ b/src/py/libcamera/py_properties_generated.cpp.in
@@ -9,7 +9,7 @@
#include <libcamera/property_ids.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
@@ -17,14 +17,12 @@ class PyProperties
{
};
-class PyDraftProperties
-{
-};
+${vendors_class_def}
void init_py_properties_generated(py::module& m)
{
auto controls = py::class_<PyProperties>(m, "properties");
- auto draft = py::class_<PyDraftProperties>(controls, "draft");
+${vendors_defs}
${controls}
}
diff --git a/src/py/libcamera/py_transform.cpp b/src/py/libcamera/py_transform.cpp
new file mode 100644
index 00000000..f3a0bfaf
--- /dev/null
+++ b/src/py/libcamera/py_transform.cpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ *
+ * Python bindings - Transform class
+ */
+
+#include <libcamera/transform.h>
+#include <libcamera/libcamera.h>
+
+#include <pybind11/operators.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+void init_py_transform(py::module &m)
+{
+ auto pyTransform = py::class_<Transform>(m, "Transform");
+
+ pyTransform
+ .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
+ bool ok;
+
+ Transform t = transformFromRotation(rotation, &ok);
+ if (!ok)
+ throw std::invalid_argument("Invalid rotation");
+
+ if (hflip)
+ t ^= Transform::HFlip;
+ if (vflip)
+ t ^= Transform::VFlip;
+ if (transpose)
+ t ^= Transform::Transpose;
+ return t;
+ }), py::arg("rotation") = 0, py::arg("hflip") = false,
+ py::arg("vflip") = false, py::arg("transpose") = false)
+ .def(py::init([](Transform &other) { return other; }))
+ .def("__str__", [](Transform &self) {
+ return "<libcamera.Transform '" + std::string(transformToString(self)) + "'>";
+ })
+ .def_property("hflip",
+ [](Transform &self) {
+ return !!(self & Transform::HFlip);
+ },
+ [](Transform &self, bool hflip) {
+ if (hflip)
+ self |= Transform::HFlip;
+ else
+ self &= ~Transform::HFlip;
+ })
+ .def_property("vflip",
+ [](Transform &self) {
+ return !!(self & Transform::VFlip);
+ },
+ [](Transform &self, bool vflip) {
+ if (vflip)
+ self |= Transform::VFlip;
+ else
+ self &= ~Transform::VFlip;
+ })
+ .def_property("transpose",
+ [](Transform &self) {
+ return !!(self & Transform::Transpose);
+ },
+ [](Transform &self, bool transpose) {
+ if (transpose)
+ self |= Transform::Transpose;
+ else
+ self &= ~Transform::Transpose;
+ })
+ .def("inverse", [](Transform &self) { return -self; })
+ .def("invert", [](Transform &self) {
+ self = -self;
+ })
+ .def("compose", [](Transform &self, Transform &other) {
+ self = self * other;
+ });
+}
diff --git a/src/py/meson.build b/src/py/meson.build
index 4ce9668c..a4586b4a 100644
--- a/src/py/meson.build
+++ b/src/py/meson.build
@@ -1 +1,3 @@
+# SPDX-License-Identifier: CC0-1.0
+
subdir('libcamera')
diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build
index f132103c..58f53bf3 100644
--- a/src/v4l2/meson.build
+++ b/src/v4l2/meson.build
@@ -24,6 +24,7 @@ v4l2_compat_cpp_args = [
'-U_FILE_OFFSET_BITS',
'-D_FILE_OFFSET_BITS=32',
'-D_LARGEFILE64_SOURCE',
+ '-U_TIME_BITS',
'-fvisibility=hidden',
]
@@ -31,6 +32,7 @@ v4l2_compat = shared_library('v4l2-compat',
v4l2_compat_sources,
name_prefix : '',
install : true,
+ install_dir : libcamera_libexecdir,
dependencies : [libcamera_private, libdl],
cpp_args : v4l2_compat_cpp_args)
@@ -38,9 +40,10 @@ v4l2_compat = shared_library('v4l2-compat',
# adaptation layer.
cdata = configuration_data()
-cdata.set('LIBCAMERA_V4L2_SO', get_option('prefix') / get_option('libdir') / 'v4l2-compat.so')
+cdata.set('LIBCAMERA_V4L2_SO', get_option('prefix') / libcamera_libexecdir / 'v4l2-compat.so')
configure_file(input : 'libcamerify.in',
output : 'libcamerify',
configuration : cdata,
- install_dir : get_option('bindir'))
+ install_dir : get_option('bindir'),
+ install_tag : 'bin')
diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp
index e922b9e6..7b97c2d5 100644
--- a/src/v4l2/v4l2_camera.cpp
+++ b/src/v4l2/v4l2_camera.cpp
@@ -71,11 +71,10 @@ std::vector<V4L2Camera::Buffer> V4L2Camera::completedBuffers()
{
std::vector<Buffer> v;
- bufferLock_.lock();
+ MutexLocker lock(bufferLock_);
for (std::unique_ptr<Buffer> &metadata : completedBuffers_)
v.push_back(*metadata.get());
completedBuffers_.clear();
- bufferLock_.unlock();
return v;
}
@@ -278,7 +277,7 @@ int V4L2Camera::qbuf(unsigned int index)
void V4L2Camera::waitForBufferAvailable()
{
MutexLocker locker(bufferMutex_);
- bufferCV_.wait(locker, [&] {
+ bufferCV_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(bufferMutex_) {
return bufferAvailableCount_ >= 1 || !isRunning_;
});
if (isRunning_)
diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h
index 03e74118..d3483444 100644
--- a/src/v4l2/v4l2_camera.h
+++ b/src/v4l2/v4l2_camera.h
@@ -39,7 +39,7 @@ public:
void bind(int efd);
void unbind();
- std::vector<Buffer> completedBuffers();
+ std::vector<Buffer> completedBuffers() LIBCAMERA_TSA_EXCLUDES(bufferLock_);
int configure(libcamera::StreamConfiguration *streamConfigOut,
const libcamera::Size &size,
@@ -58,13 +58,14 @@ public:
int qbuf(unsigned int index);
- void waitForBufferAvailable();
- bool isBufferAvailable();
+ void waitForBufferAvailable() LIBCAMERA_TSA_EXCLUDES(bufferMutex_);
+ bool isBufferAvailable() LIBCAMERA_TSA_EXCLUDES(bufferMutex_);
bool isRunning();
private:
- void requestComplete(libcamera::Request *request);
+ void requestComplete(libcamera::Request *request)
+ LIBCAMERA_TSA_EXCLUDES(bufferLock_);
std::shared_ptr<libcamera::Camera> camera_;
std::unique_ptr<libcamera::CameraConfiguration> config_;
@@ -77,11 +78,12 @@ private:
std::vector<std::unique_ptr<libcamera::Request>> requestPool_;
std::deque<libcamera::Request *> pendingRequests_;
- std::deque<std::unique_ptr<Buffer>> completedBuffers_;
+ std::deque<std::unique_ptr<Buffer>> completedBuffers_
+ LIBCAMERA_TSA_GUARDED_BY(bufferLock_);
int efd_;
libcamera::Mutex bufferMutex_;
libcamera::ConditionVariable bufferCV_;
- unsigned int bufferAvailableCount_;
+ unsigned int bufferAvailableCount_ LIBCAMERA_TSA_GUARDED_BY(bufferMutex_);
};
diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 26a227da..341f7902 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -182,7 +182,7 @@ void V4L2CameraProxy::setFmtFromConfig(const StreamConfiguration &streamConfig)
v4l2PixFormat_.width = size.width;
v4l2PixFormat_.height = size.height;
- v4l2PixFormat_.pixelformat = V4L2PixelFormat::fromPixelFormat(streamConfig.pixelFormat);
+ v4l2PixFormat_.pixelformat = V4L2PixelFormat::fromPixelFormat(streamConfig.pixelFormat)[0];
v4l2PixFormat_.field = V4L2_FIELD_NONE;
v4l2PixFormat_.bytesperline = streamConfig.stride;
v4l2PixFormat_.sizeimage = streamConfig.frameSize;
@@ -290,7 +290,7 @@ int V4L2CameraProxy::vidioc_enum_fmt(V4L2CameraFile *file, struct v4l2_fmtdesc *
return -EINVAL;
PixelFormat format = streamConfig_.formats().pixelformats()[arg->index];
- V4L2PixelFormat v4l2Format = V4L2PixelFormat::fromPixelFormat(format);
+ V4L2PixelFormat v4l2Format = V4L2PixelFormat::fromPixelFormat(format)[0];
arg->flags = format == formats::MJPEG ? V4L2_FMT_FLAG_COMPRESSED : 0;
utils::strlcpy(reinterpret_cast<char *>(arg->description),
@@ -333,7 +333,7 @@ int V4L2CameraProxy::tryFormat(struct v4l2_format *arg)
arg->fmt.pix.width = config.size.width;
arg->fmt.pix.height = config.size.height;
- arg->fmt.pix.pixelformat = V4L2PixelFormat::fromPixelFormat(config.pixelFormat);
+ arg->fmt.pix.pixelformat = V4L2PixelFormat::fromPixelFormat(config.pixelFormat)[0];
arg->fmt.pix.field = V4L2_FIELD_NONE;
arg->fmt.pix.bytesperline = config.stride;
arg->fmt.pix.sizeimage = config.frameSize;
@@ -778,10 +778,20 @@ const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
VIDIOC_STREAMOFF,
};
-int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long request, void *arg)
+int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void *arg)
{
MutexLocker locker(proxyMutex_);
+ /*
+ * The Linux Kernel only processes 32 bits of an IOCTL.
+ *
+ * Prevent unexpected sign-extensions that could occur if applications
+ * use a signed int for the ioctl request, which would sign-extend to
+ * an incorrect value for unsigned longs on 64 bit architectures by
+ * explicitly casting as an unsigned int here.
+ */
+ unsigned int request = longRequest;
+
if (!arg && (_IOC_DIR(request) & _IOC_WRITE)) {
errno = EFAULT;
return -1;
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 76ca2d8a..8a0195e1 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -27,13 +27,15 @@ class V4L2CameraProxy
public:
V4L2CameraProxy(unsigned int index, std::shared_ptr<libcamera::Camera> camera);
- int open(V4L2CameraFile *file);
- void close(V4L2CameraFile *file);
+ int open(V4L2CameraFile *file) LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
+ void close(V4L2CameraFile *file) LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
void *mmap(V4L2CameraFile *file, void *addr, size_t length, int prot,
- int flags, off64_t offset);
- int munmap(V4L2CameraFile *file, void *addr, size_t length);
+ int flags, off64_t offset) LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
+ int munmap(V4L2CameraFile *file, void *addr, size_t length)
+ LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
- int ioctl(V4L2CameraFile *file, unsigned long request, void *arg);
+ int ioctl(V4L2CameraFile *file, unsigned long request, void *arg)
+ LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
private:
bool validateBufferType(uint32_t type);
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 0f7575c5..5e8cdb4f 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -24,6 +24,7 @@
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
+#include <libcamera/property_ids.h>
#include "v4l2_camera_file.h"
@@ -113,14 +114,35 @@ int V4L2CompatManager::getCameraIndex(int fd)
if (ret < 0)
return -1;
- std::shared_ptr<Camera> target = cm_->get(statbuf.st_rdev);
- if (!target)
- return -1;
+ const dev_t devnum = statbuf.st_rdev;
+ /*
+ * Iterate each known camera and identify if it reports this nodes
+ * device number in its list of SystemDevices.
+ */
auto cameras = cm_->cameras();
for (auto [index, camera] : utils::enumerate(cameras)) {
- if (camera == target)
- return index;
+ Span<const int64_t> devices = camera->properties()
+ .get(properties::SystemDevices)
+ .value_or(Span<int64_t>{});
+
+ /*
+ * While there may be multiple cameras that could reference the
+ * same device node, we take a first match as a best effort for
+ * now.
+ *
+ * \todo Each camera can be accessed through any of the video
+ * device nodes that it uses. This may confuse applications.
+ * Consider reworking the V4L2 adaptation layer to instead
+ * expose each Camera instance through a single video device
+ * node (with a consistent and stable mapping). The other
+ * device nodes could possibly be hidden from the application
+ * by intercepting additional calls to the C library.
+ */
+ for (const int64_t dev : devices) {
+ if (dev == static_cast<int64_t>(devnum))
+ return index;
+ }
}
return -1;
diff --git a/subprojects/.gitignore b/subprojects/.gitignore
index fd3f4a5b..04b6271f 100644
--- a/subprojects/.gitignore
+++ b/subprojects/.gitignore
@@ -1,5 +1,6 @@
+# SPDX-License-Identifier: CC0-1.0
+
/googletest-release*
/libyaml
/libyuv
/packagecache
-/pybind11
diff --git a/subprojects/gtest.wrap b/subprojects/gtest.wrap
index 40128b35..8892e184 100644
--- a/subprojects/gtest.wrap
+++ b/subprojects/gtest.wrap
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
[wrap-file]
directory = googletest-release-1.11.0
source_url = https://github.com/google/googletest/archive/release-1.11.0.zip
diff --git a/subprojects/libyaml.wrap b/subprojects/libyaml.wrap
index 3d7d0a32..392416c6 100644
--- a/subprojects/libyaml.wrap
+++ b/subprojects/libyaml.wrap
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
[wrap-git]
directory = libyaml
url = https://github.com/yaml/libyaml
diff --git a/subprojects/libyuv.wrap b/subprojects/libyuv.wrap
index 8ba51fa0..3417e73f 100644
--- a/subprojects/libyuv.wrap
+++ b/subprojects/libyuv.wrap
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
[wrap-git]
directory = libyuv
url = https://chromium.googlesource.com/libyuv/libyuv.git
diff --git a/subprojects/packagefiles/pybind11/meson.build b/subprojects/packagefiles/pybind11/meson.build
deleted file mode 100644
index 1be47ca4..00000000
--- a/subprojects/packagefiles/pybind11/meson.build
+++ /dev/null
@@ -1,7 +0,0 @@
-project('pybind11', 'cpp',
- version : '2.9.1',
- license : 'BSD-3-Clause')
-
-pybind11_incdir = include_directories('include')
-
-pybind11_dep = declare_dependency(include_directories : pybind11_incdir)
diff --git a/subprojects/pybind11.wrap b/subprojects/pybind11.wrap
deleted file mode 100644
index e8037a5d..00000000
--- a/subprojects/pybind11.wrap
+++ /dev/null
@@ -1,9 +0,0 @@
-[wrap-git]
-url = https://github.com/pybind/pybind11.git
-# This is the head of 'smart_holder' branch
-revision = aebdf00cd060b871c5a1e0c2cf4a333503dd0431
-depth = 1
-patch_directory = pybind11
-
-[provide]
-pybind11 = pybind11_dep
diff --git a/test/camera-sensor.cpp b/test/camera-sensor.cpp
index d3dcb510..9503d775 100644
--- a/test/camera-sensor.cpp
+++ b/test/camera-sensor.cpp
@@ -12,6 +12,7 @@
#include <libcamera/base/utils.h>
+#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
@@ -57,6 +58,10 @@ protected:
return TestFail;
}
+ lens_ = sensor_->focusLens();
+ if (lens_)
+ cout << "Found lens controller" << endl;
+
return TestPass;
}
@@ -95,7 +100,7 @@ protected:
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_BGR888_1X24 },
Size(1024, 768));
- if (format.mbus_code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
+ if (format.code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
format.size != Size(4096, 2160)) {
cerr << "Failed to get a suitable format, expected 4096x2160-0x"
<< utils::hex(MEDIA_BUS_FMT_SBGGR10_1X10)
@@ -103,6 +108,11 @@ protected:
return TestFail;
}
+ if (lens_ && lens_->setFocusPosition(10)) {
+ cerr << "Failed to set lens focus position" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
@@ -115,6 +125,7 @@ private:
std::unique_ptr<DeviceEnumerator> enumerator_;
std::shared_ptr<MediaDevice> media_;
CameraSensor *sensor_;
+ CameraLens *lens_;
};
TEST_REGISTER(CameraSensorTest)
diff --git a/test/camera/camera_reconfigure.cpp b/test/camera/camera_reconfigure.cpp
index f6076baa..06c87730 100644
--- a/test/camera/camera_reconfigure.cpp
+++ b/test/camera/camera_reconfigure.cpp
@@ -98,7 +98,7 @@ private:
return TestFail;
}
- requests_.push_back(move(request));
+ requests_.push_back(std::move(request));
}
camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete);
@@ -179,7 +179,7 @@ private:
continue;
string pname("/proc/" + string(ptr->d_name) + "/comm");
- if (File::exists(pname.c_str())) {
+ if (File::exists(pname)) {
ifstream pfile(pname.c_str());
string comm;
getline(pfile, comm);
diff --git a/test/camera/meson.build b/test/camera/meson.build
index 668d5c03..4f9f8c8c 100644
--- a/test/camera/meson.build
+++ b/test/camera/meson.build
@@ -3,18 +3,18 @@
# Tests are listed in order of complexity.
# They are not alphabetically sorted.
camera_tests = [
- ['configuration_default', 'configuration_default.cpp'],
- ['configuration_set', 'configuration_set.cpp'],
- ['buffer_import', 'buffer_import.cpp'],
- ['statemachine', 'statemachine.cpp'],
- ['capture', 'capture.cpp'],
- ['camera_reconfigure', 'camera_reconfigure.cpp'],
+ {'name': 'configuration_default', 'sources': ['configuration_default.cpp']},
+ {'name': 'configuration_set', 'sources': ['configuration_set.cpp']},
+ {'name': 'buffer_import', 'sources': ['buffer_import.cpp']},
+ {'name': 'statemachine', 'sources': ['statemachine.cpp']},
+ {'name': 'capture', 'sources': ['capture.cpp']},
+ {'name': 'camera_reconfigure', 'sources': ['camera_reconfigure.cpp']},
]
-foreach t : camera_tests
- exe = executable(t[0], t[1],
+foreach test : camera_tests
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'camera', is_parallel : false)
+ test(test['name'], exe, suite : 'camera', is_parallel : false)
endforeach
diff --git a/test/color-space.cpp b/test/color-space.cpp
new file mode 100644
index 00000000..7d45b217
--- /dev/null
+++ b/test/color-space.cpp
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * libcamera ColorSpace test
+ */
+
+#include <array>
+#include <iostream>
+
+#include <libcamera/color_space.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class ColorSpaceTest : public Test
+{
+protected:
+ int run()
+ {
+ if (ColorSpace::toString(std::nullopt) != "Unset") {
+ std::cerr << "Conversion from nullopt to string failed" << std::endl;
+ return TestFail;
+ }
+
+ const std::array<std::pair<ColorSpace, std::string>, 10> colorSpaces = { {
+ { ColorSpace::Raw, "RAW" },
+ { ColorSpace::Srgb, "sRGB" },
+ { ColorSpace::Sycc, "sYCC" },
+ { ColorSpace::Smpte170m, "SMPTE170M" },
+ { ColorSpace::Rec709, "Rec709" },
+ { ColorSpace::Rec2020, "Rec2020" },
+ {
+ ColorSpace{
+ ColorSpace::Primaries::Raw,
+ ColorSpace::TransferFunction::Linear,
+ ColorSpace::YcbcrEncoding::None,
+ ColorSpace::Range::Limited
+ },
+ "RAW/Linear/None/Limited"
+ }, {
+ ColorSpace{
+ ColorSpace::Primaries::Smpte170m,
+ ColorSpace::TransferFunction::Srgb,
+ ColorSpace::YcbcrEncoding::Rec601,
+ ColorSpace::Range::Full
+ },
+ "SMPTE170M/sRGB/Rec601/Full"
+ }, {
+ ColorSpace{
+ ColorSpace::Primaries::Rec709,
+ ColorSpace::TransferFunction::Rec709,
+ ColorSpace::YcbcrEncoding::Rec709,
+ ColorSpace::Range::Full
+ },
+ "Rec709/Rec709/Rec709/Full"
+ }, {
+ ColorSpace{
+ ColorSpace::Primaries::Rec2020,
+ ColorSpace::TransferFunction::Linear,
+ ColorSpace::YcbcrEncoding::Rec2020,
+ ColorSpace::Range::Limited
+ },
+ "Rec2020/Linear/Rec2020/Limited"
+ },
+ } };
+
+ for (const auto &[colorSpace, name] : colorSpaces) {
+ if (colorSpace.toString() != name) {
+ std::cerr
+ << "Conversion from ColorSpace to string failed: "
+ << "expected " << name
+ << ", got " << colorSpace.toString()
+ << std::endl;
+ return TestFail;
+ }
+
+ if (ColorSpace::fromString(name) != colorSpace) {
+ std::cerr
+ << "Conversion from string "
+ << name << " to ColorSpace failed"
+ << std::endl;
+ return TestFail;
+ }
+ }
+
+ if (ColorSpace::fromString("Invalid")) {
+ std::cerr << "Conversion from invalid name string to color space succeeded"
+ << std::endl;
+ return TestFail;
+ }
+
+ if (ColorSpace::fromString("Rec709/Rec709/Rec710/Limited")) {
+ std::cerr << "Conversion from invalid component string to color space succeeded"
+ << std::endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(ColorSpaceTest)
diff --git a/test/controls/control_info.cpp b/test/controls/control_info.cpp
index 2827473b..1176a502 100644
--- a/test/controls/control_info.cpp
+++ b/test/controls/control_info.cpp
@@ -26,20 +26,22 @@ protected:
*/
ControlInfo brightness;
- if (brightness.min().get<int32_t>() != 0 ||
- brightness.max().get<int32_t>() != 0) {
+ if (brightness.min().type() != ControlType::ControlTypeNone ||
+ brightness.max().type() != ControlType::ControlTypeNone ||
+ brightness.def().type() != ControlType::ControlTypeNone) {
cout << "Invalid control range for Brightness" << endl;
return TestFail;
}
/*
* Test information retrieval from a control with a minimum and
- * a maximum value.
+ * a maximum value, and an implicit default value.
*/
ControlInfo contrast(10, 200);
if (contrast.min().get<int32_t>() != 10 ||
- contrast.max().get<int32_t>() != 200) {
+ contrast.max().get<int32_t>() != 200 ||
+ !contrast.def().isNone()) {
cout << "Invalid control range for Contrast" << endl;
return TestFail;
}
diff --git a/test/controls/control_info_map.cpp b/test/controls/control_info_map.cpp
index db95945a..29b33515 100644
--- a/test/controls/control_info_map.cpp
+++ b/test/controls/control_info_map.cpp
@@ -75,6 +75,13 @@ protected:
return TestFail;
}
+ /* Test looking up a control on a default-constructed infoMap */
+ const ControlInfoMap emptyInfoMap;
+ if (emptyInfoMap.find(12345) != emptyInfoMap.end()) {
+ cerr << "find() on empty ControlInfoMap failed" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/controls/control_list.cpp b/test/controls/control_list.cpp
index 70cf61b8..bb35aab7 100644
--- a/test/controls/control_list.cpp
+++ b/test/controls/control_list.cpp
@@ -50,7 +50,7 @@ protected:
return TestFail;
}
- if (list.contains(controls::Brightness)) {
+ if (list.get(controls::Brightness)) {
cout << "List should not contain Brightness control" << endl;
return TestFail;
}
@@ -80,7 +80,7 @@ protected:
return TestFail;
}
- if (!list.contains(controls::Brightness)) {
+ if (!list.get(controls::Brightness)) {
cout << "List should contain Brightness control" << endl;
return TestFail;
}
@@ -99,7 +99,7 @@ protected:
return TestFail;
}
- if (list.contains(controls::Contrast)) {
+ if (list.get(controls::Contrast)) {
cout << "List should not contain Contract control" << endl;
return TestFail;
}
@@ -108,8 +108,8 @@ protected:
list.set(controls::Brightness, 0.0f);
list.set(controls::Contrast, 1.5f);
- if (!list.contains(controls::Brightness) ||
- !list.contains(controls::Contrast)) {
+ if (!list.get(controls::Brightness) ||
+ !list.get(controls::Contrast)) {
cout << "List should contain Brightness and Contrast controls"
<< endl;
return TestFail;
@@ -145,7 +145,7 @@ protected:
*/
list.set(controls::AwbEnable, true);
- if (list.contains(controls::AwbEnable)) {
+ if (list.get(controls::AwbEnable)) {
cout << "List shouldn't contain AwbEnable control" << endl;
return TestFail;
}
@@ -171,9 +171,9 @@ protected:
return TestFail;
}
- if (!mergeList.contains(controls::Brightness) ||
- !mergeList.contains(controls::Contrast) ||
- !mergeList.contains(controls::Saturation)) {
+ if (!mergeList.get(controls::Brightness) ||
+ !mergeList.get(controls::Contrast) ||
+ !mergeList.get(controls::Saturation)) {
cout << "Merged list does not contain all controls" << endl;
return TestFail;
}
@@ -196,6 +196,56 @@ protected:
return TestFail;
}
+ /*
+ * Create two lists with overlapping controls. Merge them with
+ * overwriteExisting = true, verifying that the existing control
+ * values *get* overwritten.
+ */
+ mergeList.clear();
+ mergeList.set(controls::Brightness, 0.7f);
+ mergeList.set(controls::Saturation, 0.4f);
+
+ list.clear();
+ list.set(controls::Brightness, 0.5f);
+ list.set(controls::Contrast, 1.1f);
+
+ mergeList.merge(list, ControlList::MergePolicy::OverwriteExisting);
+ if (mergeList.size() != 3) {
+ cout << "Merged list should contain three elements" << endl;
+ return TestFail;
+ }
+
+ if (list.size() != 2) {
+ cout << "The list to merge should contain two elements"
+ << endl;
+ return TestFail;
+ }
+
+ if (!mergeList.get(controls::Brightness) ||
+ !mergeList.get(controls::Contrast) ||
+ !mergeList.get(controls::Saturation)) {
+ cout << "Merged list does not contain all controls" << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Brightness) != 0.5f) {
+ cout << "Brightness control value did not change after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Contrast) != 1.1f) {
+ cout << "Contrast control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
+ if (mergeList.get(controls::Saturation) != 0.4f) {
+ cout << "Saturation control value changed after merging lists"
+ << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/controls/meson.build b/test/controls/meson.build
index 0103543e..763f8905 100644
--- a/test/controls/meson.build
+++ b/test/controls/meson.build
@@ -1,16 +1,16 @@
# SPDX-License-Identifier: CC0-1.0
control_tests = [
- ['control_info', 'control_info.cpp'],
- ['control_info_map', 'control_info_map.cpp'],
- ['control_list', 'control_list.cpp'],
- ['control_value', 'control_value.cpp'],
+ {'name': 'control_info', 'sources': ['control_info.cpp']},
+ {'name': 'control_info_map', 'sources': ['control_info_map.cpp']},
+ {'name': 'control_list', 'sources': ['control_list.cpp']},
+ {'name': 'control_value', 'sources': ['control_value.cpp']},
]
-foreach t : control_tests
- exe = executable(t[0], t[1],
+foreach test : control_tests
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_public,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'controls', is_parallel : false)
+ test(test['name'], exe, suite : 'controls', is_parallel : false)
endforeach
diff --git a/test/delayed_controls.cpp b/test/delayed_controls.cpp
index c6f195b7..a8ce9828 100644
--- a/test/delayed_controls.cpp
+++ b/test/delayed_controls.cpp
@@ -155,7 +155,7 @@ protected:
return TestPass;
}
- int dualControlsWithDelay(uint32_t startOffset)
+ int dualControlsWithDelay()
{
static const unsigned int maxDelay = 2;
@@ -175,25 +175,24 @@ protected:
delayed->reset();
/* Trigger the first frame start event */
- delayed->applyControls(startOffset);
+ delayed->applyControls(0);
/* Test dual control with delay. */
for (unsigned int i = 1; i < 100; i++) {
- uint32_t frame = startOffset + i;
int32_t value = 10 + i;
ctrls.set(V4L2_CID_BRIGHTNESS, value);
ctrls.set(V4L2_CID_CONTRAST, value + 1);
delayed->push(ctrls);
- delayed->applyControls(frame);
+ delayed->applyControls(i);
- ControlList result = delayed->get(frame);
+ ControlList result = delayed->get(i);
int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
if (brightness != expected || contrast != expected + 1) {
cerr << "Failed dual controls"
- << " frame " << frame
+ << " frame " << i
<< " brightness " << brightness
<< " contrast " << contrast
<< " expected " << expected
@@ -283,17 +282,7 @@ protected:
return ret;
/* Test dual controls with different delays. */
- ret = dualControlsWithDelay(0);
- if (ret)
- return ret;
-
- /* Test dual controls with non-zero sequence start. */
- ret = dualControlsWithDelay(10000);
- if (ret)
- return ret;
-
- /* Test dual controls with sequence number wraparound. */
- ret = dualControlsWithDelay(UINT32_MAX - 50);
+ ret = dualControlsWithDelay();
if (ret)
return ret;
diff --git a/test/event-thread.cpp b/test/event-thread.cpp
index ef8a52c3..d6e5d27a 100644
--- a/test/event-thread.cpp
+++ b/test/event-thread.cpp
@@ -11,6 +11,7 @@
#include <unistd.h>
#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
@@ -84,10 +85,17 @@ private:
class EventThreadTest : public Test
{
protected:
+ int init()
+ {
+ thread_.start();
+
+ handler_ = new EventHandler();
+
+ return TestPass;
+ }
+
int run()
{
- Thread thread;
- thread.start();
/*
* Fire the event notifier and then move the notifier to a
@@ -97,23 +105,33 @@ protected:
* different thread will correctly process already pending
* events in the new thread.
*/
- EventHandler handler;
- handler.notify();
- handler.moveToThread(&thread);
+ handler_->notify();
+ handler_->moveToThread(&thread_);
this_thread::sleep_for(chrono::milliseconds(100));
- /* Must stop thread before destroying the handler. */
- thread.exit(0);
- thread.wait();
-
- if (!handler.notified()) {
+ if (!handler_->notified()) {
cout << "Thread event handling test failed" << endl;
return TestFail;
}
return TestPass;
}
+
+ void cleanup()
+ {
+ /*
+ * Object class instances must be destroyed from the thread
+ * they live in.
+ */
+ handler_->deleteLater();
+ thread_.exit(0);
+ thread_.wait();
+ }
+
+private:
+ EventHandler *handler_;
+ Thread thread_;
};
TEST_REGISTER(EventThreadTest)
diff --git a/test/gstreamer/gstreamer_device_provider_test.cpp b/test/gstreamer/gstreamer_device_provider_test.cpp
new file mode 100644
index 00000000..237af8cd
--- /dev/null
+++ b/test/gstreamer/gstreamer_device_provider_test.cpp
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com>
+ *
+ * gstreamer_single_stream_test.cpp - GStreamer single stream capture test
+ */
+
+#include <vector>
+
+#include <libcamera/libcamera.h>
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerDeviceProviderTest : public GstreamerTest, public Test
+{
+public:
+ GstreamerDeviceProviderTest()
+ : GstreamerTest()
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ return TestPass;
+ }
+
+ int run() override
+ {
+ g_autoptr(GstDeviceProvider) provider = NULL;
+ GList *devices, *l;
+ std::vector<std::string> cameraNames;
+ std::unique_ptr<libcamera::CameraManager> cm;
+
+ cm = std::make_unique<libcamera::CameraManager>();
+ cm->start();
+ for (auto &camera : cm->cameras())
+ cameraNames.push_back(camera->id());
+ cm->stop();
+ cm.reset();
+
+ provider = gst_device_provider_factory_get_by_name("libcameraprovider");
+ devices = gst_device_provider_get_devices(provider);
+
+ for (l = devices; l != NULL; l = g_list_next(l)) {
+ GstDevice *device = GST_DEVICE(l->data);
+ g_autofree gchar *gst_name;
+ bool matched = false;
+
+ g_autoptr(GstElement) element = gst_device_create_element(device, NULL);
+ g_object_get(element, "camera-name", &gst_name, NULL);
+
+ for (auto name : cameraNames) {
+ if (strcmp(name.c_str(), gst_name) == 0) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched)
+ return TestFail;
+ }
+
+ g_list_free_full(devices, (GDestroyNotify)gst_object_unref);
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(GstreamerDeviceProviderTest)
diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp
index 112f1dee..cd669308 100644
--- a/test/gstreamer/gstreamer_multi_stream_test.cpp
+++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
@@ -29,7 +29,7 @@ class GstreamerMultiStreamTest : public GstreamerTest, public Test
{
public:
GstreamerMultiStreamTest()
- : GstreamerTest()
+ : GstreamerTest(2)
{
}
@@ -39,24 +39,6 @@ protected:
if (status_ != TestPass)
return status_;
- /* Check if platform supports multistream capture */
- libcamera::CameraManager cm;
- cm.start();
- bool cameraFound = false;
- for (auto &camera : cm.cameras()) {
- if (camera->streams().size() > 1) {
- cameraName_ = camera->id();
- cameraFound = true;
- cm.stop();
- break;
- }
- }
-
- if (!cameraFound) {
- cm.stop();
- return TestSkip;
- }
-
const gchar *streamDescription = "queue ! fakesink";
g_autoptr(GError) error = NULL;
@@ -88,8 +70,6 @@ protected:
int run() override
{
- g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL);
-
/* Build the pipeline */
gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
stream0_, stream1_, NULL);
@@ -124,7 +104,6 @@ protected:
}
private:
- std::string cameraName_;
GstElement *stream0_;
GstElement *stream1_;
};
diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp
index a0dd12cf..301e4a93 100644
--- a/test/gstreamer/gstreamer_single_stream_test.cpp
+++ b/test/gstreamer/gstreamer_single_stream_test.cpp
@@ -29,7 +29,7 @@ protected:
if (status_ != TestPass)
return status_;
- const gchar *streamDescription = "videoconvert ! fakesink";
+ const gchar *streamDescription = "fakesink";
g_autoptr(GError) error0 = NULL;
stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE,
NULL,
diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp
index 227a5c37..e8119b85 100644
--- a/test/gstreamer/gstreamer_test.cpp
+++ b/test/gstreamer/gstreamer_test.cpp
@@ -5,6 +5,10 @@
* libcamera Gstreamer element API tests
*/
+#include <libcamera/libcamera.h>
+
+#include <libcamera/base/utils.h>
+
#include "gstreamer_test.h"
#include "test.h"
@@ -23,19 +27,18 @@ const char *__asan_default_options()
}
}
-GstreamerTest::GstreamerTest()
+GstreamerTest::GstreamerTest(unsigned int numStreams)
: pipeline_(nullptr), libcameraSrc_(nullptr)
{
/*
- * GStreamer by default spawns a process to run the
- * gst-plugin-scanner helper. If libcamera is compiled with ASan
- * enabled, and as GStreamer is most likely not, this causes the
- * ASan link order check to fail when gst-plugin-scanner
- * dlopen()s the plugin as many libraries will have already been
- * loaded by then. Fix this issue by disabling spawning of a
- * child helper process when scanning the build directory for
- * plugins.
- */
+ * GStreamer by default spawns a process to run the gst-plugin-scanner
+ * helper. If libcamera is compiled with ASan enabled, and as GStreamer
+ * is most likely not, this causes the ASan link order check to fail
+ * when gst-plugin-scanner dlopen()s the plugin as many libraries will
+ * have already been loaded by then. Fix this issue by disabling
+ * spawning of a child helper process when scanning the build directory
+ * for plugins.
+ */
gst_registry_fork_set_enabled(false);
/* Initialize GStreamer */
@@ -49,25 +52,38 @@ GstreamerTest::GstreamerTest()
}
/*
- * Remove the system libcamera plugin, if any, and add the
- * plugin from the build directory.
- */
- GstRegistry *registry = gst_registry_get();
- g_autoptr(GstPlugin) plugin = gst_registry_lookup(registry, "libgstlibcamera.so");
- if (plugin)
- gst_registry_remove_plugin(registry, plugin);
-
- std::string path = libcamera::utils::libcameraBuildPath() + "src/gstreamer";
- if (!gst_registry_scan_path(registry, path.c_str())) {
- g_printerr("Failed to add plugin to registry\n");
-
- status_ = TestFail;
+ * Atleast one camera should be available with numStreams streams,
+ * otherwise skip the test entirely.
+ */
+ if (!checkMinCameraStreamsAndSetCameraName(numStreams)) {
+ status_ = TestSkip;
return;
}
status_ = TestPass;
}
+bool GstreamerTest::checkMinCameraStreamsAndSetCameraName(unsigned int numStreams)
+{
+ libcamera::CameraManager cm;
+ bool cameraFound = false;
+
+ cm.start();
+
+ for (auto &camera : cm.cameras()) {
+ if (camera->streams().size() < numStreams)
+ continue;
+
+ cameraFound = true;
+ cameraName_ = camera->id();
+ break;
+ }
+
+ cm.stop();
+
+ return cameraFound;
+}
+
GstreamerTest::~GstreamerTest()
{
g_clear_object(&pipeline_);
@@ -82,12 +98,13 @@ int GstreamerTest::createPipeline()
pipeline_ = gst_pipeline_new("test-pipeline");
if (!libcameraSrc_ || !pipeline_) {
- g_printerr("Unable to create create pipeline %p.%p\n",
+ g_printerr("Unable to create pipeline %p.%p\n",
libcameraSrc_, pipeline_);
return TestFail;
}
+ g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL);
g_object_ref_sink(libcameraSrc_);
return TestPass;
diff --git a/test/gstreamer/gstreamer_test.h b/test/gstreamer/gstreamer_test.h
index 9869d252..aa2261e2 100644
--- a/test/gstreamer/gstreamer_test.h
+++ b/test/gstreamer/gstreamer_test.h
@@ -10,16 +10,12 @@
#include <iostream>
#include <unistd.h>
-#include <libcamera/base/utils.h>
-
-#include "libcamera/internal/source_paths.h"
-
#include <gst/gst.h>
class GstreamerTest
{
public:
- GstreamerTest();
+ GstreamerTest(unsigned int numStreams = 1);
virtual ~GstreamerTest();
protected:
@@ -28,7 +24,11 @@ protected:
int processEvent();
void printError(GstMessage *msg);
+ std::string cameraName_;
GstElement *pipeline_;
GstElement *libcameraSrc_;
int status_;
+
+private:
+ bool checkMinCameraStreamsAndSetCameraName(unsigned int numStreams);
};
diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
index 10058fc5..f3ba5a23 100644
--- a/test/gstreamer/meson.build
+++ b/test/gstreamer/meson.build
@@ -5,16 +5,17 @@ if not gst_enabled
endif
gstreamer_tests = [
- ['single_stream_test', 'gstreamer_single_stream_test.cpp'],
- ['multi_stream_test', 'gstreamer_multi_stream_test.cpp'],
+ {'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']},
+ {'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']},
+ {'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']},
]
-gstreamer_dep = dependency('gstreamer-1.0', required: true)
+gstreamer_dep = dependency('gstreamer-1.0', required : true)
-foreach t : gstreamer_tests
- exe = executable(t[0], t[1], 'gstreamer_test.cpp',
+foreach test : gstreamer_tests
+ exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp',
dependencies : [libcamera_private, gstreamer_dep],
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'gstreamer', is_parallel : false)
+ test(test['name'], exe, suite : 'gstreamer', is_parallel : false, env : gst_env)
endforeach
diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
index 3c0df843..56f3cd6d 100644
--- a/test/ipa/ipa_interface_test.cpp
+++ b/test/ipa/ipa_interface_test.cpp
@@ -16,6 +16,7 @@
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
@@ -52,9 +53,9 @@ protected:
ipaManager_ = make_unique<IPAManager>();
/* Create a pipeline handler for vimc. */
- std::vector<PipelineHandlerFactory *> &factories =
- PipelineHandlerFactory::factories();
- for (PipelineHandlerFactory *factory : factories) {
+ const std::vector<PipelineHandlerFactoryBase *> &factories =
+ PipelineHandlerFactoryBase::factories();
+ for (const PipelineHandlerFactoryBase *factory : factories) {
if (factory->name() == "PipelineHandlerVimc") {
pipe_ = factory->create(nullptr);
break;
@@ -106,7 +107,11 @@ protected:
/* Test initialization of IPA module. */
std::string conf = ipa_->configurationFile("vimc.conf");
- int ret = ipa_->init(IPASettings{ conf, "vimc" });
+ Flags<ipa::vimc::TestFlag> inFlags;
+ Flags<ipa::vimc::TestFlag> outFlags;
+ int ret = ipa_->init(IPASettings{ conf, "vimc" },
+ ipa::vimc::IPAOperationInit,
+ inFlags, &outFlags);
if (ret < 0) {
cerr << "IPA interface init() failed" << endl;
return TestFail;
diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index 7938633e..180b0da0 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -1,15 +1,15 @@
# SPDX-License-Identifier: CC0-1.0
ipa_test = [
- ['ipa_module_test', 'ipa_module_test.cpp'],
- ['ipa_interface_test', 'ipa_interface_test.cpp'],
+ {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']},
+ {'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']},
]
-foreach t : ipa_test
- exe = executable(t[0], [t[1], libcamera_generated_ipa_headers],
+foreach test : ipa_test
+ exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers,
dependencies : libcamera_private,
link_with : [libipa, test_libraries],
include_directories : [libipa_includes, test_includes_internal])
- test(t[0], exe, suite : 'ipa')
+ test(test['name'], exe, suite : 'ipa')
endforeach
diff --git a/test/ipc/meson.build b/test/ipc/meson.build
index 2a6cd7fb..8e447d22 100644
--- a/test/ipc/meson.build
+++ b/test/ipc/meson.build
@@ -1,15 +1,15 @@
# SPDX-License-Identifier: CC0-1.0
ipc_tests = [
- ['unixsocket_ipc', 'unixsocket_ipc.cpp'],
- ['unixsocket', 'unixsocket.cpp'],
+ {'name': 'unixsocket_ipc', 'sources': ['unixsocket_ipc.cpp']},
+ {'name': 'unixsocket', 'sources': ['unixsocket.cpp']},
]
-foreach t : ipc_tests
- exe = executable(t[0], t[1],
+foreach test : ipc_tests
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'ipc')
+ test(test['name'], exe, suite : 'ipc')
endforeach
diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp
index 304e613b..1d4df287 100644
--- a/test/ipc/unixsocket.cpp
+++ b/test/ipc/unixsocket.cpp
@@ -431,7 +431,7 @@ private:
if (ret)
return ret;
- timeout.start(200ms);
+ timeout.start(2s);
while (!callDone_) {
if (!timeout.isRunning()) {
cerr << "Call timeout!" << endl;
diff --git a/test/libtest/buffer_source.cpp b/test/libtest/buffer_source.cpp
index 1b261697..dde11f36 100644
--- a/test/libtest/buffer_source.cpp
+++ b/test/libtest/buffer_source.cpp
@@ -72,7 +72,7 @@ int BufferSource::allocate(const StreamConfiguration &config)
}
format.size = config.size;
- format.fourcc = V4L2PixelFormat::fromPixelFormat(config.pixelFormat);
+ format.fourcc = video->toV4L2PixelFormat(config.pixelFormat);
if (video->setFormat(&format)) {
std::cout << "Failed to set format on output device" << std::endl;
return TestFail;
diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp
index 966b80cf..1926c560 100644
--- a/test/log/log_process.cpp
+++ b/test/log/log_process.cpp
@@ -81,12 +81,13 @@ protected:
return TestFail;
}
- timeout.start(200ms);
+ timeout.start(2s);
while (timeout.isRunning())
dispatcher->processEvents();
if (exitStatus_ != Process::NormalExit) {
- cerr << "process did not exit normally" << endl;
+ cerr << "process did not exit normally: " << exitStatus_
+ << endl;
return TestFail;
}
@@ -115,8 +116,11 @@ protected:
close(fd);
string str(buf);
- if (str.find(message) == string::npos)
+ if (str.find(message) == string::npos) {
+ cerr << "Received message is not correct (received "
+ << str.length() << " bytes)" << endl;
return TestFail;
+ }
return TestPass;
}
@@ -136,7 +140,7 @@ private:
ProcessManager processManager_;
Process proc_;
- Process::ExitStatus exitStatus_;
+ Process::ExitStatus exitStatus_ = Process::NotExited;
string logPath_;
int exitCode_;
int num_;
diff --git a/test/log/meson.build b/test/log/meson.build
index ac87841a..2298ff84 100644
--- a/test/log/meson.build
+++ b/test/log/meson.build
@@ -1,15 +1,15 @@
# SPDX-License-Identifier: CC0-1.0
log_test = [
- ['log_api', 'log_api.cpp'],
- ['log_process', 'log_process.cpp'],
+ {'name': 'log_api', 'sources': ['log_api.cpp']},
+ {'name': 'log_process', 'sources': ['log_process.cpp']},
]
-foreach t : log_test
- exe = executable(t[0], t[1],
+foreach test : log_test
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'log')
+ test(test['name'], exe, suite : 'log')
endforeach
diff --git a/test/media_device/meson.build b/test/media_device/meson.build
index 83dfe8f1..84966c97 100644
--- a/test/media_device/meson.build
+++ b/test/media_device/meson.build
@@ -5,20 +5,20 @@ lib_mdev_test_sources = files([
])
media_device_tests = [
- ['media_device_acquire', 'media_device_acquire.cpp'],
- ['media_device_print_test', 'media_device_print_test.cpp'],
- ['media_device_link_test', 'media_device_link_test.cpp'],
+ {'name': 'media_device_acquire', 'sources': ['media_device_acquire.cpp']},
+ {'name': 'media_device_print_test', 'sources': ['media_device_print_test.cpp']},
+ {'name': 'media_device_link_test', 'sources': ['media_device_link_test.cpp']},
]
lib_mdev_test = static_library('lib_mdev_test', lib_mdev_test_sources,
dependencies : libcamera_private,
include_directories : test_includes_internal)
-foreach t : media_device_tests
- exe = executable(t[0], t[1],
+foreach test : media_device_tests
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : [test_libraries, lib_mdev_test],
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'media_device', is_parallel : false)
+ test(test['name'], exe, suite : 'media_device', is_parallel : false)
endforeach
diff --git a/test/meson.build b/test/meson.build
index d050bfa1..8b6057d4 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,6 +7,22 @@ endif
test_enabled = true
+# When ASan is enabled, find the path to the ASan runtime needed by multiple
+# tests. This currently works with gcc only, as clang uses different file names
+# depending on the compiler version and target architecture.
+asan_enabled = false
+asan_runtime_missing = false
+
+if get_option('b_sanitize').contains('address')
+ asan_enabled = true
+
+ if cc.get_id() == 'gcc'
+ asan_runtime = run_command(cc, '-print-file-name=libasan.so', check : true).stdout().strip()
+ else
+ asan_runtime_missing = true
+ endif
+endif
+
subdir('libtest')
subdir('camera')
@@ -16,7 +32,6 @@ subdir('ipa')
subdir('ipc')
subdir('log')
subdir('media_device')
-subdir('pipeline')
subdir('process')
subdir('py')
subdir('serialization')
@@ -26,66 +41,86 @@ subdir('v4l2_subdevice')
subdir('v4l2_videodevice')
public_tests = [
- ['geometry', 'geometry.cpp'],
- ['public-api', 'public-api.cpp'],
- ['signal', 'signal.cpp'],
- ['span', 'span.cpp'],
+ {'name': 'color-space', 'sources': ['color-space.cpp']},
+ {'name': 'geometry', 'sources': ['geometry.cpp']},
+ {'name': 'public-api', 'sources': ['public-api.cpp']},
+ {'name': 'signal', 'sources': ['signal.cpp']},
+ {'name': 'span', 'sources': ['span.cpp']},
+ {'name': 'transform', 'sources': ['transform.cpp']},
]
internal_tests = [
- ['bayer-format', 'bayer-format.cpp'],
- ['byte-stream-buffer', 'byte-stream-buffer.cpp'],
- ['camera-sensor', 'camera-sensor.cpp'],
- ['delayed_controls', 'delayed_controls.cpp'],
- ['event', 'event.cpp'],
- ['event-dispatcher', 'event-dispatcher.cpp'],
- ['event-thread', 'event-thread.cpp'],
- ['file', 'file.cpp'],
- ['flags', 'flags.cpp'],
- ['hotplug-cameras', 'hotplug-cameras.cpp'],
- ['message', 'message.cpp'],
- ['object', 'object.cpp'],
- ['object-delete', 'object-delete.cpp'],
- ['object-invoke', 'object-invoke.cpp'],
- ['pixel-format', 'pixel-format.cpp'],
- ['shared-fd', 'shared-fd.cpp'],
- ['signal-threads', 'signal-threads.cpp'],
- ['threads', 'threads.cpp'],
- ['timer', 'timer.cpp'],
- ['timer-thread', 'timer-thread.cpp'],
- ['unique-fd', 'unique-fd.cpp'],
- ['utils', 'utils.cpp'],
- ['yaml-parser', 'yaml-parser.cpp'],
+ {'name': 'bayer-format', 'sources': ['bayer-format.cpp']},
+ {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']},
+ {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']},
+ {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']},
+ {'name': 'event', 'sources': ['event.cpp']},
+ {'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']},
+ {'name': 'event-thread', 'sources': ['event-thread.cpp']},
+ {'name': 'file', 'sources': ['file.cpp']},
+ {'name': 'flags', 'sources': ['flags.cpp']},
+ {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']},
+ {'name': 'message', 'sources': ['message.cpp']},
+ {'name': 'object', 'sources': ['object.cpp']},
+ {'name': 'object-delete', 'sources': ['object-delete.cpp']},
+ {'name': 'object-invoke', 'sources': ['object-invoke.cpp']},
+ {'name': 'pixel-format', 'sources': ['pixel-format.cpp']},
+ {'name': 'shared-fd', 'sources': ['shared-fd.cpp']},
+ {'name': 'signal-threads', 'sources': ['signal-threads.cpp']},
+ {'name': 'threads', 'sources': 'threads.cpp', 'dependencies': [libthreads]},
+ {'name': 'timer', 'sources': ['timer.cpp']},
+ {'name': 'timer-fail', 'sources': ['timer-fail.cpp'], 'should_fail': true},
+ {'name': 'timer-thread', 'sources': ['timer-thread.cpp']},
+ {'name': 'unique-fd', 'sources': ['unique-fd.cpp']},
+ {'name': 'utils', 'sources': ['utils.cpp']},
+ {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']},
]
internal_non_parallel_tests = [
- ['fence', 'fence.cpp'],
- ['mapped-buffer', 'mapped-buffer.cpp'],
+ {'name': 'fence', 'sources': ['fence.cpp']},
+ {'name': 'mapped-buffer', 'sources': ['mapped-buffer.cpp']},
]
-foreach t : public_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_public,
+foreach test : public_tests
+ deps = [libcamera_public]
+ if 'dependencies' in test
+ deps += test['dependencies']
+ endif
+
+ exe = executable(test['name'], test['sources'],
+ dependencies : deps,
link_with : test_libraries,
include_directories : test_includes_public)
- test(t[0], exe)
+ test(test['name'], exe, should_fail : test.get('should_fail', false))
endforeach
-foreach t : internal_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_private,
+foreach test : internal_tests
+ deps = [libcamera_private]
+ if 'dependencies' in test
+ deps += test['dependencies']
+ endif
+
+ exe = executable(test['name'], test['sources'],
+ dependencies : deps,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe)
+ test(test['name'], exe, should_fail : test.get('should_fail', false))
endforeach
-foreach t : internal_non_parallel_tests
- exe = executable(t[0], t[1],
- dependencies : libcamera_private,
+foreach test : internal_non_parallel_tests
+ deps = [libcamera_private]
+ if 'dependencies' in test
+ deps += test['dependencies']
+ endif
+
+ exe = executable(test['name'], test['sources'],
+ dependencies : deps,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, is_parallel : false)
+ test(test['name'], exe,
+ is_parallel : false,
+ should_fail : test.get('should_fail', false))
endforeach
diff --git a/test/message.cpp b/test/message.cpp
index d148a13d..2f9f281c 100644
--- a/test/message.cpp
+++ b/test/message.cpp
@@ -11,6 +11,7 @@
#include <thread>
#include <libcamera/base/message.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include "test.h"
@@ -92,25 +93,6 @@ private:
bool success_;
};
-class SlowMessageReceiver : public Object
-{
-protected:
- void message(Message *msg)
- {
- if (msg->type() != Message::None) {
- Object::message(msg);
- return;
- }
-
- /*
- * Don't access any member of the object here (including the
- * vtable) as the object will be deleted by the main thread
- * while we're sleeping.
- */
- this_thread::sleep_for(chrono::milliseconds(100));
- }
-};
-
class MessageTest : public Test
{
protected:
@@ -127,16 +109,19 @@ protected:
return TestFail;
}
- MessageReceiver receiver;
- receiver.moveToThread(&thread_);
+ MessageReceiver *receiver = new MessageReceiver();
+ receiver->moveToThread(&thread_);
thread_.start();
- receiver.postMessage(std::make_unique<Message>(Message::None));
+ receiver->postMessage(std::make_unique<Message>(Message::None));
this_thread::sleep_for(chrono::milliseconds(100));
- switch (receiver.status()) {
+ MessageReceiver::Status status = receiver->status();
+ receiver->deleteLater();
+
+ switch (status) {
case MessageReceiver::NoMessage:
cout << "No message received" << endl;
return TestFail;
@@ -148,28 +133,12 @@ protected:
}
/*
- * Test for races between message delivery and object deletion.
- * Failures result in assertion errors, there is no need for
- * explicit checks.
- */
- SlowMessageReceiver *slowReceiver = new SlowMessageReceiver();
- slowReceiver->moveToThread(&thread_);
- slowReceiver->postMessage(std::make_unique<Message>(Message::None));
-
- this_thread::sleep_for(chrono::milliseconds(10));
-
- delete slowReceiver;
-
- this_thread::sleep_for(chrono::milliseconds(100));
-
- /*
* Test recursive calls to Thread::dispatchMessages(). Messages
* should be delivered correctly, without crashes or memory
* leaks. Two messages need to be posted to ensure we don't only
* test the simple case of a queue containing a single message.
*/
- std::unique_ptr<RecursiveMessageReceiver> recursiveReceiver =
- std::make_unique<RecursiveMessageReceiver>();
+ RecursiveMessageReceiver *recursiveReceiver = new RecursiveMessageReceiver();
recursiveReceiver->moveToThread(&thread_);
recursiveReceiver->postMessage(std::make_unique<Message>(Message::None));
@@ -177,7 +146,10 @@ protected:
this_thread::sleep_for(chrono::milliseconds(10));
- if (!recursiveReceiver->success()) {
+ bool success = recursiveReceiver->success();
+ recursiveReceiver->deleteLater();
+
+ if (!success) {
cout << "Recursive message delivery failed" << endl;
return TestFail;
}
diff --git a/test/object-delete.cpp b/test/object-delete.cpp
index eabefe93..80b7dc41 100644
--- a/test/object-delete.cpp
+++ b/test/object-delete.cpp
@@ -33,10 +33,10 @@ public:
unsigned int *deleteCount_;
};
-class NewThread : public Thread
+class DeleterThread : public Thread
{
public:
- NewThread(Object *obj)
+ DeleterThread(Object *obj)
: object_(obj)
{
}
@@ -63,9 +63,9 @@ protected:
unsigned int count = 0;
TestObject *obj = new TestObject(&count);
- NewThread thread(obj);
- thread.start();
- thread.wait();
+ DeleterThread delThread(obj);
+ delThread.start();
+ delThread.wait();
Thread::current()->dispatchMessages(Message::Type::DeferredDelete);
@@ -89,6 +89,26 @@ protected:
return TestFail;
}
+ /*
+ * Test that deleteLater() works properly when called just
+ * before the object's thread exits.
+ */
+ Thread boundThread;
+ boundThread.start();
+
+ count = 0;
+ obj = new TestObject(&count);
+ obj->moveToThread(&boundThread);
+
+ obj->deleteLater();
+ boundThread.exit();
+ boundThread.wait();
+
+ if (count != 1) {
+ cout << "Object deletion right before thread exit failed (" << count << ")" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
};
diff --git a/test/pipeline/ipu3/ipu3_pipeline_test.cpp b/test/pipeline/ipu3/ipu3_pipeline_test.cpp
deleted file mode 100644
index 9e647af5..00000000
--- a/test/pipeline/ipu3/ipu3_pipeline_test.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * ipu3_pipeline_test.cpp - Intel IPU3 pipeline test
- */
-
-#include <iostream>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-
-#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/media_device.h"
-#include "libcamera/internal/media_object.h"
-
-#include "test.h"
-
-using namespace std;
-using namespace libcamera;
-
-/*
- * Verify that the Intel IPU3 pipeline handler gets matched and cameras
- * are enumerated correctly.
- *
- * The test is supposed to be run on an IPU3 platform, otherwise it gets
- * skipped.
- *
- * The test lists all cameras registered in the system, if any camera is
- * available at all.
- */
-class IPU3PipelineTest : public Test
-{
-protected:
- int init();
- int run();
- void cleanup();
-
-private:
- CameraManager *cameraManager_;
- unsigned int sensors_;
-};
-
-int IPU3PipelineTest::init()
-{
- unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create();
- if (!enumerator) {
- cerr << "Failed to create device enumerator" << endl;
- return TestFail;
- }
-
- if (enumerator->enumerate()) {
- cerr << "Failed to enumerate media devices" << endl;
- return TestFail;
- }
-
- DeviceMatch imgu_dm("ipu3-imgu");
- DeviceMatch cio2_dm("ipu3-cio2");
-
- if (!enumerator->search(imgu_dm)) {
- cerr << "Failed to find IPU3 IMGU: test skip" << endl;
- return TestSkip;
- }
-
- std::shared_ptr<MediaDevice> cio2 = enumerator->search(cio2_dm);
- if (!cio2) {
- cerr << "Failed to find IPU3 CIO2: test skip" << endl;
- return TestSkip;
- }
-
- /*
- * Camera sensor are connected to the CIO2 unit.
- * Count how many sensors are connected in the system
- * and later verify this matches the number of registered
- * cameras.
- */
- int ret = cio2->populate();
- if (ret) {
- cerr << "Failed to populate media device " << cio2->deviceNode() << endl;
- return TestFail;
- }
-
- sensors_ = 0;
- const vector<MediaEntity *> &entities = cio2->entities();
- for (MediaEntity *entity : entities) {
- if (entity->function() == MEDIA_ENT_F_CAM_SENSOR)
- sensors_++;
- }
-
- enumerator.reset(nullptr);
-
- cameraManager_ = new CameraManager();
- ret = cameraManager_->start();
- if (ret) {
- cerr << "Failed to start the CameraManager" << endl;
- return TestFail;
- }
-
- return 0;
-}
-
-int IPU3PipelineTest::run()
-{
- auto cameras = cameraManager_->cameras();
- for (const std::shared_ptr<Camera> &cam : cameras)
- cout << "Found camera '" << cam->id() << "'" << endl;
-
- if (cameras.size() != sensors_) {
- cerr << cameras.size() << " cameras registered, but " << sensors_
- << " were expected" << endl;
- return TestFail;
- }
-
- return TestPass;
-}
-
-void IPU3PipelineTest::cleanup()
-{
- cameraManager_->stop();
- delete cameraManager_;
-}
-
-TEST_REGISTER(IPU3PipelineTest)
diff --git a/test/pipeline/ipu3/meson.build b/test/pipeline/ipu3/meson.build
deleted file mode 100644
index 16701080..00000000
--- a/test/pipeline/ipu3/meson.build
+++ /dev/null
@@ -1,14 +0,0 @@
-# SPDX-License-Identifier: CC0-1.0
-
-ipu3_test = [
- ['ipu3_pipeline_test', 'ipu3_pipeline_test.cpp'],
-]
-
-foreach t : ipu3_test
- exe = executable(t[0], t[1],
- dependencies : libcamera_private,
- link_with : test_libraries,
- include_directories : test_includes_internal)
-
- test(t[0], exe, suite : 'ipu3', is_parallel : false)
-endforeach
diff --git a/test/pipeline/rkisp1/meson.build b/test/pipeline/rkisp1/meson.build
deleted file mode 100644
index 364b5711..00000000
--- a/test/pipeline/rkisp1/meson.build
+++ /dev/null
@@ -1,14 +0,0 @@
-# SPDX-License-Identifier: CC0-1.0
-
-rkisp1_test = [
- ['rkisp1_pipeline_test', 'rkisp1_pipeline_test.cpp'],
-]
-
-foreach t : rkisp1_test
- exe = executable(t[0], t[1],
- dependencies : libcamera_private,
- link_with : test_libraries,
- include_directories : test_includes_internal)
-
- test(t[0], exe, suite : 'rkisp1', is_parallel : false)
-endforeach
diff --git a/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp b/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp
deleted file mode 100644
index acaf3c33..00000000
--- a/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2020, Linaro
- *
- * Based on test/pipeline/ipu3/ipu3_pipeline_test.cpp
- *
- * rkisp1_pipeline_test.cpp - Rockchip RK3399 rkisp1 pipeline test
- */
-
-#include <iostream>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/camera_manager.h>
-
-#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/media_device.h"
-#include "libcamera/internal/media_object.h"
-
-#include "test.h"
-
-using namespace std;
-using namespace libcamera;
-
-/*
- * Verify that the RK3399 pipeline handler gets matched and cameras
- * are enumerated correctly.
- *
- * The test is supposed to be run on rockchip platform.
- *
- * The test lists all cameras registered in the system, if any camera is
- * available at all.
- */
-class RKISP1PipelineTest : public Test
-{
-protected:
- int init();
- int run();
- void cleanup();
-
-private:
- CameraManager *cameraManager_;
- unsigned int sensors_;
-};
-
-int RKISP1PipelineTest::init()
-{
- unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create();
- if (!enumerator) {
- cerr << "Failed to create device enumerator" << endl;
- return TestFail;
- }
-
- if (enumerator->enumerate()) {
- cerr << "Failed to enumerate media devices" << endl;
- return TestFail;
- }
-
- DeviceMatch dm("rkisp1");
-
- std::shared_ptr<MediaDevice> rkisp1 = enumerator->search(dm);
- if (!rkisp1) {
- cerr << "Failed to find rkisp1: test skip" << endl;
- return TestSkip;
- }
-
- int ret = rkisp1->populate();
- if (ret) {
- cerr << "Failed to populate media device "
- << rkisp1->deviceNode() << endl;
- return TestFail;
- }
-
- sensors_ = 0;
- const vector<MediaEntity *> &entities = rkisp1->entities();
- for (MediaEntity *entity : entities) {
- if (entity->function() == MEDIA_ENT_F_CAM_SENSOR)
- sensors_++;
- }
-
- cameraManager_ = new CameraManager();
- ret = cameraManager_->start();
- if (ret) {
- cerr << "Failed to start the CameraManager" << endl;
- return TestFail;
- }
-
- return 0;
-}
-
-int RKISP1PipelineTest::run()
-{
- auto cameras = cameraManager_->cameras();
- for (const std::shared_ptr<Camera> &cam : cameras)
- cout << "Found camera '" << cam->id() << "'" << endl;
-
- if (cameras.size() != sensors_) {
- cerr << cameras.size() << " cameras registered, but " << sensors_
- << " were expected" << endl;
- return TestFail;
- }
-
- return TestPass;
-}
-
-void RKISP1PipelineTest::cleanup()
-{
- cameraManager_->stop();
- delete cameraManager_;
-}
-
-TEST_REGISTER(RKISP1PipelineTest)
diff --git a/test/process/meson.build b/test/process/meson.build
index af86b277..a80dc2d9 100644
--- a/test/process/meson.build
+++ b/test/process/meson.build
@@ -1,14 +1,14 @@
# SPDX-License-Identifier: CC0-1.0
process_tests = [
- ['process_test', 'process_test.cpp'],
+ {'name': 'process_test', 'sources': ['process_test.cpp']},
]
-foreach t : process_tests
- exe = executable(t[0], t[1],
+foreach test : process_tests
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'process', is_parallel : false)
+ test(test['name'], exe, suite : 'process', is_parallel : false)
endforeach
diff --git a/test/py/meson.build b/test/py/meson.build
index 2affdbd4..0b679d31 100644
--- a/test/py/meson.build
+++ b/test/py/meson.build
@@ -4,14 +4,29 @@ if not pycamera_enabled
subdir_done()
endif
+# If ASan is enabled, the link order runtime check will fail as Python is not
+# linked to ASan. LD_PRELOAD the ASan runtime if available, or skip the test
+# otherwise.
+
+if asan_runtime_missing
+ warning('Unable to get path to ASan runtime, Python test disabled')
+ subdir_done()
+endif
+
pymod = import('python')
py3 = pymod.find_installation('python3')
pypathdir = meson.project_build_root() / 'src' / 'py'
+py_env = ['PYTHONPATH=' + pypathdir]
+
+if asan_enabled
+ # Disable leak detection as the Python interpreter is full of leaks.
+ py_env += ['LD_PRELOAD=' + asan_runtime, 'ASAN_OPTIONS=detect_leaks=0']
+endif
test('pyunittests',
py3,
args : files('unittests.py'),
- env : ['PYTHONPATH=' + pypathdir],
+ env : py_env,
suite : 'pybindings',
is_parallel : false)
diff --git a/test/py/unittests.py b/test/py/unittests.py
index 9adc4337..1caea98e 100755
--- a/test/py/unittests.py
+++ b/test/py/unittests.py
@@ -4,11 +4,9 @@
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
from collections import defaultdict
-import errno
import gc
import libcamera as libcam
import selectors
-import time
import typing
import unittest
import weakref
@@ -18,6 +16,18 @@ class BaseTestCase(unittest.TestCase):
def assertZero(self, a, msg=None):
self.assertEqual(a, 0, msg)
+ def assertIsAlive(self, wr, msg='object not alive'):
+ self.assertIsNotNone(wr(), msg)
+
+ def assertIsDead(self, wr, msg='object not dead'):
+ self.assertIsNone(wr(), msg)
+
+ def assertIsAllAlive(self, wr_list, msg='object not alive'):
+ self.assertTrue(all([wr() for wr in wr_list]), msg)
+
+ def assertIsAllDead(self, wr_list, msg='object not dead'):
+ self.assertTrue(all([not wr() for wr in wr_list]), msg)
+
class SimpleTestMethods(BaseTestCase):
def test_get_ref(self):
@@ -28,45 +38,44 @@ class SimpleTestMethods(BaseTestCase):
self.assertIsNotNone(cam)
wr_cam = weakref.ref(cam)
- cm = None
+ del cm
gc.collect()
- self.assertIsNotNone(wr_cm())
+ self.assertIsAlive(wr_cm)
- cam = None
+ del cam
gc.collect()
- self.assertIsNone(wr_cm())
- self.assertIsNone(wr_cam())
+ self.assertIsDead(wr_cm)
+ self.assertIsDead(wr_cam)
def test_acquire_release(self):
cm = libcam.CameraManager.singleton()
cam = cm.get('platform/vimc.0 Sensor B')
self.assertIsNotNone(cam)
- ret = cam.acquire()
- self.assertZero(ret)
+ cam.acquire()
- ret = cam.release()
- self.assertZero(ret)
+ cam.release()
def test_double_acquire(self):
cm = libcam.CameraManager.singleton()
cam = cm.get('platform/vimc.0 Sensor B')
self.assertIsNotNone(cam)
- ret = cam.acquire()
- self.assertZero(ret)
+ cam.acquire()
libcam.log_set_level('Camera', 'FATAL')
- ret = cam.acquire()
- self.assertEqual(ret, -errno.EBUSY)
+ with self.assertRaises(RuntimeError):
+ cam.acquire()
libcam.log_set_level('Camera', 'ERROR')
- ret = cam.release()
- self.assertZero(ret)
+ cam.release()
- ret = cam.release()
- # I expected EBUSY, but looks like double release works fine
- self.assertZero(ret)
+ # I expected exception here, but looks like double release works fine
+ cam.release()
+
+ def test_version(self):
+ cm = libcam.CameraManager.singleton()
+ self.assertIsInstance(cm.version, str)
class CameraTesterBase(BaseTestCase):
@@ -80,11 +89,7 @@ class CameraTesterBase(BaseTestCase):
self.cm = None
self.skipTest('No vimc found')
- ret = self.cam.acquire()
- if ret != 0:
- self.cam = None
- self.cm = None
- raise Exception('Failed to acquire camera')
+ self.cam.acquire()
self.wr_cam = weakref.ref(self.cam)
self.wr_cm = weakref.ref(self.cm)
@@ -93,15 +98,13 @@ class CameraTesterBase(BaseTestCase):
# If a test fails, the camera may be in running state. So always stop.
self.cam.stop()
- ret = self.cam.release()
- if ret != 0:
- raise Exception('Failed to release camera')
+ self.cam.release()
self.cam = None
self.cm = None
- self.assertIsNone(self.wr_cm())
- self.assertIsNone(self.wr_cam())
+ self.assertIsDead(self.wr_cm)
+ self.assertIsDead(self.wr_cam)
class AllocatorTestMethods(CameraTesterBase):
@@ -115,49 +118,48 @@ class AllocatorTestMethods(CameraTesterBase):
streamconfig = camconfig.at(0)
wr_streamconfig = weakref.ref(streamconfig)
- ret = cam.configure(camconfig)
- self.assertZero(ret)
+ cam.configure(camconfig)
stream = streamconfig.stream
wr_stream = weakref.ref(stream)
# stream should keep streamconfig and camconfig alive
- streamconfig = None
- camconfig = None
+ del streamconfig
+ del camconfig
gc.collect()
- self.assertIsNotNone(wr_camconfig())
- self.assertIsNotNone(wr_streamconfig())
+ self.assertIsAlive(wr_camconfig)
+ self.assertIsAlive(wr_streamconfig)
allocator = libcam.FrameBufferAllocator(cam)
- ret = allocator.allocate(stream)
- self.assertTrue(ret > 0)
+ num_bufs = allocator.allocate(stream)
+ self.assertTrue(num_bufs > 0)
wr_allocator = weakref.ref(allocator)
buffers = allocator.buffers(stream)
self.assertIsNotNone(buffers)
- buffers = None
+ del buffers
buffer = allocator.buffers(stream)[0]
self.assertIsNotNone(buffer)
wr_buffer = weakref.ref(buffer)
- allocator = None
+ del allocator
gc.collect()
- self.assertIsNotNone(wr_buffer())
- self.assertIsNotNone(wr_allocator())
- self.assertIsNotNone(wr_stream())
+ self.assertIsAlive(wr_buffer)
+ self.assertIsAlive(wr_allocator)
+ self.assertIsAlive(wr_stream)
- buffer = None
+ del buffer
gc.collect()
- self.assertIsNone(wr_buffer())
- self.assertIsNone(wr_allocator())
- self.assertIsNotNone(wr_stream())
+ self.assertIsDead(wr_buffer)
+ self.assertIsDead(wr_allocator)
+ self.assertIsAlive(wr_stream)
- stream = None
+ del stream
gc.collect()
- self.assertIsNone(wr_stream())
- self.assertIsNone(wr_camconfig())
- self.assertIsNone(wr_streamconfig())
+ self.assertIsDead(wr_stream)
+ self.assertIsDead(wr_camconfig)
+ self.assertIsDead(wr_streamconfig)
class SimpleCaptureMethods(CameraTesterBase):
@@ -173,14 +175,13 @@ class SimpleCaptureMethods(CameraTesterBase):
self.assertIsNotNone(fmts)
fmts = None
- ret = cam.configure(camconfig)
- self.assertZero(ret)
+ cam.configure(camconfig)
stream = streamconfig.stream
allocator = libcam.FrameBufferAllocator(cam)
- ret = allocator.allocate(stream)
- self.assertTrue(ret > 0)
+ num_bufs = allocator.allocate(stream)
+ self.assertTrue(num_bufs > 0)
num_bufs = len(allocator.buffers(stream))
@@ -190,26 +191,30 @@ class SimpleCaptureMethods(CameraTesterBase):
self.assertIsNotNone(req)
buffer = allocator.buffers(stream)[i]
- ret = req.add_buffer(stream, buffer)
- self.assertZero(ret)
+ req.add_buffer(stream, buffer)
reqs.append(req)
buffer = None
- ret = cam.start()
- self.assertZero(ret)
+ cam.start()
for req in reqs:
- ret = cam.queue_request(req)
- self.assertZero(ret)
+ cam.queue_request(req)
reqs = None
gc.collect()
+ sel = selectors.DefaultSelector()
+ sel.register(cm.event_fd, selectors.EVENT_READ)
+
reqs = []
while True:
+ events = sel.select()
+ if not events:
+ continue
+
ready_reqs = cm.get_ready_requests()
reqs += ready_reqs
@@ -223,8 +228,7 @@ class SimpleCaptureMethods(CameraTesterBase):
reqs = None
gc.collect()
- ret = cam.stop()
- self.assertZero(ret)
+ cam.stop()
def test_select(self):
cm = self.cm
@@ -238,14 +242,13 @@ class SimpleCaptureMethods(CameraTesterBase):
self.assertIsNotNone(fmts)
fmts = None
- ret = cam.configure(camconfig)
- self.assertZero(ret)
+ cam.configure(camconfig)
stream = streamconfig.stream
allocator = libcam.FrameBufferAllocator(cam)
- ret = allocator.allocate(stream)
- self.assertTrue(ret > 0)
+ num_bufs = allocator.allocate(stream)
+ self.assertTrue(num_bufs > 0)
num_bufs = len(allocator.buffers(stream))
@@ -255,19 +258,16 @@ class SimpleCaptureMethods(CameraTesterBase):
self.assertIsNotNone(req)
buffer = allocator.buffers(stream)[i]
- ret = req.add_buffer(stream, buffer)
- self.assertZero(ret)
+ req.add_buffer(stream, buffer)
reqs.append(req)
buffer = None
- ret = cam.start()
- self.assertZero(ret)
+ cam.start()
for req in reqs:
- ret = cam.queue_request(req)
- self.assertZero(ret)
+ cam.queue_request(req)
reqs = None
gc.collect()
@@ -280,7 +280,7 @@ class SimpleCaptureMethods(CameraTesterBase):
running = True
while running:
events = sel.select()
- for key, _ in events:
+ for _ in events:
ready_reqs = cm.get_ready_requests()
reqs += ready_reqs
@@ -296,8 +296,7 @@ class SimpleCaptureMethods(CameraTesterBase):
reqs = None
gc.collect()
- ret = cam.stop()
- self.assertZero(ret)
+ cam.stop()
# Recursively expand slist's objects into olist, using seen to track already
diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp
index 698c81d6..4670fe46 100644
--- a/test/serialization/generated_serializer/generated_serializer_test.cpp
+++ b/test/serialization/generated_serializer/generated_serializer_test.cpp
@@ -35,6 +35,13 @@ if (struct1.field != struct2.field) { \
return TestFail; \
}
+#define TEST_SCOPED_ENUM_EQUALITY(struct1, struct2, field) \
+if (struct1.field != struct2.field) { \
+ cerr << #field << " field incorrect" << endl; \
+ return TestFail; \
+}
+
+
ipa::test::TestStruct t, u;
t.m = {
@@ -51,6 +58,13 @@ if (struct1.field != struct2.field) { \
t.s2 = "goodbye";
t.s3 = "lorem ipsum";
t.i = 58527;
+ t.c = ipa::test::IPAOperationInit;
+ t.e = ipa::test::ErrorFlags::Error1;
+
+ Flags<ipa::test::ErrorFlags> flags;
+ flags |= ipa::test::ErrorFlags::Error1;
+ flags |= ipa::test::ErrorFlags::Error2;
+ t.f = flags;
std::vector<uint8_t> serialized;
@@ -69,7 +83,10 @@ if (struct1.field != struct2.field) { \
TEST_FIELD_EQUALITY(t, u, s2);
TEST_FIELD_EQUALITY(t, u, s3);
TEST_FIELD_EQUALITY(t, u, i);
+ TEST_FIELD_EQUALITY(t, u, c);
+ TEST_SCOPED_ENUM_EQUALITY(t, u, e);
+ TEST_SCOPED_ENUM_EQUALITY(t, u, f);
/* Test vector of generated structs */
std::vector<ipa::test::TestStruct> v = { t, u };
@@ -92,11 +109,19 @@ if (struct1.field != struct2.field) { \
TEST_FIELD_EQUALITY(v[0], w[0], s2);
TEST_FIELD_EQUALITY(v[0], w[0], s3);
TEST_FIELD_EQUALITY(v[0], w[0], i);
+ TEST_FIELD_EQUALITY(v[0], w[0], c);
+
+ TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], e);
+ TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], f);
TEST_FIELD_EQUALITY(v[1], w[1], s1);
TEST_FIELD_EQUALITY(v[1], w[1], s2);
TEST_FIELD_EQUALITY(v[1], w[1], s3);
TEST_FIELD_EQUALITY(v[1], w[1], i);
+ TEST_FIELD_EQUALITY(v[1], w[1], c);
+
+ TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], e);
+ TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], f);
return TestPass;
}
diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom b/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom
index 5f200885..91c31642 100644
--- a/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom
+++ b/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom
@@ -9,6 +9,13 @@ enum IPAOperationCode {
IPAOperationStop,
};
+[scopedEnum] enum ErrorFlags {
+ Error1 = 0x1,
+ Error2 = 0x2,
+ Error3 = 0x4,
+ Error4 = 0x8,
+};
+
struct IPASettings {};
struct TestStruct {
@@ -18,6 +25,9 @@ struct TestStruct {
string s2;
int32 i;
string s3;
+ IPAOperationCode c;
+ ErrorFlags e;
+ [flags] ErrorFlags f;
};
interface IPATestInterface {
diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp
index d2050a86..377ecdb0 100644
--- a/test/serialization/ipa_data_serializer_test.cpp
+++ b/test/serialization/ipa_data_serializer_test.cpp
@@ -20,11 +20,7 @@
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
-#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/ipa_data_serializer.h"
-#include "libcamera/internal/ipa_manager.h"
-#include "libcamera/internal/ipa_module.h"
-#include "libcamera/internal/pipeline_handler.h"
#include "serialization_test.h"
#include "test.h"
diff --git a/test/serialization/meson.build b/test/serialization/meson.build
index 26e42b15..a6e8d793 100644
--- a/test/serialization/meson.build
+++ b/test/serialization/meson.build
@@ -3,14 +3,14 @@
subdir('generated_serializer')
serialization_tests = [
- ['control_serialization', 'control_serialization.cpp'],
- ['ipa_data_serializer_test', 'ipa_data_serializer_test.cpp'],
+ {'name': 'control_serialization', 'sources': ['control_serialization.cpp']},
+ {'name': 'ipa_data_serializer_test', 'sources': ['ipa_data_serializer_test.cpp']},
]
-foreach t : serialization_tests
- exe = executable(t[0], [t[1], 'serialization_test.cpp'],
+foreach test : serialization_tests
+ exe = executable(test['name'], test['sources'], 'serialization_test.cpp',
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'serialization', is_parallel : false)
+ test(test['name'], exe, suite : 'serialization', is_parallel : false)
endforeach
diff --git a/test/signal-threads.cpp b/test/signal-threads.cpp
index d5e2eb66..8c212b6f 100644
--- a/test/signal-threads.cpp
+++ b/test/signal-threads.cpp
@@ -10,6 +10,7 @@
#include <thread>
#include <libcamera/base/message.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/utils.h>
@@ -58,15 +59,20 @@ private:
class SignalThreadsTest : public Test
{
protected:
- int run()
+ int init()
{
- SignalReceiver receiver;
- signal_.connect(&receiver, &SignalReceiver::slot);
+ receiver_ = new SignalReceiver();
+ signal_.connect(receiver_, &SignalReceiver::slot);
+
+ return TestPass;
+ }
+ int run()
+ {
/* Test that a signal is received in the main thread. */
signal_.emit(0);
- switch (receiver.status()) {
+ switch (receiver_->status()) {
case SignalReceiver::NoSignal:
cout << "No signal received for direct connection" << endl;
return TestFail;
@@ -82,8 +88,8 @@ protected:
* Move the object to a thread and verify that the signal is
* correctly delivered, with the correct data.
*/
- receiver.reset();
- receiver.moveToThread(&thread_);
+ receiver_->reset();
+ receiver_->moveToThread(&thread_);
thread_.start();
@@ -91,7 +97,7 @@ protected:
this_thread::sleep_for(chrono::milliseconds(100));
- switch (receiver.status()) {
+ switch (receiver_->status()) {
case SignalReceiver::NoSignal:
cout << "No signal received for message connection" << endl;
return TestFail;
@@ -103,7 +109,7 @@ protected:
break;
}
- if (receiver.value() != 42) {
+ if (receiver_->value() != 42) {
cout << "Signal received with incorrect value" << endl;
return TestFail;
}
@@ -113,11 +119,13 @@ protected:
void cleanup()
{
+ receiver_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
+ SignalReceiver *receiver_;
Thread thread_;
Signal<int> signal_;
diff --git a/test/stream/meson.build b/test/stream/meson.build
index 73608ffd..dd77f2f7 100644
--- a/test/stream/meson.build
+++ b/test/stream/meson.build
@@ -1,13 +1,14 @@
# SPDX-License-Identifier: CC0-1.0
stream_tests = [
- ['stream_formats', 'stream_formats.cpp'],
+ {'name': 'stream_colorspace', 'sources': ['stream_colorspace.cpp']},
+ {'name': 'stream_formats', 'sources': ['stream_formats.cpp']},
]
-foreach t : stream_tests
- exe = executable(t[0], t[1],
+foreach test : stream_tests
+ exe = executable(test['name'], test['sources'],
dependencies : libcamera_public,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite: 'stream')
+ test(test['name'], exe, suite : 'stream')
endforeach
diff --git a/test/stream/stream_colorspace.cpp b/test/stream/stream_colorspace.cpp
new file mode 100644
index 00000000..1b7afe65
--- /dev/null
+++ b/test/stream/stream_colorspace.cpp
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy.
+ *
+ * stream_colorspace.cpp - Stream colorspace adjustment test
+ */
+
+#include <iostream>
+
+#include <libcamera/camera.h>
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class TestCameraConfiguration : public CameraConfiguration
+{
+public:
+ TestCameraConfiguration()
+ : CameraConfiguration()
+ {
+ }
+
+ Status validate() override
+ {
+ return validateColorSpaces();
+ }
+};
+
+class StreamColorSpaceTest : public Test
+{
+protected:
+ int run()
+ {
+ TestCameraConfiguration config;
+
+ StreamConfiguration cfg;
+ cfg.size = { 640, 320 };
+ cfg.pixelFormat = formats::YUV422;
+ cfg.colorSpace = ColorSpace::Srgb;
+ config.addConfiguration(cfg);
+
+ StreamConfiguration &streamCfg = config.at(0);
+
+ /*
+ * YUV pixelformat with sRGB colorspace should have Y'CbCr encoding
+ * adjusted.
+ */
+ config.validate();
+ if (streamCfg.colorSpace->ycbcrEncoding == ColorSpace::YcbcrEncoding::None) {
+ cerr << "YUV format must have YCbCr encoding" << endl;
+ return TestFail;
+ }
+
+ /*
+ * For YUV pixelFormat, encoding should be picked up according
+ * to primaries and transfer function, if 'None' is specified.
+ */
+ streamCfg.pixelFormat = formats::YUV422;
+ streamCfg.colorSpace = ColorSpace(ColorSpace::Primaries::Rec2020,
+ ColorSpace::TransferFunction::Rec709,
+ ColorSpace::YcbcrEncoding::None,
+ ColorSpace::Range::Limited);
+ config.validate();
+ if (streamCfg.colorSpace->ycbcrEncoding != ColorSpace::YcbcrEncoding::Rec2020) {
+ cerr << "Failed to adjust colorspace Y'CbCr encoding according"
+ << " to primaries and transfer function" << endl;
+ return TestFail;
+ }
+
+ /* For RGB pixelFormat, Sycc colorspace should get adjusted to sRGB. */
+ streamCfg.pixelFormat = formats::RGB888;
+ streamCfg.colorSpace = ColorSpace::Sycc;
+ config.validate();
+ if (streamCfg.colorSpace != ColorSpace::Srgb) {
+ cerr << "RGB format's colorspace should be set to Srgb" << endl;
+ return TestFail;
+ }
+
+ /* Raw formats should always set colorspace to ColorSpace::Raw. */
+ streamCfg.pixelFormat = formats::SBGGR8;
+ streamCfg.colorSpace = ColorSpace::Rec709;
+ config.validate();
+ if (streamCfg.colorSpace != ColorSpace::Raw) {
+ cerr << "Raw format must always have Raw colorspace" << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(StreamColorSpaceTest)
diff --git a/test/threads.cpp b/test/threads.cpp
index d83b5833..8f366c9d 100644
--- a/test/threads.cpp
+++ b/test/threads.cpp
@@ -8,7 +8,9 @@
#include <chrono>
#include <iostream>
#include <memory>
+#include <pthread.h>
#include <thread>
+#include <time.h>
#include <libcamera/base/thread.h>
@@ -35,6 +37,35 @@ private:
chrono::steady_clock::duration duration_;
};
+class CancelThread : public Thread
+{
+public:
+ CancelThread(bool &cancelled)
+ : cancelled_(cancelled)
+ {
+ }
+
+protected:
+ void run()
+ {
+ cancelled_ = true;
+
+ /*
+ * Cancel the thread and call a guaranteed cancellation point
+ * (nanosleep).
+ */
+ pthread_cancel(pthread_self());
+
+ struct timespec req{ 0, 100*000*000 };
+ nanosleep(&req, nullptr);
+
+ cancelled_ = false;
+ }
+
+private:
+ bool &cancelled_;
+};
+
class ThreadTest : public Test
{
protected:
@@ -118,6 +149,22 @@ protected:
return TestFail;
}
+ /* Test thread cleanup upon abnormal termination. */
+ bool cancelled = false;
+ bool finished = false;
+
+ thread = std::make_unique<CancelThread>(cancelled);
+ thread->finished.connect(this, [&finished]() { finished = true; });
+
+ thread->start();
+ thread->exit(0);
+ thread->wait(chrono::milliseconds(1000));
+
+ if (!cancelled || !finished) {
+ cout << "Cleanup failed upon abnormal termination" << endl;
+ return TestFail;
+ }
+
return TestPass;
}
diff --git a/test/timer-fail.cpp b/test/timer-fail.cpp
new file mode 100644
index 00000000..82854b89
--- /dev/null
+++ b/test/timer-fail.cpp
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * timer-fail.cpp - Threaded timer failure test
+ */
+
+#include <chrono>
+#include <iostream>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+using namespace std::chrono_literals;
+
+class TimeoutHandler : public Object
+{
+public:
+ TimeoutHandler()
+ : timer_(this), timeout_(false)
+ {
+ timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler);
+ }
+
+ void start()
+ {
+ timer_.start(100ms);
+ }
+
+ bool timeout() const
+ {
+ return timeout_;
+ }
+
+private:
+ void timeoutHandler()
+ {
+ timeout_ = true;
+ }
+
+ Timer timer_;
+ bool timeout_;
+};
+
+class TimerFailTest : public Test
+{
+protected:
+ int init()
+ {
+ thread_.start();
+
+ timeout_ = new TimeoutHandler();
+ timeout_->moveToThread(&thread_);
+
+ return TestPass;
+ }
+
+ int run()
+ {
+ /*
+ * Test that the forbidden operation of starting the timer from
+ * another thread results in a failure. We need to interrupt the
+ * event dispatcher to make sure we don't succeed simply because
+ * the event dispatcher hasn't noticed the timer restart.
+ */
+ timeout_->start();
+ thread_.eventDispatcher()->interrupt();
+
+ this_thread::sleep_for(chrono::milliseconds(200));
+
+ /*
+ * The wrong start() call should result in an assertion in debug
+ * builds, and a timeout in release builds. The test is
+ * therefore marked in meson.build as expected to fail. We need
+ * to return TestPass in the unexpected (usually known as
+ * "fail") case, and TestFail otherwise.
+ */
+ if (timeout_->timeout()) {
+ cout << "Timer start from wrong thread succeeded unexpectedly"
+ << endl;
+ return TestPass;
+ }
+
+ return TestFail;
+ }
+
+ void cleanup()
+ {
+ /*
+ * Object class instances must be destroyed from the thread
+ * they live in.
+ */
+ timeout_->deleteLater();
+ thread_.exit(0);
+ thread_.wait();
+ }
+
+private:
+ TimeoutHandler *timeout_;
+ Thread thread_;
+};
+
+TEST_REGISTER(TimerFailTest)
diff --git a/test/timer-thread.cpp b/test/timer-thread.cpp
index 61821753..8675e248 100644
--- a/test/timer-thread.cpp
+++ b/test/timer-thread.cpp
@@ -9,6 +9,7 @@
#include <iostream>
#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
@@ -28,12 +29,6 @@ public:
timer_.start(100ms);
}
- void restart()
- {
- timeout_ = false;
- timer_.start(100ms);
- }
-
bool timeout() const
{
return timeout_;
@@ -55,7 +50,9 @@ protected:
int init()
{
thread_.start();
- timeout_.moveToThread(&thread_);
+
+ timeout_ = new TimeoutHandler();
+ timeout_->moveToThread(&thread_);
return TestPass;
}
@@ -68,39 +65,27 @@ protected:
*/
this_thread::sleep_for(chrono::milliseconds(200));
- if (!timeout_.timeout()) {
+ if (!timeout_->timeout()) {
cout << "Timer expiration test failed" << endl;
return TestFail;
}
- /*
- * Test that starting the timer from another thread fails. We
- * need to interrupt the event dispatcher to make sure we don't
- * succeed simply because the event dispatcher hasn't noticed
- * the timer restart.
- */
- timeout_.restart();
- thread_.eventDispatcher()->interrupt();
-
- this_thread::sleep_for(chrono::milliseconds(200));
-
- if (timeout_.timeout()) {
- cout << "Timer restart test failed" << endl;
- return TestFail;
- }
-
return TestPass;
}
void cleanup()
{
- /* Must stop thread before destroying timeout. */
+ /*
+ * Object class instances must be destroyed from the thread
+ * they live in.
+ */
+ timeout_->deleteLater();
thread_.exit(0);
thread_.wait();
}
private:
- TimeoutHandler timeout_;
+ TimeoutHandler *timeout_;
Thread thread_;
};
diff --git a/test/transform.cpp b/test/transform.cpp
new file mode 100644
index 00000000..fbc0308c
--- /dev/null
+++ b/test/transform.cpp
@@ -0,0 +1,329 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Ideas On Board Oy
+ *
+ * transform.cpp - Transform and Orientation tests
+ */
+
+#include <iostream>
+
+#include <libcamera/orientation.h>
+#include <libcamera/transform.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class TransformTest : public Test
+{
+protected:
+ int run();
+};
+
+int TransformTest::run()
+{
+ /*
+ * RotationTestEntry collects two Orientation and one Transform that
+ * gets combined to validate that (o1 / o2 = T) and (o1 = o2 * T)
+ *
+ * o1 / o2 = t computes the Transform to apply to o2 to obtain o1
+ * o2 * t = o1 combines o2 with t by applying o2 first then t
+ *
+ * The comments on the (most complex) transform show how applying to
+ * an image with orientation o2 the Transform t allows to obtain o1.
+ *
+ * The image with basic rotation0 is assumed to be:
+ *
+ * AB
+ * CD
+ *
+ * And the Transform operators are:
+ *
+ * V = vertical flip
+ * H = horizontal flip
+ * T = transpose
+ *
+ * the operator '* (T|V)' applies V first then T.
+ */
+ static const struct RotationTestEntry {
+ Orientation o1;
+ Orientation o2;
+ Transform t;
+ } testEntries[] = {
+ /* Test identities transforms first. */
+ {
+ Orientation::Rotate0, Orientation::Rotate0,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate0Mirror, Orientation::Rotate0Mirror,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate180, Orientation::Rotate180,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate180Mirror, Orientation::Rotate180Mirror,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate90, Orientation::Rotate90,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate90Mirror, Orientation::Rotate90Mirror,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate270, Orientation::Rotate270,
+ Transform::Identity,
+ },
+ {
+ Orientation::Rotate270Mirror, Orientation::Rotate270Mirror,
+ Transform::Identity,
+ },
+ /*
+ * Combine 0 and 180 degrees rotation as they're the most common
+ * ones.
+ */
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CD * (H|V) = BA AB
+ * BA CD CD
+ */
+ Orientation::Rotate0, Orientation::Rotate180,
+ Transform::Rot180,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * AB * (H|V) = CD DC
+ * CD AB BA
+ */
+ Orientation::Rotate180, Orientation::Rotate0,
+ Transform::Rot180
+ },
+ /* Test that transpositions are handled correctly. */
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * AB * (T|V) = CD CA
+ * CD AB DB
+ */
+ Orientation::Rotate90, Orientation::Rotate0,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CA * (T|H) = AC AB
+ * DB BD CD
+ */
+ Orientation::Rotate0, Orientation::Rotate90,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * AB * (T|H) = BA BD
+ * CD DC AC
+ */
+ Orientation::Rotate270, Orientation::Rotate0,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * BD * (T|V) = AC AB
+ * AC BD CD
+ */
+ Orientation::Rotate0, Orientation::Rotate270,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CD * (T|H) = DC DA
+ * BA AB CB
+ */
+ Orientation::Rotate90, Orientation::Rotate180,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * DA * (T|V) = CB CD
+ * CB DA BA
+ */
+ Orientation::Rotate180, Orientation::Rotate90,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CD * (T|V) = BA BC
+ * BA CD AD
+ */
+ Orientation::Rotate270, Orientation::Rotate180,
+ Transform::Rot90,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * BC * (T|H) = CB CD
+ * AD DA BA
+ */
+ Orientation::Rotate180, Orientation::Rotate270,
+ Transform::Rot270,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * DA * (V|H) = AD BC
+ * CB BC AD
+ */
+ Orientation::Rotate270, Orientation::Rotate90,
+ Transform::Rot180,
+ },
+ /* Test that mirroring is handled correctly. */
+ {
+ Orientation::Rotate0, Orientation::Rotate0Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate0Mirror, Orientation::Rotate0,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate180, Orientation::Rotate180Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate180Mirror, Orientation::Rotate180,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate90, Orientation::Rotate90Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate90Mirror, Orientation::Rotate90,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate270, Orientation::Rotate270Mirror,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate270Mirror, Orientation::Rotate270,
+ Transform::HFlip
+ },
+ {
+ Orientation::Rotate0, Orientation::Rotate0Mirror,
+ Transform::HFlip
+ },
+ /*
+ * More exotic transforms which include Transpositions and
+ * mirroring.
+ */
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * BC * (V) = AD
+ * AD BC
+ */
+ Orientation::Rotate90Mirror, Orientation::Rotate270,
+ Transform::VFlip,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * CB * (T) = CD
+ * DA BA
+ */
+ Orientation::Rotate180, Orientation::Rotate270Mirror,
+ Transform::Transpose,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * AD * (T) = AB
+ * BC DC
+ */
+ Orientation::Rotate0, Orientation::Rotate90Mirror,
+ Transform::Transpose,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * AD * (V) = BC
+ * BC AD
+ */
+ Orientation::Rotate270, Orientation::Rotate90Mirror,
+ Transform::VFlip,
+ },
+ {
+ /*
+ * o2 t o1
+ * ------------------
+ * DA * (V) = CB
+ * CB DA
+ */
+ Orientation::Rotate270Mirror, Orientation::Rotate90,
+ Transform::VFlip,
+ },
+ {
+ /*
+ * o2 t o1
+ * --------------------------
+ * CB * (V|H) = BC AD
+ * DA AD BC
+ */
+ Orientation::Rotate90Mirror, Orientation::Rotate270Mirror,
+ Transform::Rot180,
+ },
+ };
+
+ for (const auto &entry : testEntries) {
+ Transform transform = entry.o1 / entry.o2;
+ if (transform != entry.t) {
+ cerr << "Failed to validate: " << entry.o1
+ << " / " << entry.o2
+ << " = " << transformToString(entry.t) << endl;
+ cerr << "Got back: "
+ << transformToString(transform) << endl;
+ return TestFail;
+ }
+
+ Orientation adjusted = entry.o2 * entry.t;
+ if (adjusted != entry.o1) {
+ cerr << "Failed to validate: " << entry.o2
+ << " * " << transformToString(entry.t)
+ << " = " << entry.o1 << endl;
+ cerr << "Got back: " << adjusted << endl;
+ return TestFail;
+ }
+ }
+
+ return TestPass;
+}
+
+TEST_REGISTER(TransformTest)
diff --git a/test/utils.cpp b/test/utils.cpp
index d65467b5..fc56e14e 100644
--- a/test/utils.cpp
+++ b/test/utils.cpp
@@ -7,6 +7,7 @@
#include <iostream>
#include <map>
+#include <optional>
#include <sstream>
#include <string>
#include <vector>
@@ -226,6 +227,14 @@ protected:
return TestFail;
}
+ const auto &split = utils::split(path, ":");
+ dirs = std::vector<std::string>{ split.begin(), split.end() };
+
+ if (dirs != elements) {
+ cerr << "utils::split() LegacyInputIterator test failed" << endl;
+ return TestFail;
+ }
+
/* utils::join() with conversion function test. */
std::vector<Size> sizes = { { 0, 0 }, { 100, 100 } };
s = utils::join(sizes, "/", [](const Size &size) {
diff --git a/test/v4l2_compat/meson.build b/test/v4l2_compat/meson.build
index 87809589..2691eacf 100644
--- a/test/v4l2_compat/meson.build
+++ b/test/v4l2_compat/meson.build
@@ -1,20 +1,29 @@
# SPDX-License-Identifier: CC0-1.0
-# If ASan is enabled, the link order runtime check will fail as v4l2-ctl and
-# v4l2-compliance are not linked to ASan. Skip the test in that case.
-#
-# TODO: Find a way to LD_PRELOAD the ASan dynamic library instead, in a
-# cross-platform way with support for both gcc and clang.
+if not is_variable('v4l2_compat')
+ subdir_done()
+endif
+
+# If ASan is enabled but the ASan runtime shared library is missing,
+# v4l2_compat_test.py won't be able to LD_PRELOAD it, resulting in a link order
+# runtime check failure as v4l2-ctl and v4l2-compliance are not linked to ASan.
+# Skip the test in that case.
-if get_option('b_sanitize').contains('address')
+if asan_runtime_missing
+ warning('Unable to get path to ASan runtime, v4l2_compat test disabled')
subdir_done()
endif
-if is_variable('v4l2_compat')
- v4l2_compat_test = files('v4l2_compat_test.py')
+v4l2_compat_test = files('v4l2_compat_test.py')
+v4l2_compat_args = []
- test('v4l2_compat_test', v4l2_compat_test,
- args : v4l2_compat,
- suite : 'v4l2_compat',
- timeout : 60)
+if asan_enabled
+ v4l2_compat_args += ['-s', asan_runtime]
endif
+
+v4l2_compat_args += [v4l2_compat]
+
+test('v4l2_compat_test', v4l2_compat_test,
+ args : v4l2_compat_args,
+ suite : 'v4l2_compat',
+ timeout : 60)
diff --git a/test/v4l2_compat/v4l2_compat_test.py b/test/v4l2_compat/v4l2_compat_test.py
index a77585fc..bd89d496 100755
--- a/test/v4l2_compat/v4l2_compat_test.py
+++ b/test/v4l2_compat/v4l2_compat_test.py
@@ -57,8 +57,8 @@ def extract_result(result):
return ret
-def test_v4l2_compliance(v4l2_compliance, v4l2_compat, device, base_driver):
- ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': v4l2_compat})
+def test_v4l2_compliance(v4l2_compliance, ld_preload, device, base_driver):
+ ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': ld_preload})
if ret < 0:
output.append(f'Test for {device} terminated due to signal {signal.Signals(-ret).name}')
return TestFail, output
@@ -82,13 +82,21 @@ def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--all', action='store_true',
help='Test all available cameras')
+ parser.add_argument('-s', '--sanitizer', type=str,
+ help='Path to the address sanitizer (ASan) runtime')
parser.add_argument('-v', '--verbose', action='store_true',
help='Make the output verbose')
parser.add_argument('v4l2_compat', type=str,
help='Path to v4l2-compat.so')
args = parser.parse_args(argv[1:])
- v4l2_compat = args.v4l2_compat
+ # Compute the LD_PRELOAD value by first loading ASan (if specified) and
+ # then the V4L2 compat layer.
+ ld_preload = []
+ if args.sanitizer:
+ ld_preload.append(args.sanitizer)
+ ld_preload.append(args.v4l2_compat)
+ ld_preload = ':'.join(ld_preload)
v4l2_compliance = shutil.which('v4l2-compliance')
if v4l2_compliance is None:
@@ -118,7 +126,7 @@ def main(argv):
failed = []
drivers_tested = {}
for device in dev_nodes:
- ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': v4l2_compat})
+ ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': ld_preload})
if ret < 0:
failed.append(device)
print(f'v4l2-ctl failed on {device} with v4l2-compat')
@@ -144,7 +152,7 @@ def main(argv):
continue
print(f'Testing {device} with {driver} driver... ', end='')
- ret, msg = test_v4l2_compliance(v4l2_compliance, v4l2_compat, device, driver)
+ ret, msg = test_v4l2_compliance(v4l2_compliance, ld_preload, device, driver)
if ret == TestFail:
failed.append(device)
print('failed')
diff --git a/test/v4l2_subdevice/meson.build b/test/v4l2_subdevice/meson.build
index d82be3c6..277f29bb 100644
--- a/test/v4l2_subdevice/meson.build
+++ b/test/v4l2_subdevice/meson.build
@@ -1,14 +1,14 @@
# SPDX-License-Identifier: CC0-1.0
v4l2_subdevice_tests = [
- ['list_formats', 'list_formats.cpp'],
- ['test_formats', 'test_formats.cpp'],
+ {'name': 'list_formats', 'sources': ['list_formats.cpp']},
+ {'name': 'test_formats', 'sources': ['test_formats.cpp']},
]
-foreach t : v4l2_subdevice_tests
- exe = executable(t[0], [t[1], 'v4l2_subdevice_test.cpp'],
+foreach test : v4l2_subdevice_tests
+ exe = executable(test['name'], test['sources'], 'v4l2_subdevice_test.cpp',
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'v4l2_subdevice', is_parallel : false)
+ test(test['name'], exe, suite : 'v4l2_subdevice', is_parallel : false)
endforeach
diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build
index 7a26f53d..87ea4f96 100644
--- a/test/v4l2_videodevice/meson.build
+++ b/test/v4l2_videodevice/meson.build
@@ -3,22 +3,22 @@
# Tests are listed in order of complexity.
# They are not alphabetically sorted.
v4l2_videodevice_tests = [
- ['double_open', 'double_open.cpp'],
- ['controls', 'controls.cpp'],
- ['formats', 'formats.cpp'],
- ['dequeue_watchdog', 'dequeue_watchdog.cpp'],
- ['request_buffers', 'request_buffers.cpp'],
- ['buffer_cache', 'buffer_cache.cpp'],
- ['stream_on_off', 'stream_on_off.cpp'],
- ['capture_async', 'capture_async.cpp'],
- ['buffer_sharing', 'buffer_sharing.cpp'],
- ['v4l2_m2mdevice', 'v4l2_m2mdevice.cpp'],
+ {'name': 'double_open', 'sources': ['double_open.cpp']},
+ {'name': 'controls', 'sources': ['controls.cpp']},
+ {'name': 'formats', 'sources': ['formats.cpp']},
+ {'name': 'dequeue_watchdog', 'sources': ['dequeue_watchdog.cpp']},
+ {'name': 'request_buffers', 'sources': ['request_buffers.cpp']},
+ {'name': 'buffer_cache', 'sources': ['buffer_cache.cpp']},
+ {'name': 'stream_on_off', 'sources': ['stream_on_off.cpp']},
+ {'name': 'capture_async', 'sources': ['capture_async.cpp']},
+ {'name': 'buffer_sharing', 'sources': ['buffer_sharing.cpp']},
+ {'name': 'v4l2_m2mdevice', 'sources': ['v4l2_m2mdevice.cpp']},
]
-foreach t : v4l2_videodevice_tests
- exe = executable(t[0], [t[1], 'v4l2_videodevice_test.cpp'],
+foreach test : v4l2_videodevice_tests
+ exe = executable(test['name'], [test['sources'], 'v4l2_videodevice_test.cpp'],
dependencies : libcamera_private,
link_with : test_libraries,
include_directories : test_includes_internal)
- test(t[0], exe, suite : 'v4l2_videodevice', is_parallel : false)
+ test(test['name'], exe, suite : 'v4l2_videodevice', is_parallel : false)
endforeach
diff --git a/test/v4l2_videodevice/v4l2_m2mdevice.cpp b/test/v4l2_videodevice/v4l2_m2mdevice.cpp
index 852b853f..c45f581a 100644
--- a/test/v4l2_videodevice/v4l2_m2mdevice.cpp
+++ b/test/v4l2_videodevice/v4l2_m2mdevice.cpp
@@ -95,6 +95,11 @@ protected:
V4L2VideoDevice *capture = vim2m_->capture();
V4L2VideoDevice *output = vim2m_->output();
+ if (capture->controls().empty() || output->controls().empty()) {
+ cerr << "VIM2M device has no control" << endl;
+ return TestFail;
+ }
+
V4L2DeviceFormat format = {};
if (capture->getFormat(&format)) {
cerr << "Failed to get capture format" << endl;
diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp
index 125aafd6..1113cf5b 100644
--- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp
+++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp
@@ -75,7 +75,7 @@ int V4L2VideoDeviceTest::init()
format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8);
V4L2SubdeviceFormat subformat = {};
- subformat.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8;
+ subformat.code = MEDIA_BUS_FMT_SBGGR8_1X8;
subformat.size = format.size;
if (sensor_->setFormat(&subformat))
diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
index 38f84823..2d92463a 100644
--- a/test/yaml-parser.cpp
+++ b/test/yaml-parser.cpp
@@ -24,16 +24,20 @@ using namespace std;
static const string testYaml =
"string: libcamera\n"
"double: 3.14159\n"
- "uint32_t: 100\n"
- "int32_t: -100\n"
+ "int8_t: -100\n"
+ "uint8_t: 100\n"
+ "int16_t: -1000\n"
+ "uint16_t: 1000\n"
+ "int32_t: -100000\n"
+ "uint32_t: 100000\n"
"size: [1920, 1080]\n"
"list:\n"
" - James\n"
" - Mary\n"
"dictionary:\n"
" a: 1\n"
- " b: 2\n"
" c: 3\n"
+ " b: 2\n"
"level1:\n"
" level2:\n"
" - [1, 2]\n"
@@ -72,309 +76,359 @@ protected:
return TestPass;
}
- int run()
+ enum class Type {
+ String,
+ Int8,
+ UInt8,
+ Int16,
+ UInt16,
+ Int32,
+ UInt32,
+ Double,
+ Size,
+ List,
+ Dictionary,
+ };
+
+ int testObjectType(const YamlObject &obj, const char *name, Type type)
{
- /* Test invalid YAML file */
- File file{ invalidYamlFile_ };
- if (!file.open(File::OpenModeFlag::ReadOnly)) {
- cerr << "Fail to open invalid YAML file" << std::endl;
- return TestFail;
- }
+ bool isList = type == Type::List || type == Type::Size;
+ bool isScalar = !isList && type != Type::Dictionary;
+ bool isInteger8 = type == Type::Int8 || type == Type::UInt8;
+ bool isInteger16 = type == Type::Int16 || type == Type::UInt16;
+ bool isInteger32 = type == Type::Int32 || type == Type::UInt32;
+ bool isIntegerUpTo16 = isInteger8 || isInteger16;
+ bool isIntegerUpTo32 = isIntegerUpTo16 || isInteger32;
+ bool isSigned = type == Type::Int8 || type == Type::Int16 ||
+ type == Type::Int32;
- std::unique_ptr<YamlObject> root = YamlParser::parse(file);
- if (root) {
- cerr << "Invalid YAML file parse successfully" << std::endl;
+ if ((isScalar && !obj.isValue()) || (!isScalar && obj.isValue())) {
+ std::cerr
+ << "Object " << name << " type mismatch when compared to "
+ << "value" << std::endl;
return TestFail;
}
- /* Test YAML file */
- file.close();
- file.setFileName(testYamlFile_);
- if (!file.open(File::OpenModeFlag::ReadOnly)) {
- cerr << "Fail to open test YAML file" << std::endl;
+ if ((isList && !obj.isList()) || (!isList && obj.isList())) {
+ std::cerr
+ << "Object " << name << " type mismatch when compared to "
+ << "list" << std::endl;
return TestFail;
}
- root = YamlParser::parse(file);
-
- if (!root) {
- cerr << "Fail to parse test YAML file: " << std::endl;
+ if ((type == Type::Dictionary && !obj.isDictionary()) ||
+ (type != Type::Dictionary && obj.isDictionary())) {
+ std::cerr
+ << "Object " << name << " type mismatch when compared to "
+ << "dictionary" << std::endl;
return TestFail;
}
- if (!root->isDictionary()) {
- cerr << "YAML root is not dictionary" << std::endl;
+ if (!isScalar && obj.get<std::string>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "string" << std::endl;
return TestFail;
}
- if (!root->contains("string")) {
- cerr << "Missing string object in YAML root" << std::endl;
+ if (!isInteger8 && obj.get<int8_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "int8_t" << std::endl;
return TestFail;
}
- if (!root->contains("double")) {
- cerr << "Missing double object in YAML root" << std::endl;
+ if ((!isInteger8 || isSigned) && obj.get<uint8_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "uint8_t" << std::endl;
return TestFail;
}
- if (!root->contains("int32_t")) {
- cerr << "Missing int32_t object in YAML root" << std::endl;
+ if (!isIntegerUpTo16 && obj.get<int16_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "int16_t" << std::endl;
return TestFail;
}
- if (!root->contains("uint32_t")) {
- cerr << "Missing uint32_t object in YAML root" << std::endl;
+ if ((!isIntegerUpTo16 || isSigned) && obj.get<uint16_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "uint16_t" << std::endl;
return TestFail;
}
- if (!root->contains("size")) {
- cerr << "Missing Size object in YAML root" << std::endl;
+ if (!isIntegerUpTo32 && obj.get<int32_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "int32_t" << std::endl;
return TestFail;
}
- if (!root->contains("list")) {
- cerr << "Missing list object in YAML root" << std::endl;
+ if ((!isIntegerUpTo32 || isSigned) && obj.get<uint32_t>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "uint32_t" << std::endl;
return TestFail;
}
- if (!root->contains("dictionary")) {
- cerr << "Missing dictionary object in YAML root" << std::endl;
+ if (!isIntegerUpTo32 && type != Type::Double && obj.get<double>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "double" << std::endl;
return TestFail;
}
- if (!root->contains("level1")) {
- cerr << "Missing leveled object in YAML root" << std::endl;
+ if (type != Type::Size && obj.get<Size>()) {
+ std::cerr
+ << "Object " << name << " didn't fail to parse as "
+ << "Size" << std::endl;
return TestFail;
}
- /* Test string object */
- bool ok;
- auto &strObj = (*root)["string"];
+ return TestPass;
+ }
- if (strObj.isDictionary()) {
- cerr << "String object parse as Dictionary" << std::endl;
- return TestFail;
- }
+ int testIntegerObject(const YamlObject &obj, const char *name, Type type,
+ int64_t value)
+ {
+ uint64_t unsignedValue = static_cast<uint64_t>(value);
+ std::string strValue = std::to_string(value);
+ bool isInteger8 = type == Type::Int8 || type == Type::UInt8;
+ bool isInteger16 = type == Type::Int16 || type == Type::UInt16;
+ bool isSigned = type == Type::Int8 || type == Type::Int16 ||
+ type == Type::Int32;
- if (strObj.isList()) {
- cerr << "String object parse as List" << std::endl;
- return TestFail;
- }
+ /* All integers can be parsed as strings or double. */
- if (strObj.get<string>("", &ok) != "libcamera" || !ok) {
- cerr << "String object parse as wrong content" << std::endl;
+ if (obj.get<string>().value_or("") != strValue ||
+ obj.get<string>("") != strValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "string" << std::endl;
return TestFail;
}
- if (strObj.get<int32_t>(-1, &ok) != -1 || ok) {
- cerr << "String object parse as integer" << std::endl;
+ if (obj.get<double>().value_or(0.0) != value ||
+ obj.get<double>(0.0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "double" << std::endl;
return TestFail;
}
- if (strObj.get<uint32_t>(1, &ok) != 1 || ok) {
- cerr << "String object parse as unsigned integer" << std::endl;
- return TestFail;
+ if (isInteger8) {
+ if (obj.get<int8_t>().value_or(0) != value ||
+ obj.get<int8_t>(0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "int8_t" << std::endl;
+ return TestFail;
+ }
}
- if (strObj.get<double>(1.0, &ok) != 1.0 || ok) {
- cerr << "String object parse as double" << std::endl;
- return TestFail;
+ if (isInteger8 && !isSigned) {
+ if (obj.get<uint8_t>().value_or(0) != unsignedValue ||
+ obj.get<uint8_t>(0) != unsignedValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "uint8_t" << std::endl;
+ return TestFail;
+ }
}
- if (strObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) {
- cerr << "String object parse as Size" << std::endl;
- return TestFail;
+ if (isInteger8 || isInteger16) {
+ if (obj.get<int16_t>().value_or(0) != value ||
+ obj.get<int16_t>(0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "int16_t" << std::endl;
+ return TestFail;
+ }
}
- /* Test int32_t object */
- auto &int32Obj = (*root)["int32_t"];
-
- if (int32Obj.isDictionary()) {
- cerr << "Integer object parse as Dictionary" << std::endl;
- return TestFail;
+ if ((isInteger8 || isInteger16) && !isSigned) {
+ if (obj.get<uint16_t>().value_or(0) != unsignedValue ||
+ obj.get<uint16_t>(0) != unsignedValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "uint16_t" << std::endl;
+ return TestFail;
+ }
}
- if (int32Obj.isList()) {
- cerr << "Integer object parse as Integer" << std::endl;
+ if (obj.get<int32_t>().value_or(0) != value ||
+ obj.get<int32_t>(0) != value) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "int32_t" << std::endl;
return TestFail;
}
- if (int32Obj.get<int32_t>(-100, &ok) != -100 || !ok) {
- cerr << "Integer object parse as wrong value" << std::endl;
- return TestFail;
+ if (!isSigned) {
+ if (obj.get<uint32_t>().value_or(0) != unsignedValue ||
+ obj.get<uint32_t>(0) != unsignedValue) {
+ std::cerr
+ << "Object " << name << " failed to parse as "
+ << "uint32_t" << std::endl;
+ return TestFail;
+ }
}
- if (int32Obj.get<string>("", &ok) != "-100" || !ok) {
- cerr << "Integer object fail to parse as string" << std::endl;
- return TestFail;
- }
+ return TestPass;
+ }
- if (int32Obj.get<double>(1.0, &ok) != -100.0 || !ok) {
- cerr << "Integer object fail to parse as double" << std::endl;
+ int run()
+ {
+ /* Test invalid YAML file */
+ File file{ invalidYamlFile_ };
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ cerr << "Fail to open invalid YAML file" << std::endl;
return TestFail;
}
- if (int32Obj.get<uint32_t>(1, &ok) != 1 || ok) {
- cerr << "Negative integer object parse as unsigned integer" << std::endl;
+ std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+ if (root) {
+ cerr << "Invalid YAML file parse successfully" << std::endl;
return TestFail;
}
- if (int32Obj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) {
- cerr << "Integer object parse as Size" << std::endl;
+ /* Test YAML file */
+ file.close();
+ file.setFileName(testYamlFile_);
+ if (!file.open(File::OpenModeFlag::ReadOnly)) {
+ cerr << "Fail to open test YAML file" << std::endl;
return TestFail;
}
- /* Test uint32_t object */
- auto &uint32Obj = (*root)["uint32_t"];
+ root = YamlParser::parse(file);
- if (uint32Obj.isDictionary()) {
- cerr << "Unsigned integer object parse as Dictionary" << std::endl;
+ if (!root) {
+ cerr << "Fail to parse test YAML file: " << std::endl;
return TestFail;
}
- if (uint32Obj.isList()) {
- cerr << "Unsigned integer object parse as List" << std::endl;
+ if (!root->isDictionary()) {
+ cerr << "YAML root is not dictionary" << std::endl;
return TestFail;
}
- if (uint32Obj.get<int32_t>(-1, &ok) != 100 || !ok) {
- cerr << "Unsigned integer object fail to parse as integer" << std::endl;
- return TestFail;
- }
+ std::vector<const char *> rootElemNames = {
+ "string", "double", "int8_t", "uint8_t", "int16_t",
+ "uint16_t", "int32_t", "uint32_t", "size", "list",
+ "dictionary", "level1",
+ };
- if (uint32Obj.get<string>("", &ok) != "100" || !ok) {
- cerr << "Unsigned integer object fail to parse as string" << std::endl;
- return TestFail;
+ for (const char *name : rootElemNames) {
+ if (!root->contains(name)) {
+ cerr << "Missing " << name << " object in YAML root"
+ << std::endl;
+ return TestFail;
+ }
}
- if (uint32Obj.get<double>(1.0, &ok) != 100.0 || !ok) {
- cerr << "Unsigned integer object fail to parse as double" << std::endl;
- return TestFail;
- }
+ /* Test string object */
+ auto &strObj = (*root)["string"];
- if (uint32Obj.get<uint32_t>(100, &ok) != 100 || !ok) {
- cerr << "Unsigned integer object parsed as wrong value" << std::endl;
+ if (testObjectType(strObj, "string", Type::String) != TestPass)
return TestFail;
- }
- if (uint32Obj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) {
- cerr << "Unsigned integer object parsed as Size" << std::endl;
+ if (strObj.get<string>().value_or("") != "libcamera" ||
+ strObj.get<string>("") != "libcamera") {
+ cerr << "String object parse as wrong content" << std::endl;
return TestFail;
}
- /* Test double value */
- auto &doubleObj = (*root)["double"];
+ /* Test int8_t object */
+ auto &int8Obj = (*root)["int8_t"];
- if (doubleObj.isDictionary()) {
- cerr << "Double object parse as Dictionary" << std::endl;
+ if (testObjectType(int8Obj, "int8_t", Type::Int8) != TestPass)
return TestFail;
- }
- if (doubleObj.isList()) {
- cerr << "Double object parse as List" << std::endl;
+ if (testIntegerObject(int8Obj, "int8_t", Type::Int8, -100) != TestPass)
return TestFail;
- }
- if (doubleObj.get<string>("", &ok) != "3.14159" || !ok) {
- cerr << "Double object fail to parse as string" << std::endl;
- return TestFail;
- }
+ /* Test uint8_t object */
+ auto &uint8Obj = (*root)["uint8_t"];
- if (doubleObj.get<double>(1.0, &ok) != 3.14159 || !ok) {
- cerr << "Double object parse as wrong value" << std::endl;
+ if (testObjectType(uint8Obj, "uint8_t", Type::UInt8) != TestPass)
return TestFail;
- }
- if (doubleObj.get<int32_t>(-1, &ok) != -1 || ok) {
- cerr << "Double object parse as integer" << std::endl;
+ if (testIntegerObject(uint8Obj, "uint8_t", Type::UInt8, 100) != TestPass)
return TestFail;
- }
- if (doubleObj.get<uint32_t>(1, &ok) != 1 || ok) {
- cerr << "Double object parse as unsigned integer" << std::endl;
+ /* Test int16_t object */
+ auto &int16Obj = (*root)["int16_t"];
+
+ if (testObjectType(int16Obj, "int16_t", Type::Int16) != TestPass)
return TestFail;
- }
- if (doubleObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) {
- cerr << "Double object parse as Size" << std::endl;
+ if (testIntegerObject(int16Obj, "int16_t", Type::Int16, -1000) != TestPass)
return TestFail;
- }
- /* Test Size value */
- auto &sizeObj = (*root)["size"];
+ /* Test uint16_t object */
+ auto &uint16Obj = (*root)["uint16_t"];
- if (sizeObj.isDictionary()) {
- cerr << "Size object parse as Dictionary" << std::endl;
+ if (testObjectType(uint16Obj, "uint16_t", Type::UInt16) != TestPass)
return TestFail;
- }
- if (!sizeObj.isList()) {
- cerr << "Size object parse as List" << std::endl;
+ if (testIntegerObject(uint16Obj, "uint16_t", Type::UInt16, 1000) != TestPass)
return TestFail;
- }
- if (sizeObj.get<string>("", &ok) != "" || ok) {
- cerr << "Size object parse as string" << std::endl;
- return TestFail;
- }
+ /* Test int32_t object */
+ auto &int32Obj = (*root)["int32_t"];
- if (sizeObj.get<double>(1.0, &ok) != 1.0 || ok) {
- cerr << "Size object parse as double" << std::endl;
+ if (testObjectType(int32Obj, "int32_t", Type::Int32) != TestPass)
return TestFail;
- }
- if (sizeObj.get<int32_t>(-1, &ok) != -1 || ok) {
- cerr << "Size object parse as integer" << std::endl;
+ if (testIntegerObject(int32Obj, "int32_t", Type::Int32, -100000) != TestPass)
return TestFail;
- }
- if (sizeObj.get<uint32_t>(1, &ok) != 1 || ok) {
- cerr << "Size object parse as unsigned integer" << std::endl;
+ /* Test uint32_t object */
+ auto &uint32Obj = (*root)["uint32_t"];
+
+ if (testObjectType(uint32Obj, "uint32_t", Type::UInt32) != TestPass)
return TestFail;
- }
- if (sizeObj.get<Size>(Size(0, 0), &ok) != Size(1920, 1080) || !ok) {
- cerr << "Size object parse as wrong value" << std::endl;
+ if (testIntegerObject(uint32Obj, "uint32_t", Type::UInt32, 100000) != TestPass)
return TestFail;
- }
- /* Test list object */
- auto &listObj = (*root)["list"];
+ /* Test double value */
+ auto &doubleObj = (*root)["double"];
- if (listObj.isDictionary()) {
- cerr << "List object parse as Dictionary" << std::endl;
+ if (testObjectType(doubleObj, "double", Type::Double) != TestPass)
return TestFail;
- }
- if (!listObj.isList()) {
- cerr << "List object fail to parse as List" << std::endl;
+ if (doubleObj.get<string>().value_or("") != "3.14159" ||
+ doubleObj.get<string>("") != "3.14159") {
+ cerr << "Double object fail to parse as string" << std::endl;
return TestFail;
}
- if (listObj.get<string>("", &ok) != "" || ok) {
- cerr << "List object parse as string" << std::endl;
+ if (doubleObj.get<double>().value_or(0.0) != 3.14159 ||
+ doubleObj.get<double>(0.0) != 3.14159) {
+ cerr << "Double object parse as wrong value" << std::endl;
return TestFail;
}
- if (listObj.get<double>(1.0, &ok) != 1.0 || ok) {
- cerr << "List object parse as double" << std::endl;
- return TestFail;
- }
+ /* Test Size value */
+ auto &sizeObj = (*root)["size"];
- if (listObj.get<int32_t>(-1, &ok) != -1 || ok) {
- cerr << "List object parse as integer" << std::endl;
+ if (testObjectType(sizeObj, "size", Type::Size) != TestPass)
return TestFail;
- }
- if (listObj.get<uint32_t>(1, &ok) != 1 || ok) {
- cerr << "List object parse as unsigne integer" << std::endl;
+ if (sizeObj.get<Size>().value_or(Size(0, 0)) != Size(1920, 1080) ||
+ sizeObj.get<Size>(Size(0, 0)) != Size(1920, 1080)) {
+ cerr << "Size object parse as wrong value" << std::endl;
return TestFail;
}
- if (listObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) {
- cerr << "String list object parse as Size" << std::endl;
+ /* Test list object */
+ auto &listObj = (*root)["list"];
+
+ if (testObjectType(listObj, "list", Type::List) != TestPass)
return TestFail;
- }
static constexpr std::array<const char *, 2> listValues{
"James",
@@ -414,45 +468,13 @@ protected:
/* Test dictionary object */
auto &dictObj = (*root)["dictionary"];
- if (!dictObj.isDictionary()) {
- cerr << "Dictionary object fail to parse as Dictionary" << std::endl;
+ if (testObjectType(dictObj, "dictionary", Type::Dictionary) != TestPass)
return TestFail;
- }
- if (dictObj.isList()) {
- cerr << "Dictionary object parse as List" << std::endl;
- return TestFail;
- }
-
- if (dictObj.get<string>("", &ok) != "" || ok) {
- cerr << "Dictionary object parse as string" << std::endl;
- return TestFail;
- }
-
- if (dictObj.get<double>(1.0, &ok) != 1.0 || ok) {
- cerr << "Dictionary object parse as double" << std::endl;
- return TestFail;
- }
-
- if (dictObj.get<int32_t>(-1, &ok) != -1 || ok) {
- cerr << "Dictionary object parse as integer" << std::endl;
- return TestFail;
- }
-
- if (dictObj.get<uint32_t>(1, &ok) != 1 || ok) {
- cerr << "Dictionary object parse as unsigned integer" << std::endl;
- return TestFail;
- }
-
- if (dictObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) {
- cerr << "Dictionary object parse as Size" << std::endl;
- return TestFail;
- }
-
- std::map<std::string, int> dictValues{ {
+ static constexpr std::array<std::pair<const char *, int>, 3> dictValues{ {
{ "a", 1 },
- { "b", 2 },
{ "c", 3 },
+ { "b", 2 },
} };
size_t dictSize = dictValues.size();
@@ -470,8 +492,8 @@ protected:
return TestFail;
}
- const auto item = dictValues.find(key);
- if (item == dictValues.end()) {
+ const auto &item = dictValues[i];
+ if (item.first != key) {
std::cerr << "Dictionary key " << i << " has wrong value"
<< std::endl;
return TestFail;
@@ -483,17 +505,12 @@ protected:
return TestFail;
}
- if (elem.get<int32_t>(0) != item->second) {
+ if (elem.get<int32_t>(0) != item.second) {
std::cerr << "Dictionary element " << i << " has wrong value"
<< std::endl;
return TestFail;
}
- /*
- * Erase the item to make sure that each iteration
- * produces a different value.
- */
- dictValues.erase(item);
i++;
}
@@ -524,6 +541,12 @@ protected:
return TestFail;
}
+ const auto &values = firstElement.getList<uint16_t>();
+ if (!values || values->size() != 2 || (*values)[0] != 1 || (*values)[1] != 2) {
+ cerr << "getList() failed to return correct vector" << std::endl;
+ return TestFail;
+ }
+
auto &secondElement = level2Obj[1];
if (!secondElement.isDictionary() ||
!secondElement.contains("one") ||
diff --git a/utils/abi-compat.sh b/utils/abi-compat.sh
new file mode 100755
index 00000000..c936ac05
--- /dev/null
+++ b/utils/abi-compat.sh
@@ -0,0 +1,212 @@
+#!/bin/bash
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Generate and compare the ABI compatibilty of two libcamera versions
+
+name=$(basename "$0")
+
+usage() {
+ cat << EOF
+$name: Determine the ABI/API compatibility of two build versions
+
+ $name [--help] [--abi-dir=<PATH>] [--tmp-dir=<PATH>] ARGS
+
+The positional arguments (ARGS) determine the versions that will be compared and
+take three variants:
+
+ - No positional arguments:
+ $name [optional arguments]
+
+ It is assumed to compare the current git HEAD against the most recent TAG
+
+ - One positional argument:
+ $name [optional aguments] COMMITISH
+
+ The given COMMITISH is compared against it's most recent TAG
+
+ - Two positional arguments:
+ $name [optional aguments] BASE COMMITISH
+
+ The given COMMITISH is compared against the given BASE.
+
+Optional Arguments:
+ --abi-dir <path> Use <path> for storing (or retrieving existing) ABI data
+ files
+
+ --tmp-dir <path> Specify temporary build location for building ABI data.
+ This could be a tmpfs/RAM disk to save on disk writes.
+EOF
+}
+
+dbg () {
+ echo "$@" >&2
+}
+
+die () {
+ echo "$name: $*" >&2
+ exit 1
+}
+
+describe () {
+ git describe --tags "$1" \
+ || die "Failed to describe $1"
+}
+
+prev_release () {
+ git describe --tags --abbrev=0 "$1"^ \
+ || die "Failed to identify previous release tag from $1"
+}
+
+# Make sure we exit on errors during argument parsing.
+set -Eeuo pipefail
+
+positional=()
+while [[ $# -gt 0 ]] ; do
+ option="$1"
+ shift
+
+ case $option in
+ -h|--help)
+ usage
+ exit 0
+ ;;
+
+ --abi-dir)
+ abi_dir=$1
+ shift
+ ;;
+
+ --tmp-dir)
+ tmp=$1
+ shift
+ ;;
+
+ -*)
+ die "Unrecognised argument $option"
+ ;;
+
+ *) # Parse unidentified arguments based on position.
+ positional+=("$option")
+ ;;
+ esac
+done
+set -- "${positional[@]}" # restore positional parameters.
+
+# Parse positional arguments.
+case $# in
+ 0) # Check HEAD against previous 'release'.
+ from=$(prev_release HEAD)
+ to=$(describe HEAD)
+ ;;
+
+ 1) # Check COMMIT against previous release.
+ from=$(prev_release "$1")
+ to=$(describe "$1")
+ ;;
+
+ 2) # Check ABI between FROM and TO explicitly.
+ from=$(describe "$1")
+ to=$(describe "$2")
+ ;;
+
+ *)
+ die "Invalid arguments"
+ ;;
+esac
+
+if ! which abi-compliance-checker; then
+ die "This tool requires 'abi-compliance-checker' to be installed."
+fi
+
+
+abi_dir=${abi_dir:-abi}
+tmp=${tmp:-"$abi_dir/tmp/"}
+
+echo "Validating ABI compatibility between $from and $to"
+
+mkdir -p "$abi_dir"
+mkdir -p "$tmp"
+
+# Generate an abi-compliance-checker xml description file.
+create_xml() {
+ local output="$1"
+ local version="$2"
+ local root="$3"
+
+ echo "<version>$version</version>" > "$output"
+ echo "<headers>$root/usr/local/include/</headers>" >> "$output"
+ echo "<libs>$root/usr/local/lib/</libs>" >> "$output"
+}
+
+# Check if an ABI dump file exists, and if not create one by building a minimal
+# configuration of libcamera at the specified version using a clean worktree.
+create_abi_dump() {
+ local version="$1"
+ local abi_file="$abi_dir/$version.abi.dump"
+ local worktree="$tmp/$version"
+ local build="$tmp/$version-build"
+
+ # Use a fully qualified path when calling ninja -C.
+ install=$(realpath "$tmp/$version-install")
+
+ if [[ ! -e "$abi_file" ]] ; then
+ dbg "Creating ABI dump for $version in $abi_dir"
+ git worktree add --force "$worktree" "$version"
+
+ # Generate a minimal libcamera build. "lib" and "prefix" are
+ # defined explicitly to avoid system default ambiguities.
+ meson setup "$build" "$worktree" \
+ -Dlibdir=lib \
+ -Dprefix=/usr/local/ \
+ -Ddocumentation=disabled \
+ -Dcam=disabled \
+ -Dqcam=disabled \
+ -Dgstreamer=disabled \
+ -Dlc-compliance=disabled \
+ -Dtracing=disabled \
+ -Dpipelines=
+
+ ninja -C "$build"
+ DESTDIR="$install" ninja -C "$build" install
+
+ # Create an xml descriptor with parameters to generate the dump file.
+ create_xml \
+ "$install/libcamera-abi-dump.xml" \
+ "$version" \
+ "$install"
+
+ abi-compliance-checker \
+ -lib libcamera \
+ -v1 "$version" \
+ -dump "$install/libcamera-abi-dump.xml" \
+ -dump-path "$abi_file"
+
+ dbg Created "$abi_file"
+
+ dbg Removing Worktree "$worktree"
+ git worktree remove -f "$worktree"
+
+ dbg Removing "$build"
+ rm -r "$build"
+
+ dbg Removing "$install"
+ rm -r "$install"
+ fi
+}
+
+# Create the requested ABI dump files if they don't yet exist.
+create_abi_dump "$from"
+create_abi_dump "$to"
+
+# TODO: Future iterations and extensions here could add "-stdout -xml" and
+# parse the results automatically.
+abi-compliance-checker -l libcamera \
+ -old "$abi_dir/$from.abi.dump" \
+ -new "$abi_dir/$to.abi.dump"
+
+# On (far too many) occasions, the tools keep running leaving a cpu core @ 100%
+# CPU usage. Perhaps some subprocess gets launched but never rejoined. Stop
+# them all.
+#
+# TODO: Investigate this and report upstream.
+killall abi-compliance-checker 2>/dev/null
diff --git a/utils/checkstyle.py b/utils/checkstyle.py
index f0248d65..88078a61 100755
--- a/utils/checkstyle.py
+++ b/utils/checkstyle.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3
+#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2018, Google Inc.
#
@@ -168,6 +168,12 @@ def parse_diff(diff):
hunk = DiffHunk(line)
elif hunk is not None:
+ # Work around https://github.com/python/cpython/issues/46395
+ # See https://www.gnu.org/software/diffutils/manual/html_node/Incomplete-Lines.html
+ if line[-1] != '\n':
+ hunk.append(line + '\n')
+ line = '\\ No newline at end of file\n'
+
hunk.append(line)
if hunk:
@@ -191,6 +197,9 @@ class CommitFile:
else:
self.__filename = info[1]
+ def __repr__(self):
+ return f'{self.__status} {self.__filename}'
+
@property
def filename(self):
return self.__filename
@@ -203,16 +212,30 @@ class CommitFile:
class Commit:
def __init__(self, commit):
self.commit = commit
+ self._trailers = []
self._parse()
+ def _parse_trailers(self, lines):
+ for index in range(1, len(lines)):
+ line = lines[index]
+ if not line:
+ break
+
+ self._trailers.append(line)
+
+ return index
+
def _parse(self):
# Get the commit title and list of files.
- ret = subprocess.run(['git', 'show', '--pretty=oneline', '--name-status',
+ ret = subprocess.run(['git', 'show', '--format=%s%n%(trailers:only,unfold)', '--name-status',
self.commit],
stdout=subprocess.PIPE).stdout.decode('utf-8')
- files = ret.splitlines()
- self._files = [CommitFile(f) for f in files[1:]]
- self._title = files[0]
+ lines = ret.splitlines()
+
+ self._title = lines[0]
+
+ index = self._parse_trailers(lines)
+ self._files = [CommitFile(f) for f in lines[index:] if f]
def files(self, filter='AMR'):
return [f.filename for f in self._files if f.status in filter]
@@ -221,6 +244,10 @@ class Commit:
def title(self):
return self._title
+ @property
+ def trailers(self):
+ return self._trailers
+
def get_diff(self, top_level, filename):
diff = subprocess.run(['git', 'diff', '%s~..%s' % (self.commit, self.commit),
'--', '%s/%s' % (top_level, filename)],
@@ -249,15 +276,21 @@ class StagedChanges(Commit):
return parse_diff(diff.splitlines(True))
-class Amendment(StagedChanges):
+class Amendment(Commit):
def __init__(self):
- StagedChanges.__init__(self)
+ Commit.__init__(self, '')
def _parse(self):
- # Create a title using HEAD commit
- ret = subprocess.run(['git', 'show', '--pretty=oneline', '--no-patch'],
+ # Create a title using HEAD commit and parse the trailers.
+ ret = subprocess.run(['git', 'show', '--format=%H %s%n%(trailers:only,unfold)',
+ '--no-patch'],
stdout=subprocess.PIPE).stdout.decode('utf-8')
- self._title = 'Amendment of ' + ret.strip()
+ lines = ret.splitlines()
+
+ self._title = 'Amendment of ' + lines[0].strip()
+
+ self._parse_trailers(lines)
+
# Extract the list of modified files
ret = subprocess.run(['git', 'diff', '--staged', '--name-status', 'HEAD~'],
stdout=subprocess.PIPE).stdout.decode('utf-8')
@@ -298,8 +331,10 @@ class CommitChecker(metaclass=ClassRegistry):
# Class methods
#
@classmethod
- def checkers(cls):
+ def checkers(cls, names):
for checker in cls.subclasses:
+ if names and checker.__name__ not in names:
+ continue
yield checker
@@ -313,7 +348,7 @@ class HeaderAddChecker(CommitChecker):
def check(cls, commit, top_level):
issues = []
- meson_files = [f for f in commit.files('M')
+ meson_files = [f for f in commit.files()
if os.path.basename(f) == 'meson.build']
for filename in commit.files('AR'):
@@ -352,6 +387,137 @@ class HeaderAddChecker(CommitChecker):
return issues
+class TitleChecker(CommitChecker):
+ prefix_regex = re.compile(r'^([a-zA-Z0-9_.-]+: )+')
+ release_regex = re.compile(r'libcamera v[0-9]+\.[0-9]+\.[0-9]+')
+
+ @classmethod
+ def check(cls, commit, top_level):
+ title = commit.title
+
+ # Skip the check when validating staged changes (as done through a
+ # pre-commit hook) as there is no title to check in that case.
+ if isinstance(commit, StagedChanges):
+ return []
+
+ # Ignore release commits, they don't need a prefix.
+ if TitleChecker.release_regex.fullmatch(title):
+ return []
+
+ prefix_pos = title.find(': ')
+ if prefix_pos != -1 and prefix_pos != len(title) - 2:
+ return []
+
+ # Find prefix candidates by searching the git history
+ msgs = subprocess.run(['git', 'log', '--no-decorate', '--oneline', '-n100', '--'] + commit.files(),
+ stdout=subprocess.PIPE).stdout.decode('utf-8')
+ prefixes = {}
+ prefixes_count = 0
+ for msg in msgs.splitlines():
+ prefix = TitleChecker.prefix_regex.match(msg)
+ if not prefix:
+ continue
+
+ prefix = prefix.group(0)
+ if prefix in prefixes:
+ prefixes[prefix] += 1
+ else:
+ prefixes[prefix] = 1
+
+ prefixes_count += 1
+
+ if not prefixes:
+ return [CommitIssue('Commit title is missing prefix')]
+
+ # Sort the candidates by number of occurrences and pick the best ones.
+ # When multiple prefixes are possible without a clear winner, we want to
+ # display the most common options to the user, but without the most
+ # unlikely options to avoid too long messages. As a heuristic, select
+ # enough candidates to cover at least 2/3 of the possible prefixes, but
+ # never more than 4 candidates.
+ prefixes = list(prefixes.items())
+ prefixes.sort(key=lambda x: x[1], reverse=True)
+
+ candidates = []
+ candidates_count = 0
+ for prefix in prefixes:
+ candidates.append(f"`{prefix[0]}'")
+ candidates_count += prefix[1]
+ if candidates_count >= prefixes_count * 2 / 3 or \
+ len(candidates) == 4:
+ break
+
+ candidates = candidates[:-2] + [' or '.join(candidates[-2:])]
+ candidates = ', '.join(candidates)
+
+ return [CommitIssue('Commit title is missing prefix, '
+ 'possible candidates are ' + candidates)]
+
+
+class TrailersChecker(CommitChecker):
+ commit_regex = re.compile(r'[0-9a-f]{12}[0-9a-f]* \(".*"\)')
+
+ coverity_regex = re.compile(r'Coverity CID=.*')
+
+ # Simple e-mail address validator regex, with an additional trailing
+ # comment. The complexity of a full RFC6531 validator isn't worth the
+ # additional invalid addresses it would reject.
+ email_regex = re.compile(r'[^<]+ <[^@>]+@[^>]+>( # .*)?')
+
+ link_regex = re.compile(r'https?://.*')
+
+ @staticmethod
+ def validate_reported_by(value):
+ if TrailersChecker.email_regex.fullmatch(value):
+ return True
+ if TrailersChecker.coverity_regex.fullmatch(value):
+ return True
+ return False
+
+ known_trailers = {
+ 'Acked-by': email_regex,
+ 'Bug': link_regex,
+ 'Co-developed-by': email_regex,
+ 'Fixes': commit_regex,
+ 'Link': link_regex,
+ 'Reported-by': validate_reported_by,
+ 'Reviewed-by': email_regex,
+ 'Signed-off-by': email_regex,
+ 'Suggested-by': email_regex,
+ 'Tested-by': email_regex,
+ }
+
+ trailer_regex = re.compile(r'([A-Z][a-zA-Z-]*)\s*:\s*(.*)')
+
+ @classmethod
+ def check(cls, commit, top_level):
+ issues = []
+
+ for trailer in commit.trailers:
+ match = TrailersChecker.trailer_regex.fullmatch(trailer)
+ if not match:
+ issues.append(CommitIssue(f"Malformed commit trailer '{trailer}'"))
+ continue
+
+ key, value = match.groups()
+
+ validator = TrailersChecker.known_trailers.get(key)
+ if not validator:
+ issues.append(CommitIssue(f"Invalid commit trailer key '{key}'"))
+ continue
+
+ if isinstance(validator, re.Pattern):
+ valid = bool(validator.fullmatch(value))
+ else:
+ valid = validator(value)
+
+ if not valid:
+ issues.append(CommitIssue(f"Malformed value '{value}' for commit trailer '{key}'"))
+ continue
+
+ return issues
+
+
# ------------------------------------------------------------------------------
# Style Checkers
#
@@ -366,8 +532,10 @@ class StyleChecker(metaclass=ClassRegistry):
# Class methods
#
@classmethod
- def checkers(cls, filename):
+ def checkers(cls, filename, names):
for checker in cls.subclasses:
+ if names and checker.__name__ not in names:
+ continue
if checker.supports(filename):
yield checker
@@ -401,7 +569,7 @@ class IncludeChecker(StyleChecker):
'limits', 'locale', 'setjmp', 'signal', 'stdarg', 'stddef',
'stdint', 'stdio', 'stdlib', 'string', 'time', 'uchar', 'wchar',
'wctype')
- include_regex = re.compile('^#include <c([a-z]*)>')
+ include_regex = re.compile(r'^#include <c([a-z]*)>')
def __init__(self, content):
super().__init__()
@@ -427,7 +595,7 @@ class IncludeChecker(StyleChecker):
class LogCategoryChecker(StyleChecker):
- log_regex = re.compile('\\bLOG\((Debug|Info|Warning|Error|Fatal)\)')
+ log_regex = re.compile(r'\bLOG\((Debug|Info|Warning|Error|Fatal)\)')
patterns = ('*.cpp',)
def __init__(self, content):
@@ -464,7 +632,7 @@ class MesonChecker(StyleChecker):
class Pep8Checker(StyleChecker):
patterns = ('*.py',)
- results_regex = re.compile('stdin:([0-9]+):([0-9]+)(.*)')
+ results_regex = re.compile(r'stdin:([0-9]+):([0-9]+)(.*)')
def __init__(self, content):
super().__init__()
@@ -497,7 +665,7 @@ class Pep8Checker(StyleChecker):
class ShellChecker(StyleChecker):
patterns = ('*.sh',)
- results_line_regex = re.compile('In - line ([0-9]+):')
+ results_line_regex = re.compile(r'In - line ([0-9]+):')
def __init__(self, content):
super().__init__()
@@ -547,8 +715,10 @@ class Formatter(metaclass=ClassRegistry):
# Class methods
#
@classmethod
- def formatters(cls, filename):
+ def formatters(cls, filename, names):
for formatter in cls.subclasses:
+ if names and formatter.__name__ not in names:
+ continue
if formatter.supports(filename):
yield formatter
@@ -583,7 +753,8 @@ class CLangFormatter(Formatter):
class DoxygenFormatter(Formatter):
patterns = ('*.c', '*.cpp')
- return_regex = re.compile(' +\\* +\\\\return +[a-z]')
+ oneliner_regex = re.compile(r'^ +\* +\\(brief|param|return)\b.*\.$')
+ return_regex = re.compile(r' +\* +\\return +[a-z]')
@classmethod
def format(cls, filename, data):
@@ -598,6 +769,7 @@ class DoxygenFormatter(Formatter):
lines.append(line)
continue
+ line = cls.oneliner_regex.sub(lambda m: m.group(0)[:-1], line)
line = cls.return_regex.sub(lambda m: m.group(0)[:-1] + m.group(0)[-1].upper(), line)
if line.find('*/') != -1:
@@ -643,7 +815,7 @@ class DPointerFormatter(Formatter):
class IncludeOrderFormatter(Formatter):
patterns = ('*.cpp', '*.h')
- include_regex = re.compile('^#include (["<])([^">]*)([">])')
+ include_regex = re.compile(r'^#include (["<])([^">]*)([">])')
@classmethod
def format(cls, filename, data):
@@ -710,7 +882,7 @@ class StripTrailingSpaceFormatter(Formatter):
# Style checking
#
-def check_file(top_level, commit, filename):
+def check_file(top_level, commit, filename, checkers):
# Extract the line numbers touched by the commit.
commit_diff = commit.get_diff(top_level, filename)
@@ -727,7 +899,7 @@ def check_file(top_level, commit, filename):
after = commit.get_file(filename)
formatted = after
- for formatter in Formatter.formatters(filename):
+ for formatter in Formatter.formatters(filename, checkers):
formatted = formatter.format(filename, formatted)
after = after.splitlines(True)
@@ -741,7 +913,7 @@ def check_file(top_level, commit, filename):
# Check for code issues not related to formatting.
issues = []
- for checker in StyleChecker.checkers(filename):
+ for checker in StyleChecker.checkers(filename, checkers):
checker = checker(after)
for hunk in commit_diff:
issues += checker.check(hunk.side('to').touched)
@@ -769,16 +941,17 @@ def check_file(top_level, commit, filename):
return len(formatted_diff) + len(issues)
-def check_style(top_level, commit):
- separator = '-' * len(commit.title)
+def check_style(top_level, commit, checkers):
+ title = commit.commit + ' ' + commit.title
+ separator = '-' * len(title)
print(separator)
- print(commit.title)
+ print(title)
print(separator)
issues = 0
# Apply the commit checkers first.
- for checker in CommitChecker.checkers():
+ for checker in CommitChecker.checkers(checkers):
for issue in checker.check(commit, top_level):
print('%s%s%s' % (Colours.fg(Colours.Yellow), issue.msg, Colours.reset()))
issues += 1
@@ -790,7 +963,7 @@ def check_style(top_level, commit):
files = [f for f in commit.files() if len([p for p in patterns if fnmatch.fnmatch(os.path.basename(f), p)])]
for f in files:
- issues += check_file(top_level, commit, f)
+ issues += check_file(top_level, commit, f, checkers)
if issues == 0:
print('No issue detected')
@@ -840,6 +1013,8 @@ def main(argv):
# Parse command line arguments
parser = argparse.ArgumentParser()
+ parser.add_argument('--checkers', '-c', type=str,
+ help='Specify which checkers to run as a comma-separated list. Defaults to all checkers')
parser.add_argument('--staged', '-s', action='store_true',
help='Include the changes in the index. Defaults to False')
parser.add_argument('--amend', '-a', action='store_true',
@@ -848,6 +1023,9 @@ def main(argv):
help='Revision range (as defined by git rev-parse). Defaults to HEAD if not specified.')
args = parser.parse_args(argv[1:])
+ if args.checkers:
+ args.checkers = args.checkers.split(',')
+
# Check for required dependencies.
for command, mandatory in dependencies.items():
found = shutil.which(command)
@@ -881,7 +1059,7 @@ def main(argv):
issues = 0
for commit in commits:
- issues += check_style(top_level, commit)
+ issues += check_style(top_level, commit, args.checkers)
print('')
if issues:
diff --git a/utils/gen-controls.py b/utils/gen-controls.py
index 3f99b5e2..6cd5e362 100755
--- a/utils/gen-controls.py
+++ b/utils/gen-controls.py
@@ -7,9 +7,110 @@
# gen-controls.py - Generate control definitions from YAML
import argparse
+from functools import reduce
+import operator
import string
import sys
import yaml
+import os
+
+
+class ControlEnum(object):
+ def __init__(self, data):
+ self.__data = data
+
+ @property
+ def description(self):
+ """The enum description"""
+ return self.__data.get('description')
+
+ @property
+ def name(self):
+ """The enum name"""
+ return self.__data.get('name')
+
+ @property
+ def value(self):
+ """The enum value"""
+ return self.__data.get('value')
+
+
+class Control(object):
+ def __init__(self, name, data, vendor):
+ self.__name = name
+ self.__data = data
+ self.__enum_values = None
+ self.__size = None
+ self.__vendor = vendor
+
+ enum_values = data.get('enum')
+ if enum_values is not None:
+ self.__enum_values = [ControlEnum(enum) for enum in enum_values]
+
+ size = self.__data.get('size')
+ if size is not None:
+ if len(size) == 0:
+ raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension')
+
+ # Compute the total number of elements in the array. If any of the
+ # array dimension is a string, the array is variable-sized.
+ num_elems = 1
+ for dim in size:
+ if type(dim) is str:
+ num_elems = 0
+ break
+
+ dim = int(dim)
+ if dim <= 0:
+ raise RuntimeError(f'Control `{self.__name}` size must have positive values only')
+
+ num_elems *= dim
+
+ self.__size = num_elems
+
+ @property
+ def description(self):
+ """The control description"""
+ return self.__data.get('description')
+
+ @property
+ def enum_values(self):
+ """The enum values, if the control is an enumeration"""
+ if self.__enum_values is None:
+ return
+ for enum in self.__enum_values:
+ yield enum
+
+ @property
+ def is_enum(self):
+ """Is the control an enumeration"""
+ return self.__enum_values is not None
+
+ @property
+ def vendor(self):
+ """The vendor string, or None"""
+ return self.__vendor
+
+ @property
+ def name(self):
+ """The control name (CamelCase)"""
+ return self.__name
+
+ @property
+ def type(self):
+ typ = self.__data.get('type')
+ size = self.__data.get('size')
+
+ if typ == 'string':
+ return 'std::string'
+
+ if self.__size is None:
+ return typ
+
+ if self.__size:
+ return f"Span<const {typ}, {self.__size}>"
+ else:
+ return f"Span<const {typ}>"
def snake_case(s):
@@ -40,46 +141,38 @@ ${description}
enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''')
enum_values_values = string.Template('''\tstatic_cast<int32_t>(${name}),''')
- ctrls_doc = []
- ctrls_def = []
- draft_ctrls_doc = []
- draft_ctrls_def = []
+ ctrls_doc = {}
+ ctrls_def = {}
ctrls_map = []
for ctrl in controls:
- name, ctrl = ctrl.popitem()
- id_name = snake_case(name).upper()
+ id_name = snake_case(ctrl.name).upper()
- ctrl_type = ctrl['type']
- if ctrl_type == 'string':
- ctrl_type = 'std::string'
- elif ctrl.get('size'):
- ctrl_type = 'Span<const %s>' % ctrl_type
+ vendor = ctrl.vendor
+ if vendor not in ctrls_doc:
+ ctrls_doc[vendor] = []
+ ctrls_def[vendor] = []
info = {
- 'name': name,
- 'type': ctrl_type,
- 'description': format_description(ctrl['description']),
+ 'name': ctrl.name,
+ 'type': ctrl.type,
+ 'description': format_description(ctrl.description),
'id_name': id_name,
}
- target_doc = ctrls_doc
- target_def = ctrls_def
- if ctrl.get('draft'):
- target_doc = draft_ctrls_doc
- target_def = draft_ctrls_def
+ target_doc = ctrls_doc[vendor]
+ target_def = ctrls_def[vendor]
- enum = ctrl.get('enum')
- if enum:
+ if ctrl.is_enum:
enum_doc = []
enum_doc.append(enum_doc_start_template.substitute(info))
num_entries = 0
- for entry in enum:
+ for enum in ctrl.enum_values:
value_info = {
- 'name': name,
- 'value': entry['name'],
- 'description': format_description(entry['description']),
+ 'name': ctrl.name,
+ 'value': enum.name,
+ 'description': format_description(enum.description),
}
enum_doc.append(enum_doc_value_template.substitute(value_info))
num_entries += 1
@@ -94,9 +187,9 @@ ${description}
}
target_doc.append(enum_values_doc.substitute(values_info))
target_def.append(enum_values_start.substitute(values_info))
- for entry in enum:
+ for enum in ctrl.enum_values:
value_info = {
- 'name': entry['name']
+ 'name': enum.name
}
target_def.append(enum_values_values.substitute(value_info))
target_def.append("};")
@@ -104,61 +197,75 @@ ${description}
target_doc.append(doc_template.substitute(info))
target_def.append(def_template.substitute(info))
- if ctrl.get('draft'):
- name = 'draft::' + name
+ vendor_ns = vendor + '::' if vendor != "libcamera" else ''
+ ctrls_map.append('\t{ ' + vendor_ns + id_name + ', &' + vendor_ns + ctrl.name + ' },')
- ctrls_map.append('\t{ ' + id_name + ', &' + name + ' },')
+ vendor_ctrl_doc_sub = []
+ vendor_ctrl_template = string.Template('''
+/**
+ * \\brief Namespace for ${vendor} controls
+ */
+namespace ${vendor} {
+
+${vendor_controls_str}
+
+} /* namespace ${vendor} */''')
+
+ for vendor in [v for v in ctrls_doc.keys() if v not in ['libcamera']]:
+ vendor_ctrl_doc_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n\n'.join(ctrls_doc[vendor])}))
+
+ vendor_ctrl_def_sub = []
+ for vendor in [v for v in ctrls_def.keys() if v not in ['libcamera']]:
+ vendor_ctrl_def_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n'.join(ctrls_def[vendor])}))
return {
- 'controls_doc': '\n\n'.join(ctrls_doc),
- 'controls_def': '\n'.join(ctrls_def),
- 'draft_controls_doc': '\n\n'.join(draft_ctrls_doc),
- 'draft_controls_def': '\n\n'.join(draft_ctrls_def),
+ 'controls_doc': '\n\n'.join(ctrls_doc['libcamera']),
+ 'controls_def': '\n'.join(ctrls_def['libcamera']),
'controls_map': '\n'.join(ctrls_map),
+ 'vendor_controls_doc': '\n'.join(vendor_ctrl_doc_sub),
+ 'vendor_controls_def': '\n'.join(vendor_ctrl_def_sub),
}
-def generate_h(controls):
+def generate_h(controls, mode, ranges):
enum_template_start = string.Template('''enum ${name}Enum {''')
enum_value_template = string.Template('''\t${name} = ${value},''')
enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''')
template = string.Template('''extern const Control<${type}> ${name};''')
- ctrls = []
- draft_ctrls = []
- ids = []
- id_value = 1
+ ctrls = {}
+ ids = {}
+ id_value = {}
for ctrl in controls:
- name, ctrl = ctrl.popitem()
- id_name = snake_case(name).upper()
+ id_name = snake_case(ctrl.name).upper()
- ids.append('\t' + id_name + ' = ' + str(id_value) + ',')
+ vendor = ctrl.vendor
+ if vendor not in ctrls:
+ if vendor not in ranges.keys():
+ raise RuntimeError(f'Control id range is not defined for vendor {vendor}')
+ id_value[vendor] = ranges[vendor] + 1
+ ids[vendor] = []
+ ctrls[vendor] = []
- ctrl_type = ctrl['type']
- if ctrl_type == 'string':
- ctrl_type = 'std::string'
- elif ctrl.get('size'):
- ctrl_type = 'Span<const %s>' % ctrl_type
+ target_ids = ids[vendor]
+ target_ids.append('\t' + id_name + ' = ' + str(id_value[vendor]) + ',')
info = {
- 'name': name,
- 'type': ctrl_type,
+ 'name': ctrl.name,
+ 'type': ctrl.type,
}
- target_ctrls = ctrls
- if ctrl.get('draft'):
- target_ctrls = draft_ctrls
+ target_ctrls = ctrls[vendor]
- enum = ctrl.get('enum')
- if enum:
+ if ctrl.is_enum:
target_ctrls.append(enum_template_start.substitute(info))
num_entries = 0
- for entry in enum:
+ for enum in ctrl.enum_values:
value_info = {
- 'name': entry['name'],
- 'value': entry['value'],
+ 'name': enum.name,
+ 'value': enum.value,
}
target_ctrls.append(enum_value_template.substitute(value_info))
num_entries += 1
@@ -171,12 +278,34 @@ def generate_h(controls):
target_ctrls.append(enum_values_template.substitute(values_info))
target_ctrls.append(template.substitute(info))
- id_value += 1
+ id_value[vendor] += 1
+
+ vendor_template = string.Template('''
+namespace ${vendor} {
+
+#define LIBCAMERA_HAS_${vendor_def}_VENDOR_${mode}
+
+enum {
+${vendor_enums}
+};
+
+${vendor_controls}
+
+} /* namespace ${vendor} */
+''')
+
+ vendor_sub = []
+ for vendor in [v for v in ctrls.keys() if v != 'libcamera']:
+ vendor_sub.append(vendor_template.substitute({'mode': mode.upper(),
+ 'vendor': vendor,
+ 'vendor_def': vendor.upper(),
+ 'vendor_enums': '\n'.join(ids[vendor]),
+ 'vendor_controls': '\n'.join(ctrls[vendor])}))
return {
- 'ids': '\n'.join(ids),
- 'controls': '\n'.join(ctrls),
- 'draft_controls': '\n'.join(draft_ctrls)
+ 'ids': '\n'.join(ids['libcamera']),
+ 'controls': '\n'.join(ctrls['libcamera']),
+ 'vendor_controls': '\n'.join(vendor_sub)
}
@@ -192,21 +321,36 @@ def main(argv):
# Parse command line arguments
parser = argparse.ArgumentParser()
- parser.add_argument('-o', dest='output', metavar='file', type=str,
+ parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],
+ help='Mode of operation')
+ parser.add_argument('--output', '-o', metavar='file', type=str,
help='Output file name. Defaults to standard output if not specified.')
- parser.add_argument('input', type=str,
- help='Input file name.')
- parser.add_argument('template', type=str,
+ parser.add_argument('--ranges', '-r', type=str, required=True,
+ help='Control id range reservation file.')
+ parser.add_argument('--template', '-t', dest='template', type=str, required=True,
help='Template file name.')
+ parser.add_argument('input', type=str, nargs='+',
+ help='Input file name.')
+
args = parser.parse_args(argv[1:])
- data = open(args.input, 'rb').read()
- controls = yaml.safe_load(data)['controls']
+ ranges = {}
+ with open(args.ranges, 'rb') as f:
+ data = open(args.ranges, 'rb').read()
+ ranges = yaml.safe_load(data)['ranges']
+
+ controls = []
+ for input in args.input:
+ with open(input, 'rb') as f:
+ data = f.read()
+ vendor = yaml.safe_load(data)['vendor']
+ ctrls = yaml.safe_load(data)['controls']
+ controls = controls + [Control(*ctrl.popitem(), vendor) for ctrl in ctrls]
if args.template.endswith('.cpp.in'):
data = generate_cpp(controls)
elif args.template.endswith('.h.in'):
- data = generate_h(controls)
+ data = generate_h(controls, args.mode, ranges)
else:
raise RuntimeError('Unknown template type')
diff --git a/utils/gen-version.sh b/utils/gen-version.sh
index eb7c7268..e1f7ca7b 100755
--- a/utils/gen-version.sh
+++ b/utils/gen-version.sh
@@ -5,6 +5,7 @@
build_dir="$1"
src_dir="$2"
+project_version="$3"
# If .tarball-version exists, output the version string from the file and exit.
# This file is auto-generated on a 'meson dist' command from the run-dist.sh
@@ -43,6 +44,13 @@ then
fi
git diff-index --quiet HEAD || version="$version-dirty ($(date --iso-8601=seconds))"
+# If a project version is provided, use it to replace the version number.
+if [ -n "$project_version" ]
+then
+ version=$(echo "$version" | sed -e 's/^[^-]*-//')
+ version="v$project_version-$version"
+fi
+
# Replace first '-' with a '+' to denote build metadata, strip the 'g' in from
# of the git SHA1 and remove the initial 'v'.
version=$(echo "$version" | sed -e 's/-/+/' | sed -e 's/-g/-/' | cut -c 2-)
diff --git a/utils/hooks/pre-push b/utils/hooks/pre-push
index 90ffdf6f..9918b286 100755
--- a/utils/hooks/pre-push
+++ b/utils/hooks/pre-push
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
@@ -61,7 +61,7 @@ do
msg=$(git cat-file commit "$commit")
# 1. The commit message shall not contain a local changelog.
- if echo "$msg" | grep -q '^--- *$'
+ if echo -E "$msg" | grep -q '^--- *$'
then
echo >&2 "Found local changelog in commit $commit"
errors=$((errors+1))
@@ -71,7 +71,7 @@ do
# corresponding the committer and the author.
committer=$(echo "$msg" | grep '^committer ' | head -1 | \
cut -d ' ' -f 2- | rev | cut -d ' ' -f 3- | rev)
- if ! echo "$msg" | grep -F -q "Signed-off-by: ${committer}"
+ if ! echo -E "$msg" | grep -F -q "Signed-off-by: ${committer}"
then
echo >&2 "Missing committer Signed-off-by in commit $commit"
errors=$((errors+1))
@@ -79,21 +79,21 @@ do
author=$(echo "$msg" | grep '^author ' | head -1 | \
cut -d ' ' -f 2- | rev | cut -d ' ' -f 3- | rev)
- if ! echo "$msg" | grep -F -q "Signed-off-by: ${author}"
+ if ! echo -E "$msg" | grep -F -q "Signed-off-by: ${author}"
then
echo >&2 "Missing author Signed-off-by in commit $commit"
errors=$((errors+1))
fi
# 3. A Reviewed-by or Acked-by is required.
- if ! echo "$msg" | grep -q '^\(Reviewed\|Acked\)-by: '
+ if ! echo -E "$msg" | grep -q '^\(Reviewed\|Acked\)-by: '
then
echo >&2 "No Reviewed-by or Acked-by in commit $commit"
errors=$((errors+1))
fi
# 4. The commit message shall not contain a Change-Id.
- if echo "$msg" | grep -q '^Change-Id:'
+ if echo -E "$msg" | grep -q '^Change-Id:'
then
echo >&2 "Found Change-Id in commit $commit"
errors=$((errors+1))
diff --git a/utils/ipc/extract-docs.py b/utils/ipc/extract-docs.py
index 8f7fff9f..c2050c99 100755
--- a/utils/ipc/extract-docs.py
+++ b/utils/ipc/extract-docs.py
@@ -10,9 +10,9 @@ import argparse
import re
import sys
-regex_block_start = re.compile('^\/\*\*$')
-regex_block_end = re.compile('^ \*\/$')
-regex_spdx = re.compile('^\/\* SPDX-License-Identifier: .* \*\/$')
+regex_block_start = re.compile(r'^/\*\*$')
+regex_block_end = re.compile(r'^ \*/$')
+regex_spdx = re.compile(r'^/\* SPDX-License-Identifier: .* \*/$')
def main(argv):
diff --git a/utils/ipc/generate.py b/utils/ipc/generate.py
index 8771e0a6..71bdee3b 100755
--- a/utils/ipc/generate.py
+++ b/utils/ipc/generate.py
@@ -12,10 +12,20 @@ import sys
# TODO set sys.pycache_prefix for >= python3.8
sys.dont_write_bytecode = True
+sys.path.insert(0, f'{os.path.dirname(__file__)}/mojo/public/tools/bindings')
+
import mojo.public.tools.bindings.mojom_bindings_generator as generator
def _GetModulePath(path, output_dir):
- return os.path.join(output_dir, path.relative_path())
+ return os.path.join(output_dir, path.relative_path())
+
+
+# Disable the attribute checker to support our custom attributes. Ideally we
+# should add the attributes to the list of allowed attributes in
+# utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py, but
+# we're trying hard to use the upstream mojom as-is.
+if hasattr(generator, '_BUILTIN_CHECKS'):
+ del generator._BUILTIN_CHECKS['attributes']
# Override the mojo code generator's generator list to only contain our
# libcamera generator
diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
index a565b59a..c60b99b8 100644
--- a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
+++ b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
@@ -26,7 +26,7 @@ namespace libcamera {
static const {{const.kind|name}} {{const.mojom_name}} = {{const.value}};
{% endfor %}
-{% for enum in enums %}
+{% for enum in enums_gen_header %}
{{funcs.define_enum(enum)}}
{% endfor %}
diff --git a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
index 94bb4918..8b8509f3 100644
--- a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
+++ b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
@@ -9,7 +9,7 @@
# \param enum Enum object whose definition is to be generated
#}
{%- macro define_enum(enum) -%}
-enum {{enum.mojom_name}} {
+enum{{" class" if enum|is_scoped}} {{enum.mojom_name}} {
{%- for field in enum.fields %}
{{field.mojom_name}} = {{field.numeric_value}},
{%- endfor %}
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
index 415ec283..160601f7 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
@@ -69,7 +69,7 @@ public:
{%- for method in interface_event.methods %}
Signal<
{%- for param in method.parameters -%}
- {{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}}
+ {{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod and not param|is_enum}}
{{- ", " if not loop.last}}
{%- endfor -%}
> {{method.mojom_name}};
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
index c37c4941..f64c3c93 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
@@ -175,9 +175,9 @@ void {{proxy_name}}::recvMessage(const IPCMessage &data)
);
{% elif method|is_async %}
ASSERT(state_ == ProxyRunning);
- proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued,
+ proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued
{%- for param in method|method_param_names -%}
- {{param}}{{- ", " if not loop.last}}
+ , {{param}}
{%- endfor -%}
);
{%- endif %}
@@ -235,8 +235,8 @@ void {{proxy_name}}::recvMessage(const IPCMessage &data)
}
void {{proxy_name}}::{{method.mojom_name}}IPC(
- std::vector<uint8_t>::const_iterator data,
- size_t dataSize,
+ [[maybe_unused]] std::vector<uint8_t>::const_iterator data,
+ [[maybe_unused]] size_t dataSize,
[[maybe_unused]] const std::vector<SharedFD> &fds)
{
{%- for param in method.parameters %}
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
index c308dd10..6e823598 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
@@ -18,6 +18,7 @@
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
+#include <libcamera/base/object.h>
#include <libcamera/base/thread.h>
#include "libcamera/internal/control_serializer.h"
@@ -46,7 +47,7 @@ public:
{%- for method in interface_event.methods %}
Signal<
{%- for param in method.parameters -%}
- {{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}}
+ {{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod and not param|is_enum}}
{{- ", " if not loop.last}}
{%- endfor -%}
> {{method.mojom_name}};
diff --git a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
index bac826a7..b5797b14 100644
--- a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
+++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
@@ -52,6 +52,9 @@
#}
{%- macro serialize_call(params, buf, fds) %}
{%- for param in params %}
+{%- if param|is_enum %}
+ static_assert(sizeof({{param|name_full}}) <= 4);
+{%- endif %}
std::vector<uint8_t> {{param.mojom_name}}Buf;
{%- if param|has_fd %}
std::vector<SharedFD> {{param.mojom_name}}Fds;
@@ -59,7 +62,13 @@
{%- else %}
std::tie({{param.mojom_name}}Buf, std::ignore) =
{%- endif %}
+{%- if param|is_flags %}
+ IPADataSerializer<{{param|name_full}}>::serialize({{param.mojom_name}}
+{%- elif param|is_enum %}
+ IPADataSerializer<uint32_t>::serialize(static_cast<uint32_t>({{param.mojom_name}})
+{%- else %}
IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}}
+{% endif -%}
{{- ", &controlSerializer_" if param|needs_control_serializer -}}
);
{%- endfor %}
@@ -97,7 +106,14 @@
# This code is meant to be used by macro deserialize_call.
#}
{%- macro deserialize_param(param, pointer, loop, buf, fds, iter, data_size) -%}
-{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize(
+{{"*" if pointer}}{{param.mojom_name}} =
+{%- if param|is_flags %}
+IPADataSerializer<{{param|name_full}}>::deserialize(
+{%- elif param|is_enum %}
+static_cast<{{param|name_full}}>(IPADataSerializer<uint32_t>::deserialize(
+{%- else %}
+IPADataSerializer<{{param|name}}>::deserialize(
+{%- endif %}
{{buf}}{{- ".cbegin()" if not iter}} + {{param.mojom_name}}Start,
{%- if loop.last and not iter %}
{{buf}}.cend()
@@ -121,7 +137,7 @@
{%- if param|needs_control_serializer %}
&controlSerializer_
{%- endif -%}
-);
+){{")" if param|is_enum and not param|is_flags}};
{%- endmacro -%}
@@ -170,7 +186,7 @@
{% for param in params|with_fds %}
{%- if loop.first %}
const size_t {{param.mojom_name}}FdStart = 0;
-{%- elif not loop.last %}
+{%- else %}
const size_t {{param.mojom_name}}FdStart = {{loop.previtem.mojom_name}}FdStart + {{loop.previtem.mojom_name}}FdsSize;
{%- endif %}
{%- endfor %}
diff --git a/utils/ipc/generators/libcamera_templates/serializer.tmpl b/utils/ipc/generators/libcamera_templates/serializer.tmpl
index 77bae36f..323e1293 100644
--- a/utils/ipc/generators/libcamera_templates/serializer.tmpl
+++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl
@@ -34,6 +34,10 @@
std::tie({{field.mojom_name}}, std::ignore) =
{%- if field|is_pod %}
IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
+ {%- elif field|is_flags %}
+ IPADataSerializer<{{field|name_full}}>::serialize(data.{{field.mojom_name}});
+ {%- elif field|is_enum_scoped %}
+ IPADataSerializer<uint{{field|bit_width}}_t>::serialize(static_cast<uint{{field|bit_width}}_t>(data.{{field.mojom_name}}));
{%- elif field|is_enum %}
IPADataSerializer<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}});
{%- endif %}
@@ -96,6 +100,8 @@
{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
{%- if field|is_pod %}
ret.{{field.mojom_name}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field_size}});
+ {%- elif field|is_flags %}
+ ret.{{field.mojom_name}} = IPADataSerializer<{{field|name_full}}>::deserialize(m, m + {{field_size}});
{%- else %}
ret.{{field.mojom_name}} = static_cast<{{field|name_full}}>(IPADataSerializer<uint{{field|bit_width}}_t>::deserialize(m, m + {{field_size}}));
{%- endif %}
diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py
index 753bfc73..99d905de 100644
--- a/utils/ipc/generators/mojom_libcamera_generator.py
+++ b/utils/ipc/generators/mojom_libcamera_generator.py
@@ -72,8 +72,10 @@ def ParamsCommaSep(l):
def GetDefaultValue(element):
if element.default is not None:
return element.default
- if type(element.kind) == mojom.Kind:
+ if type(element.kind) == mojom.ValueKind:
return '0'
+ if IsFlags(element):
+ return ''
if mojom.IsEnumKind(element.kind):
return f'static_cast<{element.kind.mojom_name}>(0)'
if isinstance(element.kind, mojom.Struct) and \
@@ -184,7 +186,7 @@ def MethodParameters(method):
params = []
for param in method.parameters:
params.append('const %s %s%s' % (GetNameForElement(param),
- '&' if not IsPod(param) else '',
+ '' if IsPod(param) or IsEnum(param) else '&',
param.mojom_name))
for param in MethodParamOutputs(method):
params.append(f'{GetNameForElement(param)} *{param.mojom_name}')
@@ -220,9 +222,30 @@ def IsControls(element):
def IsEnum(element):
return mojom.IsEnumKind(element.kind)
+
+# Only works the enum definition, not types
+def IsScoped(element):
+ attributes = getattr(element, 'attributes', None)
+ if not attributes:
+ return False
+ return 'scopedEnum' in attributes
+
+
+def IsEnumScoped(element):
+ if not IsEnum(element):
+ return False
+ return IsScoped(element.kind)
+
def IsFd(element):
return mojom.IsStructKind(element.kind) and element.kind.mojom_name == "SharedFD"
+
+def IsFlags(element):
+ attributes = getattr(element, 'attributes', None)
+ if not attributes:
+ return False
+ return 'flags' in attributes
+
def IsMap(element):
return mojom.IsMapKind(element.kind)
@@ -251,9 +274,11 @@ def ByteWidthFromCppType(t):
raise Exception('invalid type')
return str(int(_bit_widths[key]) // 8)
-
# Get the type name for a given element
def GetNameForElement(element):
+ # Flags
+ if IsFlags(element):
+ return f'Flags<{GetFullNameForElement(element.kind)}>'
# structs
if (mojom.IsEnumKind(element) or
mojom.IsInterfaceKind(element) or
@@ -302,15 +327,18 @@ def GetNameForElement(element):
def GetFullNameForElement(element):
name = GetNameForElement(element)
namespace_str = ''
- if mojom.IsStructKind(element):
+ if (mojom.IsStructKind(element) or mojom.IsEnumKind(element)):
namespace_str = element.module.mojom_namespace.replace('.', '::')
elif (hasattr(element, 'kind') and
- (mojom.IsStructKind(element.kind) or
- mojom.IsEnumKind(element.kind))):
+ (mojom.IsStructKind(element.kind) or mojom.IsEnumKind(element.kind))):
namespace_str = element.kind.module.mojom_namespace.replace('.', '::')
if namespace_str == '':
return name
+
+ if IsFlags(element):
+ return GetNameForElement(element)
+
return f'{namespace_str}::{name}'
def ValidateZeroLength(l, s, cap=True):
@@ -341,7 +369,7 @@ def ValidateNamespace(namespace):
if namespace == '':
raise Exception('Must have a namespace')
- if not re.match('^ipa\.[0-9A-Za-z_]+', namespace):
+ if not re.match(r'^ipa\.[0-9A-Za-z_]+', namespace):
raise Exception('Namespace must be of the form "ipa.{pipeline_name}"')
def ValidateInterfaces(interfaces):
@@ -407,10 +435,13 @@ class Generator(generator.Generator):
'is_array': IsArray,
'is_controls': IsControls,
'is_enum': IsEnum,
+ 'is_enum_scoped': IsEnumScoped,
'is_fd': IsFd,
+ 'is_flags': IsFlags,
'is_map': IsMap,
'is_plain_struct': IsPlainStruct,
'is_pod': IsPod,
+ 'is_scoped': IsScoped,
'is_str': IsStr,
'method_input_has_fd': MethodInputHasFd,
'method_output_has_fd': MethodOutputHasFd,
@@ -452,7 +483,7 @@ class Generator(generator.Generator):
def _GetJinjaExportsForCore(self):
return {
'consts': self.module.constants,
- 'enums': self.module.enums,
+ 'enums_gen_header': [x for x in self.module.enums if x.attributes is None or 'skipHeader' not in x.attributes],
'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes],
diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README
index d5c24fc3..961cabd2 100644
--- a/utils/ipc/mojo/README
+++ b/utils/ipc/mojo/README
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0
-Files in this directory are imported from 9c138d992bfc of Chromium. Do not
+Files in this directory are imported from 9be4263648d7 of Chromium. Do not
modify them manually.
diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE
index 972bb2ed..513e8a6a 100644
--- a/utils/ipc/mojo/public/LICENSE
+++ b/utils/ipc/mojo/public/LICENSE
@@ -1,4 +1,4 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2014 The Chromium Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn
index eb6391a6..5328a34a 100644
--- a/utils/ipc/mojo/public/tools/BUILD.gn
+++ b/utils/ipc/mojo/public/tools/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -10,7 +10,11 @@ group("mojo_python_unittests") {
"run_all_python_unittests.py",
"//testing/scripts/run_isolated_script_test.py",
]
- deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
+ deps = [
+ "//mojo/public/tools/bindings:tests",
+ "//mojo/public/tools/mojom:tests",
+ "//mojo/public/tools/mojom/mojom:tests",
+ ]
data_deps = [
"//testing:test_scripts_shared",
"//third_party/catapult/third_party/typ/",
diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
index 3e242532..eeca73ea 100644
--- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
+++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
@@ -1,24 +1,27 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
+# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/python.gni")
import("//mojo/public/tools/bindings/mojom.gni")
import("//third_party/jinja2/jinja2.gni")
-# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-python2_action("precompile_templates") {
+action("precompile_templates") {
sources = mojom_generator_sources
sources += [
+ "$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl",
"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl",
@@ -26,7 +29,6 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
@@ -65,9 +67,6 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/java_templates/struct.java.tmpl",
"$mojom_generator_root/generators/java_templates/union.java.tmpl",
"$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
- "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
"$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
"$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
@@ -93,8 +92,11 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
+ "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
+ "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
- "$mojom_generator_root/generators/ts_templates/mojom.tmpl",
+ "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
+ "$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
]
script = mojom_generator_script
@@ -102,8 +104,8 @@ python2_action("precompile_templates") {
outputs = [
"$target_gen_dir/cpp_templates.zip",
"$target_gen_dir/java_templates.zip",
- "$target_gen_dir/mojolpm_templates.zip",
"$target_gen_dir/js_templates.zip",
+ "$target_gen_dir/mojolpm_templates.zip",
"$target_gen_dir/ts_templates.zip",
]
args = [
@@ -113,3 +115,17 @@ python2_action("precompile_templates") {
"precompile",
]
}
+
+group("tests") {
+ data = [
+ mojom_generator_script,
+ "checks/mojom_attributes_check_unittest.py",
+ "checks/mojom_interface_feature_check_unittest.py",
+ "checks/mojom_restrictions_checks_unittest.py",
+ "mojom_bindings_generator_unittest.py",
+ "//tools/diagnosis/crbug_1001171.py",
+ "//third_party/markupsafe/",
+ ]
+ data += mojom_generator_sources
+ data += jinja2_sources
+}
diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md
index 43882450..b27b2d01 100644
--- a/utils/ipc/mojo/public/tools/bindings/README.md
+++ b/utils/ipc/mojo/public/tools/bindings/README.md
@@ -96,7 +96,7 @@ for message parameters.
| `string` | UTF-8 encoded string.
| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.
| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.
-| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
+| `map<S, T>` | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.
| `handle<message_pipe>` | Generic message pipe handle.
| `handle<shared_buffer>` | Shared buffer handle.
@@ -188,8 +188,8 @@ struct StringPair {
};
enum AnEnum {
- YES,
- NO
+ kYes,
+ kNo
};
interface SampleInterface {
@@ -209,7 +209,7 @@ struct AllTheThings {
uint64 unsigned_64bit_value;
float float_value_32bit;
double float_value_64bit;
- AnEnum enum_value = AnEnum.YES;
+ AnEnum enum_value = AnEnum.kYes;
// Strings may be nullable.
string? maybe_a_string_maybe_not;
@@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:
module business.mojom;
enum Department {
- SALES = 0,
- DEV,
+ kSales = 0,
+ kDev,
};
struct Employee {
enum Type {
- FULL_TIME,
- PART_TIME,
+ kFullTime,
+ kPartTime,
};
Type type;
@@ -315,6 +315,9 @@ struct Employee {
};
```
+C++ constant-style enum value names are preferred as specified in the
+[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
+
Similar to C-style enums, individual values may be explicitly assigned within an
enum definition. By default, values are based at zero and increment by
1 sequentially.
@@ -336,8 +339,8 @@ struct Employee {
const uint64 kInvalidId = 0;
enum Type {
- FULL_TIME,
- PART_TIME,
+ kFullTime,
+ kPartTime,
};
uint64 id = kInvalidId;
@@ -348,6 +351,37 @@ struct Employee {
The effect of nested definitions on generated bindings varies depending on the
target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages).
+### Features
+
+Features can be declared with a `name` and `default_state` and can be attached
+in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the
+feature is disabled at runtime, the method will crash and the interface will
+refuse to be bound / instantiated. Features cannot be serialized to be sent over
+IPC at this time.
+
+```
+module experimental.mojom;
+
+feature kUseElevators {
+ const string name = "UseElevators";
+ const bool default_state = false;
+}
+
+[RuntimeFeature=kUseElevators]
+interface Elevator {
+ // This interface cannot be bound or called if the feature is disabled.
+}
+
+interface Building {
+ // This method cannot be called if the feature is disabled.
+ [RuntimeFeature=kUseElevators]
+ CallElevator(int floor);
+
+ // This method can be called.
+ RingDoorbell(int volume);
+}
+```
+
### Interfaces
An **interface** is a logical bundle of parameterized request messages. Each
@@ -396,20 +430,33 @@ interesting attributes supported today.
extreme caution, because it can lead to deadlocks otherwise.
* **`[Default]`**:
- The `Default` attribute may be used to specify an enumerator value that
- will be used if an `Extensible` enumeration does not deserialize to a known
- value on the receiver side, i.e. the sender is using a newer version of the
- enum. This allows unknown values to be mapped to a well-defined value that can
- be appropriately handled.
+ The `Default` attribute may be used to specify an enumerator value or union
+ field that will be used if an `Extensible` enumeration or union does not
+ deserialize to a known value on the receiver side, i.e. the sender is using a
+ newer version of the enum or union. This allows unknown values to be mapped to
+ a well-defined value that can be appropriately handled.
+
+ Note: The `Default` field for a union must be of nullable or integral type.
+ When a union is defaulted to this field, the field takes on the default value
+ for its type: null for nullable types, and zero/false for integral types.
* **`[Extensible]`**:
- The `Extensible` attribute may be specified for any enum definition. This
- essentially disables builtin range validation when receiving values of the
- enum type in a message, allowing older bindings to tolerate unrecognized
- values from newer versions of the enum.
+ The `Extensible` attribute may be specified for any enum or union definition.
+ For enums, this essentially disables builtin range validation when receiving
+ values of the enum type in a message, allowing older bindings to tolerate
+ unrecognized values from newer versions of the enum.
- Note: in the future, an `Extensible` enumeration will require that a `Default`
- enumerator value also be specified.
+ If an enum value within an extensible enum definition is affixed with the
+ `Default` attribute, out-of-range values for the enum will deserialize to that
+ default value. Only one enum value may be designated as the `Default`.
+
+ Similarly, a union marked `Extensible` will deserialize to its `Default` field
+ when an unrecognized field is received. Extensible unions MUST specify exactly
+ one `Default` field, and the field must be of nullable or integral type. When
+ defaulted to this field, the value is always null/zero/false as appropriate.
+
+ An `Extensible` enumeration REQUIRES that a `Default` value be specified,
+ so all new extensible enums should specify one.
* **`[Native]`**:
The `Native` attribute may be specified for an empty struct declaration to
@@ -422,7 +469,10 @@ interesting attributes supported today.
* **`[MinVersion=N]`**:
The `MinVersion` attribute is used to specify the version at which a given
field, enum value, interface method, or method parameter was introduced.
- See [Versioning](#Versioning) for more details.
+ See [Versioning](#Versioning) for more details. `MinVersion` does not apply
+ to interfaces, structs or enums, but to the fields of those types.
+ `MinVersion` is not a module-global value, but it is ok to pretend it is by
+ skipping versions when adding fields or parameters.
* **`[Stable]`**:
The `Stable` attribute specifies that a given mojom type or interface
@@ -442,13 +492,73 @@ interesting attributes supported today.
string representation as specified by RFC 4122. New UUIDs can be generated
with common tools such as `uuidgen`.
+* **`[RuntimeFeature=feature]`**
+ The `RuntimeFeature` attribute should reference a mojo `feature`. If this
+ feature is enabled (e.g. using `--enable-features={feature.name}`) then the
+ interface behaves entirely as expected. If the feature is not enabled the
+ interface cannot be bound to a concrete receiver or remote - attempting to do
+ so will result in the receiver or remote being reset() to an unbound state.
+ Note that this is a different concept to the build-time `EnableIf` directive.
+ `RuntimeFeature` is currently only supported for C++ bindings and has no
+ effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253).
+
* **`[EnableIf=value]`**:
The `EnableIf` attribute is used to conditionally enable definitions when the
mojom is parsed. If the `mojom` target in the GN file does not include the
matching `value` in the list of `enabled_features`, the definition will be
disabled. This is useful for mojom definitions that only make sense on one
platform. Note that the `EnableIf` attribute can only be set once per
- definition.
+ definition and cannot be set at the same time as `EnableIfNot`. Also be aware
+ that only one condition can be tested, `EnableIf=value,xyz` introduces a new
+ `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends
+ only on the feature `value`. Complex conditions can be introduced via
+ enabled_features in `build.gn` files.
+
+* **`[EnableIfNot=value]`**:
+ The `EnableIfNot` attribute is used to conditionally enable definitions when
+ the mojom is parsed. If the `mojom` target in the GN file includes the
+ matching `value` in the list of `enabled_features`, the definition will be
+ disabled. This is useful for mojom definitions that only make sense on all but
+ one platform. Note that the `EnableIfNot` attribute can only be set once per
+ definition and cannot be set at the same time as `EnableIf`.
+
+* **`[ServiceSandbox=value]`**:
+ The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
+ service hosting an implementation of interface will be launched in. This only
+ applies to `C++` bindings. `value` should match a constant defined in an
+ imported `sandbox.mojom.Sandbox` enum (for Chromium this is
+ `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
+
+* **`[RequireContext=enum]`**:
+ The `RequireContext` attribute is used in Chromium to tag interfaces that
+ should be passed (as remotes or receivers) only to privileged process
+ contexts. The process context must be an enum that is imported into the
+ mojom that defines the tagged interface. `RequireContext` may be used in
+ future to DCHECK or CHECK if remotes are made available in contexts that
+ conflict with the one provided in the interface definition. Process contexts
+ are not the same as the sandbox a process is running in, but will reflect
+ the set of capabilities provided to the service.
+
+* **`[AllowedContext=enum]`**:
+ The `AllowedContext` attribute is used in Chromium to tag methods that pass
+ remotes or receivers of interfaces that are marked with a `RequireContext`
+ attribute. The enum provided on the method must be equal or better (lower
+ numerically) than the one required on the interface being passed. At present
+ failing to specify an adequate `AllowedContext` value will cause mojom
+ generation to fail at compile time. In future DCHECKs or CHECKs might be
+ added to enforce that method is only called from a process context that meets
+ the given `AllowedContext` value. The enum must of the same type as that
+ specified in the interface's `RequireContext` attribute. Adding an
+ `AllowedContext` attribute to a method is a strong indication that you need
+ a detailed security review of your design - please reach out to the security
+ team.
+
+* **`[SupportsUrgent]`**:
+ The `SupportsUrgent` attribute is used in conjunction with
+ `mojo::UrgentMessageScope` in Chromium to tag messages as having high
+ priority. The IPC layer notifies the underlying scheduler upon both receiving
+ and processing an urgent message. At present, this attribute only affects
+ channel associated messages in the renderer process.
## Generated Code For Target Languages
@@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:
``` cpp
enum AdvancedBoolean {
- TRUE = 0,
- FALSE = 1,
- FILE_NOT_FOUND = 2,
+ kTrue = 0,
+ kFalse = 1,
+ kFileNotFound = 2,
};
```
@@ -550,10 +660,16 @@ See the documentation for
*** note
**NOTE:** You don't need to worry about versioning if you don't care about
-backwards compatibility. Specifically, all parts of Chrome are updated
-atomically today and there is not yet any possibility of any two Chrome
-processes communicating with two different versions of any given Mojom
-interface.
+backwards compatibility. Today, all parts of the Chrome browser are
+updated atomically and there is not yet any possibility of any two
+Chrome processes communicating with two different versions of any given Mojom
+interface. On Chrome OS, there are several places where versioning is required.
+For example,
+[ARC++](https://developer.android.com/chrome-os/intro)
+uses versioned mojo to send IPC to the Android container.
+Likewise, the
+[Lacros](/docs/lacros.md)
+browser uses versioned mojo to talk to the ash system UI.
***
Services extend their interfaces to support new features over time, and clients
@@ -593,8 +709,8 @@ struct Employee {
*** note
**NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
-optional (nullable). See [Primitive Types](#Primitive-Types) for details on
-nullable values.
+optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
+details on nullable values.
***
By default, fields belong to version 0. New fields must be appended to the
@@ -624,10 +740,10 @@ the following hard constraints:
* For any given struct or interface, if any field or method explicitly specifies
an ordinal value, all fields or methods must explicitly specify an ordinal
value.
-* For an *N*-field struct or *N*-method interface, the set of explicitly
- assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces
- should include placeholder methods to fill the ordinal positions of removed
- methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc).
+* For an *N*-field struct, the set of explicitly assigned ordinal values must be
+ limited to the range *[0, N-1]*. Structs should include placeholder fields
+ to fill the ordinal positions of removed fields (for example "Unused_Field"
+ or "RemovedField", etc).
You may reorder fields, but you must ensure that the ordinal values of existing
fields remain unchanged. For example, the following struct remains
@@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended
that the version number is scoped to the whole interface rather than to any
individual parameter list.
+``` cpp
+// Old version:
+interface HumanResourceDatabase {
+ QueryEmployee(uint64 id) => (Employee? employee);
+};
+
+// New version:
+interface HumanResourceDatabase {
+ QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
+ => (Employee? employee,
+ [MinVersion=1] array<uint8>? finger_print);
+};
+```
+
+Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
+list of a request or response method to a destination using an older version of
+an interface, unrecognized fields are silently discarded.
+
Please note that adding a response to a message which did not previously
expect a response is a not a backwards-compatible change.
@@ -664,17 +798,12 @@ For example:
``` cpp
// Old version:
interface HumanResourceDatabase {
- AddEmployee(Employee employee) => (bool success);
QueryEmployee(uint64 id) => (Employee? employee);
};
// New version:
interface HumanResourceDatabase {
- AddEmployee(Employee employee) => (bool success);
-
- QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
- => (Employee? employee,
- [MinVersion=1] array<uint8>? finger_print);
+ QueryEmployee(uint64 id) => (Employee? employee);
[MinVersion=1]
AttachFingerPrint(uint64 id, array<uint8> finger_print)
@@ -682,10 +811,7 @@ interface HumanResourceDatabase {
};
```
-Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
-list of a request or response method to a destination using an older version of
-an interface, unrecognized fields are silently discarded. However, if the method
-call itself is not recognized, it is considered a validation error and the
+If a method call is not recognized, it is considered a validation error and the
receiver will close its end of the interface pipe. For example, if a client on
version 1 of the above interface sends an `AttachFingerPrint` request to an
implementation of version 0, the client will be disconnected.
@@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the
``` cpp
[Extensible]
enum Department {
- SALES,
- DEV,
+ kSales,
+ kDev,
};
```
@@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:
``` cpp
[Extensible]
enum Department {
- SALES,
- DEV,
- [MinVersion=1] RESEARCH,
+ kSales,
+ kDev,
+ [MinVersion=1] kResearch,
};
```
@@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition
ModuleStatement = AttributeSection "module" Identifier ";"
ImportStatement = "import" StringLiteral ";"
-Definition = Struct Union Interface Enum Const
+Definition = Struct Union Interface Enum Feature Const
AttributeSection = <empty> | "[" AttributeList "]"
AttributeList = <empty> | NonEmptyAttributeList
@@ -809,7 +935,7 @@ InterfaceBody = <empty>
| InterfaceBody Const
| InterfaceBody Enum
| InterfaceBody Method
-Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";"
+Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";"
ParameterList = <empty> | NonEmptyParameterList
NonEmptyParameterList = Parameter
| Parameter "," NonEmptyParameterList
@@ -847,6 +973,13 @@ EnumValue = AttributeSection Name
| AttributeSection Name "=" Integer
| AttributeSection Name "=" Identifier
+; Note: `feature` is a weak keyword and can appear as, say, a struct field name.
+Feature = AttributeSection "feature" Name "{" FeatureBody "}" ";"
+ | AttributeSection "feature" Name ";"
+FeatureBody = <empty>
+ | FeatureBody FeatureField
+FeatureField = AttributeSection TypeSpec Name Default ";"
+
Const = "const" TypeSpec Name "=" Constant ";"
Constant = Literal | Identifier ";"
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
new file mode 100644
index 00000000..e6e4f2c9
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
@@ -0,0 +1,170 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Validate mojo attributes are allowed in Chrome before generation."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+_COMMON_ATTRIBUTES = {
+ 'EnableIf',
+ 'EnableIfNot',
+}
+
+# For struct, union & parameter lists.
+_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'MinVersion',
+ 'RenamedFrom',
+}
+
+# Note: `Default`` goes on the default _value_, not on the enum.
+# Note: [Stable] without [Extensible] is not allowed.
+_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'Extensible',
+ 'Native',
+ 'Stable',
+ 'RenamedFrom',
+ 'Uuid',
+}
+
+# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
+_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'Default',
+ 'MinVersion',
+}
+
+_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'RenamedFrom',
+ 'RequireContext',
+ 'RuntimeFeature',
+ 'ServiceSandbox',
+ 'Stable',
+ 'Uuid',
+}
+
+_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'AllowedContext',
+ 'MinVersion',
+ 'NoInterrupt',
+ 'RuntimeFeature',
+ 'SupportsUrgent',
+ 'Sync',
+ 'UnlimitedSize',
+}
+
+_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'JavaConstantsClassName',
+ 'JavaPackage',
+}
+
+_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
+
+_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'CustomSerializer',
+ 'JavaClassName',
+ 'Native',
+ 'Stable',
+ 'RenamedFrom',
+ 'Uuid',
+}
+
+_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
+
+_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+ 'Extensible',
+ 'Stable',
+ 'RenamedFrom',
+ 'Uuid',
+}
+
+_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
+ 'Default',
+}
+
+# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.
+_STABLE_ONLY_ALLOWLISTED_ENUMS = {
+ 'crosapi.mojom.OptionalBool',
+ 'crosapi.mojom.TriState',
+}
+
+
+class Check(check.Check):
+ def __init__(self, *args, **kwargs):
+ super(Check, self).__init__(*args, **kwargs)
+
+ def _Respell(self, allowed, attribute):
+ for a in allowed:
+ if a.lower() == attribute.lower():
+ return f" - Did you mean: {a}?"
+ return ""
+
+ def _CheckAttributes(self, context, allowed, attributes):
+ if not attributes:
+ return
+ for attribute in attributes:
+ if not attribute in allowed:
+ # Is there a close misspelling?
+ hint = self._Respell(allowed, attribute)
+ raise check.CheckException(
+ self.module,
+ f"attribute {attribute} not allowed on {context}{hint}")
+
+ def _CheckEnumAttributes(self, enum):
+ if enum.attributes:
+ self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
+ if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:
+ full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
+ if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
+ raise check.CheckException(
+ self.module,
+ f"[Extensible] required on [Stable] enum {full_name}")
+ for enumval in enum.fields:
+ self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
+ enumval.attributes)
+
+ def _CheckInterfaceAttributes(self, interface):
+ self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
+ interface.attributes)
+ for method in interface.methods:
+ self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes)
+ for param in method.parameters:
+ self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
+ param.attributes)
+ if method.response_parameters:
+ for param in method.response_parameters:
+ self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
+ param.attributes)
+ for enum in interface.enums:
+ self._CheckEnumAttributes(enum)
+
+ def _CheckModuleAttributes(self):
+ self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes)
+
+ def _CheckStructAttributes(self, struct):
+ self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes)
+ for field in struct.fields:
+ self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
+ field.attributes)
+ for enum in struct.enums:
+ self._CheckEnumAttributes(enum)
+
+ def _CheckUnionAttributes(self, union):
+ self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
+ for field in union.fields:
+ self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
+ field.attributes)
+
+ def CheckModule(self):
+ """Note that duplicate attributes are forbidden at the parse phase.
+ We also do not need to look at the types of any parameters, as they will be
+ checked where they are defined. Consts do not have attributes so can be
+ skipped."""
+ self._CheckModuleAttributes()
+ for interface in self.module.interfaces:
+ self._CheckInterfaceAttributes(interface)
+ for enum in self.module.enums:
+ self._CheckEnumAttributes(enum)
+ for struct in self.module.structs:
+ self._CheckStructAttributes(struct)
+ for union in self.module.unions:
+ self._CheckUnionAttributes(union)
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
new file mode 100644
index 00000000..f1a50a4a
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
@@ -0,0 +1,194 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mojom.generate.check as check
+from mojom_bindings_generator import LoadChecks, _Generate
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class FakeArgs:
+ """Fakes args to _Generate - intention is to do just enough to run checks"""
+
+ def __init__(self, tester, files=None):
+ """ `tester` is MojomParserTestCase for paths.
+ `files` will have tester path added."""
+ self.checks_string = 'attributes'
+ self.depth = tester.GetPath('')
+ self.filelist = None
+ self.filename = [tester.GetPath(x) for x in files]
+ self.gen_directories = tester.GetPath('gen')
+ self.generators_string = ''
+ self.import_directories = []
+ self.output_dir = tester.GetPath('out')
+ self.scrambled_message_id_salt_paths = None
+ self.typemaps = []
+ self.variant = 'none'
+
+
+class MojoBindingsCheckTest(MojomParserTestCase):
+ def _ParseAndGenerate(self, mojoms):
+ self.ParseMojoms(mojoms)
+ args = FakeArgs(self, files=mojoms)
+ _Generate(args, {})
+
+ def _testValid(self, filename, content):
+ self.WriteFile(filename, content)
+ self._ParseAndGenerate([filename])
+
+ def _testThrows(self, filename, content, regexp):
+ mojoms = []
+ self.WriteFile(filename, content)
+ mojoms.append(filename)
+ with self.assertRaisesRegexp(check.CheckException, regexp):
+ self._ParseAndGenerate(mojoms)
+
+ def testLoads(self):
+ """Validate that the check is registered under the expected name."""
+ check_modules = LoadChecks('attributes')
+ self.assertTrue(check_modules['attributes'])
+
+ def testNoAnnotations(self):
+ # Undecorated mojom should be fine.
+ self._testValid(
+ "a.mojom", """
+ module a;
+ struct Bar { int32 a; };
+ enum Hello { kValue };
+ union Thingy { Bar b; Hello hi; };
+ interface Foo {
+ Foo(int32 a, Hello hi, Thingy t) => (Bar b);
+ };
+ """)
+
+ def testValidAnnotations(self):
+ # Obviously this is meaningless and won't generate, but it should pass
+ # the attribute check's validation.
+ self._testValid(
+ "a.mojom", """
+ [JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
+ module a;
+ [Stable, Extensible]
+ enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
+ [Native]
+ enum NativeEnum {};
+ [Stable,Extensible]
+ union Thingy { Bar b; [Default]int32 c; Hello hi; };
+
+ [Stable,RenamedFrom="module.other.Foo",
+ Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
+ struct Structure { Hello hi; };
+
+ [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
+ Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
+ interface Foo {
+ [AllowedContext=Hello.kValue]
+ Foo@0(int32 a) => (int32 b);
+ [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
+ Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
+ };
+
+ [RuntimeFeature=test.mojom.FeatureName]
+ interface FooFeatureControlled {};
+
+ interface FooMethodFeatureControlled {
+ [RuntimeFeature=test.mojom.FeatureName]
+ MethodWithFeature() => (bool c);
+ };
+ """)
+
+ def testWrongModuleStable(self):
+ contents = """
+ // err: module cannot be Stable
+ [Stable]
+ module a;
+ enum Hello { kValue, kValue2, kValue3 };
+ enum NativeEnum {};
+ struct Structure { Hello hi; };
+
+ interface Foo {
+ Foo(int32 a) => (int32 b);
+ Bar(int32 b, Structure? s) => (bool c);
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'attribute Stable not allowed on module')
+
+ def testWrongEnumDefault(self):
+ contents = """
+ module a;
+ // err: default should go on EnumValue not Enum.
+ [Default=kValue]
+ enum Hello { kValue, kValue2, kValue3 };
+ enum NativeEnum {};
+ struct Structure { Hello hi; };
+
+ interface Foo {
+ Foo(int32 a) => (int32 b);
+ Bar(int32 b, Structure? s) => (bool c);
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'attribute Default not allowed on enum')
+
+ def testWrongStructMinVersion(self):
+ contents = """
+ module a;
+ enum Hello { kValue, kValue2, kValue3 };
+ enum NativeEnum {};
+ // err: struct cannot have MinVersion.
+ [MinVersion=2]
+ struct Structure { Hello hi; };
+
+ interface Foo {
+ Foo(int32 a) => (int32 b);
+ Bar(int32 b, Structure? s) => (bool c);
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'attribute MinVersion not allowed on struct')
+
+ def testWrongMethodRequireContext(self):
+ contents = """
+ module a;
+ enum Hello { kValue, kValue2, kValue3 };
+ enum NativeEnum {};
+ struct Structure { Hello hi; };
+
+ interface Foo {
+ // err: RequireContext is for interfaces.
+ [RequireContext=Hello.kValue]
+ Foo(int32 a) => (int32 b);
+ Bar(int32 b, Structure? s) => (bool c);
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'RequireContext not allowed on method')
+
+ def testWrongMethodRequireContext(self):
+ # crbug.com/1230122
+ contents = """
+ module a;
+ interface Foo {
+ // err: sync not Sync.
+ [sync]
+ Foo(int32 a) => (int32 b);
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'attribute sync not allowed.*Did you mean: Sync')
+
+ def testStableExtensibleEnum(self):
+ # crbug.com/1193875
+ contents = """
+ module a;
+ [Stable]
+ enum Foo {
+ kDefaultVal,
+ kOtherVal = 2,
+ };
+ """
+ self._testThrows('a.mojom', contents,
+ 'Extensible.*?required.*?Stable.*?enum')
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
new file mode 100644
index 00000000..702d41c3
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
@@ -0,0 +1,34 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Ensure no duplicate type definitions before generation."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+
+class Check(check.Check):
+ def __init__(self, *args, **kwargs):
+ super(Check, self).__init__(*args, **kwargs)
+
+ def CheckModule(self):
+ kinds = dict()
+ for module in self.module.imports:
+ for kind in module.enums + module.structs + module.unions:
+ kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
+ if kind_name in kinds:
+ previous_module = kinds[kind_name]
+ if previous_module.path != module.path:
+ raise check.CheckException(
+ self.module, f"multiple-definition for type {kind_name}" +
+ f"(defined in both {previous_module} and {module})")
+ kinds[kind_name] = kind.module
+
+ for kind in self.module.enums + self.module.structs + self.module.unions:
+ kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
+ if kind_name in kinds:
+ previous_module = kinds[kind_name]
+ raise check.CheckException(
+ self.module, f"multiple-definition for type {kind_name}" +
+ f"(previous definition in {previous_module})")
+ return True
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
new file mode 100644
index 00000000..07f51a64
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
@@ -0,0 +1,62 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Validate mojo runtime feature guarded interfaces are nullable."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+
+class Check(check.Check):
+ def __init__(self, *args, **kwargs):
+ super(Check, self).__init__(*args, **kwargs)
+
+ # `param` is an Interface of some sort.
+ def _CheckNonNullableFeatureGuardedInterface(self, kind):
+ # Only need to validate interface if it has a RuntimeFeature
+ if not kind.kind.runtime_feature:
+ return
+ # Nullable (optional) is ok as the interface expects they might not be sent.
+ if kind.is_nullable:
+ return
+ interface = kind.kind.mojom_name
+ raise check.CheckException(
+ self.module,
+ f"interface {interface} has a RuntimeFeature but is not nullable")
+
+ # `param` can be a lot of things so check if it is a remote/receiver.
+ # Array/Map must be recursed into.
+ def _CheckFieldOrParam(self, kind):
+ if module.IsAnyInterfaceKind(kind):
+ self._CheckNonNullableFeatureGuardedInterface(kind)
+ if module.IsArrayKind(kind):
+ self._CheckFieldOrParam(kind.kind)
+ if module.IsMapKind(kind):
+ self._CheckFieldOrParam(kind.key_kind)
+ self._CheckFieldOrParam(kind.value_kind)
+
+ def _CheckInterfaceFeatures(self, interface):
+ for method in interface.methods:
+ for param in method.parameters:
+ self._CheckFieldOrParam(param.kind)
+ if method.response_parameters:
+ for param in method.response_parameters:
+ self._CheckFieldOrParam(param.kind)
+
+ def _CheckStructFeatures(self, struct):
+ for field in struct.fields:
+ self._CheckFieldOrParam(field.kind)
+
+ def _CheckUnionFeatures(self, union):
+ for field in union.fields:
+ self._CheckFieldOrParam(field.kind)
+
+ def CheckModule(self):
+ """Validate that any runtime feature guarded interfaces that might be passed
+ over mojo are nullable."""
+ for interface in self.module.interfaces:
+ self._CheckInterfaceFeatures(interface)
+ for struct in self.module.structs:
+ self._CheckStructFeatures(struct)
+ for union in self.module.unions:
+ self._CheckUnionFeatures(union)
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
new file mode 100644
index 00000000..e96152fd
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
@@ -0,0 +1,173 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mojom.generate.check as check
+from mojom_bindings_generator import LoadChecks, _Generate
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class FakeArgs:
+ """Fakes args to _Generate - intention is to do just enough to run checks"""
+ def __init__(self, tester, files=None):
+ """ `tester` is MojomParserTestCase for paths.
+ `files` will have tester path added."""
+ self.checks_string = 'features'
+ self.depth = tester.GetPath('')
+ self.filelist = None
+ self.filename = [tester.GetPath(x) for x in files]
+ self.gen_directories = tester.GetPath('gen')
+ self.generators_string = ''
+ self.import_directories = []
+ self.output_dir = tester.GetPath('out')
+ self.scrambled_message_id_salt_paths = None
+ self.typemaps = []
+ self.variant = 'none'
+
+
+class MojoBindingsCheckTest(MojomParserTestCase):
+ def _ParseAndGenerate(self, mojoms):
+ self.ParseMojoms(mojoms)
+ args = FakeArgs(self, files=mojoms)
+ _Generate(args, {})
+
+ def assertValid(self, filename, content):
+ self.WriteFile(filename, content)
+ self._ParseAndGenerate([filename])
+
+ def assertThrows(self, filename, content, regexp):
+ mojoms = []
+ self.WriteFile(filename, content)
+ mojoms.append(filename)
+ with self.assertRaisesRegexp(check.CheckException, regexp):
+ self._ParseAndGenerate(mojoms)
+
+ def testLoads(self):
+ """Validate that the check is registered under the expected name."""
+ check_modules = LoadChecks('features')
+ self.assertTrue(check_modules['features'])
+
+ def testNullableOk(self):
+ self.assertValid(
+ "a.mojom", """
+ module a;
+ // Scaffolding.
+ feature kFeature {
+ const string name = "Hello";
+ const bool enabled_state = false;
+ };
+ [RuntimeFeature=kFeature]
+ interface Guarded {
+ };
+
+ // Unguarded interfaces should be ok everywhere.
+ interface NotGuarded { };
+
+ // Optional (nullable) interfaces should be ok everywhere:
+ struct Bar {
+ pending_remote<Guarded>? remote;
+ pending_receiver<Guarded>? receiver;
+ };
+ union Thingy {
+ pending_remote<Guarded>? remote;
+ pending_receiver<Guarded>? receiver;
+ };
+ interface Foo {
+ Foo(
+ pending_remote<Guarded>? remote,
+ pending_receiver<Guarded>? receiver,
+ pending_associated_remote<Guarded>? a_remote,
+ pending_associated_receiver<Guarded>? a_receiver,
+ // Unguarded interfaces do not have to be nullable.
+ pending_remote<NotGuarded> remote,
+ pending_receiver<NotGuarded> receiver,
+ pending_associated_remote<NotGuarded> a_remote,
+ pending_associated_receiver<NotGuarded> a_receiver
+ ) => (
+ pending_remote<Guarded>? remote,
+ pending_receiver<Guarded>? receiver
+ );
+ Bar(array<pending_remote<Guarded>?> remote)
+ => (map<string, pending_receiver<Guarded>?> a);
+ };
+ """)
+
+ def testMethodParamsMustBeNullable(self):
+ prelude = """
+ module a;
+ // Scaffolding.
+ feature kFeature {
+ const string name = "Hello";
+ const bool enabled_state = false;
+ };
+ [RuntimeFeature=kFeature]
+ interface Guarded { };
+ """
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(pending_remote<Guarded> a) => ();
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(bool foo) => (pending_receiver<Guarded> a);
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(pending_receiver<Guarded> a) => ();
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(pending_associated_remote<Guarded> a) => ();
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(pending_associated_receiver<Guarded> a) => ();
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(array<pending_associated_receiver<Guarded>> a) => ();
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ interface Trial {
+ Method(map<string, pending_associated_receiver<Guarded>> a) => ();
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+
+ def testStructUnionMembersMustBeNullable(self):
+ prelude = """
+ module a;
+ // Scaffolding.
+ feature kFeature {
+ const string name = "Hello";
+ const bool enabled_state = false;
+ };
+ [RuntimeFeature=kFeature]
+ interface Guarded { };
+ """
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ struct Trial {
+ pending_remote<Guarded> a;
+ };
+ """, 'interface Guarded has a RuntimeFeature')
+ self.assertThrows(
+ 'a.mojom', prelude + """
+ union Trial {
+ pending_remote<Guarded> a;
+ };
+ """, 'interface Guarded has a RuntimeFeature')
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
new file mode 100644
index 00000000..d570e26c
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
@@ -0,0 +1,102 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Validate RequireContext and AllowedContext annotations before generation."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+
+class Check(check.Check):
+ def __init__(self, *args, **kwargs):
+ self.kind_to_interfaces = dict()
+ super(Check, self).__init__(*args, **kwargs)
+
+ def _IsPassedInterface(self, candidate):
+ if isinstance(
+ candidate.kind,
+ (module.PendingReceiver, module.PendingRemote,
+ module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):
+ return True
+ return False
+
+ def _CheckInterface(self, method, param):
+ # |param| is a pending_x<Interface> so need .kind.kind to get Interface.
+ interface = param.kind.kind
+ if interface.require_context:
+ if method.allowed_context is None:
+ raise check.CheckException(
+ self.module, "method `{}` has parameter `{}` which passes interface"
+ " `{}` that requires an AllowedContext annotation but none exists.".
+ format(
+ method.mojom_name,
+ param.mojom_name,
+ interface.mojom_name,
+ ))
+ # If a string was provided, or if an enum was not imported, this will
+ # be a string and we cannot validate that it is in range.
+ if not isinstance(method.allowed_context, module.EnumValue):
+ raise check.CheckException(
+ self.module,
+ "method `{}` has AllowedContext={} which is not a valid enum value."
+ .format(method.mojom_name, method.allowed_context))
+ # EnumValue must be from the same enum to be compared.
+ if interface.require_context.enum != method.allowed_context.enum:
+ raise check.CheckException(
+ self.module, "method `{}` has parameter `{}` which passes interface"
+ " `{}` that requires AllowedContext={} but one of kind `{}` was "
+ "provided.".format(
+ method.mojom_name,
+ param.mojom_name,
+ interface.mojom_name,
+ interface.require_context.enum,
+ method.allowed_context.enum,
+ ))
+ # RestrictContext enums have most privileged field first (lowest value).
+ interface_value = interface.require_context.field.numeric_value
+ method_value = method.allowed_context.field.numeric_value
+ if interface_value < method_value:
+ raise check.CheckException(
+ self.module, "RequireContext={} > AllowedContext={} for method "
+ "`{}` which passes interface `{}`.".format(
+ interface.require_context.GetSpec(),
+ method.allowed_context.GetSpec(), method.mojom_name,
+ interface.mojom_name))
+ return True
+
+ def _GatherReferencedInterfaces(self, field):
+ key = field.kind.spec
+ # structs/unions can nest themselves so we need to bookkeep.
+ if not key in self.kind_to_interfaces:
+ # Might reference ourselves so have to create the list first.
+ self.kind_to_interfaces[key] = set()
+ for param in field.kind.fields:
+ if self._IsPassedInterface(param):
+ self.kind_to_interfaces[key].add(param)
+ elif isinstance(param.kind, (module.Struct, module.Union)):
+ for iface in self._GatherReferencedInterfaces(param):
+ self.kind_to_interfaces[key].add(iface)
+ return self.kind_to_interfaces[key]
+
+ def _CheckParams(self, method, params):
+ # Note: we have to repeat _CheckParams for each method as each might have
+ # different AllowedContext= attributes. We cannot memoize this function,
+ # but can do so for gathering referenced interfaces as their RequireContext
+ # attributes do not change.
+ for param in params:
+ if self._IsPassedInterface(param):
+ self._CheckInterface(method, param)
+ elif isinstance(param.kind, (module.Struct, module.Union)):
+ for interface in self._GatherReferencedInterfaces(param):
+ self._CheckInterface(method, interface)
+
+ def _CheckMethod(self, method):
+ if method.parameters:
+ self._CheckParams(method, method.parameters)
+ if method.response_parameters:
+ self._CheckParams(method, method.response_parameters)
+
+ def CheckModule(self):
+ for interface in self.module.interfaces:
+ for method in interface.methods:
+ self._CheckMethod(method)
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
new file mode 100644
index 00000000..a6cd71e2
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
@@ -0,0 +1,254 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mojom.generate.check as check
+from mojom_bindings_generator import LoadChecks, _Generate
+from mojom_parser_test_case import MojomParserTestCase
+
+# Mojoms that we will use in multiple tests.
+basic_mojoms = {
+ 'level.mojom':
+ """
+ module level;
+ enum Level {
+ kHighest,
+ kMiddle,
+ kLowest,
+ };
+ """,
+ 'interfaces.mojom':
+ """
+ module interfaces;
+ import "level.mojom";
+ struct Foo {int32 bar;};
+ [RequireContext=level.Level.kHighest]
+ interface High {
+ DoFoo(Foo foo);
+ };
+ [RequireContext=level.Level.kMiddle]
+ interface Mid {
+ DoFoo(Foo foo);
+ };
+ [RequireContext=level.Level.kLowest]
+ interface Low {
+ DoFoo(Foo foo);
+ };
+ """
+}
+
+
+class FakeArgs:
+ """Fakes args to _Generate - intention is to do just enough to run checks"""
+
+ def __init__(self, tester, files=None):
+ """ `tester` is MojomParserTestCase for paths.
+ `files` will have tester path added."""
+ self.checks_string = 'restrictions'
+ self.depth = tester.GetPath('')
+ self.filelist = None
+ self.filename = [tester.GetPath(x) for x in files]
+ self.gen_directories = tester.GetPath('gen')
+ self.generators_string = ''
+ self.import_directories = []
+ self.output_dir = tester.GetPath('out')
+ self.scrambled_message_id_salt_paths = None
+ self.typemaps = []
+ self.variant = 'none'
+
+
+class MojoBindingsCheckTest(MojomParserTestCase):
+ def _WriteBasicMojoms(self):
+ for filename, contents in basic_mojoms.items():
+ self.WriteFile(filename, contents)
+ return list(basic_mojoms.keys())
+
+ def _ParseAndGenerate(self, mojoms):
+ self.ParseMojoms(mojoms)
+ args = FakeArgs(self, files=mojoms)
+ _Generate(args, {})
+
+ def testLoads(self):
+ """Validate that the check is registered under the expected name."""
+ check_modules = LoadChecks('restrictions')
+ self.assertTrue(check_modules['restrictions'])
+
+ def testValidAnnotations(self):
+ mojoms = self._WriteBasicMojoms()
+
+ a = 'a.mojom'
+ self.WriteFile(
+ a, """
+ module a;
+ import "level.mojom";
+ import "interfaces.mojom";
+
+ interface PassesHigh {
+ [AllowedContext=level.Level.kHighest]
+ DoHigh(pending_receiver<interfaces.High> hi);
+ };
+ interface PassesMedium {
+ [AllowedContext=level.Level.kMiddle]
+ DoMedium(pending_receiver<interfaces.Mid> hi);
+ [AllowedContext=level.Level.kMiddle]
+ DoMediumRem(pending_remote<interfaces.Mid> hi);
+ [AllowedContext=level.Level.kMiddle]
+ DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
+ [AllowedContext=level.Level.kMiddle]
+ DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
+ };
+ interface PassesLow {
+ [AllowedContext=level.Level.kLowest]
+ DoLow(pending_receiver<interfaces.Low> hi);
+ };
+
+ struct One { pending_receiver<interfaces.High> hi; };
+ struct Two { One one; };
+ interface PassesNestedHigh {
+ [AllowedContext=level.Level.kHighest]
+ DoNestedHigh(Two two);
+ };
+
+ // Allowed as PassesHigh is not itself restricted.
+ interface PassesPassesHigh {
+ DoPass(pending_receiver<PassesHigh> hiho);
+ };
+ """)
+ mojoms.append(a)
+ self._ParseAndGenerate(mojoms)
+
+ def _testThrows(self, filename, content, regexp):
+ mojoms = self._WriteBasicMojoms()
+ self.WriteFile(filename, content)
+ mojoms.append(filename)
+ with self.assertRaisesRegexp(check.CheckException, regexp):
+ self._ParseAndGenerate(mojoms)
+
+ def testMissingAnnotation(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+
+ interface PassesHigh {
+ // err: missing annotation.
+ DoHigh(pending_receiver<interfaces.High> hi);
+ };
+ """
+ self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
+
+ def testAllowTooLow(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+
+ interface PassesHigh {
+ // err: level is worse than required.
+ [AllowedContext=level.Level.kMiddle]
+ DoHigh(pending_receiver<interfaces.High> hi);
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
+
+ def testWrongEnumInAllow(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+ enum Blah {
+ kZero,
+ };
+ interface PassesHigh {
+ // err: different enums.
+ [AllowedContext=Blah.kZero]
+ DoHigh(pending_receiver<interfaces.High> hi);
+ };
+ """
+ self._testThrows('b.mojom', contents, 'but one of kind')
+
+ def testNotAnEnumInAllow(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+ interface PassesHigh {
+ // err: not an enum.
+ [AllowedContext=doopdedoo.mojom.kWhatever]
+ DoHigh(pending_receiver<interfaces.High> hi);
+ };
+ """
+ self._testThrows('b.mojom', contents, 'not a valid enum value')
+
+ def testMissingAllowedForNestedStructs(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+ struct One { pending_receiver<interfaces.High> hi; };
+ struct Two { One one; };
+ interface PassesNestedHigh {
+ // err: missing annotation.
+ DoNestedHigh(Two two);
+ };
+ """
+ self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
+
+ def testMissingAllowedForNestedUnions(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+ struct One { pending_receiver<interfaces.High> hi; };
+ struct Two { One one; };
+ union Three {One one; Two two; };
+ interface PassesNestedHigh {
+ // err: missing annotation.
+ DoNestedHigh(Three three);
+ };
+ """
+ self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
+
+ def testMultipleInterfacesThrows(self):
+ contents = """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+ struct One { pending_receiver<interfaces.High> hi; };
+ interface PassesMultipleInterfaces {
+ [AllowedContext=level.Level.kMiddle]
+ DoMultiple(
+ pending_remote<interfaces.Mid> mid,
+ pending_receiver<interfaces.High> hi,
+ One one
+ );
+ };
+ """
+ self._testThrows('b.mojom', contents,
+ 'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
+
+ def testMultipleInterfacesAllowed(self):
+ """Multiple interfaces can be passed, all satisfy the level."""
+ mojoms = self._WriteBasicMojoms()
+
+ b = "b.mojom"
+ self.WriteFile(
+ b, """
+ module b;
+ import "level.mojom";
+ import "interfaces.mojom";
+ struct One { pending_receiver<interfaces.High> hi; };
+ interface PassesMultipleInterfaces {
+ [AllowedContext=level.Level.kHighest]
+ DoMultiple(
+ pending_receiver<interfaces.High> hi,
+ pending_remote<interfaces.Mid> mid,
+ One one
+ );
+ };
+ """)
+ mojoms.append(b)
+ self._ParseAndGenerate(mojoms)
diff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni
deleted file mode 100644
index d8a13874..00000000
--- a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-_typemap_imports = [
- "//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni",
- "//chrome/common/importer/typemaps.gni",
- "//chrome/common/media_router/mojom/typemaps.gni",
- "//chrome/typemaps.gni",
- "//chromecast/typemaps.gni",
- "//chromeos/typemaps.gni",
- "//chromeos/components/multidevice/mojom/typemaps.gni",
- "//chromeos/services/cros_healthd/public/mojom/typemaps.gni",
- "//chromeos/services/device_sync/public/mojom/typemaps.gni",
- "//chromeos/services/network_config/public/mojom/typemaps.gni",
- "//chromeos/services/secure_channel/public/mojom/typemaps.gni",
- "//components/arc/mojom/typemaps.gni",
- "//components/chromeos_camera/common/typemaps.gni",
- "//components/services/storage/public/cpp/filesystem/typemaps.gni",
- "//components/sync/mojom/typemaps.gni",
- "//components/typemaps.gni",
- "//content/browser/typemaps.gni",
- "//content/public/common/typemaps.gni",
- "//sandbox/mac/mojom/typemaps.gni",
- "//services/media_session/public/cpp/typemaps.gni",
- "//services/proxy_resolver/public/cpp/typemaps.gni",
- "//services/resource_coordinator/public/cpp/typemaps.gni",
- "//services/service_manager/public/cpp/typemaps.gni",
- "//services/tracing/public/mojom/typemaps.gni",
-]
-
-_typemaps = []
-foreach(typemap_import, _typemap_imports) {
- # Avoid reassignment error by assigning to empty scope first.
- _imported = {
- }
- _imported = read_file(typemap_import, "scope")
- _typemaps += _imported.typemaps
-}
-
-typemaps = []
-foreach(typemap, _typemaps) {
- typemaps += [
- {
- filename = typemap
- config = read_file(typemap, "scope")
- },
- ]
-}
-
-component_macro_suffix = ""
diff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py
deleted file mode 100644
index a978901b..00000000
--- a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import sys
-import argparse
-
-_HERE_PATH = os.path.dirname(__file__)
-_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
-
-sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
-import node
-import node_modules
-
-def main(argv):
- parser = argparse.ArgumentParser()
- parser.add_argument('--tsconfig_path', required=True)
- args = parser.parse_args(argv)
-
- result = node.RunNode([node_modules.PathToTypescript()] +
- ['--project', args.tsconfig_path])
- if len(result) != 0:
- raise RuntimeError('Failed to compile Typescript: \n%s' % result)
-
-if __name__ == '__main__':
- main(sys.argv[1:])
diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
index 48bc66fd..4dd26d4a 100755
--- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
+++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Copyright 2019 The Chromium Authors. All rights reserved.
+# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
@@ -15,6 +15,7 @@
from __future__ import print_function
import optparse
+import sys
def Concatenate(filenames):
@@ -47,7 +48,7 @@ def main():
parser.set_usage("""Concatenate several files into one.
Equivalent to: cat file1 ... > target.""")
(_options, args) = parser.parse_args()
- exit(0 if Concatenate(args) else 1)
+ sys.exit(0 if Concatenate(args) else 1)
if __name__ == "__main__":
diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
index be8985ce..7d56c9f9 100755
--- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
+++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -20,6 +20,7 @@ from __future__ import print_function
import optparse
import re
+import sys
_MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
@@ -31,10 +32,10 @@ def FilterLine(filename, line, output):
return
if line.startswith("goog.provide"):
- match = re.match("goog.provide\('([^']+)'\);", line)
+ match = re.match(r"goog.provide\('([^']+)'\);", line)
if not match:
print("Invalid goog.provide line in %s:\n%s" % (filename, line))
- exit(1)
+ sys.exit(1)
module_name = match.group(1)
if module_name == _MOJO_INTERNAL_MODULE_NAME:
@@ -67,7 +68,8 @@ def main():
Concatenate several files into one, stripping Closure provide and
require directives along the way.""")
(_, args) = parser.parse_args()
- exit(0 if ConcatenateAndReplaceExports(args) else 1)
+ sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
+
if __name__ == "__main__":
main()
diff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py
deleted file mode 100755
index 7ac4af5f..00000000
--- a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-from __future__ import print_function
-
-import sys
-
-# This utility converts mojom dependencies into their corresponding typemap
-# paths and formats them to be consumed by generate_type_mappings.py.
-
-
-def FormatTypemap(typemap_filename):
- # A simple typemap is valid Python with a minor alteration.
- with open(typemap_filename) as f:
- typemap_content = f.read().replace('=\n', '=')
- typemap = {}
- exec typemap_content in typemap
-
- for header in typemap.get('public_headers', []):
- yield 'public_headers=%s' % header
- for header in typemap.get('traits_headers', []):
- yield 'traits_headers=%s' % header
- for header in typemap.get('type_mappings', []):
- yield 'type_mappings=%s' % header
-
-
-def main():
- typemaps = sys.argv[1:]
- print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))
- for typemap in typemaps))
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
index 8b78d092..c6daff03 100644
--- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
+++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
@@ -1,4 +1,4 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
+# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates a list of all files in a directory.
diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
index a0096649..4a53e2bf 100755
--- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
+++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Copyright 2016 The Chromium Authors. All rights reserved.
+# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates a JSON typemap from its command-line arguments and dependencies.
@@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):
for entry in config['types']:
configs[entry['mojom']] = {
'typename': entry['cpp'],
+ 'forward_declaration': entry.get('forward_declaration', None),
'public_headers': config.get('traits_headers', []),
'traits_headers': config.get('traits_private_headers', []),
'copyable_pass_by_value': entry.get('copyable_pass_by_value',
False),
+ 'default_constructible': entry.get('default_constructible', True),
'force_serialize': entry.get('force_serialize', False),
'hashable': entry.get('hashable', False),
'move_only': entry.get('move_only', False),
diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
new file mode 100755
index 00000000..cefee7a4
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This utility minifies JS files with terser.
+#
+# Instance of 'node' has no 'RunNode' member (no-member)
+# pylint: disable=no-member
+
+import argparse
+import os
+import sys
+
+_HERE_PATH = os.path.dirname(__file__)
+_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
+_CWD = os.getcwd()
+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
+import node
+import node_modules
+
+
+def MinifyFile(input_file, output_file):
+ node.RunNode([
+ node_modules.PathToTerser(), input_file, '--mangle', '--compress',
+ '--comments', 'false', '--output', output_file
+ ])
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--input', required=True)
+ parser.add_argument('--output', required=True)
+ args = parser.parse_args(argv)
+
+ # Delete the output file if it already exists. It may be a sym link to the
+ # input, because in non-optimized/pre-Terser builds the input file is copied
+ # to the output location with gn copy().
+ out_path = os.path.join(_CWD, args.output)
+ if (os.path.exists(out_path)):
+ os.remove(out_path)
+
+ MinifyFile(os.path.join(_CWD, args.input), out_path)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni
index fe2a1da3..3f6e54e0 100644
--- a/utils/ipc/mojo/public/tools/bindings/mojom.gni
+++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni
@@ -1,25 +1,28 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/python.gni")
import("//third_party/closure_compiler/closure_args.gni")
import("//third_party/closure_compiler/compile_js.gni")
import("//third_party/protobuf/proto_library.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
import("//ui/webui/webui_features.gni")
+import("//build/config/cast.gni")
+
# TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're
# used to conditionally enable message ID scrambling in a way which is
# consistent across toolchains and which is affected by branded vs non-branded
# Chrome builds. Ideally we could create some generic knobs here that could be
# flipped elsewhere though.
import("//build/config/chrome_build.gni")
-import("//build/config/chromecast_build.gni")
import("//build/config/chromeos/ui_mode.gni")
+import("//build/config/features.gni")
import("//build/config/nacl/config.gni")
import("//build/toolchain/kythe.gni")
import("//components/nacl/features.gni")
import("//third_party/jinja2/jinja2.gni")
+import("//third_party/ply/ply.gni")
import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
declare_args() {
# Indicates whether typemapping should be supported in this build
@@ -34,21 +37,30 @@ declare_args() {
# Controls message ID scrambling behavior. If |true|, message IDs are
# scrambled (i.e. randomized based on the contents of //chrome/VERSION) on
- # non-Chrome OS desktop platforms. Set to |false| to disable message ID
- # scrambling on all platforms.
- enable_mojom_message_id_scrambling = true
+ # non-Chrome OS desktop platforms. Enabled on official builds by default.
+ # Set to |true| to enable message ID scrambling on a specific build.
+ # See also `enable_scrambled_message_ids` below for more details.
+ enable_mojom_message_id_scrambling = is_official_build
+
+ # Enables generating javascript fuzzing-related code and the bindings for the
+ # MojoLPM fuzzer targets. Off by default.
+ enable_mojom_fuzzer = false
# Enables Closure compilation of generated JS lite bindings. In environments
# where compilation is supported, any mojom target "foo" will also have a
# corresponding "foo_js_library_for_compile" target generated.
- enable_mojom_closure_compile = enable_js_type_check && optimize_webui
-
- # Enables generating Typescript bindings and compiling them to JS bindings.
- enable_typescript_bindings = false
+ if (is_chromeos_ash) {
+ enable_mojom_closure_compile = enable_js_type_check && optimize_webui
+ }
+}
- # Enables generating javascript fuzzing-related code and the bindings for the
- # MojoLPM fuzzer targets. Off by default.
- enable_mojom_fuzzer = false
+# Closure libraries are needed for mojom_closure_compile, and when
+# js_type_check is enabled on Ash.
+if (is_chromeos_ash) {
+ generate_mojom_closure_libraries =
+ enable_mojom_closure_compile || enable_js_type_check
+} else {
+ generate_mojom_closure_libraries = false
}
# NOTE: We would like to avoid scrambling message IDs where it doesn't add
@@ -69,9 +81,8 @@ declare_args() {
# lacros-chrome switches to target_os="chromeos"
enable_scrambled_message_ids =
enable_mojom_message_id_scrambling &&
- (is_mac || is_win ||
- (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) ||
- ((enable_nacl || is_nacl || is_nacl_nonsfi) &&
+ (is_mac || is_win || (is_linux && !is_castos) ||
+ ((enable_nacl || is_nacl) &&
(target_os != "chromeos" && !chromeos_is_browser_only)))
_mojom_tools_root = "//mojo/public/tools"
@@ -80,7 +91,9 @@ mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py"
mojom_parser_sources = [
"$_mojom_library_root/__init__.py",
"$_mojom_library_root/error.py",
+ "$_mojom_library_root/fileutil.py",
"$_mojom_library_root/generate/__init__.py",
+ "$_mojom_library_root/generate/check.py",
"$_mojom_library_root/generate/generator.py",
"$_mojom_library_root/generate/module.py",
"$_mojom_library_root/generate/pack.py",
@@ -88,21 +101,32 @@ mojom_parser_sources = [
"$_mojom_library_root/generate/translate.py",
"$_mojom_library_root/parse/__init__.py",
"$_mojom_library_root/parse/ast.py",
+ "$_mojom_library_root/parse/conditional_features.py",
"$_mojom_library_root/parse/lexer.py",
"$_mojom_library_root/parse/parser.py",
+ "//tools/diagnosis/crbug_1001171.py",
]
mojom_generator_root = "$_mojom_tools_root/bindings"
mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
mojom_generator_sources =
mojom_parser_sources + [
+ "$mojom_generator_root/checks/__init__.py",
+ "$mojom_generator_root/checks/mojom_attributes_check.py",
+ "$mojom_generator_root/checks/mojom_definitions_check.py",
+ "$mojom_generator_root/checks/mojom_interface_feature_check.py",
+ "$mojom_generator_root/checks/mojom_restrictions_check.py",
+ "$mojom_generator_root/generators/__init__.py",
"$mojom_generator_root/generators/cpp_util.py",
"$mojom_generator_root/generators/mojom_cpp_generator.py",
"$mojom_generator_root/generators/mojom_java_generator.py",
- "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
"$mojom_generator_root/generators/mojom_js_generator.py",
+ "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
"$mojom_generator_root/generators/mojom_ts_generator.py",
"$mojom_generator_script",
+ "//build/action_helpers.py",
+ "//build/gn_helpers.py",
+ "//build/zip_helpers.py",
]
if (enable_scrambled_message_ids) {
@@ -243,12 +267,16 @@ if (enable_scrambled_message_ids) {
# |cpp_only| is set to true, it overrides this to prevent generation of
# Java bindings.
#
-# enable_fuzzing (optional)
+# enable_js_fuzzing (optional)
+# Enables generation of javascript fuzzing sources for the target if the
+# global build arg |enable_mojom_fuzzer| is also set to |true|.
+# Defaults to |true|. If JS fuzzing generation is enabled for a target,
+# the target will always generate JS bindings even if |cpp_only| is set to
+# |true|. See note above.
+#
+# enable_mojolpm_fuzzing (optional)
# Enables generation of fuzzing sources for the target if the global build
-# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If
-# fuzzing generation is enabled for a target, the target will always
-# generate JS bindings even if |cpp_only| is set to |true|. See note
-# above.
+# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.
#
# support_lazy_serialization (optional)
# If set to |true|, generated C++ bindings will effectively prefer to
@@ -310,8 +338,15 @@ if (enable_scrambled_message_ids) {
# correct dependency order. Note that this only has an effect if
# the |enable_mojom_closure_compile| global arg is set to |true| as well.
#
-# use_typescript_sources (optional)
-# Uses the Typescript generator to generate JavaScript bindings.
+# generate_webui_js_bindings (optional)
+# Generate WebUI bindings in JavaScript rather than TypeScript. Defaults
+# to false. ChromeOS only parameter.
+#
+# generate_legacy_js_bindings (optional)
+# Generate js_data_deps target containing legacy JavaScript bindings files
+# for Blink tests and other non-WebUI users when generating TypeScript
+# bindings for WebUI. Ignored if generate_webui_js_bindings is set to
+# true.
#
# js_generate_struct_deserializers (optional)
# Generates JS deerialize methods for structs.
@@ -323,17 +358,23 @@ if (enable_scrambled_message_ids) {
# webui_module_path (optional)
# The path or URL at which modules generated by this target will be
# accessible to WebUI pages. This may either be an absolute path or
-# a full URL path starting with "chrome://resources/mojo".
+# a full URL path starting with "chrome://resources/mojo". If this path
+# is not specified, WebUI bindings will not be generated.
#
# If an absolute path, a WebUI page may only import these modules if
-# they are manually packaged and mapped independently by that page's
-# WebUIDataSource. The mapped path must match the path given here.
+# they are added to that page's data source (usually by adding the
+# modules to the mojo_files list for build_webui(), or by listing the
+# files as inputs to the page's ts_library() and/or generate_grd() build
+# steps.
#
# If this is is instead a URL string starting with
-# "chrome://resources/mojo", the generated resources must be added to
-# content_resources.grd and registered with
-# content::SharedResourcesDataSource with a corresponding path, at which
-# point they will be made available to all WebUI pages at the given URL.
+# "chrome://resources/mojo", the resulting bindings files should
+# be added to one of the lists in ui/webui/resources/mojo/BUILD.gn,
+# at which point they will be made available to all WebUI pages at the
+# given URL.
+#
+# Note: WebUI module bindings are generated in TypeScript by default,
+# unless |generate_webui_js_bindings| is specified as true.
#
# The following parameters are used to support the component build. They are
# needed so that bindings which are linked with a component can use the same
@@ -402,16 +443,41 @@ if (enable_scrambled_message_ids) {
# should be mapped in generated bindings. This is a string like
# "::base::Value" or "std::vector<::base::Value>".
#
-# move_only (optional)
-# A boolean value (default false) which indicates whether the C++
-# type is move-only. If true, generated bindings will pass the type
-# by value and use std::move() at call sites.
-#
# copyable_pass_by_value (optional)
# A boolean value (default false) which effectively indicates
# whether the C++ type is very cheap to copy. If so, generated
# bindings will pass by value but not use std::move() at call sites.
#
+# default_constructible (optional)
+# A boolean value (default true) which indicates whether the C++
+# type is default constructible. If a C++ type is not default
+# constructible (e.g. the implementor of the type prefers not to
+# publicly expose a default constructor that creates an object in an
+# invalid state), Mojo will instead construct C++ type with an
+# argument of the type `mojo::DefaultConstruct::Tag` (essentially a
+# passkey-like type specifically for this use case).
+#
+# force_serialize (optional)
+# A boolean value (default false) which disables lazy serialization
+# of the typemapped type if lazy serialization is enabled for the
+# mojom target applying this typemap.
+#
+# forward_declaration (optional)
+# A forward declaration of the C++ type, which bindings that don't
+# need the full type definition can use to reduce the size of
+# the generated code. This is a string like
+# "namespace base { class Value; }".
+#
+# hashable (optional)
+# A boolean value (default false) indicating whether the C++ type is
+# hashable. Set to true if true AND needed (i.e. you need to use the
+# type as the key of a mojom map).
+#
+# move_only (optional)
+# A boolean value (default false) which indicates whether the C++
+# type is move-only. If true, generated bindings will pass the type
+# by value and use std::move() at call sites.
+#
# nullable_is_same_type (optional)
# A boolean value (default false) which indicates that the C++ type
# has some baked-in semantic notion of a "null" state. If true, the
@@ -421,16 +487,6 @@ if (enable_scrambled_message_ids) {
# type with absl::optional, and null values are simply
# absl::nullopt.
#
-# hashable (optional)
-# A boolean value (default false) indicating whether the C++ type is
-# hashable. Set to true if true AND needed (i.e. you need to use the
-# type as the key of a mojom map).
-#
-# force_serialize (optional)
-# A boolean value (default false) which disables lazy serialization
-# of the typemapped type if lazy serialization is enabled for the
-# mojom target applying this typemap.
-#
# Additional typemap scope parameters:
#
# traits_headers (optional)
@@ -621,20 +677,26 @@ template("mojom") {
build_metadata_filename = "$target_gen_dir/$target_name.build_metadata"
build_metadata = {
}
- build_metadata.sources = rebase_path(sources_list)
+ build_metadata.sources = rebase_path(sources_list, target_gen_dir)
build_metadata.deps = []
foreach(dep, all_deps) {
dep_target_gen_dir = get_label_info(dep, "target_gen_dir")
dep_name = get_label_info(dep, "name")
build_metadata.deps +=
- [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ]
+ [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata",
+ target_gen_dir) ]
}
write_file(build_metadata_filename, build_metadata, "json")
- generate_fuzzing =
- (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&
+ generate_js_fuzzing =
+ (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&
enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)
+ generate_mojolpm_fuzzing =
+ (!defined(invoker.enable_mojolpm_fuzzing) ||
+ invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&
+ (!defined(invoker.testonly) || !invoker.testonly)
+
parser_target_name = "${target_name}__parser"
parser_deps = []
foreach(dep, all_deps) {
@@ -665,30 +727,34 @@ template("mojom") {
"is_chromeos",
"is_chromeos_ash",
]
+ } else if (is_chromeos_lacros) {
+ enabled_features += [
+ "is_chromeos",
+ "is_chromeos_lacros",
+ ]
} else if (is_fuchsia) {
enabled_features += [ "is_fuchsia" ]
} else if (is_ios) {
enabled_features += [ "is_ios" ]
- } else if (is_linux || is_chromeos_lacros) {
+ } else if (is_linux) {
enabled_features += [ "is_linux" ]
- if (is_chromeos_lacros) {
- enabled_features += [
- "is_chromeos",
- "is_chromeos_lacros",
- ]
- }
} else if (is_mac) {
enabled_features += [ "is_mac" ]
} else if (is_win) {
enabled_features += [ "is_win" ]
}
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(parser_target_name) {
+ if (is_apple) {
+ enabled_features += [ "is_apple" ]
+ }
+
+ action(parser_target_name) {
+ allow_remote = true
+ custom_processor = "mojom_parser"
script = mojom_parser_script
- inputs = mojom_parser_sources + [ build_metadata_filename ]
+ inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]
sources = sources_list
- deps = parser_deps
+ public_deps = parser_deps
outputs = []
foreach(base_path, output_file_base_paths) {
filename = get_path_info(base_path, "file")
@@ -698,31 +764,35 @@ template("mojom") {
filelist = []
foreach(source, sources_list) {
- filelist += [ rebase_path(source) ]
+ filelist += [ rebase_path(source, root_build_dir) ]
}
- response_file_contents = filelist
+
+ # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+ rsp_file = "$target_gen_dir/${target_name}.rsp"
+ write_file(rsp_file, filelist)
+ inputs += [ rsp_file ]
args = [
# Resolve relative input mojom paths against both the root src dir and
# the root gen dir.
"--input-root",
- rebase_path("//."),
+ rebase_path("//.", root_build_dir),
"--input-root",
- rebase_path(root_gen_dir),
+ rebase_path(root_gen_dir, root_build_dir),
"--output-root",
- rebase_path(root_gen_dir),
+ rebase_path(root_gen_dir, root_build_dir),
- "--mojom-file-list={{response_file_name}}",
+ "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir),
"--check-imports",
- rebase_path(build_metadata_filename),
+ rebase_path(build_metadata_filename, root_build_dir),
]
if (defined(invoker.input_root_override)) {
args += [
"--input-root",
- rebase_path(invoker.input_root_override),
+ rebase_path(invoker.input_root_override, root_build_dir),
]
}
@@ -738,6 +808,13 @@ template("mojom") {
"--add-module-metadata",
"webui_module_path=${invoker.webui_module_path}",
]
+ if (defined(invoker.generate_webui_js_bindings) &&
+ invoker.generate_webui_js_bindings) {
+ args += [
+ "--add-module-metadata",
+ "generate_webui_js=True",
+ ]
+ }
}
}
}
@@ -819,11 +896,12 @@ template("mojom") {
}
}
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_cpp_message_ids_target_name) {
+ action(generator_cpp_message_ids_target_name) {
+ allow_remote = true
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
- sources = sources_list
+ sources = sources_list +
+ [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
deps = [
":$parser_target_name",
"//mojo/public/tools/bindings:precompile_templates",
@@ -835,16 +913,22 @@ template("mojom") {
args = common_generator_args
filelist = []
foreach(source, sources_list) {
- filelist += [ rebase_path("$source", root_build_dir) ]
+ filelist += [ rebase_path(source, root_build_dir) ]
}
foreach(base_path, output_file_base_paths) {
+ filename = get_path_info(base_path, "file")
+ dirname = get_path_info(base_path, "dir")
+ inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ]
}
- response_file_contents = filelist
+ # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+ rsp_file = "$target_gen_dir/${target_name}.rsp"
+ write_file(rsp_file, filelist)
+ inputs += [ rsp_file ]
args += [
- "--filelist={{response_file_name}}",
+ "--filelist=" + rebase_path(rsp_file, root_build_dir),
"--generate_non_variant_code",
"--generate_message_ids",
"-g",
@@ -860,12 +944,13 @@ template("mojom") {
generator_shared_target_name = "${target_name}_shared__generator"
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_shared_target_name) {
+ action(generator_shared_target_name) {
+ allow_remote = true
visibility = [ ":*" ]
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
- sources = sources_list
+ sources = sources_list +
+ [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
deps = [
":$parser_target_name",
"//mojo/public/tools/bindings:precompile_templates",
@@ -878,10 +963,16 @@ template("mojom") {
args = common_generator_args
filelist = []
foreach(source, sources_list) {
- filelist += [ rebase_path("$source", root_build_dir) ]
+ filelist += [ rebase_path(source, root_build_dir) ]
}
foreach(base_path, output_file_base_paths) {
+ # Need the mojom-module as an input to this action.
+ filename = get_path_info(base_path, "file")
+ dirname = get_path_info(base_path, "dir")
+ inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
+
outputs += [
+ "$root_gen_dir/$base_path-features.h",
"$root_gen_dir/$base_path-params-data.h",
"$root_gen_dir/$base_path-shared-internal.h",
"$root_gen_dir/$base_path-shared.cc",
@@ -889,10 +980,13 @@ template("mojom") {
]
}
- response_file_contents = filelist
+ # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+ rsp_file = "$target_gen_dir/${target_name}.rsp"
+ write_file(rsp_file, filelist)
+ inputs += [ rsp_file ]
args += [
- "--filelist={{response_file_name}}",
+ "--filelist=" + rebase_path(rsp_file, root_build_dir),
"--generate_non_variant_code",
"-g",
"c++",
@@ -923,12 +1017,14 @@ template("mojom") {
if (defined(invoker.testonly)) {
testonly = invoker.testonly
}
+ configs += [ "//build/config/compiler:wexit_time_destructors" ]
deps = []
public_deps = []
if (output_file_base_paths != []) {
sources = []
foreach(base_path, output_file_base_paths) {
sources += [
+ "$root_gen_dir/$base_path-features.h",
"$root_gen_dir/$base_path-params-data.h",
"$root_gen_dir/$base_path-shared-internal.h",
"$root_gen_dir/$base_path-shared.cc",
@@ -972,7 +1068,7 @@ template("mojom") {
}
}
- if (generate_fuzzing) {
+ if (generate_mojolpm_fuzzing) {
# This block generates the proto files used for the MojoLPM fuzzer,
# and the corresponding proto targets that will be linked in the fuzzer
# targets. These are independent of the typemappings, and can be done
@@ -981,11 +1077,15 @@ template("mojom") {
generator_mojolpm_proto_target_name =
"${target_name}_mojolpm_proto_generator"
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_mojolpm_proto_target_name) {
+ action(generator_mojolpm_proto_target_name) {
+ allow_remote = true
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
- sources = invoker.sources
+ sources =
+ invoker.sources + [
+ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
+ "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
+ ]
deps = [
":$parser_target_name",
"//mojo/public/tools/bindings:precompile_templates",
@@ -994,15 +1094,37 @@ template("mojom") {
outputs = []
args = common_generator_args
filelist = []
- foreach(source, invoker.sources) {
- filelist += [ rebase_path("$source", root_build_dir) ]
+
+ # Split the input into generated and non-generated source files. They
+ # need to be processed separately.
+ gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
+ non_gen_sources =
+ filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
+ gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
+
+ foreach(source, non_gen_sources) {
+ filelist += [ rebase_path(source, root_build_dir) ]
+ inputs += [ "$target_gen_dir/$source-module" ]
outputs += [ "$target_gen_dir/$source.mojolpm.proto" ]
}
- response_file_contents = filelist
+ foreach(source, gen_sources) {
+ filelist += [ rebase_path(source, root_build_dir) ]
+
+ # For generated files, we assume they're in the target_gen_dir or a
+ # sub-folder of it. Rebase the path so we can get the relative location.
+ source_file = rebase_path(source, target_gen_dir)
+ inputs += [ "$target_gen_dir/$source_file-module" ]
+ outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ]
+ }
+
+ # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+ rsp_file = "$target_gen_dir/${target_name}.rsp"
+ write_file(rsp_file, filelist)
+ inputs += [ rsp_file ]
args += [
- "--filelist={{response_file_name}}",
+ "--filelist=" + rebase_path(rsp_file, root_build_dir),
"--generate_non_variant_code",
"-g",
"mojolpm",
@@ -1014,9 +1136,20 @@ template("mojom") {
proto_library(mojolpm_proto_target_name) {
testonly = true
generate_python = false
+
+ # Split the input into generated and non-generated source files. They
+ # need to be processed separately.
+ gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
+ non_gen_sources =
+ filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
+ gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
sources = process_file_template(
- invoker.sources,
+ non_gen_sources,
[ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ])
+ sources += process_file_template(
+ gen_sources,
+ [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ])
+
import_dirs = [ "//" ]
proto_in_dir = "${root_gen_dir}"
proto_out_dir = "."
@@ -1055,7 +1188,7 @@ template("mojom") {
component_macro_suffix = ""
}
if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&
- !is_ios) {
+ use_blink) {
blink_variant = {
variant = "blink"
component_macro_suffix = "_BLINK"
@@ -1149,39 +1282,6 @@ template("mojom") {
"${bindings_configuration.component_macro_suffix}_IMPL" ]
}
- export_args = []
- export_args_overridden = false
- if (defined(bindings_configuration.for_blink) &&
- bindings_configuration.for_blink) {
- if (defined(invoker.export_class_attribute_blink)) {
- export_args_overridden = true
- export_args += [
- "--export_attribute",
- invoker.export_class_attribute_blink,
- "--export_header",
- invoker.export_header_blink,
- ]
- }
- } else if (defined(invoker.export_class_attribute)) {
- export_args_overridden = true
- export_args += [
- "--export_attribute",
- invoker.export_class_attribute,
- "--export_header",
- invoker.export_header,
- ]
- }
-
- if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
- export_args += [
- "--export_attribute",
- "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
- "${bindings_configuration.component_macro_suffix})",
- "--export_header",
- "base/component_export.h",
- ]
- }
-
generate_java = false
if (!cpp_only && defined(invoker.generate_java)) {
generate_java = invoker.generate_java
@@ -1190,6 +1290,38 @@ template("mojom") {
type_mappings_path =
"$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
if (sources_list != []) {
+ export_args = []
+ export_args_overridden = false
+ if (defined(bindings_configuration.for_blink) &&
+ bindings_configuration.for_blink) {
+ if (defined(invoker.export_class_attribute_blink)) {
+ export_args_overridden = true
+ export_args += [
+ "--export_attribute",
+ invoker.export_class_attribute_blink,
+ "--export_header",
+ invoker.export_header_blink,
+ ]
+ }
+ } else if (defined(invoker.export_class_attribute)) {
+ export_args_overridden = true
+ export_args += [
+ "--export_attribute",
+ invoker.export_class_attribute,
+ "--export_header",
+ invoker.export_header,
+ ]
+ }
+ if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
+ export_args += [
+ "--export_attribute",
+ "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
+ "${bindings_configuration.component_macro_suffix})",
+ "--export_header",
+ "base/component_export.h",
+ ]
+ }
+
generator_cpp_output_suffixes = []
variant_dash_suffix = ""
if (defined(variant)) {
@@ -1198,7 +1330,6 @@ template("mojom") {
generator_cpp_output_suffixes += [
"${variant_dash_suffix}-forward.h",
"${variant_dash_suffix}-import-headers.h",
- "${variant_dash_suffix}-test-utils.cc",
"${variant_dash_suffix}-test-utils.h",
"${variant_dash_suffix}.cc",
"${variant_dash_suffix}.h",
@@ -1207,16 +1338,28 @@ template("mojom") {
generator_target_name = "${target_name}${variant_suffix}__generator"
# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_target_name) {
+ action(generator_target_name) {
+ allow_remote = true
visibility = [ ":*" ]
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
- sources = sources_list
+ sources =
+ sources_list + [
+ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
+ type_mappings_path,
+ ]
+ if (generate_mojolpm_fuzzing &&
+ !defined(bindings_configuration.variant)) {
+ sources += [
+ "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
+ ]
+ }
deps = [
":$parser_target_name",
":$type_mappings_target_name",
"//mojo/public/tools/bindings:precompile_templates",
]
+
if (defined(invoker.parser_deps)) {
deps += invoker.parser_deps
}
@@ -1224,18 +1367,22 @@ template("mojom") {
args = common_generator_args + export_args
filelist = []
foreach(source, sources_list) {
- filelist += [ rebase_path("$source", root_build_dir) ]
+ filelist += [ rebase_path(source, root_build_dir) ]
}
foreach(base_path, output_file_base_paths) {
+ filename = get_path_info(base_path, "file")
+ dirname = get_path_info(base_path, "dir")
+ inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
+
outputs += [
"$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h",
"$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h",
- "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc",
"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h",
"$root_gen_dir/${base_path}${variant_dash_suffix}.cc",
"$root_gen_dir/${base_path}${variant_dash_suffix}.h",
]
- if (generate_fuzzing && !defined(bindings_configuration.variant)) {
+ if (generate_mojolpm_fuzzing &&
+ !defined(bindings_configuration.variant)) {
outputs += [
"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc",
"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h",
@@ -1243,14 +1390,17 @@ template("mojom") {
}
}
- response_file_contents = filelist
-
+ # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+ rsp_file = "$target_gen_dir/${target_name}.rsp"
+ write_file(rsp_file, filelist)
+ inputs += [ rsp_file ]
args += [
- "--filelist={{response_file_name}}",
+ "--filelist=" + rebase_path("$rsp_file", root_build_dir),
"-g",
]
- if (generate_fuzzing && !defined(bindings_configuration.variant)) {
+ if (generate_mojolpm_fuzzing &&
+ !defined(bindings_configuration.variant)) {
args += [ "c++,mojolpm" ]
} else {
args += [ "c++" ]
@@ -1294,6 +1444,8 @@ template("mojom") {
"--extra_cpp_template_paths",
rebase_path(extra_cpp_template, root_build_dir),
]
+ inputs += [ extra_cpp_template ]
+
assert(
get_path_info(extra_cpp_template, "extension") == "tmpl",
"--extra_cpp_template_paths only accepts template files ending in extension .tmpl")
@@ -1306,62 +1458,6 @@ template("mojom") {
}
}
- if (generate_fuzzing && !defined(variant)) {
- # This block contains the C++ targets for the MojoLPM fuzzer, we need to
- # do this here so that we can use the typemap configuration for the
- # empty-variant Mojo target.
-
- mojolpm_target_name = "${target_name}_mojolpm"
- mojolpm_generator_target_name = "${target_name}__generator"
- source_set(mojolpm_target_name) {
- # There are still a few missing header dependencies between mojo targets
- # with typemaps and the dependencies of their typemap headers. It would
- # be good to enable include checking for these in the future though.
- check_includes = false
- testonly = true
- if (defined(invoker.sources)) {
- sources = process_file_template(
- invoker.sources,
- [
- "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
- "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
- ])
- deps = []
- } else {
- sources = []
- deps = []
- }
-
- public_deps = [
- ":$generator_shared_target_name",
-
- # NB: hardcoded dependency on the no-variant variant generator, since
- # mojolpm only uses the no-variant type.
- ":$mojolpm_generator_target_name",
- ":$mojolpm_proto_target_name",
- "//base",
- "//mojo/public/tools/fuzzers:mojolpm",
- ]
-
- foreach(d, all_deps) {
- # Resolve the name, so that a target //mojo/something becomes
- # //mojo/something:something and we can append variant_suffix to
- # get the cpp dependency name.
- full_name = get_label_info("$d", "label_no_toolchain")
- public_deps += [ "${full_name}_mojolpm" ]
- }
-
- foreach(config, cpp_typemap_configs) {
- if (defined(config.traits_deps)) {
- deps += config.traits_deps
- }
- if (defined(config.traits_public_deps)) {
- public_deps += config.traits_public_deps
- }
- }
- }
- }
-
# Write the typemapping configuration for this target out to a file to be
# validated by a Python script. This helps catch mistakes that can't
# be caught by logic in GN.
@@ -1389,20 +1485,20 @@ template("mojom") {
write_file(_typemap_config_filename, _rebased_typemap_configs, "json")
_mojom_target_name = target_name
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(_typemap_validator_target_name) {
+ action(_typemap_validator_target_name) {
+ allow_remote = true
script = "$mojom_generator_root/validate_typemap_config.py"
inputs = [ _typemap_config_filename ]
outputs = [ _typemap_stamp_filename ]
args = [
get_label_info(_mojom_target_name, "label_no_toolchain"),
- rebase_path(_typemap_config_filename),
- rebase_path(_typemap_stamp_filename),
+ rebase_path(_typemap_config_filename, root_build_dir),
+ rebase_path(_typemap_stamp_filename, root_build_dir),
]
}
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(type_mappings_target_name) {
+ action(type_mappings_target_name) {
+ allow_remote = true
inputs =
mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]
outputs = [ type_mappings_path ]
@@ -1413,6 +1509,7 @@ template("mojom") {
rebase_path(type_mappings_path, root_build_dir),
]
+ sources = []
foreach(d, all_deps) {
name = get_label_info(d, "label_no_toolchain")
toolchain = get_label_info(d, "toolchain")
@@ -1422,12 +1519,11 @@ template("mojom") {
dependency_output_dir =
get_label_info(dependency_output, "target_gen_dir")
dependency_name = get_label_info(dependency_output, "name")
- dependency_path =
- rebase_path("$dependency_output_dir/${dependency_name}",
- root_build_dir)
+ dependency_path = "$dependency_output_dir/${dependency_name}"
+ sources += [ dependency_path ]
args += [
"--dependency",
- dependency_path,
+ rebase_path(dependency_path, root_build_dir),
]
}
@@ -1485,11 +1581,15 @@ template("mojom") {
if (defined(output_name_override)) {
output_name = output_name_override
}
- visibility = output_visibility + [ ":$output_target_name" ]
+ visibility = output_visibility + [
+ ":$output_target_name",
+ ":${target_name}_mojolpm",
+ ]
if (defined(invoker.testonly)) {
testonly = invoker.testonly
}
defines = export_defines
+ configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs += extra_configs
if (output_file_base_paths != []) {
sources = []
@@ -1578,13 +1678,81 @@ template("mojom") {
}
}
+ if (generate_mojolpm_fuzzing && !defined(variant)) {
+ # This block contains the C++ targets for the MojoLPM fuzzer, we need to
+ # do this here so that we can use the typemap configuration for the
+ # empty-variant Mojo target.
+
+ mojolpm_target_name = "${target_name}_mojolpm"
+ mojolpm_generator_target_name = "${target_name}__generator"
+ source_set(mojolpm_target_name) {
+ # There are still a few missing header dependencies between mojo targets
+ # with typemaps and the dependencies of their typemap headers. It would
+ # be good to enable include checking for these in the future though.
+ check_includes = false
+ testonly = true
+ if (defined(invoker.sources)) {
+ # Split the input into generated and non-generated source files. They
+ # need to be processed separately.
+ gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
+ non_gen_sources =
+ filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
+ gen_sources =
+ filter_include(invoker.sources, [ gen_dir_path_wildcard ])
+ sources = process_file_template(
+ non_gen_sources,
+ [
+ "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
+ "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
+ ])
+ sources += process_file_template(
+ gen_sources,
+ [
+ "{{source_dir}}/{{source_file_part}}-mojolpm.cc",
+ "{{source_dir}}/{{source_file_part}}-mojolpm.h",
+ ])
+ deps = [ ":$output_target_name" ]
+ } else {
+ sources = []
+ deps = []
+ }
+
+ public_deps = [
+ ":$generator_shared_target_name",
+
+ # NB: hardcoded dependency on the no-variant variant generator, since
+ # mojolpm only uses the no-variant type.
+ ":$mojolpm_generator_target_name",
+ ":$mojolpm_proto_target_name",
+ "//base",
+ "//mojo/public/tools/fuzzers:mojolpm",
+ ]
+
+ foreach(d, all_deps) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append variant_suffix to
+ # get the cpp dependency name.
+ full_name = get_label_info("$d", "label_no_toolchain")
+ public_deps += [ "${full_name}_mojolpm" ]
+ }
+
+ foreach(config, cpp_typemap_configs) {
+ if (defined(config.traits_deps)) {
+ deps += config.traits_deps
+ }
+ if (defined(config.traits_public_deps)) {
+ public_deps += config.traits_public_deps
+ }
+ }
+ }
+ }
+
if (generate_java && is_android) {
import("//build/config/android/rules.gni")
java_generator_target_name = target_name + "_java__generator"
if (sources_list != []) {
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(java_generator_target_name) {
+ action(java_generator_target_name) {
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
sources = sources_list
@@ -1597,7 +1765,7 @@ template("mojom") {
args = common_generator_args
filelist = []
foreach(source, sources_list) {
- filelist += [ rebase_path("$source", root_build_dir) ]
+ filelist += [ rebase_path(source, root_build_dir) ]
}
foreach(base_path, output_file_base_paths) {
outputs += [ "$root_gen_dir/$base_path.srcjar" ]
@@ -1624,8 +1792,7 @@ template("mojom") {
java_srcjar_target_name = target_name + "_java_sources"
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(java_srcjar_target_name) {
+ action(java_srcjar_target_name) {
script = "//build/android/gyp/zip.py"
inputs = []
if (output_file_base_paths != []) {
@@ -1651,7 +1818,6 @@ template("mojom") {
android_library(java_target_name) {
forward_variables_from(invoker, [ "enable_bytecode_checks" ])
deps = [
- "//base:base_java",
"//mojo/public/java:bindings_java",
"//mojo/public/java:system_java",
"//third_party/androidx:androidx_annotation_annotation_java",
@@ -1673,21 +1839,36 @@ template("mojom") {
}
}
- use_typescript_for_target =
- enable_typescript_bindings && defined(invoker.use_typescript_sources) &&
- invoker.use_typescript_sources
+ if (defined(invoker.generate_webui_js_bindings)) {
+ assert(is_chromeos_ash,
+ "generate_webui_js_bindings can only be used on ChromeOS Ash")
+ assert(invoker.generate_webui_js_bindings,
+ "generate_webui_js_bindings should be set to true or removed")
+ }
+
+ use_typescript_for_target = defined(invoker.webui_module_path) &&
+ !defined(invoker.generate_webui_js_bindings)
- if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {
- not_needed(invoker, [ "use_typescript_sources" ])
+ generate_legacy_js = !use_typescript_for_target ||
+ (defined(invoker.generate_legacy_js_bindings) &&
+ invoker.generate_legacy_js_bindings)
+
+ if (!use_typescript_for_target &&
+ defined(invoker.generate_legacy_js_bindings)) {
+ not_needed(invoker, [ "generate_legacy_js_bindings" ])
}
- if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
- !use_typescript_for_target) {
+ # Targets needed by both TS and JS bindings targets. These are needed
+ # unconditionally for JS bindings targets, and are needed for TS bindings
+ # targets when generate_legacy_js_bindings is true. This option is provided
+ # since the legacy bindings are needed by Blink tests and non-Chromium users,
+ # which are not expected to migrate to modules or TypeScript.
+ if (generate_legacy_js && (generate_js_fuzzing ||
+ !defined(invoker.cpp_only) || !invoker.cpp_only)) {
if (sources_list != []) {
generator_js_target_name = "${target_name}_js__generator"
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_js_target_name) {
+ action(generator_js_target_name) {
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
sources = sources_list
@@ -1702,19 +1883,18 @@ template("mojom") {
args = common_generator_args
filelist = []
foreach(source, sources_list) {
- filelist += [ rebase_path("$source", root_build_dir) ]
+ filelist += [ rebase_path(source, root_build_dir) ]
}
foreach(base_path, output_file_base_paths) {
outputs += [
"$root_gen_dir/$base_path.js",
- "$root_gen_dir/$base_path.externs.js",
"$root_gen_dir/$base_path.m.js",
"$root_gen_dir/$base_path-lite.js",
- "$root_gen_dir/$base_path.html",
"$root_gen_dir/$base_path-lite-for-compile.js",
]
- if (defined(invoker.webui_module_path)) {
+ if (defined(invoker.webui_module_path) &&
+ !use_typescript_for_target) {
outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ]
}
}
@@ -1725,7 +1905,6 @@ template("mojom") {
"--filelist={{response_file_name}}",
"-g",
"javascript",
- "--js_bindings_mode=new",
]
if (defined(invoker.js_generate_struct_deserializers) &&
@@ -1739,7 +1918,7 @@ template("mojom") {
args += message_scrambling_args
}
- if (generate_fuzzing) {
+ if (generate_js_fuzzing) {
args += [ "--generate_fuzzing" ]
}
}
@@ -1783,31 +1962,13 @@ template("mojom") {
data_deps += [ "${full_name}_js_data_deps" ]
}
}
+ }
- js_library_target_name = "${target_name}_js_library"
- if (sources_list != []) {
- js_library(js_library_target_name) {
- extra_public_deps = [ ":$generator_js_target_name" ]
- sources = []
- foreach(base_path, output_file_base_paths) {
- sources += [ "$root_gen_dir/${base_path}-lite.js" ]
- }
- externs_list = [
- "${externs_path}/mojo_core.js",
- "${externs_path}/pending.js",
- ]
-
- deps = []
- foreach(d, all_deps) {
- full_name = get_label_info(d, "label_no_toolchain")
- deps += [ "${full_name}_js_library" ]
- }
- }
- } else {
- group(js_library_target_name) {
- }
- }
-
+ # js_library() closure compiler targets, primarily used on ChromeOS. Only
+ # generate these targets if the mojom target is not C++ only and is not using
+ # TypeScript.
+ if (generate_mojom_closure_libraries &&
+ (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {
js_library_for_compile_target_name = "${target_name}_js_library_for_compile"
if (sources_list != []) {
js_library(js_library_for_compile_target_name) {
@@ -1834,35 +1995,9 @@ template("mojom") {
}
}
- js_modules_target_name = "${target_name}_js_modules"
- if (sources_list != []) {
- js_library(js_modules_target_name) {
- extra_public_deps = [ ":$generator_js_target_name" ]
- sources = []
- foreach(base_path, output_file_base_paths) {
- sources += [ "$root_gen_dir/${base_path}.m.js" ]
- }
- externs_list = [
- "${externs_path}/mojo_core.js",
- "${externs_path}/pending.js",
- ]
- if (defined(invoker.disallow_native_types) &&
- invoker.disallow_native_types) {
- deps = []
- } else {
- deps = [ "//mojo/public/js:bindings_uncompiled" ]
- }
- foreach(d, all_deps) {
- full_name = get_label_info(d, "label_no_toolchain")
- deps += [ "${full_name}_js_modules" ]
- }
- }
- } else {
- group(js_modules_target_name) {
- }
- }
-
- if (defined(invoker.webui_module_path)) {
+ # WebUI specific closure targets, not needed by targets that are generating
+ # TypeScript WebUI bindings or by legacy-only targets.
+ if (defined(invoker.webui_module_path) && !use_typescript_for_target) {
webui_js_target_name = "${target_name}_webui_js"
if (sources_list != []) {
js_library(webui_js_target_name) {
@@ -1890,46 +2025,38 @@ template("mojom") {
group(webui_js_target_name) {
}
}
- }
- }
- if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
- use_typescript_for_target) {
- generator_js_target_names = []
- source_filelist = []
- foreach(source, sources_list) {
- source_filelist += [ rebase_path("$source", root_build_dir) ]
- }
- dependency_types = [
- {
- name = "regular"
- ts_extension = ".ts"
- js_extension = ".js"
- },
- {
- name = "es_modules"
- ts_extension = ".m.ts"
- js_extension = ".m.js"
- },
- ]
+ webui_grdp_target_name = "${target_name}_webui_grdp"
+ out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp"
+ grd_prefix = "${target_name}_webui"
+ generate_grd(webui_grdp_target_name) {
+ grd_prefix = grd_prefix
+ out_grd = out_grd
- foreach(dependency_type, dependency_types) {
- ts_outputs = []
- js_outputs = []
+ deps = [ ":$webui_js_target_name" ]
- foreach(base_path, output_file_base_paths) {
- ts_outputs +=
- [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ]
- js_outputs +=
- [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ]
+ input_files = []
+ foreach(base_path, output_file_base_paths) {
+ input_files += [ "${base_path}-webui.js" ]
+ }
+
+ input_files_base_dir =
+ rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir")
+ }
+ }
+ }
+ if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||
+ !invoker.cpp_only) && use_typescript_for_target) {
+ if (sources_list != []) {
+ source_filelist = []
+ foreach(source, sources_list) {
+ source_filelist += [ rebase_path(source, root_build_dir) ]
}
# Generate Typescript bindings.
- generator_ts_target_name =
- "${target_name}_${dependency_type.name}__ts__generator"
+ generator_ts_target_name = "${target_name}_ts__generator"
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_ts_target_name) {
+ action(generator_ts_target_name) {
script = mojom_generator_script
inputs = mojom_generator_sources + jinja2_sources
sources = sources_list
@@ -1938,7 +2065,10 @@ template("mojom") {
"//mojo/public/tools/bindings:precompile_templates",
]
- outputs = ts_outputs
+ outputs = []
+ foreach(base_path, output_file_base_paths) {
+ outputs += [ "$root_gen_dir/$base_path-webui.ts" ]
+ }
args = common_generator_args
response_file_contents = source_filelist
@@ -1948,97 +2078,20 @@ template("mojom") {
"typescript",
]
- if (dependency_type.name == "es_modules") {
- args += [ "--ts_use_es_modules" ]
- }
-
- # TODO(crbug.com/1007587): Support scramble_message_ids.
- # TODO(crbug.com/1007591): Support generate_fuzzing.
- }
-
- # Create tsconfig.json for the generated Typescript.
- tsconfig_filename =
- "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json"
- tsconfig = {
- }
- tsconfig.compilerOptions = {
- composite = true
- target = "es6"
- module = "es6"
- lib = [
- "es6",
- "esnext.bigint",
- ]
- strict = true
- }
- tsconfig.files = []
- foreach(base_path, output_file_base_paths) {
- tsconfig.files += [ rebase_path(
- "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}",
- target_gen_dir,
- root_gen_dir) ]
- }
- tsconfig.references = []
-
- # Get tsconfigs for deps.
- foreach(d, all_deps) {
- dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir"))
- dep_name = get_label_info(d, "name")
- reference = {
- }
- reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json"
- tsconfig.references += [ reference ]
- }
- write_file(tsconfig_filename, tsconfig, "json")
-
- # Compile previously generated Typescript to Javascript.
- generator_js_target_name =
- "${target_name}_${dependency_type.name}__js__generator"
- generator_js_target_names += [ generator_js_target_name ]
-
- # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
- python2_action(generator_js_target_name) {
- script = "$mojom_generator_root/compile_typescript.py"
- sources = ts_outputs
- outputs = js_outputs
- public_deps = [ ":$generator_ts_target_name" ]
- foreach(d, all_deps) {
- full_name = get_label_info(d, "label_no_toolchain")
- public_deps +=
- [ "${full_name}_${dependency_type.name}__js__generator" ]
+ if (!defined(invoker.scramble_message_ids) ||
+ invoker.scramble_message_ids) {
+ inputs += message_scrambling_inputs
+ args += message_scrambling_args
}
- absolute_tsconfig_path =
- rebase_path(tsconfig_filename, "", target_gen_dir)
- args = [ "--tsconfig_path=$absolute_tsconfig_path" ]
- }
- }
-
- js_target_name = target_name + "_js"
- group(js_target_name) {
- public_deps = []
- if (sources_list != []) {
- foreach(generator_js_target_name, generator_js_target_names) {
- public_deps += [ ":$generator_js_target_name" ]
+ if (defined(invoker.js_generate_struct_deserializers) &&
+ invoker.js_generate_struct_deserializers) {
+ args += [ "--js_generate_struct_deserializers" ]
}
- }
- foreach(d, all_deps) {
- full_name = get_label_info(d, "label_no_toolchain")
- public_deps += [ "${full_name}_js" ]
- }
- }
-
- group(js_data_deps_target_name) {
- data = js_outputs
- deps = []
- foreach(generator_js_target_name, generator_js_target_names) {
- deps += [ ":$generator_js_target_name" ]
- }
- data_deps = []
- foreach(d, all_deps) {
- full_name = get_label_info(d, "label_no_toolchain")
- data_deps += [ "${full_name}_js_data_deps" ]
+ # TODO(crbug.com/1007587): Support scramble_message_ids if above is
+ # insufficient.
+ # TODO(crbug.com/1007591): Support generate_fuzzing.
}
}
}
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
index da9efc71..8c641c2a 100755
--- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {
"typescript": "mojom_ts_generator",
}
+_BUILTIN_CHECKS = {
+ "attributes": "mojom_attributes_check",
+ "definitions": "mojom_definitions_check",
+ "features": "mojom_interface_feature_check",
+ "restrictions": "mojom_restrictions_check",
+}
+
def LoadGenerators(generators_string):
if not generators_string:
- return [] # No generators.
+ return {} # No generators.
generators = {}
for generator_name in [s.strip() for s in generators_string.split(",")]:
@@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
return generators
+def LoadChecks(checks_string):
+ if not checks_string:
+ return {} # No checks.
+
+ checks = {}
+ for check_name in [s.strip() for s in checks_string.split(",")]:
+ check = check_name.lower()
+ if check not in _BUILTIN_CHECKS:
+ print("Unknown check name %s" % check_name)
+ sys.exit(1)
+ check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check])
+ checks[check] = check_module
+ return checks
+
+
def MakeImportStackMessage(imported_filename_stack):
"""Make a (human-readable) message listing a chain of imports. (Returned
string begins with a newline (if nonempty) and does not end with one.)"""
@@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):
zip(imported_filename_stack[1:], imported_filename_stack)]))
-class RelativePath(object):
+class RelativePath:
"""Represents a path relative to the source tree or generated output dir."""
def __init__(self, path, source_root, output_dir):
@@ -142,7 +164,7 @@ def ReadFileContents(filename):
return f.read()
-class MojomProcessor(object):
+class MojomProcessor:
"""Takes parsed mojom modules and generates language bindings from them.
Attributes:
@@ -169,8 +191,8 @@ class MojomProcessor(object):
if 'c++' in self._typemap:
self._typemap['mojolpm'] = self._typemap['c++']
- def _GenerateModule(self, args, remaining_args, generator_modules,
- rel_filename, imported_filename_stack):
+ def _GenerateModule(self, args, remaining_args, check_modules,
+ generator_modules, rel_filename, imported_filename_stack):
# Return the already-generated module.
if rel_filename.path in self._processed_files:
return self._processed_files[rel_filename.path]
@@ -190,12 +212,16 @@ class MojomProcessor(object):
ScrambleMethodOrdinals(module.interfaces, salt)
if self._should_generate(rel_filename.path):
+ # Run checks on module first.
+ for check_module in check_modules.values():
+ checker = check_module.Check(module)
+ checker.CheckModule()
+ # Then run generation.
for language, generator_module in generator_modules.items():
generator = generator_module.Generator(
module, args.output_dir, typemap=self._typemap.get(language, {}),
variant=args.variant, bytecode_path=args.bytecode_path,
for_blink=args.for_blink,
- js_bindings_mode=args.js_bindings_mode,
js_generate_struct_deserializers=\
args.js_generate_struct_deserializers,
export_attribute=args.export_attribute,
@@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
args.import_directories[idx] = RelativePath(tokens[0], args.depth,
args.output_dir)
generator_modules = LoadGenerators(args.generators_string)
+ check_modules = LoadChecks(args.checks_string)
fileutil.EnsureDirectoryExists(args.output_dir)
@@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
for filename in args.filename:
processor._GenerateModule(
- args, remaining_args, generator_modules,
+ args, remaining_args, check_modules, generator_modules,
RelativePath(filename, args.depth, args.output_dir), [])
return 0
@@ -286,6 +313,12 @@ def main():
metavar="GENERATORS",
default="c++,javascript,java,mojolpm",
help="comma-separated list of generators")
+ generate_parser.add_argument("-c",
+ "--checks",
+ dest="checks_string",
+ metavar="CHECKS",
+ default=",".join(_BUILTIN_CHECKS.keys()),
+ help="comma-separated list of checks")
generate_parser.add_argument(
"--gen_dir", dest="gen_directories", action="append", metavar="directory",
default=[], help="add a directory to be searched for the syntax trees.")
@@ -309,11 +342,6 @@ def main():
help="Use WTF types as generated types for mojo "
"string/array/map.")
generate_parser.add_argument(
- "--js_bindings_mode", choices=["new", "old"], default="old",
- help="This option only affects the JavaScript bindings. The value could "
- "be \"new\" to generate new-style lite JS bindings in addition to the "
- "old, or \"old\" to only generate old bindings.")
- generate_parser.add_argument(
"--js_generate_struct_deserializers", action="store_true",
help="Generate javascript deserialize methods for structs in "
"mojom-lite.js file")
@@ -387,4 +415,10 @@ def main():
if __name__ == "__main__":
with crbug_1001171.DumpStateOnLookupError():
- sys.exit(main())
+ ret = main()
+ # Exit without running GC, which can save multiple seconds due to the large
+ # number of object created. But flush is necessary as os._exit doesn't do
+ # that.
+ sys.stdout.flush()
+ sys.stderr.flush()
+ os._exit(ret)
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
index bddbe3f4..761922b6 100644
--- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
+++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage
from mojom_bindings_generator import ScrambleMethodOrdinals
-class FakeIface(object):
+class FakeIface:
def __init__(self):
self.mojom_name = None
self.methods = None
-class FakeMethod(object):
+class FakeMethod:
def __init__(self, explicit_ordinal=None):
self.explicit_ordinal = explicit_ordinal
self.ordinal = explicit_ordinal
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py
deleted file mode 100755
index 15f0e3ba..00000000
--- a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""Downgrades *.mojom files to the old mojo types for remotes and receivers."""
-
-import argparse
-import fnmatch
-import os
-import re
-import shutil
-import sys
-import tempfile
-
-# List of patterns and replacements to match and use against the contents of a
-# mojo file. Each replacement string will be used with Python string's format()
-# function, so the '{}' substring is used to mark where the mojo type should go.
-_MOJO_REPLACEMENTS = {
- r'pending_remote': r'{}',
- r'pending_receiver': r'{}&',
- r'pending_associated_remote': r'associated {}',
- r'pending_associated_receiver': r'associated {}&',
-}
-
-# Pre-compiled regular expression that matches against any of the replacements.
-_REGEXP_PATTERN = re.compile(
- r'|'.join(
- ['{}\s*<\s*(.*?)\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),
- flags=re.DOTALL)
-
-
-def ReplaceFunction(match_object):
- """Returns the right replacement for the string matched against the regexp."""
- for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):
- if match_object.group(0).startswith(match):
- return repl.format(match_object.group(index))
-
-
-def DowngradeFile(path, output_dir=None):
- """Downgrades the mojom file specified by |path| to the old mojo types.
-
- Optionally pass |output_dir| to place the result under a separate output
- directory, preserving the relative path to the file included in |path|.
- """
- # Use a temporary file to dump the new contents after replacing the patterns.
- with open(path) as src_mojo_file:
- with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:
- tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())
- tmp_mojo_file.write(tmp_contents)
-
- # Files should be placed in the desired output directory
- if output_dir:
- output_filepath = os.path.join(output_dir, os.path.basename(path))
- if not os.path.exists(output_dir):
- os.makedirs(output_dir)
- else:
- output_filepath = path
-
- # Write the new contents preserving the original file's attributes.
- shutil.copystat(path, tmp_mojo_file.name)
- shutil.move(tmp_mojo_file.name, output_filepath)
-
- # Make sure to "touch" the new file so that access, modify and change times
- # are always newer than the source file's, otherwise Modify time will be kept
- # as per the call to shutil.copystat(), causing unnecessary generations of the
- # output file in subsequent builds due to ninja considering it dirty.
- os.utime(output_filepath, None)
-
-
-def DowngradeDirectory(path, output_dir=None):
- """Downgrades mojom files inside directory |path| to the old mojo types.
-
- Optionally pass |output_dir| to place the result under a separate output
- directory, preserving the relative path to the file included in |path|.
- """
- # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7
- mojom_filepaths = []
- for dir_path, _, filenames in os.walk(path):
- for filename in fnmatch.filter(filenames, "*mojom"):
- mojom_filepaths.append(os.path.join(dir_path, filename))
-
- for path in mojom_filepaths:
- absolute_dirpath = os.path.dirname(os.path.abspath(path))
- if output_dir:
- dest_dirpath = output_dir + absolute_dirpath
- else:
- dest_dirpath = absolute_dirpath
- DowngradeFile(path, dest_dirpath)
-
-
-def DowngradePath(src_path, output_dir=None):
- """Downgrades the mojom files pointed by |src_path| to the old mojo types.
-
- Optionally pass |output_dir| to place the result under a separate output
- directory, preserving the relative path to the file included in |path|.
- """
- if os.path.isdir(src_path):
- DowngradeDirectory(src_path, output_dir)
- elif os.path.isfile(src_path):
- DowngradeFile(src_path, output_dir)
- else:
- print(">>> {} not pointing to a valid file or directory".format(src_path))
- sys.exit(1)
-
-
-def main():
- parser = argparse.ArgumentParser(
- description="Downgrade *.mojom files to use the old mojo types.")
- parser.add_argument(
- "srcpath", help="path to the file or directory to apply the conversion")
- parser.add_argument(
- "--outdir", help="the directory to place the converted file(s) under")
- args = parser.parse_args()
-
- DowngradePath(args.srcpath, args.outdir)
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
index f1783d59..6bb7a209 100755
--- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
+++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
])
_SUPPORTED_TYPE_KEYS = set([
'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
- 'move_only', 'nullable_is_same_type'
+ 'move_only', 'nullable_is_same_type', 'forward_declaration',
+ 'default_constructible'
])
with open(config_filename, 'r') as f:
for config in json.load(f):
diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
new file mode 100644
index 00000000..eafb95a1
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+group("tests") {
+ data = [
+ "check_stable_mojom_compatibility_unittest.py",
+ "check_stable_mojom_compatibility.py",
+ "const_unittest.py",
+ "enum_unittest.py",
+ "feature_unittest.py",
+ "mojom_parser_test_case.py",
+ "mojom_parser_unittest.py",
+ "mojom_parser.py",
+ "stable_attribute_unittest.py",
+ "version_compatibility_unittest.py",
+ ]
+}
diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
index 08bd672f..35cd1cfd 100755
--- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
+++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Verifies backward-compatibility of mojom type changes.
@@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making
breaking changes to stable mojoms."""
import argparse
-import errno
import io
import json
import os
import os.path
-import shutil
-import six
import sys
-import tempfile
from mojom.generate import module
from mojom.generate import translate
from mojom.parse import parser
+# pylint: disable=raise-missing-from
+
class ParseError(Exception):
pass
@@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
transitive closure of a mojom's input dependencies all at once.
"""
+ translate.is_running_backwards_compatibility_check_hack = True
+
# First build a map of all files covered by the delta
affected_files = set()
old_files = {}
@@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):
try:
ast = parser.Parse(contents, mojom)
except Exception as e:
- six.reraise(
- ParseError,
- 'encountered exception {0} while parsing {1}'.format(e, mojom),
- sys.exc_info()[2])
+ raise ParseError('encountered exception {0} while parsing {1}'.format(
+ e, mojom))
+
+ # Files which are generated at compile time can't be checked by this script
+ # (at the moment) since they may not exist in the output directory.
+ generated_files_to_skip = {
+ ('third_party/blink/public/mojom/runtime_feature_state/'
+ 'runtime_feature.mojom'),
+ ('third_party/blink/public/mojom/origin_trial_feature/'
+ 'origin_trial_feature.mojom'),
+ }
+
+ ast.import_list.items = [
+ x for x in ast.import_list.items
+ if x.import_filename not in generated_files_to_skip
+ ]
+
for imp in ast.import_list:
+ if (not file_overrides.get(imp.import_filename)
+ and not os.path.exists(os.path.join(root, imp.import_filename))):
+ # Speculatively construct a path prefix to locate the import_filename
+ mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)
+ test_prefix = ''
+ for path_component in mojom_path:
+ test_prefix = os.path.join(test_prefix, path_component)
+ test_import_filename = os.path.join(test_prefix, imp.import_filename)
+ if os.path.exists(os.path.join(root, test_import_filename)):
+ imp.import_filename = test_import_filename
+ break
parseMojom(imp.import_filename, file_overrides, override_modules)
# Now that the transitive set of dependencies has been imported and parsed
@@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):
modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
old_modules = {}
- for mojom in old_files.keys():
+ for mojom in old_files:
parseMojom(mojom, old_files, old_modules)
new_modules = {}
- for mojom in new_files.keys():
+ for mojom in new_files:
parseMojom(mojom, new_files, new_modules)
# At this point we have a complete set of translated Modules from both the
@@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):
'can be deleted by a subsequent change.' % qualified_name)
checker = module.BackwardCompatibilityChecker()
- if not checker.IsBackwardCompatible(new_types[new_name], kind):
- raise Exception('Stable type %s appears to have changed in a way which '
- 'breaks backward-compatibility. Please fix!\n\nIf you '
- 'believe this assessment to be incorrect, please file a '
- 'Chromium bug against the "Internals>Mojo>Bindings" '
- 'component.' % qualified_name)
+ try:
+ if not checker.IsBackwardCompatible(new_types[new_name], kind):
+ raise Exception(
+ 'Stable type %s appears to have changed in a way which '
+ 'breaks backward-compatibility. Please fix!\n\nIf you '
+ 'believe this assessment to be incorrect, please file a '
+ 'Chromium bug against the "Internals>Mojo>Bindings" '
+ 'component.' % qualified_name)
+ except Exception as e:
+ raise Exception(
+ 'Stable type %s appears to have changed in a way which '
+ 'breaks backward-compatibility: \n\n%s.\nPlease fix!\n\nIf you '
+ 'believe this assessment to be incorrect, please file a '
+ 'Chromium bug against the "Internals>Mojo>Bindings" '
+ 'component.' % (qualified_name, e))
def Run(command_line, delta=None):
diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
index 9f51ea77..06769c95 100755
--- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
from mojom.generate import module
-class Change(object):
+class Change:
"""Helper to clearly define a mojom file delta to be analyzed."""
def __init__(self, filename, old=None, new=None):
@@ -28,7 +28,7 @@ class Change(object):
class UnchangedFile(Change):
def __init__(self, filename, contents):
- super(UnchangedFile, self).__init__(filename, old=contents, new=contents)
+ super().__init__(filename, old=contents, new=contents)
class CheckStableMojomCompatibilityTest(unittest.TestCase):
@@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):
[Stable] struct T { foo.S s; int32 x; };
""")
])
+
+ def testWithPartialImport(self):
+ """The compatibility checking tool correctly parses imports with partial
+ paths."""
+ self.assertBackwardCompatible([
+ UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+ Change('foo/bar.mojom',
+ old="""\
+ module bar;
+ import "foo/foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """,
+ new="""\
+ module bar;
+ import "foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """)
+ ])
+
+ self.assertBackwardCompatible([
+ UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+ Change('foo/bar.mojom',
+ old="""\
+ module bar;
+ import "foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """,
+ new="""\
+ module bar;
+ import "foo/foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """)
+ ])
+
+ self.assertNotBackwardCompatible([
+ UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+ Change('bar/bar.mojom',
+ old="""\
+ module bar;
+ import "foo/foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """,
+ new="""\
+ module bar;
+ import "foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """)
+ ])
+
+ self.assertNotBackwardCompatible([
+ UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+ Change('bar/bar.mojom',
+ old="""\
+ module bar;
+ import "foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """,
+ new="""\
+ module bar;
+ import "foo/foo.mojom";
+ [Stable] struct T { foo.S s; };
+ """)
+ ])
+
+ def testNewEnumDefault(self):
+ # Should be backwards compatible since it does not affect the wire format.
+ # This specific case also checks that the backwards compatibility checker
+ # does not throw an error due to the older version of the enum not
+ # specifying [Default].
+ self.assertBackwardCompatible([
+ Change('foo/foo.mojom',
+ old='[Extensible] enum E { One };',
+ new='[Extensible] enum E { [Default] One };')
+ ])
+ self.assertBackwardCompatible([
+ Change('foo/foo.mojom',
+ old='[Extensible] enum E { [Default] One, Two, };',
+ new='[Extensible] enum E { One, [Default] Two, };')
+ ])
diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
index cb42dfac..e8ed36a7 100644
--- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
index d9005078..9269cde5 100644
--- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
self.assertEqual('F', b.enums[0].mojom_name)
self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
self.assertEqual(37, b.enums[0].fields[0].numeric_value)
+
+ def testEnumAttributesAreEnums(self):
+ """Verifies that enum values in attributes are really enum types."""
+ a_mojom = 'a.mojom'
+ self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
+ b_mojom = 'b.mojom'
+ self.WriteFile(
+ b_mojom, 'module b;'
+ 'import "a.mojom";'
+ '[MooCow=a.E.kFoo]'
+ 'interface Foo { Foo(); };')
+ self.ParseMojoms([a_mojom, b_mojom])
+ b = self.LoadModule(b_mojom)
+ self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')
+
+ def testConstantAttributes(self):
+ """Verifies that constants as attributes are translated to the constant."""
+ a_mojom = 'a.mojom'
+ self.WriteFile(
+ a_mojom, 'module a;'
+ 'enum E { kFoo, kBar };'
+ 'const E kB = E.kFoo;'
+ '[Attr=kB] interface Hello { Foo(); };')
+ self.ParseMojoms([a_mojom])
+ a = self.LoadModule(a_mojom)
+ self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')
+ self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
+ 'kFoo')
diff --git a/utils/ipc/mojo/public/tools/mojom/feature_unittest.py b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py
new file mode 100644
index 00000000..5f014e1c
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py
@@ -0,0 +1,84 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class FeatureTest(MojomParserTestCase):
+ """Tests feature parsing behavior."""
+ def testFeatureOff(self):
+ """Verifies basic parsing of feature types."""
+ types = self.ExtractTypes("""
+ // e.g. BASE_DECLARE_FEATURE(kFeature);
+ [AttributeOne=ValueOne]
+ feature kFeature {
+ // BASE_FEATURE(kFeature,"MyFeature",
+ // base::FEATURE_DISABLED_BY_DEFAULT);
+ const string name = "MyFeature";
+ const bool default_state = false;
+ };
+ """)
+ self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
+ self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
+ self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
+ self.assertEqual('false', types['kFeature'].constants[1].value)
+
+ def testFeatureOn(self):
+ """Verifies basic parsing of feature types."""
+ types = self.ExtractTypes("""
+ // e.g. BASE_DECLARE_FEATURE(kFeature);
+ feature kFeature {
+ // BASE_FEATURE(kFeature,"MyFeature",
+ // base::FEATURE_ENABLED_BY_DEFAULT);
+ const string name = "MyFeature";
+ const bool default_state = true;
+ };
+ """)
+ self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
+ self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
+ self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
+ self.assertEqual('true', types['kFeature'].constants[1].value)
+
+ def testFeatureWeakKeyword(self):
+ """Verifies that `feature` is a weak keyword."""
+ types = self.ExtractTypes("""
+ // e.g. BASE_DECLARE_FEATURE(kFeature);
+ [AttributeOne=ValueOne]
+ feature kFeature {
+ // BASE_FEATURE(kFeature,"MyFeature",
+ // base::FEATURE_DISABLED_BY_DEFAULT);
+ const string name = "MyFeature";
+ const bool default_state = false;
+ };
+ struct MyStruct {
+ bool feature = true;
+ };
+ interface InterfaceName {
+ Method(string feature) => (int32 feature);
+ };
+ """)
+ self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
+ self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
+ self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
+ self.assertEqual('false', types['kFeature'].constants[1].value)
+
+ def testFeatureAttributesAreFeatures(self):
+ """Verifies that feature values in attributes are really feature types."""
+ a_mojom = 'a.mojom'
+ self.WriteFile(
+ a_mojom, 'module a;'
+ 'feature F { const string name = "f";'
+ 'const bool default_state = false; };')
+ b_mojom = 'b.mojom'
+ self.WriteFile(
+ b_mojom, 'module b;'
+ 'import "a.mojom";'
+ 'feature G'
+ '{const string name = "g"; const bool default_state = false;};'
+ '[Attri=a.F] interface Foo { Foo(); };'
+ '[Boink=G] interface Bar {};')
+ self.ParseMojoms([a_mojom, b_mojom])
+ b = self.LoadModule(b_mojom)
+ self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F')
+ self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G')
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
index 51facc0c..a0edf0eb 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -8,6 +8,7 @@ group("mojom") {
"error.py",
"fileutil.py",
"generate/__init__.py",
+ "generate/check.py",
"generate/generator.py",
"generate/module.py",
"generate/pack.py",
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
index 8a1e03da..dd53b835 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
@@ -1,4 +1,4 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
index bf626f54..124f12c1 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
@@ -1,9 +1,8 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import errno
-import imp
import os.path
import sys
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
index ff5753a2..c93d2289 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
@@ -1,20 +1,17 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
import os.path
import shutil
-import sys
import tempfile
import unittest
from mojom import fileutil
-
class FileUtilTest(unittest.TestCase):
def testEnsureDirectoryExists(self):
- """Test that EnsureDirectoryExists fuctions correctly."""
+ """Test that EnsureDirectoryExists functions correctly."""
temp_dir = tempfile.mkdtemp()
try:
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
new file mode 100644
index 00000000..1efe2022
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
@@ -0,0 +1,26 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Code shared by the various pre-generation mojom checkers."""
+
+
+class CheckException(Exception):
+ def __init__(self, module, message):
+ self.module = module
+ self.message = message
+ super().__init__(self.message)
+
+ def __str__(self):
+ return "Failed mojo pre-generation check for {}:\n{}".format(
+ self.module.path, self.message)
+
+
+class Check:
+ def __init__(self, module):
+ self.module = module
+
+ def CheckModule(self):
+ """ Subclass should return True if its Checks pass, and throw an
+ exception otherwise. CheckModule will be called immediately before
+ mojom.generate.Generator.GenerateFiles()"""
+ raise NotImplementedError("Subclasses must override/implement this method")
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py
deleted file mode 100644
index 0dfd996e..00000000
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""Resolves the values used for constants and enums."""
-
-from itertools import ifilter
-
-from mojom.generate import module as mojom
-
-
-def ResolveConstants(module, expression_to_text):
- in_progress = set()
- computed = set()
-
- def GetResolvedValue(named_value):
- assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))
- if isinstance(named_value, mojom.EnumValue):
- field = next(
- ifilter(lambda field: field.name == named_value.name,
- named_value.enum.fields), None)
- if not field:
- raise RuntimeError(
- 'Unable to get computed value for field %s of enum %s' %
- (named_value.name, named_value.enum.name))
- if field not in computed:
- ResolveEnum(named_value.enum)
- return field.resolved_value
- else:
- ResolveConstant(named_value.constant)
- named_value.resolved_value = named_value.constant.resolved_value
- return named_value.resolved_value
-
- def ResolveConstant(constant):
- if constant in computed:
- return
- if constant in in_progress:
- raise RuntimeError('Circular dependency for constant: %s' % constant.name)
- in_progress.add(constant)
- if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
- resolved_value = GetResolvedValue(constant.value)
- else:
- resolved_value = expression_to_text(constant.value)
- constant.resolved_value = resolved_value
- in_progress.remove(constant)
- computed.add(constant)
-
- def ResolveEnum(enum):
- def ResolveEnumField(enum, field, default_value):
- if field in computed:
- return
- if field in in_progress:
- raise RuntimeError('Circular dependency for enum: %s' % enum.name)
- in_progress.add(field)
- if field.value:
- if isinstance(field.value, mojom.EnumValue):
- resolved_value = GetResolvedValue(field.value)
- elif isinstance(field.value, str):
- resolved_value = int(field.value, 0)
- else:
- raise RuntimeError('Unexpected value: %s' % field.value)
- else:
- resolved_value = default_value
- field.resolved_value = resolved_value
- in_progress.remove(field)
- computed.add(field)
-
- current_value = 0
- for field in enum.fields:
- ResolveEnumField(enum, field, current_value)
- current_value = field.resolved_value + 1
-
- for constant in module.constants:
- ResolveConstant(constant)
-
- for enum in module.enums:
- ResolveEnum(enum)
-
- for struct in module.structs:
- for constant in struct.constants:
- ResolveConstant(constant)
- for enum in struct.enums:
- ResolveEnum(enum)
- for field in struct.fields:
- if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
- field.default.resolved_value = GetResolvedValue(field.default)
-
- for interface in module.interfaces:
- for constant in interface.constants:
- ResolveConstant(constant)
- for enum in interface.enums:
- ResolveEnum(enum)
-
- return module
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
index 4a1c73fc..96fe3a2d 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
@@ -1,4 +1,4 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Code shared by the various language-specific code generators."""
@@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
return _ToSnakeCase(identifier, upper=False)
-class Stylizer(object):
+class Stylizer:
"""Stylizers specify naming rules to map mojom names to names in generated
code. For example, if you would like method_name in mojom to be mapped to
MethodName in the generated code, you need to define a subclass of Stylizer
@@ -130,6 +130,9 @@ class Stylizer(object):
def StylizeEnum(self, mojom_name):
return mojom_name
+ def StylizeFeature(self, mojom_name):
+ return mojom_name
+
def StylizeModule(self, mojom_namespace):
return mojom_namespace
@@ -233,7 +236,7 @@ def AddComputedData(module):
_AddInterfaceComputedData(interface)
-class Generator(object):
+class Generator:
# Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
# files to stdout.
def __init__(self,
@@ -243,7 +246,6 @@ class Generator(object):
variant=None,
bytecode_path=None,
for_blink=False,
- js_bindings_mode="new",
js_generate_struct_deserializers=False,
export_attribute=None,
export_header=None,
@@ -262,7 +264,6 @@ class Generator(object):
self.variant = variant
self.bytecode_path = bytecode_path
self.for_blink = for_blink
- self.js_bindings_mode = js_bindings_mode
self.js_generate_struct_deserializers = js_generate_struct_deserializers
self.export_attribute = export_attribute
self.export_header = export_header
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
index 32c884a8..7143e07c 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
@@ -1,13 +1,12 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
+import importlib.util
import os.path
import sys
import unittest
-
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
try:
- imp.find_module("mojom")
+ importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
from mojom.generate import generator
-
class StringManipulationTest(unittest.TestCase):
"""generator contains some string utilities, this tests only those."""
@@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
self.assertEquals("SNAKE_D3D11_CASE",
generator.ToUpperSnakeCase("snakeD3d11Case"))
-
if __name__ == "__main__":
unittest.main()
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
index 9bdb28e0..ca71059d 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
@@ -1,4 +1,4 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -12,15 +12,14 @@
# method = interface.AddMethod('Tat', 0)
# method.AddParameter('baz', 0, mojom.INT32)
-import sys
-if sys.version_info.major == 2:
- import cPickle as pickle
-else:
- import pickle
+import pickle
+from collections import OrderedDict
from uuid import UUID
+# pylint: disable=raise-missing-from
-class BackwardCompatibilityChecker(object):
+
+class BackwardCompatibilityChecker:
"""Used for memoization while recursively checking two type definitions for
backward-compatibility."""
@@ -64,23 +63,20 @@ def Repr(obj, as_ref=True):
return obj.Repr(as_ref=as_ref)
# Since we cannot implement Repr for existing container types, we
# handle them here.
- elif isinstance(obj, list):
+ if isinstance(obj, list):
if not obj:
return '[]'
- else:
- return ('[\n%s\n]' % (',\n'.join(
- ' %s' % Repr(elem, as_ref).replace('\n', '\n ')
- for elem in obj)))
- elif isinstance(obj, dict):
+ return ('[\n%s\n]' %
+ (',\n'.join(' %s' % Repr(elem, as_ref).replace('\n', '\n ')
+ for elem in obj)))
+ if isinstance(obj, dict):
if not obj:
return '{}'
- else:
- return ('{\n%s\n}' % (',\n'.join(
- ' %s: %s' % (Repr(key, as_ref).replace('\n', '\n '),
- Repr(val, as_ref).replace('\n', '\n '))
- for key, val in obj.items())))
- else:
- return repr(obj)
+ return ('{\n%s\n}' % (',\n'.join(' %s: %s' %
+ (Repr(key, as_ref).replace('\n', '\n '),
+ Repr(val, as_ref).replace('\n', '\n '))
+ for key, val in obj.items())))
+ return repr(obj)
def GenericRepr(obj, names):
@@ -104,7 +100,7 @@ def GenericRepr(obj, names):
ReprIndent(name, as_ref) for (name, as_ref) in names.items()))
-class Kind(object):
+class Kind:
"""Kind represents a type (e.g. int8, string).
Attributes:
@@ -112,16 +108,43 @@ class Kind(object):
module: {Module} The defining module. Set to None for built-in types.
parent_kind: The enclosing type. For example, an enum defined
inside an interface has that interface as its parent. May be None.
+ is_nullable: True if the type is nullable.
"""
- def __init__(self, spec=None, module=None):
+ def __init__(self, spec=None, is_nullable=False, module=None):
self.spec = spec
self.module = module
self.parent_kind = None
+ self.is_nullable = is_nullable
+ self.shared_definition = {}
+
+ @classmethod
+ def AddSharedProperty(cls, name):
+ """Adds a property |name| to |cls|, which accesses the corresponding item in
+ |shared_definition|.
+
+ The reason of adding such indirection is to enable sharing definition
+ between a reference kind and its nullable variation. For example:
+ a = Struct('test_struct_1')
+ b = a.MakeNullableKind()
+ a.name = 'test_struct_2'
+ print(b.name) # Outputs 'test_struct_2'.
+ """
+ def Get(self):
+ try:
+ return self.shared_definition[name]
+ except KeyError: # Must raise AttributeError if property doesn't exist.
+ raise AttributeError
+
+ def Set(self, value):
+ self.shared_definition[name] = value
+
+ setattr(cls, name, property(Get, Set))
def Repr(self, as_ref=True):
# pylint: disable=unused-argument
- return '<%s spec=%r>' % (self.__class__.__name__, self.spec)
+ return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
+ self.is_nullable)
def __repr__(self):
# Gives us a decent __repr__ for all kinds.
@@ -130,7 +153,8 @@ class Kind(object):
def __eq__(self, rhs):
# pylint: disable=unidiomatic-typecheck
return (type(self) == type(rhs)
- and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))
+ and (self.spec, self.parent_kind, self.is_nullable)
+ == (rhs.spec, rhs.parent_kind, rhs.is_nullable))
def __hash__(self):
# TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind
@@ -138,32 +162,113 @@ class Kind(object):
# some primitive Kinds as dict keys. The default hash (object identity)
# breaks these dicts when a pickled Module instance is unpickled and used
# during a subsequent run of the parser.
- return hash((self.spec, self.parent_kind))
+ return hash((self.spec, self.parent_kind, self.is_nullable))
# pylint: disable=unused-argument
def IsBackwardCompatible(self, rhs, checker):
return self == rhs
+class ValueKind(Kind):
+ """ValueKind represents values that aren't reference kinds.
+
+ The primary difference is the wire representation for nullable value kinds
+ still reserves space for the value type itself, even if that value itself
+ is logically null.
+ """
+ def __init__(self, spec=None, is_nullable=False, module=None):
+ assert spec is None or is_nullable == spec.startswith('?')
+ Kind.__init__(self, spec, is_nullable, module)
+
+ def MakeNullableKind(self):
+ assert not self.is_nullable
+
+ if self == BOOL:
+ return NULLABLE_BOOL
+ if self == INT8:
+ return NULLABLE_INT8
+ if self == INT16:
+ return NULLABLE_INT16
+ if self == INT32:
+ return NULLABLE_INT32
+ if self == INT64:
+ return NULLABLE_INT64
+ if self == UINT8:
+ return NULLABLE_UINT8
+ if self == UINT16:
+ return NULLABLE_UINT16
+ if self == UINT32:
+ return NULLABLE_UINT32
+ if self == UINT64:
+ return NULLABLE_UINT64
+ if self == FLOAT:
+ return NULLABLE_FLOAT
+ if self == DOUBLE:
+ return NULLABLE_DOUBLE
+
+ nullable_kind = type(self)()
+ nullable_kind.shared_definition = self.shared_definition
+ if self.spec is not None:
+ nullable_kind.spec = '?' + self.spec
+ nullable_kind.is_nullable = True
+ nullable_kind.parent_kind = self.parent_kind
+ nullable_kind.module = self.module
+
+ return nullable_kind
+
+ def MakeUnnullableKind(self):
+ assert self.is_nullable
+
+ if self == NULLABLE_BOOL:
+ return BOOL
+ if self == NULLABLE_INT8:
+ return INT8
+ if self == NULLABLE_INT16:
+ return INT16
+ if self == NULLABLE_INT32:
+ return INT32
+ if self == NULLABLE_INT64:
+ return INT64
+ if self == NULLABLE_UINT8:
+ return UINT8
+ if self == NULLABLE_UINT16:
+ return UINT16
+ if self == NULLABLE_UINT32:
+ return UINT32
+ if self == NULLABLE_UINT64:
+ return UINT64
+ if self == NULLABLE_FLOAT:
+ return FLOAT
+ if self == NULLABLE_DOUBLE:
+ return DOUBLE
+
+ nullable_kind = type(self)()
+ nullable_kind.shared_definition = self.shared_definition
+ if self.spec is not None:
+ nullable_kind.spec = self.spec[1:]
+ nullable_kind.is_nullable = False
+ nullable_kind.parent_kind = self.parent_kind
+ nullable_kind.module = self.module
+
+ return nullable_kind
+
+ def __eq__(self, rhs):
+ return (isinstance(rhs, ValueKind) and super().__eq__(rhs))
+
+ def __hash__(self): # pylint: disable=useless-super-delegation
+ return super().__hash__()
+
+
class ReferenceKind(Kind):
"""ReferenceKind represents pointer and handle types.
A type is nullable if null (for pointer types) or invalid handle (for handle
types) is a legal value for the type.
-
- Attributes:
- is_nullable: True if the type is nullable.
"""
def __init__(self, spec=None, is_nullable=False, module=None):
assert spec is None or is_nullable == spec.startswith('?')
- Kind.__init__(self, spec, module)
- self.is_nullable = is_nullable
- self.shared_definition = {}
-
- def Repr(self, as_ref=True):
- return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
- self.is_nullable)
+ Kind.__init__(self, spec, is_nullable, module)
def MakeNullableKind(self):
assert not self.is_nullable
@@ -193,55 +298,65 @@ class ReferenceKind(Kind):
return nullable_kind
- @classmethod
- def AddSharedProperty(cls, name):
- """Adds a property |name| to |cls|, which accesses the corresponding item in
- |shared_definition|.
-
- The reason of adding such indirection is to enable sharing definition
- between a reference kind and its nullable variation. For example:
- a = Struct('test_struct_1')
- b = a.MakeNullableKind()
- a.name = 'test_struct_2'
- print(b.name) # Outputs 'test_struct_2'.
- """
-
- def Get(self):
- try:
- return self.shared_definition[name]
- except KeyError: # Must raise AttributeError if property doesn't exist.
- raise AttributeError
-
- def Set(self, value):
- self.shared_definition[name] = value
+ def MakeUnnullableKind(self):
+ assert self.is_nullable
+
+ if self == NULLABLE_STRING:
+ return STRING
+ if self == NULLABLE_HANDLE:
+ return HANDLE
+ if self == NULLABLE_DCPIPE:
+ return DCPIPE
+ if self == NULLABLE_DPPIPE:
+ return DPPIPE
+ if self == NULLABLE_MSGPIPE:
+ return MSGPIPE
+ if self == NULLABLE_SHAREDBUFFER:
+ return SHAREDBUFFER
+ if self == NULLABLE_PLATFORMHANDLE:
+ return PLATFORMHANDLE
+
+ unnullable_kind = type(self)()
+ unnullable_kind.shared_definition = self.shared_definition
+ if self.spec is not None:
+ assert self.spec[0] == '?'
+ unnullable_kind.spec = self.spec[1:]
+ unnullable_kind.is_nullable = False
+ unnullable_kind.parent_kind = self.parent_kind
+ unnullable_kind.module = self.module
- setattr(cls, name, property(Get, Set))
+ return unnullable_kind
def __eq__(self, rhs):
- return (isinstance(rhs, ReferenceKind)
- and super(ReferenceKind, self).__eq__(rhs)
- and self.is_nullable == rhs.is_nullable)
+ return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))
- def __hash__(self):
- return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))
-
- def IsBackwardCompatible(self, rhs, checker):
- return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker)
- and self.is_nullable == rhs.is_nullable)
+ def __hash__(self): # pylint: disable=useless-super-delegation
+ return super().__hash__()
# Initialize the set of primitive types. These can be accessed by clients.
-BOOL = Kind('b')
-INT8 = Kind('i8')
-INT16 = Kind('i16')
-INT32 = Kind('i32')
-INT64 = Kind('i64')
-UINT8 = Kind('u8')
-UINT16 = Kind('u16')
-UINT32 = Kind('u32')
-UINT64 = Kind('u64')
-FLOAT = Kind('f')
-DOUBLE = Kind('d')
+BOOL = ValueKind('b')
+INT8 = ValueKind('i8')
+INT16 = ValueKind('i16')
+INT32 = ValueKind('i32')
+INT64 = ValueKind('i64')
+UINT8 = ValueKind('u8')
+UINT16 = ValueKind('u16')
+UINT32 = ValueKind('u32')
+UINT64 = ValueKind('u64')
+FLOAT = ValueKind('f')
+DOUBLE = ValueKind('d')
+NULLABLE_BOOL = ValueKind('?b', True)
+NULLABLE_INT8 = ValueKind('?i8', True)
+NULLABLE_INT16 = ValueKind('?i16', True)
+NULLABLE_INT32 = ValueKind('?i32', True)
+NULLABLE_INT64 = ValueKind('?i64', True)
+NULLABLE_UINT8 = ValueKind('?u8', True)
+NULLABLE_UINT16 = ValueKind('?u16', True)
+NULLABLE_UINT32 = ValueKind('?u32', True)
+NULLABLE_UINT64 = ValueKind('?u64', True)
+NULLABLE_FLOAT = ValueKind('?f', True)
+NULLABLE_DOUBLE = ValueKind('?d', True)
STRING = ReferenceKind('s')
HANDLE = ReferenceKind('h')
DCPIPE = ReferenceKind('h:d:c')
@@ -270,6 +385,17 @@ PRIMITIVES = (
UINT64,
FLOAT,
DOUBLE,
+ NULLABLE_BOOL,
+ NULLABLE_INT8,
+ NULLABLE_INT16,
+ NULLABLE_INT32,
+ NULLABLE_INT64,
+ NULLABLE_UINT8,
+ NULLABLE_UINT16,
+ NULLABLE_UINT32,
+ NULLABLE_UINT64,
+ NULLABLE_FLOAT,
+ NULLABLE_DOUBLE,
STRING,
HANDLE,
DCPIPE,
@@ -291,12 +417,17 @@ ATTRIBUTE_DEFAULT = 'Default'
ATTRIBUTE_EXTENSIBLE = 'Extensible'
ATTRIBUTE_NO_INTERRUPT = 'NoInterrupt'
ATTRIBUTE_STABLE = 'Stable'
+ATTRIBUTE_SUPPORTS_URGENT = 'SupportsUrgent'
ATTRIBUTE_SYNC = 'Sync'
ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'
ATTRIBUTE_UUID = 'Uuid'
+ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'
+ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'
+ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'
+ATTRIBUTE_RUNTIME_FEATURE = 'RuntimeFeature'
-class NamedValue(object):
+class NamedValue:
def __init__(self, module, parent_kind, mojom_name):
self.module = module
self.parent_kind = parent_kind
@@ -316,7 +447,7 @@ class NamedValue(object):
return hash((self.parent_kind, self.mojom_name))
-class BuiltinValue(object):
+class BuiltinValue:
def __init__(self, value):
self.value = value
@@ -350,7 +481,7 @@ class EnumValue(NamedValue):
return self.field.name
-class Constant(object):
+class Constant:
def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):
self.mojom_name = mojom_name
self.name = None
@@ -368,7 +499,7 @@ class Constant(object):
rhs.parent_kind))
-class Field(object):
+class Field:
def __init__(self,
mojom_name=None,
kind=None,
@@ -414,7 +545,18 @@ class StructField(Field):
class UnionField(Field):
- pass
+ def __init__(self,
+ mojom_name=None,
+ kind=None,
+ ordinal=None,
+ default=None,
+ attributes=None):
+ Field.__init__(self, mojom_name, kind, ordinal, default, attributes)
+
+ @property
+ def is_default(self):
+ return self.attributes.get(ATTRIBUTE_DEFAULT, False) \
+ if self.attributes else False
def _IsFieldBackwardCompatible(new_field, old_field, checker):
@@ -424,6 +566,38 @@ def _IsFieldBackwardCompatible(new_field, old_field, checker):
return checker.IsBackwardCompatible(new_field.kind, old_field.kind)
+class Feature(ReferenceKind):
+ """A runtime enabled feature defined from mojom.
+
+ Attributes:
+ mojom_name: {str} The name of the feature type as defined in mojom.
+ name: {str} The stylized name. (Note: not the "name" used by FeatureList.)
+ constants: {List[Constant]} The constants defined in the feature scope.
+ attributes: {dict} Additional information about the feature.
+ """
+
+ Kind.AddSharedProperty('mojom_name')
+ Kind.AddSharedProperty('name')
+ Kind.AddSharedProperty('constants')
+ Kind.AddSharedProperty('attributes')
+
+ def __init__(self, mojom_name=None, module=None, attributes=None):
+ if mojom_name is not None:
+ spec = 'x:' + mojom_name
+ else:
+ spec = None
+ ReferenceKind.__init__(self, spec, False, module)
+ self.mojom_name = mojom_name
+ self.name = None
+ self.constants = []
+ self.attributes = attributes
+
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeFeature(self.mojom_name)
+ for constant in self.constants:
+ constant.Stylize(stylizer)
+
+
class Struct(ReferenceKind):
"""A struct with typed fields.
@@ -441,14 +615,14 @@ class Struct(ReferenceKind):
if it's a native struct.
"""
- ReferenceKind.AddSharedProperty('mojom_name')
- ReferenceKind.AddSharedProperty('name')
- ReferenceKind.AddSharedProperty('native_only')
- ReferenceKind.AddSharedProperty('custom_serializer')
- ReferenceKind.AddSharedProperty('fields')
- ReferenceKind.AddSharedProperty('enums')
- ReferenceKind.AddSharedProperty('constants')
- ReferenceKind.AddSharedProperty('attributes')
+ Kind.AddSharedProperty('mojom_name')
+ Kind.AddSharedProperty('name')
+ Kind.AddSharedProperty('native_only')
+ Kind.AddSharedProperty('custom_serializer')
+ Kind.AddSharedProperty('fields')
+ Kind.AddSharedProperty('enums')
+ Kind.AddSharedProperty('constants')
+ Kind.AddSharedProperty('attributes')
def __init__(self, mojom_name=None, module=None, attributes=None):
if mojom_name is not None:
@@ -470,12 +644,11 @@ class Struct(ReferenceKind):
return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,
self.mojom_name,
Repr(self.module, as_ref=True))
- else:
- return GenericRepr(self, {
- 'mojom_name': False,
- 'fields': False,
- 'module': True
- })
+ return GenericRepr(self, {
+ 'mojom_name': False,
+ 'fields': False,
+ 'module': True
+ })
def AddField(self,
mojom_name,
@@ -496,13 +669,13 @@ class Struct(ReferenceKind):
for constant in self.constants:
constant.Stylize(stylizer)
- def IsBackwardCompatible(self, older_struct, checker):
- """This struct is backward-compatible with older_struct if and only if all
- of the following conditions hold:
+ def IsBackwardCompatible(self, rhs, checker):
+ """This struct is backward-compatible with rhs (older_struct) if and only if
+ all of the following conditions hold:
- Any newly added field is tagged with a [MinVersion] attribute specifying
a version number greater than all previously used [MinVersion]
attributes within the struct.
- - All fields present in older_struct remain present in the new struct,
+ - All fields present in rhs remain present in the new struct,
with the same ordinal position, same optional or non-optional status,
same (or backward-compatible) type and where applicable, the same
[MinVersion] attribute value.
@@ -521,7 +694,7 @@ class Struct(ReferenceKind):
return fields_by_ordinal
new_fields = buildOrdinalFieldMap(self)
- old_fields = buildOrdinalFieldMap(older_struct)
+ old_fields = buildOrdinalFieldMap(rhs)
if len(new_fields) < len(old_fields):
# At least one field was removed, which is not OK.
return False
@@ -574,11 +747,18 @@ class Struct(ReferenceKind):
prefix = self.module.GetNamespacePrefix()
return '%s%s' % (prefix, self.mojom_name)
+ def _tuple(self):
+ return (self.mojom_name, self.native_only, self.fields, self.constants,
+ self.attributes)
+
def __eq__(self, rhs):
- return (isinstance(rhs, Struct) and
- (self.mojom_name, self.native_only, self.fields, self.constants,
- self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,
- rhs.constants, rhs.attributes))
+ return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()
+
+ def __lt__(self, rhs):
+ if not isinstance(self, type(rhs)):
+ return str(type(self)) < str(type(rhs))
+
+ return self._tuple() < rhs._tuple()
def __hash__(self):
return id(self)
@@ -595,10 +775,11 @@ class Union(ReferenceKind):
which Java class name to use to represent it in the generated
bindings.
"""
- ReferenceKind.AddSharedProperty('mojom_name')
- ReferenceKind.AddSharedProperty('name')
- ReferenceKind.AddSharedProperty('fields')
- ReferenceKind.AddSharedProperty('attributes')
+ Kind.AddSharedProperty('mojom_name')
+ Kind.AddSharedProperty('name')
+ Kind.AddSharedProperty('fields')
+ Kind.AddSharedProperty('attributes')
+ Kind.AddSharedProperty('default_field')
def __init__(self, mojom_name=None, module=None, attributes=None):
if mojom_name is not None:
@@ -610,14 +791,14 @@ class Union(ReferenceKind):
self.name = None
self.fields = []
self.attributes = attributes
+ self.default_field = None
def Repr(self, as_ref=True):
if as_ref:
return '<%s spec=%r is_nullable=%r fields=%s>' % (
self.__class__.__name__, self.spec, self.is_nullable, Repr(
self.fields))
- else:
- return GenericRepr(self, {'fields': True, 'is_nullable': False})
+ return GenericRepr(self, {'fields': True, 'is_nullable': False})
def AddField(self, mojom_name, kind, ordinal=None, attributes=None):
field = UnionField(mojom_name, kind, ordinal, None, attributes)
@@ -629,13 +810,13 @@ class Union(ReferenceKind):
for field in self.fields:
field.Stylize(stylizer)
- def IsBackwardCompatible(self, older_union, checker):
- """This union is backward-compatible with older_union if and only if all
- of the following conditions hold:
+ def IsBackwardCompatible(self, rhs, checker):
+ """This union is backward-compatible with rhs (older_union) if and only if
+ all of the following conditions hold:
- Any newly added field is tagged with a [MinVersion] attribute specifying
a version number greater than all previously used [MinVersion]
attributes within the union.
- - All fields present in older_union remain present in the new union,
+ - All fields present in rhs remain present in the new union,
with the same ordinal value, same optional or non-optional status,
same (or backward-compatible) type, and where applicable, the same
[MinVersion] attribute value.
@@ -651,7 +832,7 @@ class Union(ReferenceKind):
return fields_by_ordinal
new_fields = buildOrdinalFieldMap(self)
- old_fields = buildOrdinalFieldMap(older_union)
+ old_fields = buildOrdinalFieldMap(rhs)
if len(new_fields) < len(old_fields):
# At least one field was removed, which is not OK.
return False
@@ -678,6 +859,11 @@ class Union(ReferenceKind):
return True
@property
+ def extensible(self):
+ return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \
+ if self.attributes else False
+
+ @property
def stable(self):
return self.attributes.get(ATTRIBUTE_STABLE, False) \
if self.attributes else False
@@ -690,10 +876,17 @@ class Union(ReferenceKind):
prefix = self.module.GetNamespacePrefix()
return '%s%s' % (prefix, self.mojom_name)
+ def _tuple(self):
+ return (self.mojom_name, self.fields, self.attributes)
+
def __eq__(self, rhs):
- return (isinstance(rhs, Union) and
- (self.mojom_name, self.fields,
- self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))
+ return isinstance(rhs, Union) and self._tuple() == rhs._tuple()
+
+ def __lt__(self, rhs):
+ if not isinstance(self, type(rhs)):
+ return str(type(self)) < str(type(rhs))
+
+ return self._tuple() < rhs._tuple()
def __hash__(self):
return id(self)
@@ -707,8 +900,8 @@ class Array(ReferenceKind):
length: The number of elements. None if unknown.
"""
- ReferenceKind.AddSharedProperty('kind')
- ReferenceKind.AddSharedProperty('length')
+ Kind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('length')
def __init__(self, kind=None, length=None):
if kind is not None:
@@ -728,12 +921,11 @@ class Array(ReferenceKind):
return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (
self.__class__.__name__, self.spec, self.is_nullable, Repr(
self.kind), self.length)
- else:
- return GenericRepr(self, {
- 'kind': True,
- 'length': False,
- 'is_nullable': False
- })
+ return GenericRepr(self, {
+ 'kind': True,
+ 'length': False,
+ 'is_nullable': False
+ })
def __eq__(self, rhs):
return (isinstance(rhs, Array)
@@ -754,8 +946,8 @@ class Map(ReferenceKind):
key_kind: {Kind} The type of the keys. May be None.
value_kind: {Kind} The type of the elements. May be None.
"""
- ReferenceKind.AddSharedProperty('key_kind')
- ReferenceKind.AddSharedProperty('value_kind')
+ Kind.AddSharedProperty('key_kind')
+ Kind.AddSharedProperty('value_kind')
def __init__(self, key_kind=None, value_kind=None):
if (key_kind is not None and value_kind is not None):
@@ -780,8 +972,7 @@ class Map(ReferenceKind):
return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (
self.__class__.__name__, self.spec, self.is_nullable,
Repr(self.key_kind), Repr(self.value_kind))
- else:
- return GenericRepr(self, {'key_kind': True, 'value_kind': True})
+ return GenericRepr(self, {'key_kind': True, 'value_kind': True})
def __eq__(self, rhs):
return (isinstance(rhs, Map) and
@@ -797,7 +988,7 @@ class Map(ReferenceKind):
class PendingRemote(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -822,7 +1013,7 @@ class PendingRemote(ReferenceKind):
class PendingReceiver(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -847,7 +1038,7 @@ class PendingReceiver(ReferenceKind):
class PendingAssociatedRemote(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -873,7 +1064,7 @@ class PendingAssociatedRemote(ReferenceKind):
class PendingAssociatedReceiver(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -899,7 +1090,7 @@ class PendingAssociatedReceiver(ReferenceKind):
class InterfaceRequest(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -923,7 +1114,7 @@ class InterfaceRequest(ReferenceKind):
class AssociatedInterfaceRequest(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -949,7 +1140,7 @@ class AssociatedInterfaceRequest(ReferenceKind):
self.kind, rhs.kind)
-class Parameter(object):
+class Parameter:
def __init__(self,
mojom_name=None,
kind=None,
@@ -983,7 +1174,7 @@ class Parameter(object):
rhs.default, rhs.attributes))
-class Method(object):
+class Method:
def __init__(self, interface, mojom_name, ordinal=None, attributes=None):
self.interface = interface
self.mojom_name = mojom_name
@@ -999,12 +1190,11 @@ class Method(object):
def Repr(self, as_ref=True):
if as_ref:
return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
- else:
- return GenericRepr(self, {
- 'mojom_name': False,
- 'parameters': True,
- 'response_parameters': True
- })
+ return GenericRepr(self, {
+ 'mojom_name': False,
+ 'parameters': True,
+ 'response_parameters': True
+ })
def AddParameter(self,
mojom_name,
@@ -1061,21 +1251,49 @@ class Method(object):
return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \
if self.attributes else False
+ @property
+ def allowed_context(self):
+ return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \
+ if self.attributes else None
+
+ @property
+ def supports_urgent(self):
+ return self.attributes.get(ATTRIBUTE_SUPPORTS_URGENT) \
+ if self.attributes else None
+
+ @property
+ def runtime_feature(self):
+ if not self.attributes:
+ return None
+ runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)
+ if runtime_feature is None:
+ return None
+ if not isinstance(runtime_feature, Feature):
+ raise Exception("RuntimeFeature attribute on %s must be a feature." %
+ self.name)
+ return runtime_feature
+
+ def _tuple(self):
+ return (self.mojom_name, self.ordinal, self.parameters,
+ self.response_parameters, self.attributes)
+
def __eq__(self, rhs):
- return (isinstance(rhs, Method) and
- (self.mojom_name, self.ordinal, self.parameters,
- self.response_parameters,
- self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,
- rhs.response_parameters, rhs.attributes))
+ return isinstance(rhs, Method) and self._tuple() == rhs._tuple()
+
+ def __lt__(self, rhs):
+ if not isinstance(self, type(rhs)):
+ return str(type(self)) < str(type(rhs))
+
+ return self._tuple() < rhs._tuple()
class Interface(ReferenceKind):
- ReferenceKind.AddSharedProperty('mojom_name')
- ReferenceKind.AddSharedProperty('name')
- ReferenceKind.AddSharedProperty('methods')
- ReferenceKind.AddSharedProperty('enums')
- ReferenceKind.AddSharedProperty('constants')
- ReferenceKind.AddSharedProperty('attributes')
+ Kind.AddSharedProperty('mojom_name')
+ Kind.AddSharedProperty('name')
+ Kind.AddSharedProperty('methods')
+ Kind.AddSharedProperty('enums')
+ Kind.AddSharedProperty('constants')
+ Kind.AddSharedProperty('attributes')
def __init__(self, mojom_name=None, module=None, attributes=None):
if mojom_name is not None:
@@ -1093,12 +1311,11 @@ class Interface(ReferenceKind):
def Repr(self, as_ref=True):
if as_ref:
return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
- else:
- return GenericRepr(self, {
- 'mojom_name': False,
- 'attributes': False,
- 'methods': False
- })
+ return GenericRepr(self, {
+ 'mojom_name': False,
+ 'attributes': False,
+ 'methods': False
+ })
def AddMethod(self, mojom_name, ordinal=None, attributes=None):
method = Method(self, mojom_name, ordinal, attributes)
@@ -1114,10 +1331,10 @@ class Interface(ReferenceKind):
for constant in self.constants:
constant.Stylize(stylizer)
- def IsBackwardCompatible(self, older_interface, checker):
- """This interface is backward-compatible with older_interface if and only
- if all of the following conditions hold:
- - All defined methods in older_interface (when identified by ordinal) have
+ def IsBackwardCompatible(self, rhs, checker):
+ """This interface is backward-compatible with rhs (older_interface) if and
+ only if all of the following conditions hold:
+ - All defined methods in rhs (when identified by ordinal) have
backward-compatible definitions in this interface. For each method this
means:
- The parameter list is backward-compatible, according to backward-
@@ -1131,7 +1348,7 @@ class Interface(ReferenceKind):
rules for structs.
- All newly introduced methods in this interface have a [MinVersion]
attribute specifying a version greater than any method in
- older_interface.
+ rhs.
"""
def buildOrdinalMethodMap(interface):
@@ -1144,7 +1361,7 @@ class Interface(ReferenceKind):
return methods_by_ordinal
new_methods = buildOrdinalMethodMap(self)
- old_methods = buildOrdinalMethodMap(older_interface)
+ old_methods = buildOrdinalMethodMap(rhs)
max_old_min_version = 0
for ordinal, old_method in old_methods.items():
new_method = new_methods.get(ordinal)
@@ -1187,6 +1404,39 @@ class Interface(ReferenceKind):
return True
@property
+ def service_sandbox(self):
+ if not self.attributes:
+ return None
+ service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None)
+ if service_sandbox is None:
+ return None
+ # Constants are only allowed to refer to an enum here, so replace.
+ if isinstance(service_sandbox, Constant):
+ service_sandbox = service_sandbox.value
+ if not isinstance(service_sandbox, EnumValue):
+ raise Exception("ServiceSandbox attribute on %s must be an enum value." %
+ self.module.name)
+ return service_sandbox
+
+ @property
+ def runtime_feature(self):
+ if not self.attributes:
+ return None
+ runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)
+ if runtime_feature is None:
+ return None
+ if not isinstance(runtime_feature, Feature):
+ raise Exception("RuntimeFeature attribute on %s must be a feature." %
+ self.name)
+ return runtime_feature
+
+ @property
+ def require_context(self):
+ if not self.attributes:
+ return None
+ return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)
+
+ @property
def stable(self):
return self.attributes.get(ATTRIBUTE_STABLE, False) \
if self.attributes else False
@@ -1199,11 +1449,18 @@ class Interface(ReferenceKind):
prefix = self.module.GetNamespacePrefix()
return '%s%s' % (prefix, self.mojom_name)
+ def _tuple(self):
+ return (self.mojom_name, self.methods, self.enums, self.constants,
+ self.attributes)
+
def __eq__(self, rhs):
- return (isinstance(rhs, Interface)
- and (self.mojom_name, self.methods, self.enums, self.constants,
- self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,
- rhs.constants, rhs.attributes))
+ return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()
+
+ def __lt__(self, rhs):
+ if not isinstance(self, type(rhs)):
+ return str(type(self)) < str(type(rhs))
+
+ return self._tuple() < rhs._tuple()
@property
def uuid(self):
@@ -1224,7 +1481,7 @@ class Interface(ReferenceKind):
class AssociatedInterface(ReferenceKind):
- ReferenceKind.AddSharedProperty('kind')
+ Kind.AddSharedProperty('kind')
def __init__(self, kind=None):
if kind is not None:
@@ -1249,7 +1506,7 @@ class AssociatedInterface(ReferenceKind):
self.kind, rhs.kind)
-class EnumField(object):
+class EnumField:
def __init__(self,
mojom_name=None,
value=None,
@@ -1281,16 +1538,25 @@ class EnumField(object):
rhs.attributes, rhs.numeric_value))
-class Enum(Kind):
+class Enum(ValueKind):
+ Kind.AddSharedProperty('mojom_name')
+ Kind.AddSharedProperty('name')
+ Kind.AddSharedProperty('native_only')
+ Kind.AddSharedProperty('fields')
+ Kind.AddSharedProperty('attributes')
+ Kind.AddSharedProperty('min_value')
+ Kind.AddSharedProperty('max_value')
+ Kind.AddSharedProperty('default_field')
+
def __init__(self, mojom_name=None, module=None, attributes=None):
- self.mojom_name = mojom_name
- self.name = None
- self.native_only = False
if mojom_name is not None:
spec = 'x:' + mojom_name
else:
spec = None
- Kind.__init__(self, spec, module)
+ ValueKind.__init__(self, spec, False, module)
+ self.mojom_name = mojom_name
+ self.name = None
+ self.native_only = False
self.fields = []
self.attributes = attributes
self.min_value = None
@@ -1300,8 +1566,7 @@ class Enum(Kind):
def Repr(self, as_ref=True):
if as_ref:
return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
- else:
- return GenericRepr(self, {'mojom_name': False, 'fields': False})
+ return GenericRepr(self, {'mojom_name': False, 'fields': False})
def Stylize(self, stylizer):
self.name = stylizer.StylizeEnum(self.mojom_name)
@@ -1327,14 +1592,14 @@ class Enum(Kind):
return '%s%s' % (prefix, self.mojom_name)
# pylint: disable=unused-argument
- def IsBackwardCompatible(self, older_enum, checker):
- """This enum is backward-compatible with older_enum if and only if one of
- the following conditions holds:
+ def IsBackwardCompatible(self, rhs, checker):
+ """This enum is backward-compatible with rhs (older_enum) if and only if one
+ of the following conditions holds:
- Neither enum is [Extensible] and both have the exact same set of valid
numeric values. Field names and aliases for the same numeric value do
not affect compatibility.
- - older_enum is [Extensible], and for every version defined by
- older_enum, this enum has the exact same set of valid numeric values.
+ - rhs is [Extensible], and for every version defined by
+ rhs, this enum has the exact same set of valid numeric values.
"""
def buildVersionFieldMap(enum):
@@ -1345,32 +1610,49 @@ class Enum(Kind):
fields_by_min_version[field.min_version].add(field.numeric_value)
return fields_by_min_version
- old_fields = buildVersionFieldMap(older_enum)
+ old_fields = buildVersionFieldMap(rhs)
new_fields = buildVersionFieldMap(self)
- if new_fields.keys() != old_fields.keys() and not older_enum.extensible:
- return False
+ if new_fields.keys() != old_fields.keys() and not rhs.extensible:
+ raise Exception("Non-extensible enum cannot be modified")
for min_version, valid_values in old_fields.items():
- if (min_version not in new_fields
- or new_fields[min_version] != valid_values):
- return False
+ if min_version not in new_fields:
+ raise Exception('New values added to an extensible enum '
+ 'do not specify MinVersion: %s' % new_fields)
+
+ if (new_fields[min_version] != valid_values):
+ if (len(new_fields[min_version]) < len(valid_values)):
+ raise Exception('Removing values for an existing MinVersion %s '
+ 'is not allowed' % min_version)
+ raise Exception(
+ 'New values don\'t match old values'
+ 'for an existing MinVersion %s,'
+ ' please specify MinVersion equal to "Next version" '
+ 'in the enum description'
+ ' for the following values:\n%s' %
+ (min_version, new_fields[min_version].difference(valid_values)))
return True
+ def _tuple(self):
+ return (self.mojom_name, self.native_only, self.fields, self.attributes,
+ self.min_value, self.max_value, self.default_field)
+
def __eq__(self, rhs):
- return (isinstance(rhs, Enum) and
- (self.mojom_name, self.native_only, self.fields, self.attributes,
- self.min_value, self.max_value,
- self.default_field) == (rhs.mojom_name, rhs.native_only,
- rhs.fields, rhs.attributes, rhs.min_value,
- rhs.max_value, rhs.default_field))
+ return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()
+
+ def __lt__(self, rhs):
+ if not isinstance(self, type(rhs)):
+ return str(type(self)) < str(type(rhs))
+
+ return self._tuple() < rhs._tuple()
def __hash__(self):
return id(self)
-class Module(object):
+class Module:
def __init__(self, path=None, mojom_namespace=None, attributes=None):
self.path = path
self.mojom_namespace = mojom_namespace
@@ -1379,24 +1661,26 @@ class Module(object):
self.unions = []
self.interfaces = []
self.enums = []
+ self.features = []
self.constants = []
- self.kinds = {}
+ self.kinds = OrderedDict()
self.attributes = attributes
self.imports = []
- self.imported_kinds = {}
- self.metadata = {}
+ self.imported_kinds = OrderedDict()
+ self.metadata = OrderedDict()
def __repr__(self):
# Gives us a decent __repr__ for modules.
return self.Repr()
def __eq__(self, rhs):
- return (isinstance(rhs, Module) and
- (self.path, self.attributes, self.mojom_namespace, self.imports,
- self.constants, self.enums, self.structs, self.unions,
- self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace,
- rhs.imports, rhs.constants, rhs.enums,
- rhs.structs, rhs.unions, rhs.interfaces))
+ return (isinstance(rhs, Module)
+ and (self.path, self.attributes, self.mojom_namespace, self.imports,
+ self.constants, self.enums, self.structs, self.unions,
+ self.interfaces, self.features)
+ == (rhs.path, rhs.attributes, rhs.mojom_namespace, rhs.imports,
+ rhs.constants, rhs.enums, rhs.structs, rhs.unions,
+ rhs.interfaces, rhs.features))
def __hash__(self):
return id(self)
@@ -1405,16 +1689,16 @@ class Module(object):
if as_ref:
return '<%s path=%r mojom_namespace=%r>' % (
self.__class__.__name__, self.path, self.mojom_namespace)
- else:
- return GenericRepr(
- self, {
- 'path': False,
- 'mojom_namespace': False,
- 'attributes': False,
- 'structs': False,
- 'interfaces': False,
- 'unions': False
- })
+ return GenericRepr(
+ self, {
+ 'path': False,
+ 'mojom_namespace': False,
+ 'attributes': False,
+ 'structs': False,
+ 'interfaces': False,
+ 'unions': False,
+ 'features': False,
+ })
def GetNamespacePrefix(self):
return '%s.' % self.mojom_namespace if self.mojom_namespace else ''
@@ -1434,6 +1718,11 @@ class Module(object):
self.unions.append(union)
return union
+ def AddFeature(self, mojom_name, attributes=None):
+ feature = Feature(mojom_name, self, attributes)
+ self.features.append(feature)
+ return feature
+
def Stylize(self, stylizer):
self.namespace = stylizer.StylizeModule(self.mojom_namespace)
for struct in self.structs:
@@ -1446,12 +1735,14 @@ class Module(object):
enum.Stylize(stylizer)
for constant in self.constants:
constant.Stylize(stylizer)
+ for feature in self.features:
+ feature.Stylize(stylizer)
for imported_module in self.imports:
imported_module.Stylize(stylizer)
def Dump(self, f):
- pickle.dump(self, f, 2)
+ pickle.dump(self, f)
@classmethod
def Load(cls, f):
@@ -1461,15 +1752,15 @@ class Module(object):
def IsBoolKind(kind):
- return kind.spec == BOOL.spec
+ return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec
def IsFloatKind(kind):
- return kind.spec == FLOAT.spec
+ return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec
def IsDoubleKind(kind):
- return kind.spec == DOUBLE.spec
+ return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec
def IsIntegralKind(kind):
@@ -1477,7 +1768,14 @@ def IsIntegralKind(kind):
or kind.spec == INT16.spec or kind.spec == INT32.spec
or kind.spec == INT64.spec or kind.spec == UINT8.spec
or kind.spec == UINT16.spec or kind.spec == UINT32.spec
- or kind.spec == UINT64.spec)
+ or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec
+ or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec
+ or kind.spec == NULLABLE_INT32.spec
+ or kind.spec == NULLABLE_INT64.spec
+ or kind.spec == NULLABLE_UINT8.spec
+ or kind.spec == NULLABLE_UINT16.spec
+ or kind.spec == NULLABLE_UINT32.spec
+ or kind.spec == NULLABLE_UINT64.spec)
def IsStringKind(kind):
@@ -1522,6 +1820,10 @@ def IsArrayKind(kind):
return isinstance(kind, Array)
+def IsFeatureKind(kind):
+ return isinstance(kind, Feature)
+
+
def IsInterfaceKind(kind):
return isinstance(kind, Interface)
@@ -1558,12 +1860,16 @@ def IsEnumKind(kind):
return isinstance(kind, Enum)
+def IsValueKind(kind):
+ return isinstance(kind, ValueKind)
+
+
def IsReferenceKind(kind):
return isinstance(kind, ReferenceKind)
def IsNullableKind(kind):
- return IsReferenceKind(kind) and kind.is_nullable
+ return kind.is_nullable
def IsMapKind(kind):
@@ -1664,11 +1970,8 @@ def MethodPassesInterfaces(method):
return _AnyMethodParameterRecursive(method, IsInterfaceKind)
-def HasSyncMethods(interface):
- for method in interface.methods:
- if method.sync:
- return True
- return False
+def GetSyncMethodOrdinals(interface):
+ return [method.ordinal for method in interface.methods if method.sync]
def HasUninterruptableMethods(interface):
@@ -1700,18 +2003,17 @@ def ContainsHandlesOrInterfaces(kind):
checked.add(kind.spec)
if IsStructKind(kind):
return any(Check(field.kind) for field in kind.fields)
- elif IsUnionKind(kind):
+ if IsUnionKind(kind):
return any(Check(field.kind) for field in kind.fields)
- elif IsAnyHandleKind(kind):
+ if IsAnyHandleKind(kind):
return True
- elif IsAnyInterfaceKind(kind):
+ if IsAnyInterfaceKind(kind):
return True
- elif IsArrayKind(kind):
+ if IsArrayKind(kind):
return Check(kind.kind)
- elif IsMapKind(kind):
+ if IsMapKind(kind):
return Check(kind.key_kind) or Check(kind.value_kind)
- else:
- return False
+ return False
return Check(kind)
@@ -1738,21 +2040,20 @@ def ContainsNativeTypes(kind):
checked.add(kind.spec)
if IsEnumKind(kind):
return kind.native_only
- elif IsStructKind(kind):
+ if IsStructKind(kind):
if kind.native_only:
return True
if any(enum.native_only for enum in kind.enums):
return True
return any(Check(field.kind) for field in kind.fields)
- elif IsUnionKind(kind):
+ if IsUnionKind(kind):
return any(Check(field.kind) for field in kind.fields)
- elif IsInterfaceKind(kind):
+ if IsInterfaceKind(kind):
return any(enum.native_only for enum in kind.enums)
- elif IsArrayKind(kind):
+ if IsArrayKind(kind):
return Check(kind.kind)
- elif IsMapKind(kind):
+ if IsMapKind(kind):
return Check(kind.key_kind) or Check(kind.value_kind)
- else:
- return False
+ return False
return Check(kind)
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
index e8fd4936..2a4e852c 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
index 88b77c98..61240426 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
@@ -1,7 +1,8 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import copy
from mojom.generate import module as mojom
# This module provides a mechanism for determining the packed order and offsets
@@ -15,7 +16,7 @@ from mojom.generate import module as mojom
HEADER_SIZE = 8
-class PackedField(object):
+class PackedField:
kind_to_size = {
mojom.BOOL: 1,
mojom.INT8: 1,
@@ -75,18 +76,55 @@ class PackedField(object):
return 8
return cls.GetSizeForKind(kind)
- def __init__(self, field, index, ordinal):
+ def __init__(self,
+ field,
+ index,
+ ordinal,
+ original_field=None,
+ sub_ordinal=None,
+ linked_value_packed_field=None):
"""
Args:
field: the original field.
index: the position of the original field in the struct.
ordinal: the ordinal of the field for serialization.
+ original_field: See below.
+ sub_ordinal: See below.
+ linked_value_packed_field: See below.
+
+ original_field, sub_ordinal, and linked_value_packed_field are used to
+ support nullable ValueKind fields. For legacy reasons, nullable ValueKind
+ fields actually generate two PackedFields. This allows:
+
+ - backwards compatibility prior to Mojo support for nullable ValueKinds.
+ - correct packing of fields for the aforementioned backwards compatibility.
+
+ When translating Fields to PackedFields, the original field is turned into
+ two PackedFields: the first PackedField always has type mojom.BOOL, while
+ the second PackedField has the non-nullable version of the field's kind.
+
+ When constructing these PackedFields, original_field references the field
+ as defined in the mojom; the name as defined in the mojom will be used for
+ all layers above the wire/data layer.
+
+ sub_ordinal is used to sort the two PackedFields correctly with respect to
+ each other: the first mojom.BOOL field always has sub_ordinal 0, while the
+ second field always has sub_ordinal 1.
+
+ Finally, linked_value_packed_field is used by the serialization and
+ deserialization helpers, which generally just iterate over a PackedStruct's
+ PackedField's in ordinal order. This allows the helpers to easily reference
+ any related PackedFields rather than having to lookup related PackedFields
+ by index while iterating.
"""
self.field = field
self.index = index
self.ordinal = ordinal
- self.size = self.GetSizeForKind(field.kind)
- self.alignment = self.GetAlignmentForKind(field.kind)
+ self.original_field = original_field
+ self.sub_ordinal = sub_ordinal
+ self.linked_value_packed_field = linked_value_packed_field
+ self.size = self.GetSizeForKind(self.field.kind)
+ self.alignment = self.GetAlignmentForKind(self.field.kind)
self.offset = None
self.bit = None
self.min_version = None
@@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
return offset + pad
-class PackedStruct(object):
+def IsNullableValueKindPackedField(field):
+ """Returns true if `field` is derived from a nullable ValueKind field.
+
+ Nullable ValueKind fields often require special handling in the bindings due
+ to the way the implementation is constrained for wire compatibility.
+ """
+ assert isinstance(field, PackedField)
+ return field.sub_ordinal is not None
+
+
+def IsPrimaryNullableValueKindPackedField(field):
+ """Returns true if `field` is derived from a nullable ValueKind mojom field
+ and is the "primary" field.
+
+ The primary field is a bool PackedField that controls if the field should be
+ considered as present or not; it will have a reference to the PackedField that
+ holds the actual value representation if considered present.
+
+ Bindings code that translates between the wire protocol and the higher layers
+ can use this to simplify mapping multiple PackedFields to the single field
+ that is logically exposed to bindings consumers.
+ """
+ assert isinstance(field, PackedField)
+ return field.linked_value_packed_field is not None
+
+
+class PackedStruct:
def __init__(self, struct):
self.struct = struct
# |packed_fields| contains all the fields, in increasing offset order.
@@ -139,9 +203,41 @@ class PackedStruct(object):
for index, field in enumerate(struct.fields):
if field.ordinal is not None:
ordinal = field.ordinal
- src_fields.append(PackedField(field, index, ordinal))
+ # Nullable value types are a bit weird: they generate two PackedFields
+ # despite being a single ValueKind. This is for wire compatibility to
+ # ease the transition from legacy mojom syntax where nullable value types
+ # were not supported.
+ if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:
+ # The suffixes intentionally use Unicode codepoints which are considered
+ # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in
+ # actual user code.
+ has_value_field = copy.copy(field)
+ has_value_field.name = f'{field.mojom_name}_$flag'
+ has_value_field.kind = mojom.BOOL
+
+ value_field = copy.copy(field)
+ value_field.name = f'{field.mojom_name}_$value'
+ value_field.kind = field.kind.MakeUnnullableKind()
+
+ value_packed_field = PackedField(value_field,
+ index,
+ ordinal,
+ original_field=field,
+ sub_ordinal=1,
+ linked_value_packed_field=None)
+ has_value_packed_field = PackedField(
+ has_value_field,
+ index,
+ ordinal,
+ original_field=field,
+ sub_ordinal=0,
+ linked_value_packed_field=value_packed_field)
+ src_fields.append(has_value_packed_field)
+ src_fields.append(value_packed_field)
+ else:
+ src_fields.append(PackedField(field, index, ordinal))
ordinal += 1
- src_fields.sort(key=lambda field: field.ordinal)
+ src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))
# Set |min_version| for each field.
next_min_version = 0
@@ -156,10 +252,11 @@ class PackedStruct(object):
if (packed_field.min_version != 0
and mojom.IsReferenceKind(packed_field.field.kind)
and not packed_field.field.kind.is_nullable):
- raise Exception("Non-nullable fields are only allowed in version 0 of "
- "a struct. %s.%s is defined with [MinVersion=%d]." %
- (self.struct.name, packed_field.field.name,
- packed_field.min_version))
+ raise Exception(
+ "Non-nullable reference fields are only allowed in version 0 of a "
+ "struct. %s.%s is defined with [MinVersion=%d]." %
+ (self.struct.name, packed_field.field.name,
+ packed_field.min_version))
src_field = src_fields[0]
src_field.offset = 0
@@ -186,7 +283,7 @@ class PackedStruct(object):
dst_fields.append(src_field)
-class ByteInfo(object):
+class ByteInfo:
def __init__(self):
self.is_padding = False
self.packed_fields = []
@@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):
return byte_info
-class VersionInfo(object):
- def __init__(self, version, num_fields, num_bytes):
+class VersionInfo:
+ def __init__(self, version, num_fields, num_packed_fields, num_bytes):
self.version = version
self.num_fields = num_fields
+ self.num_packed_fields = num_packed_fields
self.num_bytes = num_bytes
@@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):
versions = []
last_version = 0
last_num_fields = 0
+ last_num_packed_fields = 0
last_payload_size = 0
for packed_field in packed_struct.packed_fields_in_ordinal_order:
if packed_field.min_version != last_version:
versions.append(
- VersionInfo(last_version, last_num_fields,
+ VersionInfo(last_version, last_num_fields, last_num_packed_fields,
last_payload_size + HEADER_SIZE))
last_version = packed_field.min_version
- last_num_fields += 1
+ # Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to
+ # avoid double-counting, only increment if the field is:
+ # - not used for representing a nullable value kind field, or
+ # - the primary field representing the nullable value kind field.
+ last_num_fields += 1 if (
+ not IsNullableValueKindPackedField(packed_field)
+ or IsPrimaryNullableValueKindPackedField(packed_field)) else 0
+
+ last_num_packed_fields += 1
+
# The fields are iterated in ordinal order here. However, the size of a
# version is determined by the last field of that version in pack order,
# instead of ordinal order. Therefore, we need to calculate the max value.
- last_payload_size = max(
- GetPayloadSizeUpToField(packed_field), last_payload_size)
+ last_payload_size = max(GetPayloadSizeUpToField(packed_field),
+ last_payload_size)
- assert len(versions) == 0 or last_num_fields != versions[-1].num_fields
+ assert len(
+ versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields
versions.append(
- VersionInfo(last_version, last_num_fields,
+ VersionInfo(last_version, last_num_fields, last_num_packed_fields,
last_payload_size + HEADER_SIZE))
return versions
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
index 98c705ad..7d8e4e01 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):
self.assertEqual(4, versions[2].num_fields)
self.assertEqual(32, versions[2].num_bytes)
+ def testGetVersionInfoPackedStruct(self):
+ """Tests that pack.GetVersionInfo() correctly sets version, num_fields,
+ and num_packed_fields for a packed struct.
+ """
+ struct = mojom.Struct('test')
+ struct.AddField('field_0', mojom.BOOL, ordinal=0)
+ struct.AddField('field_1',
+ mojom.NULLABLE_BOOL,
+ ordinal=1,
+ attributes={'MinVersion': 1})
+ struct.AddField('field_2',
+ mojom.NULLABLE_BOOL,
+ ordinal=2,
+ attributes={'MinVersion': 2})
+ ps = pack.PackedStruct(struct)
+ versions = pack.GetVersionInfo(ps)
+
+ self.assertEqual(3, len(versions))
+ self.assertEqual(0, versions[0].version)
+ self.assertEqual(1, versions[1].version)
+ self.assertEqual(2, versions[2].version)
+ self.assertEqual(1, versions[0].num_fields)
+ self.assertEqual(2, versions[1].num_fields)
+ self.assertEqual(3, versions[2].num_fields)
+ self.assertEqual(1, versions[0].num_packed_fields)
+ self.assertEqual(3, versions[1].num_packed_fields)
+ self.assertEqual(5, versions[2].num_packed_fields)
+
def testInterfaceAlignment(self):
"""Tests that interfaces are aligned on 4-byte boundaries, although the size
of an interface is 8 bytes.
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
index 0da90058..807e2a4f 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
@@ -1,4 +1,4 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
index 7580b780..83bb297f 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
@@ -1,4 +1,4 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Convert parse tree to AST.
@@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.
import itertools
import os
import re
-import sys
+from collections import OrderedDict
from mojom.generate import generator
from mojom.generate import module as mojom
from mojom.parse import ast
-def _IsStrOrUnicode(x):
- if sys.version_info[0] < 3:
- return isinstance(x, (unicode, str))
- return isinstance(x, str)
+is_running_backwards_compatibility_check_hack = False
+
+### DO NOT ADD ENTRIES TO THIS LIST. ###
+_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
+ 'x:arc.keymaster.mojom.Algorithm',
+ 'x:arc.keymaster.mojom.Digest',
+ 'x:arc.keymaster.mojom.SignatureResult',
+ 'x:arc.mojom.AccessibilityActionType',
+ 'x:arc.mojom.AccessibilityBooleanProperty',
+ 'x:arc.mojom.AccessibilityEventIntListProperty',
+ 'x:arc.mojom.AccessibilityEventIntProperty',
+ 'x:arc.mojom.AccessibilityEventStringProperty',
+ 'x:arc.mojom.AccessibilityEventType',
+ 'x:arc.mojom.AccessibilityFilterType',
+ 'x:arc.mojom.AccessibilityIntListProperty',
+ 'x:arc.mojom.AccessibilityIntProperty',
+ 'x:arc.mojom.AccessibilityLiveRegionType',
+ 'x:arc.mojom.AccessibilityNotificationStateType',
+ 'x:arc.mojom.AccessibilityRangeType',
+ 'x:arc.mojom.AccessibilitySelectionMode',
+ 'x:arc.mojom.AccessibilityStringListProperty',
+ 'x:arc.mojom.AccessibilityStringProperty',
+ 'x:arc.mojom.AccessibilityWindowBooleanProperty',
+ 'x:arc.mojom.AccessibilityWindowIntListProperty',
+ 'x:arc.mojom.AccessibilityWindowIntProperty',
+ 'x:arc.mojom.AccessibilityWindowStringProperty',
+ 'x:arc.mojom.AccessibilityWindowType',
+ 'x:arc.mojom.AccountCheckStatus',
+ 'x:arc.mojom.AccountUpdateType',
+ 'x:arc.mojom.ActionType',
+ 'x:arc.mojom.Algorithm',
+ 'x:arc.mojom.AndroidIdSource',
+ 'x:arc.mojom.AnrSource',
+ 'x:arc.mojom.AnrType',
+ 'x:arc.mojom.AppDiscoveryRequestState',
+ 'x:arc.mojom.AppKillType',
+ 'x:arc.mojom.AppPermission',
+ 'x:arc.mojom.AppPermissionGroup',
+ 'x:arc.mojom.AppReinstallState',
+ 'x:arc.mojom.AppShortcutItemType',
+ 'x:arc.mojom.ArcAuthCodeStatus',
+ 'x:arc.mojom.ArcClipboardDragDropEvent',
+ 'x:arc.mojom.ArcCorePriAbiMigEvent',
+ 'x:arc.mojom.ArcDnsQuery',
+ 'x:arc.mojom.ArcImageCopyPasteCompatAction',
+ 'x:arc.mojom.ArcNetworkError',
+ 'x:arc.mojom.ArcNetworkEvent',
+ 'x:arc.mojom.ArcNotificationEvent',
+ 'x:arc.mojom.ArcNotificationExpandState',
+ 'x:arc.mojom.ArcNotificationPriority',
+ 'x:arc.mojom.ArcNotificationRemoteInputState',
+ 'x:arc.mojom.ArcNotificationShownContents',
+ 'x:arc.mojom.ArcNotificationStyle',
+ 'x:arc.mojom.ArcNotificationType',
+ 'x:arc.mojom.ArcPipEvent',
+ 'x:arc.mojom.ArcResizeLockState',
+ 'x:arc.mojom.ArcSignInSuccess',
+ 'x:arc.mojom.ArcTimerResult',
+ 'x:arc.mojom.AudioSwitch',
+ 'x:arc.mojom.BluetoothAclState',
+ 'x:arc.mojom.BluetoothAdapterState',
+ 'x:arc.mojom.BluetoothAdvertisingDataType',
+ 'x:arc.mojom.BluetoothBondState',
+ 'x:arc.mojom.BluetoothDeviceType',
+ 'x:arc.mojom.BluetoothDiscoveryState',
+ 'x:arc.mojom.BluetoothGattDBAttributeType',
+ 'x:arc.mojom.BluetoothGattStatus',
+ 'x:arc.mojom.BluetoothPropertyType',
+ 'x:arc.mojom.BluetoothScanMode',
+ 'x:arc.mojom.BluetoothSdpAttributeType',
+ 'x:arc.mojom.BluetoothSocketType',
+ 'x:arc.mojom.BluetoothStatus',
+ 'x:arc.mojom.BootType',
+ 'x:arc.mojom.CaptionTextShadowType',
+ 'x:arc.mojom.ChangeType',
+ 'x:arc.mojom.ChromeAccountType',
+ 'x:arc.mojom.ChromeApp',
+ 'x:arc.mojom.ChromePage',
+ 'x:arc.mojom.ClockId',
+ 'x:arc.mojom.CloudProvisionFlowError',
+ 'x:arc.mojom.CommandResultType',
+ 'x:arc.mojom.CompanionLibApiId',
+ 'x:arc.mojom.ConnectionStateType',
+ 'x:arc.mojom.ContentChangeType',
+ 'x:arc.mojom.CpuRestrictionState',
+ 'x:arc.mojom.CursorCoordinateSpace',
+ 'x:arc.mojom.DataRestoreStatus',
+ 'x:arc.mojom.DecoderStatus',
+ 'x:arc.mojom.DeviceType',
+ 'x:arc.mojom.Digest',
+ 'x:arc.mojom.DisplayWakeLockType',
+ 'x:arc.mojom.EapMethod',
+ 'x:arc.mojom.EapPhase2Method',
+ 'x:arc.mojom.FileSelectorEventType',
+ 'x:arc.mojom.GMSCheckInError',
+ 'x:arc.mojom.GMSSignInError',
+ 'x:arc.mojom.GeneralSignInError',
+ 'x:arc.mojom.GetNetworksRequestType',
+ 'x:arc.mojom.HalPixelFormat',
+ 'x:arc.mojom.IPAddressType',
+ 'x:arc.mojom.InstallErrorReason',
+ 'x:arc.mojom.KeyFormat',
+ 'x:arc.mojom.KeyManagement',
+ 'x:arc.mojom.KeyPurpose',
+ 'x:arc.mojom.KeymasterError',
+ 'x:arc.mojom.MainAccountHashMigrationStatus',
+ 'x:arc.mojom.MainAccountResolutionStatus',
+ 'x:arc.mojom.ManagementChangeStatus',
+ 'x:arc.mojom.ManagementState',
+ 'x:arc.mojom.MessageCenterVisibility',
+ 'x:arc.mojom.MetricsType',
+ 'x:arc.mojom.MountEvent',
+ 'x:arc.mojom.NativeBridgeType',
+ 'x:arc.mojom.NetworkResult',
+ 'x:arc.mojom.NetworkType',
+ 'x:arc.mojom.OemCryptoAlgorithm',
+ 'x:arc.mojom.OemCryptoCipherMode',
+ 'x:arc.mojom.OemCryptoHdcpCapability',
+ 'x:arc.mojom.OemCryptoLicenseType',
+ 'x:arc.mojom.OemCryptoPrivateKey',
+ 'x:arc.mojom.OemCryptoProvisioningMethod',
+ 'x:arc.mojom.OemCryptoResult',
+ 'x:arc.mojom.OemCryptoRsaPaddingScheme',
+ 'x:arc.mojom.OemCryptoUsageEntryStatus',
+ 'x:arc.mojom.Padding',
+ 'x:arc.mojom.PaiFlowState',
+ 'x:arc.mojom.PatternType',
+ 'x:arc.mojom.PressureLevel',
+ 'x:arc.mojom.PrintColorMode',
+ 'x:arc.mojom.PrintContentType',
+ 'x:arc.mojom.PrintDuplexMode',
+ 'x:arc.mojom.PrinterStatus',
+ 'x:arc.mojom.ProcessState',
+ 'x:arc.mojom.PurchaseState',
+ 'x:arc.mojom.ReauthReason',
+ 'x:arc.mojom.ScaleFactor',
+ 'x:arc.mojom.SecurityType',
+ 'x:arc.mojom.SegmentStyle',
+ 'x:arc.mojom.SelectFilesActionType',
+ 'x:arc.mojom.SetNativeChromeVoxResponse',
+ 'x:arc.mojom.ShowPackageInfoPage',
+ 'x:arc.mojom.SpanType',
+ 'x:arc.mojom.SupportedLinkChangeSource',
+ 'x:arc.mojom.TetheringClientState',
+ 'x:arc.mojom.TextInputType',
+ 'x:arc.mojom.TtsEventType',
+ 'x:arc.mojom.VideoCodecProfile',
+ 'x:arc.mojom.VideoDecodeAccelerator.Result',
+ 'x:arc.mojom.VideoEncodeAccelerator.Error',
+ 'x:arc.mojom.VideoFrameStorageType',
+ 'x:arc.mojom.VideoPixelFormat',
+ 'x:arc.mojom.WakefulnessMode',
+ 'x:arc.mojom.WebApkInstallResult',
+ 'x:ash.ime.mojom.InputFieldType',
+ 'x:ash.ime.mojom.PersonalizationMode',
+ 'x:ash.language.mojom.FeatureId',
+ 'x:blink.mojom.ScrollRestorationType',
+ 'x:chromeos.cdm.mojom.CdmKeyStatus',
+ 'x:chromeos.cdm.mojom.CdmMessageType',
+ 'x:chromeos.cdm.mojom.CdmSessionType',
+ 'x:chromeos.cdm.mojom.DecryptStatus',
+ 'x:chromeos.cdm.mojom.EmeInitDataType',
+ 'x:chromeos.cdm.mojom.EncryptionScheme',
+ 'x:chromeos.cdm.mojom.HdcpVersion',
+ 'x:chromeos.cdm.mojom.OutputProtection.LinkType',
+ 'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',
+ 'x:chromeos.cdm.mojom.PromiseException',
+ 'x:chromeos.cfm.mojom.EnqueuePriority',
+ 'x:chromeos.cfm.mojom.LoggerErrorCode',
+ 'x:chromeos.cfm.mojom.LoggerState',
+ 'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',
+ 'x:chromeos.cros_healthd.mojom.EncryptionState',
+ 'x:chromeos.machine_learning.mojom.AnnotationUsecase',
+ 'x:chromeos.machine_learning.mojom.BuiltinModelId',
+ 'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',
+ 'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',
+ 'x:chromeos.machine_learning.mojom.EndpointReason',
+ 'x:chromeos.machine_learning.mojom.EndpointerType',
+ 'x:chromeos.machine_learning.mojom.ExecuteResult',
+ 'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',
+ 'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',
+ 'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',
+ 'x:chromeos.machine_learning.mojom.LoadModelResult',
+ 'x:chromeos.machine_learning.mojom.Rotation',
+ 'x:chromeos.network_config.mojom.ConnectionStateType',
+ 'x:chromeos.network_config.mojom.DeviceStateType',
+ 'x:chromeos.network_config.mojom.IPConfigType',
+ 'x:chromeos.network_config.mojom.NetworkType',
+ 'x:chromeos.network_config.mojom.OncSource',
+ 'x:chromeos.network_config.mojom.PolicySource',
+ 'x:chromeos.network_config.mojom.PortalState',
+ 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',
+ 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',
+ 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',
+ 'x:cros.mojom.CameraClientType',
+ 'x:cros.mojom.CameraMetadataSectionStart',
+ 'x:cros.mojom.CameraMetadataTag',
+ 'x:cros.mojom.HalPixelFormat',
+ 'x:crosapi.mojom.AllowedPaths',
+ 'x:crosapi.mojom.BrowserAppInstanceType',
+ 'x:crosapi.mojom.CreationResult',
+ 'x:crosapi.mojom.DeviceAccessResultCode',
+ 'x:crosapi.mojom.DeviceMode',
+ 'x:crosapi.mojom.DlpRestrictionLevel',
+ 'x:crosapi.mojom.ExoImeSupport',
+ 'x:crosapi.mojom.FullscreenVisibility',
+ 'x:crosapi.mojom.GoogleServiceAuthError.State',
+ 'x:crosapi.mojom.IsInstallableResult',
+ 'x:crosapi.mojom.KeyTag',
+ 'x:crosapi.mojom.KeystoreSigningAlgorithmName',
+ 'x:crosapi.mojom.KeystoreType',
+ 'x:crosapi.mojom.LacrosFeedbackSource',
+ 'x:crosapi.mojom.MemoryPressureLevel',
+ 'x:crosapi.mojom.MetricsReportingManaged',
+ 'x:crosapi.mojom.NotificationType',
+ 'x:crosapi.mojom.OndeviceHandwritingSupport',
+ 'x:crosapi.mojom.OpenResult',
+ 'x:crosapi.mojom.PolicyDomain',
+ 'x:crosapi.mojom.RegistrationCodeType',
+ 'x:crosapi.mojom.ScaleFactor',
+ 'x:crosapi.mojom.SearchResult.OptionalBool',
+ 'x:crosapi.mojom.SelectFileDialogType',
+ 'x:crosapi.mojom.SelectFileResult',
+ 'x:crosapi.mojom.SharesheetResult',
+ 'x:crosapi.mojom.TouchEventType',
+ 'x:crosapi.mojom.VideoRotation',
+ 'x:crosapi.mojom.WallpaperLayout',
+ 'x:crosapi.mojom.WebAppInstallResultCode',
+ 'x:crosapi.mojom.WebAppUninstallResultCode',
+ 'x:device.mojom.HidBusType',
+ 'x:device.mojom.WakeLockReason',
+ 'x:device.mojom.WakeLockType',
+ 'x:drivefs.mojom.DialogReason.Type',
+ 'x:drivefs.mojom.DriveError.Type',
+ 'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',
+ 'x:drivefs.mojom.FileMetadata.CanPinStatus',
+ 'x:drivefs.mojom.FileMetadata.Type',
+ 'x:drivefs.mojom.ItemEventReason',
+ 'x:drivefs.mojom.MirrorPathStatus',
+ 'x:drivefs.mojom.MirrorSyncStatus',
+ 'x:drivefs.mojom.QueryParameters.SortField',
+ 'x:fuzz.mojom.FuzzEnum',
+ 'x:media.mojom.FillLightMode',
+ 'x:media.mojom.MeteringMode',
+ 'x:media.mojom.PowerLineFrequency',
+ 'x:media.mojom.RedEyeReduction',
+ 'x:media.mojom.ResolutionChangePolicy',
+ 'x:media.mojom.VideoCaptureApi',
+ 'x:media.mojom.VideoCaptureBufferType',
+ 'x:media.mojom.VideoCaptureError',
+ 'x:media.mojom.VideoCaptureFrameDropReason',
+ 'x:media.mojom.VideoCapturePixelFormat',
+ 'x:media.mojom.VideoCaptureTransportType',
+ 'x:media.mojom.VideoFacingMode',
+ 'x:media_session.mojom.AudioFocusType',
+ 'x:media_session.mojom.CameraState',
+ 'x:media_session.mojom.EnforcementMode',
+ 'x:media_session.mojom.MediaAudioVideoState',
+ 'x:media_session.mojom.MediaImageBitmapColorType',
+ 'x:media_session.mojom.MediaPictureInPictureState',
+ 'x:media_session.mojom.MediaPlaybackState',
+ 'x:media_session.mojom.MediaSession.SuspendType',
+ 'x:media_session.mojom.MediaSessionAction',
+ 'x:media_session.mojom.MediaSessionImageType',
+ 'x:media_session.mojom.MediaSessionInfo.SessionState',
+ 'x:media_session.mojom.MicrophoneState',
+ 'x:ml.model_loader.mojom.ComputeResult',
+ 'x:ml.model_loader.mojom.CreateModelLoaderResult',
+ 'x:ml.model_loader.mojom.LoadModelResult',
+ 'x:mojo.test.AnExtensibleEnum',
+ 'x:mojo.test.EnumB',
+ 'x:mojo.test.ExtensibleEmptyEnum',
+ 'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',
+ 'x:network.mojom.WebSandboxFlags',
+ 'x:payments.mojom.BillingResponseCode',
+ 'x:payments.mojom.CreateDigitalGoodsResponseCode',
+ 'x:payments.mojom.ItemType',
+ 'x:printing.mojom.PrinterType',
+ 'x:ui.mojom.KeyboardCode',
+)
+### DO NOT ADD ENTRIES TO THIS LIST. ###
def _DuplicateName(values):
@@ -98,12 +375,6 @@ def _MapKind(kind):
}
if kind.endswith('?'):
base_kind = _MapKind(kind[0:-1])
- # NOTE: This doesn't rule out enum types. Those will be detected later, when
- # cross-reference is established.
- reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',
- 'rma', 'rca')
- if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
- raise Exception('A type (spec "%s") cannot be made nullable' % base_kind)
return '?' + base_kind
if kind.endswith('}'):
lbracket = kind.rfind('{')
@@ -113,8 +384,6 @@ def _MapKind(kind):
lbracket = kind.rfind('[')
typename = kind[0:lbracket]
return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
- if kind.endswith('&'):
- return 'r:' + _MapKind(kind[0:-1])
if kind.startswith('asso<'):
assert kind.endswith('>')
return 'asso:' + _MapKind(kind[5:-1])
@@ -135,13 +404,45 @@ def _MapKind(kind):
return 'x:' + kind
-def _AttributeListToDict(attribute_list):
+def _MapAttributeValue(module, kind, value):
+ # True/False/None
+ if value is None:
+ return value
+ if not isinstance(value, str):
+ return value
+ # Is the attribute value the name of a feature?
+ try:
+ # Features cannot be nested in other types, so lookup in the global scope.
+ trial = _LookupKind(module.kinds, 'x:' + value,
+ _GetScopeForKind(module, kind))
+ if isinstance(trial, mojom.Feature):
+ return trial
+ except ValueError:
+ pass
+ # Is the attribute value a constant or enum value?
+ try:
+ trial = _LookupValue(module, None, None, ('IDENTIFIER', value))
+ if isinstance(trial, mojom.ConstantValue):
+ return trial.constant
+ if isinstance(trial, mojom.EnumValue):
+ return trial
+ except ValueError:
+ pass
+ # If not a referenceable mojo type - return as a string.
+ return value
+
+
+def _AttributeListToDict(module, kind, attribute_list):
if attribute_list is None:
return None
assert isinstance(attribute_list, ast.AttributeList)
- # TODO(vtl): Check for duplicate keys here.
- return dict(
- [(attribute.key, attribute.value) for attribute in attribute_list])
+ attributes = dict()
+ for attribute in attribute_list:
+ if attribute.key in attributes:
+ raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
+ attributes[attribute.key] = _MapAttributeValue(module, kind,
+ attribute.value)
+ return attributes
builtin_values = frozenset([
@@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):
return kind
if spec.startswith('?'):
- kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
+ kind = _Kind(kinds, spec[1:], scope)
+ kind = kind.MakeNullableKind()
elif spec.startswith('a:'):
kind = mojom.Array(_Kind(kinds, spec[2:], scope))
elif spec.startswith('asso:'):
@@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):
def _Import(module, import_module):
# Copy the struct kinds from our imports into the current module.
- importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
+ importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface,
+ mojom.Feature)
for kind in import_module.kinds.values():
if (isinstance(kind, importable_kinds)
and kind.module.path == import_module.path):
@@ -316,6 +619,32 @@ def _Import(module, import_module):
return import_module
+def _Feature(module, parsed_feature):
+ """
+ Args:
+ module: {mojom.Module} Module currently being constructed.
+ parsed_feature: {ast.Feature} Parsed feature.
+
+ Returns:
+ {mojom.Feature} AST feature.
+ """
+ feature = mojom.Feature(module=module)
+ feature.mojom_name = parsed_feature.mojom_name
+ feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name
+ module.kinds[feature.spec] = feature
+ feature.constants = []
+ _ProcessElements(
+ parsed_feature.mojom_name, parsed_feature.body, {
+ ast.Const:
+ lambda const: feature.constants.append(
+ _Constant(module, const, feature)),
+ })
+
+ feature.attributes = _AttributeListToDict(module, feature,
+ parsed_feature.attribute_list)
+ return feature
+
+
def _Struct(module, parsed_struct):
"""
Args:
@@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):
struct.fields_data.append,
})
- struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
+ struct.attributes = _AttributeListToDict(module, struct,
+ parsed_struct.attribute_list)
# Enforce that a [Native] attribute is set to make native-only struct
# declarations more explicit.
@@ -377,7 +707,8 @@ def _Union(module, parsed_union):
union.fields_data = []
_ProcessElements(parsed_union.mojom_name, parsed_union.body,
{ast.UnionField: union.fields_data.append})
- union.attributes = _AttributeListToDict(parsed_union.attribute_list)
+ union.attributes = _AttributeListToDict(module, union,
+ parsed_union.attribute_list)
return union
@@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct):
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = _LookupValue(module, struct, field.kind,
parsed_field.default_value)
- field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+ field.attributes = _AttributeListToDict(module, field,
+ parsed_field.attribute_list)
return field
@@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):
"""
field = mojom.UnionField()
field.mojom_name = parsed_field.mojom_name
+ # Disallow unions from being self-recursive.
+ parsed_typename = parsed_field.typename
+ if parsed_typename.endswith('?'):
+ parsed_typename = parsed_typename[:-1]
+ assert parsed_typename != union.mojom_name
field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
(module.mojom_namespace, union.mojom_name))
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = None
- field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+ field.attributes = _AttributeListToDict(module, field,
+ parsed_field.attribute_list)
+ if field.is_default and not mojom.IsNullableKind(field.kind) and \
+ not mojom.IsIntegralKind(field.kind):
+ raise Exception(
+ '[Default] field for union %s must be nullable or integral type.' %
+ union.mojom_name)
return field
@@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):
parameter.ordinal = (parsed_param.ordinal.value
if parsed_param.ordinal else None)
parameter.default = None # TODO(tibell): We never have these. Remove field?
- parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
+ parameter.attributes = _AttributeListToDict(module, parameter,
+ parsed_param.attribute_list)
return parameter
@@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):
method.response_parameters = list(
map(lambda parameter: _Parameter(module, parameter, interface),
parsed_method.response_parameter_list))
- method.attributes = _AttributeListToDict(parsed_method.attribute_list)
+ method.attributes = _AttributeListToDict(module, method,
+ parsed_method.attribute_list)
# Enforce that only methods with response can have a [Sync] attribute.
if method.sync and method.response_parameters is None:
@@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):
interface.mojom_name = parsed_iface.mojom_name
interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
module.kinds[interface.spec] = interface
- interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
+ interface.attributes = _AttributeListToDict(module, interface,
+ parsed_iface.attribute_list)
interface.enums = []
interface.constants = []
interface.methods_data = []
@@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):
field = mojom.EnumField()
field.mojom_name = parsed_field.mojom_name
field.value = _LookupValue(module, enum, None, parsed_field.value)
- field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+ field.attributes = _AttributeListToDict(module, field,
+ parsed_field.attribute_list)
value = mojom.EnumValue(module, enum, field)
module.values[value.GetSpec()] = value
return field
@@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):
prev_value += 1
# Integral value (e.g: BEGIN = -0x1).
- elif _IsStrOrUnicode(field.value):
+ elif isinstance(field.value, str):
prev_value = int(field.value, 0)
# Reference to a previous enum value (e.g: INIT = BEGIN).
@@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):
else:
raise Exception('Unresolved enum value for %s' % field.value.GetSpec())
- #resolved_enum_values[field.mojom_name] = prev_value
+ if prev_value in (-128, -127):
+ raise Exception(f'{field.mojom_name} in {enum.spec} has the value '
+ f'{prev_value}, which is reserved for WTF::HashTrait\'s '
+ 'default enum specialization and may not be used.')
field.numeric_value = prev_value
if min_value is None or prev_value < min_value:
min_value = prev_value
@@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):
mojom_name = parent_kind.mojom_name + '.' + mojom_name
enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
enum.parent_kind = parent_kind
- enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
+ enum.attributes = _AttributeListToDict(module, enum,
+ parsed_enum.attribute_list)
if not enum.native_only:
enum.fields = list(
@@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):
for field in enum.fields:
if field.default:
if not enum.extensible:
- raise Exception('Non-extensible enums may not specify a default')
- if enum.default_field is not None:
raise Exception(
- 'Only one enumerator value may be specified as the default')
+ f'Non-extensible enum {enum.spec} may not specify a default')
+ if enum.default_field is not None:
+ raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')
enum.default_field = field
+ # While running the backwards compatibility check, ignore errors because the
+ # old version of the enum might not specify [Default].
+ if (enum.extensible and enum.default_field is None
+ and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
+ and not is_running_backwards_compatibility_check_hack):
+ raise Exception(
+ f'Extensible enum {enum.spec} must specify a [Default] enumerator')
module.kinds[enum.spec] = enum
@@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):
for referenced_kind in extract_referenced_user_kinds(param.kind):
sanitized_kind = sanitize_kind(referenced_kind)
referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
+ # Consts can reference imported enums.
+ for const in module.constants:
+ if not const.kind in mojom.PRIMITIVES:
+ sanitized_kind = sanitize_kind(const.kind)
+ referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
return referenced_user_kinds
@@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):
assertDependencyIsStable(response_param.kind)
+def _AssertStructIsValid(kind):
+ expected_ordinals = set(range(0, len(kind.fields)))
+ ordinals = set(map(lambda field: field.ordinal, kind.fields))
+ if ordinals != expected_ordinals:
+ raise Exception(
+ 'Structs must use contiguous ordinals starting from 0. ' +
+ '{} is missing the following ordinals: {}.'.format(
+ kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))
+
+
def _Module(tree, path, imports):
"""
Args:
@@ -778,6 +1151,8 @@ def _Module(tree, path, imports):
module.structs = []
module.unions = []
module.interfaces = []
+ module.features = []
+
_ProcessElements(
filename, tree.definition_list, {
ast.Const:
@@ -791,6 +1166,8 @@ def _Module(tree, path, imports):
ast.Interface:
lambda interface: module.interfaces.append(
_Interface(module, interface)),
+ ast.Feature:
+ lambda feature: module.features.append(_Feature(module, feature)),
})
# Second pass expands fields and methods. This allows fields and parameters
@@ -806,12 +1183,24 @@ def _Module(tree, path, imports):
for enum in struct.enums:
all_defined_kinds[enum.spec] = enum
+ for feature in module.features:
+ all_defined_kinds[feature.spec] = feature
+
for union in module.unions:
union.fields = list(
map(lambda field: _UnionField(module, field, union), union.fields_data))
_AssignDefaultOrdinals(union.fields)
+ for field in union.fields:
+ if field.is_default:
+ if union.default_field is not None:
+ raise Exception('Multiple [Default] fields in union %s.' %
+ union.mojom_name)
+ union.default_field = field
del union.fields_data
all_defined_kinds[union.spec] = union
+ if union.extensible and union.default_field is None:
+ raise Exception('Extensible union %s must specify a [Default] field' %
+ union.mojom_name)
for interface in module.interfaces:
interface.methods = list(
@@ -829,8 +1218,8 @@ def _Module(tree, path, imports):
all_defined_kinds.values())
imported_kind_specs = set(all_referenced_kinds.keys()).difference(
set(all_defined_kinds.keys()))
- module.imported_kinds = dict(
- (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)
+ module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
+ for spec in sorted(imported_kind_specs))
generator.AddComputedData(module)
for iface in module.interfaces:
@@ -847,6 +1236,9 @@ def _Module(tree, path, imports):
if kind.stable:
_AssertTypeIsStable(kind)
+ for kind in module.structs:
+ _AssertStructIsValid(kind)
+
return module
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
index 19905c8a..b4fea924 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
@@ -1,17 +1,13 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
-import os.path
-import sys
import unittest
from mojom.generate import module as mojom
from mojom.generate import translate
from mojom.parse import ast
-
class TranslateTest(unittest.TestCase):
"""Tests |parser.Parse()|."""
@@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):
# pylint: disable=W0212
self.assertEquals(
translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
- self.assertEquals(
- translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface")
+ self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
+ "?rca:x:SomeInterface")
+
+ def testSelfRecursiveUnions(self):
+ """Verifies _UnionField() raises when a union is self-recursive."""
+ tree = ast.Mojom(None, ast.ImportList(), [
+ ast.Union("SomeUnion", None,
+ ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")]))
+ ])
+ with self.assertRaises(Exception):
+ translate.OrderedModule(tree, "mojom_tree", [])
+
+ tree = ast.Mojom(None, ast.ImportList(), [
+ ast.Union(
+ "SomeUnion", None,
+ ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")]))
+ ])
+ with self.assertRaises(Exception):
+ translate.OrderedModule(tree, "mojom_tree", [])
+
+ def testDuplicateAttributesException(self):
+ tree = ast.Mojom(None, ast.ImportList(), [
+ ast.Union(
+ "FakeUnion",
+ ast.AttributeList([
+ ast.Attribute("key1", "value"),
+ ast.Attribute("key1", "value")
+ ]),
+ ast.UnionBody([
+ ast.UnionField("a", None, None, "int32"),
+ ast.UnionField("b", None, None, "string")
+ ]))
+ ])
+ with self.assertRaises(Exception):
+ translate.OrderedModule(tree, "mojom_tree", [])
+
+ def testEnumWithReservedValues(self):
+ """Verifies that assigning reserved values to enumerators fails."""
+ # -128 is reserved for the empty representation in WTF::HashTraits.
+ tree = ast.Mojom(None, ast.ImportList(), [
+ ast.Enum(
+ "MyEnum", None,
+ ast.EnumValueList([
+ ast.EnumValue('kReserved', None, '-128'),
+ ]))
+ ])
+ with self.assertRaises(Exception) as context:
+ translate.OrderedModule(tree, "mojom_tree", [])
+ self.assertIn("reserved for WTF::HashTrait", str(context.exception))
+
+ # -127 is reserved for the deleted representation in WTF::HashTraits.
+ tree = ast.Mojom(None, ast.ImportList(), [
+ ast.Enum(
+ "MyEnum", None,
+ ast.EnumValueList([
+ ast.EnumValue('kReserved', None, '-127'),
+ ]))
+ ])
+ with self.assertRaises(Exception) as context:
+ translate.OrderedModule(tree, "mojom_tree", [])
+ self.assertIn("reserved for WTF::HashTrait", str(context.exception))
+
+ # Implicitly assigning a reserved value should also fail.
+ tree = ast.Mojom(None, ast.ImportList(), [
+ ast.Enum(
+ "MyEnum", None,
+ ast.EnumValueList([
+ ast.EnumValue('kNotReserved', None, '-129'),
+ ast.EnumValue('kImplicitlyReserved', None, None),
+ ]))
+ ])
+ with self.assertRaises(Exception) as context:
+ translate.OrderedModule(tree, "mojom_tree", [])
+ self.assertIn("reserved for WTF::HashTrait", str(context.exception))
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
index 1f0db200..aae9cdb6 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
@@ -1,4 +1,4 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Node classes for the AST for a Mojo IDL file."""
@@ -8,17 +8,14 @@
# and lineno). You may also define __repr__() to help with analyzing test
# failures, especially for more complex types.
+import os.path
-import sys
+# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
+# pylint: disable=no-member
-def _IsStrOrUnicode(x):
- if sys.version_info[0] < 3:
- return isinstance(x, (unicode, str))
- return isinstance(x, str)
-
-class NodeBase(object):
+class NodeBase:
"""Base class for nodes in the AST."""
def __init__(self, filename=None, lineno=None):
@@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
classes, in a tuple) of the members of the list.)"""
def __init__(self, item_or_items=None, **kwargs):
- super(NodeListBase, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.items = []
if item_or_items is None:
pass
@@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
return self.items.__iter__()
def __eq__(self, other):
- return super(NodeListBase, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.items == other.items
# Implement this so that on failure, we get slightly more sensible output.
@@ -96,7 +93,7 @@ class Definition(NodeBase):
include parameter definitions.) This class is meant to be subclassed."""
def __init__(self, mojom_name, **kwargs):
- assert _IsStrOrUnicode(mojom_name)
+ assert isinstance(mojom_name, str)
NodeBase.__init__(self, **kwargs)
self.mojom_name = mojom_name
@@ -108,13 +105,13 @@ class Attribute(NodeBase):
"""Represents an attribute."""
def __init__(self, key, value, **kwargs):
- assert _IsStrOrUnicode(key)
- super(Attribute, self).__init__(**kwargs)
+ assert isinstance(key, str)
+ super().__init__(**kwargs)
self.key = key
self.value = value
def __eq__(self, other):
- return super(Attribute, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.key == other.key and \
self.value == other.value
@@ -131,17 +128,17 @@ class Const(Definition):
def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
# The typename is currently passed through as a string.
- assert _IsStrOrUnicode(typename)
+ assert isinstance(typename, str)
# The value is either a literal (currently passed through as a string) or a
# "wrapped identifier".
- assert _IsStrOrUnicode or isinstance(value, tuple)
- super(Const, self).__init__(mojom_name, **kwargs)
+ assert isinstance(value, (tuple, str))
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.typename = typename
self.value = value
def __eq__(self, other):
- return super(Const, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.typename == other.typename and \
self.value == other.value
@@ -153,12 +150,12 @@ class Enum(Definition):
def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
- super(Enum, self).__init__(mojom_name, **kwargs)
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.enum_value_list = enum_value_list
def __eq__(self, other):
- return super(Enum, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.enum_value_list == other.enum_value_list
@@ -170,13 +167,13 @@ class EnumValue(Definition):
# The optional value is either an int (which is current a string) or a
# "wrapped identifier".
assert attribute_list is None or isinstance(attribute_list, AttributeList)
- assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
- super(EnumValue, self).__init__(mojom_name, **kwargs)
+ assert value is None or isinstance(value, (tuple, str))
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.value = value
def __eq__(self, other):
- return super(EnumValue, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.value == other.value
@@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):
_list_item_type = EnumValue
+class Feature(Definition):
+ """Represents a runtime feature definition."""
+ def __init__(self, mojom_name, attribute_list, body, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert isinstance(body, FeatureBody) or body is None
+ super().__init__(mojom_name, **kwargs)
+ self.attribute_list = attribute_list
+ self.body = body
+
+ def __eq__(self, other):
+ return super().__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.body == other.body
+
+ def __repr__(self):
+ return "Feature(mojom_name = %s, attribute_list = %s, body = %s)" % (
+ self.mojom_name, self.attribute_list, self.body)
+
+
+# This needs to be declared after `FeatureConst` and `FeatureField`.
+class FeatureBody(NodeListBase):
+ """Represents the body of (i.e., list of definitions inside) a feature."""
+
+ # Features are compile time helpers so all fields are initializers/consts
+ # for the underlying platform feature type.
+ _list_item_type = (Const)
+
+
class Import(NodeBase):
"""Represents an import statement."""
def __init__(self, attribute_list, import_filename, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
- assert _IsStrOrUnicode(import_filename)
- super(Import, self).__init__(**kwargs)
+ assert isinstance(import_filename, str)
+ super().__init__(**kwargs)
self.attribute_list = attribute_list
- self.import_filename = import_filename
+ # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.
+ self.import_filename = os.path.normpath(import_filename).replace('\\', '/')
def __eq__(self, other):
- return super(Import, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.import_filename == other.import_filename
@@ -216,12 +242,12 @@ class Interface(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, InterfaceBody)
- super(Interface, self).__init__(mojom_name, **kwargs)
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
- return super(Interface, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
@@ -236,14 +262,14 @@ class Method(Definition):
assert isinstance(parameter_list, ParameterList)
assert response_parameter_list is None or \
isinstance(response_parameter_list, ParameterList)
- super(Method, self).__init__(mojom_name, **kwargs)
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.parameter_list = parameter_list
self.response_parameter_list = response_parameter_list
def __eq__(self, other):
- return super(Method, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.parameter_list == other.parameter_list and \
@@ -264,12 +290,12 @@ class Module(NodeBase):
# |mojom_namespace| is either none or a "wrapped identifier".
assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
- super(Module, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.mojom_namespace = mojom_namespace
self.attribute_list = attribute_list
def __eq__(self, other):
- return super(Module, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.mojom_namespace == other.mojom_namespace and \
self.attribute_list == other.attribute_list
@@ -281,13 +307,13 @@ class Mojom(NodeBase):
assert module is None or isinstance(module, Module)
assert isinstance(import_list, ImportList)
assert isinstance(definition_list, list)
- super(Mojom, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.module = module
self.import_list = import_list
self.definition_list = definition_list
def __eq__(self, other):
- return super(Mojom, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.module == other.module and \
self.import_list == other.import_list and \
self.definition_list == other.definition_list
@@ -302,11 +328,11 @@ class Ordinal(NodeBase):
def __init__(self, value, **kwargs):
assert isinstance(value, int)
- super(Ordinal, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.value = value
def __eq__(self, other):
- return super(Ordinal, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.value == other.value
@@ -314,18 +340,18 @@ class Parameter(NodeBase):
"""Represents a method request or response parameter."""
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
- assert _IsStrOrUnicode(mojom_name)
+ assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
- assert _IsStrOrUnicode(typename)
- super(Parameter, self).__init__(**kwargs)
+ assert isinstance(typename, str)
+ super().__init__(**kwargs)
self.mojom_name = mojom_name
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
def __eq__(self, other):
- return super(Parameter, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.mojom_name == other.mojom_name and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
@@ -344,42 +370,51 @@ class Struct(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, StructBody) or body is None
- super(Struct, self).__init__(mojom_name, **kwargs)
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
- return super(Struct, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
+ def __repr__(self):
+ return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
+ self.mojom_name, self.attribute_list, self.body)
+
class StructField(Definition):
"""Represents a struct field definition."""
def __init__(self, mojom_name, attribute_list, ordinal, typename,
default_value, **kwargs):
- assert _IsStrOrUnicode(mojom_name)
+ assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
- assert _IsStrOrUnicode(typename)
+ assert isinstance(typename, str)
# The optional default value is currently either a value as a string or a
# "wrapped identifier".
- assert default_value is None or _IsStrOrUnicode(default_value) or \
- isinstance(default_value, tuple)
- super(StructField, self).__init__(mojom_name, **kwargs)
+ assert default_value is None or isinstance(default_value, (str, tuple))
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
self.default_value = default_value
def __eq__(self, other):
- return super(StructField, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.typename == other.typename and \
self.default_value == other.default_value
+ def __repr__(self):
+ return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, "
+ "typename = %s, default_value = %s") % (
+ self.mojom_name, self.attribute_list, self.ordinal,
+ self.typename, self.default_value)
+
# This needs to be declared after |StructField|.
class StructBody(NodeListBase):
@@ -394,29 +429,29 @@ class Union(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, UnionBody)
- super(Union, self).__init__(mojom_name, **kwargs)
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
- return super(Union, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
class UnionField(Definition):
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
- assert _IsStrOrUnicode(mojom_name)
+ assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
- assert _IsStrOrUnicode(typename)
- super(UnionField, self).__init__(mojom_name, **kwargs)
+ assert isinstance(typename, str)
+ super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
def __eq__(self, other):
- return super(UnionField, self).__eq__(other) and \
+ return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.typename == other.typename
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
index 62798631..b289f7b1 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
@@ -1,32 +1,26 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
-import os.path
-import sys
import unittest
from mojom.parse import ast
-
class _TestNode(ast.NodeBase):
"""Node type for tests."""
def __init__(self, value, **kwargs):
- super(_TestNode, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.value = value
def __eq__(self, other):
- return super(_TestNode, self).__eq__(other) and self.value == other.value
-
+ return super().__eq__(other) and self.value == other.value
class _TestNodeList(ast.NodeListBase):
"""Node list type for tests."""
_list_item_type = _TestNode
-
class ASTTest(unittest.TestCase):
"""Tests various AST classes."""
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
index 3cb73c5d..9687edbf 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
@@ -1,4 +1,4 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helpers for processing conditionally enabled features in a mojom."""
@@ -17,8 +17,10 @@ class EnableIfError(Error):
def _IsEnabled(definition, enabled_features):
"""Returns true if a definition is enabled.
- A definition is enabled if it has no EnableIf attribute, or if the value of
- the EnableIf attribute is in enabled_features.
+ A definition is enabled if it has no EnableIf/EnableIfNot attribute.
+ It is retained if it has an EnableIf attribute and the attribute is in
+ enabled_features. It is retained if it has an EnableIfNot attribute and the
+ attribute is not in enabled features.
"""
if not hasattr(definition, "attribute_list"):
return True
@@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
already_defined = False
for a in definition.attribute_list:
- if a.key == 'EnableIf':
+ if a.key == 'EnableIf' or a.key == 'EnableIfNot':
if already_defined:
raise EnableIfError(
definition.filename,
- "EnableIf attribute may only be defined once per field.",
+ "EnableIf/EnableIfNot attribute may only be set once per field.",
definition.lineno)
already_defined = True
for attribute in definition.attribute_list:
if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
return False
+ if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
+ return False
return True
@@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):
"""Filters definitions with a body."""
if isinstance(definition, ast.Enum):
_FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)
- elif isinstance(definition, ast.Interface):
- _FilterDisabledFromNodeList(definition.body, enabled_features)
elif isinstance(definition, ast.Method):
_FilterDisabledFromNodeList(definition.parameter_list, enabled_features)
_FilterDisabledFromNodeList(definition.response_parameter_list,
enabled_features)
- elif isinstance(definition, ast.Struct):
- _FilterDisabledFromNodeList(definition.body, enabled_features)
- elif isinstance(definition, ast.Union):
+ elif isinstance(definition,
+ (ast.Interface, ast.Struct, ast.Union, ast.Feature)):
_FilterDisabledFromNodeList(definition.body, enabled_features)
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
index aa609be7..cca1764b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
@@ -1,13 +1,12 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
+import importlib.util
import os
import sys
import unittest
-
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
-
try:
- imp.find_module('mojom')
+ importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
import mojom.parse.ast as ast
@@ -29,7 +27,6 @@ import mojom.parse.parser as parser
ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
-
class ConditionalFeaturesTest(unittest.TestCase):
"""Tests |mojom.parse.conditional_features|."""
@@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(const_source, expected_source)
+ def testFilterIfNotConst(self):
+ """Test that Consts are correctly filtered."""
+ const_source = """
+ [EnableIfNot=blue]
+ const int kMyConst1 = 1;
+ [EnableIfNot=orange]
+ const double kMyConst2 = 2;
+ [EnableIf=blue]
+ const int kMyConst3 = 3;
+ [EnableIfNot=blue]
+ const int kMyConst4 = 4;
+ [EnableIfNot=purple]
+ const int kMyConst5 = 5;
+ """
+ expected_source = """
+ [EnableIfNot=orange]
+ const double kMyConst2 = 2;
+ [EnableIf=blue]
+ const int kMyConst3 = 3;
+ [EnableIfNot=purple]
+ const int kMyConst5 = 5;
+ """
+ self.parseAndAssertEqual(const_source, expected_source)
+
+ def testFilterIfNotMultipleConst(self):
+ """Test that Consts are correctly filtered."""
+ const_source = """
+ [EnableIfNot=blue]
+ const int kMyConst1 = 1;
+ [EnableIfNot=orange]
+ const double kMyConst2 = 2;
+ [EnableIfNot=orange]
+ const int kMyConst3 = 3;
+ """
+ expected_source = """
+ [EnableIfNot=orange]
+ const double kMyConst2 = 2;
+ [EnableIfNot=orange]
+ const int kMyConst3 = 3;
+ """
+ self.parseAndAssertEqual(const_source, expected_source)
+
def testFilterEnum(self):
"""Test that EnumValues are correctly filtered from an Enum."""
enum_source = """
@@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(import_source, expected_source)
+ def testFilterIfNotImport(self):
+ """Test that imports are correctly filtered from a Mojom."""
+ import_source = """
+ [EnableIf=blue]
+ import "foo.mojom";
+ [EnableIfNot=purple]
+ import "bar.mojom";
+ [EnableIfNot=green]
+ import "baz.mojom";
+ """
+ expected_source = """
+ [EnableIf=blue]
+ import "foo.mojom";
+ [EnableIfNot=purple]
+ import "bar.mojom";
+ """
+ self.parseAndAssertEqual(import_source, expected_source)
+
def testFilterInterface(self):
"""Test that definitions are correctly filtered from an Interface."""
interface_source = """
@@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(struct_source, expected_source)
+ def testFilterIfNotStruct(self):
+ """Test that definitions are correctly filtered from a Struct."""
+ struct_source = """
+ struct MyStruct {
+ [EnableIf=blue]
+ enum MyEnum {
+ VALUE1,
+ [EnableIfNot=red]
+ VALUE2,
+ };
+ [EnableIfNot=yellow]
+ const double kMyConst = 1.23;
+ [EnableIf=green]
+ int32 a;
+ double b;
+ [EnableIfNot=purple]
+ int32 c;
+ [EnableIf=blue]
+ double d;
+ int32 e;
+ [EnableIfNot=red]
+ double f;
+ };
+ """
+ expected_source = """
+ struct MyStruct {
+ [EnableIf=blue]
+ enum MyEnum {
+ VALUE1,
+ };
+ [EnableIfNot=yellow]
+ const double kMyConst = 1.23;
+ [EnableIf=green]
+ int32 a;
+ double b;
+ [EnableIfNot=purple]
+ int32 c;
+ [EnableIf=blue]
+ double d;
+ int32 e;
+ };
+ """
+ self.parseAndAssertEqual(struct_source, expected_source)
+
def testFilterUnion(self):
"""Test that UnionFields are correctly filtered from a Union."""
union_source = """
@@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(mojom_source, expected_source)
+ def testFeaturesWithEnableIf(self):
+ mojom_source = """
+ feature Foo {
+ const string name = "FooFeature";
+ [EnableIf=red]
+ const bool default_state = false;
+ [EnableIf=yellow]
+ const bool default_state = true;
+ };
+ """
+ expected_source = """
+ feature Foo {
+ const string name = "FooFeature";
+ [EnableIf=red]
+ const bool default_state = false;
+ };
+ """
+ self.parseAndAssertEqual(mojom_source, expected_source)
+
def testMultipleEnableIfs(self):
source = """
enum Foo {
@@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):
conditional_features.RemoveDisabledDefinitions,
definition, ENABLED_FEATURES)
+ def testMultipleEnableIfs(self):
+ source = """
+ enum Foo {
+ [EnableIf=red,EnableIfNot=yellow]
+ kBarValue = 5,
+ };
+ """
+ definition = parser.Parse(source, "my_file.mojom")
+ self.assertRaises(conditional_features.EnableIfError,
+ conditional_features.RemoveDisabledDefinitions,
+ definition, ENABLED_FEATURES)
+
+ def testMultipleEnableIfs(self):
+ source = """
+ enum Foo {
+ [EnableIfNot=red,EnableIfNot=yellow]
+ kBarValue = 5,
+ };
+ """
+ definition = parser.Parse(source, "my_file.mojom")
+ self.assertRaises(conditional_features.EnableIfError,
+ conditional_features.RemoveDisabledDefinitions,
+ definition, ENABLED_FEATURES)
if __name__ == '__main__':
unittest.main()
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
index 3e084bbf..00136a8b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
@@ -1,8 +1,7 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
import os.path
import sys
@@ -22,7 +21,7 @@ class LexError(Error):
# We have methods which look like they could be functions:
# pylint: disable=R0201
-class Lexer(object):
+class Lexer:
def __init__(self, filename):
self.filename = filename
@@ -56,6 +55,7 @@ class Lexer(object):
'PENDING_RECEIVER',
'PENDING_ASSOCIATED_REMOTE',
'PENDING_ASSOCIATED_RECEIVER',
+ 'FEATURE',
)
keyword_map = {}
@@ -81,7 +81,6 @@ class Lexer(object):
# Operators
'MINUS',
'PLUS',
- 'AMP',
'QSTN',
# Assignment
@@ -168,7 +167,6 @@ class Lexer(object):
# Operators
t_MINUS = r'-'
t_PLUS = r'\+'
- t_AMP = r'&'
t_QSTN = r'\?'
# =
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
index eadc6587..bc9f8354 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
@@ -1,13 +1,12 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
+import importlib.util
import os.path
import sys
import unittest
-
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
-
sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex
try:
- imp.find_module("mojom")
+ importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
import mojom.parse.lexer
-
# This (monkey-patching LexToken to make comparison value-based) is evil, but
# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
# for object identity.)
@@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):
self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
self.assertEquals(
self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
- self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
self.assertEquals(
self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
self.assertEquals(
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
index b3b803d6..1dffd98b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
@@ -1,8 +1,11 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates a syntax tree from a Mojo IDL file."""
+# Breaking parser stanzas is unhelpful so allow longer lines.
+# pylint: disable=line-too-long
+
import os.path
import sys
@@ -33,7 +36,7 @@ class ParseError(Error):
# We have methods which look like they could be functions:
# pylint: disable=R0201
-class Parser(object):
+class Parser:
def __init__(self, lexer, source, filename):
self.tokens = lexer.tokens
self.source = source
@@ -111,7 +114,8 @@ class Parser(object):
| union
| interface
| enum
- | const"""
+ | const
+ | feature"""
p[0] = p[1]
def p_attribute_section_1(self, p):
@@ -140,12 +144,19 @@ class Parser(object):
p[0].Append(p[3])
def p_attribute_1(self, p):
- """attribute : NAME EQUALS evaled_literal
- | NAME EQUALS NAME"""
- p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
+ """attribute : name_wrapped EQUALS identifier_wrapped"""
+ p[0] = ast.Attribute(p[1],
+ p[3][1],
+ filename=self.filename,
+ lineno=p.lineno(1))
def p_attribute_2(self, p):
- """attribute : NAME"""
+ """attribute : name_wrapped EQUALS evaled_literal
+ | name_wrapped EQUALS name_wrapped"""
+ p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
+
+ def p_attribute_3(self, p):
+ """attribute : name_wrapped"""
p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
def p_evaled_literal(self, p):
@@ -161,11 +172,11 @@ class Parser(object):
p[0] = eval(p[1])
def p_struct_1(self, p):
- """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
+ """struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI"""
p[0] = ast.Struct(p[3], p[1], p[5])
def p_struct_2(self, p):
- """struct : attribute_section STRUCT NAME SEMI"""
+ """struct : attribute_section STRUCT name_wrapped SEMI"""
p[0] = ast.Struct(p[3], p[1], None)
def p_struct_body_1(self, p):
@@ -180,11 +191,24 @@ class Parser(object):
p[0].Append(p[2])
def p_struct_field(self, p):
- """struct_field : attribute_section typename NAME ordinal default SEMI"""
+ """struct_field : attribute_section typename name_wrapped ordinal default SEMI"""
p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])
+ def p_feature(self, p):
+ """feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI"""
+ p[0] = ast.Feature(p[3], p[1], p[5])
+
+ def p_feature_body_1(self, p):
+ """feature_body : """
+ p[0] = ast.FeatureBody()
+
+ def p_feature_body_2(self, p):
+ """feature_body : feature_body const"""
+ p[0] = p[1]
+ p[0].Append(p[2])
+
def p_union(self, p):
- """union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI"""
+ """union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI"""
p[0] = ast.Union(p[3], p[1], p[5])
def p_union_body_1(self, p):
@@ -197,7 +221,7 @@ class Parser(object):
p[1].Append(p[2])
def p_union_field(self, p):
- """union_field : attribute_section typename NAME ordinal SEMI"""
+ """union_field : attribute_section typename name_wrapped ordinal SEMI"""
p[0] = ast.UnionField(p[3], p[1], p[4], p[2])
def p_default_1(self, p):
@@ -209,8 +233,7 @@ class Parser(object):
p[0] = p[2]
def p_interface(self, p):
- """interface : attribute_section INTERFACE NAME LBRACE interface_body \
- RBRACE SEMI"""
+ """interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI"""
p[0] = ast.Interface(p[3], p[1], p[5])
def p_interface_body_1(self, p):
@@ -233,8 +256,7 @@ class Parser(object):
p[0] = p[3]
def p_method(self, p):
- """method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \
- response SEMI"""
+ """method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI"""
p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])
def p_parameter_list_1(self, p):
@@ -255,7 +277,7 @@ class Parser(object):
p[0].Append(p[3])
def p_parameter(self, p):
- """parameter : attribute_section typename NAME ordinal"""
+ """parameter : attribute_section typename name_wrapped ordinal"""
p[0] = ast.Parameter(
p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))
@@ -271,8 +293,7 @@ class Parser(object):
"""nonnullable_typename : basictypename
| array
| fixed_array
- | associative_array
- | interfacerequest"""
+ | associative_array"""
p[0] = p[1]
def p_basictypename(self, p):
@@ -297,18 +318,16 @@ class Parser(object):
p[0] = "rcv<%s>" % p[3]
def p_associatedremotetype(self, p):
- """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \
- RANGLE"""
+ """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE"""
p[0] = "rma<%s>" % p[3]
def p_associatedreceivertype(self, p):
- """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \
- RANGLE"""
+ """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE"""
p[0] = "rca<%s>" % p[3]
def p_handletype(self, p):
"""handletype : HANDLE
- | HANDLE LANGLE NAME RANGLE"""
+ | HANDLE LANGLE name_wrapped RANGLE"""
if len(p) == 2:
p[0] = p[1]
else:
@@ -342,14 +361,6 @@ class Parser(object):
"""associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
p[0] = p[5] + "{" + p[3] + "}"
- def p_interfacerequest(self, p):
- """interfacerequest : identifier AMP
- | ASSOCIATED identifier AMP"""
- if len(p) == 3:
- p[0] = p[1] + "&"
- else:
- p[0] = "asso<" + p[2] + "&>"
-
def p_ordinal_1(self, p):
"""ordinal : """
p[0] = None
@@ -366,15 +377,14 @@ class Parser(object):
p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
def p_enum_1(self, p):
- """enum : attribute_section ENUM NAME LBRACE enum_value_list \
- RBRACE SEMI
- | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \
- COMMA RBRACE SEMI"""
+ """enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI
+ | attribute_section ENUM name_wrapped LBRACE \
+ nonempty_enum_value_list COMMA RBRACE SEMI"""
p[0] = ast.Enum(
p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))
def p_enum_2(self, p):
- """enum : attribute_section ENUM NAME SEMI"""
+ """enum : attribute_section ENUM name_wrapped SEMI"""
p[0] = ast.Enum(
p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))
@@ -396,9 +406,9 @@ class Parser(object):
p[0].Append(p[3])
def p_enum_value(self, p):
- """enum_value : attribute_section NAME
- | attribute_section NAME EQUALS int
- | attribute_section NAME EQUALS identifier_wrapped"""
+ """enum_value : attribute_section name_wrapped
+ | attribute_section name_wrapped EQUALS int
+ | attribute_section name_wrapped EQUALS identifier_wrapped"""
p[0] = ast.EnumValue(
p[2],
p[1],
@@ -407,7 +417,7 @@ class Parser(object):
lineno=p.lineno(2))
def p_const(self, p):
- """const : attribute_section CONST typename NAME EQUALS constant SEMI"""
+ """const : attribute_section CONST typename name_wrapped EQUALS constant SEMI"""
p[0] = ast.Const(p[4], p[1], p[3], p[6])
def p_constant(self, p):
@@ -422,10 +432,16 @@ class Parser(object):
# TODO(vtl): Make this produce a "wrapped" identifier (probably as an
# |ast.Identifier|, to be added) and get rid of identifier_wrapped.
def p_identifier(self, p):
- """identifier : NAME
- | NAME DOT identifier"""
+ """identifier : name_wrapped
+ | name_wrapped DOT identifier"""
p[0] = ''.join(p[1:])
+ # Allow 'feature' to be a name literal not just a keyword.
+ def p_name_wrapped(self, p):
+ """name_wrapped : NAME
+ | FEATURE"""
+ p[0] = p[1]
+
def p_literal(self, p):
"""literal : int
| float
@@ -458,6 +474,12 @@ class Parser(object):
# TODO(vtl): Can we figure out what's missing?
raise ParseError(self.filename, "Unexpected end of file")
+ if e.value == 'feature':
+ raise ParseError(self.filename,
+ "`feature` is reserved for a future mojom keyword",
+ lineno=e.lineno,
+ snippet=self._GetSnippet(e.lineno))
+
raise ParseError(
self.filename,
"Unexpected %r:" % e.value,
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
index 6d6b7153..0a26307b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
@@ -1,17 +1,13 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
-import os.path
-import sys
import unittest
from mojom.parse import ast
from mojom.parse import lexer
from mojom.parse import parser
-
class ParserTest(unittest.TestCase):
"""Tests |parser.Parse()|."""
@@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):
handle<data_pipe_producer>? k;
handle<message_pipe>? l;
handle<shared_buffer>? m;
- some_interface&? n;
+ pending_receiver<some_interface>? n;
handle<platform>? o;
};
"""
@@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):
ast.StructField('l', None, None, 'handle<message_pipe>?', None),
ast.StructField('m', None, None, 'handle<shared_buffer>?',
None),
- ast.StructField('n', None, None, 'some_interface&?', None),
+ ast.StructField('n', None, None, 'rcv<some_interface>?', None),
ast.StructField('o', None, None, 'handle<platform>?', None)
]))
])
@@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):
r" *handle\?<data_pipe_consumer> a;$"):
parser.Parse(source2, "my_file.mojom")
- source3 = """\
- struct MyStruct {
- some_interface?& a;
- };
- """
- with self.assertRaisesRegexp(
- parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n"
- r" *some_interface\?& a;$"):
- parser.Parse(source3, "my_file.mojom")
-
def testSimpleUnion(self):
"""Tests a simple .mojom source that just defines a union."""
source = """\
@@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):
source1 = """\
struct MyStruct {
associated MyInterface a;
- associated MyInterface& b;
+ pending_associated_receiver<MyInterface> b;
associated MyInterface? c;
- associated MyInterface&? d;
+ pending_associated_receiver<MyInterface>? d;
};
"""
expected1 = ast.Mojom(None, ast.ImportList(), [
@@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):
'MyStruct', None,
ast.StructBody([
ast.StructField('a', None, None, 'asso<MyInterface>', None),
- ast.StructField('b', None, None, 'asso<MyInterface&>', None),
+ ast.StructField('b', None, None, 'rca<MyInterface>', None),
ast.StructField('c', None, None, 'asso<MyInterface>?', None),
- ast.StructField('d', None, None, 'asso<MyInterface&>?', None)
+ ast.StructField('d', None, None, 'rca<MyInterface>?', None)
]))
])
self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
source2 = """\
interface MyInterface {
- MyMethod(associated A a) =>(associated B& b);
+ MyMethod(associated A a) =>(pending_associated_receiver<B> b);
};"""
expected2 = ast.Mojom(None, ast.ImportList(), [
ast.Interface(
@@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):
ast.InterfaceBody(
ast.Method(
'MyMethod', None, None,
- ast.ParameterList(
- ast.Parameter('a', None, None, 'asso<A>')),
- ast.ParameterList(
- ast.Parameter('b', None, None, 'asso<B&>')))))
+ ast.ParameterList(ast.Parameter('a', None, None,
+ 'asso<A>')),
+ ast.ParameterList(ast.Parameter('b', None, None,
+ 'rca<B>')))))
])
self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
@@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):
r" *associated\? MyInterface& a;$"):
parser.Parse(source3, "my_file.mojom")
-
if __name__ == "__main__":
unittest.main()
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
index eb90c825..9693090e 100755
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Parses mojom IDL files.
@@ -11,6 +11,7 @@ generate usable language bindings.
"""
import argparse
+import builtins
import codecs
import errno
import json
@@ -19,6 +20,7 @@ import multiprocessing
import os
import os.path
import sys
+import traceback
from collections import defaultdict
from mojom.generate import module
@@ -28,16 +30,12 @@ from mojom.parse import conditional_features
# Disable this for easier debugging.
-# In Python 2, subprocesses just hang when exceptions are thrown :(.
-_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
+_ENABLE_MULTIPROCESSING = True
-if sys.version_info < (3, 4):
- _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
-else:
- # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
- if __name__ == '__main__' and sys.platform == 'darwin':
- multiprocessing.set_start_method('fork')
- _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
+# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
+if __name__ == '__main__' and sys.platform == 'darwin':
+ multiprocessing.set_start_method('fork')
+_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
def _ResolveRelativeImportPath(path, roots):
@@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
raise ValueError('"%s" does not exist in any of %s' % (path, roots))
-def _RebaseAbsolutePath(path, roots):
+def RebaseAbsolutePath(path, roots):
"""Rewrites an absolute file path as relative to an absolute directory path in
roots.
@@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
# Already done.
return
- for dep_abspath, dep_path in dependencies[mojom_abspath]:
+ for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
if dep_abspath not in loaded_modules:
_EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
loaded_modules, module_metadata)
@@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
def collect(metadata_filename):
processed_deps.add(metadata_filename)
+
+ # Paths in the metadata file are relative to the metadata file's dir.
+ metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
+
+ def to_abs(s):
+ return os.path.normpath(os.path.join(metadata_dir, s))
+
with open(metadata_filename) as f:
metadata = json.load(f)
allowed_imports.update(
- map(os.path.normcase, map(os.path.normpath, metadata['sources'])))
+ [os.path.normcase(to_abs(s)) for s in metadata['sources']])
for dep_metadata in metadata['deps']:
+ dep_metadata = to_abs(dep_metadata)
if dep_metadata not in processed_deps:
collect(dep_metadata)
@@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
# multiprocessing helper.
-def _ParseAstHelper(args):
- mojom_abspath, enabled_features = args
+def _ParseAstHelper(mojom_abspath, enabled_features):
with codecs.open(mojom_abspath, encoding='utf-8') as f:
ast = parser.Parse(f.read(), mojom_abspath)
conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
@@ -181,8 +186,7 @@ def _ParseAstHelper(args):
# multiprocessing helper.
-def _SerializeHelper(args):
- mojom_abspath, mojom_path = args
+def _SerializeHelper(mojom_abspath, mojom_path):
module_path = os.path.join(_SerializeHelper.output_root_path,
_GetModuleFilename(mojom_path))
module_dir = os.path.dirname(module_path)
@@ -199,12 +203,33 @@ def _SerializeHelper(args):
_SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
-def _Shard(target_func, args, processes=None):
- args = list(args)
+class _ExceptionWrapper:
+ def __init__(self):
+ # Do not capture exception object to ensure pickling works.
+ self.formatted_trace = traceback.format_exc()
+
+
+class _FuncWrapper:
+ """Marshals exceptions and spreads args."""
+
+ def __init__(self, func):
+ self._func = func
+
+ def __call__(self, args):
+ # multiprocessing does not gracefully handle excptions.
+ # https://crbug.com/1219044
+ try:
+ return self._func(*args)
+ except: # pylint: disable=bare-except
+ return _ExceptionWrapper()
+
+
+def _Shard(target_func, arg_list, processes=None):
+ arg_list = list(arg_list)
if processes is None:
processes = multiprocessing.cpu_count()
# Seems optimal to have each process perform at least 2 tasks.
- processes = min(processes, len(args) // 2)
+ processes = min(processes, len(arg_list) // 2)
if sys.platform == 'win32':
# TODO(crbug.com/1190269) - we can't use more than 56
@@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):
# Don't spin up processes unless there is enough work to merit doing so.
if not _ENABLE_MULTIPROCESSING or processes < 2:
- for result in map(target_func, args):
- yield result
+ for arg_tuple in arg_list:
+ yield target_func(*arg_tuple)
return
pool = multiprocessing.Pool(processes=processes)
try:
- for result in pool.imap_unordered(target_func, args):
+ wrapped_func = _FuncWrapper(target_func)
+ for result in pool.imap_unordered(wrapped_func, arg_list):
+ if isinstance(result, _ExceptionWrapper):
+ sys.stderr.write(result.formatted_trace)
+ sys.exit(1)
yield result
finally:
pool.close()
@@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
def _ParseMojoms(mojom_files,
input_root_paths,
output_root_path,
+ module_root_paths,
enabled_features,
module_metadata,
allowed_imports=None):
@@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
are based on the mojom's relative path, rebased onto this path.
Additionally, the script expects this root to contain already-generated
modules for any transitive dependencies not listed in mojom_files.
+ module_root_paths: A list of absolute filesystem paths which contain
+ already-generated modules for any non-transitive dependencies.
enabled_features: A list of enabled feature names, controlling which AST
- nodes are filtered by [EnableIf] attributes.
+ nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
module_metadata: A list of 2-tuples representing metadata key-value pairs to
attach to each compiled module output.
@@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
loaded_modules = {}
input_dependencies = defaultdict(set)
mojom_files_to_parse = dict((os.path.normcase(abs_path),
- _RebaseAbsolutePath(abs_path, input_root_paths))
+ RebaseAbsolutePath(abs_path, input_root_paths))
for abs_path in mojom_files)
abs_paths = dict(
(path, abs_path) for abs_path, path in mojom_files_to_parse.items())
@@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,
loaded_mojom_asts[mojom_abspath] = ast
logging.info('Processing dependencies')
- for mojom_abspath, ast in loaded_mojom_asts.items():
+ for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
invalid_imports = []
for imp in ast.import_list:
import_abspath = _ResolveRelativeImportPath(imp.import_filename,
@@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,
# be parsed and have a module file sitting in a corresponding output
# location.
module_path = _GetModuleFilename(imp.import_filename)
- module_abspath = _ResolveRelativeImportPath(module_path,
- [output_root_path])
+ module_abspath = _ResolveRelativeImportPath(
+ module_path, module_root_paths + [output_root_path])
with open(module_abspath, 'rb') as module_file:
loaded_modules[import_abspath] = module.Module.Load(module_file)
@@ -371,6 +403,15 @@ already present in the provided output root.""")
'ROOT is also searched for existing modules of any transitive imports '
'which were not included in the set of inputs.')
arg_parser.add_argument(
+ '--module-root',
+ default=[],
+ action='append',
+ metavar='ROOT',
+ dest='module_root_paths',
+ help='Adds ROOT to the set of root paths to search for existing modules '
+ 'of non-transitive imports. Provided root paths are always searched in '
+ 'order from longest absolute path to shortest.')
+ arg_parser.add_argument(
'--mojoms',
nargs='+',
dest='mojom_files',
@@ -396,9 +437,9 @@ already present in the provided output root.""")
help='Enables a named feature when parsing the given mojoms. Features '
'are identified by arbitrary string values. Specifying this flag with a '
'given FEATURE name will cause the parser to process any syntax elements '
- 'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
- 'provided for a given FEATURE, such tagged elements are discarded by the '
- 'parser and will not be present in the compiled output.')
+ 'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
+ 'flag is not provided for a given FEATURE, such tagged elements are '
+ 'discarded by the parser and will not be present in the compiled output.')
arg_parser.add_argument(
'--check-imports',
dest='build_metadata_filename',
@@ -436,6 +477,7 @@ already present in the provided output root.""")
mojom_files = list(map(os.path.abspath, args.mojom_files))
input_roots = list(map(os.path.abspath, args.input_root_paths))
output_root = os.path.abspath(args.output_root_path)
+ module_roots = list(map(os.path.abspath, args.module_root_paths))
if args.build_metadata_filename:
allowed_imports = _CollectAllowedImportsFromBuildMetadata(
@@ -445,13 +487,16 @@ already present in the provided output root.""")
module_metadata = list(
map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
- _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,
- module_metadata, allowed_imports)
+ _ParseMojoms(mojom_files, input_roots, output_root, module_roots,
+ args.enabled_features, module_metadata, allowed_imports)
logging.info('Finished')
- # Exit without running GC, which can save multiple seconds due the large
- # number of object created.
- os._exit(0)
if __name__ == '__main__':
Run(sys.argv[1:])
+ # Exit without running GC, which can save multiple seconds due to the large
+ # number of object created. But flush is necessary as os._exit doesn't do
+ # that.
+ sys.stdout.flush()
+ sys.stderr.flush()
+ os._exit(0)
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
index e213fbfa..f0ee6966 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
resolution, and module serialization and deserialization."""
def __init__(self, method_name):
- super(MojomParserTestCase, self).__init__(method_name)
+ super().__init__(method_name)
self._temp_dir = None
def setUp(self):
@@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):
self.ParseMojoms([filename])
m = self.LoadModule(filename)
definitions = {}
- for kinds in (m.enums, m.structs, m.unions, m.interfaces):
+ for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features):
for kind in kinds:
definitions[kind.mojom_name] = kind
return definitions
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
index a93f34ba..353a2b6e 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
@@ -1,7 +1,9 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import json
+
from mojom_parser_test_case import MojomParserTestCase
@@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
c = 'c.mojom'
c_metadata = 'out/c.build_metadata'
self.WriteFile(a_metadata,
- '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
+ json.dumps({
+ "sources": [self.GetPath(a)],
+ "deps": []
+ }))
self.WriteFile(
b_metadata,
- '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
- self.GetPath(a_metadata)))
+ json.dumps({
+ "sources": [self.GetPath(b)],
+ "deps": [self.GetPath(a_metadata)]
+ }))
self.WriteFile(
c_metadata,
- '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
- self.GetPath(b_metadata)))
+ json.dumps({
+ "sources": [self.GetPath(c)],
+ "deps": [self.GetPath(b_metadata)]
+ }))
self.WriteFile(a, """\
module a;
struct Bar {};""")
@@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
b = 'b.mojom'
b_metadata = 'out/b.build_metadata'
self.WriteFile(a_metadata,
- '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
+ json.dumps({
+ "sources": [self.GetPath(a)],
+ "deps": []
+ }))
self.WriteFile(b_metadata,
- '{"sources": ["%s"], "deps": []}\n' % self.GetPath(b))
+ json.dumps({
+ "sources": [self.GetPath(b)],
+ "deps": []
+ }))
self.WriteFile(a, """\
module a;
struct Bar {};""")
diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
index d45ec586..d10d69c6 100644
--- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
new file mode 100644
index 00000000..6b2525e5
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
@@ -0,0 +1,44 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class UnionTest(MojomParserTestCase):
+ """Tests union parsing behavior."""
+
+ def testExtensibleMustHaveDefault(self):
+ """Verifies that extensible unions must have a default field."""
+ mojom = 'foo.mojom'
+ self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')
+ with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'):
+ self.ParseMojoms([mojom])
+
+ def testExtensibleSingleDefault(self):
+ """Verifies that extensible unions must not have multiple default fields."""
+ mojom = 'foo.mojom'
+ self.WriteFile(
+ mojom, """\
+ module foo;
+ [Extensible] union U {
+ [Default] bool x;
+ [Default] bool y;
+ };
+ """)
+ with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'):
+ self.ParseMojoms([mojom])
+
+ def testExtensibleDefaultTypeValid(self):
+ """Verifies that an extensible union's default field must be nullable or
+ integral type."""
+ mojom = 'foo.mojom'
+ self.WriteFile(
+ mojom, """\
+ module foo;
+ [Extensible] union U {
+ [Default] handle<message_pipe> p;
+ };
+ """)
+ with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):
+ self.ParseMojoms([mojom])
diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
index 65db4dc9..45e45ec5 100644
--- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase):
checker = module.BackwardCompatibilityChecker()
compatibility_map = {}
- for name in old.keys():
- compatibility_map[name] = checker.IsBackwardCompatible(
- new[name], old[name])
+ for name in old:
+ try:
+ compatibility_map[name] = checker.IsBackwardCompatible(
+ new[name], old[name])
+ except Exception:
+ compatibility_map[name] = False
return compatibility_map
def assertBackwardCompatible(self, old_mojom, new_mojom):
@@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase):
"""Adding a value to an existing version is not allowed, even if the old
enum was marked [Extensible]. Note that it is irrelevant whether or not the
new enum is marked [Extensible]."""
- self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
- 'enum E { kFoo, kBar, kBaz };')
self.assertNotBackwardCompatible(
- '[Extensible] enum E { kFoo, kBar };',
- '[Extensible] enum E { kFoo, kBar, kBaz };')
+ '[Extensible] enum E { [Default] kFoo, kBar };',
+ 'enum E { kFoo, kBar, kBaz };')
+ self.assertNotBackwardCompatible(
+ '[Extensible] enum E { [Default] kFoo, kBar };',
+ '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
self.assertNotBackwardCompatible(
- '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',
+ '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
def testEnumValueRemoval(self):
"""Removal of an enum value is never valid even for [Extensible] enums."""
self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
'enum E { kFoo };')
- self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
- '[Extensible] enum E { kFoo };')
self.assertNotBackwardCompatible(
- '[Extensible] enum E { kA, [MinVersion=1] kB };',
- '[Extensible] enum E { kA, };')
+ '[Extensible] enum E { [Default] kFoo, kBar };',
+ '[Extensible] enum E { [Default] kFoo };')
+ self.assertNotBackwardCompatible(
+ '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
+ '[Extensible] enum E { [Default] kA, };')
self.assertNotBackwardCompatible(
- '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',
- '[Extensible] enum E { kA, [MinVersion=1] kB };')
+ """[Extensible] enum E {
+ [Default] kA,
+ [MinVersion=1] kB,
+ [MinVersion=1] kZ };""",
+ '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
def testNewExtensibleEnumValueWithMinVersion(self):
"""Adding a new and properly [MinVersion]'d value to an [Extensible] enum
is a backward-compatible change. Note that it is irrelevant whether or not
the new enum is marked [Extensible]."""
- self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',
+ self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',
'enum E { kA, kB, [MinVersion=1] kC };')
self.assertBackwardCompatible(
- '[Extensible] enum E { kA, kB };',
- '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')
+ '[Extensible] enum E { [Default] kA, kB };',
+ '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
self.assertBackwardCompatible(
- '[Extensible] enum E { kA, [MinVersion=1] kB };',
- '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')
+ '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
+ """[Extensible] enum E {
+ [Default] kA,
+ [MinVersion=1] kB,
+ [MinVersion=2] kC };""")
def testRenameEnumValue(self):
"""Renaming an enum value does not affect backward-compatibility. Only
@@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
'struct S {}; struct T { S s; };',
'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
self.assertBackwardCompatible(
- '[Extensible] enum E { kA }; struct S { E e; };',
- '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')
+ '[Extensible] enum E { [Default] kA }; struct S { E e; };',
+ """[Extensible] enum E {
+ [Default] kA,
+ [MinVersion=1] kB };
+ struct S { E e; };""")
self.assertNotBackwardCompatible(
'struct S {}; struct T { S s; };',
'struct S { int32 x; }; struct T { S s; };')
self.assertNotBackwardCompatible(
- '[Extensible] enum E { kA }; struct S { E e; };',
- '[Extensible] enum E { kA, kB }; struct S { E e; };')
+ '[Extensible] enum E { [Default] kA }; struct S { E e; };',
+ '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
def testNewStructFieldWithInvalidMinVersion(self):
"""Adding a new field using an existing MinVersion breaks backward-
@@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
'struct S {}; union U { S s; };',
'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
self.assertBackwardCompatible(
- '[Extensible] enum E { kA }; union U { E e; };',
- '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')
+ '[Extensible] enum E { [Default] kA }; union U { E e; };',
+ """[Extensible] enum E {
+ [Default] kA,
+ [MinVersion=1] kB };
+ union U { E e; };""")
self.assertNotBackwardCompatible(
'struct S {}; union U { S s; };',
'struct S { int32 x; }; union U { S s; };')
self.assertNotBackwardCompatible(
- '[Extensible] enum E { kA }; union U { E e; };',
- '[Extensible] enum E { kA, kB }; union U { E e; };')
+ '[Extensible] enum E { [Default] kA }; union U { E e; };',
+ '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
def testNewUnionFieldWithInvalidMinVersion(self):
"""Adding a new field using an existing MinVersion breaks backward-
diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
index b2010958..98bce18c 100755
--- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
+++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -8,11 +8,13 @@ import sys
_TOOLS_DIR = os.path.dirname(__file__)
_MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')
+_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')
_SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
os.path.pardir)
# Ensure that the mojom library is discoverable.
sys.path.append(_MOJOM_DIR)
+sys.path.append(_BINDINGS_DIR)
# Help Python find typ in //third_party/catapult/third_party/typ/
sys.path.append(
@@ -21,7 +23,7 @@ import typ
def Main():
- return typ.main(top_level_dir=_MOJOM_DIR)
+ return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
if __name__ == '__main__':
diff --git a/utils/ipc/parser.py b/utils/ipc/parser.py
index f46820fa..231a3266 100755
--- a/utils/ipc/parser.py
+++ b/utils/ipc/parser.py
@@ -13,7 +13,7 @@ import sys
sys.dont_write_bytecode = True
# Make sure that mojom_parser.py can import mojom
-sys.path.append(f'{os.path.dirname(__file__)}/mojo/public/tools/mojom')
+sys.path.insert(0, f'{os.path.dirname(__file__)}/mojo/public/tools/mojom')
import mojo.public.tools.mojom.mojom_parser as parser
diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README
index d5c24fc3..961cabd2 100644
--- a/utils/ipc/tools/README
+++ b/utils/ipc/tools/README
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0
-Files in this directory are imported from 9c138d992bfc of Chromium. Do not
+Files in this directory are imported from 9be4263648d7 of Chromium. Do not
modify them manually.
diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py
index 478fb8c1..40900d10 100644
--- a/utils/ipc/tools/diagnosis/crbug_1001171.py
+++ b/utils/ipc/tools/diagnosis/crbug_1001171.py
@@ -1,4 +1,4 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
+# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
diff --git a/utils/ipu3/ipu3-capture.sh b/utils/ipu3/ipu3-capture.sh
index ba6147b4..9294d025 100755
--- a/utils/ipu3/ipu3-capture.sh
+++ b/utils/ipu3/ipu3-capture.sh
@@ -63,7 +63,8 @@ parse_pipeline() {
if (sensor) {
gsub(\".*fmt:\", \"\");
gsub(\"[] ].*\", \"\");
- gsub(\"/\", \" \");
+ sub(\"/\", \" \");
+ sub(\"@[0-9]+/[0-9]+\", \"\");
format=\$0;
}
}
diff --git a/utils/ipu3/ipu3-pack.c b/utils/ipu3/ipu3-pack.c
new file mode 100644
index 00000000..23d2db8b
--- /dev/null
+++ b/utils/ipu3/ipu3-pack.c
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ipu3-pack - Convert unpacked RAW10 Bayer data to the IPU3 packed Bayer formats
+ *
+ * Copyright 2022 Umang Jain <umang.jain@ideasonboard.com>
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static void usage(char *argv0)
+{
+ printf("Usage: %s input-file output-file\n", basename(argv0));
+ printf("Convert unpacked RAW10 Bayer data to the IPU3 packed Bayer formats\n");
+ printf("If the output-file '-', output data will be written to standard output\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int in_fd;
+ int out_fd;
+ int ret;
+
+ if (argc != 3) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ in_fd = open(argv[1], O_RDONLY);
+ if (in_fd == -1) {
+ fprintf(stderr, "Failed to open input file '%s': %s\n",
+ argv[1], strerror(errno));
+ return 1;
+ }
+
+ if (strcmp(argv[2], "-") == 0) {
+ out_fd = STDOUT_FILENO;
+ } else {
+ out_fd = open(argv[2], O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ if (out_fd == -1) {
+ fprintf(stderr, "Failed to open output file '%s': %s\n",
+ argv[2], strerror(errno));
+ close(in_fd);
+ return 1;
+ }
+ }
+
+ while (1) {
+ uint16_t in_data[25];
+ uint8_t out_data[32];
+ unsigned int i;
+
+ ret = read(in_fd, in_data, sizeof(in_data));
+ if (ret < 0) {
+ fprintf(stderr, "Failed to read input data: %s\n",
+ strerror(errno));
+ goto done;
+ }
+
+ if ((unsigned)ret < sizeof(in_data)) {
+ if (ret != 0)
+ fprintf(stderr, "%u bytes of stray data at end of input\n",
+ ret);
+ goto done;
+ }
+
+ for (i = 0; i < 30; ++i) {
+ unsigned int index = (i * 8) / 10;
+ unsigned int msb_shift = (i * 8) % 10;
+ unsigned int lsb_shift = 10 - msb_shift;
+
+ out_data[i] = ((in_data[index] >> msb_shift) & 0xff)
+ | ((in_data[index+1] << lsb_shift) & 0xff);
+ }
+
+ out_data[30] = (in_data[24] >> 0) & 0xff;
+ out_data[31] = (in_data[24] >> 8) & 0x03;
+
+ ret = write(out_fd, out_data, sizeof(out_data));
+ if (ret < 0) {
+ fprintf(stderr, "Failed to write output data: %s\n",
+ strerror(errno));
+ goto done;
+ }
+ }
+
+done:
+ close(in_fd);
+ if (out_fd != STDOUT_FILENO)
+ close(out_fd);
+
+ return ret ? 1 : 0;
+}
diff --git a/utils/ipu3/ipu3-unpack.c b/utils/ipu3/ipu3-unpack.c
index 2dce1038..6ee8c45a 100644
--- a/utils/ipu3/ipu3-unpack.c
+++ b/utils/ipu3/ipu3-unpack.c
@@ -8,6 +8,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <libgen.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
@@ -15,7 +16,7 @@
#include <sys/types.h>
#include <unistd.h>
-static void usage(const char *argv0)
+static void usage(char *argv0)
{
printf("Usage: %s input-file output-file\n", basename(argv0));
printf("Unpack the IPU3 raw Bayer format to 16-bit Bayer\n");
@@ -78,8 +79,8 @@ int main(int argc, char *argv[])
}
ret = write(out_fd, out_data, 50);
- if (ret < -1) {
- fprintf(stderr, "Failed to read input data: %s\n",
+ if (ret == -1) {
+ fprintf(stderr, "Failed to write output data: %s\n",
strerror(errno));
goto done;
}
diff --git a/utils/ipu3/meson.build b/utils/ipu3/meson.build
index 88049f58..c92cc658 100644
--- a/utils/ipu3/meson.build
+++ b/utils/ipu3/meson.build
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: CC0-1.0
+ipu3_pack = executable('ipu3-pack', 'ipu3-pack.c')
ipu3_unpack = executable('ipu3-unpack', 'ipu3-unpack.c')
diff --git a/utils/raspberrypi/ctt/alsc_only.py b/utils/raspberrypi/ctt/alsc_only.py
new file mode 100755
index 00000000..7cd0ac01
--- /dev/null
+++ b/utils/raspberrypi/ctt/alsc_only.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2022, Raspberry Pi (Trading) Limited
+#
+# alsc_only.py - alsc tuning tool
+
+from ctt import *
+
+
+if __name__ == '__main__':
+ """
+ initialise calibration
+ """
+ if len(sys.argv) == 1:
+ print("""
+ Pisp Camera Tuning Tool version 1.0
+
+ Required Arguments:
+ '-i' : Calibration image directory.
+ '-o' : Name of output json file.
+
+ Optional Arguments:
+ '-c' : Config file for the CTT. If not passed, default parameters used.
+ '-l' : Name of output log file. If not passed, 'ctt_log.txt' used.
+ """)
+ quit(0)
+ else:
+ """
+ parse input arguments
+ """
+ json_output, directory, config, log_output = parse_input()
+ run_ctt(json_output, directory, config, log_output, alsc_only=True)
diff --git a/utils/raspberrypi/ctt/colors.py b/utils/raspberrypi/ctt/colors.py
new file mode 100644
index 00000000..1ab986d6
--- /dev/null
+++ b/utils/raspberrypi/ctt/colors.py
@@ -0,0 +1,30 @@
+# colors.py - Program to convert from RGB to LAB color space
+def RGB_to_LAB(RGB): # where RGB is a 1x3 array. e.g RGB = [100, 255, 230]
+ num = 0
+ XYZ = [0, 0, 0]
+ # converted all the three R, G, B to X, Y, Z
+ X = RGB[0] * 0.4124 + RGB[1] * 0.3576 + RGB[2] * 0.1805
+ Y = RGB[0] * 0.2126 + RGB[1] * 0.7152 + RGB[2] * 0.0722
+ Z = RGB[0] * 0.0193 + RGB[1] * 0.1192 + RGB[2] * 0.9505
+
+ XYZ[0] = X / 255 * 100
+ XYZ[1] = Y / 255 * 100 # XYZ Must be in range 0 -> 100, so scale down from 255
+ XYZ[2] = Z / 255 * 100
+ XYZ[0] = XYZ[0] / 95.047 # ref_X = 95.047 Observer= 2°, Illuminant= D65
+ XYZ[1] = XYZ[1] / 100.0 # ref_Y = 100.000
+ XYZ[2] = XYZ[2] / 108.883 # ref_Z = 108.883
+ num = 0
+ for value in XYZ:
+ if value > 0.008856:
+ value = value ** (0.3333333333333333)
+ else:
+ value = (7.787 * value) + (16 / 116)
+ XYZ[num] = value
+ num = num + 1
+
+ # L, A, B, values calculated below
+ L = (116 * XYZ[1]) - 16
+ a = 500 * (XYZ[0] - XYZ[1])
+ b = 200 * (XYZ[1] - XYZ[2])
+
+ return [L, a, b]
diff --git a/utils/raspberrypi/ctt/convert_tuning.py b/utils/raspberrypi/ctt/convert_tuning.py
new file mode 100755
index 00000000..f4504d45
--- /dev/null
+++ b/utils/raspberrypi/ctt/convert_tuning.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Script to convert version 1.0 Raspberry Pi camera tuning files to version 2.0.
+#
+# Copyright 2022 Raspberry Pi Ltd
+
+import argparse
+import json
+import sys
+
+from ctt_pretty_print_json import pretty_print
+
+
+def convert_v2(in_json: dict) -> str:
+
+ if 'version' in in_json.keys() and in_json['version'] != 1.0:
+ print(f'The JSON config reports version {in_json["version"]} that is incompatible with this tool.')
+ sys.exit(-1)
+
+ converted = {
+ 'version': 2.0,
+ 'target': 'bcm2835',
+ 'algorithms': [{algo: config} for algo, config in in_json.items()]
+ }
+
+ return pretty_print(converted)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=
+ 'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0.\n')
+ parser.add_argument('input', type=str, help='Input tuning file.')
+ parser.add_argument('output', type=str, nargs='?',
+ help='Output converted tuning file. If not provided, the input file will be updated in-place.',
+ default=None)
+ args = parser.parse_args()
+
+ with open(args.input, 'r') as f:
+ in_json = json.load(f)
+
+ out_json = convert_v2(in_json)
+
+ with open(args.output if args.output is not None else args.input, 'w') as f:
+ f.write(out_json)
diff --git a/utils/raspberrypi/ctt/ctt.py b/utils/raspberrypi/ctt/ctt.py
index 15064634..89159e63 100755
--- a/utils/raspberrypi/ctt/ctt.py
+++ b/utils/raspberrypi/ctt/ctt.py
@@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt.py - camera tuning tool
@@ -15,7 +15,7 @@ from ctt_alsc import *
from ctt_lux import *
from ctt_noise import *
from ctt_geq import *
-from ctt_pretty_print_json import *
+from ctt_pretty_print_json import pretty_print
import random
import json
import re
@@ -350,7 +350,7 @@ class Camera:
alsc_out = alsc_all(self, do_alsc_colour, plot)
cal_cr_list, cal_cb_list, luminance_lut, av_corn = alsc_out
"""
- write ouput to json and finish if not do_alsc_colour
+ write output to json and finish if not do_alsc_colour
"""
if not do_alsc_colour:
self.json['rpi.alsc']['luminance_lut'] = luminance_lut
@@ -511,13 +511,17 @@ class Camera:
"""
def write_json(self):
"""
- Write json dictionary to file
+ Write json dictionary to file using our version 2 format
"""
- jstring = json.dumps(self.json, sort_keys=False)
- """
- make it pretty :)
- """
- pretty_print_json(jstring, self.jf)
+
+ out_json = {
+ "version": 2.0,
+ 'target': 'bcm2835',
+ "algorithms": [{name: data} for name, data in self.json.items()],
+ }
+
+ with open(self.jf, 'w') as f:
+ f.write(pretty_print(out_json))
"""
add a new section to the log file
@@ -664,7 +668,7 @@ class Camera:
- incorrect filename/extension
- images from different cameras
"""
- def check_imgs(self):
+ def check_imgs(self, macbeth=True):
self.log += '\n\nImages found:'
self.log += '\nMacbeth : {}'.format(len(self.imgs))
self.log += '\nALSC : {} '.format(len(self.imgs_alsc))
@@ -672,10 +676,14 @@ class Camera:
"""
check usable images found
"""
- if len(self.imgs) == 0:
+ if len(self.imgs) == 0 and macbeth:
print('\nERROR: No usable macbeth chart images found')
self.log += '\nERROR: No usable macbeth chart images found'
return 0
+ elif len(self.imgs) == 0 and len(self.imgs_alsc) == 0:
+ print('\nERROR: No usable images found')
+ self.log += '\nERROR: No usable images found'
+ return 0
"""
Double check that every image has come from the same camera...
"""
@@ -704,7 +712,7 @@ class Camera:
return 0
-def run_ctt(json_output, directory, config, log_output):
+def run_ctt(json_output, directory, config, log_output, alsc_only=False):
"""
check input files are jsons
"""
@@ -766,6 +774,8 @@ def run_ctt(json_output, directory, config, log_output):
try:
Cam = Camera(json_output)
Cam.log_user_input(json_output, directory, config, log_output)
+ if alsc_only:
+ disable = set(Cam.json.keys()).symmetric_difference({"rpi.alsc"})
Cam.disable = disable
Cam.plot = plot
Cam.add_imgs(directory, mac_config, blacklevel)
@@ -779,8 +789,9 @@ def run_ctt(json_output, directory, config, log_output):
ccm also technically does an awb but it measures this from the macbeth
chart in the image rather than using calibration data
"""
- if Cam.check_imgs():
- Cam.json['rpi.black_level']['black_level'] = Cam.blacklevel_16
+ if Cam.check_imgs(macbeth=not alsc_only):
+ if not alsc_only:
+ Cam.json['rpi.black_level']['black_level'] = Cam.blacklevel_16
Cam.json_remove(disable)
print('\nSTARTING CALIBRATIONS')
Cam.alsc_cal(luminance_strength, do_alsc_colour)
diff --git a/utils/raspberrypi/ctt/ctt_alsc.py b/utils/raspberrypi/ctt/ctt_alsc.py
index 89e86469..e51d6931 100644
--- a/utils/raspberrypi/ctt/ctt_alsc.py
+++ b/utils/raspberrypi/ctt/ctt_alsc.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_alsc.py - camera tuning tool for ALSC (auto lens shading correction)
@@ -132,7 +132,7 @@ def alsc(Cam, Img, do_alsc_colour, plot=False):
"""
average the green channels into one
"""
- av_ch_g = np.mean((channels[1:2]), axis=0)
+ av_ch_g = np.mean((channels[1:3]), axis=0)
if do_alsc_colour:
"""
obtain 16x12 grid of intensities for each channel and subtract black level
diff --git a/utils/raspberrypi/ctt/ctt_awb.py b/utils/raspberrypi/ctt/ctt_awb.py
index 3c8cd902..bf45e54d 100644
--- a/utils/raspberrypi/ctt/ctt_awb.py
+++ b/utils/raspberrypi/ctt/ctt_awb.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_awb.py - camera tuning tool for AWB
diff --git a/utils/raspberrypi/ctt/ctt_ccm.py b/utils/raspberrypi/ctt/ctt_ccm.py
index cebecfc2..a09bfd09 100644
--- a/utils/raspberrypi/ctt/ctt_ccm.py
+++ b/utils/raspberrypi/ctt/ctt_ccm.py
@@ -1,32 +1,68 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_ccm.py - camera tuning tool for CCM (colour correction matrix)
from ctt_image_load import *
from ctt_awb import get_alsc_patches
-
-
+import colors
+from scipy.optimize import minimize
+from ctt_visualise import visualise_macbeth_chart
+import numpy as np
"""
takes 8-bit macbeth chart values, degammas and returns 16 bit
"""
+
+'''
+This program has many options from which to derive the color matrix from.
+The first is average. This minimises the average delta E across all patches of
+the macbeth chart. Testing across all cameras yeilded this as the most color
+accurate and vivid. Other options are avalible however.
+Maximum minimises the maximum Delta E of the patches. It iterates through till
+a minimum maximum is found (so that there is
+not one patch that deviates wildly.)
+This yields generally good results but overall the colors are less accurate
+Have a fiddle with maximum and see what you think.
+The final option allows you to select the patches for which to average across.
+This means that you can bias certain patches, for instance if you want the
+reds to be more accurate.
+'''
+
+matrix_selection_types = ["average", "maximum", "patches"]
+typenum = 0 # select from array above, 0 = average, 1 = maximum, 2 = patches
+test_patches = [1, 2, 5, 8, 9, 12, 14]
+
+'''
+Enter patches to test for. Can also be entered twice if you
+would like twice as much bias on one patch.
+'''
+
+
def degamma(x):
- x = x / ((2**8)-1)
- x = np.where(x < 0.04045, x/12.92, ((x+0.055)/1.055)**2.4)
- x = x * ((2**16)-1)
+ x = x / ((2 ** 8) - 1) # takes 255 and scales it down to one
+ x = np.where(x < 0.04045, x / 12.92, ((x + 0.055) / 1.055) ** 2.4)
+ x = x * ((2 ** 16) - 1) # takes one and scales up to 65535, 16 bit color
return x
+def gamma(x):
+ # Take 3 long array of color values and gamma them
+ return [((colour / 255) ** (1 / 2.4) * 1.055 - 0.055) * 255 for colour in x]
+
+
"""
FInds colour correction matrices for list of images
"""
+
+
def ccm(Cam, cal_cr_list, cal_cb_list):
+ global matrix_selection_types, typenum
imgs = Cam.imgs
"""
standard macbeth chart colour values
"""
- m_rgb = np.array([ # these are in sRGB
+ m_rgb = np.array([ # these are in RGB
[116, 81, 67], # dark skin
[199, 147, 129], # light skin
[91, 122, 156], # blue sky
@@ -34,7 +70,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
[130, 128, 176], # blue flower
[92, 190, 172], # bluish green
[224, 124, 47], # orange
- [68, 91, 170], # purplish blue
+ [68, 91, 170], # purplish blue
[198, 82, 97], # moderate red
[94, 58, 106], # purple
[159, 189, 63], # yellow green
@@ -52,16 +88,20 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
[82, 84, 86], # neutral 3.5
[49, 49, 51] # black 2
])
-
"""
convert reference colours from srgb to rgb
"""
- m_srgb = degamma(m_rgb)
+ m_srgb = degamma(m_rgb) # now in 16 bit color.
+
+ # Produce array of LAB values for ideal color chart
+ m_lab = [colors.RGB_to_LAB(color / 256) for color in m_srgb]
+
"""
reorder reference values to match how patches are ordered
"""
m_srgb = np.array([m_srgb[i::6] for i in range(6)]).reshape((24, 3))
-
+ m_lab = np.array([m_lab[i::6] for i in range(6)]).reshape((24, 3))
+ m_rgb = np.array([m_rgb[i::6] for i in range(6)]).reshape((24, 3))
"""
reformat alsc correction tables or set colour_cals to None if alsc is
deactivated
@@ -76,8 +116,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
"""
normalise tables so min value is 1
"""
- cr_tab = cr_tab/np.min(cr_tab)
- cb_tab = cb_tab/np.min(cb_tab)
+ cr_tab = cr_tab / np.min(cr_tab)
+ cb_tab = cb_tab / np.min(cb_tab)
colour_cals[cr['ct']] = [cr_tab, cb_tab]
"""
@@ -94,6 +134,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
the function will simply return the macbeth patches
"""
r, b, g = get_alsc_patches(Img, colour_cals, grey=False)
+ # 256 values for each patch of sRGB values
+
"""
do awb
Note: awb is done by measuring the macbeth chart in the image, rather
@@ -101,34 +143,123 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
and the ccm matrices will be more accurate.
"""
r_greys, b_greys, g_greys = r[3::4], b[3::4], g[3::4]
- r_g = np.mean(r_greys/g_greys)
- b_g = np.mean(b_greys/g_greys)
+ r_g = np.mean(r_greys / g_greys)
+ b_g = np.mean(b_greys / g_greys)
r = r / r_g
b = b / b_g
-
"""
normalise brightness wrt reference macbeth colours and then average
each channel for each patch
"""
- gain = np.mean(m_srgb)/np.mean((r, g, b))
+ gain = np.mean(m_srgb) / np.mean((r, g, b))
Cam.log += '\nGain with respect to standard colours: {:.3f}'.format(gain)
- r = np.mean(gain*r, axis=1)
- b = np.mean(gain*b, axis=1)
- g = np.mean(gain*g, axis=1)
-
+ r = np.mean(gain * r, axis=1)
+ b = np.mean(gain * b, axis=1)
+ g = np.mean(gain * g, axis=1)
"""
calculate ccm matrix
"""
+ # ==== All of below should in sRGB ===##
+ sumde = 0
ccm = do_ccm(r, g, b, m_srgb)
+ # This is the initial guess that our optimisation code works with.
+ original_ccm = ccm
+ r1 = ccm[0]
+ r2 = ccm[1]
+ g1 = ccm[3]
+ g2 = ccm[4]
+ b1 = ccm[6]
+ b2 = ccm[7]
+ '''
+ COLOR MATRIX LOOKS AS BELOW
+ R1 R2 R3 Rval Outr
+ G1 G2 G3 * Gval = G
+ B1 B2 B3 Bval B
+ Will be optimising 6 elements and working out the third element using 1-r1-r2 = r3
+ '''
+
+ x0 = [r1, r2, g1, g2, b1, b2]
+ '''
+ We use our old CCM as the initial guess for the program to find the
+ optimised matrix
+ '''
+ result = minimize(guess, x0, args=(r, g, b, m_lab), tol=0.01)
+ '''
+ This produces a color matrix which has the lowest delta E possible,
+ based off the input data. Note it is impossible for this to reach
+ zero since the input data is imperfect
+ '''
+
+ Cam.log += ("\n \n Optimised Matrix Below: \n \n")
+ [r1, r2, g1, g2, b1, b2] = result.x
+ # The new, optimised color correction matrix values
+ optimised_ccm = [r1, r2, (1 - r1 - r2), g1, g2, (1 - g1 - g2), b1, b2, (1 - b1 - b2)]
+
+ # This is the optimised Color Matrix (preserving greys by summing rows up to 1)
+ Cam.log += str(optimised_ccm)
+ Cam.log += "\n Old Color Correction Matrix Below \n"
+ Cam.log += str(ccm)
+
+ formatted_ccm = np.array(original_ccm).reshape((3, 3))
+
+ '''
+ below is a whole load of code that then applies the latest color
+ matrix, and returns LAB values for color. This can then be used
+ to calculate the final delta E
+ '''
+ optimised_ccm_rgb = [] # Original Color Corrected Matrix RGB / LAB
+ optimised_ccm_lab = []
+
+ formatted_optimised_ccm = np.array(optimised_ccm).reshape((3, 3))
+ after_gamma_rgb = []
+ after_gamma_lab = []
+
+ for RGB in zip(r, g, b):
+ ccm_applied_rgb = np.dot(formatted_ccm, (np.array(RGB) / 256))
+ optimised_ccm_rgb.append(gamma(ccm_applied_rgb))
+ optimised_ccm_lab.append(colors.RGB_to_LAB(ccm_applied_rgb))
+
+ optimised_ccm_applied_rgb = np.dot(formatted_optimised_ccm, np.array(RGB) / 256)
+ after_gamma_rgb.append(gamma(optimised_ccm_applied_rgb))
+ after_gamma_lab.append(colors.RGB_to_LAB(optimised_ccm_applied_rgb))
+ '''
+ Gamma After RGB / LAB - not used in calculations, only used for visualisation
+ We now want to spit out some data that shows
+ how the optimisation has improved the color matrices
+ '''
+ Cam.log += "Here are the Improvements"
+
+ # CALCULATE WORST CASE delta e
+ old_worst_delta_e = 0
+ before_average = transform_and_evaluate(formatted_ccm, r, g, b, m_lab)
+ new_worst_delta_e = 0
+ after_average = transform_and_evaluate(formatted_optimised_ccm, r, g, b, m_lab)
+ for i in range(24):
+ old_delta_e = deltae(optimised_ccm_lab[i], m_lab[i]) # Current Old Delta E
+ new_delta_e = deltae(after_gamma_lab[i], m_lab[i]) # Current New Delta E
+ if old_delta_e > old_worst_delta_e:
+ old_worst_delta_e = old_delta_e
+ if new_delta_e > new_worst_delta_e:
+ new_worst_delta_e = new_delta_e
+
+ Cam.log += "Before color correction matrix was optimised, we got an average delta E of " + str(before_average) + " and a maximum delta E of " + str(old_worst_delta_e)
+ Cam.log += "After color correction matrix was optimised, we got an average delta E of " + str(after_average) + " and a maximum delta E of " + str(new_worst_delta_e)
+
+ visualise_macbeth_chart(m_rgb, optimised_ccm_rgb, after_gamma_rgb, str(Img.col) + str(matrix_selection_types[typenum]))
+ '''
+ The program will also save some visualisations of improvements.
+ Very pretty to look at. Top rectangle is ideal, Left square is
+ before optimisation, right square is after.
+ '''
"""
if a ccm has already been calculated for that temperature then don't
overwrite but save both. They will then be averaged later on
- """
+ """ # Now going to use optimised color matrix, optimised_ccm
if Img.col in ccm_tab.keys():
- ccm_tab[Img.col].append(ccm)
+ ccm_tab[Img.col].append(optimised_ccm)
else:
- ccm_tab[Img.col] = [ccm]
+ ccm_tab[Img.col] = [optimised_ccm]
Cam.log += '\n'
Cam.log += '\nFinished processing images'
@@ -137,8 +268,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
"""
for k, v in ccm_tab.items():
tab = np.mean(v, axis=0)
- tab = np.where((10000*tab) % 1 <= 0.05, tab+0.00001, tab)
- tab = np.where((10000*tab) % 1 >= 0.95, tab-0.00001, tab)
+ tab = np.where((10000 * tab) % 1 <= 0.05, tab + 0.00001, tab)
+ tab = np.where((10000 * tab) % 1 >= 0.95, tab - 0.00001, tab)
ccm_tab[k] = list(np.round(tab, 5))
Cam.log += '\nMatrix calculated for colour temperature of {} K'.format(k)
@@ -156,20 +287,65 @@ def ccm(Cam, cal_cr_list, cal_cb_list):
return ccms
+def guess(x0, r, g, b, m_lab): # provides a method of numerical feedback for the optimisation code
+ [r1, r2, g1, g2, b1, b2] = x0
+ ccm = np.array([r1, r2, (1 - r1 - r2),
+ g1, g2, (1 - g1 - g2),
+ b1, b2, (1 - b1 - b2)]).reshape((3, 3)) # format the matrix correctly
+ return transform_and_evaluate(ccm, r, g, b, m_lab)
+
+
+def transform_and_evaluate(ccm, r, g, b, m_lab): # Transforms colors to LAB and applies the correction matrix
+ # create list of matrix changed colors
+ realrgb = []
+ for RGB in zip(r, g, b):
+ rgb_post_ccm = np.dot(ccm, np.array(RGB) / 256) # This is RGB values after the color correction matrix has been applied
+ realrgb.append(colors.RGB_to_LAB(rgb_post_ccm))
+ # now compare that with m_lab and return numeric result, averaged for each patch
+ return (sumde(realrgb, m_lab) / 24) # returns an average result of delta E
+
+
+def sumde(listA, listB):
+ global typenum, test_patches
+ sumde = 0
+ maxde = 0
+ patchde = [] # Create array of the delta E values for each patch. useful for optimisation of certain patches
+ for listA_item, listB_item in zip(listA, listB):
+ if maxde < (deltae(listA_item, listB_item)):
+ maxde = deltae(listA_item, listB_item)
+ patchde.append(deltae(listA_item, listB_item))
+ sumde += deltae(listA_item, listB_item)
+ '''
+ The different options specified at the start allow for
+ the maximum to be returned, average or specific patches
+ '''
+ if typenum == 0:
+ return sumde
+ if typenum == 1:
+ return maxde
+ if typenum == 2:
+ output = sum([patchde[test_patch] for test_patch in test_patches])
+ # Selects only certain patches and returns the output for them
+ return output
+
+
"""
calculates the ccm for an individual image.
-ccms are calculate in rgb space, and are fit by hand. Although it is a 3x3
+ccms are calculated in rgb space, and are fit by hand. Although it is a 3x3
matrix, each row must add up to 1 in order to conserve greyness, simplifying
calculation.
-Should you want to fit them in another space (e.g. LAB) we wish you the best of
-luck and send us the code when you are done! :-)
+The initial CCM is calculated in RGB, and then optimised in LAB color space
+This simplifies the initial calculation but then gets us the accuracy of
+using LAB color space.
"""
+
+
def do_ccm(r, g, b, m_srgb):
rb = r-b
gb = g-b
- rb_2s = (rb*rb)
- rb_gbs = (rb*gb)
- gb_2s = (gb*gb)
+ rb_2s = (rb * rb)
+ rb_gbs = (rb * gb)
+ gb_2s = (gb * gb)
r_rbs = rb * (m_srgb[..., 0] - b)
r_gbs = gb * (m_srgb[..., 0] - b)
@@ -191,7 +367,7 @@ def do_ccm(r, g, b, m_srgb):
b_rb = np.sum(b_rbs)
b_gb = np.sum(b_gbs)
- det = rb_2*gb_2 - rb_gb*rb_gb
+ det = rb_2 * gb_2 - rb_gb * rb_gb
"""
Raise error if matrix is singular...
@@ -201,19 +377,19 @@ def do_ccm(r, g, b, m_srgb):
if det < 0.001:
raise ArithmeticError
- r_a = (gb_2*r_rb - rb_gb*r_gb)/det
- r_b = (rb_2*r_gb - rb_gb*r_rb)/det
+ r_a = (gb_2 * r_rb - rb_gb * r_gb) / det
+ r_b = (rb_2 * r_gb - rb_gb * r_rb) / det
"""
Last row can be calculated by knowing the sum must be 1
"""
r_c = 1 - r_a - r_b
- g_a = (gb_2*g_rb - rb_gb*g_gb)/det
- g_b = (rb_2*g_gb - rb_gb*g_rb)/det
+ g_a = (gb_2 * g_rb - rb_gb * g_gb) / det
+ g_b = (rb_2 * g_gb - rb_gb * g_rb) / det
g_c = 1 - g_a - g_b
- b_a = (gb_2*b_rb - rb_gb*b_gb)/det
- b_b = (rb_2*b_gb - rb_gb*b_rb)/det
+ b_a = (gb_2 * b_rb - rb_gb * b_gb) / det
+ b_b = (rb_2 * b_gb - rb_gb * b_rb) / det
b_c = 1 - b_a - b_b
"""
@@ -222,3 +398,9 @@ def do_ccm(r, g, b, m_srgb):
ccm = [r_a, r_b, r_c, g_a, g_b, g_c, b_a, b_b, b_c]
return ccm
+
+
+def deltae(colorA, colorB):
+ return ((colorA[0] - colorB[0]) ** 2 + (colorA[1] - colorB[1]) ** 2 + (colorA[2] - colorB[2]) ** 2) ** 0.5
+ # return ((colorA[1]-colorB[1]) * * 2 + (colorA[2]-colorB[2]) * * 2) * * 0.5
+ # UNCOMMENT IF YOU WANT TO NEGLECT LUMINANCE FROM CALCULATION OF DELTA E
diff --git a/utils/raspberrypi/ctt/ctt_geq.py b/utils/raspberrypi/ctt/ctt_geq.py
index 2aa668f1..c45addcd 100644
--- a/utils/raspberrypi/ctt/ctt_geq.py
+++ b/utils/raspberrypi/ctt/ctt_geq.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_geq.py - camera tuning tool for GEQ (green equalisation)
diff --git a/utils/raspberrypi/ctt/ctt_image_load.py b/utils/raspberrypi/ctt/ctt_image_load.py
index 66adb237..310c5e88 100644
--- a/utils/raspberrypi/ctt/ctt_image_load.py
+++ b/utils/raspberrypi/ctt/ctt_image_load.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019-2020, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019-2020, Raspberry Pi Ltd
#
# ctt_image_load.py - camera tuning tool image loading
@@ -301,17 +301,35 @@ def dng_load_image(Cam, im_str):
metadata.read()
Img.ver = 100 # random value
- Img.w = metadata['Exif.SubImage1.ImageWidth'].value
+ """
+ The DNG and TIFF/EP specifications use different IFDs to store the raw
+ image data and the Exif tags. DNG stores them in a SubIFD and in an Exif
+ IFD respectively (named "SubImage1" and "Photo" by pyexiv2), while
+ TIFF/EP stores them both in IFD0 (name "Image"). Both are used in "DNG"
+ files, with libcamera-apps following the DNG recommendation and
+ applications based on picamera2 following TIFF/EP.
+
+ This code detects which tags are being used, and therefore extracts the
+ correct values.
+ """
+ try:
+ Img.w = metadata['Exif.SubImage1.ImageWidth'].value
+ subimage = "SubImage1"
+ photo = "Photo"
+ except KeyError:
+ Img.w = metadata['Exif.Image.ImageWidth'].value
+ subimage = "Image"
+ photo = "Image"
Img.pad = 0
- Img.h = metadata['Exif.SubImage1.ImageLength'].value
- white = metadata['Exif.SubImage1.WhiteLevel'].value
+ Img.h = metadata[f'Exif.{subimage}.ImageLength'].value
+ white = metadata[f'Exif.{subimage}.WhiteLevel'].value
Img.sigbits = int(white).bit_length()
Img.fmt = (Img.sigbits - 4) // 2
- Img.exposure = int(metadata['Exif.Photo.ExposureTime'].value*1000000)
- Img.againQ8 = metadata['Exif.Photo.ISOSpeedRatings'].value*256/100
+ Img.exposure = int(metadata[f'Exif.{photo}.ExposureTime'].value * 1000000)
+ Img.againQ8 = metadata[f'Exif.{photo}.ISOSpeedRatings'].value * 256 / 100
Img.againQ8_norm = Img.againQ8 / 256
Img.camName = metadata['Exif.Image.Model'].value
- Img.blacklevel = int(metadata['Exif.SubImage1.BlackLevel'].value[0])
+ Img.blacklevel = int(metadata[f'Exif.{subimage}.BlackLevel'].value[0])
Img.blacklevel_16 = Img.blacklevel << (16 - Img.sigbits)
bayer_case = {
'0 1 1 2': (0, (0, 1, 2, 3)),
@@ -319,7 +337,7 @@ def dng_load_image(Cam, im_str):
'2 1 1 0': (2, (3, 2, 1, 0)),
'1 0 2 1': (3, (1, 0, 3, 2))
}
- cfa_pattern = metadata['Exif.SubImage1.CFAPattern'].value
+ cfa_pattern = metadata[f'Exif.{subimage}.CFAPattern'].value
Img.pattern = bayer_case[cfa_pattern][0]
Img.order = bayer_case[cfa_pattern][1]
@@ -358,6 +376,11 @@ def load_image(Cam, im_str, mac_config=None, show=False, mac=True, show_meta=Fal
Img = dng_load_image(Cam, im_str)
else:
Img = brcm_load_image(Cam, im_str)
+ """
+ handle errors smoothly if loading image failed
+ """
+ if Img == 0:
+ return 0
if show_meta:
Img.print_meta()
diff --git a/utils/raspberrypi/ctt/ctt_lux.py b/utils/raspberrypi/ctt/ctt_lux.py
index 4e7785ef..70855e1b 100644
--- a/utils/raspberrypi/ctt/ctt_lux.py
+++ b/utils/raspberrypi/ctt/ctt_lux.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_lux.py - camera tuning tool for lux level
diff --git a/utils/raspberrypi/ctt/ctt_macbeth_locator.py b/utils/raspberrypi/ctt/ctt_macbeth_locator.py
index cae1d334..178aeed0 100644
--- a/utils/raspberrypi/ctt/ctt_macbeth_locator.py
+++ b/utils/raspberrypi/ctt/ctt_macbeth_locator.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_macbeth_locator.py - camera tuning tool Macbeth chart locator
@@ -57,6 +57,10 @@ def find_macbeth(Cam, img, mac_config=(0, 0)):
"""
cor, mac, coords, msg = get_macbeth_chart(img, ref_data)
+ # Keep a list that will include this and any brightened up versions of
+ # the image for reuse.
+ all_images = [img]
+
"""
following bits of code tries to fix common problems with simple
techniques.
@@ -71,6 +75,7 @@ def find_macbeth(Cam, img, mac_config=(0, 0)):
if cor < 0.75:
a = 2
img_br = cv2.convertScaleAbs(img, alpha=a, beta=0)
+ all_images.append(img_br)
cor_b, mac_b, coords_b, msg_b = get_macbeth_chart(img_br, ref_data)
if cor_b > cor:
cor, mac, coords, msg = cor_b, mac_b, coords_b, msg_b
@@ -81,6 +86,7 @@ def find_macbeth(Cam, img, mac_config=(0, 0)):
if cor < 0.75:
a = 4
img_br = cv2.convertScaleAbs(img, alpha=a, beta=0)
+ all_images.append(img_br)
cor_b, mac_b, coords_b, msg_b = get_macbeth_chart(img_br, ref_data)
if cor_b > cor:
cor, mac, coords, msg = cor_b, mac_b, coords_b, msg_b
@@ -128,23 +134,26 @@ def find_macbeth(Cam, img, mac_config=(0, 0)):
h_inc = int(h/6)
"""
for each subselection, look for a macbeth chart
+ loop over this and any brightened up images that we made to increase the
+ likelihood of success
"""
- for i in range(3):
- for j in range(3):
- w_s, h_s = i*w_inc, j*h_inc
- img_sel = img[w_s:w_s+w_sel, h_s:h_s+h_sel]
- cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data)
- """
- if the correlation is better than the best then record the
- scale and current subselection at which macbeth chart was
- found. Also record the coordinates, macbeth chart and message.
- """
- if cor_ij > cor:
- cor = cor_ij
- mac, coords, msg = mac_ij, coords_ij, msg_ij
- ii, jj = i, j
- w_best, h_best = w_inc, h_inc
- d_best = 1
+ for img_br in all_images:
+ for i in range(3):
+ for j in range(3):
+ w_s, h_s = i*w_inc, j*h_inc
+ img_sel = img_br[w_s:w_s+w_sel, h_s:h_s+h_sel]
+ cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data)
+ """
+ if the correlation is better than the best then record the
+ scale and current subselection at which macbeth chart was
+ found. Also record the coordinates, macbeth chart and message.
+ """
+ if cor_ij > cor:
+ cor = cor_ij
+ mac, coords, msg = mac_ij, coords_ij, msg_ij
+ ii, jj = i, j
+ w_best, h_best = w_inc, h_inc
+ d_best = 1
"""
scale 2
@@ -157,17 +166,19 @@ def find_macbeth(Cam, img, mac_config=(0, 0)):
h_sel = int(h/2)
w_inc = int(w/8)
h_inc = int(h/8)
- for i in range(5):
- for j in range(5):
- w_s, h_s = i*w_inc, j*h_inc
- img_sel = img[w_s:w_s+w_sel, h_s:h_s+h_sel]
- cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data)
- if cor_ij > cor:
- cor = cor_ij
- mac, coords, msg = mac_ij, coords_ij, msg_ij
- ii, jj = i, j
- w_best, h_best = w_inc, h_inc
- d_best = 2
+ # Again, loop over any brightened up images as well
+ for img_br in all_images:
+ for i in range(5):
+ for j in range(5):
+ w_s, h_s = i*w_inc, j*h_inc
+ img_sel = img_br[w_s:w_s+w_sel, h_s:h_s+h_sel]
+ cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data)
+ if cor_ij > cor:
+ cor = cor_ij
+ mac, coords, msg = mac_ij, coords_ij, msg_ij
+ ii, jj = i, j
+ w_best, h_best = w_inc, h_inc
+ d_best = 2
"""
The following code checks for macbeth charts at even smaller scales. This
@@ -238,7 +249,7 @@ def find_macbeth(Cam, img, mac_config=(0, 0)):
print error or success message
"""
print(msg)
- Cam.log += '\n' + msg
+ Cam.log += '\n' + str(msg)
if msg == success_msg:
coords_fit = coords
Cam.log += '\nMacbeth chart vertices:\n'
@@ -606,7 +617,7 @@ def get_macbeth_chart(img, ref_data):
'\nNot enough squares found'
'\nPossible problems:\n'
'- Macbeth chart is occluded\n'
- '- Macbeth chart is too dark of bright\n'
+ '- Macbeth chart is too dark or bright\n'
)
ref_cents = np.array(ref_cents)
diff --git a/utils/raspberrypi/ctt/ctt_noise.py b/utils/raspberrypi/ctt/ctt_noise.py
index 0afcf8f8..3270bf34 100644
--- a/utils/raspberrypi/ctt/ctt_noise.py
+++ b/utils/raspberrypi/ctt/ctt_noise.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_noise.py - camera tuning tool noise calibration
diff --git a/utils/raspberrypi/ctt/ctt_pretty_print_json.py b/utils/raspberrypi/ctt/ctt_pretty_print_json.py
index d38ae617..3e3b8475 100644..100755
--- a/utils/raspberrypi/ctt/ctt_pretty_print_json.py
+++ b/utils/raspberrypi/ctt/ctt_pretty_print_json.py
@@ -1,106 +1,116 @@
+#!/usr/bin/env python3
+#
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright 2022 Raspberry Pi Ltd
#
-# ctt_pretty_print_json.py - camera tuning tool JSON formatter
-
-import sys
-
-
-class JSONPrettyPrinter(object):
- """
- Take a collapsed JSON file and make it more readable
- """
- def __init__(self, fout):
- self.state = {
- "indent": 0,
- "inarray": [False],
- "arraycount": [],
- "skipnewline": True,
- "need_indent": False,
- "need_space": False,
+# Script to pretty print a Raspberry Pi tuning config JSON structure in
+# version 2.0 and later formats.
+
+import argparse
+import json
+import textwrap
+
+
+class Encoder(json.JSONEncoder):
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.indentation_level = 0
+ self.hard_break = 120
+ self.custom_elems = {
+ 'table': 16,
+ 'luminance_lut': 16,
+ 'ct_curve': 3,
+ 'ccm': 3,
+ 'gamma_curve': 2,
+ 'y_target': 2,
+ 'prior': 2
}
- self.fout = fout
-
- def newline(self):
- if not self.state["skipnewline"]:
- self.fout.write('\n')
- self.state["need_indent"] = True
- self.state["need_space"] = False
- self.state["skipnewline"] = True
-
- def write(self, c):
- if self.state["need_indent"]:
- self.fout.write(' ' * self.state["indent"] * 4)
- self.state["need_indent"] = False
- if self.state["need_space"]:
- self.fout.write(' ')
- self.state["need_space"] = False
- self.fout.write(c)
- self.state["skipnewline"] = False
-
- def process_char(self, c):
- if c == '{':
- self.newline()
- self.write(c)
- self.state["indent"] += 1
- self.newline()
- elif c == '}':
- self.state["indent"] -= 1
- self.newline()
- self.write(c)
- elif c == '[':
- self.newline()
- self.write(c)
- self.state["indent"] += 1
- self.newline()
- self.state["inarray"] = [True] + self.state["inarray"]
- self.state["arraycount"] = [0] + self.state["arraycount"]
- elif c == ']':
- self.state["indent"] -= 1
- self.newline()
- self.state["inarray"].pop(0)
- self.state["arraycount"].pop(0)
- self.write(c)
- elif c == ':':
- self.write(c)
- self.state["need_space"] = True
- elif c == ',':
- if not self.state["inarray"][0]:
- self.write(c)
- self.newline()
+ def encode(self, o, node_key=None):
+ if isinstance(o, (list, tuple)):
+ # Check if we are a flat list of numbers.
+ if not any(isinstance(el, (list, tuple, dict)) for el in o):
+ s = ', '.join(json.dumps(el) for el in o)
+ if node_key in self.custom_elems.keys():
+ # Special case handling to specify number of elements in a row for tables, ccm, etc.
+ self.indentation_level += 1
+ sl = s.split(', ')
+ num = self.custom_elems[node_key]
+ chunk = [self.indent_str + ', '.join(sl[x:x + num]) for x in range(0, len(sl), num)]
+ t = ',\n'.join(chunk)
+ self.indentation_level -= 1
+ output = f'\n{self.indent_str}[\n{t}\n{self.indent_str}]'
+ elif len(s) > self.hard_break - len(self.indent_str):
+ # Break a long list with wraps.
+ self.indentation_level += 1
+ t = textwrap.fill(s, self.hard_break, break_long_words=False,
+ initial_indent=self.indent_str, subsequent_indent=self.indent_str)
+ self.indentation_level -= 1
+ output = f'\n{self.indent_str}[\n{t}\n{self.indent_str}]'
+ else:
+ # Smaller lists can remain on a single line.
+ output = f' [ {s} ]'
+ return output
else:
- self.write(c)
- self.state["arraycount"][0] += 1
- if self.state["arraycount"][0] == 16:
- self.state["arraycount"][0] = 0
- self.newline()
+ # Sub-structures in the list case.
+ self.indentation_level += 1
+ output = [self.indent_str + self.encode(el) for el in o]
+ self.indentation_level -= 1
+ output = ',\n'.join(output)
+ return f' [\n{output}\n{self.indent_str}]'
+
+ elif isinstance(o, dict):
+ self.indentation_level += 1
+ output = []
+ for k, v in o.items():
+ if isinstance(v, dict) and len(v) == 0:
+ # Empty config block special case.
+ output.append(self.indent_str + f'{json.dumps(k)}: {{ }}')
else:
- self.state["need_space"] = True
- elif c.isspace():
- pass
+ # Only linebreak if the next node is a config block.
+ sep = f'\n{self.indent_str}' if isinstance(v, dict) else ''
+ output.append(self.indent_str + f'{json.dumps(k)}:{sep}{self.encode(v, k)}')
+ output = ',\n'.join(output)
+ self.indentation_level -= 1
+ return f'{{\n{output}\n{self.indent_str}}}'
+
else:
- self.write(c)
+ return ' ' + json.dumps(o)
+
+ @property
+ def indent_str(self) -> str:
+ return ' ' * self.indentation_level * self.indent
+
+ def iterencode(self, o, **kwargs):
+ return self.encode(o)
+
+
+def pretty_print(in_json: dict) -> str:
+
+ if 'version' not in in_json or \
+ 'target' not in in_json or \
+ 'algorithms' not in in_json or \
+ in_json['version'] < 2.0:
+ raise RuntimeError('Incompatible JSON dictionary has been provided')
- def print(self, string):
- for c in string:
- self.process_char(c)
- self.newline()
+ return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)
-def pretty_print_json(str_in, output_filename):
- with open(output_filename, "w") as fout:
- printer = JSONPrettyPrinter(fout)
- printer.print(str_in)
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=
+ 'Prettify a version 2.0 camera tuning config JSON file.')
+ parser.add_argument('input', type=str, help='Input tuning file.')
+ parser.add_argument('output', type=str, nargs='?',
+ help='Output converted tuning file. If not provided, the input file will be updated in-place.',
+ default=None)
+ args = parser.parse_args()
+ with open(args.input, 'r') as f:
+ in_json = json.load(f)
-if __name__ == '__main__':
- if len(sys.argv) != 2:
- print("Usage: %s filename" % sys.argv[0])
- sys.exit(1)
+ out_json = pretty_print(in_json)
- input_filename = sys.argv[1]
- with open(input_filename, "r") as fin:
- printer = JSONPrettyPrinter(sys.stdout)
- printer.print(fin.read())
+ with open(args.output if args.output is not None else args.input, 'w') as f:
+ f.write(out_json)
diff --git a/utils/raspberrypi/ctt/ctt_ransac.py b/utils/raspberrypi/ctt/ctt_ransac.py
index 11515a4f..9ed7d93c 100644
--- a/utils/raspberrypi/ctt/ctt_ransac.py
+++ b/utils/raspberrypi/ctt/ctt_ransac.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_ransac.py - camera tuning tool RANSAC selector for Macbeth chart locator
diff --git a/utils/raspberrypi/ctt/ctt_tools.py b/utils/raspberrypi/ctt/ctt_tools.py
index 8728ff16..79195289 100644
--- a/utils/raspberrypi/ctt/ctt_tools.py
+++ b/utils/raspberrypi/ctt/ctt_tools.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (C) 2019, Raspberry Pi (Trading) Limited
+# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_tools.py - camera tuning tool miscellaneous
diff --git a/utils/raspberrypi/ctt/ctt_visualise.py b/utils/raspberrypi/ctt/ctt_visualise.py
new file mode 100644
index 00000000..ed2339fd
--- /dev/null
+++ b/utils/raspberrypi/ctt/ctt_visualise.py
@@ -0,0 +1,43 @@
+"""
+Some code that will save virtual macbeth charts that show the difference between optimised matrices and non optimised matrices
+
+The function creates an image that is 1550 by 1050 pixels wide, and fills it with patches which are 200x200 pixels in size
+Each patch contains the ideal color, the color from the original matrix, and the color from the final matrix
+_________________
+| |
+| Ideal Color |
+|_______________|
+| Old | new |
+| Color | Color |
+|_______|_______|
+
+Nice way of showing how the optimisation helps change the colors and the color matricies
+"""
+import numpy as np
+from PIL import Image
+
+
+def visualise_macbeth_chart(macbeth_rgb, original_rgb, new_rgb, output_filename):
+ image = np.zeros((1050, 1550, 3), dtype=np.uint8)
+ colorindex = -1
+ for y in range(6):
+ for x in range(4): # Creates 6 x 4 grid of macbeth chart
+ colorindex += 1
+ xlocation = 50 + 250 * x # Means there is 50px of black gap between each square, more like the real macbeth chart.
+ ylocation = 50 + 250 * y
+ for g in range(200):
+ for i in range(100):
+ image[xlocation + i, ylocation + g] = macbeth_rgb[colorindex]
+ xlocation = 150 + 250 * x
+ ylocation = 50 + 250 * y
+ for i in range(100):
+ for g in range(100):
+ image[xlocation + i, ylocation + g] = original_rgb[colorindex] # Smaller squares below to compare the old colors with the new ones
+ xlocation = 150 + 250 * x
+ ylocation = 150 + 250 * y
+ for i in range(100):
+ for g in range(100):
+ image[xlocation + i, ylocation + g] = new_rgb[colorindex]
+
+ img = Image.fromarray(image, 'RGB')
+ img.save(str(output_filename) + 'Generated Macbeth Chart.png')
diff --git a/utils/raspberrypi/delayedctrls_parse.py b/utils/raspberrypi/delayedctrls_parse.py
index e38145d8..1decf73f 100644
--- a/utils/raspberrypi/delayedctrls_parse.py
+++ b/utils/raspberrypi/delayedctrls_parse.py
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
import re
import sys
import os
diff --git a/utils/release.sh b/utils/release.sh
new file mode 100755
index 00000000..8cc85859
--- /dev/null
+++ b/utils/release.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Prepare a project release
+
+set -e
+
+# Abort if we are not within the project root or the tree is not clean.
+if [ ! -e utils/gen-version.sh ] || [ ! -e .git ]; then
+ echo "This release script must be run from the root of libcamera git tree."
+ exit 1
+fi
+
+if ! git diff-index --quiet HEAD; then
+ echo "Tree must be clean to release."
+ exit 1
+fi
+
+# Identify current version components
+version=$(./utils/gen-version.sh)
+
+# Decide if we are here to bump major, minor, or patch release.
+case $1 in
+ major|minor|patch)
+ bump=$1;
+ ;;
+ *)
+ echo "You must specify the version bump level: (major, minor, patch)"
+ exit 1
+ ;;
+esac
+
+new_version=$(./utils/semver bump "$bump" "$version")
+
+echo "Bumping $bump"
+echo " Existing version is: $version"
+echo " New version is : $new_version"
+
+# Patch in the version to our meson.build
+sed -i -E "s/ version : '.*',/ version : '$new_version',/" meson.build
+
+# Commit the update
+git commit meson.build -esm "libcamera v$new_version"
+
+# Create a tag from that commit
+git show -s --format=%B | git tag "v$new_version" -s -F -
diff --git a/utils/rkisp1/gen-csc-table.py b/utils/rkisp1/gen-csc-table.py
new file mode 100755
index 00000000..ffc0370a
--- /dev/null
+++ b/utils/rkisp1/gen-csc-table.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2022, Ideas on Board Oy
+#
+# Generate color space conversion table coefficients with configurable
+# fixed-point precision
+
+import argparse
+import enum
+import numpy as np
+import sys
+
+
+encodings = {
+ 'rec601': [
+ [ 0.299, 0.587, 0.114 ],
+ [ -0.299 / 1.772, -0.587 / 1.772, 0.886 / 1.772 ],
+ [ 0.701 / 1.402, -0.587 / 1.402, -0.114 / 1.402 ]
+ ],
+ 'rec709': [
+ [ 0.2126, 0.7152, 0.0722 ],
+ [ -0.2126 / 1.8556, -0.7152 / 1.8556, 0.9278 / 1.8556 ],
+ [ 0.7874 / 1.5748, -0.7152 / 1.5748, -0.0722 / 1.5748 ]
+ ],
+ 'rec2020': [
+ [ 0.2627, 0.6780, 0.0593 ],
+ [ -0.2627 / 1.8814, -0.6780 / 1.8814, 0.9407 / 1.8814 ],
+ [ 0.7373 / 1.4746, -0.6780 / 1.4746, -0.0593 / 1.4746 ],
+ ],
+ 'smpte240m': [
+ [ 0.2122, 0.7013, 0.0865 ],
+ [ -0.2122 / 1.8270, -0.7013 / 1.8270, 0.9135 / 1.8270 ],
+ [ 0.7878 / 1.5756, -0.7013 / 1.5756, -0.0865 / 1.5756 ],
+ ],
+}
+
+
+class Precision(object):
+ def __init__(self, precision):
+ if precision[0].upper() != 'Q':
+ raise RuntimeError(f'Invalid precision `{precision}`')
+ prec = precision[1:].split('.')
+ if len(prec) != 2:
+ raise RuntimeError(f'Invalid precision `{precision}`')
+
+ self.__prec = [int(v) for v in prec]
+
+ @property
+ def integer(self):
+ return self.__prec[0]
+
+ @property
+ def fractional(self):
+ return self.__prec[1]
+
+ @property
+ def total(self):
+ # Add 1 for the sign bit
+ return self.__prec[0] + self.__prec[1] + 1
+
+
+class Quantization(enum.Enum):
+ FULL = 0
+ LIMITED = 1
+
+
+def scale_coeff(coeff, quantization, luma):
+ """Scale a coefficient to the output range dictated by the quantization.
+
+ Parameters
+ ----------
+ coeff : float
+ The CSC matrix coefficient to scale
+ quantization : Quantization
+ The quantization, either FULL or LIMITED
+ luma : bool
+ True if the coefficient corresponds to a luma value, False otherwise
+ """
+
+ # Assume the input range is 8 bits. The output range is set by the
+ # quantization and differs between luma and chrome components for limited
+ # range.
+ in_range = 255 - 0
+ if quantization == Quantization.FULL:
+ out_range = 255 - 0
+ elif luma:
+ out_range = 235 - 16
+ else:
+ out_range = 240 - 16
+
+ return coeff * out_range / in_range
+
+
+def round_array(values):
+ """Round a list of signed floating point values to the closest integer while
+ preserving the (rounded) value of the sum of all elements.
+ """
+
+ # Calculate the rounding error as the difference between the rounded sum of
+ # values and the sum of rounded values. This is by definition an integer
+ # (positive or negative), which indicates how many values will need to be
+ # 'flipped' to the opposite rounding.
+ rounded_values = [round(value) for value in values]
+ sum_values = round(sum(values))
+ sum_error = sum_values - sum(rounded_values)
+
+ if sum_error == 0:
+ return rounded_values
+
+ # The next step is to distribute the error among the values, in a way that
+ # will minimize the relative error introduced in individual values. We
+ # extend the values list with the rounded value and original index for each
+ # element, and sort by rounding error. Then we modify the elements with the
+ # highest or lowest error, depending on whether the sum error is negative
+ # or positive.
+
+ values = [[value, round(value), index] for index, value in enumerate(values)]
+ values.sort(key=lambda v: v[1] - v[0])
+
+ # It could also be argued that the key for the sort order should not be the
+ # absolute rouding error but the relative error, as the impact of identical
+ # rounding errors will differ for coefficients with widely different values.
+ # This is a topic for further research.
+ #
+ # values.sort(key=lambda v: (v[1] - v[0]) / abs(v[0]))
+
+ if sum_error > 0:
+ for i in range(sum_error):
+ values[i][1] += 1
+ else:
+ for i in range(-sum_error):
+ values[len(values) - i - 1][1] -= 1
+
+ # Finally, sort back by index, make sure the total rounding error is now 0,
+ # and return the rounded values.
+ values.sort(key=lambda v: v[2])
+ values = [value[1] for value in values]
+ assert(sum(values) == sum_values)
+
+ return values
+
+
+def main(argv):
+
+ # Parse command line arguments.
+ parser = argparse.ArgumentParser(
+ description='Generate color space conversion table coefficients with '
+ 'configurable fixed-point precision.'
+ )
+ parser.add_argument('--invert', '-i', action='store_true',
+ help='Invert the color space conversion (YUV -> RGB)')
+ parser.add_argument('--precision', '-p', default='Q1.7',
+ help='The output fixed point precision in Q notation (sign bit excluded)')
+ parser.add_argument('--quantization', '-q', choices=['full', 'limited'],
+ default='limited', help='Quantization range')
+ parser.add_argument('encoding', choices=encodings.keys(), help='YCbCr encoding')
+ args = parser.parse_args(argv[1:])
+
+ try:
+ precision = Precision(args.precision)
+ except Exception:
+ print(f'Invalid precision `{args.precision}`')
+ return 1
+
+ encoding = encodings[args.encoding]
+ quantization = Quantization[args.quantization.upper()]
+
+ # Scale and round the encoding coefficients based on the precision and
+ # quantization range.
+ luma = True
+ scaled_coeffs = []
+ for line in encoding:
+ line = [scale_coeff(coeff, quantization, luma) for coeff in line]
+ scaled_coeffs.append(line)
+ luma = False
+
+ if args.invert:
+ scaled_coeffs = np.linalg.inv(scaled_coeffs)
+
+ rounded_coeffs = []
+ for line in scaled_coeffs:
+ line = [coeff * (1 << precision.fractional) for coeff in line]
+ # For the RGB to YUV conversion, use a rounding method that preserves
+ # the rounded sum of each line to avoid biases and overflow, as the sum
+ # of luma and chroma coefficients should be 1.0 and 0.0 respectively
+ # (in full range). For the YUV to RGB conversion, there is no such
+ # constraint, so use simple rounding.
+ if args.invert:
+ line = [round(coeff) for coeff in line]
+ else:
+ line = round_array(line)
+
+ # Convert coefficients to the number of bits selected by the precision.
+ # Negative values will be turned into positive integers using 2's
+ # complement.
+ line = [coeff & ((1 << precision.total) - 1) for coeff in line]
+ rounded_coeffs.append(line)
+
+ # Print the result as C code.
+ nbits = 1 << (precision.total - 1).bit_length()
+ nbytes = nbits // 4
+ print(f'static const u{nbits} {"yuv2rgb" if args.invert else "rgb2yuv"}_{args.encoding}_{quantization.name.lower()}_coeffs[] = {{')
+
+ for line in rounded_coeffs:
+ line = [f'0x{coeff:0{nbytes}x}' for coeff in line]
+
+ print(f'\t{", ".join(line)},')
+
+ print('};')
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/utils/rkisp1/rkisp1-capture.sh b/utils/rkisp1/rkisp1-capture.sh
index 4d09f5d5..c5f859f2 100755
--- a/utils/rkisp1/rkisp1-capture.sh
+++ b/utils/rkisp1/rkisp1-capture.sh
@@ -14,6 +14,37 @@
# - raw2rgbpnm (from git://git.retiisi.org.uk/~sailus/raw2rgbpnm.git)
# - yavta (from git://git.ideasonboard.org/yavta.git)
+# Return the entity connected to a given pad
+# $1: The pad, expressed as "entity":index
+mc_remote_entity() {
+ local entity="${1%:*}"
+ local pad="${1#*:}"
+
+ ${mediactl} -p | awk '
+/^- entity / {
+ in_entity=0
+}
+
+/^- entity [0-9]+: '"${entity}"' / {
+ in_entity=1
+}
+
+/^[ \t]+pad/ {
+ in_pad=0
+}
+
+/^[ \t]+pad'"${pad}"': / {
+ in_pad=1
+}
+
+/^[ \t]+(<-|->) "[^"]+"/ {
+ if (in_entity && in_pad) {
+ print gensub(/^[^"]+"([^"]+)":([0-9]+).*$/, "\\1", "g")
+ exit
+ }
+}'
+}
+
# Locate the sensor entity
find_sensor() {
local bus
@@ -28,6 +59,17 @@ find_sensor() {
echo "$sensor_name $bus"
}
+# Locate the CSI-2 receiver
+find_csi2_rx() {
+ local sensor_name=$1
+ local csi2_rx
+
+ csi2_rx=$(mc_remote_entity "$sensor_name:0")
+ if [ "$csi2_rx" != rkisp1_isp ] ; then
+ echo "$csi2_rx"
+ fi
+}
+
# Locate the media device
find_media_device() {
local mdev
@@ -51,7 +93,7 @@ get_sensor_format() {
local format
local sensor=$1
- format=$($mediactl --get-v4l2 "'$sensor':0" | sed 's/\[\([^ ]*\).*/\1/')
+ format=$($mediactl --get-v4l2 "'$sensor':0" | grep 'fmt:' | sed 's/.*\(fmt:\S*\).*/\1/')
sensor_mbus_code=$(echo $format | sed 's/fmt:\([A-Z0-9_]*\).*/\1/')
sensor_size=$(echo $format | sed 's/[^\/]*\/\([0-9x]*\).*/\1/')
@@ -63,15 +105,27 @@ configure_pipeline() {
local format="fmt:$sensor_mbus_code/$sensor_size"
local capture_mbus_code=$1
local capture_size=$2
+ local csi2_rx
echo "Configuring pipeline for $sensor in $format"
+ csi2_rx=$(find_csi2_rx "$sensor")
+
$mediactl -r
- $mediactl -l "'$sensor':0 -> 'rkisp1_isp':0 [1]"
+ if [ -n "$csi2_rx" ] ; then
+ $mediactl -l "'$sensor':0 -> '$csi2_rx':0 [1]"
+ $mediactl -l "'$csi2_rx':1 -> 'rkisp1_isp':0 [1]"
+ else
+ $mediactl -l "'$sensor':0 -> 'rkisp1_isp':0 [1]"
+ fi
$mediactl -l "'rkisp1_isp':2 -> 'rkisp1_resizer_mainpath':0 [1]"
$mediactl -V "\"$sensor\":0 [$format]"
+ if [ -n "$csi2_rx" ] ; then
+ $mediactl -V "'$csi2_rx':0 [$format]"
+ $mediactl -V "'$csi2_rx':1 [$format]"
+ fi
$mediactl -V "'rkisp1_isp':0 [$format crop:(0,0)/$sensor_size]"
$mediactl -V "'rkisp1_isp':2 [fmt:$capture_mbus_code/$sensor_size crop:(0,0)/$sensor_size]"
$mediactl -V "'rkisp1_resizer_mainpath':0 [fmt:$capture_mbus_code/$sensor_size crop:(0,0)/$sensor_size]"
@@ -88,6 +142,7 @@ capture_frames() {
if [[ $save_file -eq 1 ]]; then
file_op="--file=/tmp/frame-#.bin"
+ rm -f /tmp/frame-*.bin
fi
yavta -c$frame_count -n5 -I -f $capture_format -s $capture_size \
@@ -170,7 +225,7 @@ mediactl="media-ctl -d $mdev"
get_sensor_format "$sensor"
if [[ $raw == true ]] ; then
- capture_format=$(echo $sensor_mbus_code | sed 's/_[0-9X]$//')
+ capture_format=$(echo $sensor_mbus_code | sed 's/_[0-9X]*$//')
capture_mbus_code=$sensor_mbus_code
else
capture_format=YUYV
diff --git a/utils/semver b/utils/semver
new file mode 100755
index 00000000..a1604250
--- /dev/null
+++ b/utils/semver
@@ -0,0 +1,446 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0
+
+set -o errexit -o nounset -o pipefail
+
+NAT='0|[1-9][0-9]*'
+ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*'
+IDENT="$NAT|$ALPHANUM"
+FIELD='[0-9A-Za-z-]+'
+
+SEMVER_REGEX="\
+^[vV]?\
+($NAT)\\.($NAT)\\.($NAT)\
+(\\-(${IDENT})(\\.(${IDENT}))*)?\
+(\\+${FIELD}(\\.${FIELD})*)?$"
+
+PROG=semver
+PROG_VERSION="3.4.0"
+
+USAGE="\
+Usage:
+ $PROG bump major <version>
+ $PROG bump minor <version>
+ $PROG bump patch <version>
+ $PROG bump prerel|prerelease [<prerel>] <version>
+ $PROG bump build <build> <version>
+ $PROG bump release <version>
+ $PROG get major <version>
+ $PROG get minor <version>
+ $PROG get patch <version>
+ $PROG get prerel|prerelease <version>
+ $PROG get build <version>
+ $PROG get release <version>
+ $PROG compare <version> <other_version>
+ $PROG diff <version> <other_version>
+ $PROG validate <version>
+ $PROG --help
+ $PROG --version
+
+Arguments:
+ <version> A version must match the following regular expression:
+ \"${SEMVER_REGEX}\"
+ In English:
+ -- The version must match X.Y.Z[-PRERELEASE][+BUILD]
+ where X, Y and Z are non-negative integers.
+ -- PRERELEASE is a dot separated sequence of non-negative integers and/or
+ identifiers composed of alphanumeric characters and hyphens (with
+ at least one non-digit). Numeric identifiers must not have leading
+ zeros. A hyphen (\"-\") introduces this optional part.
+ -- BUILD is a dot separated sequence of identifiers composed of alphanumeric
+ characters and hyphens. A plus (\"+\") introduces this optional part.
+
+ <other_version> See <version> definition.
+
+ <prerel> A string as defined by PRERELEASE above. Or, it can be a PRERELEASE
+ prototype string followed by a dot.
+
+ <build> A string as defined by BUILD above.
+
+Options:
+ -v, --version Print the version of this tool.
+ -h, --help Print this help message.
+
+Commands:
+ bump Bump by one of major, minor, patch; zeroing or removing
+ subsequent parts. \"bump prerel\" (or its synonym \"bump prerelease\")
+ sets the PRERELEASE part and removes any BUILD part. A trailing dot
+ in the <prerel> argument introduces an incrementing numeric field
+ which is added or bumped. If no <prerel> argument is provided, an
+ incrementing numeric field is introduced/bumped. \"bump build\" sets
+ the BUILD part. \"bump release\" removes any PRERELEASE or BUILD parts.
+ The bumped version is written to stdout.
+
+ get Extract given part of <version>, where part is one of major, minor,
+ patch, prerel (alternatively: prerelease), build, or release.
+
+ compare Compare <version> with <other_version>, output to stdout the
+ following values: -1 if <other_version> is newer, 0 if equal, 1 if
+ older. The BUILD part is not used in comparisons.
+
+ diff Compare <version> with <other_version>, output to stdout the
+ difference between two versions by the release type (MAJOR, MINOR,
+ PATCH, PRERELEASE, BUILD).
+
+ validate Validate if <version> follows the SEMVER pattern (see <version>
+ definition). Print 'valid' to stdout if the version is valid, otherwise
+ print 'invalid'.
+
+See also:
+ https://semver.org -- Semantic Versioning 2.0.0"
+
+function error {
+ echo -e "$1" >&2
+ exit 1
+}
+
+function usage_help {
+ error "$USAGE"
+}
+
+function usage_version {
+ echo -e "${PROG}: $PROG_VERSION"
+ exit 0
+}
+
+# normalize the "part" keywords to a canonical string. At present,
+# only "prerelease" is normalized to "prerel".
+
+function normalize_part {
+ if [ "$1" == "prerelease" ]
+ then
+ echo "prerel"
+ else
+ echo "$1"
+ fi
+}
+
+function validate_version {
+ local version=$1
+ if [[ "$version" =~ $SEMVER_REGEX ]]; then
+ # if a second argument is passed, store the result in var named by $2
+ if [ "$#" -eq "2" ]; then
+ local major=${BASH_REMATCH[1]}
+ local minor=${BASH_REMATCH[2]}
+ local patch=${BASH_REMATCH[3]}
+ local prere=${BASH_REMATCH[4]}
+ local build=${BASH_REMATCH[8]}
+ eval "$2=(\"$major\" \"$minor\" \"$patch\" \"$prere\" \"$build\")"
+ else
+ echo "$version"
+ fi
+ else
+ error "version $version does not match the semver scheme 'X.Y.Z(-PRERELEASE)(+BUILD)'. See help for more information."
+ fi
+}
+
+function is_nat {
+ [[ "$1" =~ ^($NAT)$ ]]
+}
+
+function is_null {
+ [ -z "$1" ]
+}
+
+function order_nat {
+ [ "$1" -lt "$2" ] && { echo -1 ; return ; }
+ [ "$1" -gt "$2" ] && { echo 1 ; return ; }
+ echo 0
+}
+
+function order_string {
+ [[ $1 < $2 ]] && { echo -1 ; return ; }
+ [[ $1 > $2 ]] && { echo 1 ; return ; }
+ echo 0
+}
+
+# given two (named) arrays containing NAT and/or ALPHANUM fields, compare them
+# one by one according to semver 2.0.0 spec. Return -1, 0, 1 if left array ($1)
+# is less-than, equal, or greater-than the right array ($2). The longer array
+# is considered greater-than the shorter if the shorter is a prefix of the longer.
+#
+function compare_fields {
+ local l="$1[@]"
+ local r="$2[@]"
+ local leftfield=( "${!l}" )
+ local rightfield=( "${!r}" )
+ local left
+ local right
+
+ local i=$(( -1 ))
+ local order=$(( 0 ))
+
+ while true
+ do
+ [ $order -ne 0 ] && { echo $order ; return ; }
+
+ : $(( i++ ))
+ left="${leftfield[$i]}"
+ right="${rightfield[$i]}"
+
+ is_null "$left" && is_null "$right" && { echo 0 ; return ; }
+ is_null "$left" && { echo -1 ; return ; }
+ is_null "$right" && { echo 1 ; return ; }
+
+ is_nat "$left" && is_nat "$right" && { order=$(order_nat "$left" "$right") ; continue ; }
+ is_nat "$left" && { echo -1 ; return ; }
+ is_nat "$right" && { echo 1 ; return ; }
+ { order=$(order_string "$left" "$right") ; continue ; }
+ done
+}
+
+# shellcheck disable=SC2206 # checked by "validate"; ok to expand prerel id's into array
+function compare_version {
+ local order
+ validate_version "$1" V
+ validate_version "$2" V_
+
+ # compare major, minor, patch
+
+ local left=( "${V[0]}" "${V[1]}" "${V[2]}" )
+ local right=( "${V_[0]}" "${V_[1]}" "${V_[2]}" )
+
+ order=$(compare_fields left right)
+ [ "$order" -ne 0 ] && { echo "$order" ; return ; }
+
+ # compare pre-release ids when M.m.p are equal
+
+ local prerel="${V[3]:1}"
+ local prerel_="${V_[3]:1}"
+ local left=( ${prerel//./ } )
+ local right=( ${prerel_//./ } )
+
+ # if left and right have no pre-release part, then left equals right
+ # if only one of left/right has pre-release part, that one is less than simple M.m.p
+
+ [ -z "$prerel" ] && [ -z "$prerel_" ] && { echo 0 ; return ; }
+ [ -z "$prerel" ] && { echo 1 ; return ; }
+ [ -z "$prerel_" ] && { echo -1 ; return ; }
+
+ # otherwise, compare the pre-release id's
+
+ compare_fields left right
+}
+
+# render_prerel -- return a prerel field with a trailing numeric string
+# usage: render_prerel numeric [prefix-string]
+#
+function render_prerel {
+ if [ -z "$2" ]
+ then
+ echo "${1}"
+ else
+ echo "${2}${1}"
+ fi
+}
+
+# extract_prerel -- extract prefix and trailing numeric portions of a pre-release part
+# usage: extract_prerel prerel prerel_parts
+# The prefix and trailing numeric parts are returned in "prerel_parts".
+#
+PREFIX_ALPHANUM='[.0-9A-Za-z-]*[.A-Za-z-]'
+DIGITS='[0-9][0-9]*'
+EXTRACT_REGEX="^(${PREFIX_ALPHANUM})*(${DIGITS})$"
+
+function extract_prerel {
+ local prefix; local numeric;
+
+ if [[ "$1" =~ $EXTRACT_REGEX ]]
+ then # found prefix and trailing numeric parts
+ prefix="${BASH_REMATCH[1]}"
+ numeric="${BASH_REMATCH[2]}"
+ else # no numeric part
+ prefix="${1}"
+ numeric=
+ fi
+
+ eval "$2=(\"$prefix\" \"$numeric\")"
+}
+
+# bump_prerel -- return the new pre-release part based on previous pre-release part
+# and prototype for bump
+# usage: bump_prerel proto previous
+#
+function bump_prerel {
+ local proto; local prev_prefix; local prev_numeric;
+
+ # case one: no trailing dot in prototype => simply replace previous with proto
+ if [[ ! ( "$1" =~ \.$ ) ]]
+ then
+ echo "$1"
+ return
+ fi
+
+ proto="${1%.}" # discard trailing dot marker from prototype
+
+ extract_prerel "${2#-}" prerel_parts # extract parts of previous pre-release
+# shellcheck disable=SC2154
+ prev_prefix="${prerel_parts[0]}"
+ prev_numeric="${prerel_parts[1]}"
+
+ # case two: bump or append numeric to previous pre-release part
+ if [ "$proto" == "+" ] # dummy "+" indicates no prototype argument provided
+ then
+ if [ -n "$prev_numeric" ]
+ then
+ : $(( ++prev_numeric )) # previous pre-release is already numbered, bump it
+ render_prerel "$prev_numeric" "$prev_prefix"
+ else
+ render_prerel 1 "$prev_prefix" # append starting number
+ fi
+ return
+ fi
+
+ # case three: set, bump, or append using prototype prefix
+ if [ "$prev_prefix" != "$proto" ]
+ then
+ render_prerel 1 "$proto" # proto not same pre-release; set and start at '1'
+ elif [ -n "$prev_numeric" ]
+ then
+ : $(( ++prev_numeric )) # pre-release is numbered; bump it
+ render_prerel "$prev_numeric" "$prev_prefix"
+ else
+ render_prerel 1 "$prev_prefix" # start pre-release at number '1'
+ fi
+}
+
+function command_bump {
+ local new; local version; local sub_version; local command;
+
+ command="$(normalize_part "$1")"
+
+ case $# in
+ 2) case "$command" in
+ major|minor|patch|prerel|release) sub_version="+."; version=$2;;
+ *) usage_help;;
+ esac ;;
+ 3) case "$command" in
+ prerel|build) sub_version=$2 version=$3 ;;
+ *) usage_help;;
+ esac ;;
+ *) usage_help;;
+ esac
+
+ validate_version "$version" parts
+ # shellcheck disable=SC2154
+ local major="${parts[0]}"
+ local minor="${parts[1]}"
+ local patch="${parts[2]}"
+ local prere="${parts[3]}"
+ local build="${parts[4]}"
+
+ case "$command" in
+ major) new="$((major + 1)).0.0";;
+ minor) new="${major}.$((minor + 1)).0";;
+ patch) new="${major}.${minor}.$((patch + 1))";;
+ release) new="${major}.${minor}.${patch}";;
+ prerel) new=$(validate_version "${major}.${minor}.${patch}-$(bump_prerel "$sub_version" "$prere")");;
+ build) new=$(validate_version "${major}.${minor}.${patch}${prere}+${sub_version}");;
+ *) usage_help ;;
+ esac
+
+ echo "$new"
+ exit 0
+}
+
+function command_compare {
+ local v; local v_;
+
+ case $# in
+ 2) v=$(validate_version "$1"); v_=$(validate_version "$2") ;;
+ *) usage_help ;;
+ esac
+
+ set +u # need unset array element to evaluate to null
+ compare_version "$v" "$v_"
+ exit 0
+}
+
+function command_diff {
+ validate_version "$1" v1_parts
+ # shellcheck disable=SC2154
+ local v1_major="${v1_parts[0]}"
+ local v1_minor="${v1_parts[1]}"
+ local v1_patch="${v1_parts[2]}"
+ local v1_prere="${v1_parts[3]}"
+ local v1_build="${v1_parts[4]}"
+
+ validate_version "$2" v2_parts
+ # shellcheck disable=SC2154
+ local v2_major="${v2_parts[0]}"
+ local v2_minor="${v2_parts[1]}"
+ local v2_patch="${v2_parts[2]}"
+ local v2_prere="${v2_parts[3]}"
+ local v2_build="${v2_parts[4]}"
+
+ if [ "${v1_major}" != "${v2_major}" ]; then
+ echo "major"
+ elif [ "${v1_minor}" != "${v2_minor}" ]; then
+ echo "minor"
+ elif [ "${v1_patch}" != "${v2_patch}" ]; then
+ echo "patch"
+ elif [ "${v1_prere}" != "${v2_prere}" ]; then
+ echo "prerelease"
+ elif [ "${v1_build}" != "${v2_build}" ]; then
+ echo "build"
+ fi
+}
+
+# shellcheck disable=SC2034
+function command_get {
+ local part version
+
+ if [[ "$#" -ne "2" ]] || [[ -z "$1" ]] || [[ -z "$2" ]]; then
+ usage_help
+ exit 0
+ fi
+
+ part="$1"
+ version="$2"
+
+ validate_version "$version" parts
+ local major="${parts[0]}"
+ local minor="${parts[1]}"
+ local patch="${parts[2]}"
+ local prerel="${parts[3]:1}"
+ local build="${parts[4]:1}"
+ local release="${major}.${minor}.${patch}"
+
+ part="$(normalize_part "$part")"
+
+ case "$part" in
+ major|minor|patch|release|prerel|build) echo "${!part}" ;;
+ *) usage_help ;;
+ esac
+
+ exit 0
+}
+
+function command_validate {
+ if [[ "$#" -ne "1" ]]; then
+ usage_help
+ fi
+
+ if [[ "$1" =~ $SEMVER_REGEX ]]; then
+ echo "valid"
+ else
+ echo "invalid"
+ fi
+
+ exit 0
+}
+
+case $# in
+ 0) echo "Unknown command: $*"; usage_help;;
+esac
+
+case $1 in
+ --help|-h) echo -e "$USAGE"; exit 0;;
+ --version|-v) usage_version ;;
+ bump) shift; command_bump "$@";;
+ get) shift; command_get "$@";;
+ compare) shift; command_compare "$@";;
+ diff) shift; command_diff "$@";;
+ validate) shift; command_validate "$@";;
+ *) echo "Unknown arguments: $*"; usage_help;;
+esac
diff --git a/utils/tracepoints/gen-tp-header.py b/utils/tracepoints/gen-tp-header.py
index bbd472d9..a454615e 100755
--- a/utils/tracepoints/gen-tp-header.py
+++ b/utils/tracepoints/gen-tp-header.py
@@ -8,22 +8,23 @@
import datetime
import jinja2
+import pathlib
import os
import sys
def main(argv):
- if len(argv) < 3:
- print(f'Usage: {argv[0]} output template tp_files...')
+ if len(argv) < 4:
+ print(f'Usage: {argv[0]} include_build_dir output template tp_files...')
return 1
- output = argv[1]
- template = argv[2]
+ output = argv[2]
+ template = argv[3]
year = datetime.datetime.now().year
- path = output.replace('include/', '', 1)
+ path = pathlib.Path(output).absolute().relative_to(argv[1])
source = ''
- for fname in argv[3:]:
+ for fname in argv[4:]:
source += open(fname, 'r', encoding='utf-8').read() + '\n\n'
template = jinja2.Template(open(template, 'r', encoding='utf-8').read())
diff --git a/utils/tuning/README.rst b/utils/tuning/README.rst
new file mode 100644
index 00000000..ef3e6ad7
--- /dev/null
+++ b/utils/tuning/README.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. TODO: Write an overview of libtuning
+
+Dependencies
+------------
+
+- numpy
+- opencv-python
+- py3exiv2
+- rawpy
diff --git a/utils/tuning/libtuning/__init__.py b/utils/tuning/libtuning/__init__.py
new file mode 100644
index 00000000..93049976
--- /dev/null
+++ b/utils/tuning/libtuning/__init__.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+
+from libtuning.utils import *
+from libtuning.libtuning import *
+
+from libtuning.image import *
+from libtuning.macbeth import *
+
+from libtuning.average import *
+from libtuning.gradient import *
+from libtuning.smoothing import *
diff --git a/utils/tuning/libtuning/average.py b/utils/tuning/libtuning/average.py
new file mode 100644
index 00000000..e28770d7
--- /dev/null
+++ b/utils/tuning/libtuning/average.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# average.py - Wrapper for numpy averaging functions to enable duck-typing
+
+import numpy as np
+
+
+# @brief Wrapper for np averaging functions so that they can be duck-typed
+class Average(object):
+ def __init__(self):
+ pass
+
+ def average(self, np_array):
+ raise NotImplementedError
+
+
+class Mean(Average):
+ def average(self, np_array):
+ return np.mean(np_array)
diff --git a/utils/tuning/libtuning/generators/__init__.py b/utils/tuning/libtuning/generators/__init__.py
new file mode 100644
index 00000000..f28b6149
--- /dev/null
+++ b/utils/tuning/libtuning/generators/__init__.py
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+
+from libtuning.generators.raspberrypi_output import RaspberryPiOutput
+from libtuning.generators.yaml_output import YamlOutput
diff --git a/utils/tuning/libtuning/generators/generator.py b/utils/tuning/libtuning/generators/generator.py
new file mode 100644
index 00000000..7c8c9b99
--- /dev/null
+++ b/utils/tuning/libtuning/generators/generator.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# generator.py - Base class for a generator to convert dict to tuning file
+
+from pathlib import Path
+
+
+class Generator(object):
+ def __init__(self):
+ pass
+
+ def write(self, output_path: Path, output_dict: dict, output_order: list):
+ raise NotImplementedError
diff --git a/utils/tuning/libtuning/generators/raspberrypi_output.py b/utils/tuning/libtuning/generators/raspberrypi_output.py
new file mode 100644
index 00000000..813491cd
--- /dev/null
+++ b/utils/tuning/libtuning/generators/raspberrypi_output.py
@@ -0,0 +1,114 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright 2022 Raspberry Pi Ltd
+#
+# raspberrypi_output.py - Generate tuning file in Raspberry Pi's json format
+#
+# (Copied from ctt_pretty_print_json.py)
+
+from .generator import Generator
+
+import json
+from pathlib import Path
+import textwrap
+
+
+class Encoder(json.JSONEncoder):
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.indentation_level = 0
+ self.hard_break = 120
+ self.custom_elems = {
+ 'table': 16,
+ 'luminance_lut': 16,
+ 'ct_curve': 3,
+ 'ccm': 3,
+ 'gamma_curve': 2,
+ 'y_target': 2,
+ 'prior': 2
+ }
+
+ def encode(self, o, node_key=None):
+ if isinstance(o, (list, tuple)):
+ # Check if we are a flat list of numbers.
+ if not any(isinstance(el, (list, tuple, dict)) for el in o):
+ s = ', '.join(json.dumps(el) for el in o)
+ if node_key in self.custom_elems.keys():
+ # Special case handling to specify number of elements in a row for tables, ccm, etc.
+ self.indentation_level += 1
+ sl = s.split(', ')
+ num = self.custom_elems[node_key]
+ chunk = [self.indent_str + ', '.join(sl[x:x + num]) for x in range(0, len(sl), num)]
+ t = ',\n'.join(chunk)
+ self.indentation_level -= 1
+ output = f'\n{self.indent_str}[\n{t}\n{self.indent_str}]'
+ elif len(s) > self.hard_break - len(self.indent_str):
+ # Break a long list with wraps.
+ self.indentation_level += 1
+ t = textwrap.fill(s, self.hard_break, break_long_words=False,
+ initial_indent=self.indent_str, subsequent_indent=self.indent_str)
+ self.indentation_level -= 1
+ output = f'\n{self.indent_str}[\n{t}\n{self.indent_str}]'
+ else:
+ # Smaller lists can remain on a single line.
+ output = f' [ {s} ]'
+ return output
+ else:
+ # Sub-structures in the list case.
+ self.indentation_level += 1
+ output = [self.indent_str + self.encode(el) for el in o]
+ self.indentation_level -= 1
+ output = ',\n'.join(output)
+ return f' [\n{output}\n{self.indent_str}]'
+
+ elif isinstance(o, dict):
+ self.indentation_level += 1
+ output = []
+ for k, v in o.items():
+ if isinstance(v, dict) and len(v) == 0:
+ # Empty config block special case.
+ output.append(self.indent_str + f'{json.dumps(k)}: {{ }}')
+ else:
+ # Only linebreak if the next node is a config block.
+ sep = f'\n{self.indent_str}' if isinstance(v, dict) else ''
+ output.append(self.indent_str + f'{json.dumps(k)}:{sep}{self.encode(v, k)}')
+ output = ',\n'.join(output)
+ self.indentation_level -= 1
+ return f'{{\n{output}\n{self.indent_str}}}'
+
+ else:
+ return ' ' + json.dumps(o)
+
+ @property
+ def indent_str(self) -> str:
+ return ' ' * self.indentation_level * self.indent
+
+ def iterencode(self, o, **kwargs):
+ return self.encode(o)
+
+
+class RaspberryPiOutput(Generator):
+ def __init__(self):
+ super().__init__()
+
+ def _pretty_print(self, in_json: dict) -> str:
+
+ if 'version' not in in_json or \
+ 'target' not in in_json or \
+ 'algorithms' not in in_json or \
+ in_json['version'] < 2.0:
+ raise RuntimeError('Incompatible JSON dictionary has been provided')
+
+ return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False)
+
+ def write(self, output_file: Path, output_dict: dict, output_order: list):
+ # Write json dictionary to file using ctt's version 2 format
+ out_json = {
+ "version": 2.0,
+ 'target': 'bcm2835',
+ "algorithms": [{f'{module.out_name}': output_dict[module]} for module in output_order]
+ }
+
+ with open(output_file, 'w') as f:
+ f.write(self._pretty_print(out_json))
diff --git a/utils/tuning/libtuning/generators/yaml_output.py b/utils/tuning/libtuning/generators/yaml_output.py
new file mode 100644
index 00000000..effb4fb3
--- /dev/null
+++ b/utils/tuning/libtuning/generators/yaml_output.py
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright 2022 Paul Elder <paul.elder@ideasonboard.com>
+#
+# yaml_output.py - Generate tuning file in YAML format
+
+from .generator import Generator
+
+from numbers import Number
+from pathlib import Path
+
+import libtuning.utils as utils
+
+
+class YamlOutput(Generator):
+ def __init__(self):
+ super().__init__()
+
+ def _stringify_number_list(self, listt: list):
+ line_wrap = 80
+
+ line = '[ ' + ', '.join([str(x) for x in listt]) + ' ]'
+ if len(line) <= line_wrap:
+ return [line]
+
+ out_lines = ['[']
+ line = ' '
+ for x in listt:
+ x_str = str(x)
+ # If the first number is longer than line_wrap, it'll add an extra line
+ if len(line) + len(x_str) > line_wrap:
+ out_lines.append(line)
+ line = f' {x_str},'
+ continue
+ line += f' {x_str},'
+ out_lines.append(line)
+ out_lines.append(']')
+
+ return out_lines
+
+ # @return Array of lines, and boolean of if all elements were numbers
+ def _stringify_list(self, listt: list):
+ out_lines = []
+
+ all_numbers = set([isinstance(x, Number) for x in listt]).issubset({True})
+
+ if all_numbers:
+ return self._stringify_number_list(listt), True
+
+ for value in listt:
+ if isinstance(value, Number):
+ out_lines.append(f'- {str(value)}')
+ elif isinstance(value, str):
+ out_lines.append(f'- "{value}"')
+ elif isinstance(value, list):
+ lines, all_numbers = self._stringify_list(value)
+
+ if all_numbers:
+ out_lines.append( f'- {lines[0]}')
+ out_lines += [f' {line}' for line in lines[1:]]
+ else:
+ out_lines.append( f'-')
+ out_lines += [f' {line}' for line in lines]
+ elif isinstance(value, dict):
+ lines = self._stringify_dict(value)
+ out_lines.append( f'- {lines[0]}')
+ out_lines += [f' {line}' for line in lines[1:]]
+
+ return out_lines, False
+
+ def _stringify_dict(self, dictt: dict):
+ out_lines = []
+
+ for key in dictt:
+ value = dictt[key]
+
+ if isinstance(value, Number):
+ out_lines.append(f'{key}: {str(value)}')
+ elif isinstance(value, str):
+ out_lines.append(f'{key}: "{value}"')
+ elif isinstance(value, list):
+ lines, all_numbers = self._stringify_list(value)
+
+ if all_numbers:
+ out_lines.append( f'{key}: {lines[0]}')
+ out_lines += [f'{" " * (len(key) + 2)}{line}' for line in lines[1:]]
+ else:
+ out_lines.append( f'{key}:')
+ out_lines += [f' {line}' for line in lines]
+ elif isinstance(value, dict):
+ lines = self._stringify_dict(value)
+ out_lines.append( f'{key}:')
+ out_lines += [f' {line}' for line in lines]
+
+ return out_lines
+
+ def write(self, output_file: Path, output_dict: dict, output_order: list):
+ out_lines = [
+ '%YAML 1.1',
+ '---',
+ 'version: 1',
+ # No need to condition this, as libtuning already guarantees that
+ # we have at least one module. Even if the module has no output,
+ # its prescence is meaningful.
+ 'algorithms:'
+ ]
+
+ for module in output_order:
+ out_lines.append(f' - {module.out_name}:')
+
+ if len(output_dict[module]) == 0:
+ continue
+
+ if not isinstance(output_dict[module], dict):
+ utils.eprint(f'Error: Output of {module.type} is not a dictionary')
+ continue
+
+ lines = self._stringify_dict(output_dict[module])
+ out_lines += [f' {line}' for line in lines]
+
+ with open(output_file, 'w', encoding='utf-8') as f:
+ for line in out_lines:
+ f.write(f'{line}\n')
diff --git a/utils/tuning/libtuning/gradient.py b/utils/tuning/libtuning/gradient.py
new file mode 100644
index 00000000..5106f821
--- /dev/null
+++ b/utils/tuning/libtuning/gradient.py
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# gradient.py - Gradients that can be used to distribute or map numbers
+
+import libtuning as lt
+
+import math
+from numbers import Number
+
+
+# @brief Gradient for how to allocate pixels to sectors
+# @description There are no parameters for the gradients as the domain is the
+# number of pixels and the range is the number of sectors, and
+# there is only one curve that has a startpoint and endpoint at
+# (0, 0) and at (#pixels, #sectors). The exception is for curves
+# that *do* have multiple solutions for only two points, such as
+# gaussian, and curves of higher polynomial orders if we had them.
+#
+# \todo There will probably be a helper in the Gradient class, as I have a
+# feeling that all the other curves (besides Linear and Gaussian) can be
+# implemented in the same way.
+class Gradient(object):
+ def __init__(self):
+ pass
+
+ # @brief Distribute pixels into sectors (only in one dimension)
+ # @param domain Number of pixels
+ # @param sectors Number of sectors
+ # @return A list of number of pixels in each sector
+ def distribute(self, domain: list, sectors: list) -> list:
+ raise NotImplementedError
+
+ # @brief Map a number on a curve
+ # @param domain Domain of the curve
+ # @param rang Range of the curve
+ # @param x Input on the domain of the curve
+ # @return y from the range of the curve
+ def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
+ raise NotImplementedError
+
+
+class Linear(Gradient):
+ # @param remainder Mode of handling remainder
+ def __init__(self, remainder: lt.Remainder = lt.Remainder.Float):
+ self.remainder = remainder
+
+ def distribute(self, domain: list, sectors: list) -> list:
+ size = domain / sectors
+ rem = domain % sectors
+
+ if rem == 0:
+ return [int(size)] * sectors
+
+ size = math.ceil(size)
+ rem = domain % size
+ output_sectors = [int(size)] * (sectors - 1)
+
+ if self.remainder == lt.Remainder.Float:
+ size = domain / sectors
+ output_sectors = [size] * sectors
+ elif self.remainder == lt.Remainder.DistributeFront:
+ output_sectors.append(int(rem))
+ elif self.remainder == lt.Remainder.DistributeBack:
+ output_sectors.insert(0, int(rem))
+ else:
+ raise ValueError
+
+ return output_sectors
+
+ def map(self, domain: tuple, rang: tuple, x: Number) -> Number:
+ m = (rang[1] - rang[0]) / (domain[1] - domain[0])
+ b = rang[0] - m * domain[0]
+ return m * x + b
diff --git a/utils/tuning/libtuning/image.py b/utils/tuning/libtuning/image.py
new file mode 100644
index 00000000..aa9d20b5
--- /dev/null
+++ b/utils/tuning/libtuning/image.py
@@ -0,0 +1,136 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+#
+# image.py - Container for an image and associated metadata
+
+import binascii
+import numpy as np
+from pathlib import Path
+import pyexiv2 as pyexif
+import rawpy as raw
+import re
+
+import libtuning as lt
+import libtuning.utils as utils
+
+
+class Image:
+ def __init__(self, path: Path):
+ self.path = path
+ self.lsc_only = False
+ self.color = -1
+ self.lux = -1
+
+ try:
+ self._load_metadata_exif()
+ except Exception as e:
+ utils.eprint(f'Failed to load metadata from {self.path}: {e}')
+ raise e
+
+ try:
+ self._read_image_dng()
+ except Exception as e:
+ utils.eprint(f'Failed to load image data from {self.path}: {e}')
+ raise e
+
+ @property
+ def name(self):
+ return self.path.name
+
+ # May raise KeyError as there are too many to check
+ def _load_metadata_exif(self):
+ # RawPy doesn't load all the image tags that we need, so we use py3exiv2
+ metadata = pyexif.ImageMetadata(str(self.path))
+ metadata.read()
+
+ # The DNG and TIFF/EP specifications use different IFDs to store the
+ # raw image data and the Exif tags. DNG stores them in a SubIFD and in
+ # an Exif IFD respectively (named "SubImage1" and "Photo" by pyexiv2),
+ # while TIFF/EP stores them both in IFD0 (name "Image"). Both are used
+ # in "DNG" files, with libcamera-apps following the DNG recommendation
+ # and applications based on picamera2 following TIFF/EP.
+ #
+ # This code detects which tags are being used, and therefore extracts the
+ # correct values.
+ try:
+ self.w = metadata['Exif.SubImage1.ImageWidth'].value
+ subimage = 'SubImage1'
+ photo = 'Photo'
+ except KeyError:
+ self.w = metadata['Exif.Image.ImageWidth'].value
+ subimage = 'Image'
+ photo = 'Image'
+ self.pad = 0
+ self.h = metadata[f'Exif.{subimage}.ImageLength'].value
+ white = metadata[f'Exif.{subimage}.WhiteLevel'].value
+ self.sigbits = int(white).bit_length()
+ self.fmt = (self.sigbits - 4) // 2
+ self.exposure = int(metadata[f'Exif.{photo}.ExposureTime'].value * 1000000)
+ self.againQ8 = metadata[f'Exif.{photo}.ISOSpeedRatings'].value * 256 / 100
+ self.againQ8_norm = self.againQ8 / 256
+ self.camName = metadata['Exif.Image.Model'].value
+ self.blacklevel = int(metadata[f'Exif.{subimage}.BlackLevel'].value[0])
+ self.blacklevel_16 = self.blacklevel << (16 - self.sigbits)
+
+ # Channel order depending on bayer pattern
+ # The key is the order given by exif, where 0 is R, 1 is G, and 2 is B
+ # The value is the index where the color can be found, where the first
+ # is R, then G, then G, then B.
+ bayer_case = {
+ '0 1 1 2': (lt.Color.R, lt.Color.GR, lt.Color.GB, lt.Color.B),
+ '1 2 0 1': (lt.Color.GB, lt.Color.R, lt.Color.B, lt.Color.GR),
+ '2 1 1 0': (lt.Color.B, lt.Color.GB, lt.Color.GR, lt.Color.R),
+ '1 0 2 1': (lt.Color.GR, lt.Color.R, lt.Color.B, lt.Color.GB)
+ }
+ # Note: This needs to be in IFD0
+ cfa_pattern = metadata[f'Exif.{subimage}.CFAPattern'].value
+ self.order = bayer_case[cfa_pattern]
+
+ def _read_image_dng(self):
+ raw_im = raw.imread(str(self.path))
+ raw_data = raw_im.raw_image
+ shift = 16 - self.sigbits
+ c0 = np.left_shift(raw_data[0::2, 0::2].astype(np.int64), shift)
+ c1 = np.left_shift(raw_data[0::2, 1::2].astype(np.int64), shift)
+ c2 = np.left_shift(raw_data[1::2, 0::2].astype(np.int64), shift)
+ c3 = np.left_shift(raw_data[1::2, 1::2].astype(np.int64), shift)
+ self.channels = [c0, c1, c2, c3]
+ # Reorder the channels into R, GR, GB, B
+ self.channels = [self.channels[i] for i in self.order]
+
+ # \todo Move this to macbeth.py
+ def get_patches(self, cen_coords, size=16):
+ saturated = False
+
+ # Obtain channel widths and heights
+ ch_w, ch_h = self.w, self.h
+ cen_coords = list(np.array((cen_coords[0])).astype(np.int32))
+ self.cen_coords = cen_coords
+
+ # Squares are ordered by stacking macbeth chart columns from left to
+ # right. Some useful patch indices:
+ # white = 3
+ # black = 23
+ # 'reds' = 9, 10
+ # 'blues' = 2, 5, 8, 20, 22
+ # 'greens' = 6, 12, 17
+ # greyscale = 3, 7, 11, 15, 19, 23
+ all_patches = []
+ for ch in self.channels:
+ ch_patches = []
+ for cen in cen_coords:
+ # Macbeth centre is placed at top left of central 2x2 patch to
+ # account for rounding. Patch pixels are sorted by pixel
+ # brightness so spatial information is lost.
+ patch = ch[cen[1] - 7:cen[1] + 9, cen[0] - 7:cen[0] + 9].flatten()
+ patch.sort()
+ if patch[-5] == (2**self.sigbits - 1) * 2**(16 - self.sigbits):
+ saturated = True
+ ch_patches.append(patch)
+
+ all_patches.append(ch_patches)
+
+ self.patches = all_patches
+
+ return not saturated
diff --git a/utils/tuning/libtuning/libtuning.py b/utils/tuning/libtuning/libtuning.py
new file mode 100644
index 00000000..d84c148f
--- /dev/null
+++ b/utils/tuning/libtuning/libtuning.py
@@ -0,0 +1,208 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# libtuning.py - An infrastructure for camera tuning tools
+
+import argparse
+
+import libtuning as lt
+import libtuning.utils as utils
+from libtuning.utils import eprint
+
+from enum import Enum, IntEnum
+
+
+class Color(IntEnum):
+ R = 0
+ GR = 1
+ GB = 2
+ B = 3
+
+
+class Debug(Enum):
+ Plot = 1
+
+
+# @brief What to do with the leftover pixels after dividing them into ALSC
+# sectors, when the division gradient is uniform
+# @var Float Force floating point division so all sectors divide equally
+# @var DistributeFront Divide the remainder equally (until running out,
+# obviously) into the existing sectors, starting from the front
+# @var DistributeBack Same as DistributeFront but starting from the back
+class Remainder(Enum):
+ Float = 0
+ DistributeFront = 1
+ DistributeBack = 2
+
+
+# @brief A helper class to contain a default value for a module configuration
+# parameter
+class Param(object):
+ # @var Required The value contained in this instance is irrelevant, and the
+ # value must be provided by the tuning configuration file.
+ # @var Optional If the value is not provided by the tuning configuration
+ # file, then the value contained in this instance will be used instead.
+ # @var Hardcode The value contained in this instance will be used
+ class Mode(Enum):
+ Required = 0
+ Optional = 1
+ Hardcode = 2
+
+ # @param name Name of the parameter. Shall match the name used in the
+ # configuration file for the parameter
+ # @param required Whether or not a value is required in the config
+ # parameter of get_value()
+ # @param val Default value (only relevant if mode is Optional)
+ def __init__(self, name: str, required: Mode, val=None):
+ self.name = name
+ self.__required = required
+ self.val = val
+
+ def get_value(self, config: dict):
+ if self.__required is self.Mode.Hardcode:
+ return self.val
+
+ if self.__required is self.Mode.Required and self.name not in config:
+ raise ValueError(f'Parameter {self.name} is required but not provided in the configuration')
+
+ return config[self.name] if self.required else self.val
+
+ @property
+ def required(self):
+ return self.__required is self.Mode.Required
+
+ # @brief Used by libtuning to auto-generate help information for the tuning
+ # script on the available parameters for the configuration file
+ # \todo Implement this
+ @property
+ def info(self):
+ raise NotImplementedError
+
+
+class Tuner(object):
+
+ # External functions
+
+ def __init__(self, platform_name):
+ self.name = platform_name
+ self.modules = []
+ self.parser = None
+ self.generator = None
+ self.output_order = []
+ self.config = {}
+ self.output = {}
+
+ def add(self, module):
+ self.modules.append(module)
+
+ def set_input_parser(self, parser):
+ self.parser = parser
+
+ def set_output_formatter(self, output):
+ self.generator = output
+
+ def set_output_order(self, modules):
+ self.output_order = modules
+
+ # @brief Convert classes in self.output_order to the instances in self.modules
+ def _prepare_output_order(self):
+ output_order = self.output_order
+ self.output_order = []
+ for module_type in output_order:
+ modules = [module for module in self.modules if module.type == module_type.type]
+ if len(modules) > 1:
+ eprint(f'Multiple modules found for module type "{module_type.type}"')
+ return False
+ if len(modules) < 1:
+ eprint(f'No module found for module type "{module_type.type}"')
+ return False
+ self.output_order.append(modules[0])
+
+ return True
+
+ # \todo Validate parser and generator at Tuner construction time?
+ def _validate_settings(self):
+ if self.parser is None:
+ eprint('Missing parser')
+ return False
+
+ if self.generator is None:
+ eprint('Missing generator')
+ return False
+
+ if len(self.modules) == 0:
+ eprint('No modules added')
+ return False
+
+ if len(self.output_order) != len(self.modules):
+ eprint('Number of outputs does not match number of modules')
+ return False
+
+ return True
+
+ def _process_args(self, argv, platform_name):
+ parser = argparse.ArgumentParser(description=f'Camera Tuning for {platform_name}')
+ parser.add_argument('-i', '--input', type=str, required=True,
+ help='''Directory containing calibration images (required).
+ Images for ALSC must be named "alsc_{Color Temperature}k_1[u].dng",
+ and all other images must be named "{Color Temperature}k_{Lux Level}l.dng"''')
+ parser.add_argument('-o', '--output', type=str, required=True,
+ help='Output file (required)')
+ # It is not our duty to scan all modules to figure out their default
+ # options, so simply return an empty configuration if none is provided.
+ parser.add_argument('-c', '--config', type=str, default='',
+ help='Config file (optional)')
+ # \todo Check if we really need this or if stderr is good enough, or if
+ # we want a better logging infrastructure with log levels
+ parser.add_argument('-l', '--log', type=str, default=None,
+ help='Output log file (optional)')
+ return parser.parse_args(argv[1:])
+
+ def run(self, argv):
+ args = self._process_args(argv, self.name)
+ if args is None:
+ return -1
+
+ if not self._validate_settings():
+ return -1
+
+ if not self._prepare_output_order():
+ return -1
+
+ if len(args.config) > 0:
+ self.config, disable = self.parser.parse(args.config, self.modules)
+ else:
+ self.config = {'general': {}}
+ disable = []
+
+ # Remove disabled modules
+ for module in disable:
+ if module in self.modules:
+ self.modules.remove(module)
+
+ for module in self.modules:
+ if not module.validate_config(self.config):
+ eprint(f'Config is invalid for module {module.type}')
+ return -1
+
+ has_lsc = any(isinstance(m, lt.modules.lsc.LSC) for m in self.modules)
+ # Only one LSC module allowed
+ has_only_lsc = has_lsc and len(self.modules) == 1
+
+ images = utils.load_images(args.input, self.config, not has_only_lsc, has_lsc)
+ if images is None or len(images) == 0:
+ eprint(f'No images were found, or able to load')
+ return -1
+
+ # Do the tuning
+ for module in self.modules:
+ out = module.process(self.config, images, self.output)
+ if out is None:
+ eprint(f'Module {module.name} failed to process, aborting')
+ break
+ self.output[module] = out
+
+ self.generator.write(args.output, self.output, self.output_order)
+
+ return 0
diff --git a/utils/tuning/libtuning/macbeth.py b/utils/tuning/libtuning/macbeth.py
new file mode 100644
index 00000000..5faddf66
--- /dev/null
+++ b/utils/tuning/libtuning/macbeth.py
@@ -0,0 +1,516 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+#
+# macbeth.py - Locate and extract Macbeth charts from images
+# (Copied from: ctt_macbeth_locator.py)
+
+# \todo Add debugging
+
+import cv2
+import os
+from pathlib import Path
+import numpy as np
+
+from libtuning.image import Image
+
+
+# Reshape image to fixed width without distorting returns image and scale
+# factor
+def reshape(img, width):
+ factor = width / img.shape[0]
+ return cv2.resize(img, None, fx=factor, fy=factor), factor
+
+
+# Correlation function to quantify match
+def correlate(im1, im2):
+ f1 = im1.flatten()
+ f2 = im2.flatten()
+ cor = np.corrcoef(f1, f2)
+ return cor[0][1]
+
+
+# @brief Compute coordinates of macbeth chart vertices and square centres
+# @return (max_cor, best_map_col_norm, fit_coords, success)
+#
+# Also returns an error/success message for debugging purposes. Additionally,
+# it scores the match with a confidence value.
+#
+# Brief explanation of the macbeth chart locating algorithm:
+# - Find rectangles within image
+# - Take rectangles within percentage offset of median perimeter. The
+# assumption is that these will be the macbeth squares
+# - For each potential square, find the 24 possible macbeth centre locations
+# that would produce a square in that location
+# - Find clusters of potential macbeth chart centres to find the potential
+# macbeth centres with the most votes, i.e. the most likely ones
+# - For each potential macbeth centre, use the centres of the squares that
+# voted for it to find macbeth chart corners
+# - For each set of corners, transform the possible match into normalised
+# space and correlate with a reference chart to evaluate the match
+# - Select the highest correlation as the macbeth chart match, returning the
+# correlation as the confidence score
+#
+# \todo Clean this up
+def get_macbeth_chart(img, ref_data):
+ ref, ref_w, ref_h, ref_corns = ref_data
+
+ # The code will raise and catch a MacbethError in case of a problem, trying
+ # to give some likely reasons why the problem occured, hence the try/except
+ try:
+ # Obtain image, convert to grayscale and normalise
+ src = img
+ src, factor = reshape(src, 200)
+ original = src.copy()
+ a = 125 / np.average(src)
+ src_norm = cv2.convertScaleAbs(src, alpha=a, beta=0)
+
+ # This code checks if there are seperate colour channels. In the past the
+ # macbeth locator ran on jpgs and this makes it robust to different
+ # filetypes. Note that running it on a jpg has 4x the pixels of the
+ # average bayer channel so coordinates must be doubled.
+
+ # This is best done in img_load.py in the get_patches method. The
+ # coordinates and image width, height must be divided by two if the
+ # macbeth locator has been run on a demosaicked image.
+ if len(src_norm.shape) == 3:
+ src_bw = cv2.cvtColor(src_norm, cv2.COLOR_BGR2GRAY)
+ else:
+ src_bw = src_norm
+ original_bw = src_bw.copy()
+
+ # Obtain image edges
+ sigma = 2
+ src_bw = cv2.GaussianBlur(src_bw, (0, 0), sigma)
+ t1, t2 = 50, 100
+ edges = cv2.Canny(src_bw, t1, t2)
+
+ # Dilate edges to prevent self-intersections in contours
+ k_size = 2
+ kernel = np.ones((k_size, k_size))
+ its = 1
+ edges = cv2.dilate(edges, kernel, iterations=its)
+
+ # Find contours in image
+ conts, _ = cv2.findContours(edges, cv2.RETR_TREE,
+ cv2.CHAIN_APPROX_NONE)
+ if len(conts) == 0:
+ raise MacbethError(
+ '\nWARNING: No macbeth chart found!'
+ '\nNo contours found in image\n'
+ 'Possible problems:\n'
+ '- Macbeth chart is too dark or bright\n'
+ '- Macbeth chart is occluded\n'
+ )
+
+ # Find quadrilateral contours
+ epsilon = 0.07
+ conts_per = []
+ for i in range(len(conts)):
+ per = cv2.arcLength(conts[i], True)
+ poly = cv2.approxPolyDP(conts[i], epsilon * per, True)
+ if len(poly) == 4 and cv2.isContourConvex(poly):
+ conts_per.append((poly, per))
+
+ if len(conts_per) == 0:
+ raise MacbethError(
+ '\nWARNING: No macbeth chart found!'
+ '\nNo quadrilateral contours found'
+ '\nPossible problems:\n'
+ '- Macbeth chart is too dark or bright\n'
+ '- Macbeth chart is occluded\n'
+ '- Macbeth chart is out of camera plane\n'
+ )
+
+ # Sort contours by perimeter and get perimeters within percent of median
+ conts_per = sorted(conts_per, key=lambda x: x[1])
+ med_per = conts_per[int(len(conts_per) / 2)][1]
+ side = med_per / 4
+ perc = 0.1
+ med_low, med_high = med_per * (1 - perc), med_per * (1 + perc)
+ squares = []
+ for i in conts_per:
+ if med_low <= i[1] and med_high >= i[1]:
+ squares.append(i[0])
+
+ # Obtain coordinates of nomralised macbeth and squares
+ square_verts, mac_norm = get_square_verts(0.06)
+ # For each square guess, find 24 possible macbeth chart centres
+ mac_mids = []
+ squares_raw = []
+ for i in range(len(squares)):
+ square = squares[i]
+ squares_raw.append(square)
+
+ # Convert quads to rotated rectangles. This is required as the
+ # 'squares' are usually quite irregular quadrilaterls, so
+ # performing a transform would result in exaggerated warping and
+ # inaccurate macbeth chart centre placement
+ rect = cv2.minAreaRect(square)
+ square = cv2.boxPoints(rect).astype(np.float32)
+
+ # Reorder vertices to prevent 'hourglass shape'
+ square = sorted(square, key=lambda x: x[0])
+ square_1 = sorted(square[:2], key=lambda x: x[1])
+ square_2 = sorted(square[2:], key=lambda x: -x[1])
+ square = np.array(np.concatenate((square_1, square_2)), np.float32)
+ square = np.reshape(square, (4, 2)).astype(np.float32)
+ squares[i] = square
+
+ # Find 24 possible macbeth chart centres by trasnforming normalised
+ # macbeth square vertices onto candidate square vertices found in image
+ for j in range(len(square_verts)):
+ verts = square_verts[j]
+ p_mat = cv2.getPerspectiveTransform(verts, square)
+ mac_guess = cv2.perspectiveTransform(mac_norm, p_mat)
+ mac_guess = np.round(mac_guess).astype(np.int32)
+
+ mac_mid = np.mean(mac_guess, axis=1)
+ mac_mids.append([mac_mid, (i, j)])
+
+ if len(mac_mids) == 0:
+ raise MacbethError(
+ '\nWARNING: No macbeth chart found!'
+ '\nNo possible macbeth charts found within image'
+ '\nPossible problems:\n'
+ '- Part of the macbeth chart is outside the image\n'
+ '- Quadrilaterals in image background\n'
+ )
+
+ # Reshape data
+ for i in range(len(mac_mids)):
+ mac_mids[i][0] = mac_mids[i][0][0]
+
+ # Find where midpoints cluster to identify most likely macbeth centres
+ clustering = cluster.AgglomerativeClustering(
+ n_clusters=None,
+ compute_full_tree=True,
+ distance_threshold=side * 2
+ )
+ mac_mids_list = [x[0] for x in mac_mids]
+
+ if len(mac_mids_list) == 1:
+ # Special case of only one valid centre found (probably not needed)
+ clus_list = []
+ clus_list.append([mac_mids, len(mac_mids)])
+
+ else:
+ clustering.fit(mac_mids_list)
+
+ # Create list of all clusters
+ clus_list = []
+ if clustering.n_clusters_ > 1:
+ for i in range(clustering.labels_.max() + 1):
+ indices = [j for j, x in enumerate(clustering.labels_) if x == i]
+ clus = []
+ for index in indices:
+ clus.append(mac_mids[index])
+ clus_list.append([clus, len(clus)])
+ clus_list.sort(key=lambda x: -x[1])
+
+ elif clustering.n_clusters_ == 1:
+ # Special case of only one cluster found
+ clus_list.append([mac_mids, len(mac_mids)])
+ else:
+ raise MacbethError(
+ '\nWARNING: No macebth chart found!'
+ '\nNo clusters found'
+ '\nPossible problems:\n'
+ '- NA\n'
+ )
+
+ # Keep only clusters with enough votes
+ clus_len_max = clus_list[0][1]
+ clus_tol = 0.7
+ for i in range(len(clus_list)):
+ if clus_list[i][1] < clus_len_max * clus_tol:
+ clus_list = clus_list[:i]
+ break
+ cent = np.mean(clus_list[i][0], axis=0)[0]
+ clus_list[i].append(cent)
+
+ # Get centres of each normalised square
+ reference = get_square_centres(0.06)
+
+ # For each possible macbeth chart, transform image into
+ # normalised space and find correlation with reference
+ max_cor = 0
+ best_map = None
+ best_fit = None
+ best_cen_fit = None
+ best_ref_mat = None
+
+ for clus in clus_list:
+ clus = clus[0]
+ sq_cents = []
+ ref_cents = []
+ i_list = [p[1][0] for p in clus]
+ for point in clus:
+ i, j = point[1]
+
+ # Remove any square that voted for two different points within
+ # the same cluster. This causes the same point in the image to be
+ # mapped to two different reference square centres, resulting in
+ # a very distorted perspective transform since cv2.findHomography
+ # simply minimises error.
+ # This phenomenon is not particularly likely to occur due to the
+ # enforced distance threshold in the clustering fit but it is
+ # best to keep this in just in case.
+ if i_list.count(i) == 1:
+ square = squares_raw[i]
+ sq_cent = np.mean(square, axis=0)
+ ref_cent = reference[j]
+ sq_cents.append(sq_cent)
+ ref_cents.append(ref_cent)
+
+ # At least four squares need to have voted for a centre in
+ # order for a transform to be found
+ if len(sq_cents) < 4:
+ raise MacbethError(
+ '\nWARNING: No macbeth chart found!'
+ '\nNot enough squares found'
+ '\nPossible problems:\n'
+ '- Macbeth chart is occluded\n'
+ '- Macbeth chart is too dark of bright\n'
+ )
+
+ ref_cents = np.array(ref_cents)
+ sq_cents = np.array(sq_cents)
+
+ # Find best fit transform from normalised centres to image
+ h_mat, mask = cv2.findHomography(ref_cents, sq_cents)
+ if 'None' in str(type(h_mat)):
+ raise MacbethError(
+ '\nERROR\n'
+ )
+
+ # Transform normalised corners and centres into image space
+ mac_fit = cv2.perspectiveTransform(mac_norm, h_mat)
+ mac_cen_fit = cv2.perspectiveTransform(np.array([reference]), h_mat)
+
+ # Transform located corners into reference space
+ ref_mat = cv2.getPerspectiveTransform(
+ mac_fit,
+ np.array([ref_corns])
+ )
+ map_to_ref = cv2.warpPerspective(
+ original_bw, ref_mat,
+ (ref_w, ref_h)
+ )
+
+ # Normalise brigthness
+ a = 125 / np.average(map_to_ref)
+ map_to_ref = cv2.convertScaleAbs(map_to_ref, alpha=a, beta=0)
+
+ # Find correlation with bw reference macbeth
+ cor = correlate(map_to_ref, ref)
+
+ # Keep only if best correlation
+ if cor > max_cor:
+ max_cor = cor
+ best_map = map_to_ref
+ best_fit = mac_fit
+ best_cen_fit = mac_cen_fit
+ best_ref_mat = ref_mat
+
+ # Rotate macbeth by pi and recorrelate in case macbeth chart is
+ # upside-down
+ mac_fit_inv = np.array(
+ ([[mac_fit[0][2], mac_fit[0][3],
+ mac_fit[0][0], mac_fit[0][1]]])
+ )
+ mac_cen_fit_inv = np.flip(mac_cen_fit, axis=1)
+ ref_mat = cv2.getPerspectiveTransform(
+ mac_fit_inv,
+ np.array([ref_corns])
+ )
+ map_to_ref = cv2.warpPerspective(
+ original_bw, ref_mat,
+ (ref_w, ref_h)
+ )
+ a = 125 / np.average(map_to_ref)
+ map_to_ref = cv2.convertScaleAbs(map_to_ref, alpha=a, beta=0)
+ cor = correlate(map_to_ref, ref)
+ if cor > max_cor:
+ max_cor = cor
+ best_map = map_to_ref
+ best_fit = mac_fit_inv
+ best_cen_fit = mac_cen_fit_inv
+ best_ref_mat = ref_mat
+
+ # Check best match is above threshold
+ cor_thresh = 0.6
+ if max_cor < cor_thresh:
+ raise MacbethError(
+ '\nWARNING: Correlation too low'
+ '\nPossible problems:\n'
+ '- Bad lighting conditions\n'
+ '- Macbeth chart is occluded\n'
+ '- Background is too noisy\n'
+ '- Macbeth chart is out of camera plane\n'
+ )
+
+ # Represent coloured macbeth in reference space
+ best_map_col = cv2.warpPerspective(
+ original, best_ref_mat, (ref_w, ref_h)
+ )
+ best_map_col = cv2.resize(
+ best_map_col, None, fx=4, fy=4
+ )
+ a = 125 / np.average(best_map_col)
+ best_map_col_norm = cv2.convertScaleAbs(
+ best_map_col, alpha=a, beta=0
+ )
+
+ # Rescale coordinates to original image size
+ fit_coords = (best_fit / factor, best_cen_fit / factor)
+
+ return (max_cor, best_map_col_norm, fit_coords, True)
+
+ # Catch macbeth errors and continue with code
+ except MacbethError as error:
+ eprint(error)
+ return (0, None, None, False)
+
+
+def find_macbeth(img, mac_config):
+ small_chart = mac_config['small']
+ show = mac_config['show']
+
+ # Catch the warnings
+ warnings.simplefilter("ignore")
+ warnings.warn("runtime", RuntimeWarning)
+
+ # Reference macbeth chart is created that will be correlated with the
+ # located macbeth chart guess to produce a confidence value for the match.
+ script_dir = Path(os.path.realpath(os.path.dirname(__file__)))
+ macbeth_ref_path = script_dir.joinpath('macbeth_ref.pgm')
+ ref = cv2.imread(str(macbeth_ref_path), flags=cv2.IMREAD_GRAYSCALE)
+ ref_w = 120
+ ref_h = 80
+ rc1 = (0, 0)
+ rc2 = (0, ref_h)
+ rc3 = (ref_w, ref_h)
+ rc4 = (ref_w, 0)
+ ref_corns = np.array((rc1, rc2, rc3, rc4), np.float32)
+ ref_data = (ref, ref_w, ref_h, ref_corns)
+
+ # Locate macbeth chart
+ cor, mac, coords, ret = get_macbeth_chart(img, ref_data)
+
+ # Following bits of code try to fix common problems with simple techniques.
+ # If now or at any point the best correlation is of above 0.75, then
+ # nothing more is tried as this is a high enough confidence to ensure
+ # reliable macbeth square centre placement.
+
+ for brightness in [2, 4]:
+ if cor >= 0.75:
+ break
+ img_br = cv2.convertScaleAbs(img, alpha=brightness, beta=0)
+ cor_b, mac_b, coords_b, ret_b = get_macbeth_chart(img_br, ref_data)
+ if cor_b > cor:
+ cor, mac, coords, ret = cor_b, mac_b, coords_b, ret_b
+
+ # In case macbeth chart is too small, take a selection of the image and
+ # attempt to locate macbeth chart within that. The scale increment is
+ # root 2
+
+ # These variables will be used to transform the found coordinates at
+ # smaller scales back into the original. If ii is still -1 after this
+ # section that means it was not successful
+ ii = -1
+ w_best = 0
+ h_best = 0
+ d_best = 100
+
+ # d_best records the scale of the best match. Macbeth charts are only looked
+ # for at one scale increment smaller than the current best match in order to avoid
+ # unecessarily searching for macbeth charts at small scales.
+ # If a macbeth chart ha already been found then set d_best to 0
+ if cor != 0:
+ d_best = 0
+
+ for index, pair in enumerate([{'sel': 2 / 3, 'inc': 1 / 6},
+ {'sel': 1 / 2, 'inc': 1 / 8},
+ {'sel': 1 / 3, 'inc': 1 / 12},
+ {'sel': 1 / 4, 'inc': 1 / 16}]):
+ if cor >= 0.75:
+ break
+
+ # Check if we need to check macbeth charts at even smaller scales. This
+ # slows the code down significantly and has therefore been omitted by
+ # default, however it is not unusably slow so might be useful if the
+ # macbeth chart is too small to be picked up to by the current
+ # subselections. Use this for macbeth charts with side lengths around
+ # 1/5 image dimensions (and smaller...?) it is, however, recommended
+ # that macbeth charts take up as large as possible a proportion of the
+ # image.
+ if index >= 2 and (not small_chart or d_best <= index - 1):
+ break
+
+ w, h = list(img.shape[:2])
+ # Set dimensions of the subselection and the step along each axis
+ # between selections
+ w_sel = int(w * pair['sel'])
+ h_sel = int(h * pair['sel'])
+ w_inc = int(w * pair['inc'])
+ h_inc = int(h * pair['inc'])
+
+ loop = ((1 - pair['sel']) / pair['inc']) + 1
+ # For each subselection, look for a macbeth chart
+ for i in range(loop):
+ for j in range(loop):
+ w_s, h_s = i * w_inc, j * h_inc
+ img_sel = img[w_s:w_s + w_sel, h_s:h_s + h_sel]
+ cor_ij, mac_ij, coords_ij, ret_ij = get_macbeth_chart(img_sel, ref_data)
+
+ # If the correlation is better than the best then record the
+ # scale and current subselection at which macbeth chart was
+ # found. Also record the coordinates, macbeth chart and message.
+ if cor_ij > cor:
+ cor = cor_ij
+ mac, coords, ret = mac_ij, coords_ij, ret_ij
+ ii, jj = i, j
+ w_best, h_best = w_inc, h_inc
+ d_best = index + 1
+
+ # Transform coordinates from subselection to original image
+ if ii != -1:
+ for a in range(len(coords)):
+ for b in range(len(coords[a][0])):
+ coords[a][0][b][1] += ii * w_best
+ coords[a][0][b][0] += jj * h_best
+
+ if not ret:
+ return None
+
+ coords_fit = coords
+ if cor < 0.75:
+ eprint(f'Warning: Low confidence {cor:.3f} for macbeth chart in {img.path.name}')
+
+ if show:
+ draw_macbeth_results(img, coords_fit)
+
+ return coords_fit
+
+
+def locate_macbeth(image: Image, config: dict):
+ # Find macbeth centres
+ av_chan = (np.mean(np.array(image.channels), axis=0) / (2**16))
+ av_val = np.mean(av_chan)
+ if av_val < image.blacklevel_16 / (2**16) + 1 / 64:
+ eprint(f'Image {image.path.name} too dark')
+ return None
+
+ macbeth = find_macbeth(av_chan, config['general']['macbeth'])
+
+ if macbeth is None:
+ eprint(f'No macbeth chart found in {image.path.name}')
+ return None
+
+ mac_cen_coords = macbeth[1]
+ if not image.get_patches(mac_cen_coords):
+ eprint(f'Macbeth patches have saturated in {image.path.name}')
+ return None
+
+ return macbeth
diff --git a/utils/tuning/libtuning/macbeth_ref.pgm b/utils/tuning/libtuning/macbeth_ref.pgm
new file mode 100644
index 00000000..37897140
--- /dev/null
+++ b/utils/tuning/libtuning/macbeth_ref.pgm
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: BSD-2-Clause
+P5
+# Reference macbeth chart
+120 80
+255
+  !#!" #!"&&$#$#'"%&#+2///..../.........-()))))))))))))))))))(((-,*)'(&)#($%(%"###""!%""&"&&!$" #!$ !"! $&**" !#5.,%+,-5"0<HBAA54" %##((()*+,---.........+*)))))))))))))))-.,,--+))('((''('%'%##"!""!"!""""#!   ! %‚/vÀ¯z:òøßãLñ©û¶ÑÔcÒ,!#""%%''')**+)-../..../.-*)))))))))))))**,,)**'(''&'((&&%%##$! !!!! ! !  !  5*"-)&7(1.75Rnge`\`$ ""!"%%%'')())++--/---,-..,-.,++**))))())*)*)''%'%&%&'&%%"""""        !  !!$&$$&##(+*,,/10122126545./66402006486869650*.1.***)*+)()&((('('##)('&%%&%$$$#$%$%$ (((*))('((('('(&%V0;>>;@@>@AAAACBCB=&<­·³µ¶¾¿ÃÇÇÆÇËÒÐÇÄ<5x–•ŠŽŒŠ‰„„„„|64RYVTSRRRMMNLKJJLH+&0gijgdeffmmnpnkji`#3™ ª¦¨¨£Ÿ›››š–—™šbY! 3FHHIIIHIJIIJHIII@#?¾ÈÊÍÏÑÔÖØÚÚÚÛßáßÔ=7}—š˜———˜—˜˜——˜——‘:5Wcbcbdcb`^^`^^_^Y,'6‰ŽŒ‰ˆˆˆ‡†…„††„‚r'<½ÆÅÅÅÄÂÀ¿¾¾¼»¼¼µl%2FHHIIHJJJJJJIIJI?%;ÁÌÌÒÓÖØÙÛÛÜÜÞßâãÕ>7|•™™ž™—˜˜˜—™™™š˜–;8Xfeeegeccb`^aba]Z+)<Ž“’‘‹Š‰‰‰‰ˆ†r)>¿ÇÇÇÆÅÅÄÂÁÁÀ¾¾¼·q#3GHIIIIJIIJJIHIJI@&5ÁÎÑÔÕØÙÚÜÜÞßßßàâ×=8~”•˜™š›šš™›šœ››“;8Zgghggedbdcbda^\Z+(;““’‘‘Ž‹‹ŠŠ‰ˆy)9¿ÈÈÈÇÇÅÄÂÁÁÀ¿½½¹z"3GIIJJJJJKJJJJJJJ@'4ÂÑÔÔÙÚÛÜÞÝßßààààØ>9|”—–—™ššš™›œŸ¥ ž˜=8Zhighgeeeedeca__[/)B’–•••“‘ŽŒŒŒŒŠv&:ÁÊÊÊÊÆÆÆÂÁÂÂÁ¿¿º|#3GJJIIJKKKJJJKKJK@&6ÆÒ××ÙÛÛÞÞßààààààÖ>9~”———˜˜—™šžž    ˜<8Yghegggffihccab^\/*C“™˜—––””’‘‘Žz'9ÄÍËÈÈÇÇÆÆÄÂÂÀÀ¿»‚$  6IKJJMMMKMKKMKKMLC&2É××ÙÛÜßÞàááâââââÖ@9•——˜˜™˜˜š››žŸžž—<9Yghhhhijiegdcebc^0)G—›š™˜˜˜–•“’‘Ž(7ÃÍÌËÊÈÇÇÅÆÄÂÂÂÁº‰% 6JLMMNMMKMMNMMMMMD&2ÊÙÙÛÝßßßààáââáãâÖ@:~”—™™š™™››žžžžž—=9Xfghhjiigdgddedc`1)M—œ›š˜™—•”‘’‘Ž}(:ÄÐÍÌËÊÇÆÆÆÅÂÄÁ¾& "8LNOONNOMONNMMNOND'3ÍÛÛÞßàààáââãâåãå×@;–˜˜™žŸŸ  ¡¡  —=:Ziiigheegegegggdc1,Q›ŸŸž›šš˜––““‘~)8ÂÍÎÌËÊÊÈÆÆÆÆÄÆÇÁ•%# "9NNNPPPQOOOOONNOOD'0ÎÜÜßßáàáââååäãåæ×?;–˜—™šœžŸ¡¡ ¡Ÿ  ™=;[iigeeegghgdedgea0-P› ¡ žš˜—–•”(8ÃÏÎÎÌÊÈÈÇÇÇÆÈÇÆÃ' "#$:NNOQPPRPQPOOPQPPD*1ÐßßàààâãããåææåææÛA;‚˜™™šœžžŸ  Ÿž Ÿ—;:Yfghgghgghghhdggc3.\¡£¡  Ÿœœš˜—•’‘~);ÅÎÎÑÐÌËÊÇÈÉÊÊÇŤ(&%%;OQQQRSSRPQQQQSQQF)3ÓßàááãâãåææææææçÜB<ƒ™šœœžžžžŸ žŸ Ÿž—=:Wfhghhhihggghfhee4/f ¥¤¢¡¡ŸŸš˜—””’‘‚*:ÇÏÍÍÎÎÍÌÉÈËÊÈÆÆä&%%%?RSSSSSTTTTSSSTTRE)5ÕàááãâäåæåæçççèèÛB=„šœœžŸ Ÿ ¡ žŸŸŸ˜@:Ygiihhiiiihihiiif72p £¤¤£ ŸŸœœ™—–•’‘}(9ÇÎÏÎÍÍÍÍÍËÌÊÈÈÇÆ©'#%&?TUTTTUUQSTTTTTVSF*3ÕàãâãäåæææçççèééßF>†žž  ¡¡£ £¡¡¡ Ÿ˜A;[ghjiihiiiihihije50r¢¦¥¥££ Ÿžœš™—–““‚)6ÈÏÏÎÌÎÎÌÏÏËÊÊÈÈÆ«& &#%?SVVVUUUUUTUUVVUUG*5ÖãããåæææçèèèèééëßF=…ŸŸ¢££££ ¡¡  £ ˜A;Yhijiiijjiiiiijje81t¦¦¦¥¥£¡ Ÿ›˜——•’~)5ÇÑÑÏÎËÍÍÑÑÌËÈÈÉÆ°' '$$=OQRRQQPRSRSSSSSSG+6ËÙÙÜÛÜÞÝßààààáããÙD@‚š›œŸœžœ›š”?;Wefgggggfffgeeefc41xŸžŸž››š˜•”’‘ŽŒ{*5¾ÈÈÇÅÃÃÄÄÃÂÂÂÀ¿¼«( &&&'++++,,*-,-00-0100*-SUX\]]`_ffgiooopo=;X\bedbadbca`]\]ZZ;;<::8:;9983433110/-,...1//12410/..--+)"",---,-./,,.-/-0-( &&%+/0103322011223233)(34534767::;;==:=B9;BFGEEGIKJKIJGIJCD=<:76566554111/0/1.*+00233300/00//..,+*#")(*)++,++))*++**'!!&$*w³½¾¿Â¼ÀÀ¼¼·¹¹¸´²Ž1-_addc`ceccdccedbb?A|ŒŒ‘‹ŒŒ‹ŠŠ‰‰ˆB>=>?@@?====;<:;:<:11r‹ŒŽ“–““•–˜™Ž+.’—”™ ¥¢¡¤žšŸŸœ( !'%*zÀÇÆÆÇÇÊÊÈÈÈÊËËËÉ 42gjmllklomooonpopmHG‘©¬«««¬©«««ª««ª©£D>AEDEFEECEECCCDDEC46µåçèçççæåäãáàÞÜÚ׿0:Î×Ö×××ÖÕÒÓÏÐÐÍÍѾ,!!&&,|ÂÇÇÇÇÇÇËËÇÈÊËËÍÊ¡61inknnoopoppoqqrqoEE”¬­­­®®®­®®¯­®®­¥FACGFFFFFFDFDDDDDDC57¹íñïîîíííëéçæãáßÝÄ09ÓÛÛÛÛÚÙØ×ÖÕÔÔÒÔÒÁ+!"%%-~ÀÆÈÊÇÇÈÉÌÌÊÊËÌÌÊ¡42inopppppoqqqrrsrnAB“«®®­®®®®®±­®¬°­¥C?DGGGGFFFFDFFDDEDC48ºíððïïîîíìëèçæãáßÅ1;ÔÞÞÝÜÚÚÙÙ×ÕÕÔÕÔÒÁ+!!"#*|¿ÄÉÊÈÈÈÈÉÍÉÈËÍÍÊ¡62imoppppqqqqrtrqtrGD•¬®®­°®°°°±±°®®­§H?CGGGGGGGGFFFFFFDB38»îðïïïïîíììëèçæâàÅ1<ÖààßÞÞÜÚÚÙÙÙ××ÔÔ½, !)}¿ÃÈÈÊÇÈÈËÎËÊËÌÍË¢63mooppqqqqqqrrtvtoDH—­­®±®°±°­°®­±°°¦JACHHGGHGGFFFDDGGFD29ÀðóòðïïïîííìêéèæâÆ3>ÖááààßÞÜÛÙÙÙØ×Ø×½, $){¼ÂÅÆÉÇÈÆËËÌÊËÊÍË¢53jpppqprqrrrttuvuo>H˜®°®±²±±°°°±°±°°ªJAFHHHHHGGHGGFGGFFE28ÁðôòòððïïîíëìëéçãÇ3:×ãáááßÞÝÛÛÚÙÚÚÚÚ½- "*{¸ÁÁÅÆÇÆÆÊËÌÉÊËÎÌ£53loqpqsqrrrtrutsvrAH—«®®±±°°°®±±±®­°©HCGHIHHHHHHGFGHGGGD5;ÀðóóòñððïîííìëëèäÇ28ØäãááààßÞÜÛÛÛÚÚÚÀ, +}¹¾ÀÂÂÅÅÅÇÉÍËÊËÌÊ¡52mqoqpqrttttttuurpFI–®°±°±±²°±±°±±¯°§OCEHHIHHHHGHGGFFIGF8<ÃðòòóóòððïíîìììéæÍ48ÚçåããáààßÝÜÜÜÜÛÛ¿, (|º¼¾ÀÀÃÄÄÆÇÍËÊÊËÊ¢41krqpqqqrrtrtuvtuoEH—­°°²±±±±¯²²®²±®«PBHHIIIHIIHIHGHGHHE7<ÃðóóòñððððïíííìêçÑ58ÜèæåãââáßßÜÞÜÞÞÚÄ* (zºº»¾ÀÂÂÂÄÄÇËÈÊËÊ¡63kpqprqqstttutrvvoFO˜¯°¯°±±°±±±±±°±²©LEHHIIHIHHHIGHGIHGF4=ÅñóóóððððïïîìíëéèÓ5<ÞêèçåââááßÞÞßßßÚÇ* 'zº½º»¾ÁÀÂÂÄÅÊÇÈÊÈ¡62lppqrqrrrtttuttvpAG›¯°±°±°°°°°±±°±±«MGHIIIIHIIIHHIIJHHG4<ÃñóóóðòððîîïííëéèÓ4<ÞëêççæãâáàßàÞÞÛØÇ+ !){º¼º»¾½ÀÁÁÂÄÉÇÇÉÈ 62jopqqqqqrtttutttrEH™¯±°°°¯°°±±²²°±±ªOHFIIIIIJIIIIHIHIHI7>ÅðôóòòòïðïîîíììëèÒ5;àíêèææãâáâßßÜÛÙÖÇ, !)z¼¾¼¹»»ÁÁ¿ÁÁÈÇÆÆÆŸ53lppqqrqrtttuuuutsFI™®±²±±±±²²²±°¯±²«RHGJIJHJKJJJIIIIIIH9>ÂñôôôòóððïîííìëééÓ5;àìééèææäááßÜÛØ×ÔÇ+  !({»¿¸º½½¿¾¿¾ÀÅÆÄÆÅœ41joppprqrrrutttvvrIH’­±°°°±±±²²°±²±±ªTHCJJJJJIJIJJIJJJIH7=ÂòôòóóñðïîîííììéèÒ5;ßìêêèæåäâàÞÛÙÖ×ÕÇ+ (u±±®¯±³µ²´´µº»¸»º‘65gjlmmmnoopnpprpqoIH¦©ª©«ªªª«¬«ªª¨ª¤OIBIJJJIJJJJIIIHHHG89ºåççæçåäããâáßàßÝÜÈ29ÔàßÝÛÛÙØÕÓÑÎÌÈÌʾ' "&,-*)-01/,0/12102-+04448789<>>??AFAD@DBCIJNRWTSUXT[WUQUOKFEBBABA?>>=<<;;67942:<<<>9999864565363&(13335422./1/-+..+ !"&$$""$"&$%'()(''*+-0124688:<>>??A>?EBCHKOLJLNOSQOXQQVMLACGHGHIGFHGDCCBB@??7432233210111.,++,++%(++)*(''%%%$$#%&$# ")0/001120024455520+-U]`addcdhefeekecYGFJRXYYVWWZWVXXVZTOBF}™œšœžœ›š™–™K7Ybccddfeg`^]^]\[Z[*)OTTPPQPOKOLLJJLIK  !1;:9:<<===;=???A@9*/„Ž‘’”•”––—™™š››’FJmxyxwyzzzxyzzz{zxLOÉÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú]=‹§©¨¦§¥¦¤¤¢¡¡¡ ›Ž.-‹’’Œ‰‡…‚€€€y# !!2><=;==>=<<>@@@@A9-0‡‘‘”—˜˜™—š›žŸ —IKnz||{|{||{}}~}}{zLOÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý]>Ž¬­¬««¨ª¨§¦¥¥££¡¡–..Œ––”“Ž‹‹‰‡…………„~% $2==;<>>?===>@A@AB;+1…Ž‘“•—™™˜˜™œžŸŸ—JJo{|y{||}{||}}}}}yMTÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý_>Ž¬­¬«ª©©¦¦¦¤¤££¢ ”-.–”‘‘ŽŠŠ‰…„…„…„}# %2<=;=<@?>==>?A@AA9+3…Ž‘“–——˜™šœšœœžž•FMlz{{y|}}}}||}|}}{MTÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýd>«¬«ªªª¨§¦¦¤¤¤¡  ”-,Œ“‘’Š‡†……„„„…# %1<<<;==<<=>?A?@AA:,3†Ž‘’•——˜˜šœšœœž–INo{{y{||||}|}}|~}{RTÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýd=Š©­¬«©©§¦¦¥¤¤£¡ Ÿ—/-‹’‘‹‹‰ˆ…………ƒƒ„}#!$0<<<=<<==>A@@>@AA:-2†‘“’–——™™šš™œ›œ—HInzz{{||{{}~~}}|}zMRÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýd=‰ª«ªªª§¥¥¥£¤¡¡  ”++ˆŽŽ‹ŠŠ‰………„„„ƒ„~# "$/;<==>;===@@@@>AA:+2†Ž’’“•—–™˜šœ™œ–KHn||y|||||{}~}|}|xMSÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýd=†©©ª©©§¦¦¥¤£¡Ÿ žœ’+,‡‹Š‰ˆ††…„„„„ƒ}# ! "/:<=>@<<>=@@@@@AA;-3„’’•–˜˜š™šššœ›˜MFs||{{{y}z}}|}|}}yMWÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýc>„©ª©§¦¦¥¤£¡£  ŸŸ›’,)…ŒŠ‹‰ˆ‡†…„„„ƒƒƒ|! !1;>?>><<>@>>=>ABB;,0ƒŽ‘’––™™™™ššœœ›˜LHr{|{|}|y|}}}}}zNXÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýc?„©«ª§§¦¥¥££   žžšŽ()„‹ŠŠ‰ˆ…†„„„„„‚ƒƒz# $/;;<=;<>>=>>>@@BB:,1†‘“•–—˜™šœšœšž˜IInyz||||||{||}{~|{NVÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýc;§¨¨¦¦¦¤££¡¡ŸŸœš“('ƒŠŠˆ‰ˆ……ƒ„ƒƒƒƒ‚€}# $0:<==<;>@>>>>@ABB:,/„‘““–˜™™™šœšš—HLlx|}y{y{|y{|}}}}yMRÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýd>~¥§¦¦§¥¤££¢ ŸžššŽ*(ƒŠ‡‡ˆˆ„ƒ‚„ƒƒ‚‚‚y" !&3:;<<;==@@=>AABBA;-3†‘“‘”–—˜˜™šœœš›–KLqz{|||y{}|}{}|~{zRQÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüc9w¤§¦¥¦¤¢£  Ÿžžš™Ž)'ˆ†……„…„ƒƒƒ‚€€€€y" !%1<<;=>===<=@@ABBC<.5†’’•–—™˜™™œœž•IIlz{|}~~~|}{||~}}zMUÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüd;p¤¦¥¥¤¤£¤¢ Ÿž›š˜)$€ˆˆ…„„„…‚‚€€€x" $2===<==@=<>=ABBBC?/0ˆ‘’•••˜—˜™™š™œž˜IGkz}}{||}{||y||}zyOVÊÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüc7o¢¥¥¤££¡¤¡žŸš™š˜‘'&~‡„„„„ƒ…„€~€z"#"#/;<:<<?>;===@?AAA>07‹‹Ž’’’”“•–—•‘GGgwxz{yyxyzzyz{yuuHO½ùûüüüüüüûûûúúúúò\8v›žœ›š™˜—•••”‘†'$w~~}|||{~|{zxxxxv!"""'*+(+)*))()+,,.../0398;=<=>DCCDDCBBDHBCJMMLMPNPOJPKPSJDICCNMPONMNNOKHIFDBHE3/46433323.....*+,)( !##!!!!!$#$$#$#&"!!"(+**,,*+.//1478:<:33ACDFGGIIHIJLPKNMQFIPTTRVXVUXUUTXUSTNEGGFDEFAA>==;94877520-,))*(((('&$#!!" &%'FQPQR]dqŒ˜£«¹ÍàðÈ=FñûüÿÿÿÿÿÿÿÿÿÿÿÿÿúQN·èììêìæéììêéëëéêáLEœ˜…znki^[YTPUOS;.%-/12322221/10//,/%#0¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß@QýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQMÁðôõôóôóôõõôõôôóæKE„¨©¨§§¤¥¥¢¤£ žžž˜H01NNQOQQOOMNNLKLJGB'&/¸ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâAWþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOLÀñóôôôóóóõôôõóôòèKE„¦¨©©§ª©ª¦¨§¥¢¤¢œF-,PQQPQPPQPOONMNNKE''0·ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáCZþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRMÁñôóóòòôòôõóôôóòåJE‚¥©¬¬©ª©ª§¥¥¤¤¤¢™F,*NSQPPQOOOOMNNMKID('2·ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáD[þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQKÀðòòòóóòóõóõóòòðæIF€§©ª©§©§©¥¤¤¤¤¡ ˜F,*NPPPPPPNOONMMMJIF!'(2¶ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáF]þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRL¿íððòðòòóóòòñïòðäHD£¦©©§¨¦¦¦¤¤¤¤¢ ˜F+%MPPPPOOONONNMMKID)*4¸ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáD^þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿPL¿ìðïïòòððòòðïðòïäIC€¢¦¨¨¥¦¥§¥¤££¡ŸŸ—F+&NPOOOPPOONMMKMKHD**6ºÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáD_þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQJ¾ëïïïòðððððïðïîïãFC~¢§¥¥¦¦¦¤££¤¡   ˜F,'MPOOOOONONNKKIIIG,+7»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáD^ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQI¾êîîîïðïðïððïïïîâEB|£¥¤££¤¤¤£¤¢Ÿ ŸŸ—E+&MONOOONNNNKMJKJHH,-8¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàD]þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿPI¼éíìîîîîñðòóóöù÷èHE¥¨§¥¥¤¤£¡£¡  žŸ—C,#LOOOONONNNKKKMKJF,*6»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáCaýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþMH»éììíîðððôóõöööõçIF‚©ª§¦¦¥££¢ Ÿžž Ÿ–D*%KONOMNMMKMKJJJIJE,,6»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâB^ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþMG¹èììîðòóòóóóóòóôéHB}£©¦¦§¥¤¤¢ŸŸžžš”D+&LONOOONNMMMMKLKIA,,6ºÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàA\ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþMF¹éìííïòóôððôöõööêIE¦ª©¦§¨§§¡¡Ÿš™”E+&LNNMONNMMKKKKKIHF --6¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßA[üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþKF¶çìðïððïðóöõöõùúîJC©«­«¦§¦¥¤¡¤žžš—F*&LMONMNMNKKJMKJJIF **5»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß>WüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþKE¶èïíðîðóöõøòùóöôçF?}¨©²¯¬¬©¥¤¤£žœ˜˜‘C*%KONNNJKKKMKJKJKID,*4¶ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØ<WöþÿÿÿÿÿÿÿÿÿÿÿÿÿÿøMA°áäååçêêïêëëåæéçÝGCxž¨¦ ©¥¤ šœ¡˜•’ŠB)%HKLKKJJJKIHIHHFGC!()*q ¡š›šš™““’‘‘’‹‹o39v|}wwwwwwrqtuspn=9^gadcfgce`dbUY[\^>;DIJDB?FEGE=7>8634.(&&(%&*&%%'+*)+*#%()''03364443233222243/-+133423333423766645789:><<<;<;<?=?;<<:78673/001113--.-+*)&&#"&$#%&""$!! ))+rbPpAD9-*******+*++)++--.//./.0/21453469:=;98<;<>=;><7766666741012.-13/-+-/(''&&&%%&$.%0()-%-#-#' #&(% )))hn›YQgÛ7(*))))*)**,--....../0/0001357666::;;>?>AA866666666656565300/20/.-*)(('((&&%)d=yoP¼<Ñ?ßFQFx;§2»1«0))*RQ.0*,,5*(*))))*,**,+/.../...02/22224456468;:>BB;>;:76666666666755303033/,.-*(())('&')#)"##(+$+*#)) & 
diff --git a/utils/tuning/libtuning/modules/__init__.py b/utils/tuning/libtuning/modules/__init__.py
new file mode 100644
index 00000000..9ccabb0e
--- /dev/null
+++ b/utils/tuning/libtuning/modules/__init__.py
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py
new file mode 100644
index 00000000..0ba4411b
--- /dev/null
+++ b/utils/tuning/libtuning/modules/lsc/__init__.py
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+
+from libtuning.modules.lsc.lsc import LSC
+from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi
+from libtuning.modules.lsc.rkisp1 import LSCRkISP1
diff --git a/utils/tuning/libtuning/modules/lsc/lsc.py b/utils/tuning/libtuning/modules/lsc/lsc.py
new file mode 100644
index 00000000..344a07a3
--- /dev/null
+++ b/utils/tuning/libtuning/modules/lsc/lsc.py
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+
+from ..module import Module
+
+import libtuning as lt
+import libtuning.utils as utils
+
+import numpy as np
+
+
+class LSC(Module):
+ type = 'lsc'
+ hr_name = 'LSC (Base)'
+ out_name = 'GenericLSC'
+
+ def __init__(self, *,
+ debug: list,
+ sector_shape: tuple,
+ sector_x_gradient: lt.Gradient,
+ sector_y_gradient: lt.Gradient,
+ sector_average_function: lt.Average,
+ smoothing_function: lt.Smoothing):
+ super().__init__()
+
+ self.debug = debug
+
+ self.sector_shape = sector_shape
+ self.sector_x_gradient = sector_x_gradient
+ self.sector_y_gradient = sector_y_gradient
+ self.sector_average_function = sector_average_function
+
+ self.smoothing_function = smoothing_function
+
+ def _enumerate_lsc_images(self, images):
+ for image in images:
+ if image.lsc_only:
+ yield image
+
+ def _get_grid(self, channel, img_w, img_h):
+ # List of number of pixels in each sector
+ sectors_x = self.sector_x_gradient.distribute(img_w / 2, self.sector_shape[0])
+ sectors_y = self.sector_y_gradient.distribute(img_h / 2, self.sector_shape[1])
+
+ grid = []
+
+ r = 0
+ for y in sectors_y:
+ c = 0
+ for x in sectors_x:
+ grid.append(self.sector_average_function.average(channel[r:r + y, c:c + x]))
+ c += x
+ r += y
+
+ return np.array(grid)
+
+ def _lsc_single_channel(self, channel: np.array,
+ image: lt.Image, green_grid: np.array = None):
+ grid = self._get_grid(channel, image.w, image.h)
+ grid -= image.blacklevel_16
+ if green_grid is None:
+ table = np.reshape(1 / grid, self.sector_shape[::-1])
+ else:
+ table = np.reshape(green_grid / grid, self.sector_shape[::-1])
+ table = self.smoothing_function.smoothing(table)
+
+ if green_grid is None:
+ table = table / np.min(table)
+
+ return table, grid
diff --git a/utils/tuning/libtuning/modules/lsc/raspberrypi.py b/utils/tuning/libtuning/modules/lsc/raspberrypi.py
new file mode 100644
index 00000000..58f5000d
--- /dev/null
+++ b/utils/tuning/libtuning/modules/lsc/raspberrypi.py
@@ -0,0 +1,246 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# raspberrypi.py - ALSC module for tuning Raspberry Pi
+
+from .lsc import LSC
+
+import libtuning as lt
+import libtuning.utils as utils
+
+from numbers import Number
+import numpy as np
+
+
+class ALSCRaspberryPi(LSC):
+ # Override the type name so that the parser can match the entry in the
+ # config file.
+ type = 'alsc'
+ hr_name = 'ALSC (Raspberry Pi)'
+ out_name = 'rpi.alsc'
+ compatible = ['raspberrypi']
+
+ def __init__(self, *,
+ do_color: lt.Param,
+ luminance_strength: lt.Param,
+ **kwargs):
+ super().__init__(**kwargs)
+
+ self.do_color = do_color
+ self.luminance_strength = luminance_strength
+
+ self.output_range = (0, 3.999)
+
+ def validate_config(self, config: dict) -> bool:
+ if self not in config:
+ utils.eprint(f'{self.type} not in config')
+ return False
+
+ valid = True
+
+ conf = config[self]
+
+ lum_key = self.luminance_strength.name
+ color_key = self.do_color.name
+
+ if lum_key not in conf and self.luminance_strength.required:
+ utils.eprint(f'{lum_key} is not in config')
+ valid = False
+
+ if lum_key in conf and (conf[lum_key] < 0 or conf[lum_key] > 1):
+ utils.eprint(f'Warning: {lum_key} is not in range [0, 1]; defaulting to 0.5')
+
+ if color_key not in conf and self.do_color.required:
+ utils.eprint(f'{color_key} is not in config')
+ valid = False
+
+ return valid
+
+ # @return Image color temperature, flattened array of red calibration table
+ # (containing {sector size} elements), flattened array of blue
+ # calibration table, flattened array of green calibration
+ # table
+
+ def _do_single_alsc(self, image: lt.Image, do_alsc_colour: bool):
+ average_green = np.mean((image.channels[lt.Color.GR:lt.Color.GB + 1]), axis=0)
+
+ cg, g = self._lsc_single_channel(average_green, image)
+
+ if not do_alsc_colour:
+ return image.color, None, None, cg.flatten()
+
+ cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, g)
+ cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, g)
+
+ # \todo implement debug
+
+ return image.color, cr.flatten(), cb.flatten(), cg.flatten()
+
+ # @return Red shading table, Blue shading table, Green shading table,
+ # number of images processed
+
+ def _do_all_alsc(self, images: list, do_alsc_colour: bool, general_conf: dict) -> (list, list, list, Number, int):
+ # List of colour temperatures
+ list_col = []
+ # Associated calibration tables
+ list_cr = []
+ list_cb = []
+ list_cg = []
+ count = 0
+ for image in self._enumerate_lsc_images(images):
+ col, cr, cb, cg = self._do_single_alsc(image, do_alsc_colour)
+ list_col.append(col)
+ list_cr.append(cr)
+ list_cb.append(cb)
+ list_cg.append(cg)
+ count += 1
+
+ # Convert to numpy array for data manipulation
+ list_col = np.array(list_col)
+ list_cr = np.array(list_cr)
+ list_cb = np.array(list_cb)
+ list_cg = np.array(list_cg)
+
+ cal_cr_list = []
+ cal_cb_list = []
+
+ # Note: Calculation of average corners and center of the shading tables
+ # has been removed (which ctt had, as it was unused)
+
+ # Average all values for luminance shading and return one table for all temperatures
+ lum_lut = list(np.round(np.mean(list_cg, axis=0), 3))
+
+ if not do_alsc_colour:
+ return None, None, lum_lut, count
+
+ for ct in sorted(set(list_col)):
+ # Average tables for the same colour temperature
+ indices = np.where(list_col == ct)
+ ct = int(ct)
+ t_r = np.round(np.mean(list_cr[indices], axis=0), 3)
+ t_b = np.round(np.mean(list_cb[indices], axis=0), 3)
+
+ cr_dict = {
+ 'ct': ct,
+ 'table': list(t_r)
+ }
+ cb_dict = {
+ 'ct': ct,
+ 'table': list(t_b)
+ }
+ cal_cr_list.append(cr_dict)
+ cal_cb_list.append(cb_dict)
+
+ return cal_cr_list, cal_cb_list, lum_lut, count
+
+ # @brief Calculate sigma from two adjacent gain tables
+ def _calcSigma(self, g1, g2):
+ g1 = np.reshape(g1, self.sector_shape[::-1])
+ g2 = np.reshape(g2, self.sector_shape[::-1])
+
+ # Apply gains to gain table
+ gg = g1 / g2
+ if np.mean(gg) < 1:
+ gg = 1 / gg
+
+ # For each internal patch, compute average difference between it and
+ # its 4 neighbours, then append to list
+ diffs = []
+ for i in range(self.sector_shape[1] - 2):
+ for j in range(self.sector_shape[0] - 2):
+ # Indexing is incremented by 1 since all patches on borders are
+ # not counted
+ diff = np.abs(gg[i + 1][j + 1] - gg[i][j + 1])
+ diff += np.abs(gg[i + 1][j + 1] - gg[i + 2][j + 1])
+ diff += np.abs(gg[i + 1][j + 1] - gg[i + 1][j])
+ diff += np.abs(gg[i + 1][j + 1] - gg[i + 1][j + 2])
+ diffs.append(diff / 4)
+
+ mean_diff = np.mean(diffs)
+ return np.round(mean_diff, 5)
+
+ # @brief Obtains sigmas for red and blue, effectively a measure of the
+ # 'error'
+ def _get_sigma(self, cal_cr_list, cal_cb_list):
+ # Provided colour alsc tables were generated for two different colour
+ # temperatures sigma is calculated by comparing two calibration temperatures
+ # adjacent in colour space
+
+ color_temps = [cal['ct'] for cal in cal_cr_list]
+
+ # Calculate sigmas for each adjacent color_temps and return worst one
+ sigma_rs = []
+ sigma_bs = []
+ for i in range(len(color_temps) - 1):
+ sigma_rs.append(self._calcSigma(cal_cr_list[i]['table'], cal_cr_list[i + 1]['table']))
+ sigma_bs.append(self._calcSigma(cal_cb_list[i]['table'], cal_cb_list[i + 1]['table']))
+
+ # Return maximum sigmas, not necessarily from the same colour
+ # temperature interval
+ sigma_r = max(sigma_rs) if sigma_rs else 0.005
+ sigma_b = max(sigma_bs) if sigma_bs else 0.005
+
+ return sigma_r, sigma_b
+
+ def process(self, config: dict, images: list, outputs: dict) -> dict:
+ output = {
+ 'omega': 1.3,
+ 'n_iter': 100,
+ 'luminance_strength': 0.7
+ }
+
+ conf = config[self]
+ general_conf = config['general']
+
+ do_alsc_colour = self.do_color.get_value(conf)
+
+ # \todo I have no idea where this input parameter is used
+ luminance_strength = self.luminance_strength.get_value(conf)
+ if luminance_strength < 0 or luminance_strength > 1:
+ luminance_strength = 0.5
+
+ output['luminance_strength'] = luminance_strength
+
+ # \todo Validate images from greyscale camera and force grescale mode
+ # \todo Debug functionality
+
+ alsc_out = self._do_all_alsc(images, do_alsc_colour, general_conf)
+ # \todo Handle the second green lut
+ cal_cr_list, cal_cb_list, luminance_lut, count = alsc_out
+
+ if not do_alsc_colour:
+ output['luminance_lut'] = luminance_lut
+ output['n_iter'] = 0
+ return output
+
+ output['calibrations_Cr'] = cal_cr_list
+ output['calibrations_Cb'] = cal_cb_list
+ output['luminance_lut'] = luminance_lut
+
+ # The sigmas determine the strength of the adaptive algorithm, that
+ # cleans up any lens shading that has slipped through the alsc. These
+ # are determined by measuring a 'worst-case' difference between two
+ # alsc tables that are adjacent in colour space. If, however, only one
+ # colour temperature has been provided, then this difference can not be
+ # computed as only one table is available.
+ # To determine the sigmas you would have to estimate the error of an
+ # alsc table with only the image it was taken on as a check. To avoid
+ # circularity, dfault exaggerated sigmas are used, which can result in
+ # too much alsc and is therefore not advised.
+ # In general, just take another alsc picture at another colour
+ # temperature!
+
+ if count == 1:
+ output['sigma'] = 0.005
+ output['sigma_Cb'] = 0.005
+ utils.eprint('Warning: Only one alsc calibration found; standard sigmas used for adaptive algorithm.')
+ return output
+
+ # Obtain worst-case scenario residual sigmas
+ sigma_r, sigma_b = self._get_sigma(cal_cr_list, cal_cb_list)
+ output['sigma'] = np.round(sigma_r, 5)
+ output['sigma_Cb'] = np.round(sigma_b, 5)
+
+ return output
diff --git a/utils/tuning/libtuning/modules/lsc/rkisp1.py b/utils/tuning/libtuning/modules/lsc/rkisp1.py
new file mode 100644
index 00000000..5701ae0a
--- /dev/null
+++ b/utils/tuning/libtuning/modules/lsc/rkisp1.py
@@ -0,0 +1,112 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# rkisp1.py - LSC module for tuning rkisp1
+
+from .lsc import LSC
+
+import libtuning as lt
+import libtuning.utils as utils
+
+from numbers import Number
+import numpy as np
+
+
+class LSCRkISP1(LSC):
+ hr_name = 'LSC (RkISP1)'
+ out_name = 'LensShadingCorrection'
+ # \todo Not sure if this is useful. Probably will remove later.
+ compatible = ['rkisp1']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(**kwargs)
+
+ # We don't actually need anything from the config file
+ def validate_config(self, config: dict) -> bool:
+ return True
+
+ # @return Image color temperature, flattened array of red calibration table
+ # (containing {sector size} elements), flattened array of blue
+ # calibration table, flattened array of (red's) green calibration
+ # table, flattened array of (blue's) green calibration table
+
+ def _do_single_lsc(self, image: lt.Image):
+ cgr, gr = self._lsc_single_channel(image.channels[lt.Color.GR], image)
+ cgb, gb = self._lsc_single_channel(image.channels[lt.Color.GB], image)
+
+ # \todo Should these ratio against the average of both greens or just
+ # each green like we've done here?
+ cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, gr)
+ cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, gb)
+
+ return image.color, cr.flatten(), cb.flatten(), cgr.flatten(), cgb.flatten()
+
+ # @return List of dictionaries of color temperature, red table, red's green
+ # table, blue's green table, and blue table
+
+ def _do_all_lsc(self, images: list) -> list:
+ output_list = []
+ output_map_func = lt.gradient.Linear().map
+
+ # List of colour temperatures
+ list_col = []
+ # Associated calibration tables
+ list_cr = []
+ list_cb = []
+ list_cgr = []
+ list_cgb = []
+ for image in self._enumerate_lsc_images(images):
+ col, cr, cb, cgr, cgb = self._do_single_lsc(image)
+ list_col.append(col)
+ list_cr.append(cr)
+ list_cb.append(cb)
+ list_cgr.append(cgr)
+ list_cgb.append(cgb)
+
+ # Convert to numpy array for data manipulation
+ list_col = np.array(list_col)
+ list_cr = np.array(list_cr)
+ list_cb = np.array(list_cb)
+ list_cgr = np.array(list_cgr)
+ list_cgb = np.array(list_cgb)
+
+ for color_temperature in sorted(set(list_col)):
+ # Average tables for the same colour temperature
+ indices = np.where(list_col == color_temperature)
+ color_temperature = int(color_temperature)
+
+ tables = []
+ for lis in [list_cr, list_cgr, list_cgb, list_cb]:
+ table = np.mean(lis[indices], axis=0)
+ table = output_map_func((1, 3.999), (1024, 4095), table)
+ table = np.round(table).astype('int32').tolist()
+ tables.append(table)
+
+ entry = {
+ 'ct': color_temperature,
+ 'r': tables[0],
+ 'gr': tables[1],
+ 'gb': tables[2],
+ 'b': tables[3],
+ }
+
+ output_list.append(entry)
+
+ return output_list
+
+ def process(self, config: dict, images: list, outputs: dict) -> dict:
+ output = {}
+
+ # \todo This should actually come from self.sector_{x,y}_gradient
+ size_gradient = lt.gradient.Linear(lt.Remainder.Float)
+ output['x-size'] = size_gradient.distribute(0.5, 8)
+ output['y-size'] = size_gradient.distribute(0.5, 8)
+
+ output['sets'] = self._do_all_lsc(images)
+
+ # \todo Validate images from greyscale camera and force grescale mode
+ # \todo Debug functionality
+
+ return output
diff --git a/utils/tuning/libtuning/modules/module.py b/utils/tuning/libtuning/modules/module.py
new file mode 100644
index 00000000..12e2fc7c
--- /dev/null
+++ b/utils/tuning/libtuning/modules/module.py
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# module.py - Base class for algorithm-specific tuning modules
+
+
+# @var type Type of the module. Defined in the base module.
+# @var out_name The key that will be used for the algorithm in the algorithms
+# dictionary in the tuning output file
+# @var hr_name Human-readable module name, mostly for debugging
+class Module(object):
+ type = 'base'
+ hr_name = 'Base Module'
+ out_name = 'GenericAlgorithm'
+
+ def __init__(self):
+ pass
+
+ def validate_config(self, config: dict) -> bool:
+ raise NotImplementedError
+
+ # @brief Do the module's processing
+ # @param config Full configuration from the input configuration file
+ # @param images List of images to process
+ # @param outputs The outputs of all modules that were executed before this
+ # module. Note that this is an input parameter, and the
+ # output of this module should be returned directly
+ # @return Result of the module's processing. It may be empty. None
+ # indicates failure and that the result should not be used.
+ def process(self, config: dict, images: list, outputs: dict) -> dict:
+ raise NotImplementedError
diff --git a/utils/tuning/libtuning/parsers/__init__.py b/utils/tuning/libtuning/parsers/__init__.py
new file mode 100644
index 00000000..022c1e5d
--- /dev/null
+++ b/utils/tuning/libtuning/parsers/__init__.py
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+
+from libtuning.parsers.raspberrypi_parser import RaspberryPiParser
+from libtuning.parsers.yaml_parser import YamlParser
diff --git a/utils/tuning/libtuning/parsers/parser.py b/utils/tuning/libtuning/parsers/parser.py
new file mode 100644
index 00000000..a17d8d71
--- /dev/null
+++ b/utils/tuning/libtuning/parsers/parser.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# parser.py - Base class for a parser for a specific format of config file
+
+class Parser(object):
+ def __init__(self):
+ pass
+
+ # @brief Parse a config file into a config dict
+ # @details The config dict shall have one key 'general' with a dict value
+ # for general configuration options, and all other entries shall
+ # have the module as the key with its configuration options (as a
+ # dict) as the value. The config dict shall prune entries that are
+ # for modules that are not in @a modules.
+ # @param config (str) Path to config file
+ # @param modules (list) List of modules
+ # @return (dict, list) Configuration and list of modules to disable
+ def parse(self, config_file: str, modules: list) -> (dict, list):
+ raise NotImplementedError
diff --git a/utils/tuning/libtuning/parsers/raspberrypi_parser.py b/utils/tuning/libtuning/parsers/raspberrypi_parser.py
new file mode 100644
index 00000000..d26586ba
--- /dev/null
+++ b/utils/tuning/libtuning/parsers/raspberrypi_parser.py
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# raspberrypi_parser.py - Parser for Raspberry Pi config file format
+
+from .parser import Parser
+
+import json
+import numbers
+
+import libtuning.utils as utils
+
+
+class RaspberryPiParser(Parser):
+ def __init__(self):
+ super().__init__()
+
+ # The string in the 'disable' and 'plot' lists are formatted as
+ # 'rpi.{module_name}'.
+ # @brief Enumerate, as a module, @a listt if its value exists in @a dictt
+ # and it is the name of a valid module in @a modules
+ def _enumerate_rpi_modules(self, listt, dictt, modules):
+ for x in listt:
+ name = x.replace('rpi.', '')
+ if name not in dictt:
+ continue
+ module = utils.get_module_by_typename(modules, name)
+ if module is not None:
+ yield module
+
+ def _valid_macbeth_option(self, value):
+ if not isinstance(value, dict):
+ return False
+
+ if list(value.keys()) != ['small', 'show']:
+ return False
+
+ for val in value.values():
+ if not isinstance(val, numbers.Number):
+ return False
+
+ return True
+
+ def parse(self, config_file: str, modules: list) -> (dict, list):
+ with open(config_file, 'r') as config_json:
+ config = json.load(config_json)
+
+ disable = []
+ for module in self._enumerate_rpi_modules(config['disable'], config, modules):
+ disable.append(module)
+ # Remove the disabled module's config too
+ config.pop(module.name)
+ config.pop('disable')
+
+ # The raspberrypi config format has 'plot' map to a list of module
+ # names which should be plotted. libtuning has each module contain the
+ # plot information in itself so do this conversion.
+
+ for module in self._enumerate_rpi_modules(config['plot'], config, modules):
+ # It's fine to set the value of a potentially disabled module, as
+ # the object still exists at this point
+ module.appendValue('debug', 'plot')
+ config.pop('plot')
+
+ # Convert the keys from module name to module instance
+
+ new_config = {}
+
+ for module_name in config:
+ module = utils.get_module_by_type_name(modules, module_name)
+ if module is not None:
+ new_config[module] = config[module_name]
+
+ new_config['general'] = {}
+
+ if 'blacklevel' in config:
+ if not isinstance(config['blacklevel'], numbers.Number):
+ raise TypeError('Config "blacklevel" must be a number')
+ # Raspberry Pi's ctt config has magic blacklevel value -1 to mean
+ # "get it from the image metadata". Since we already do that in
+ # Image, don't save it to the config here.
+ if config['blacklevel'] >= 0:
+ new_config['general']['blacklevel'] = config['blacklevel']
+
+ if 'macbeth' in config:
+ if not self._valid_macbeth_option(config['macbeth']):
+ raise TypeError('Config "macbeth" must be a dict: {"small": number, "show": number}')
+ new_config['general']['macbeth'] = config['macbeth']
+ else:
+ new_config['general']['macbeth'] = {'small': 0, 'show': 0}
+
+ return new_config, disable
diff --git a/utils/tuning/libtuning/parsers/yaml_parser.py b/utils/tuning/libtuning/parsers/yaml_parser.py
new file mode 100644
index 00000000..5c1673a5
--- /dev/null
+++ b/utils/tuning/libtuning/parsers/yaml_parser.py
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# yaml_parser.py - Parser for YAML format config file
+
+from .parser import Parser
+
+
+class YamlParser(Parser):
+ def __init__(self):
+ super().__init__()
+
+ # \todo Implement this (it's fine for now as we don't need a config for
+ # rkisp1 LSC, which is the only user of this so far)
+ def parse(self, config_file: str, modules: list) -> (dict, list):
+ return {}, []
diff --git a/utils/tuning/libtuning/smoothing.py b/utils/tuning/libtuning/smoothing.py
new file mode 100644
index 00000000..b8a5a242
--- /dev/null
+++ b/utils/tuning/libtuning/smoothing.py
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# smoothing.py - Wrapper for cv2 smoothing functions to enable duck-typing
+
+import cv2
+
+
+# @brief Wrapper for cv2 smoothing functions so that they can be duck-typed
+class Smoothing(object):
+ def __init__(self):
+ pass
+
+ def smoothing(self, src):
+ raise NotImplementedError
+
+
+class MedianBlur(Smoothing):
+ def __init__(self, ksize):
+ self.ksize = ksize
+
+ def smoothing(self, src):
+ return cv2.medianBlur(src.astype('float32'), self.ksize).astype('float64')
diff --git a/utils/tuning/libtuning/utils.py b/utils/tuning/libtuning/utils.py
new file mode 100644
index 00000000..b60f2c9b
--- /dev/null
+++ b/utils/tuning/libtuning/utils.py
@@ -0,0 +1,125 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# utils.py - Utilities for libtuning
+
+import decimal
+import math
+import numpy as np
+import os
+from pathlib import Path
+import re
+import sys
+
+import libtuning as lt
+from libtuning.image import Image
+from libtuning.macbeth import locate_macbeth
+
+# Utility functions
+
+
+def eprint(*args, **kwargs):
+ print(*args, file=sys.stderr, **kwargs)
+
+
+def get_module_by_type_name(modules, name):
+ for module in modules:
+ if module.type == name:
+ return module
+ return None
+
+
+# Private utility functions
+
+
+def _list_image_files(directory):
+ d = Path(directory)
+ files = [d.joinpath(f) for f in os.listdir(d)
+ if re.search(r'\.(jp[e]g$)|(dng$)', f)]
+ files.sort()
+ return files
+
+
+def _parse_image_filename(fn: Path):
+ result = re.search(r'^(alsc_)?(\d+)[kK]_(\d+)?[lLuU]?.\w{3,4}$', fn.name)
+ if result is None:
+ eprint(f'The file name of {fn.name} is incorrectly formatted')
+ return None, None, None
+
+ color = int(result.group(2))
+ lsc_only = result.group(1) is not None
+ lux = None if lsc_only else int(result.group(3))
+
+ return color, lux, lsc_only
+
+
+# \todo Implement this from check_imgs() in ctt.py
+def _validate_images(images):
+ return True
+
+
+# Public utility functions
+
+
+# @brief Load images into a single list of Image instances
+# @param input_dir Directory from which to load image files
+# @param config Configuration dictionary
+# @param load_nonlsc Whether or not to load non-lsc images
+# @param load_lsc Whether or not to load lsc-only images
+# @return A list of Image instances
+def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool) -> list:
+ files = _list_image_files(input_dir)
+ if len(files) == 0:
+ eprint(f'No images found in {input_dir}')
+ return None
+
+ images = []
+ for f in files:
+ color, lux, lsc_only = _parse_image_filename(f)
+ if color is None:
+ continue
+
+ # Skip lsc image if we don't need it
+ if lsc_only and not load_lsc:
+ eprint(f'Skipping {f.name} as this tuner has no LSC module')
+ continue
+
+ # Skip non-lsc image if we don't need it
+ if not lsc_only and not load_nonlsc:
+ eprint(f'Skipping {f.name} as this tuner only has an LSC module')
+ continue
+
+ # Load image
+ try:
+ image = Image(f)
+ except Exception as e:
+ eprint(f'Failed to load image {f.name}: {e}')
+ continue
+
+ # Populate simple fields
+ image.lsc_only = lsc_only
+ image.color = color
+ image.lux = lux
+
+ # Black level comes from the TIFF tags, but they are overridable by the
+ # config file.
+ if 'blacklevel' in config['general']:
+ image.blacklevel_16 = config['general']['blacklevel']
+
+ if lsc_only:
+ images.append(image)
+ continue
+
+ # Handle macbeth
+ macbeth = locate_macbeth(config)
+ if macbeth is None:
+ continue
+
+ images.append(image)
+
+ if not _validate_images(images):
+ return None
+
+ return images
diff --git a/utils/tuning/raspberrypi/__init__.py b/utils/tuning/raspberrypi/__init__.py
new file mode 100644
index 00000000..9ccabb0e
--- /dev/null
+++ b/utils/tuning/raspberrypi/__init__.py
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
diff --git a/utils/tuning/raspberrypi/alsc.py b/utils/tuning/raspberrypi/alsc.py
new file mode 100644
index 00000000..024eb5a3
--- /dev/null
+++ b/utils/tuning/raspberrypi/alsc.py
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# alsc.py - ALSC module instance for Raspberry Pi tuning scripts
+
+import libtuning as lt
+from libtuning.modules.lsc import ALSCRaspberryPi
+
+ALSC = \
+ ALSCRaspberryPi(do_color=lt.Param('do_alsc_colour', lt.Param.Mode.Optional, True),
+ luminance_strength=lt.Param('luminance_strength', lt.Param.Mode.Optional, 0.5),
+ debug=[lt.Debug.Plot],
+ sector_shape=(16, 12),
+ sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
+ sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
+ sector_average_function=lt.average.Mean(),
+ smoothing_function=lt.smoothing.MedianBlur(3),
+ )
diff --git a/utils/tuning/raspberrypi_alsc_only.py b/utils/tuning/raspberrypi_alsc_only.py
new file mode 100755
index 00000000..af04e6a8
--- /dev/null
+++ b/utils/tuning/raspberrypi_alsc_only.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# raspberrypi_alsc_only.py - Tuning script for raspberrypi, ALSC only
+
+import sys
+
+import libtuning as lt
+from libtuning.parsers import RaspberryPiParser
+from libtuning.generators import RaspberryPiOutput
+
+from raspberrypi.alsc import ALSC
+
+tuner = lt.Tuner('Raspberry Pi (ALSC only)')
+tuner.add(ALSC)
+tuner.set_input_parser(RaspberryPiParser())
+tuner.set_output_formatter(RaspberryPiOutput())
+tuner.set_output_order([ALSC])
+
+if __name__ == '__main__':
+ sys.exit(tuner.run(sys.argv))
diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py
new file mode 100755
index 00000000..1cea6ddb
--- /dev/null
+++ b/utils/tuning/rkisp1.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>
+#
+# rkisp1.py - Tuning script for rkisp1
+
+import sys
+
+import libtuning as lt
+from libtuning.parsers import YamlParser
+from libtuning.generators import YamlOutput
+from libtuning.modules.lsc import LSCRkISP1
+
+tuner = lt.Tuner('RkISP1')
+tuner.add(LSCRkISP1(
+ debug=[lt.Debug.Plot],
+ # This is for the actual LSC tuning, and is part of the base LSC
+ # module. rkisp1's table sector sizes (16x16 programmed as mirrored
+ # 8x8) are separate, and is hardcoded in its specific LSC tuning
+ # module.
+ sector_shape=(17, 17),
+
+ sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
+ sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
+
+ # This is the function that will be used to average the pixels in
+ # each sector. This can also be a custom function.
+ sector_average_function=lt.average.Mean(),
+
+ # This is the function that will be used to smooth the color ratio
+ # values. This can also be a custom function.
+ smoothing_function=lt.smoothing.MedianBlur(3),
+ ))
+tuner.set_input_parser(YamlParser())
+tuner.set_output_formatter(YamlOutput())
+tuner.set_output_order([LSCRkISP1])
+
+if __name__ == '__main__':
+ sys.exit(tuner.run(sys.argv))
diff --git a/utils/update-kernel-headers.sh b/utils/update-kernel-headers.sh
index a006452e..590986d2 100755
--- a/utils/update-kernel-headers.sh
+++ b/utils/update-kernel-headers.sh
@@ -18,7 +18,7 @@ if [ "$line" != "# Kbuild for top-level directory of the kernel" ] ; then
exit 1
fi
-if [ ! -d "${kernel_dir}/.git" ] ; then
+if [ ! -e "${kernel_dir}/.git" ] ; then
echo "Directory ${kernel_dir} doesn't contain a git tree"
exit 1
fi
diff --git a/utils/update-mojo.sh b/utils/update-mojo.sh
index fcbc81e7..09c8ff5b 100755
--- a/utils/update-mojo.sh
+++ b/utils/update-mojo.sh
@@ -3,13 +3,23 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Update mojo copy from a chromium source tree
+set -e
+
if [ $# != 1 ] ; then
echo "Usage: $0 <chromium dir>"
exit 1
fi
ipc_dir="$(dirname "$(realpath "$0")")/ipc"
-chromium_dir="$1"
+chromium_dir="$(realpath "$1")"
+
+cd "${ipc_dir}/../../"
+
+# Reject dirty libcamera trees
+if [ -n "$(git status --porcelain -uno)" ] ; then
+ echo "libcamera tree is dirty"
+ exit 1
+fi
if [ ! -d "${chromium_dir}/mojo" ] ; then
echo "Directory ${chromium_dir} doesn't contain mojo"
@@ -24,19 +34,23 @@ fi
# Get the chromium commit id
version=$(git -C "${chromium_dir}" rev-parse --short HEAD)
-# Reject dirty trees
+# Reject dirty chromium trees
if [ -n "$(git -C "${chromium_dir}" status --porcelain)" ] ; then
echo "Chromium tree in ${chromium_dir} is dirty"
exit 1
fi
+# Remove the previously imported files.
+rm -rf utils/ipc/mojo/
+rm -rf utils/ipc/tools/
+
# Copy the diagnosis file
-cp "${chromium_dir}/tools/diagnosis/crbug_1001171.py" "${ipc_dir}/tools/diagnosis"
+mkdir -p utils/ipc/tools/diagnosis/
+cp "${chromium_dir}/tools/diagnosis/crbug_1001171.py" utils/ipc/tools/diagnosis/
# Copy the rest of mojo
-cp "${chromium_dir}/mojo/public/LICENSE" "${ipc_dir}/mojo/public"
-
-rm -rf "${ipc_dir}/mojo/public/tools/*"
+mkdir -p utils/ipc/mojo/public/
+cp "${chromium_dir}/mojo/public/LICENSE" utils/ipc/mojo/public/
(
cd "${chromium_dir}" || exit
@@ -55,12 +69,22 @@ modify them manually.
EOF
)
-echo "$readme" > "${ipc_dir}/mojo/README"
-echo "$readme" > "${ipc_dir}/tools/README"
+echo "$readme" > utils/ipc/mojo/README
+echo "$readme" > utils/ipc/tools/README
-cat <<EOF
-------------------------------------------------------------
-mojo updated. Please review and up-port local changes before
-committing.
-------------------------------------------------------------
-EOF
+# Commit the update. Use 'git commit -n' to avoid checkstyle pre-commit hook
+# failures, as mojo doesn't comply with the Python coding style enforced by
+# checkstyle.py.
+git add utils/ipc/mojo/
+git add utils/ipc/tools/
+
+echo "utils: ipc: Update mojo
+
+Update mojo from commit
+
+$(git -C "${chromium_dir}" show --pretty='%H "%s"' --no-patch)
+
+from the Chromium repository.
+
+The update-mojo.sh script was used for this update." | \
+git commit -n -s -F -