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_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/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); 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. * 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..7c70cdf87979 --- /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;%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;%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;%umm", 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); +} 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; +} diff --git a/tests/drivers/video/api/src/video_emul.c b/tests/drivers/video/api/src/video_emul.c index cd58bbf0506b..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; @@ -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); 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