diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 102a841648e9..7e7fa157971e 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -9,6 +9,7 @@ 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_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..c826f59bd9d1 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -58,6 +58,8 @@ source "drivers/video/Kconfig.mcux_mipi_csi2rx" source "drivers/video/Kconfig.sw_generator" +source "drivers/video/Kconfig.sw_isp" + source "drivers/video/Kconfig.mt9m114" source "drivers/video/Kconfig.ov7725" diff --git a/drivers/video/Kconfig.sw_isp b/drivers/video/Kconfig.sw_isp new file mode 100644 index 000000000000..2fbf3bc94e5f --- /dev/null +++ b/drivers/video/Kconfig.sw_isp @@ -0,0 +1,37 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_SW_ISP + bool "Video Software Image Signal Processor (ISP)" + imply PIXEL + help + Enable a software-based image processing pipeline, providing the essential components + of an Image Signal Processsor (ISP) exposing the essential features of the "pixel" + library as a native Video API. + +config VIDEO_SW_ISP_STACK_SIZE + int "Stack size for the thread executing the pipelne" + default 1024 + help + Stack size for the thread executing the pipeline. All of the processing elements are not + allocated on the stack but use global variables instead. The default value is therefore + planning only for the temporary variables. + +config VIDEO_SW_ISP_THREAD_PRIORITY + int "Stack size for the thread executing the pipelne" + default 5 + help + Priority for the thread executing the pipeline. The default value is an intermediate + starting point to be tuned arbitrarily to fit the application. + +config VIDEO_SW_ISP_INPUT_WIDTH + int "Width of the input of the pipeline in pixels, used for buffer allocation purposees." + default 640 + help + The default value is VGA width, a widespread default resolution supported broadly. + +config VIDEO_SW_ISP_INPUT_HEIGHT + int "Height of the input of the pipeline in pixels, used for buffer allocation purposees." + default 480 + help + The default value is VGA height, a widespread default resolution supported broadly. diff --git a/drivers/video/video_sw_isp.c b/drivers/video/video_sw_isp.c new file mode 100644 index 000000000000..0d3a8207b5c7 --- /dev/null +++ b/drivers/video/video_sw_isp.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * Copyright (c) 2019, Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(video_sw_isp, CONFIG_VIDEO_LOG_LEVEL); + +#define WIDTH CONFIG_VIDEO_SW_ISP_INPUT_WIDTH +#define HEIGHT CONFIG_VIDEO_SW_ISP_INPUT_HEIGHT + +struct video_sw_isp_data { + const struct device *dev; + struct k_fifo fifo_input_in; + struct k_fifo fifo_input_out; + struct k_fifo fifo_output_in; + struct k_fifo fifo_output_out; + struct k_poll_signal *sig; + struct video_format fmt_in; + struct video_format fmt_out; + struct pixel_stream *first_step; + struct pixel_stream step_export; + struct k_mutex mutex; +}; + +#define VIDEO_SW_ISP_FORMAT_CAP(pixfmt) \ + { \ + .pixelformat = (pixfmt), \ + .width_min = WIDTH, \ + .width_max = WIDTH, \ + .height_min = HEIGHT, \ + .height_max = HEIGHT, \ + .width_step = 0, \ + .height_step = 0, \ + } + +static const struct video_format_cap fmts_in[] = { + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB24), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB565), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_YUYV), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGGB8), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_GRBG8), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_BGGR8), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_GBRG8), + {0}, +}; + +static const struct video_format_cap fmts_out[] = { + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB24), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB565), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_YUYV), + {0}, +}; + +/* Bayer to RGB24 conversion */ +static PIXEL_STREAM_RGGB8_TO_RGB24_3X3(step_rggb8_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_GBRG8_TO_RGB24_3X3(step_gbrg8_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_BGGR8_TO_RGB24_3X3(step_bggr8_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_GRBG8_TO_RGB24_3X3(step_grbg8_to_rgb24, WIDTH, HEIGHT); + +/* Pixel format conversion */ +static PIXEL_STREAM_RGB24_TO_YUYV_BT709(step_rgb24_to_yuyv, WIDTH, HEIGHT); +static PIXEL_STREAM_YUYV_TO_RGB24_BT709(step_yuyv_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_RGB565LE_TO_RGB24(step_rgb565le_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_RGB24_TO_RGB565LE(step_rgb24_to_rgb565le, WIDTH, HEIGHT); + +static void video_sw_isp_thread(void *p0, void *p1, void *p2) +{ + struct video_sw_isp_data *data = p0; + const struct device *dev = data->dev; + + while (true) { + struct video_buffer *input; + struct video_buffer *output; + struct video_format fmt_out = {0}; + + input = k_fifo_get(&data->fifo_input_in, K_FOREVER); + output = k_fifo_get(&data->fifo_output_in, K_FOREVER); + + /* Wait indefinitely if the stream is stopped */ + k_mutex_lock(&data->mutex, K_FOREVER); + + /* Do not block the thread calling video_set_stream(), check again next cycle */ + k_mutex_unlock(&data->mutex); + + video_get_format(dev, VIDEO_EP_OUT, &fmt_out); + + /* Load the video buffer into the last struct */ + ring_buf_init(&data->step_export.ring, output->size, output->buffer); + data->step_export.height = fmt_out.height; + data->step_export.pitch = fmt_out.pitch; + data->step_export.run = NULL; + pixel_stream_load(data->first_step, input->buffer, input->bytesused); + output->bytesused = ring_buf_size_get(&data->step_export.ring); + + LOG_DBG("Converted a buffer from %p (%u bytes) to %p (%u bytes)", input->buffer, + input->bytesused, output->buffer, output->bytesused); + + /* Move the buffer from submission to completion queue for the input endpoint */ + k_fifo_get(&data->fifo_input_in, K_NO_WAIT); + k_fifo_put(&data->fifo_input_out, input); + + /* Move the buffer from submission to completion queue for the output endpoint */ + k_fifo_get(&data->fifo_output_in, K_NO_WAIT); + k_fifo_put(&data->fifo_output_out, output); + + if (IS_ENABLED(CONFIG_POLL) && data->sig != NULL) { + k_poll_signal_raise(data->sig, VIDEO_BUF_DONE); + } + } +} + +static int video_sw_isp_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps) +{ + caps->min_vbuf_count = 0; + caps->min_line_count = 1; + caps->max_line_count = LINE_COUNT_HEIGHT; + + switch (ep) { + case VIDEO_EP_IN: + caps->format_caps = fmts_in; + return 0; + case VIDEO_EP_OUT: + caps->format_caps = fmts_out; + return 0; + default: + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } +} + +static int video_sw_isp_reconfigure(const struct device *dev) +{ + struct video_sw_isp_data *data = dev->data; + struct pixel_stream root = {0}; + struct pixel_stream *strm = &root; + struct video_format fmt_in = {0}; + struct video_format fmt_out = {0}; + uint32_t pixfmt; + int ret = 0; + + video_get_format(dev, VIDEO_EP_IN, &fmt_in); + video_get_format(dev, VIDEO_EP_OUT, &fmt_out); + pixfmt = fmt_in.pixelformat; + + /* Entering the Unpacking stage */ + + switch (pixfmt) { + case VIDEO_PIX_FMT_RGB565: + strm = strm->next = &step_rgb565le_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + } + + /* Entering the debayer stage */ + + switch (pixfmt) { + case VIDEO_PIX_FMT_RGGB8: + strm = strm->next = &step_rggb8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + case VIDEO_PIX_FMT_GRBG8: + strm = strm->next = &step_grbg8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + case VIDEO_PIX_FMT_BGGR8: + strm = strm->next = &step_bggr8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + case VIDEO_PIX_FMT_GBRG8: + strm = strm->next = &step_gbrg8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + } + + /* Entering the Output stage */ + + switch (fmt_out.pixelformat) { + case VIDEO_PIX_FMT_RGB565: + switch (pixfmt) { + case VIDEO_PIX_FMT_RGB565: + break; + case VIDEO_PIX_FMT_RGB24: + strm = strm->next = &step_rgb24_to_rgb565le; + break; + case VIDEO_PIX_FMT_YUYV: + strm = strm->next = &step_yuyv_to_rgb24; + strm = strm->next = &step_rgb24_to_rgb565le; + break; + default: + LOG_ERR("Cannot convert '%c%c%c%c' to RGB565LE", pixfmt >> 24, pixfmt >> 16, + pixfmt >> 8, pixfmt); + ret = -ENOTSUP; + goto end; + } + pixfmt = VIDEO_PIX_FMT_RGB565; + break; + case VIDEO_PIX_FMT_YUYV: + switch (pixfmt) { + case VIDEO_PIX_FMT_YUYV: + break; + case VIDEO_PIX_FMT_RGB24: + strm = strm->next = &step_rgb24_to_yuyv; + break; + default: + LOG_ERR("Cannot convert '%c%c%c%c' to YUYV", pixfmt >> 24, pixfmt >> 16, + pixfmt >> 8, pixfmt); + ret = -ENOTSUP; + goto end; + } + break; + case VIDEO_PIX_FMT_RGB24: + switch (pixfmt) { + case VIDEO_PIX_FMT_RGB24: + break; + case VIDEO_PIX_FMT_YUYV: + strm = strm->next = &step_yuyv_to_rgb24; + break; + default: + LOG_ERR("Cannot convert '%c%c%c%c' to RGB24", pixfmt >> 24, pixfmt >> 16, + pixfmt >> 8, pixfmt); + ret = -ENOTSUP; + goto end; + } + break; + default: + LOG_ERR("Unknown output format '%c%c%c%c'", fmt_out.pixelformat >> 24, + fmt_out.pixelformat >> 16, fmt_out.pixelformat >> 8, fmt_out.pixelformat); + ret = -ENOTSUP; + goto end; + } + + /* Entering the Export stage */ + + strm = strm->next = &data->step_export; + strm->name = "[export video_sw_isp " STRINGIFY(WIDTH) "x" STRINGIFY(HEIGHT) "]"; + +end: + data->first_step = root.next; + + LOG_INF("Current topology:"); + for (strm = data->first_step; strm != NULL; strm = strm->next) { + LOG_INF("- %s, pitch %u", strm->name, strm->pitch); + } + + return ret; +} + +static int video_sw_isp_set_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_sw_isp_data *data = dev->data; + const struct video_format_cap *cap; + size_t i; + int ret; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_OUT) { + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + LOG_DBG("Asking for format '%c%c%c%c' %ux%u", fmt->pixelformat >> 24, + fmt->pixelformat >> 16, fmt->pixelformat >> 8, fmt->pixelformat, fmt->width, + fmt->height); + + cap = (ep == VIDEO_EP_IN) ? &fmts_in[0] : &fmts_out[0]; + + for (i = 0; cap[i].pixelformat != 0; i++) { + if (fmt->pixelformat != cap[i].pixelformat && + IN_RANGE(fmt->width, cap[i].width_min, cap[i].width_max) && + IN_RANGE(fmt->height, cap[i].height_min, cap[i].height_max)) { + break; + } + } + + if (cap[i].pixelformat == 0) { + LOG_ERR("Trying to set an unsupported format '%c%c%c%c'", cap[i].pixelformat >> 24, + cap[i].pixelformat >> 16, cap[i].pixelformat >> 8, cap[i].pixelformat); + return -ENOTSUP; + } + + memcpy((ep == VIDEO_EP_IN) ? &data->fmt_in : &data->fmt_out, fmt, sizeof(*fmt)); + + ret = video_sw_isp_reconfigure(dev); + if (ret != 0) { + LOG_ERR("Failed to reconfigure the isp"); + return ret; + } + + return 0; +} + +static int video_sw_isp_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_sw_isp_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_OUT) { + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + memcpy(fmt, (ep == VIDEO_EP_IN) ? &data->fmt_in : &data->fmt_out, sizeof(*fmt)); + + return 0; +} + +static int video_sw_isp_flush(const struct device *dev, enum video_endpoint_id ep, bool cancel) +{ + struct video_sw_isp_data *data = dev->data; + struct video_buffer *vbuf; + + if (cancel) { + /* Skip all the buffers of the input endpointo, unprocessed */ + while ((vbuf = k_fifo_get(&data->fifo_input_in, K_NO_WAIT)) != NULL) { + k_fifo_put(&data->fifo_input_out, vbuf); + if (IS_ENABLED(CONFIG_POLL) && data->sig) { + k_poll_signal_raise(data->sig, VIDEO_BUF_ABORTED); + } + } + /* Skip all the buffers of the output endpoint, unprocessed */ + while ((vbuf = k_fifo_get(&data->fifo_output_in, K_NO_WAIT)) != NULL) { + k_fifo_put(&data->fifo_output_out, vbuf); + if (IS_ENABLED(CONFIG_POLL) && data->sig) { + k_poll_signal_raise(data->sig, VIDEO_BUF_ABORTED); + } + } + } else { + /* Wait for all buffer to be processed on the input endpointo */ + while (!k_fifo_is_empty(&data->fifo_input_in)) { + k_sleep(K_MSEC(1)); + } + /* Wait for all buffer to be processed on the output endpointo */ + while (!k_fifo_is_empty(&data->fifo_output_in)) { + k_sleep(K_MSEC(1)); + } + } + + return 0; +} + +static int video_sw_isp_enqueue(const struct device *dev, enum video_endpoint_id ep, + struct video_buffer *vbuf) +{ + struct video_sw_isp_data *data = dev->data; + + switch (ep) { + case VIDEO_EP_IN: + k_fifo_put(&data->fifo_input_in, vbuf); + break; + case VIDEO_EP_OUT: + k_fifo_put(&data->fifo_output_in, vbuf); + break; + default: + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + return 0; +} + +static int video_sw_isp_dequeue(const struct device *dev, enum video_endpoint_id ep, + struct video_buffer **vbuf, k_timeout_t timeout) +{ + struct video_sw_isp_data *data = dev->data; + + switch (ep) { + case VIDEO_EP_IN: + *vbuf = k_fifo_get(&data->fifo_input_out, timeout); + break; + case VIDEO_EP_OUT: + *vbuf = k_fifo_get(&data->fifo_output_out, timeout); + break; + default: + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + if (*vbuf == NULL) { + LOG_DBG("Failed to dequeue %s buffer", ep == VIDEO_EP_IN ? "input" : "output"); + return -EAGAIN; + } + + return 0; +} + +static int video_sw_isp_set_stream(const struct device *dev, bool enable) +{ + struct video_sw_isp_data *data = dev->data; + + if (enable) { + /* Release the stream processing thread */ + k_mutex_unlock(&data->mutex); + } else { + /* This will stop the stream thread without blocking this thread for long */ + k_mutex_lock(&data->mutex, K_FOREVER); + } + + return 0; +} + +#ifdef CONFIG_POLL +static int video_sw_isp_set_signal(const struct device *dev, enum video_endpoint_id ep, + struct k_poll_signal *sig) +{ + struct video_sw_isp_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + data->sig = sig; + + return 0; +} +#endif + +static DEVICE_API(video, video_sw_isp_driver_api) = { + .set_format = video_sw_isp_set_fmt, + .get_format = video_sw_isp_get_fmt, + .flush = video_sw_isp_flush, + .enqueue = video_sw_isp_enqueue, + .dequeue = video_sw_isp_dequeue, + .get_caps = video_sw_isp_get_caps, + .set_stream = video_sw_isp_set_stream, +#ifdef CONFIG_POLL + .set_signal = video_sw_isp_set_signal, +#endif +}; + +static int video_sw_isp_init(const struct device *dev) +{ + struct video_sw_isp_data *data = dev->data; + struct video_format fmt_in = { + .pixelformat = fmts_in[0].pixelformat, + .width = WIDTH, + .height = HEIGHT, + .pitch = WIDTH * video_bits_per_pixel(fmts_in[0].pixelformat) / BITS_PER_BYTE, + }; + struct video_format fmt_out = { + .pixelformat = fmts_out[0].pixelformat, + .width = WIDTH, + .height = HEIGHT, + .pitch = WIDTH * video_bits_per_pixel(fmts_out[0].pixelformat) / BITS_PER_BYTE, + }; + int ret; + + data->dev = dev; + k_fifo_init(&data->fifo_input_in); + k_fifo_init(&data->fifo_input_out); + k_fifo_init(&data->fifo_output_in); + k_fifo_init(&data->fifo_output_out); + + ret = video_sw_isp_set_fmt(dev, VIDEO_EP_IN, &fmt_in); + if (ret != 0) { + return ret; + } + + ret = video_sw_isp_set_fmt(dev, VIDEO_EP_OUT, &fmt_out); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct video_sw_isp_data video_sw_isp_data_0 = { + .fmt_in.pixelformat = VIDEO_PIX_FMT_RGB24, + .fmt_out.pixelformat = VIDEO_PIX_FMT_RGB24, +}; + +DEVICE_DEFINE(video_sw_isp, "VIDEO_SW_ISP", &video_sw_isp_init, NULL, &video_sw_isp_data_0, NULL, + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &video_sw_isp_driver_api); + +K_THREAD_DEFINE(video_sw_isp, CONFIG_VIDEO_SW_ISP_STACK_SIZE, video_sw_isp_thread, + &video_sw_isp_data_0, NULL, NULL, CONFIG_VIDEO_SW_ISP_THREAD_PRIORITY, 0, 0); diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 67212110ebcd..9b1cceb344a6 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -1292,6 +1292,15 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, */ #define VIDEO_PIX_FMT_XRGB32 VIDEO_FOURCC('B', 'X', '2', '4') +/** + * Red, Green, Blue, one byte per channel, 24-bits in total. + * + * @verbatim + * | Rrrrrrrr Gggggggg Bbbbbbbb | ... + * @endverbatim + */ +#define VIDEO_PIX_FMT_RGB24 VIDEO_FOURCC('R', 'G', 'B', '3') + /** * @} */ 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/build_all/video/prj.conf b/tests/drivers/build_all/video/prj.conf index b96d041d3b40..60ee761e7fbe 100644 --- a/tests/drivers/build_all/video/prj.conf +++ b/tests/drivers/build_all/video/prj.conf @@ -3,3 +3,4 @@ CONFIG_TEST_USERSPACE=y CONFIG_GPIO=y CONFIG_I2C=y CONFIG_VIDEO=y +CONFIG_VIDEO_SW_ISP=y