From c932767f4783397898e4c48c512c3c59f2b70a61 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Feb 2025 00:12:05 +0100 Subject: [PATCH 1/8] drivers: video: introduce an API for collecting statistics Introduce an abstraction layer handling the diversity of ways hardware have to report statistics. This allows to take advantage of the various channel average or histograms present on some hardware, that skip the need to manually compute statistics. Fixes #85457 Signed-off-by: Josuah Demangeon --- include/zephyr/drivers/video.h | 113 ++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 67212110ebcd..abcde2ee748f 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -25,7 +25,6 @@ #include #include #include - #include #ifdef __cplusplus @@ -205,6 +204,71 @@ struct video_frmival_enum { }; }; +/** Custom statistics allowing applications to implement their own format. */ +#define VIDEO_STATS_CUSTOM BIT(0) + +/** Channel statistics for the Y (luma) channel. */ +#define VIDEO_STATS_CHANNELS_Y BIT(1) + +/** Channel statistics for the R, G, B channels. */ +#define VIDEO_STATS_CHANNELS_RGB BIT(2) + +/** Bitmap to ask for all statistics compatible with @struct video_stats_channels */ +#define VIDEO_STATS_CHANNELS (VIDEO_STATS_CHANNELS_RGB | VIDEO_STATS_CHANNELS_Y) + +/** Statistics in the form of an histogram with the Y (luma) channel present. */ +#define VIDEO_STATS_HISTOGRAM_Y BIT(3) + +/** Statistics in the form of an histogram with the R, G, B channels present. */ +#define VIDEO_STATS_HISTOGRAM_RGB BIT(4) + +/** Bitmap to ask for one of the statistics compatible with @struct video_stats_histogram */ +#define VIDEO_STATS_HISTOGRAM (VIDEO_STATS_HISTOGRAM_RGB | VIDEO_STATS_HISTOGRAM_Y) + +/** + * @brief Statistics base type, present as the first field of the other types. + * + * This type is to be casted to one of the other "video_..._stats" types. This permits to define + * new custom types in application and still use the upstream video API. + */ +struct video_stats { + /** Bitmak that describes the type of the stats filled */ + uint16_t flags; + /** Frame counter to know if a frame elapsed since the last call. Quickly overflowing. */ + uint16_t frame_counter; +}; + +/** + * @brief Per-channel average values. + * + * Each field represent 8-bit integer values that corresponds to the average value of a channel. + */ +struct video_stats_channels { + /** Base structure with fields common to all types of statistics. */ + struct video_stats base; + /** The luma channel average. */ + uint8_t y; + /** RGB24-formatted averages. */ + uint8_t rgb[3]; +}; + +/** + * @brief Statistics about the video image color content. + * + * Used by software algorithms to control the color balance such as White Balance (AWB), + * Black Level Correction (BLC), or control sensors such as Exposure/Gain Control (AEC/AGC). + */ +struct video_stats_histogram { + /** Base structure with fields common to all types of statistics. */ + struct video_stats base; + /** The histogram content for the Y or the R, G, B channels as defined by @c base.flags. */ + uint16_t *buckets; + /** Total number of values in @c buckets. */ + size_t num_buckets; + /** Total number of values added to the historam. */ + uint32_t num_values; +}; + /** * @brief video_endpoint_id enum * @@ -344,6 +408,19 @@ typedef int (*video_api_get_caps_t)(const struct device *dev, enum video_endpoin typedef int (*video_api_set_signal_t)(const struct device *dev, enum video_endpoint_id ep, struct k_poll_signal *signal); +/** + * @typedef video_api_get_stats_t + * @brief Register/Unregister poll signal for buffer events. + * + * See video_set_signal() for argument descriptions. + * @param dev Pointer to the device structure. + * @param ep Endpoint ID. + * @param stats Pointer to the statistics structure, which must have enough storage for the + type of stats requested by the @p stats flags. + */ +typedef int (*video_api_get_stats_t)(const struct device *dev, enum video_endpoint_id ep, + struct video_stats *stats); + __subsystem struct video_driver_api { /* mandatory callbacks */ video_api_set_format_t set_format; @@ -360,6 +437,7 @@ __subsystem struct video_driver_api { video_api_set_frmival_t set_frmival; video_api_get_frmival_t get_frmival; video_api_enum_frmival_t enum_frmival; + video_api_get_stats_t get_stats; }; /** @@ -496,6 +574,39 @@ static inline int video_enum_frmival(const struct device *dev, enum video_endpoi return api->enum_frmival(dev, ep, fie); } +/** + * @brief Get image statistics out of the video devices. + * + * This permits to implement algorithms reacting to statistics about the images + * collected by the hadware. For instance, in order to implement an image signal + * processor with auto-controls (AEC, AGC, AWB...). + * + * The driver will read the @p stats flags fields to learn about what the caller wants for + * statistics, and update them with the statistics effectively added. + * + * All memory buffers of @p stats are to be provided by the caller, and the driver will update + * the @c size field with the size effectively filled with statistics data. + * + * @param dev Pointer to the device structure that collects the statistics. + * @param ep Endpoint ID from which collect the statistics. + * @param stats Pointer to a video statistic structure filled by this device. + * + * @retval 0 If successful. + * @retval -ENOSYS If API is not implemented. + * @return other error number otherwise. + */ +static inline int video_get_stats(const struct device *dev, enum video_endpoint_id ep, + struct video_stats *stats) +{ + const struct video_driver_api *api = (const struct video_driver_api *)dev->api; + + if (api->get_stats == NULL) { + return -ENOSYS; + } + + return api->get_stats(dev, ep, stats); +} + /** * @brief Enqueue a video buffer. * From 535a826971e370de972c97c48b94623ad6c30ff4 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 9 Mar 2025 00:43:21 +0000 Subject: [PATCH 2/8] drivers: video: emul_rx: add basic support for video_set_stats() Add support for the new API video_set_stats() to the emulated video RX driver, and use it to implement simple API tests. The data returned is arbitrary for the sake of testing the API itself. Signed-off-by: Josuah Demangeon --- drivers/video/video_emul_rx.c | 20 ++++++++++++++++ tests/drivers/video/api/src/video_emul.c | 29 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/video/video_emul_rx.c b/drivers/video/video_emul_rx.c index 7b7d1f580a72..94ab9fc5ca43 100644 --- a/drivers/video/video_emul_rx.c +++ b/drivers/video/video_emul_rx.c @@ -117,6 +117,25 @@ static int emul_rx_get_caps(const struct device *dev, enum video_endpoint_id ep, return video_get_caps(cfg->source_dev, VIDEO_EP_OUT, caps); } +static int emul_rx_get_stats(const struct device *dev, enum video_endpoint_id ep, + struct video_stats *stats) +{ + struct video_stats_channels *chan = (void *)stats; + + if ((stats->flags & VIDEO_STATS_CHANNELS_Y) == 0) { + return -ENOTSUP; + } + + /* Fake data for the sake of demonstrating and testing the APIs */ + chan->y = 0x7f; + stats->frame_counter = k_cycle_get_32() / 1024; + + /* Let the caller know what type of statistics is collected */ + stats->flags = VIDEO_STATS_CHANNELS_Y; + + return 0; +} + static int emul_rx_set_stream(const struct device *dev, bool enable) { const struct emul_rx_config *cfg = dev->config; @@ -238,6 +257,7 @@ static DEVICE_API(video, emul_rx_driver_api) = { .set_format = emul_rx_set_fmt, .get_format = emul_rx_get_fmt, .get_caps = emul_rx_get_caps, + .get_stats = emul_rx_get_stats, .set_stream = emul_rx_set_stream, .enqueue = emul_rx_enqueue, .dequeue = emul_rx_dequeue, diff --git a/tests/drivers/video/api/src/video_emul.c b/tests/drivers/video/api/src/video_emul.c index cd58bbf0506b..f6435496fad3 100644 --- a/tests/drivers/video/api/src/video_emul.c +++ b/tests/drivers/video/api/src/video_emul.c @@ -185,4 +185,33 @@ ZTEST(video_common, test_video_vbuf) video_buffer_release(vbuf); } +ZTEST(video_emul, test_video_stats) +{ + struct video_stats_channels chan = { + .base.flags = VIDEO_STATS_CHANNELS, + }; + + zexpect_ok(video_get_stats(rx_dev, VIDEO_EP_OUT, &chan.base), + "statistics collection should succeed for the emulated device"); + + zexpect_equal(chan.base.flags & VIDEO_STATS_HISTOGRAM, 0, "histogram was not requested"); + + zexpect_not_equal(chan.base.flags & VIDEO_STATS_CHANNELS, 0, + "this emulated device is known to support channel averages."); + + if (chan.base.flags & VIDEO_STATS_CHANNELS_Y) { + zexpect_not_equal(chan.y, 0x00, "Test data likely not completely black."); + zexpect_not_equal(chan.y, 0xff, "Test data likely not completely white."); + } + + if (chan.base.flags & VIDEO_STATS_CHANNELS_RGB) { + zexpect_not_equal(chan.rgb[0], 0x00, "Red channel likely not completely 0x00."); + zexpect_not_equal(chan.rgb[0], 0xff, "Red channel likely not completely 0xff."); + zexpect_not_equal(chan.rgb[1], 0x00, "Green channel likely not completely 0x00."); + zexpect_not_equal(chan.rgb[1], 0xff, "Green channel likely not completely 0xff."); + zexpect_not_equal(chan.rgb[2], 0x00, "Blue channel likely not completely 0x00."); + zexpect_not_equal(chan.rgb[2], 0xff, "Blue channel likely not completely 0xff."); + } +} + ZTEST_SUITE(video_emul, NULL, NULL, NULL, NULL, NULL); From d026ecfddda223b8639db6664256a160e7046669 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 4 Mar 2025 00:23:01 +0000 Subject: [PATCH 3/8] lib: pixel: Introduce a pixel and image processing library The "pixel" library aims facilitating the implementation of all image processing tasks, such as pixel conversion, with a focus on low-resource environments. The processing functions scale down to per-pixel "kernels" to line-based conversion to full streams of several frames. Signed-off-by: Josuah Demangeon --- include/zephyr/pixel/bayer.h | 248 ++++++++++++++++++++ include/zephyr/pixel/formats.h | 417 +++++++++++++++++++++++++++++++++ include/zephyr/pixel/print.h | 97 ++++++++ include/zephyr/pixel/resize.h | 75 ++++++ include/zephyr/pixel/stats.h | 121 ++++++++++ include/zephyr/pixel/stream.h | 161 +++++++++++++ lib/CMakeLists.txt | 1 + lib/Kconfig | 3 + lib/pixel/CMakeLists.txt | 12 + lib/pixel/Kconfig | 13 + lib/pixel/bayer.c | 237 +++++++++++++++++++ lib/pixel/formats.c | 122 ++++++++++ lib/pixel/print.c | 355 ++++++++++++++++++++++++++++ lib/pixel/resize.c | 60 +++++ lib/pixel/stats.c | 274 ++++++++++++++++++++++ lib/pixel/stream.c | 62 +++++ 16 files changed, 2258 insertions(+) create mode 100644 include/zephyr/pixel/bayer.h create mode 100644 include/zephyr/pixel/formats.h create mode 100644 include/zephyr/pixel/print.h create mode 100644 include/zephyr/pixel/resize.h create mode 100644 include/zephyr/pixel/stats.h create mode 100644 include/zephyr/pixel/stream.h create mode 100644 lib/pixel/CMakeLists.txt create mode 100644 lib/pixel/Kconfig create mode 100644 lib/pixel/bayer.c create mode 100644 lib/pixel/formats.c create mode 100644 lib/pixel/print.c create mode 100644 lib/pixel/resize.c create mode 100644 lib/pixel/stats.c create mode 100644 lib/pixel/stream.c diff --git a/include/zephyr/pixel/bayer.h b/include/zephyr/pixel/bayer.h new file mode 100644 index 000000000000..e4d97391531d --- /dev/null +++ b/include/zephyr/pixel/bayer.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_BAYER_H +#define ZEPHYR_INCLUDE_PIXEL_BAYER_H + +#include + +#include + +/** + * Pixel streams definitions. + * @{ + */ + +void pixel_rggb8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGGB8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rggb8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_grbg8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_GRBG8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_grbg8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_bggr8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_BGGR8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_bggr8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_gbrg8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_GBRG8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_gbrg8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_rggb8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGGB8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rggb8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +void pixel_gbrg8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_GBRG8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_gbrg8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +void pixel_bggr8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_BGGR8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_bggr8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +void pixel_grbg8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_GRBG8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_grbg8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +/** @} */ + +/** + * @brief Bayer to RGB24 conversion, 3x3 variant. + * + * @param i0 Buffer of the input row number 0 in bayer format (1 byte per pixel). + * @param i1 Buffer of the input row number 1 in bayer format (1 byte per pixel). + * @param i2 Buffer of the input row number 2 in bayer format (1 byte per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** Variant for GRBG8 format */ +void pixel_grbg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** Variant for BGGR8 format */ +void pixel_bggr8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** Variant for GBRG8 format */ +void pixel_gbrg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** @} */ + +/** + * @brief Bayer to RGB24 conversion, 2x2 variant. + * + * @param i0 Buffer of the input row number 0 in bayer format (1 byte per pixel). + * @param i1 Buffer of the input row number 1 in bayer format (1 byte per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** Variant for RGGB8 bayer format */ +void pixel_rggb8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** Variant for GBRG8 bayer format */ +void pixel_gbrg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** Variant for BGGR8 bayer format */ +void pixel_bggr8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** Variant for GRBG8 bayer format */ +void pixel_grbg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** @} */ + +/** + * @brief Perform a RGGB8 to RGB24 conversion, 3x3 variant. + * + * @param rgr0 Row 0 with 3 bytes of RGGB8 data + * @param gbg1 Row 1 with 3 bytes of RGGB8 data + * @param rgr2 Row 2 with 3 bytes of RGGB8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rggb8_to_rgb24_3x3(const uint8_t rgr0[3], const uint8_t gbg1[3], + const uint8_t rgr2[3], uint8_t rgb24[3]) +{ + rgb24[0] = ((uint16_t)rgr0[0] + rgr0[2] + rgr2[0] + rgr2[2]) / 4; + rgb24[1] = ((uint16_t)rgr0[1] + gbg1[2] + gbg1[0] + rgr2[1]) / 4; + rgb24[2] = gbg1[1]; +} + +/** + * @brief Perform a BGGR8 to RGB24 conversion, 3x3 variant. + * + * @param bgb0 Row 0 with 3 bytes of BGGR8 data + * @param grg1 Row 1 with 3 bytes of BGGR8 data + * @param bgb2 Row 2 with 3 bytes of BGGR8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_bggr8_to_rgb24_3x3(const uint8_t bgb0[3], const uint8_t grg1[3], + const uint8_t bgb2[3], uint8_t rgb24[3]) +{ + rgb24[0] = grg1[1]; + rgb24[1] = ((uint16_t)bgb0[1] + grg1[2] + grg1[0] + bgb2[1]) / 4; + rgb24[2] = ((uint16_t)bgb0[0] + bgb0[2] + bgb2[0] + bgb2[2]) / 4; +} + +/** + * @brief Perform a GRBG8 to RGB24 conversion, 3x3 variant. + * + * @param grg0 Row 0 with 3 bytes of GRBG8 data + * @param bgb1 Row 1 with 3 bytes of GRBG8 data + * @param grg2 Row 2 with 3 bytes of GRBG8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_grbg8_to_rgb24_3x3(const uint8_t grg0[3], const uint8_t bgb1[3], + const uint8_t grg2[3], uint8_t rgb24[3]) +{ + rgb24[0] = ((uint16_t)grg0[1] + grg2[1]) / 2; + rgb24[1] = bgb1[1]; + rgb24[2] = ((uint16_t)bgb1[0] + bgb1[2]) / 2; +} + +/** + * @brief Perform a GBRG8 to RGB24 conversion, 3x3 variant. + * + * @param gbg0 Row 0 with 3 bytes of GBRG8 data + * @param rgr1 Row 1 with 3 bytes of GBRG8 data + * @param gbg2 Row 2 with 3 bytes of GBRG8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_gbrg8_to_rgb24_3x3(const uint8_t gbg0[3], const uint8_t rgr1[3], + const uint8_t gbg2[3], uint8_t rgb24[3]) +{ + rgb24[0] = ((uint16_t)rgr1[0] + rgr1[2]) / 2; + rgb24[1] = rgr1[1]; + rgb24[2] = ((uint16_t)gbg0[1] + gbg2[1]) / 2; +} + +/** + * @brief Perform a RGGB8 to RGB24 conversion, 2x2 variant. + * + * @param r0 Red value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param g1 Green value from the bayer pattern. + * @param b0 Blue value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rggb8_to_rgb24_2x2(uint8_t r0, uint8_t g0, uint8_t g1, uint8_t b0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +/** + * @brief Perform a GBRG8 to RGB24 conversion, 2x2 variant. + * + * @param g1 Green value from the bayer pattern. + * @param b0 Blue value from the bayer pattern. + * @param r0 Red value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_gbrg8_to_rgb24_2x2(uint8_t g1, uint8_t b0, uint8_t r0, uint8_t g0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +/** + * @brief Perform a BGGR8 to RGB24 conversion, 2x2 variant. + * + * @param b0 Blue value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param g1 Green value from the bayer pattern. + * @param r0 Red value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_bggr8_to_rgb24_2x2(uint8_t b0, uint8_t g0, uint8_t g1, uint8_t r0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +/** + * @brief Perform a GRBG8 to RGB24 conversion, 2x2 variant. + * + * @param g1 Green value from the bayer pattern. + * @param b0 Blue value from the bayer pattern. + * @param r0 Red value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_grbg8_to_rgb24_2x2(uint8_t g1, uint8_t r0, uint8_t b0, uint8_t g0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_BAYER_H */ diff --git a/include/zephyr/pixel/formats.h b/include/zephyr/pixel/formats.h new file mode 100644 index 000000000000..1e9759e8e39a --- /dev/null +++ b/include/zephyr/pixel/formats.h @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_FORMATS_H +#define ZEPHYR_INCLUDE_PIXEL_FORMATS_H + +#include +#include + +#include +#include +#include + +/** + * Pixel stream definitions. + * @{ + */ + +void pixel_rgb24stream_to_rgb565lestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565LE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565lestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_rgb565bestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565BE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565bestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_rgb565lestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565LE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565lestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_rgb565bestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565BE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565bestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb565lestream_to_rgb24stream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB565LE_TO_RGB24(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb565lestream_to_rgb24stream, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_rgb565bestream_to_rgb24stream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB565BE_TO_RGB24(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb565bestream_to_rgb24stream, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_yuyvstream_to_rgb24stream_bt601(struct pixel_stream *strm); + +#define PIXEL_STREAM_YUYV_TO_RGB24_BT601(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_yuyvstream_to_rgb24stream_bt601, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_yuyvstream_to_rgb24stream_bt709(struct pixel_stream *strm); + +#define PIXEL_STREAM_YUYV_TO_RGB24_BT709(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_yuyvstream_to_rgb24stream_bt709, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_rgb24stream_to_yuyvstream_bt601(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_YUYV_BT601(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_yuyvstream_bt601, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_yuyvstream_bt709(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_YUYV_BT709(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_yuyvstream_bt709, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +/** @} */ + +/** + * @brief RGB565 little-endian to RGB24 conversion. + * + * @param rgb565le Buffer of the input row in RGB565 little-endian format (2 bytes per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb565leline_to_rgb24line(const uint8_t *rgb565le, uint8_t *rgb24, uint16_t width); + +/** + * @brief RGB565 big-endian to RGB24 conversion. + * + * @param rgb565be Buffer of the input row in RGB565 big-endian format (2 bytes per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb565beline_to_rgb24line(const uint8_t *rgb565be, uint8_t *rgb24, uint16_t width); + +/** + * @brief RGB24 to RGB565 little-endian conversion. + * + * @param rgb24 Buffer of the input row in RGB24 format (3 bytes per pixel). + * @param rgb565le Buffer of the output row in RGB565 little-endian format (2 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb24line_to_rgb565leline(const uint8_t *rgb24, uint8_t *rgb565le, uint16_t width); + +/** + * @brief RGB24 to RGB565 big-endian conversion. + * + * @param rgb24 Buffer of the input row in RGB24 format (3 bytes per pixel). + * @param rgb565be Buffer of the output row in RGB565 big-endian format (2 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb24line_to_rgb565beline(const uint8_t *rgb24, uint8_t *rgb565be, uint16_t width); + +/** + * @brief YUYV to RGB24 conversion. + * + * @param yuyv Buffer of the input row in YUYV format (2 bytes per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** BT.601 variant */ +void pixel_yuyvline_to_rgb24line_bt601(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width); +/** BT.709 variant */ +void pixel_yuyvline_to_rgb24line_bt709(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width); +/** @} */ + +/** + * @brief RGB24 to YUYV conversion. + * + * @param rgb24 Buffer of the input row in RGB24 format (3 bytes per pixel). + * @param yuyv Buffer of the output row in YUYV format (2 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** BT.601 variant */ +void pixel_rgb24line_to_yuyvline_bt601(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width); +/** BT.709 variant */ +void pixel_rgb24line_to_yuyvline_bt709(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width); +/** @} */ + +/* These constants are meant to be inlined by the compiler */ + +#define Q21(val) ((int32_t)((val) * (1 << 21))) + +static const int32_t pixel_yuv_to_r_bt709[] = {Q21(+1.0000), Q21(+0.0000), Q21(+1.5748)}; +static const int32_t pixel_yuv_to_g_bt709[] = {Q21(+1.0000), Q21(-0.1873), Q21(-0.4681)}; +static const int32_t pixel_yuv_to_b_bt709[] = {Q21(+1.0000), Q21(+1.8556), Q21(+0.0000)}; + +static const int32_t pixel_yuv_to_r_bt601[] = {Q21(+1.0000), Q21(+0.0000), Q21(+1.4020)}; +static const int32_t pixel_yuv_to_g_bt601[] = {Q21(+1.0000), Q21(-0.3441), Q21(-0.7141)}; +static const int32_t pixel_yuv_to_b_bt601[] = {Q21(+1.0000), Q21(+1.7720), Q21(+0.0000)}; + +static const int32_t pixel_rgb_to_y_bt709[] = {Q21(+0.2126), Q21(+0.7152), Q21(+0.0722), 0}; +static const int32_t pixel_rgb_to_u_bt709[] = {Q21(-0.1146), Q21(-0.3854), Q21(+0.5000), 128}; +static const int32_t pixel_rgb_to_v_bt709[] = {Q21(+0.5000), Q21(-0.4542), Q21(-0.0468), 128}; + +static const int32_t pixel_rgb_to_y_bt601[] = {Q21(+0.2570), Q21(+0.5040), Q21(+0.0980), 16}; +static const int32_t pixel_rgb_to_u_bt601[] = {Q21(-0.1480), Q21(-0.2910), Q21(+0.4390), 128}; +static const int32_t pixel_rgb_to_v_bt601[] = {Q21(+0.4390), Q21(-0.3680), Q21(-0.0710), 128}; + +static const int32_t pixel_rgb_to_y_jpeg[] = {Q21(+0.2990), Q21(+0.5870), Q21(+0.1140), 0}; +static const int32_t pixel_rgb_to_u_jpeg[] = {Q21(-0.1690), Q21(-0.3310), Q21(+0.5000), 128}; +static const int32_t pixel_rgb_to_v_jpeg[] = {Q21(+0.5000), Q21(-0.4190), Q21(-0.0810), 128}; + +#undef Q21 + +static inline uint8_t pixel_yuv24_to_r8g8b8(uint8_t y, uint8_t u, uint8_t v, const int32_t coeff[3]) +{ + int32_t y21 = coeff[0] * y; + int32_t u21 = coeff[1] * ((int32_t)u - 128); + int32_t v21 = coeff[2] * ((int32_t)v - 128); + int32_t x16 = (y21 + u21 + v21) >> 21; + + return CLAMP(x16, 0, 0xff); +} + +static inline uint8_t pixel_rgb24_to_y8u8v8(const uint8_t rgb24[3], const int32_t coeff[4]) +{ + int16_t x16 = (coeff[0] * rgb24[0] + coeff[1] * rgb24[1] + coeff[2] * rgb24[2]) >> 21; + + return CLAMP(x16 + coeff[3], 0, 0xff); +} + +/** + * @brief Convert a pixel from YUV24 format to RGB24 using the BT.601 profile. + * + * @param y The 8-bit luma channel value (aka Y). + * @param u The 8-bit chroma channel value (aka V or Cb). + * @param v The 8-bit chroma channel value (aka V or Cr). + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_yuv24_to_rgb24_bt601(uint8_t y, uint8_t u, uint8_t v, uint8_t rgb24[3]) +{ + rgb24[0] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_r_bt601); + rgb24[1] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_g_bt601); + rgb24[2] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_b_bt601); +} + +/** + * @brief Convert a pixel from YUV24 format to RGB24 using the BT.709 profile. + * + * @param y The 8-bit luma channel value (aka Y). + * @param u The 8-bit chroma channel value (aka V or Cb). + * @param v The 8-bit chroma channel value (aka V or Cr). + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_yuv24_to_rgb24_bt709(uint8_t y, uint8_t u, uint8_t v, uint8_t rgb24[3]) +{ + rgb24[0] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_r_bt709); + rgb24[1] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_g_bt709); + rgb24[2] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_b_bt709); +} + +/** + * @brief Convert two YUYV pixels to two RGB24 pixels using the BT.601 profile. + * + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + */ +static inline void pixel_yuyv_to_rgb24x2_bt601(const uint8_t yuyv[4], uint8_t rgb24x2[6]) +{ + pixel_yuv24_to_rgb24_bt601(yuyv[0], yuyv[1], yuyv[3], &rgb24x2[0]); + pixel_yuv24_to_rgb24_bt601(yuyv[2], yuyv[1], yuyv[3], &rgb24x2[3]); +} + +/** + * @brief Convert two YUYV pixels to two RGB24 pixels. + * + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + */ +static inline void pixel_yuyv_to_rgb24x2_bt709(const uint8_t yuyv[4], uint8_t rgb24x2[6]) +{ + pixel_yuv24_to_rgb24_bt709(yuyv[0], yuyv[1], yuyv[3], &rgb24x2[0]); + pixel_yuv24_to_rgb24_bt709(yuyv[2], yuyv[1], yuyv[3], &rgb24x2[3]); +} + +/** + * @brief Convert a pixel from RGB24 to Y8 format (Y channel of YUV24) using the BT.601 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return The Y channel for this RGB value, also known as "perceived lightness". + */ +static inline uint8_t pixel_rgb24_to_y8_bt601(const uint8_t rgb24[3]) +{ + return pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt601); +} + +/** + * @brief Convert a pixel from RGB24 to Y8 format (Y channel of YUV24) using the BT.709 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return The Y channel for this RGB value, also known as "perceived lightness". + */ +static inline uint8_t pixel_rgb24_to_y8_bt709(const uint8_t rgb24[3]) +{ + return pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt709); +} + +/** + * @brief Convert a pixel from RGB24 format to YUV24 using the BT.601 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param yuv24 The pixel in YUV24 format: {Y, U, V} + */ +static inline void pixel_rgb24_to_yuv24_bt601(const uint8_t rgb24[3], uint8_t yuv24[3]) +{ + yuv24[0] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt601); + yuv24[1] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_u_bt601); + yuv24[2] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_v_bt601); +} + +/** + * @brief Convert a pixel from RGB24 format to YUV24 using the BT.709 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param yuv24 The pixel in YUV24 format: {Y, U, V} + */ +static inline void pixel_rgb24_to_yuv24_bt709(const uint8_t rgb24[3], uint8_t yuv24[3]) +{ + yuv24[0] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt709); + yuv24[1] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_u_bt709); + yuv24[2] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_v_bt709); +} + +/** + * @brief Convert two pixels from RGB24 format to YUYV format using the BT.601 profile. + * + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + */ +static inline void pixel_rgb24x2_to_yuyv_bt601(const uint8_t rgb24x2[6], uint8_t yuyv[4]) +{ + yuyv[0] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_y_bt601); + yuyv[1] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_u_bt601); + yuyv[2] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_y_bt601); + yuyv[3] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_v_bt601); +} + +/** + * @brief Convert two pixels from RGB24 format to YUYV format using the BT.709 profile. + * + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + */ +static inline void pixel_rgb24x2_to_yuyv_bt709(const uint8_t rgb24x2[6], uint8_t yuyv[4]) +{ + yuyv[0] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_y_bt709); + yuyv[1] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_u_bt709); + yuyv[2] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_y_bt709); + yuyv[3] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_v_bt709); +} + +/** + * @brief Convert a pixel from RGB565 format to RGB24. + * + * @param rgb565 The pixel in RGB565 format, in CPU-native endianness + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rgb565_to_rgb24(uint16_t rgb565, uint8_t rgb24[3]) +{ + rgb24[0] = rgb565 >> (0 + 6 + 5) << 3; + rgb24[1] = rgb565 >> (0 + 0 + 5) << 2; + rgb24[2] = rgb565 >> (0 + 0 + 0) << 3; +} + +/** + * @brief Convert a pixel from RGB565 format to RGB24. + * + * @param rgb565le The pixel in RGB565 format, in little-endian. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rgb565le_to_rgb24(const uint8_t rgb565le[2], uint8_t rgb24[3]) +{ + pixel_rgb565_to_rgb24(sys_le16_to_cpu(*(uint16_t *)rgb565le), rgb24); +} + +/** + * @brief Convert a pixel from RGB565 big-endian format to RGB24. + * + * @param rgb565be The pixel in RGB565 format, in big-endian. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rgb565be_to_rgb24(const uint8_t rgb565be[2], uint8_t rgb24[3]) +{ + pixel_rgb565_to_rgb24(sys_be16_to_cpu(*(uint16_t *)rgb565be), rgb24); +} + +/** + * @brief Convert a pixel from RGB24 format to RGB565. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return The pixel in RGB565 format, in CPU-native endianness + */ +static inline uint16_t pixel_rgb24_to_rgb565(const uint8_t rgb24[3]) +{ + uint16_t rgb565 = 0; + + rgb565 |= (uint16_t)rgb24[0] >> 3 << (0 + 6 + 5); + rgb565 |= (uint16_t)rgb24[1] >> 2 << (0 + 0 + 5); + rgb565 |= (uint16_t)rgb24[2] >> 3 << (0 + 0 + 0); + + return rgb565; +} + +/** + * @brief Convert a pixel from RGB24 format to RGB565 little-endian. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param rgb565le The pixel in RGB565 format, in little-endian. + */ +static inline void pixel_rgb24_to_rgb565le(const uint8_t rgb24[3], uint8_t rgb565le[2]) +{ + *(uint16_t *)rgb565le = sys_cpu_to_le16(pixel_rgb24_to_rgb565(rgb24)); +} + +/** + * @brief Convert a pixel from RGB24 format to RGB565 big-endian. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param rgb565be The pixel in RGB565 format, in big-endian. + */ +static inline void pixel_rgb24_to_rgb565be(const uint8_t rgb24[3], uint8_t rgb565be[2]) +{ + *(uint16_t *)rgb565be = sys_cpu_to_be16(pixel_rgb24_to_rgb565(rgb24)); +} + +/** + * @brief Convert a pixel from RGB24 to ANSI 8-bit format (color cube of 6x6x6 colors). + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return Colors in ANSI escape code color format packed in a single 8-bit value. + */ +static inline uint8_t pixel_rgb24_to_256color(const uint8_t rgb24[3]) +{ + return 16 + rgb24[0] * 6 / 256 * 36 + rgb24[1] * 6 / 256 * 6 + rgb24[2] * 6 / 256 * 1; +} + +/** + * @brief Convert a pixel from GRAY8 to ANSI 8-bit black and white format (grayscale of 24 values) + * + * @param gray8 The pixel in GRAY8 format: 8-bit value. + * @return Colors in ANSI escape code color format packed in a single 8-bit value. + */ +static inline uint8_t pixel_gray8_to_256color(uint8_t gray8) +{ + return 232 + gray8 * 24 / 256; +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_FORMATS_H */ diff --git a/include/zephyr/pixel/print.h b/include/zephyr/pixel/print.h new file mode 100644 index 000000000000..d28f7adfeec1 --- /dev/null +++ b/include/zephyr/pixel/print.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_PRINT_H +#define ZEPHYR_INCLUDE_PIXEL_PRINT_H + +#include +#include + +/** + * Printing images to the terminal for debug and quick insights purpose. + * + * The 256COLOR variants use 256COLOR 8-bit RGB format, lower quality, more compact and supported. + * The TRUECOLOR variants use true 24-bit RGB24 colors, available on a wide range of terminals. + * + * @param buf Input buffer to display in the terminal. + * @param size Size of the input buffer in bytes. + * @param width Number of pixel of the input buffer in width + * @param height Max number of rows to print + * @{ + */ +/** Variant for RGB24 input using 256COLOR escape codes. */ +void pixel_print_rgb24frame_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB24 input using TRUECOLOR escape codes. */ +void pixel_print_rgb24frame_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565LE input using 256COLOR escape codes. */ +void pixel_print_rgb565leframe_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565LE input using TRUECOLOR escape codes. */ +void pixel_print_rgb565leframe_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565BE input using 256COLOR escape codes. */ +void pixel_print_rgb565beframe_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565BE input using TRUECOLOR escape codes. */ +void pixel_print_rgb565beframe_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT601 profile using 256COLOR escape codes. */ +void pixel_print_yuyvframe_bt601_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT601 profile using TRUECOLOR escape codes. */ +void pixel_print_yuyvframe_bt601_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT709 profile using 256COLOR escape codes. */ +void pixel_print_yuyvframe_bt709_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT709 profile using TRUECOLOR escape codes. */ +void pixel_print_yuyvframe_bt709_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** @} */ + +/** + * Printing images to the terminal for debug and quick insights purpose. + * + * The 256COLOR variants use 256COLOR 8-bit RGB format, lower quality, more compact and supported. + * The TRUECOLOR variants use true 24-bit RGB24 colors, available on a wide range of terminals. + * + * @param buf Input buffer to display in the terminal. + * @param size Size of the input buffer in bytes. + * @param width Number of pixel of the input buffer in width + * @param height Max number of rows to print + * @{ + */ +/* RAW8 pixel format variant */ +void pixel_print_raw8frame_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/* RGB24 pixel format variant */ +void pixel_print_rgb24frame_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/* RGB565 pixel format variant */ +void pixel_print_rgb565frame_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/* YUYV pixel format variant */ +void pixel_print_yuyvframe_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/** @} */ + +/** + * Printing RGB histograms to the terminal for debug and quick insights purpose. + * + * @param rgb24hist Buffer storing 3 histograms one after the other, for the R, G, B channels. + * @param size Total number of buckets in total contained within @p rgb24hist all channels included. + * @param height Desired height of the chart in pixels. + */ +void pixel_print_rgb24hist(const uint16_t *rgb24hist, size_t size, uint16_t height); + +/** + * Printing Y histograms to the terminal for debug and quick insights purpose. + * + * @param y8hist Buffer storing the histogram for the Y (luma) channel. + * @param size Total number of buckets in total contained within @p hist. + * @param height Desired height of the chart in pixels. + */ +void pixel_print_y8hist(const uint16_t *y8hist, size_t size, uint16_t height); + +#endif /* ZEPHYR_INCLUDE_PIXEL_PRINT_H */ diff --git a/include/zephyr/pixel/resize.h b/include/zephyr/pixel/resize.h new file mode 100644 index 000000000000..10b4f6d2a928 --- /dev/null +++ b/include/zephyr/pixel/resize.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_RESIZE_H +#define ZEPHYR_INCLUDE_PIXEL_RESIZE_H + +#include + +/** + * @brief Step-by-step increment two indexes preserving their proportion. + * + * @param src_index Position to sample on the input image, adjusted to preserve the same ratio + * from @p src_width to @p dst_width and from @p src_index to @p dst_index. + * @param src_width Width of the input image to sample. + * @param dst_index Index through the destination image, always incremented by one. + * @param dst_width Targeted width for the resized output. + * @param debt Accumulator telling how much overshooting was done in either direction. + * It must be initialized to zero on first call. + */ +void pixel_subsample_step(size_t *src_index, size_t src_width, size_t *dst_index, size_t dst_width, + int32_t *debt); + +/** + * @brief Resize a line of pixel by compressing/stretching it horizontally. + * + * Subsampling is used as algorithm to compute the new pixels: fast but low quality. + * + * @param src_buf Input buffer to resize + * @param src_width Number of pixels to resize to a different format. + * @param dst_buf Output buffer in which the stretched/compressed is stored. + * @param dst_width Number of pixels in the output buffer. + * @{ + */ +/** RGB24 variant */ +void pixel_subsample_rgb24line(const uint8_t *src_buf, size_t src_width, uint8_t *dst_buf, + size_t dst_width); +/** RGB565 variant */ +void pixel_subsample_rgb565line(const uint8_t *src_buf, size_t src_width, uint8_t *dst_buf, + size_t dst_width); +/** YUYV variant */ +void pixel_subsample_yuyvline(const uint8_t *src_buf, size_t src_width, uint8_t *dst_buf, + size_t dst_width); +/** @} */ + +/** + * @brief Resize a buffer of pixels by compressing/stretching it horizontally and vertically. + * + * Subsampling is used as algorithm to compute the new pixels: fast but low quality. + * + * The aspect ratio is chosen by the width and height parameters, and preserving the proportions is + * a choice that the user can optionally make. + * + * @param src_buf Input buffer to resize + * @param src_width Width of the input in number of pixels. + * @param src_height Height of the input in number of pixels. + * @param dst_buf Output buffer in which the stretched/compressed is stored. + * @param dst_width Width of the outputin number of pixels. + * @param dst_height Height of the outputin number of pixels. + * @{ + */ +/* RGB24 variant */ +void pixel_subsample_rgb24frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height); +/* RGB565 variant */ +void pixel_subsample_rgb565frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height); +/* YUYV variant */ +void pixel_subsample_yuyvframe(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height); +/** @} */ + +#endif /* ZEPHYR_INCLUDE_PIXEL_RESIZE_H */ diff --git a/include/zephyr/pixel/stats.h b/include/zephyr/pixel/stats.h new file mode 100644 index 000000000000..9a1e901ec9d0 --- /dev/null +++ b/include/zephyr/pixel/stats.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_STATS_H +#define ZEPHYR_INCLUDE_PIXEL_STATS_H + +#include + +/** + * @brief Collect red, gren, blue channel averages of all pixels in an RGB24 frame. + * + * @param buf Buffer of pixels in RGB24 format (3 bytes per pixel) to collect the statistics from.. + * @param size Size of this input buffer. + * @param rgb24avg The channel averages stored as an RGB24 pixel. + * @param nval The number of values to collect in order to perform the statistics. + */ +void pixel_rgb24frame_to_rgb24avg(const uint8_t *buf, size_t size, uint8_t rgb24avg[3], + uint16_t nval); + +/** + * @brief Collect red, gren, blue channel averages of all pixels in a frame. + * + * @param buf Buffer of pixels in bayer format (1 byte per pixel) to collect the statistics from.. + * @param size Size of this input buffer. + * @param width Width of the lines in number of pixels. + * @param rgb24avg The channel averages stored as an RGB24 pixel. + * @param nval The number of values to collect in order to perform the statistics. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** Variant for BGGR8 format */ +void pixel_bggr8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** Variant for GBRG8 format */ +void pixel_gbrg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** Variant for GRBG8 format */ +void pixel_grbg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** @} */ + +/** + * @brief Collect an histogram for each of the red, gren, blue channels of an RGB24 frame. + * + * @param buf Buffer of pixels in RGB24 format (3 bytes per pixel) to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param rgb24hist Buffer storing 3 histograms one after the other, for the R, G, B channels. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + */ +void pixel_rgb24frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t *rgb24hist, + size_t hist_size, uint16_t nval); + +/** + * @brief Collect an histogram for each of the red, gren, blue channels of a frame. + * + * @param buf Buffer of pixels to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param width Width of the lines in number of pixels. + * @param rgb24hist Buffer storing 3 histograms one after the other, for the R, G, B channels. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** Variant for GBRG8 format */ +void pixel_gbrg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** Variant for BGGR8 format */ +void pixel_bggr8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** Variant for GRBG8 format */ +void pixel_grbg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** @} */ + +/** + * @brief Collect an histogram for the Y channel, obtained from the pixel values of the image. + * + * @param buf Buffer of pixels in RGB24 format (3 bytes per pixel) to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param y8hist Buffer storing the histogram for the Y (luma) channel. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + */ +void pixel_rgb24frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t *y8hist, + size_t hist_size, uint16_t nval); + +/** + * @brief Collect an histogram for the Y channel, obtained from the pixel values of the image. + * + * @param buf Buffer of pixels in bayer format (1 byte per pixel) to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param width Width of the lines in number of pixels. + * @param y8hist Buffer storing the histogram for the Y (luma) channel. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** Variant for GBRG8 format */ +void pixel_gbrg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** Variant for BGGR8 format */ +void pixel_bggr8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** Variant for GRBG8 format */ +void pixel_grbg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** @} */ + +#endif /* ZEPHYR_INCLUDE_PIXEL_STATS_H */ diff --git a/include/zephyr/pixel/stream.h b/include/zephyr/pixel/stream.h new file mode 100644 index 000000000000..57b31773d675 --- /dev/null +++ b/include/zephyr/pixel/stream.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_STREAM_H +#define ZEPHYR_INCLUDE_PIXEL_STREAM_H + +#include +#include +#include +#include + +struct pixel_stream { + /* Display name, useful for debugging the stream */ + uint8_t *name; + /* Ring buffer that keeps track of the position in bytes */ + struct ring_buf ring; + /* Number of bytes returned while asking for an input or output line */ + size_t pitch; + /* Current position within the frame */ + uint16_t line_offset; + /* Total number of lines in the frame */ + uint16_t height; + /* Connection to the next element of the stream */ + struct pixel_stream *next; + /* Function that performs the I/O */ + void (*run)(struct pixel_stream *strm); + /* Timestamp since the strm started working in CPU cycles */ + uint32_t start_time; + /* Total time spent working in this strm through the stream in CPU cycles */ + uint32_t total_time; +}; + +#define PIXEL_STREAM_DEFINE(_name, _run, _width, _height, _pitch, _bufsize) \ + struct pixel_stream _name = { \ + .name = "[" #_name " " #_run " " STRINGIFY(_width) "x" STRINGIFY(_height) "]", \ + .ring = RING_BUF_INIT((uint8_t [_bufsize]) {0}, _bufsize), \ + .pitch = (_pitch), \ + .height = (_height), \ + .run = (_run), \ + } + +/** + * @brief Load a buffer into the stream. + * + * The parameters such as line pitch or image height are to be configured inside each individual + * strm before calling this function. + * + * @param strm Pipeline strm into which load the buffer one pitch worth of data at a time. + * @param buf Buffer of data to load into the stream. + * @param size Total of bytes in this buffer. + */ +void pixel_stream_load(struct pixel_stream *strm, const uint8_t *buf, size_t size); + +static inline uint8_t *pixel_stream_peek_input_line(struct pixel_stream *strm) +{ + uint8_t *line; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + + size = ring_buf_get_claim(&strm->ring, &line, strm->pitch); + __ASSERT_NO_MSG(size == strm->pitch); + + return line; +} + +static inline const uint8_t *pixel_stream_get_input_lines(struct pixel_stream *strm, size_t nb) +{ + uint8_t *lines; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + + strm->line_offset += nb; + __ASSERT_NO_MSG(strm->line_offset <= strm->height); + + size = ring_buf_get_claim(&strm->ring, &lines, strm->pitch * nb); + ring_buf_get_finish(&strm->ring, size); + __ASSERT(size == strm->pitch * nb, + "%s asked for %zu input bytes, obtained only %u, total used %u, total free %u", + strm->name, strm->pitch * nb, size, ring_buf_size_get(&strm->ring), + ring_buf_space_get(&strm->ring)); + + return lines; +} + +static inline const uint8_t *pixel_stream_get_input_line(struct pixel_stream *strm) +{ + return pixel_stream_get_input_lines(strm, 1); +} + +static inline const uint8_t *pixel_stream_get_all_input(struct pixel_stream *strm) +{ + uint8_t *remaining; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + + strm->line_offset = strm->height; + __ASSERT_NO_MSG(strm->line_offset <= strm->height); + + size = ring_buf_get_claim(&strm->ring, &remaining, ring_buf_capacity_get(&strm->ring)); + ring_buf_get_finish(&strm->ring, size); + __ASSERT(size == ring_buf_capacity_get(&strm->ring), + "Could not dequeue the entire input buffer of %s, %u used, %u free", + strm->name, ring_buf_size_get(&strm->ring), ring_buf_space_get(&strm->ring)); + + return remaining; +} + +static inline uint8_t *pixel_stream_get_output_lines(struct pixel_stream *strm, size_t nb) +{ + uint8_t *lines; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + __ASSERT_NO_MSG(strm->next != NULL); + + size = ring_buf_put_claim(&strm->next->ring, &lines, strm->next->pitch * nb); + ring_buf_put_finish(&strm->next->ring, size); + __ASSERT(size == strm->next->pitch, + "%s asked for %zu output bytes, only have %u, total used %u, total free %u", + strm->name, strm->pitch * nb, size, ring_buf_size_get(&strm->ring), + ring_buf_space_get(&strm->ring)); + + return lines; +} + +static inline uint8_t *pixel_stream_get_output_line(struct pixel_stream *strm) +{ + return pixel_stream_get_output_lines(strm, 1); +} + +static inline void pixel_stream_done(struct pixel_stream *strm) +{ + __ASSERT_NO_MSG(strm != NULL); + + /* Ignore any "peek" operation done previouslyl */ + ring_buf_get_finish(&strm->ring, 0); + ring_buf_put_finish(&strm->ring, 0); + + /* Flush the timestamp to the counter */ + strm->total_time += strm->start_time - k_cycle_get_32(); + strm->start_time = k_cycle_get_32(); + + if (strm->next != NULL && strm->next->run && ring_buf_space_get(&strm->next->ring) == 0) { + /* Start the counter of the next stream */ + strm->next->start_time = k_cycle_get_32(); + + /* Run the next strm */ + strm->next->run(strm->next); + + /* Resuming to this strm, upgrade the start time */ + strm->start_time = k_cycle_get_32(); + } +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_STREAM_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b945968c8fbe..d8de2732c0f5 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(heap) add_subdirectory(mem_blocks) add_subdirectory_ifdef(CONFIG_NET_BUF net_buf) add_subdirectory(os) +add_subdirectory_ifdef(CONFIG_PIXEL pixel) add_subdirectory(utils) add_subdirectory_ifdef(CONFIG_SMF smf) add_subdirectory_ifdef(CONFIG_OPENAMP open-amp) diff --git a/lib/Kconfig b/lib/Kconfig index ae97399b1995..bf5ae2371ee6 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -19,6 +19,8 @@ source "lib/net_buf/Kconfig" source "lib/os/Kconfig" +source "lib/pixel/Kconfig" + source "lib/posix/Kconfig" source "lib/open-amp/Kconfig" @@ -32,4 +34,5 @@ source "lib/runtime/Kconfig" source "lib/utils/Kconfig" source "lib/uuid/Kconfig" + endmenu diff --git a/lib/pixel/CMakeLists.txt b/lib/pixel/CMakeLists.txt new file mode 100644 index 000000000000..a80133e8181e --- /dev/null +++ b/lib/pixel/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +# zephyr-keep-sorted-start +zephyr_library_sources(bayer.c) +zephyr_library_sources(formats.c) +zephyr_library_sources(print.c) +zephyr_library_sources(resize.c) +zephyr_library_sources(stats.c) +zephyr_library_sources(stream.c) +# zephyr-keep-sorted-stop diff --git a/lib/pixel/Kconfig b/lib/pixel/Kconfig new file mode 100644 index 000000000000..33ea566a0443 --- /dev/null +++ b/lib/pixel/Kconfig @@ -0,0 +1,13 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +menuconfig PIXEL + bool "Pixel and Image Manipulation Library" + +if PIXEL + +module = PIXEL +module-str = pixel +source "subsys/logging/Kconfig.template.log_config" + +endif diff --git a/lib/pixel/bayer.c b/lib/pixel/bayer.c new file mode 100644 index 000000000000..7fdd41f43c95 --- /dev/null +++ b/lib/pixel/bayer.c @@ -0,0 +1,237 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#define FOLD_L_3X3(l0, l1, l2) \ + { \ + {l0[1], l0[0], l0[1]}, \ + {l1[1], l1[0], l1[1]}, \ + {l2[1], l2[0], l2[1]}, \ + } + +#define FOLD_R_3X3(l0, l1, l2, n) \ + { \ + {l0[(n) - 2], l0[(n) - 1], l0[(n) - 2]}, \ + {l1[(n) - 2], l1[(n) - 1], l1[(n) - 2]}, \ + {l2[(n) - 2], l2[(n) - 1], l2[(n) - 2]}, \ + } + +void pixel_rggb8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][3] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][3] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_grbg8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_rggb8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_grbg8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_rggb8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_grbg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_rggb8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_grbg8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_rggb8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_grbg8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_bggr8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_gbrg8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_bggr8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_gbrg8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_bggr8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_gbrg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_bggr8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_gbrg8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_bggr8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_gbrg8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_rggb8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_rggb8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_grbg8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_rggb8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_grbg8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +void pixel_gbrg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_gbrg8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_bggr8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_gbrg8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_bggr8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +void pixel_bggr8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_bggr8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_gbrg8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_bggr8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_gbrg8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +void pixel_grbg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_grbg8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_rggb8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_grbg8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_rggb8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +typedef void fn_3x3_t(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, uint8_t *o0, + size_t pitch); + +typedef void fn_2x2_t(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t pitch); + +static inline void pixel_bayerstream_to_rgb24stream_3x3(struct pixel_stream *strm, fn_3x3_t *fn0, + fn_3x3_t *fn1) +{ + uint8_t prev_line_offset = strm->line_offset; + const uint8_t *i0 = pixel_stream_get_input_line(strm); + const uint8_t *i1 = pixel_stream_peek_input_line(strm); + const uint8_t *i2 = pixel_stream_peek_input_line(strm); + + if (prev_line_offset == 0) { + fn1(i1, i0, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } + + if (prev_line_offset % 2 == 0) { + fn0(i0, i1, i2, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } else { + fn1(i0, i1, i2, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } + + if (strm->line_offset + 2 == strm->height) { + fn0(i1, i2, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + + pixel_stream_get_input_line(strm); + pixel_stream_get_input_line(strm); + } +} + +static inline void pixel_bayerstream_to_rgb24stream_2x2(struct pixel_stream *strm, fn_2x2_t *fn0, + fn_2x2_t *fn1) +{ + uint8_t prev_line_offset = strm->line_offset; + const uint8_t *i0 = pixel_stream_get_input_line(strm); + const uint8_t *i1 = pixel_stream_peek_input_line(strm); + + if (prev_line_offset % 2 == 0) { + fn0(i0, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } else { + fn1(i0, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } + + if (strm->line_offset + 1 == strm->height) { + fn0(i1, i0, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + + pixel_stream_get_input_line(strm); + } +} + +void pixel_rggb8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_rggb8line_to_rgb24line_3x3, + &pixel_gbrg8line_to_rgb24line_3x3); +} + +void pixel_gbrg8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_gbrg8line_to_rgb24line_3x3, + &pixel_rggb8line_to_rgb24line_3x3); +} + +void pixel_bggr8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_bggr8line_to_rgb24line_3x3, + &pixel_grbg8line_to_rgb24line_3x3); +} + +void pixel_grbg8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_grbg8line_to_rgb24line_3x3, + &pixel_bggr8line_to_rgb24line_3x3); +} + +void pixel_rggb8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_rggb8line_to_rgb24line_2x2, + &pixel_gbrg8line_to_rgb24line_2x2); +} + +void pixel_gbrg8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_gbrg8line_to_rgb24line_2x2, + &pixel_rggb8line_to_rgb24line_2x2); +} + +void pixel_bggr8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_bggr8line_to_rgb24line_2x2, + &pixel_grbg8line_to_rgb24line_2x2); +} + +void pixel_grbg8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_grbg8line_to_rgb24line_2x2, + pixel_bggr8line_to_rgb24line_2x2); +} diff --git a/lib/pixel/formats.c b/lib/pixel/formats.c new file mode 100644 index 000000000000..1365945c2af3 --- /dev/null +++ b/lib/pixel/formats.c @@ -0,0 +1,122 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +LOG_MODULE_REGISTER(pixel_formats, CONFIG_PIXEL_LOG_LEVEL); + +void pixel_rgb565leline_to_rgb24line(const uint8_t *rgb565, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb565le_to_rgb24(&rgb565[w * 2], &rgb24[w * 3]); + } +} + +void pixel_rgb565beline_to_rgb24line(const uint8_t *rgb565, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb565be_to_rgb24(&rgb565[w * 2], &rgb24[w * 3]); + } +} + +void pixel_rgb24line_to_rgb565leline(const uint8_t *rgb24, uint8_t *rgb565, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb24_to_rgb565le(&rgb24[w * 3], &rgb565[w * 2]); + } +} + +void pixel_rgb24line_to_rgb565beline(const uint8_t *rgb24, uint8_t *rgb565, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb24_to_rgb565be(&rgb24[w * 3], &rgb565[w * 2]); + } +} + +void pixel_rgb24line_to_yuyvline_bt601(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_rgb24x2_to_yuyv_bt601(&rgb24[w * 3], &yuyv[w * 2]); + } +} + +void pixel_rgb24line_to_yuyvline_bt709(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_rgb24x2_to_yuyv_bt709(&rgb24[w * 3], &yuyv[w * 2]); + } +} + +void pixel_yuyvline_to_rgb24line_bt601(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_yuyv_to_rgb24x2_bt601(&yuyv[w * 2], &rgb24[w * 3]); + } +} + +void pixel_yuyvline_to_rgb24line_bt709(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_yuyv_to_rgb24x2_bt709(&yuyv[w * 2], &rgb24[w * 3]); + } +} + +void pixel_rgb24stream_to_rgb565lestream(struct pixel_stream *strm) +{ + pixel_rgb24line_to_rgb565leline(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} + +void pixel_rgb24stream_to_rgb565bestream(struct pixel_stream *strm) +{ + pixel_rgb24line_to_rgb565beline(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} + +void pixel_rgb565lestream_to_rgb24stream(struct pixel_stream *strm) +{ + pixel_rgb565leline_to_rgb24line(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_rgb565bestream_to_rgb24stream(struct pixel_stream *strm) +{ + pixel_rgb565beline_to_rgb24line(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_yuyvstream_to_rgb24stream_bt601(struct pixel_stream *strm) +{ + pixel_yuyvline_to_rgb24line_bt601(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_yuyvstream_to_rgb24stream_bt709(struct pixel_stream *strm) +{ + pixel_yuyvline_to_rgb24line_bt709(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_rgb24stream_to_yuyvstream_bt601(struct pixel_stream *strm) +{ + pixel_rgb24line_to_yuyvline_bt601(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} + +void pixel_rgb24stream_to_yuyvstream_bt709(struct pixel_stream *strm) +{ + pixel_rgb24line_to_yuyvline_bt709(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} diff --git a/lib/pixel/print.c b/lib/pixel/print.c new file mode 100644 index 000000000000..3c6bf038dfac --- /dev/null +++ b/lib/pixel/print.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +static void pixel_print_truecolor(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) +{ + printf("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%um▄", rgb24row0[0], rgb24row0[1], rgb24row0[2], + rgb24row1[0], rgb24row1[1], rgb24row1[2]); +} + +static void pixel_print_256color(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) +{ + printf("\e[48;5;%um\e[38;5;%um▄", pixel_rgb24_to_256color(rgb24row0), + pixel_rgb24_to_256color(rgb24row1)); +} + +static void pixel_print_256gray(uint8_t gray8row0, uint8_t gray8row1) +{ + printf("\e[48;5;%um\e[38;5;%um▄", pixel_gray8_to_256color(gray8row0), + pixel_gray8_to_256color(gray8row1)); +} + +typedef void fn_rgb24_t(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]); + +static inline void pixel_print_rgb24(const uint8_t *rgb24, size_t size, uint16_t width, + uint16_t height, fn_rgb24_t *fn) +{ + size_t pitch = width * 3; + + for (size_t i = 0; height - 2 >= 0; height -= 2) { + for (size_t n = width; n > 0; n--, i += 3) { + if (i + pitch > size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + fn(&rgb24[i], &rgb24[i + pitch]); + } + printf("\e[m│\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +void pixel_print_rgb24frame_256color(const uint8_t *rgb24, size_t size, uint16_t width, + uint16_t height) +{ + pixel_print_rgb24(rgb24, size, width, height, pixel_print_256color); +} + +void pixel_print_rgb24frame_truecolor(const uint8_t *rgb24, size_t size, uint16_t width, + uint16_t height) +{ + pixel_print_rgb24(rgb24, size, width, height, pixel_print_truecolor); +} + +static inline void pixel_print_rgb565(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height, fn_rgb24_t *fn, bool big_endian) +{ + size_t pitch = width * 2; + + for (size_t i = 0; height - 2 >= 0; height -= 2) { + for (size_t n = width; n > 0; n--, i += 2) { + uint8_t rgb24[2][3]; + + if (i + pitch > size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + if (big_endian) { + pixel_rgb565be_to_rgb24(&rgb565[i + pitch * 0], rgb24[0]); + pixel_rgb565be_to_rgb24(&rgb565[i + pitch * 1], rgb24[1]); + } else { + pixel_rgb565le_to_rgb24(&rgb565[i + pitch * 0], rgb24[0]); + pixel_rgb565le_to_rgb24(&rgb565[i + pitch * 1], rgb24[1]); + } + + fn(rgb24[0], rgb24[1]); + } + printf("\e[m│\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +void pixel_print_rgb565leframe_256color(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_256color, false); +} + +void pixel_print_rgb565leframe_truecolor(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_truecolor, false); +} + +void pixel_print_rgb565beframe_256color(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_256color, true); +} + +void pixel_print_rgb565beframe_truecolor(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_truecolor, true); +} + +static inline void pixel_print_yuyv(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height, fn_rgb24_t *fn, int bt) +{ + size_t pitch = width * 2; + + for (size_t i = 0; height - 2 >= 0; height -= 2) { + for (size_t n = width; n >= 2; n -= 2, i += 4) { + uint8_t rgb24x2[2][6]; + + if (i + pitch > size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + switch (bt) { + case 601: + pixel_yuyv_to_rgb24x2_bt601(&yuyv[i + pitch * 0], rgb24x2[0]); + pixel_yuyv_to_rgb24x2_bt601(&yuyv[i + pitch * 1], rgb24x2[1]); + break; + case 709: + pixel_yuyv_to_rgb24x2_bt709(&yuyv[i + pitch * 0], rgb24x2[0]); + pixel_yuyv_to_rgb24x2_bt709(&yuyv[i + pitch * 1], rgb24x2[1]); + break; + default: + CODE_UNREACHABLE; + } + + fn(&rgb24x2[0][0], &rgb24x2[1][0]); + fn(&rgb24x2[0][3], &rgb24x2[1][3]); + } + printf("\e[m│\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +void pixel_print_yuyvframe_bt601_256color(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_256color, 601); +} + +void pixel_print_yuyvframe_bt601_truecolor(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_truecolor, 601); +} + +void pixel_print_yuyvframe_bt709_256color(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_truecolor, 709); +} + +void pixel_print_yuyvframe_bt709_truecolor(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_truecolor, 709); +} + +void pixel_print_raw8frame_hex(const uint8_t *raw8, size_t size, uint16_t width, uint16_t height) +{ + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 1 + w * 1; + + if (i >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x", raw8[i]); + } + printf(" row%u\n", h); + } +} + +void pixel_print_rgb24frame_hex(const uint8_t *rgb24, size_t size, uint16_t width, uint16_t height) +{ + printf(" "); + for (uint16_t w = 0; w < width; w++) { + printf("col%-7u", w); + } + printf("\n"); + + for (uint16_t w = 0; w < width; w++) { + printf(" R G B "); + } + printf("\n"); + + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 3 + w * 3; + + if (i + 2 >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x %02x %02x ", rgb24[i + 0], rgb24[i + 1], rgb24[i + 2]); + } + printf(" row%u\n", h); + } +} + +void pixel_print_rgb565frame_hex(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + printf(" "); + for (uint16_t w = 0; w < width; w++) { + printf("col%-4u", w); + } + printf("\n"); + + for (uint16_t w = 0; w < width; w++) { + printf(" RGB565"); + } + printf("\n"); + + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 2 + w * 2; + + if (i + 1 >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x %02x ", rgb565[i + 0], rgb565[i + 1]); + } + printf(" row%u\n", h); + } +} + +void pixel_print_yuyvframe_hex(const uint8_t *yuyv, size_t size, uint16_t width, uint16_t height) +{ + printf(" "); + for (uint16_t w = 0; w < width; w++) { + printf("col%-3u", w); + if ((w + 1) % 2 == 0) { + printf(" "); + } + } + printf("\n"); + + for (uint16_t w = 0; w < width; w++) { + printf(" %c%u", "YUYV"[w % 2 * 2 + 0], w % 2); + printf(" %c%u", "YUYV"[w % 2 * 2 + 1], w % 2); + if ((w + 1) % 2 == 0) { + printf(" "); + } + } + printf("\n"); + + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 2 + w * 2; + + if (i + 1 >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x %02x", yuyv[i], yuyv[i + 1]); + if ((w + 1) % 2 == 0) { + printf(" "); + } + } + printf(" row%u\n", h); + } +} + +static void pixel_print_hist_scale(size_t size) +{ + for (uint16_t i = 0; i < size; i++) { + pixel_print_256gray(0, i * 256 / size); + } + printf("\e[m\n"); +} + +void pixel_print_rgb24hist(const uint16_t *rgb24hist, size_t size, uint16_t height) +{ + const uint16_t *r8hist = &rgb24hist[size / 3 * 0]; + const uint16_t *g8hist = &rgb24hist[size / 3 * 1]; + const uint16_t *b8hist = &rgb24hist[size / 3 * 2]; + uint32_t max = 1; + + __ASSERT(size % 3 == 0, "Each of R, G, B channel should have the same size."); + + for (size_t i = 0; i < size; i++) { + max = rgb24hist[i] > max ? rgb24hist[i] : max; + } + + for (uint16_t h = height; h > 1; h--) { + for (size_t i = 0; i < size / 3; i++) { + uint8_t rgb24row0[3]; + uint8_t rgb24row1[3]; + + rgb24row0[0] = (r8hist[i] * height / max > h - 0) ? 0xff : 0x00; + rgb24row0[1] = (g8hist[i] * height / max > h - 0) ? 0xff : 0x00; + rgb24row0[2] = (b8hist[i] * height / max > h - 0) ? 0xff : 0x00; + rgb24row1[0] = (r8hist[i] * height / max > h - 1) ? 0xff : 0x00; + rgb24row1[1] = (g8hist[i] * height / max > h - 1) ? 0xff : 0x00; + rgb24row1[2] = (b8hist[i] * height / max > h - 1) ? 0xff : 0x00; + + pixel_print_256color(rgb24row0, rgb24row1); + } + printf("\e[m│ - %u\n", h * max / height); + } + + pixel_print_hist_scale(size / 3); +} + +void pixel_print_y8hist(const uint16_t *y8hist, size_t size, uint16_t height) +{ + uint32_t max = 1; + + for (size_t i = 0; i < size; i++) { + max = y8hist[i] > max ? y8hist[i] : max; + } + + for (uint16_t h = height; h > 1; h--) { + for (size_t i = 0; i < size; i++) { + uint8_t gray8row0 = (y8hist[i] * height / max > h - 0) ? 0xff : 0x00; + uint8_t gray8row1 = (y8hist[i] * height / max > h - 1) ? 0xff : 0x00; + + pixel_print_256gray(gray8row0, gray8row1); + } + printf("\e[m│ - %u\n", h * max / height); + } + + pixel_print_hist_scale(size); +} diff --git a/lib/pixel/resize.c b/lib/pixel/resize.c new file mode 100644 index 000000000000..0837eac7d860 --- /dev/null +++ b/lib/pixel/resize.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_resize, CONFIG_PIXEL_LOG_LEVEL); + +static inline void pixel_subsample_line(const uint8_t *src_buf, size_t src_width, + uint8_t *dst_buf, size_t dst_width, + uint8_t bits_per_pixel) +{ + for (size_t dst_w = 0; dst_w < dst_width; dst_w++) { + size_t src_w = dst_w * src_width / dst_width; + size_t src_i = src_w * bits_per_pixel / BITS_PER_BYTE; + size_t dst_i = dst_w * bits_per_pixel / BITS_PER_BYTE; + + memmove(&dst_buf[dst_i], &src_buf[src_i], bits_per_pixel / BITS_PER_BYTE); + } +} + +static inline void pixel_subsample_frame(const uint8_t *src_buf, size_t src_width, + size_t src_height, uint8_t *dst_buf, size_t dst_width, + size_t dst_height, uint8_t bits_per_pixel) +{ + for (size_t dst_h = 0; dst_h < dst_height; dst_h++) { + size_t src_h = dst_h * src_height / dst_height; + size_t src_i = src_h * src_width * bits_per_pixel / BITS_PER_BYTE; + size_t dst_i = dst_h * dst_width * bits_per_pixel / BITS_PER_BYTE; + + pixel_subsample_line(&src_buf[src_i], src_width, &dst_buf[dst_i], dst_width, + bits_per_pixel); + } +} + +void pixel_subsample_rgb24frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height) +{ + pixel_subsample_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 24); +} + +void pixel_subsample_rgb565frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height) +{ + pixel_subsample_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 16); +} + +void pixel_subsample_yuyvframe(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height) +{ + pixel_subsample_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 16); +} diff --git a/lib/pixel/stats.c b/lib/pixel/stats.c new file mode 100644 index 000000000000..07b18f778043 --- /dev/null +++ b/lib/pixel/stats.c @@ -0,0 +1,274 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +static const uint8_t pixel_rggb8[4] = {0, 1, 1, 2}; +static const uint8_t pixel_bggr8[4] = {2, 1, 1, 0}; +static const uint8_t pixel_gbrg8[4] = {1, 2, 0, 1}; +static const uint8_t pixel_grbg8[4] = {1, 0, 2, 1}; + +/* Extract a random value from the buffer */ + +static inline uint32_t pixel_rand(void) +{ + static uint32_t lcg_state; + + /* Linear Congruent Generator (LCG) are low-quality but very fast, here considered enough + * as even a fixed offset would have been enough.The % phase is skipped as there is already + * "% vbuf->bytesused" downstream in the code. + * + * The constants are from https://en.wikipedia.org/wiki/Linear_congruential_generator + */ + lcg_state = lcg_state * 1103515245 + 12345; + return lcg_state; +} + +static inline void pixel_sample_rgb24(const uint8_t *buf, size_t size, uint8_t rgb24[3]) +{ + uint32_t pos = pixel_rand() % size; + + /* Align on 24-bit pixel boundary */ + pos -= pos % 3; + + rgb24[0] = buf[pos + 0]; + rgb24[1] = buf[pos + 1]; + rgb24[2] = buf[pos + 2]; +} + +static inline void pixel_sums_to_rgb24avg(uint16_t sums[3], uint8_t rgb24avg[3], uint16_t nval) +{ + rgb24avg[0] = sums[0] / nval; + rgb24avg[1] = sums[1] / nval; + rgb24avg[2] = sums[2] / nval; +} + +static inline void pixel_sums_add_rgb24(uint16_t sums[3], uint8_t rgb24[3]) +{ + sums[0] += rgb24[0], sums[1] += rgb24[1]; + sums[2] += rgb24[2]; +} + +static inline void pixel_sample_bayer(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24[3], const uint8_t *idx) +{ + uint32_t pos = pixel_rand() % size; + + /* Make sure to be on even row and column position */ + pos -= pos % 2; + pos -= (pos / width % 2 == 0) ? 0 : width; + + rgb24[idx[0]] = buf[pos + 0]; + rgb24[idx[1]] = buf[pos + 1]; + rgb24[idx[2]] = buf[pos + width + 0]; + rgb24[idx[3]] = buf[pos + width + 1]; +} + +/* Channel average statistics */ + +void pixel_rgb24frame_to_rgb24avg(const uint8_t *buf, size_t size, uint8_t rgb24avg[3], + uint16_t nval) +{ + uint16_t sums[3] = {0}; + uint8_t rgb24[3]; + + for (uint16_t n = 0; n < nval; n++) { + pixel_sample_rgb24(buf, size, rgb24); + pixel_sums_add_rgb24(sums, rgb24); + } + pixel_sums_to_rgb24avg(sums, rgb24avg, nval); +} + +static inline void pixel_bayerframe_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval, + const uint8_t *idx) +{ + uint16_t sums[3] = {0}; + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_bayer(buf, size, width, rgb24, idx); + pixel_sums_add_rgb24(sums, rgb24); + } + pixel_sums_to_rgb24avg(sums, rgb24avg, nval); +} + +void pixel_rggb8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_bggr8); +} + +void pixel_bggr8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_bggr8); +} + +void pixel_gbrg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_grbg8); +} + +void pixel_grbg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_grbg8); +} + +/* RGB24 histogram statistics */ + +static inline void pixel_rgb24hist_add_rgb24(uint16_t *rgb24hist, uint8_t rgb24[3], + uint8_t bit_depth) +{ + uint16_t *r8hist = &rgb24hist[0 * (1 << bit_depth)], r8 = rgb24[0]; + uint16_t *g8hist = &rgb24hist[1 * (1 << bit_depth)], g8 = rgb24[1]; + uint16_t *b8hist = &rgb24hist[2 * (1 << bit_depth)], b8 = rgb24[2]; + + r8hist[r8 >> (BITS_PER_BYTE - bit_depth)]++; + g8hist[g8 >> (BITS_PER_BYTE - bit_depth)]++; + b8hist[b8 >> (BITS_PER_BYTE - bit_depth)]++; +} + +void pixel_rgb24frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t *rgb24hist, + size_t hist_size, uint16_t nval) +{ + uint8_t bit_depth = LOG2(hist_size / 3); + + __ASSERT(hist_size % 3 == 0, "Each of R, G, B channel should have the same size."); + __ASSERT(1 << bit_depth == hist_size / 3, "Each channel size should be a power of two."); + + memset(rgb24hist, 0x00, hist_size * sizeof(*rgb24hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_rgb24(buf, buf_size, rgb24); + pixel_rgb24hist_add_rgb24(rgb24hist, rgb24, bit_depth); + } +} + +static inline void pixel_bayerframe_to_rgb24hist(const uint8_t *buf, size_t buf_size, + uint16_t width, uint16_t *rgb24hist, + size_t hist_size, uint16_t nval, + const uint8_t *idx) +{ + uint8_t bit_depth = LOG2(hist_size / 3); + + __ASSERT(hist_size % 3 == 0, "Each of R, G, B channel should have the same size."); + __ASSERT(1 << bit_depth == hist_size / 3, "Each channel size should be a power of two."); + + memset(rgb24hist, 0x00, hist_size * sizeof(*rgb24hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_bayer(buf, buf_size, width, rgb24, idx); + pixel_rgb24hist_add_rgb24(rgb24hist, rgb24, bit_depth); + } +} + +void pixel_rggb8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_rggb8); +} + +void pixel_gbrg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_gbrg8); +} + +void pixel_bggr8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_bggr8); +} + +void pixel_grbg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_grbg8); +} + +/* Y8 histogram statistics + * Use BT.709 (sRGB) as an arbitrary choice, instead of BT.601 like libcamera + */ + +static inline void pixel_y8hist_add_y8(uint16_t *y8hist, uint8_t y8, uint8_t bit_depth) +{ + y8hist[y8 >> (BITS_PER_BYTE - bit_depth)]++; +} + +void pixel_rgb24frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t *y8hist, + size_t hist_size, uint16_t nval) +{ + uint8_t bit_depth = LOG2(hist_size); + + __ASSERT(1 << bit_depth == hist_size, "Histogram channel size should be a power of two."); + + memset(y8hist, 0x00, hist_size * sizeof(*y8hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_rgb24(buf, buf_size, rgb24); + pixel_y8hist_add_y8(y8hist, pixel_rgb24_to_y8_bt601(rgb24), bit_depth); + } +} + +static inline void pixel_bayerframe_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval, + const uint8_t *idx) +{ + uint8_t bit_depth = LOG2(hist_size); + + __ASSERT(1 << bit_depth == hist_size, "Histogram channel size should be a power of two."); + + memset(y8hist, 0x00, hist_size * sizeof(*y8hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_bayer(buf, buf_size, width, rgb24, idx); + pixel_y8hist_add_y8(y8hist, pixel_rgb24_to_y8_bt709(rgb24), bit_depth); + } +} + +void pixel_rggb8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_rggb8); +} + +void pixel_gbrg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_gbrg8); +} + +void pixel_bggr8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_bggr8); +} + +void pixel_grbg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_grbg8); +} diff --git a/lib/pixel/stream.c b/lib/pixel/stream.c new file mode 100644 index 000000000000..78acd2952e85 --- /dev/null +++ b/lib/pixel/stream.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_stream, CONFIG_PIXEL_LOG_LEVEL); + +static void _assert_complete(struct pixel_stream *strm, bool begin) +{ + for (; strm != NULL; strm = strm->next) { + if (strm->run == NULL) { + continue; + } + + __ASSERT(ring_buf_size_get(&strm->ring) == 0, + "Core %s did not empty its input buffer, %u bytes left", strm->name, + ring_buf_size_get(&strm->ring)); + + if (begin && strm->line_offset == 0) { + continue; + } + + __ASSERT(strm->line_offset == strm->height, + "Core %s did only process %u lines out of %u", strm->name, + strm->line_offset, strm->height); + } +} + +void pixel_stream_load(struct pixel_stream *strm, const uint8_t *buf, size_t size) +{ + struct pixel_stream prev = {.next = strm}; + + __ASSERT_NO_MSG(strm != NULL); + __ASSERT_NO_MSG(buf != NULL); + + _assert_complete(strm, true); + + LOG_DBG("Loading %zu bytes into this pipeline:", size); + + for (struct pixel_stream *x = strm; x != NULL; x = x->next) { + LOG_DBG("- %s", x->name); + x->line_offset = 0; + x->total_time = 0; + } + + for (size_t i = 0; i + strm->pitch <= size; i += strm->pitch) { + LOG_DBG("bytes %zu-%zu/%zu into %s", i, i + strm->pitch, size, strm->name); + ring_buf_put(&strm->ring, &buf[i], strm->pitch); + pixel_stream_done(&prev); + } + + LOG_DBG("Processed a full buffer of %zu bytes", size); + + _assert_complete(strm, false); +} From 5570f3469aca21e12a9d14b0b21e4b90c745e37e Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 5 Mar 2025 01:42:48 +0000 Subject: [PATCH 4/8] samples: lib: pixel: show usage of the pixel library The newly introduced lib/pixel features utilities that help composing video pipelines together for the purpose of stream processing, as well as debug utilities. Signed-off-by: Josuah Demangeon --- samples/lib/lib.rst | 6 ++ samples/lib/pixel/pixel.rst | 14 +++++ samples/lib/pixel/print/CMakeLists.txt | 8 +++ samples/lib/pixel/print/README.rst | 33 +++++++++++ samples/lib/pixel/print/prj.conf | 6 ++ samples/lib/pixel/print/sample.yaml | 19 +++++++ samples/lib/pixel/print/src/main.c | 67 +++++++++++++++++++++++ samples/lib/pixel/resize/CMakeLists.txt | 8 +++ samples/lib/pixel/resize/README.rst | 32 +++++++++++ samples/lib/pixel/resize/prj.conf | 6 ++ samples/lib/pixel/resize/sample.yaml | 17 ++++++ samples/lib/pixel/resize/src/main.c | 47 ++++++++++++++++ samples/lib/pixel/stats/CMakeLists.txt | 8 +++ samples/lib/pixel/stats/README.rst | 29 ++++++++++ samples/lib/pixel/stats/prj.conf | 6 ++ samples/lib/pixel/stats/sample.yaml | 17 ++++++ samples/lib/pixel/stats/src/main.c | 57 +++++++++++++++++++ samples/lib/pixel/stream/CMakeLists.txt | 8 +++ samples/lib/pixel/stream/README.rst | 31 +++++++++++ samples/lib/pixel/stream/prj.conf | 6 ++ samples/lib/pixel/stream/sample.yaml | 16 ++++++ samples/lib/pixel/stream/src/main.c | 73 +++++++++++++++++++++++++ 22 files changed, 514 insertions(+) create mode 100644 samples/lib/lib.rst create mode 100644 samples/lib/pixel/pixel.rst create mode 100644 samples/lib/pixel/print/CMakeLists.txt create mode 100644 samples/lib/pixel/print/README.rst create mode 100644 samples/lib/pixel/print/prj.conf create mode 100644 samples/lib/pixel/print/sample.yaml create mode 100644 samples/lib/pixel/print/src/main.c create mode 100644 samples/lib/pixel/resize/CMakeLists.txt create mode 100644 samples/lib/pixel/resize/README.rst create mode 100644 samples/lib/pixel/resize/prj.conf create mode 100644 samples/lib/pixel/resize/sample.yaml create mode 100644 samples/lib/pixel/resize/src/main.c create mode 100644 samples/lib/pixel/stats/CMakeLists.txt create mode 100644 samples/lib/pixel/stats/README.rst create mode 100644 samples/lib/pixel/stats/prj.conf create mode 100644 samples/lib/pixel/stats/sample.yaml create mode 100644 samples/lib/pixel/stats/src/main.c create mode 100644 samples/lib/pixel/stream/CMakeLists.txt create mode 100644 samples/lib/pixel/stream/README.rst create mode 100644 samples/lib/pixel/stream/prj.conf create mode 100644 samples/lib/pixel/stream/sample.yaml create mode 100644 samples/lib/pixel/stream/src/main.c diff --git a/samples/lib/lib.rst b/samples/lib/lib.rst new file mode 100644 index 000000000000..89fd6e8db9cb --- /dev/null +++ b/samples/lib/lib.rst @@ -0,0 +1,6 @@ +.. zephyr:code-sample-category:: lib + :name: Libraries + :show-listing: + :live-search: + + These samples demonstrate how to use the libraries present in Zephyr. diff --git a/samples/lib/pixel/pixel.rst b/samples/lib/pixel/pixel.rst new file mode 100644 index 000000000000..bd54c015bdd1 --- /dev/null +++ b/samples/lib/pixel/pixel.rst @@ -0,0 +1,14 @@ +.. zephyr:code-sample-category:: lib_pixel + :name: Pixel Library + :show-listing: + :live-search: + + These samples demonstrate how to use the Pixel processing library of Zephyr. + +These samples can be used as starting point for test benches that print an input image, +perform some custom processing, and print the color image back along with the logs directly +on the terminal. + +The color rendering of the ``truecolor`` printing functions will be accurate RGB output. + +This helps debugging individual functions before integrating it into a larger streams. diff --git a/samples/lib/pixel/print/CMakeLists.txt b/samples/lib/pixel/print/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/print/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/print/README.rst b/samples/lib/pixel/print/README.rst new file mode 100644 index 000000000000..9348bde56d7e --- /dev/null +++ b/samples/lib/pixel/print/README.rst @@ -0,0 +1,33 @@ +.. zephyr:code-sample:: lib_pixel_print + :name: Pixel Printiing Library + + Print images on the console. + +Overview +******** + +A sample showcasing how to make use of the pixel library to visualize an image or histogram data +by printing it out on the console using `ANSI escape codes`_. + +This allow interleaving debug logs with small previews of the image as a way to debug image data. + +.. _ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code + +Building and Running +******************** + +This application can be built and executed on QEMU as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/print + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/print/prj.conf b/samples/lib/pixel/print/prj.conf new file mode 100644 index 000000000000..ee16fb9972b2 --- /dev/null +++ b/samples/lib/pixel/print/prj.conf @@ -0,0 +1,6 @@ +CONFIG_PIXEL=y +CONFIG_ASSERT=y +CONFIG_LOG=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/print/sample.yaml b/samples/lib/pixel/print/sample.yaml new file mode 100644 index 000000000000..c57890e7b124 --- /dev/null +++ b/samples/lib/pixel/print/sample.yaml @@ -0,0 +1,19 @@ +sample: + description: Pixel Print sample, print images in the terminal for debug purpose + name: pixel print +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "truecolor:" + - "256color:" + - "hexdump:" + - "histogram" +tests: + sample.pixel.print: + tags: pixel diff --git a/samples/lib/pixel/print/src/main.c b/samples/lib/pixel/print/src/main.c new file mode 100644 index 000000000000..1e183964f762 --- /dev/null +++ b/samples/lib/pixel/print/src/main.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +static uint8_t rgb24frame[16 * 32 * 3]; + +void print_image(void) +{ + const uint8_t beg[] = {0x00, 0x70, 0xc5}; + const uint8_t end[] = {0x79, 0x29, 0xd2}; + + /* Generate an image with a gradient of the two colors above */ + for (size_t i = 0, size = sizeof(rgb24frame); i + 3 <= size; i += 3) { + rgb24frame[i + 0] = (beg[0] * (size - i) + end[0] * i) / size; + rgb24frame[i + 1] = (beg[1] * (size - i) + end[1] * i) / size; + rgb24frame[i + 2] = (beg[2] * (size - i) + end[2] * i) / size; + } + + LOG_INF("Printing the gradient #%02x%02x%02x -> #%02x%02x%02x", + beg[0], beg[1], beg[2], end[0], end[1], end[2]); + + LOG_INF("hexdump:"); + pixel_print_rgb24frame_hex(rgb24frame, sizeof(rgb24frame), 16, 32); + + LOG_INF("truecolor:"); + pixel_print_rgb24frame_truecolor(rgb24frame, sizeof(rgb24frame), 16, 32); + + LOG_INF("256color:"); + pixel_print_rgb24frame_256color(rgb24frame, sizeof(rgb24frame), 16, 32); +} + +void print_histogram(void) +{ + static const uint16_t rgb24hist[] = { + 9, 4, 7, 1, 0, 5, 1, 0, 0, 2, 2, 3, 0, 1, 3, 0, + 7, 6, 5, 1, 1, 4, 2, 0, 1, 2, 3, 4, 1, 1, 2, 2, + 8, 4, 7, 4, 2, 3, 1, 2, 2, 2, 2, 2, 0, 0, 1, 1, + }; + + static const uint16_t y8hist[] = { + 8, 5, 6, 2, 1, 4, 1, 1, 1, 2, 3, 3, 1, 1, 2, 1, + }; + + LOG_INF("Printing a histogram of %zu RGB buckets", ARRAY_SIZE(rgb24hist)); + pixel_print_rgb24hist(rgb24hist, ARRAY_SIZE(rgb24hist), 8); + + LOG_INF("Printing a histogram of %zu Y (luma) buckets", ARRAY_SIZE(y8hist)); + pixel_print_y8hist(y8hist, ARRAY_SIZE(y8hist), 8); +} + +int main(void) +{ + print_image(); + print_histogram(); + + return 0; +} diff --git a/samples/lib/pixel/resize/CMakeLists.txt b/samples/lib/pixel/resize/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/resize/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/resize/README.rst b/samples/lib/pixel/resize/README.rst new file mode 100644 index 000000000000..6986200cc30c --- /dev/null +++ b/samples/lib/pixel/resize/README.rst @@ -0,0 +1,32 @@ +.. zephyr:code-sample:: lib_pixel_resize + :name: Pixel Resizing Library + + Resize an image using subsampling. + +Overview +******** + +A sample showcasing how to make use of the pixel library to resize an input image to a smaller or +bigger output image, using the subsampling method. This helps generating a smaller preview of an +input image. + +The input and output are printed as preview images on the console. + +Building and Running +******************** + +This application can be built and executed on the native simulator as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/resize + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/resize/prj.conf b/samples/lib/pixel/resize/prj.conf new file mode 100644 index 000000000000..ee16fb9972b2 --- /dev/null +++ b/samples/lib/pixel/resize/prj.conf @@ -0,0 +1,6 @@ +CONFIG_PIXEL=y +CONFIG_ASSERT=y +CONFIG_LOG=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/resize/sample.yaml b/samples/lib/pixel/resize/sample.yaml new file mode 100644 index 000000000000..e0c7a342725a --- /dev/null +++ b/samples/lib/pixel/resize/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Pixel Resize sample, down-scale/up-scale an image + name: pixel resize +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "output image, bigger," + - "output image, smaller," +tests: + sample.pixel.resize: + tags: pixel diff --git a/samples/lib/pixel/resize/src/main.c b/samples/lib/pixel/resize/src/main.c new file mode 100644 index 000000000000..ce3cbac342da --- /dev/null +++ b/samples/lib/pixel/resize/src/main.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +static void gradient(uint8_t *rgb24buf, size_t size, const uint8_t beg[3], const uint8_t end[3]) +{ + for (int i = 0; i + 3 <= size; i += 3) { + rgb24buf[i + 0] = (beg[0] * (size - i) + end[0] * i) / size; + rgb24buf[i + 1] = (beg[1] * (size - i) + end[1] * i) / size; + rgb24buf[i + 2] = (beg[2] * (size - i) + end[2] * i) / size; + } +} + +static uint8_t rgb24frame0[32 * 16 * 3]; +static uint8_t rgb24frame1[120 * 20 * 3]; +static uint8_t rgb24frame2[10 * 10 * 3]; + +int main(void) +{ + const uint8_t beg[] = {0x00, 0x70, 0xc5}; + const uint8_t end[] = {0x79, 0x29, 0xd2}; + + LOG_INF("input image, 32x16, %zu bytes:", sizeof(rgb24frame0)); + gradient(rgb24frame0, sizeof(rgb24frame0), beg, end); + pixel_print_rgb24frame_truecolor(rgb24frame0, sizeof(rgb24frame0), 32, 16); + + LOG_INF("output image, bigger, 120x16, %zu bytes:", sizeof(rgb24frame1)); + pixel_subsample_rgb24frame(rgb24frame0, 32, 16, rgb24frame1, 120, 20); + pixel_print_rgb24frame_truecolor(rgb24frame1, sizeof(rgb24frame1), 120, 20); + + LOG_INF("output image, smaller, 10x10, %zu bytes:", sizeof(rgb24frame2)); + pixel_subsample_rgb24frame(rgb24frame0, 32, 16, rgb24frame2, 10, 10); + pixel_print_rgb24frame_truecolor(rgb24frame2, sizeof(rgb24frame2), 10, 10); + + return 0; +} diff --git a/samples/lib/pixel/stats/CMakeLists.txt b/samples/lib/pixel/stats/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/stats/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/stats/README.rst b/samples/lib/pixel/stats/README.rst new file mode 100644 index 000000000000..945bf0247892 --- /dev/null +++ b/samples/lib/pixel/stats/README.rst @@ -0,0 +1,29 @@ +.. zephyr:code-sample:: lib_pixel_stats + :name: Pixel Statistics Library + + Collect statistics of an image. + +Overview +******** + +A sample showcasing how to make use of the pixel library to collect statistics of an input image +buffer and display both the image and statistics out on the console. + +Building and Running +******************** + +This application can be built and executed on the native simulator as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/stats + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/stats/prj.conf b/samples/lib/pixel/stats/prj.conf new file mode 100644 index 000000000000..ee16fb9972b2 --- /dev/null +++ b/samples/lib/pixel/stats/prj.conf @@ -0,0 +1,6 @@ +CONFIG_PIXEL=y +CONFIG_ASSERT=y +CONFIG_LOG=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/stats/sample.yaml b/samples/lib/pixel/stats/sample.yaml new file mode 100644 index 000000000000..cdd9ed009fda --- /dev/null +++ b/samples/lib/pixel/stats/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Pixel Stats sample, collect statistics of an input buffer + name: pixel stats +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "RGB histogram of the image" + - "RGB channel averages of the image" +tests: + sample.pixel.stats: + tags: pixel diff --git a/samples/lib/pixel/stats/src/main.c b/samples/lib/pixel/stats/src/main.c new file mode 100644 index 000000000000..2c9ab8aa105e --- /dev/null +++ b/samples/lib/pixel/stats/src/main.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +#define NVAL 100 + +static const uint8_t image_rgb24[20 * 4 * 3] = { + 0x47, 0x84, 0xee, 0x46, 0x84, 0xee, 0x47, 0x84, 0xee, 0x46, 0x83, 0xee, 0x47, 0x84, 0xee, + 0x78, 0xaa, 0xec, 0x74, 0xb2, 0xe0, 0x67, 0xaa, 0xdd, 0x78, 0xb2, 0xef, 0x39, 0x8c, 0xf1, + 0x3a, 0x8c, 0xf2, 0x39, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, + 0x3a, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8a, 0xf1, + 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, 0x47, 0x82, 0xee, 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, + 0x47, 0x82, 0xed, 0x5d, 0x93, 0xed, 0x5f, 0x9d, 0xeb, 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, + 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, + 0x3b, 0x89, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x89, 0xf0, 0x3c, 0x89, 0xf0, 0x3c, 0x89, 0xf0, + 0x49, 0x82, 0xee, 0x49, 0x82, 0xed, 0x49, 0x82, 0xee, 0x48, 0x82, 0xed, 0x49, 0x82, 0xee, + 0x49, 0x82, 0xed, 0x73, 0x92, 0xe9, 0x50, 0x65, 0xd4, 0x4c, 0x93, 0xf2, 0x3c, 0x8a, 0xf1, + 0x3c, 0x8a, 0xf1, 0x3c, 0x8a, 0xf0, 0x3c, 0x8a, 0xf1, 0x3c, 0x89, 0xf0, 0x3d, 0x8a, 0xf1, + 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3e, 0x89, 0xf0, 0x3d, 0x89, 0xf0, + 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x49, 0x81, 0xed, + 0x71, 0x8c, 0xe5, 0x3e, 0x4c, 0xcc, 0x3d, 0x4c, 0xcb, 0x65, 0x85, 0xe1, 0x3d, 0x89, 0xf0, + 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3e, 0x88, 0xf0, + 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3f, 0x88, 0xf0, 0x3e, 0x88, 0xef, +}; + +static uint16_t rgb24hist[3 * 64]; +static uint8_t rgb24avg[3]; + +int main(void) +{ + LOG_INF("Input image preview:"); + pixel_print_rgb24frame_truecolor(image_rgb24, sizeof(image_rgb24), 20, 4); + + LOG_INF("RGB histogram of the image"); + pixel_rgb24frame_to_rgb24hist(image_rgb24, sizeof(image_rgb24), + rgb24hist, ARRAY_SIZE(rgb24hist), NVAL); + pixel_print_rgb24hist(rgb24hist, ARRAY_SIZE(rgb24hist), 16); + + LOG_INF("RGB channel averages of the image"); + pixel_rgb24frame_to_rgb24avg(image_rgb24, sizeof(image_rgb24), rgb24avg, NVAL); + LOG_INF("- R: 0x%02x/0xff", rgb24avg[0]); + LOG_INF("- G: 0x%02x/0xff", rgb24avg[1]); + LOG_INF("- B: 0x%02x/0xff", rgb24avg[2]); + + return 0; +} diff --git a/samples/lib/pixel/stream/CMakeLists.txt b/samples/lib/pixel/stream/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/stream/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/stream/README.rst b/samples/lib/pixel/stream/README.rst new file mode 100644 index 000000000000..56b2aeab75d8 --- /dev/null +++ b/samples/lib/pixel/stream/README.rst @@ -0,0 +1,31 @@ +.. zephyr:code-sample:: lib_pixel_stream + :name: Pixel Streaming Library + + Convert an input frame as a stream of lines. + +Overview +******** + +A sample showcasing how to make use of the pixel library to convert its content without requiring +large intermediate buffers, but only line buffers. + +The input and output will both be printed as preview images on the console. + +Building and Running +******************** + +This application can be built and executed on the native simulator as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/stream + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/stream/prj.conf b/samples/lib/pixel/stream/prj.conf new file mode 100644 index 000000000000..e1495d188eed --- /dev/null +++ b/samples/lib/pixel/stream/prj.conf @@ -0,0 +1,6 @@ +CONFIG_ASSERT=y +CONFIG_LOG=y +CONFIG_PIXEL=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/stream/sample.yaml b/samples/lib/pixel/stream/sample.yaml new file mode 100644 index 000000000000..1088f61b00ce --- /dev/null +++ b/samples/lib/pixel/stream/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: Pixel Stream sample, convert a stream of lines from a frame + name: pixel stream +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "Output image preview:" +tests: + sample.lib.pixel.stream: + tags: pixel diff --git a/samples/lib/pixel/stream/src/main.c b/samples/lib/pixel/stream/src/main.c new file mode 100644 index 000000000000..5fbe520c5b13 --- /dev/null +++ b/samples/lib/pixel/stream/src/main.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +#define WIDTH 20 +#define HEIGHT 4 + +static const uint8_t rgb24frame[WIDTH * HEIGHT * 3] = { + 0x47, 0x84, 0xee, 0x46, 0x84, 0xee, 0x47, 0x84, 0xee, 0x46, 0x83, 0xee, 0x47, 0x84, 0xee, + 0x78, 0xaa, 0xec, 0x74, 0xb2, 0xe0, 0x67, 0xaa, 0xdd, 0x78, 0xb2, 0xef, 0x39, 0x8c, 0xf1, + 0x3a, 0x8c, 0xf2, 0x39, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, + 0x3a, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8a, 0xf1, + 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, 0x47, 0x82, 0xee, 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, + 0x47, 0x82, 0xed, 0x5d, 0x93, 0xed, 0x5f, 0x9d, 0xeb, 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, + 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, + 0x3b, 0x89, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x89, 0xf0, 0x3c, 0x89, 0xf0, 0x3c, 0x89, 0xf0, + 0x49, 0x82, 0xee, 0x49, 0x82, 0xed, 0x49, 0x82, 0xee, 0x48, 0x82, 0xed, 0x49, 0x82, 0xee, + 0x49, 0x82, 0xed, 0x73, 0x92, 0xe9, 0x50, 0x65, 0xd4, 0x4c, 0x93, 0xf2, 0x3c, 0x8a, 0xf1, + 0x3c, 0x8a, 0xf1, 0x3c, 0x8a, 0xf0, 0x3c, 0x8a, 0xf1, 0x3c, 0x89, 0xf0, 0x3d, 0x8a, 0xf1, + 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3e, 0x89, 0xf0, 0x3d, 0x89, 0xf0, + 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x49, 0x81, 0xed, + 0x71, 0x8c, 0xe5, 0x3e, 0x4c, 0xcc, 0x3d, 0x4c, 0xcb, 0x65, 0x85, 0xe1, 0x3d, 0x89, 0xf0, + 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3e, 0x88, 0xf0, + 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3f, 0x88, 0xf0, 0x3e, 0x88, 0xef, +}; +static uint8_t yuyvframe[WIDTH * HEIGHT * 2] = {0}; + +/* Stream conversion steps are declared at build time */ +PIXEL_STREAM_RGB24_TO_RGB565LE(step_rgb24_to_rgb565le, WIDTH, HEIGHT); +PIXEL_STREAM_RGB565LE_TO_RGB24(step_rgb565le_to_rgb24, WIDTH, HEIGHT); +PIXEL_STREAM_RGB24_TO_YUYV_BT709(step_rgb24_to_yuyv, WIDTH, HEIGHT); + +int main(void) +{ + struct pixel_stream root = {0}; + struct pixel_stream *step = &root; + struct pixel_stream step_export_yuyv = { + .ring = RING_BUF_INIT(yuyvframe, sizeof(yuyvframe)), + .pitch = WIDTH * 2, + .name = "[export_yuyv]", + }; + + LOG_INF("Input image preview:"); + pixel_print_rgb24frame_truecolor(rgb24frame, sizeof(rgb24frame), WIDTH, HEIGHT); + + /* Interconnection between the stream steps can be done arbitrarily at runtime */ + step = step->next = &step_rgb24_to_rgb565le; + step = step->next = &step_rgb565le_to_rgb24; + step = step->next = &step_rgb24_to_yuyv; + step = step->next = &step_export_yuyv; + pixel_stream_load(root.next, rgb24frame, sizeof(rgb24frame)); + + LOG_INF("Output image preview:"); + pixel_print_yuyvframe_bt709_truecolor(yuyvframe, sizeof(yuyvframe), WIDTH, HEIGHT); + + LOG_INF("Total time spent on each step:"); + for (step = root.next; step != NULL; step = step->next) { + LOG_INF(" %4u us on %s", k_cyc_to_us_ceil32(step->total_time), step->name); + } + + return 0; +} From 9ada23df6b062804b60e8ae888c38c205a1687e1 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 9 Mar 2025 22:44:01 +0000 Subject: [PATCH 5/8] lib: pixel: print: remove all UTF-8 characters This is a temporary test for CI only. Signed-off-by: Josuah Demangeon --- lib/pixel/print.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pixel/print.c b/lib/pixel/print.c index 3c6bf038dfac..7c70cdf87979 100644 --- a/lib/pixel/print.c +++ b/lib/pixel/print.c @@ -14,19 +14,19 @@ static void pixel_print_truecolor(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) { - printf("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%um▄", rgb24row0[0], rgb24row0[1], rgb24row0[2], + printf("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%umm", rgb24row0[0], rgb24row0[1], rgb24row0[2], rgb24row1[0], rgb24row1[1], rgb24row1[2]); } static void pixel_print_256color(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) { - printf("\e[48;5;%um\e[38;5;%um▄", pixel_rgb24_to_256color(rgb24row0), + printf("\e[48;5;%um\e[38;5;%umm", pixel_rgb24_to_256color(rgb24row0), pixel_rgb24_to_256color(rgb24row1)); } static void pixel_print_256gray(uint8_t gray8row0, uint8_t gray8row1) { - printf("\e[48;5;%um\e[38;5;%um▄", pixel_gray8_to_256color(gray8row0), + printf("\e[48;5;%um\e[38;5;%umm", pixel_gray8_to_256color(gray8row0), pixel_gray8_to_256color(gray8row1)); } @@ -45,7 +45,7 @@ static inline void pixel_print_rgb24(const uint8_t *rgb24, size_t size, uint16_t } fn(&rgb24[i], &rgb24[i + pitch]); } - printf("\e[m│\n"); + printf("\e[m|\n"); /* Skip the second h being printed at the same time */ i += pitch; @@ -88,7 +88,7 @@ static inline void pixel_print_rgb565(const uint8_t *rgb565, size_t size, uint16 fn(rgb24[0], rgb24[1]); } - printf("\e[m│\n"); + printf("\e[m|\n"); /* Skip the second h being printed at the same time */ i += pitch; @@ -149,7 +149,7 @@ static inline void pixel_print_yuyv(const uint8_t *yuyv, size_t size, uint16_t w fn(&rgb24x2[0][0], &rgb24x2[1][0]); fn(&rgb24x2[0][3], &rgb24x2[1][3]); } - printf("\e[m│\n"); + printf("\e[m|\n"); /* Skip the second h being printed at the same time */ i += pitch; @@ -327,7 +327,7 @@ void pixel_print_rgb24hist(const uint16_t *rgb24hist, size_t size, uint16_t heig pixel_print_256color(rgb24row0, rgb24row1); } - printf("\e[m│ - %u\n", h * max / height); + printf("\e[m| - %u\n", h * max / height); } pixel_print_hist_scale(size / 3); @@ -348,7 +348,7 @@ void pixel_print_y8hist(const uint16_t *y8hist, size_t size, uint16_t height) pixel_print_256gray(gray8row0, gray8row1); } - printf("\e[m│ - %u\n", h * max / height); + printf("\e[m| - %u\n", h * max / height); } pixel_print_hist_scale(size); From 58e4450189dda9e30633a832b62076a007f1f323 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sat, 8 Mar 2025 23:27:50 +0000 Subject: [PATCH 6/8] drivers: video: sw_stats: introduce software-based image statistics Add a new driver computing software statistics over a frame passed to it. This allows collecting channel averges or histogram of an input image using the video statistics API even when no hardware provides it. Signed-off-by: Josuah Demangeon --- drivers/video/CMakeLists.txt | 2 + drivers/video/Kconfig | 4 + drivers/video/Kconfig.sw_stats | 20 ++ drivers/video/video_sw_stats.c | 338 +++++++++++++++++++++++++++++++++ 4 files changed, 364 insertions(+) create mode 100644 drivers/video/Kconfig.sw_stats create mode 100644 drivers/video/video_sw_stats.c diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 102a841648e9..37268411f67e 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -9,6 +9,8 @@ zephyr_library_sources(video_device.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_MIPI_CSI2RX video_mcux_mipi_csi2rx.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_GENERATOR video_sw_generator.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_ISP video_sw_isp.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_STATS video_sw_stats.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MT9M114 mt9m114.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7725 ov7725.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640 ov2640.c) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index a3b163443628..5daabee522f0 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -58,6 +58,10 @@ source "drivers/video/Kconfig.mcux_mipi_csi2rx" source "drivers/video/Kconfig.sw_generator" +source "drivers/video/Kconfig.sw_isp" + +source "drivers/video/Kconfig.sw_stats" + source "drivers/video/Kconfig.mt9m114" source "drivers/video/Kconfig.ov7725" diff --git a/drivers/video/Kconfig.sw_stats b/drivers/video/Kconfig.sw_stats new file mode 100644 index 000000000000..3fa4af4aa706 --- /dev/null +++ b/drivers/video/Kconfig.sw_stats @@ -0,0 +1,20 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_SW_STATS + bool "Video Software Statistics" + select PIXEL + help + Enable a software-based frame statistics video device, providing information that can be + by an Image Processing Algorithm (IPA) via the video stats API. This allows applications + to tune the image colors through the video control API. + +config VIDEO_SW_STATS_NUM_SAMPLES + int "Number of pixels to sample on every frame" + default 400 + range 0 65535 + help + The more pixels are sample, the most accurate the statistics may be, but the slowest they + would be collected. The default value is estimated good enough not to slow-down most + systems. Note that the performance of statistics collection does not depend on the frame + size and only the number of samples, but accuracy does. diff --git a/drivers/video/video_sw_stats.c b/drivers/video/video_sw_stats.c new file mode 100644 index 000000000000..ea5e972cef39 --- /dev/null +++ b/drivers/video/video_sw_stats.c @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2025, tinyVision.ai Inc. + * Copyright (c) 2019, Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_sw_stats + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(video_sw_stats, CONFIG_VIDEO_LOG_LEVEL); + +#define NUM_SAMPLES CONFIG_VIDEO_SW_STATS_NUM_SAMPLES + +struct video_sw_stats_data { + const struct device *dev; + struct video_format fmt; + struct video_buffer *vbuf; + uint16_t frame_counter; +}; + +#define VIDEO_SW_STATS_FORMAT_CAP(pixfmt) \ + { \ + .pixelformat = pixfmt, \ + .width_min = 2, \ + .width_max = UINT16_MAX, \ + .height_min = 2, \ + .height_max = UINT16_MAX, \ + .width_step = 2, \ + .height_step = 2, \ + } + +static const struct video_format_cap fmts[] = { + VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_RGB24), + VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_RGGB8), + VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_GRBG8), + VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_BGGR8), + VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_GBRG8), + {0}, +}; + +static int video_sw_stats_set_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_sw_stats_data *data = dev->data; + int i; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + for (i = 0; fmts[i].pixelformat != 0; ++i) { + if (fmt->pixelformat == fmts[i].pixelformat && + IN_RANGE(fmt->width, fmts[i].width_min, fmts[i].width_max) && + IN_RANGE(fmt->height, fmts[i].height_min, fmts[i].height_max)) { + break; + } + } + + if (fmts[i].pixelformat == 0) { + LOG_ERR("Unsupported pixel format or resolution"); + return -ENOTSUP; + } + + data->fmt = *fmt; + + return 0; +} + +static int video_sw_stats_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_sw_stats_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + *fmt = data->fmt; + + return 0; +} + +static int video_sw_stats_set_stream(const struct device *dev, bool enable) +{ + return 0; +} + +static void video_sw_stats_histogram_y(const struct device *const dev, struct video_buffer *vbuf, + struct video_stats *stats) +{ + struct video_sw_stats_data *data = dev->data; + struct video_stats_histogram *hist = (void *)stats; + + LOG_DBG("%u buckets submitted, using %u bits per channel", + hist->num_buckets, LOG2(hist->num_buckets)); + + memset(hist->buckets, 0x00, hist->num_buckets * sizeof(uint16_t)); + + __ASSERT(hist->num_buckets % 3 == 0, "Each of R, G, B channel should have the same size."); + + /* Adjust the size to the lower power of two, as required by the pixel library */ + hist->num_buckets = 1 << LOG2(hist->num_buckets); + hist->num_values = NUM_SAMPLES; + stats->flags = VIDEO_STATS_HISTOGRAM_Y; + + LOG_DBG("Using %u buckets", hist->num_buckets); + + switch (data->fmt.pixelformat) { + case VIDEO_PIX_FMT_RGB24: + pixel_rgb24frame_to_y8hist(vbuf->buffer, vbuf->bytesused, hist->buckets, + hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_RGGB8: + pixel_rggb8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_GBRG8: + pixel_gbrg8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_BGGR8: + pixel_bggr8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_GRBG8: + pixel_grbg8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + default: + CODE_UNREACHABLE; + } +} + +static void video_sw_stats_histogram_rgb(const struct device *const dev, struct video_buffer *vbuf, + struct video_stats *stats) +{ + struct video_sw_stats_data *data = dev->data; + struct video_stats_histogram *hist = (void *)stats; + + LOG_DBG("%u buckets submitted, using %u bits per channel", + hist->num_buckets, LOG2(hist->num_buckets / 3)); + + stats->flags = VIDEO_STATS_HISTOGRAM_RGB; + + __ASSERT(hist->num_buckets % 3 == 0, "Each of R, G, B channel should have the same size."); + + /* Adjust the size to the lower power of two, as required by the pixel library */ + hist->num_buckets = (1 << LOG2(hist->num_buckets / 3)) * 3; + hist->num_values = NUM_SAMPLES; + + memset(hist->buckets, 0x00, hist->num_buckets * sizeof(uint16_t)); + + switch (data->fmt.pixelformat) { + case VIDEO_PIX_FMT_RGB24: + pixel_rgb24frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, hist->buckets, + hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_RGGB8: + pixel_rggb8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_GBRG8: + pixel_gbrg8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_BGGR8: + pixel_bggr8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_GRBG8: + pixel_grbg8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width, + hist->buckets, hist->num_buckets, NUM_SAMPLES); + break; + default: + CODE_UNREACHABLE; + } +} + +static void video_sw_stats_channels(const struct device *const dev, struct video_buffer *vbuf, + struct video_stats *stats) +{ + struct video_sw_stats_data *data = dev->data; + struct video_stats_channels *chan = (void *)stats; + + stats->flags = VIDEO_STATS_CHANNELS_RGB; + + chan->rgb[0] = chan->rgb[1] = chan->rgb[2] = chan->y = 0x00; + + switch (data->fmt.pixelformat) { + case VIDEO_PIX_FMT_RGB24: + pixel_rgb24frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, chan->rgb, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_RGGB8: + pixel_rggb8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width, + chan->rgb, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_GBRG8: + pixel_gbrg8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width, + chan->rgb, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_BGGR8: + pixel_bggr8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width, + chan->rgb, NUM_SAMPLES); + break; + case VIDEO_PIX_FMT_GRBG8: + pixel_grbg8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width, + chan->rgb, NUM_SAMPLES); + break; + default: + CODE_UNREACHABLE; + } +} + +static int video_sw_stats_enqueue(const struct device *dev, enum video_endpoint_id ep, + struct video_buffer *vbuf) +{ + struct video_sw_stats_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + if (data->vbuf != NULL) { + LOG_ERR("Buffer already loaded: %p, dequeue it first", data->vbuf); + return -ENOTSUP; + } + + if (vbuf->bytesused == 0 || vbuf->size == 0) { + LOG_ERR("The input buffer is empty"); + return -EINVAL; + } + + + data->frame_counter++; + data->vbuf = vbuf; + + return 0; +} + +static int video_sw_stats_get_stats(const struct device *dev, enum video_endpoint_id ep, + struct video_stats *stats) +{ + struct video_sw_stats_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) { + return -ENOTSUP; + } + + /* Wait that a frame is enqueued so that statistics can be performed on it */ + if (data->vbuf == NULL) { + LOG_DBG("No frame is currently loaded, cannot perform statistics"); + return -EAGAIN; + } + + LOG_DBG("Getting statistics out of %p (%u bytes)", data->vbuf->buffer, + data->vbuf->bytesused); + + if (stats->flags & VIDEO_STATS_CHANNELS) { + video_sw_stats_channels(dev, data->vbuf, stats); + } else if (stats->flags & VIDEO_STATS_HISTOGRAM_Y) { + video_sw_stats_histogram_y(dev, data->vbuf, stats); + } else if (stats->flags & VIDEO_STATS_HISTOGRAM_RGB) { + video_sw_stats_histogram_rgb(dev, data->vbuf, stats); + } + + stats->frame_counter = data->frame_counter; + + return 0; +} + +static int video_sw_stats_dequeue(const struct device *dev, enum video_endpoint_id ep, + struct video_buffer **vbuf, k_timeout_t timeout) +{ + struct video_sw_stats_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + *vbuf = data->vbuf; + if (*vbuf == NULL) { + return -EAGAIN; + } + + data->vbuf = NULL; + + return 0; +} + +static int video_sw_stats_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps) +{ + caps->format_caps = fmts; + caps->min_vbuf_count = 0; + + /* SW stats processes full frames */ + caps->min_line_count = caps->max_line_count = LINE_COUNT_HEIGHT; + + return 0; +} + +static DEVICE_API(video, video_sw_stats_driver_api) = { + .set_format = video_sw_stats_set_fmt, + .get_format = video_sw_stats_get_fmt, + .set_stream = video_sw_stats_set_stream, + .enqueue = video_sw_stats_enqueue, + .dequeue = video_sw_stats_dequeue, + .get_caps = video_sw_stats_get_caps, + .get_stats = video_sw_stats_get_stats, +}; + +static struct video_sw_stats_data video_sw_stats_data_0 = { + .fmt.width = 320, + .fmt.height = 160, + .fmt.pitch = 320 * 2, + .fmt.pixelformat = VIDEO_PIX_FMT_RGB565, +}; + +static int video_sw_stats_init(const struct device *dev) +{ + struct video_sw_stats_data *data = dev->data; + + data->dev = dev; + + return 0; +} + +DEVICE_DEFINE(video_sw_stats, "VIDEO_SW_STATS", &video_sw_stats_init, NULL, &video_sw_stats_data_0, + NULL, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &video_sw_stats_driver_api); From abe14a6f1535ecffcc26d325119f22c87105d54e Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 9 Mar 2025 13:07:38 +0000 Subject: [PATCH 7/8] tests: drivers: video: sw_stats: add tests for VIDEO_SW_DRIVER Test it by filling a buffer manually with test data and verify that the statistics produced are matching. Signed-off-by: Josuah Demangeon --- tests/drivers/video/sw_stats/CMakeLists.txt | 7 ++ tests/drivers/video/sw_stats/prj.conf | 7 ++ tests/drivers/video/sw_stats/src/main.c | 99 +++++++++++++++++++++ tests/drivers/video/sw_stats/testcase.yaml | 9 ++ 4 files changed, 122 insertions(+) create mode 100644 tests/drivers/video/sw_stats/CMakeLists.txt create mode 100644 tests/drivers/video/sw_stats/prj.conf create mode 100644 tests/drivers/video/sw_stats/src/main.c create mode 100644 tests/drivers/video/sw_stats/testcase.yaml diff --git a/tests/drivers/video/sw_stats/CMakeLists.txt b/tests/drivers/video/sw_stats/CMakeLists.txt new file mode 100644 index 000000000000..175d5af7c288 --- /dev/null +++ b/tests/drivers/video/sw_stats/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(drivers_video_sw_stats) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/drivers/video/sw_stats/prj.conf b/tests/drivers/video/sw_stats/prj.conf new file mode 100644 index 000000000000..212fa7aaf8c5 --- /dev/null +++ b/tests/drivers/video/sw_stats/prj.conf @@ -0,0 +1,7 @@ +CONFIG_LOG=y +CONFIG_VIDEO_LOG_LEVEL_DBG=y +CONFIG_ZTEST=y +CONFIG_VIDEO=y +CONFIG_VIDEO_SW_STATS=y +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=16384 +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=1 diff --git a/tests/drivers/video/sw_stats/src/main.c b/tests/drivers/video/sw_stats/src/main.c new file mode 100644 index 000000000000..218ca857ee98 --- /dev/null +++ b/tests/drivers/video/sw_stats/src/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define WIDTH 640 +#define HEIGHT 480 + +uint8_t rggb8frame[WIDTH * HEIGHT * 1]; +uint16_t rgb24hist[(1 << 4) * 3]; + +ZTEST(video_sw_stats, test_video_sw_stats) +{ + const struct device *dev = device_get_binding("VIDEO_SW_STATS"); + struct video_buffer *vbufp; + struct video_stats_channels chan = { + .base.flags = VIDEO_STATS_CHANNELS_RGB + }; + struct video_stats_histogram hist = { + .base.flags = VIDEO_STATS_HISTOGRAM_RGB, + .buckets = rgb24hist, + .num_buckets = ARRAY_SIZE(rgb24hist), + }; + struct video_buffer vbuf = { + .buffer = rggb8frame, + .size = sizeof(rggb8frame), + .bytesused = sizeof(rggb8frame), + }; + struct video_format fmt = { + .pixelformat = VIDEO_PIX_FMT_RGGB8, + .width = WIDTH, + .pitch = WIDTH * video_bits_per_pixel(VIDEO_PIX_FMT_RGGB8) / BITS_PER_BYTE, + .height = HEIGHT, + }; + + zexpect_not_null(dev); + zexpect_true(device_is_ready(dev)); + + /* Load test data on the buffer: upper half completely white */ + + memset(rggb8frame, 0xff, sizeof(rggb8frame) / 2); + + zexpect_ok(video_set_format(dev, VIDEO_EP_IN, &fmt)); + + /* Test loading a buffer into the device */ + + zexpect_ok(video_enqueue(dev, VIDEO_EP_IN, &vbuf)); + + /* Test collecting image statistics out of this buffer */ + + zexpect_ok(video_get_stats(dev, VIDEO_EP_IN, &chan.base)); + zexpect_ok(video_get_stats(dev, VIDEO_EP_IN, &hist.base)); + + /* Test collecting image statistics out of this buffer */ + + zexpect_ok(video_dequeue(dev, VIDEO_EP_IN, &vbufp, K_NO_WAIT)); + zexpect_not_null(vbufp); + + /* Check the statistics content of channel averages */ + + zexpect_equal(chan.base.flags & VIDEO_STATS_CHANNELS_RGB, VIDEO_STATS_CHANNELS_RGB); + zexpect_within(chan.rgb[0], 0xff / 2, 10, "%u is average", chan.rgb[0]); + zexpect_within(chan.rgb[1], 0xff / 2, 10, "%u is average", chan.rgb[1]); + zexpect_within(chan.rgb[2], 0xff / 2, 10, "%u is average", chan.rgb[2]); + + /* Check the channel averagres */ + + zexpect_equal(hist.base.flags & VIDEO_STATS_HISTOGRAM_RGB, VIDEO_STATS_HISTOGRAM_RGB); + zexpect_not_equal(hist.num_buckets, 0, "The histogram size must not be zero."); + zexpect_not_equal(hist.num_values, 0, "The histogram must not be empty."); + + /* Check the histograms */ + + zexpect_within(hist.buckets[0], hist.num_values / 2, 10, + "Half of the image is filled with 0x00"); + + zexpect_within(hist.buckets[hist.num_buckets - 1], hist.num_values / 2, 10, + "Half of the image is filled with 0xff"); + + for (size_t i = hist.num_buckets * 0 / 3 + 1; i < hist.num_buckets * 1 / 3 - 1; i++) { + zexpect_equal(hist.buckets[i], 0, "%u: Only 0x00 or 0xff for the red channel", i); + } + + for (size_t i = hist.num_buckets * 1 / 3 + 1; i < hist.num_buckets * 2 / 3 - 1; i++) { + zexpect_equal(hist.buckets[i], 0, "%u: Only 0x00 or 0xff in the green channel", i); + } + + for (size_t i = hist.num_buckets * 2 / 3 + 1; i < hist.num_buckets * 3 / 3 - 1; i++) { + zexpect_equal(hist.buckets[i], 0, "%u: Only 0x00 or 0xff in the blue channel", i); + } +} + +ZTEST_SUITE(video_sw_stats, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/video/sw_stats/testcase.yaml b/tests/drivers/video/sw_stats/testcase.yaml new file mode 100644 index 000000000000..7668c4159ca4 --- /dev/null +++ b/tests/drivers/video/sw_stats/testcase.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.video.sw_stats: + tags: + - drivers + - video + platform_allow: native_sim From f64d37e78b79e4305863e678388c05f7290ebdec Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sat, 8 Mar 2025 23:39:12 +0000 Subject: [PATCH 8/8] tests: drivers: video: api: fix a typo in the ztest macro The typo is `video_common` used while `video_emul` should have been used. Signed-off-by: Josuah Demangeon --- tests/drivers/video/api/src/video_emul.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/drivers/video/api/src/video_emul.c b/tests/drivers/video/api/src/video_emul.c index f6435496fad3..ef7c0b3f37f2 100644 --- a/tests/drivers/video/api/src/video_emul.c +++ b/tests/drivers/video/api/src/video_emul.c @@ -11,7 +11,7 @@ const struct device *rx_dev = DEVICE_DT_GET(DT_NODELABEL(test_video_emul_rx)); const struct device *imager_dev = DEVICE_DT_GET(DT_NODELABEL(test_video_emul_imager)); -ZTEST(video_common, test_video_device) +ZTEST(video_emul, test_video_device) { zexpect_true(device_is_ready(rx_dev)); zexpect_true(device_is_ready(imager_dev)); @@ -23,7 +23,7 @@ ZTEST(video_common, test_video_device) zexpect_ok(video_stream_stop(rx_dev)); } -ZTEST(video_common, test_video_format) +ZTEST(video_emul, test_video_format) { struct video_caps caps = {0}; struct video_format fmt = {0}; @@ -73,7 +73,7 @@ ZTEST(video_common, test_video_format) zexpect_not_equal(fmt.pixelformat, 0x00000000, "should not store wrong formats"); } -ZTEST(video_common, test_video_frmival) +ZTEST(video_emul, test_video_frmival) { struct video_format fmt; struct video_frmival_enum fie = {.format = &fmt}; @@ -125,7 +125,7 @@ ZTEST(video_common, test_video_frmival) } while (video_enum_frmival(imager_dev, VIDEO_EP_OUT, &fie) == 0); } -ZTEST(video_common, test_video_ctrl) +ZTEST(video_emul, test_video_ctrl) { struct video_control ctrl = {.id = VIDEO_CID_PRIVATE_BASE + 0x01, .val = 30}; @@ -136,7 +136,7 @@ ZTEST(video_common, test_video_ctrl) zexpect_equal(ctrl.val, 30); } -ZTEST(video_common, test_video_vbuf) +ZTEST(video_emul, test_video_vbuf) { struct video_caps caps; struct video_format fmt;