From 324f34e539f6ceb544faba2b868bf27c8f98ea83 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 29 Apr 2025 18:59:58 +0000 Subject: [PATCH 1/7] drivers: video: add the YUV24 format Introduce the YUV24 format following the Linux V4L2 naming and fourcc definition. Signed-off-by: Josuah Demangeon --- include/zephyr/drivers/video.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 67212110ebcde..e18435deacd08 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -1333,6 +1333,13 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, */ #define VIDEO_PIX_FMT_UYVY VIDEO_FOURCC('U', 'Y', 'V', 'Y') +/** + * @code{.unparsed} + * | Yyyyyyyy Uuuuuuuu Vvvvvvvv | ... + * @endcode + */ +#define VIDEO_PIX_FMT_YUV24 VIDEO_FOURCC('Y', 'U', 'V', '3') + /** * The first byte is empty (X) for each pixel. * @@ -1419,6 +1426,7 @@ static inline unsigned int video_bits_per_pixel(uint32_t pixfmt) return 16; case VIDEO_PIX_FMT_BGR24: case VIDEO_PIX_FMT_RGB24: + case VIDEO_PIX_FMT_YUV24: return 24; case VIDEO_PIX_FMT_XRGB32: case VIDEO_PIX_FMT_XYUV32: From 1de34ea5530e45b846dc47720fcc8b302299e7b0 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 29 Apr 2025 20:15:45 +0000 Subject: [PATCH 2/7] drivers: video: add the RGB332 format Introduce the RGB332 format following the Linux V4L2 naming and fourcc definition. Signed-off-by: Josuah Demangeon --- include/zephyr/drivers/video.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index e18435deacd08..84f1bd264d28e 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -1211,6 +1211,13 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, * @{ */ +/** + * @code{.unparsed} + * | RrrGggBb | ... + * @endcode + */ +#define VIDEO_PIX_FMT_RGB332 VIDEO_FOURCC('R', 'G', 'B', '1') + /** * 5 red bits [15:11], 6 green bits [10:5], 5 blue bits [4:0]. * This 16-bit integer is then packed in big endian format over two bytes: @@ -1383,6 +1390,7 @@ static inline unsigned int video_bits_per_pixel(uint32_t pixfmt) case VIDEO_PIX_FMT_GRBG8: case VIDEO_PIX_FMT_RGGB8: case VIDEO_PIX_FMT_GREY: + case VIDEO_PIX_FMT_RGB332: return 8; case VIDEO_PIX_FMT_SBGGR10P: case VIDEO_PIX_FMT_SGBRG10P: From a1f7efc8203862c03241a5fc6966ec38889a62c1 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 30 Apr 2025 02:36:31 +0000 Subject: [PATCH 3/7] drivers: video: fix the RGB565X format's byte per pixel THe RGB565X (big-endian) did not have an entry for the function video_byte_per_pixel(). Fix it by adding the missing entry. Signed-off-by: Josuah Demangeon --- include/zephyr/drivers/video.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 84f1bd264d28e..19ca64826e0e4 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -1411,6 +1411,7 @@ static inline unsigned int video_bits_per_pixel(uint32_t pixfmt) case VIDEO_PIX_FMT_Y14P: return 14; case VIDEO_PIX_FMT_RGB565: + case VIDEO_PIX_FMT_RGB565X: case VIDEO_PIX_FMT_YUYV: case VIDEO_PIX_FMT_YVYU: case VIDEO_PIX_FMT_UYVY: From af9cb063a124d764a73e2fdcc3d39bfb4024d4f1 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 4 Mar 2025 00:23:01 +0000 Subject: [PATCH 4/7] lib: pixel: Introduce a pixel and image processing library The "pixel" library aims facilitating the implementation of all image processing tasks, such as pixel conversion, with a focus on low-resource environments. The processing functions scale down to per-pixel "kernels" to line-based conversion to full streams of several frames. Signed-off-by: Josuah Demangeon --- cmake/linker_script/common/common-ram.cmake | 6 + include/zephyr/pixel/bayer.h | 91 ++++ include/zephyr/pixel/convert.h | 137 +++++++ include/zephyr/pixel/image.h | 203 +++++++++ include/zephyr/pixel/kernel.h | 131 ++++++ include/zephyr/pixel/operation.h | 219 ++++++++++ include/zephyr/pixel/print.h | 85 ++++ include/zephyr/pixel/resize.h | 57 +++ include/zephyr/pixel/stats.h | 139 +++++++ lib/CMakeLists.txt | 1 + lib/Kconfig | 3 + lib/pixel/CMakeLists.txt | 16 + lib/pixel/Kconfig | 52 +++ lib/pixel/bayer.c | 341 +++++++++++++++ lib/pixel/convert.c | 266 ++++++++++++ lib/pixel/image.c | 208 ++++++++++ lib/pixel/kernel.c | 433 ++++++++++++++++++++ lib/pixel/pixel.ld | 8 + lib/pixel/print.c | 344 ++++++++++++++++ lib/pixel/resize.c | 130 ++++++ lib/pixel/stats.c | 274 +++++++++++++ tests/lib/pixel/kernel/src/main.c | 133 ++++++ 22 files changed, 3277 insertions(+) create mode 100644 include/zephyr/pixel/bayer.h create mode 100644 include/zephyr/pixel/convert.h create mode 100644 include/zephyr/pixel/image.h create mode 100644 include/zephyr/pixel/kernel.h create mode 100644 include/zephyr/pixel/operation.h create mode 100644 include/zephyr/pixel/print.h create mode 100644 include/zephyr/pixel/resize.h create mode 100644 include/zephyr/pixel/stats.h create mode 100644 lib/pixel/CMakeLists.txt create mode 100644 lib/pixel/Kconfig create mode 100644 lib/pixel/bayer.c create mode 100644 lib/pixel/convert.c create mode 100644 lib/pixel/image.c create mode 100644 lib/pixel/kernel.c create mode 100644 lib/pixel/pixel.ld create mode 100644 lib/pixel/print.c create mode 100644 lib/pixel/resize.c create mode 100644 lib/pixel/stats.c create mode 100644 tests/lib/pixel/kernel/src/main.c diff --git a/cmake/linker_script/common/common-ram.cmake b/cmake/linker_script/common/common-ram.cmake index d46376edd03fb..65fcd8cbbba24 100644 --- a/cmake/linker_script/common/common-ram.cmake +++ b/cmake/linker_script/common/common-ram.cmake @@ -138,6 +138,12 @@ if(CONFIG_PCIE) zephyr_iterable_section(NAME pcie_dev GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) endif() +if(CONFIG_PIXEL) + zephyr_iterable_section(NAME pixel_convert GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) + zephyr_iterable_section(NAME pixel_resize GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) + zephyr_iterable_section(NAME pixel_kernel GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) +endif() + if(CONFIG_USB_DEVICE_STACK OR CONFIG_USB_DEVICE_STACK_NEXT) zephyr_iterable_section(NAME usbd_context GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) zephyr_iterable_section(NAME usbd_class_fs GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) diff --git a/include/zephyr/pixel/bayer.h b/include/zephyr/pixel/bayer.h new file mode 100644 index 0000000000000..50c88983237b3 --- /dev/null +++ b/include/zephyr/pixel/bayer.h @@ -0,0 +1,91 @@ +/* + * 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 + +#include + +/** + * @brief Define a new bayer format conversion operation. + * + * @param _fn Function performing the operation. + * @param _fourcc_in The input format for that operation. + * @param _window_size The number of line of context needed for that debayer operation. + */ +#define PIXEL_DEFINE_BAYER_OPERATION(_fn, _fourcc_in, _window_size) \ + static const STRUCT_SECTION_ITERABLE_ALTERNATE(pixel_convert, pixel_operation, \ + _fn##_op) = { \ + .name = #_fn, \ + .fourcc_in = _fourcc_in, \ + .fourcc_out = VIDEO_PIX_FMT_RGB24, \ + .window_size = _window_size, \ + .run = _fn, \ + } + +/** + * @brief Convert a line from RGGB8 to RGB24 with 3x3 method + * + * @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. + */ +void pixel_line_rggb8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, uint16_t width); +/** + * @brief Convert a line from GRBG8 to RGB24 with 3x3 method + * @copydetails pixel_line_rggb8_to_rgb24_3x3() + */ +void pixel_line_grbg8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, uint16_t width); +/** + * @brief Convert a line from BGGR8 to RGB24 with 3x3 method + * @copydetails pixel_line_rggb8_to_rgb24_3x3() + */ +void pixel_line_bggr8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, uint16_t width); +/** + * @brief Convert a line from GBRG8 to RGB24 with 3x3 method + * @copydetails pixel_line_rggb8_to_rgb24_3x3() + */ +void pixel_line_gbrg8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, uint16_t width); + +/** + * @brief Convert a line from RGGB8 to RGB24 with 2x2 method + * + * @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. + */ +void pixel_line_rggb8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + uint16_t width); +/** + * @brief Convert a line from GBRG8 to RGB24 with 2x2 method + * @copydetails pixel_line_rggb8_to_rgb24_2x2() + */ +void pixel_line_gbrg8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + uint16_t width); +/** + * @brief Convert a line from BGGR8 to RGB24 with 2x2 method + * @copydetails pixel_line_rggb8_to_rgb24_2x2() + */ +void pixel_line_bggr8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + uint16_t width); +/** + * @brief Convert a line from GRBG8 to RGB24 with 2x2 method + * @copydetails pixel_line_rggb8_to_rgb24_2x2() + */ +void pixel_line_grbg8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + uint16_t width); + +#endif /* ZEPHYR_INCLUDE_PIXEL_BAYER_H_ */ diff --git a/include/zephyr/pixel/convert.h b/include/zephyr/pixel/convert.h new file mode 100644 index 0000000000000..3023a9eee3bdf --- /dev/null +++ b/include/zephyr/pixel/convert.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_CONVERT_H_ +#define ZEPHYR_INCLUDE_PIXEL_CONVERT_H_ + +#include +#include + +#include +#include +#include + +/** + * @brief Define a new format conversion operation. + * + * @param _fn Function converting one input line. + * @param _fourcc_in The input format for that operation. + * @param _fourcc_out The Output format for that operation. + */ +#define PIXEL_DEFINE_CONVERT_OPERATION(_fn, _fourcc_in, _fourcc_out) \ + static const STRUCT_SECTION_ITERABLE_ALTERNATE(pixel_convert, pixel_operation, \ + _fn##_op) = { \ + .name = #_fn, \ + .fourcc_in = _fourcc_in, \ + .fourcc_out = _fourcc_out, \ + .window_size = 1, \ + .run = pixel_convert_op, \ + .arg = _fn, \ + } +/** + * @brief Helper to turn a format conversion function into an operation. + * + * The line conversion function is free to perform any processing on the input line. + * The @c w argument is the width of both the source and destination buffers. + * + * The line conversion function is to be provided in @c op->arg + * + * @param op Current operation in progress. + */ +void pixel_convert_op(struct pixel_operation *op); + +/** + * @brief Get the luminance (luma channel) of an RGB24 pixel. + * + * @param rgb24 Pointer to an RGB24 pixel: red, green, blue channels. + */ +uint8_t pixel_rgb24_get_luma_bt709(const uint8_t rgb24[3]); + +/** + * @brief Convert a line of pixel data from RGB24 to RGB24 (null conversion). + * + * See @ref video_pixel_formats for the definition of the input and output formats. + * + * You only need to call this function to work directly on raw buffers. + * See @ref pixel_image_convert for converting between formats. + * + * @param src Buffer of the input line in the format, @c XXX in @c pixel_line_XXX_to_YYY(). + * @param dst Buffer of the output line in the format, @c YYY in @c pixel_line_XXX_to_YYY(). + * @param width Width of the lines in number of pixels. + */ +void pixel_line_rgb24_to_rgb24(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB332 to RGB24. + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb332_to_rgb24(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB24 to RGB332 little-endian. + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb24_to_rgb332(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB565 little-endian to RGB24. + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb565le_to_rgb24(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB565 big-endian to RGB24. + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb565be_to_rgb24(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB24 to RGB565 little-endian. + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb24_to_rgb565le(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB24 to RGB565 big-endian. + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb24_to_rgb565be(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from YUYV to RGB24 (BT.709 coefficients). + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_yuyv_to_rgb24_bt709(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB24 to YUYV (BT.709 coefficients). + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb24_to_yuyv_bt709(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB24 to YUV24 (BT.709 coefficients). + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb24_to_yuv24_bt709(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from YUV24 to RGB24 (BT.709 coefficients). + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_yuv24_to_rgb24_bt709(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from YUYV to YUV24 + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_yuyv_to_yuv24(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from YUV24 to YUYV + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_yuv24_to_yuyv(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from Y8 to RGB24 + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_y8_to_rgb24_bt709(const uint8_t *src, uint8_t *dst, uint16_t width); +/** + * @brief Convert a line of pixel data from RGB24 to Y8 + * @copydetails pixel_line_rgb24_to_rgb24() + */ +void pixel_line_rgb24_to_y8_bt709(const uint8_t *src, uint8_t *dst, uint16_t width); + +#endif /* ZEPHYR_INCLUDE_PIXEL_CONVERT_H_ */ diff --git a/include/zephyr/pixel/image.h b/include/zephyr/pixel/image.h new file mode 100644 index 0000000000000..eefa8b301ef16 --- /dev/null +++ b/include/zephyr/pixel/image.h @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_IMAGE_H +#define ZEPHYR_INCLUDE_PIXEL_IMAGE_H + +#include +#include +#include + +/** + * @brief Represent the image currently being processed + */ +struct pixel_image { + sys_slist_t operations; + /** Current width of the image */ + uint16_t width; + /** Current height of the image */ + uint16_t height; + /** Current pixel format of the image as a four character code (fourcc) */ + uint32_t fourcc; + /** Input or output buffer used with the conversion */ + uint8_t *buffer; + /** Size of the input or output buffer */ + size_t size; + /** In case an error occurred, this is set to a matching error code */ + int err; +}; + +/** + * @brief Initialize an image from a memory buffer. + * + * @param img Image to initialize. + * @param buffer Memory containinig input image data to process. + * @param size Total available size in the buffer, can be bigger/smaller than full width x height. + * @param width Width of the complete image in pixels. + * @param height Height of the complete image in pixels. + * @param fourcc Format of data in the buffer as a four-character-code. + */ +void pixel_image_from_buffer(struct pixel_image *img, uint8_t *buffer, size_t size, + uint16_t width, uint16_t height, uint32_t fourcc); + +/** + * @brief Initialize an image from a video buffer. + * + * @param img Image to initialize. + * @param vbuf Video buffer that contains the image data to process. + * @param fmt Format of that video buffer. + */ +void pixel_image_from_vbuf(struct pixel_image *img, struct video_buffer *vbuf, + struct video_format *fmt); + +/** + * @brief Initialize an image from a memory buffer. + * + * @param img Image being processed. + * @param buffer Memory that receives the image data. + * @param size Size of the buffer. + * @return 0 on success + */ +int pixel_image_to_buffer(struct pixel_image *img, uint8_t *buffer, size_t size); + +/** + * @brief Initialize an image from a memory buffer. + * + * @param img Image being processed. + * @param vbuf Video buffer that receives the image data. + * @return 0 on success + */ +int pixel_image_to_vbuf(struct pixel_image *img, struct video_buffer *vbuf); + +/** + * @brief Convert an image to a new pixel format. + * + * An operation is added to convert the image to a new pixel format. + * If the operation to convert the image from the current format to a new format does not exist, + * then the error flag is set, which can be accessed as @c img->err. + * + * In some cases, converting between two formats requires an intermediate conversion to RGB24. + * + * @param img Image to convert. + * @param new_format A four-character-code (FOURCC) as defined by @c . + */ +int pixel_image_convert(struct pixel_image *img, uint32_t new_format); + +/** + * @brief Convert an image from a bayer array format to RGB24. + * + * An operation is added to convert the image to RGB24 using the specified window size, such + * as 2x2 or 3x3. + * + * @note It is also possible to use @ref pixel_image_convert to convert from bayer to RGB24 but + * this does not allow to select the window size. + * + * @param img Image to convert. + * @param window_size The window size for convnerting, usually 2 (faster) or 3 (higher quality). + */ +int pixel_image_debayer(struct pixel_image *img, uint32_t window_size); + +/** + * @brief Resize an image. + * + * An operation is added to change the image size. The aspect ratio is not preserved and the output + * image size is exactly the same as requested. + * + * @param img Image to convert. + * @param width The new width in pixels. + * @param height The new height in pixels. + */ +int pixel_image_resize(struct pixel_image *img, uint16_t width, uint16_t height); + +/** + * @brief Apply a kernel operation on an image. + * + * Kernel operations are working on small blocks of typically 3x3 or 5x5 pixels, repeated over the + * entire image to apply a desired effect on an image. + * + * @param img Image to convert. + * @param kernel_type The type of kernel to apply as defined in + * @param kernel_size The size of the kernel operaiton, usually 3 or 5. + */ +int pixel_image_kernel(struct pixel_image *img, uint32_t kernel_type, int kernel_size); + +/** + * @brief Print an image using higher quality TRUECOLOR terminal escape codes. + * + * @param img Image to print. + */ +void pixel_image_print_truecolor(struct pixel_image *img); + +/** + * @brief Print an image using higher speed 256COLOR terminal escape codes. + * + * @param img Image to print. + */ +void pixel_image_print_256color(struct pixel_image *img); + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Add a operation processing step to an image. + * + * @note This is a low-level function only needed to implement new operations. + * + * The operation step will not be processed immediately, but rather added to a linked list of + * operations that will be performed at runtime. + * + * @param img Image to which add a processing step. + * @param template Stream processing step to apply to the image. + * @param buffer_size Size of the input buffer to allocate for this operation. + * @param threshold Minimum number of bytes the operation needs to run one cycle. + * @return 0 on success + */ +int pixel_image_add_operation(struct pixel_image *img, const struct pixel_operation *template, + size_t buffer_size, size_t threshold); + +/** + * @brief Add a operation processing step to an image for uncompressed input data. + * + * @note This is a low-level function only needed to implement new operations. + * + * The operation step will not be processed immediately, but rather added to a linked list of + * operations that will be performed at runtime. + * + * Details such as buffer size or threshold value are deduced from the stream. + * + * @param img Image to which add a processing step. + * @param template Stream processing step to apply to the image. + */ +int pixel_image_add_uncompressed(struct pixel_image *img, const struct pixel_operation *template); + +/** + * @brief Perform all the processing added to the + * + * @note This is a low-level function only needed to implement new operations. + * + * This is where all the image processing happens. The processing steps are not executed while they + * are added to the pipeline, but only while this function is called. + * + * @param img Image to which one or multiple processing steps were added. + * @return 0 on success. + */ +int pixel_image_process(struct pixel_image *img); + +/** + * @brief Perform all the processing added to the + * + * @note This is a low-level function only needed to implement new operations. + * + * This is where all the image processing happens. The processing steps are not executed while they + * are added to the pipeline, but only while this function is called. + * + * @param img Image to which one or multiple processing steps were added. + * @return 0 on success. + */ +int pixel_image_error(struct pixel_image *img, int err); + +/** @endcond */ + +#endif /* ZEPHYR_INCLUDE_PIXEL_IMAGE_H */ diff --git a/include/zephyr/pixel/kernel.h b/include/zephyr/pixel/kernel.h new file mode 100644 index 0000000000000..481ae3803b489 --- /dev/null +++ b/include/zephyr/pixel/kernel.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_KERNEL_H_ +#define ZEPHYR_INCLUDE_PIXEL_KERNEL_H_ + +#include +#include + +#include + +/** + * @brief Define a new 5x5 kernel conversion operation. + * + * @param _fn Function converting 5 input lines into 1 output line. + * @param _type Kernel operation type + * @param _fourcc The input format for that operation. + */ +#define PIXEL_DEFINE_KERNEL_5X5_OPERATION(_fn, _type, _fourcc) \ + static const STRUCT_SECTION_ITERABLE_ALTERNATE(pixel_kernel, pixel_operation, \ + _fn##_op) = { \ + .name = #_fn, \ + .fourcc_in = _fourcc, \ + .fourcc_out = _fourcc, \ + .window_size = 5, \ + .run = pixel_kernel_5x5_op, \ + .arg = _fn, \ + .type = _type, \ + } + +/** + * @brief Define a new 3x3 kernel conversion operation. + * + * @param _fn Function converting 3 input lines into 1 output line. + * @param _type Kernel operation type + * @param _fourcc The input format for that operation. + */ +#define PIXEL_DEFINE_KERNEL_3X3_OPERATION(_fn, _type, _fourcc) \ + static const STRUCT_SECTION_ITERABLE_ALTERNATE(pixel_kernel, pixel_operation, \ + _fn##_op) = { \ + .name = #_fn, \ + .fourcc_in = _fourcc, \ + .fourcc_out = _fourcc, \ + .window_size = 3, \ + .run = pixel_kernel_3x3_op, \ + .arg = _fn, \ + .type = _type, \ + } + +/** + * @brief Helper to turn a 5x5 kernel conversion function into an operation. + * + * The line conversion function is free to perform any processing on the input lines and expected + * to produce one output line. + * + * The line conversion function is to be provided in @c op->arg. + * + * @param op Current operation in progress. + */ +void pixel_kernel_5x5_op(struct pixel_operation *op); + +/** + * @brief Helper to turn a 3x3 kernel conversion function into an operation. + * @copydetails pixel_kernel_5x5_op() + */ +void pixel_kernel_3x3_op(struct pixel_operation *op); + +/** + * Available kernel operations to apply to the image. + */ +enum pixel_kernel_type { + /** Identity kernel: no change, the input is the same as the output */ + PIXEL_KERNEL_IDENTITY, + /** Edge detection kernel: keep only an outline of the edges */ + PIXEL_KERNEL_EDGE_DETECT, + /** Gaussian blur kernel: apply a blur onto an image following a Gaussian curve */ + PIXEL_KERNEL_GAUSSIAN_BLUR, + /** Sharpen kernel: accentuate the edges, making the image look less blurry */ + PIXEL_KERNEL_SHARPEN, + /** Denoise kernel: remove the parasitic image noise using the local median value */ + PIXEL_KERNEL_DENOISE, +}; + +/** + * @brief Apply a 3x3 identity kernel to an RGB24 input window and produce one RGB24 line. + * + * @param in Array of input line buffers to convert. + * @param out Pointer to the output line converted. + * @param width Width of the input and output lines in pixels. + */ +void pixel_identity_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width); +/** + * @brief Apply a 5x5 identity kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_identity_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width); +/** + * @brief Apply a 3x3 sharpen kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_sharpen_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width); +/** + * @brief Apply a 5x5 unsharp kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_sharpen_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width); +/** + * @brief Apply a 3x3 edge detection kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_edgedetect_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width); +/** + * @brief Apply a 3x3 gaussian blur kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_gaussianblur_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width); +/** + * @brief Apply a 3x3 median denoise kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_median_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width); +/** + * @brief Apply a 5x5 median denoise kernel to an RGB24 input window and produce one RGB24 line. + * @copydetails pixel_identity_rgb24_3x3() + */ +void pixel_median_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width); + +#endif /* ZEPHYR_INCLUDE_PIXEL_KERNEL_H_ */ diff --git a/include/zephyr/pixel/operation.h b/include/zephyr/pixel/operation.h new file mode 100644 index 0000000000000..2d9ed11d43156 --- /dev/null +++ b/include/zephyr/pixel/operation.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_OPERATION_H +#define ZEPHYR_INCLUDE_PIXEL_OPERATION_H + +#include + +#include +#include +#include +#include +#include +#include + +/** + * @brief One step of a line operation pipeline + * + * @c pixel_operation structs are chained together into a linked list. + * Each step of the linked list contain a ring buffer for the input data, and a pointer to a + * conversion function processing it. + * Along with extra metadata, this is used to process data as a operation of lines. + */ +struct pixel_operation { + sys_snode_t node; + /** Name of the operation, useful for debugging the operation */ + const uint8_t *name; + /** Operation type in case there are several variants for an operation */ + uint32_t type; + /** Pixel input format as a four character code */ + uint32_t fourcc_in; + /** Pixel output format as a four character code */ + uint32_t fourcc_out; + /** Width of the image in pixels */ + uint16_t width; + /** Height of the image in pixels */ + uint16_t height; + /** Current position within the frame */ + uint16_t line_offset; + /** Number of lines of context around the line being converted */ + uint16_t window_size; + /** Number of bytes of input needed to call @c run() */ + uint16_t threshold; + /** Whether the ring buffer memory is allocated on the pixel heap or externally */ + bool is_heap; + /** Ring buffer that keeps track of the position in bytes */ + struct ring_buf ring; + /** Function that performs the I/O */ + void (*run)(struct pixel_operation *op); + /** Operation-specific data */ + void *arg; + /** Timestamp since the op started working in CPU cycles */ + uint32_t start_time; + /** Total time spent working in this op through the operation in CPU cycles */ + uint32_t total_time; +}; + +/** + * @brief Get a pointer to an output line from the next step of the operation. + * + * The buffer obtained can then be used to store the output of the conversion. + * + * The lines will be considered as converted as soon as @ref pixel_operation_done() is called, + * which will feed the line into the next step of the operation. + * + * @param op Current operation in progress. + * @return Pointer to the requested line buffer, never NULL. + */ +static inline uint8_t *pixel_operation_get_output_line(struct pixel_operation *op) +{ + struct pixel_operation *next = SYS_SLIST_PEEK_NEXT_CONTAINER(op, node); + uint32_t pitch; + uint8_t *lines; + uint32_t size; + + __ASSERT_NO_MSG(next != NULL); + + pitch = next->width * video_bits_per_pixel(next->fourcc_in) / BITS_PER_BYTE; + size = ring_buf_put_claim(&next->ring, &lines, pitch); + ring_buf_put_finish(&next->ring, size); + + __ASSERT(size == pitch, + "%s asked for %u output bytes, only have %u out of %u", + op->name, pitch, size, ring_buf_capacity_get(&next->ring)); + + return lines; +} + +/** + * @brief Get a pointer to a given number of input lines, and consume them from the operation. + * + * The lines are considered as processed, which will free them from the input ring buffer, and + * allow more data to flow in. + * + * @param op Current operation in progress. + * @param nb Number of lines to get in one block. + * @return Pointer to the requested number of lines, never NULL. + */ +static inline const uint8_t *pixel_operation_get_input_lines(struct pixel_operation *op, size_t nb) +{ + uint32_t pitch; + uint32_t size; + uint8_t *lines; + + op->line_offset += nb; + + __ASSERT(op->line_offset <= op->height, + "Trying to read at position %u beyond the height of the frame %u", + op->line_offset, op->height); + + pitch = op->width * video_bits_per_pixel(op->fourcc_in) / BITS_PER_BYTE; + size = ring_buf_get_claim(&op->ring, &lines, pitch * nb); + ring_buf_get_finish(&op->ring, size); + + __ASSERT(size == pitch * nb, + "%s asked for %u input bytes, only have %u out of %u", + op->name, pitch, size, ring_buf_capacity_get(&op->ring)); + + return lines; +} + +/** + * @brief Shorthand for @ref pixel_operation_get_input_lines() to get a single input line. + * + * @param op Current operation in progress. + * @return Pointer to the requested number of lines, never NULL. + */ +static inline const uint8_t *pixel_operation_get_input_line(struct pixel_operation *op) +{ + return pixel_operation_get_input_lines(op, 1); +} + +/** + * @brief Request a pointer to the next line of data without affecting the input sream. + * + * This permits to implement a lookahead operation when one or several lines of context is needed + * in addition to the line converted. + * + * @return The pointer to the input data. + */ +static inline uint8_t *pixel_operation_peek_input_line(struct pixel_operation *op) +{ + uint32_t pitch = op->width * video_bits_per_pixel(op->fourcc_in) / BITS_PER_BYTE; + uint8_t *line; + uint32_t size = ring_buf_get_claim(&op->ring, &line, pitch); + + __ASSERT_NO_MSG(size == pitch); + + return line; +} + +/** + * @brief Request a pointer to the entire input buffer content, consumed from the input operation. + * + * @param op Current operation in progress. + * @return The pointer to the input data. + */ +static inline const uint8_t *pixel_operation_get_all_input(struct pixel_operation *op) +{ + uint8_t *remaining; + uint32_t size; + + __ASSERT_NO_MSG(op != NULL); + + op->line_offset = op->height; + + __ASSERT_NO_MSG(op->line_offset <= op->height); + + size = ring_buf_get_claim(&op->ring, &remaining, ring_buf_capacity_get(&op->ring)); + ring_buf_get_finish(&op->ring, size); + + __ASSERT(size == ring_buf_capacity_get(&op->ring), + "Could not dequeue the entire input buffer of %s, %u used, %u free", op->name, + ring_buf_size_get(&op->ring), ring_buf_space_get(&op->ring)); + + return remaining; +} + +static inline void pixel_operation_run(struct pixel_operation *op) +{ + if (op != NULL && op->run != NULL) { + /* Start the counter of the next operation */ + op->start_time = k_cycle_get_32(); + + while (ring_buf_size_get(&op->ring) >= op->threshold && + op->line_offset < op->height) { + op->run(op); + } + } +} + +/** + * @brief Mark the line obtained with @ref pixel_operation_get_output_line as converted. + * + * This will let the next step of the operation know that a new line was converted. + * This allows the pipeline to trigger the next step if there is enough data submitted to it. + * + * @param op Current operation in progress. + */ +static inline void pixel_operation_done(struct pixel_operation *op) +{ + /* Ignore any "peek" operation done previouslyl */ + ring_buf_get_finish(&op->ring, 0); + ring_buf_put_finish(&op->ring, 0); + + /* Flush the timestamp to the counter */ + op->total_time += (op->start_time == 0) ? (0) : (k_cycle_get_32() - op->start_time); + + /* Run the next operation in the chain now that more data is available */ + pixel_operation_run(SYS_SLIST_PEEK_NEXT_CONTAINER(op, node)); + + /* Resuming to this operation, reset the time counter */ + op->start_time = k_cycle_get_32(); +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_OPERATION_H */ diff --git a/include/zephyr/pixel/print.h b/include/zephyr/pixel/print.h new file mode 100644 index 0000000000000..a5f36a5c7b82e --- /dev/null +++ b/include/zephyr/pixel/print.h @@ -0,0 +1,85 @@ +/* + * 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 + +#include + +/** + * @brief Print a buffer using higher quality TRUECOLOR terminal escape codes. + * + * @param buf Imagme buffer to display in the terminal. + * @param size Size of the buffer in bytes. + * @param width Number of pixel of the input buffer in width + * @param height Max number of rows to print + * @param fourcc Format of the buffer to print + */ +void pixel_print_buffer_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height, uint32_t fourcc); +/** + * @brief Print a buffer using higher speed 256COLOR terminal escape codes. + * @copydetails pixel_print_buffer_truecolor() + */ +void pixel_print_buffer_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height, uint32_t fourcc); + +/** + * @brief Hexdump a buffer in the RAW8 format + * + * @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 + */ +void pixel_hexdump_raw8(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/** + * @brief Hexdump a buffer in the RGB24 format + * @copydetails pixel_hexdump_raw8() + */ +void pixel_hexdump_rgb24(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/** + * @brief Hexdump a buffer in the RGB565 format + * @copydetails pixel_hexdump_raw8() + */ +void pixel_hexdump_rgb565(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/** + * @brief Hexdump a buffer in the YUYV format + * @copydetails pixel_hexdump_raw8() + */ +void pixel_hexdump_yuyv(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); + +/** + * @brief Printing RGB histograms to the terminal. + * + * @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); + +/** + * @brief Printing Y histograms to the terminal. + * + * @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); + +/** + * @brief Set the shell instance to use when printing via the shell back-end. + * + * @see CONFIG_PIXEL_PRINT + * + * @param sh Shell instance set as a global variable. + */ +void pixel_print_set_shell(struct shell *sh); + +#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 0000000000000..7a7c827f2fb66 --- /dev/null +++ b/include/zephyr/pixel/resize.h @@ -0,0 +1,57 @@ +/* + * 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 + +#include + +/** + * @brief Define a format conversion operation. + * + * Invoking this macro suffices for @ref pixel_image_convert() to include the extra format. + * + * @param _fn Function converting one input line. + * @param _fourcc The pixel format of the data resized. + */ +#define PIXEL_DEFINE_RESIZE_OPERATION(_fn, _fourcc) \ + static const STRUCT_SECTION_ITERABLE_ALTERNATE(pixel_resize, pixel_operation, \ + pixel_resize_op##_fourcc) = { \ + .name = #_fn, \ + .fourcc_in = _fourcc, \ + .fourcc_out = _fourcc, \ + .window_size = 1, \ + .run = _fn, \ + } + +/** + * @brief Resize an 24-bit per pixel frame by subsampling the pixels horizontally/vertically. + * + * @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 output in number of pixels. + * @param dst_height Height of the output in number of pixels. + */ +void pixel_resize_frame_raw24(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); +/** + * @brief Resize an 16-bit per pixel frame by subsampling the pixels horizontally/vertically. + * @copydetails pixel_resize_frame_raw24() + */ +void pixel_resize_frame_raw16(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); +/** + * @brief Resize an 8-bit per pixel frame by subsampling the pixels horizontally/vertically. + * @copydetails pixel_resize_frame_raw24() + */ +void pixel_resize_frame_raw8(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 0000000000000..6d6b0e854cfd5 --- /dev/null +++ b/include/zephyr/pixel/stats.h @@ -0,0 +1,139 @@ +/* + * 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, green, 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, green, blue channel averages of all pixels in an RGGB8 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. + */ +void pixel_rggb8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** + * @brief Collect red, green, blue channel averages of all pixels in an BGGR8 frame. + * @copydetails pixel_rggb8frame_to_rgb24avg() + */ +void pixel_bggr8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** + * @brief Collect red, green, blue channel averages of all pixels in an GBRG8 frame. + * @copydetails pixel_rggb8frame_to_rgb24avg() + */ +void pixel_gbrg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** + * @brief Collect red, green, blue channel averages of all pixels in an GRBG8 frame. + * @copydetails pixel_rggb8frame_to_rgb24avg() + */ +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, green, 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, green, blue channels of an RGGB8 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. + */ +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); +/** + * @brief Collect an histogram for each of the red, green, blue channels of GBRG8 frame. + * @copydetails pixel_rggb8frame_to_rgb24hist() + */ +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); +/** + * @brief Collect an histogram for each of the red, green, blue channels of BGGR8 frame. + * @copydetails pixel_rggb8frame_to_rgb24hist() + */ +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); +/** + * @brief Collect an histogram for each of the red, green, blue channels of GRBG8 frame. + * @copydetails pixel_rggb8frame_to_rgb24hist() + */ +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 values of an RGGB8 frame. + * + * @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. + */ +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); +/** + * @brief Collect an histogram for the Y channel, obtained from the values of an GBRG8 frame. + * @copydetails pixel_rggb8frame_to_y8hist() + */ +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); +/** + * @brief Collect an histogram for the Y channel, obtained from the values of an BGGR8 frame. + * @copydetails pixel_rggb8frame_to_y8hist() + */ +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); +/** + * @brief Collect an histogram for the Y channel, obtained from the values of an GRBG8 frame. + * @copydetails pixel_rggb8frame_to_y8hist() + */ +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/lib/CMakeLists.txt b/lib/CMakeLists.txt index b945968c8fbe7..d8de2732c0f5d 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 ae97399b19953..bf5ae2371ee6d 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 0000000000000..3ef8f28fef660 --- /dev/null +++ b/lib/pixel/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +# zephyr-keep-sorted-start +zephyr_library_sources(bayer.c) +zephyr_library_sources(convert.c) +zephyr_library_sources(image.c) +zephyr_library_sources(kernel.c) +zephyr_library_sources(print.c) +zephyr_library_sources(resize.c) +zephyr_library_sources(stats.c) +# zephyr-keep-sorted-stop + +zephyr_linker_sources(DATA_SECTIONS pixel.ld) diff --git a/lib/pixel/Kconfig b/lib/pixel/Kconfig new file mode 100644 index 0000000000000..cc811427bf843 --- /dev/null +++ b/lib/pixel/Kconfig @@ -0,0 +1,52 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +menuconfig PIXEL + bool "Pixel and Image Manipulation Library" + imply RING_BUFFER_LARGE + +if PIXEL + +config PIXEL_HEAP_SIZE + int "Size in bytes for use in pixel operations intermediate buffers" + default 4096 + help + Every time an operation is performed on an image, intermediate buffers matching the + line width are allocated. This configures how much memory to use for it. + The default is enough for a few conversion for small frames. + +choice PIXEL_PRINT + bool "Select the print function to use for sending characters out." + default PIXEL_PRINT_PRINTF + help + The default is to use printf() as it most often leads to the output being printed out. + +config PIXEL_PRINT_PRINTF + bool "Image output to print is sent to printf()" + help + The data is directly handled by the libc bypassing most configuration. + +config PIXEL_PRINT_PRINTK + bool "Image output to print is sent to printk()" + help + The printk() function will sometimes drop characters to avoid slowing-down the rest of + the firmware, and can be configured to use the logging subsystem. + +config PIXEL_PRINT_SHELL + bool "Image output to print is sent to shell_print()" + help + The shell instance used is set by pixel_print_set_shell(). + +config PIXEL_PRINT_NONE + bool "Image output is dropped and not sent anywhere" + help + This is useful for environments where UTF-8 or escape characters can be a problem, + or where a human is not watching, like CI, or quickly toggle on/off this feature. + +endchoice + +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 0000000000000..6f2f9c88863d2 --- /dev/null +++ b/lib/pixel/bayer.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_bayer, CONFIG_PIXEL_LOG_LEVEL); + +int pixel_image_debayer(struct pixel_image *img, uint32_t window_size) +{ + const struct pixel_operation *op = NULL; + + STRUCT_SECTION_FOREACH_ALTERNATE(pixel_convert, pixel_operation, tmp) { + if (tmp->fourcc_in == img->fourcc && tmp->fourcc_out == VIDEO_PIX_FMT_RGB24 && + tmp->window_size == window_size) { + op = tmp; + break; + } + } + + if (op == NULL) { + LOG_ERR("Conversion operation from %s to %s using %ux%u window not found", + VIDEO_FOURCC_TO_STR(img->fourcc), VIDEO_FOURCC_TO_STR(VIDEO_PIX_FMT_RGB24), + window_size, window_size); + return pixel_image_error(img, -ENOSYS); + } + + return pixel_image_add_uncompressed(img, op); +} + +#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]}, \ + } + +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]; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +__weak void pixel_line_rggb8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, + const uint8_t *i2, uint8_t *o0, uint16_t w) +{ + __ASSERT_NO_MSG(w >= 4 && w % 2 == 0); + uint8_t il[3][3] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][3] = FOLD_R_3X3(i0, i1, i2, w); + + pixel_grbg8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= w; 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[w * 3 - 3]); +} + +__weak void pixel_line_grbg8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, + const uint8_t *i2, uint8_t *o0, uint16_t w) +{ + __ASSERT_NO_MSG(w >= 4 && w % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, w); + + pixel_rggb8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= w; 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[w * 3 - 3]); +} + +__weak void pixel_line_bggr8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, + const uint8_t *i2, uint8_t *o0, uint16_t w) +{ + __ASSERT_NO_MSG(w >= 4 && w % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, w); + + pixel_gbrg8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= w; 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[w * 3 - 3]); +} + +__weak void pixel_line_gbrg8_to_rgb24_3x3(const uint8_t *i0, const uint8_t *i1, + const uint8_t *i2, uint8_t *o0, uint16_t w) +{ + __ASSERT_NO_MSG(w >= 4 && w % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, w); + + pixel_bggr8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= w; 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[w * 3 - 3]); +} + +__weak void pixel_line_rggb8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, + uint16_t w) +{ + __ASSERT_NO_MSG(w >= 2 && w % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= w; 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[w - 1], i0[w - 2], i1[w - 1], i1[w - 2], &o0[w * 3 - 6]); + pixel_grbg8_to_rgb24_2x2(i0[w - 2], i0[w - 1], i1[w - 2], i1[w - 1], &o0[w * 3 - 3]); +} + +__weak void pixel_line_gbrg8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, + uint16_t w) +{ + __ASSERT_NO_MSG(w >= 2 && w % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= w; 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[w - 1], i0[w - 2], i1[w - 1], i1[w - 2], &o0[w * 3 - 6]); + pixel_bggr8_to_rgb24_2x2(i0[w - 2], i0[w - 1], i1[w - 2], i1[w - 1], &o0[w * 3 - 3]); +} + +__weak void pixel_line_bggr8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, + uint16_t w) +{ + __ASSERT_NO_MSG(w >= 2 && w % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= w; 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[w - 1], i0[w - 2], i1[w - 1], i1[w - 2], &o0[w * 3 - 6]); + pixel_gbrg8_to_rgb24_2x2(i0[w - 2], i0[w - 1], i1[w - 2], i1[w - 1], &o0[w * 3 - 3]); +} + +__weak void pixel_line_grbg8_to_rgb24_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, + uint16_t w) +{ + __ASSERT_NO_MSG(w >= 2 && w % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= w; 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[w - 1], i0[w - 2], i1[w - 1], i1[w - 2], &o0[w * 3 - 6]); + pixel_rggb8_to_rgb24_2x2(i0[w - 2], i0[w - 1], i1[w - 2], i1[w - 1], &o0[w * 3 - 3]); +} + +typedef void fn_3x3_t(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, uint8_t *o0, + uint16_t width); + +typedef void fn_2x2_t(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, uint16_t width); + +static inline void pixel_op_bayer_to_rgb24_3x3(struct pixel_operation *op, fn_3x3_t *fn0, + fn_3x3_t *fn1) +{ + uint16_t prev_line_offset = op->line_offset; + const uint8_t *i0 = pixel_operation_get_input_line(op); + const uint8_t *i1 = pixel_operation_peek_input_line(op); + const uint8_t *i2 = pixel_operation_peek_input_line(op); + + if (prev_line_offset == 0) { + fn1(i1, i0, i1, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } + + if (prev_line_offset % 2 == 0) { + fn0(i0, i1, i2, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } else { + fn1(i0, i1, i2, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } + + if (op->line_offset + 2 == op->height) { + fn0(i1, i2, i1, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + /* Skip the two lines of lookahead context, now that the conversion is complete */ + pixel_operation_get_input_line(op); + pixel_operation_get_input_line(op); + } +} + +static inline void pixel_op_bayer_to_rgb24_2x2(struct pixel_operation *op, fn_2x2_t *fn0, + fn_2x2_t *fn1) +{ + uint16_t prev_line_offset = op->line_offset; + const uint8_t *i0 = pixel_operation_get_input_line(op); + const uint8_t *i1 = pixel_operation_peek_input_line(op); + + if (prev_line_offset % 2 == 0) { + fn0(i0, i1, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } else { + fn1(i0, i1, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } + + if (op->line_offset + 1 == op->height) { + fn0(i0, i1, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + /* Skip the two lines of lookahead context, now that the conversion is complete */ + pixel_operation_get_input_line(op); + } +} + +static void pixel_op_rggb8_to_rgb24_3x3(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_3x3(op, &pixel_line_rggb8_to_rgb24_3x3, + &pixel_line_gbrg8_to_rgb24_3x3); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_rggb8_to_rgb24_3x3, VIDEO_PIX_FMT_RGGB8, 3); + +static void pixel_op_gbrg8_to_rgb24_3x3(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_3x3(op, &pixel_line_gbrg8_to_rgb24_3x3, + &pixel_line_rggb8_to_rgb24_3x3); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_gbrg8_to_rgb24_3x3, VIDEO_PIX_FMT_GBRG8, 3); + +static void pixel_op_bggr8_to_rgb24_3x3(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_3x3(op, &pixel_line_bggr8_to_rgb24_3x3, + &pixel_line_grbg8_to_rgb24_3x3); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_bggr8_to_rgb24_3x3, VIDEO_PIX_FMT_BGGR8, 3); + +static void pixel_op_grbg8_to_rgb24_3x3(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_3x3(op, &pixel_line_grbg8_to_rgb24_3x3, + &pixel_line_bggr8_to_rgb24_3x3); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_grbg8_to_rgb24_3x3, VIDEO_PIX_FMT_GRBG8, 3); + +static void pixel_op_rggb8_to_rgb24_2x2(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_2x2(op, &pixel_line_rggb8_to_rgb24_2x2, + &pixel_line_gbrg8_to_rgb24_2x2); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_rggb8_to_rgb24_2x2, VIDEO_PIX_FMT_RGGB8, 2); + +static void pixel_op_gbrg8_to_rgb24_2x2(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_2x2(op, &pixel_line_gbrg8_to_rgb24_2x2, + &pixel_line_rggb8_to_rgb24_2x2); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_gbrg8_to_rgb24_2x2, VIDEO_PIX_FMT_GBRG8, 2); + +static void pixel_op_bggr8_to_rgb24_2x2(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_2x2(op, &pixel_line_bggr8_to_rgb24_2x2, + &pixel_line_grbg8_to_rgb24_2x2); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_bggr8_to_rgb24_2x2, VIDEO_PIX_FMT_BGGR8, 2); + +static void pixel_op_grbg8_to_rgb24_2x2(struct pixel_operation *op) +{ + pixel_op_bayer_to_rgb24_2x2(op, &pixel_line_grbg8_to_rgb24_2x2, + pixel_line_bggr8_to_rgb24_2x2); +} +PIXEL_DEFINE_BAYER_OPERATION(pixel_op_grbg8_to_rgb24_2x2, VIDEO_PIX_FMT_GRBG8, 2); diff --git a/lib/pixel/convert.c b/lib/pixel/convert.c new file mode 100644 index 0000000000000..381ead7481464 --- /dev/null +++ b/lib/pixel/convert.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +LOG_MODULE_REGISTER(pixel_convert, CONFIG_PIXEL_LOG_LEVEL); + +int pixel_image_convert(struct pixel_image *img, uint32_t new_format) +{ + const struct pixel_operation *op = NULL; + + STRUCT_SECTION_FOREACH_ALTERNATE(pixel_convert, pixel_operation, tmp) { + if (tmp->fourcc_in == img->fourcc && tmp->fourcc_out == new_format) { + op = tmp; + break; + } + } + + if (op == NULL) { + LOG_ERR("Conversion operation from %s to %s not found", + VIDEO_FOURCC_TO_STR(img->fourcc), VIDEO_FOURCC_TO_STR(new_format)); + return pixel_image_error(img, -ENOSYS); + } + + return pixel_image_add_uncompressed(img, op); +} + +void pixel_convert_op(struct pixel_operation *op) +{ + const uint8_t *line_in = pixel_operation_get_input_line(op); + uint8_t *line_out = pixel_operation_get_output_line(op); + void (*convert)(const uint8_t *rgb24i, uint8_t *rgb24o, uint16_t width) = op->arg; + + __ASSERT_NO_MSG(convert != NULL); + + convert(line_in, line_out, op->width); + pixel_operation_done(op); +} + +__weak void pixel_line_rgb24_to_rgb24(const uint8_t *rgb24i, uint8_t *rgb24o, uint16_t width) +{ + memcpy(rgb24o, rgb24i, width * 3); +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_rgb24, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_RGB24); + +__weak void pixel_line_rgb24_to_rgb332(const uint8_t *rgb24, uint8_t *rgb332, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 3, o += 1) { + rgb332[o] = 0; + rgb332[o] |= (uint16_t)rgb24[i + 0] >> 5 << (0 + 3 + 2); + rgb332[o] |= (uint16_t)rgb24[i + 1] >> 5 << (0 + 0 + 2); + rgb332[o] |= (uint16_t)rgb24[i + 2] >> 6 << (0 + 0 + 0); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_rgb332, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_RGB332); + +__weak void pixel_line_rgb332_to_rgb24(const uint8_t *rgb332, uint8_t *rgb24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 1, o += 3) { + rgb24[o + 0] = rgb332[i] >> (0 + 3 + 2) << 5; + rgb24[o + 1] = rgb332[i] >> (0 + 0 + 2) << 5; + rgb24[o + 2] = rgb332[i] >> (0 + 0 + 0) << 6; + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb332_to_rgb24, + VIDEO_PIX_FMT_RGB332, VIDEO_PIX_FMT_RGB24); + +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; +} + +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; +} + +__weak void pixel_line_rgb24_to_rgb565be(const uint8_t *rgb24, uint8_t *rgb565be, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 3, o += 2) { + *(uint16_t *)&rgb565be[o] = sys_cpu_to_be16(pixel_rgb24_to_rgb565(&rgb24[i])); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_rgb565be, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_RGB565X); + +__weak void pixel_line_rgb24_to_rgb565le(const uint8_t *rgb24, uint8_t *rgb565le, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 3, o += 2) { + *(uint16_t *)&rgb565le[o] = sys_cpu_to_le16(pixel_rgb24_to_rgb565(&rgb24[i])); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_rgb565le, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_RGB565); + +__weak void pixel_line_rgb565be_to_rgb24(const uint8_t *rgb565be, uint8_t *rgb24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 2, o += 3) { + pixel_rgb565_to_rgb24(sys_be16_to_cpu(*(uint16_t *)&rgb565be[i]), &rgb24[o]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb565be_to_rgb24, + VIDEO_PIX_FMT_RGB565X, VIDEO_PIX_FMT_RGB24); + +__weak void pixel_line_rgb565le_to_rgb24(const uint8_t *rgb565le, uint8_t *rgb24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 2, o += 3) { + pixel_rgb565_to_rgb24(sys_le16_to_cpu(*(uint16_t *)&rgb565le[i]), &rgb24[o]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb565le_to_rgb24, + VIDEO_PIX_FMT_RGB565, VIDEO_PIX_FMT_RGB24); + +#define Q21(val) ((int32_t)((val) * (1 << 21))) + +static inline uint8_t pixel_rgb24_to_y8_bt709(const uint8_t rgb24[3]) +{ + int16_t r = rgb24[0], g = rgb24[1], b = rgb24[2]; + + return CLAMP(((Q21(+0.1826) * r + Q21(+0.6142) * g + Q21(+0.0620) * b) >> 21) + 16, + 0x00, 0xff); +} + +uint8_t pixel_rgb24_get_luma_bt709(const uint8_t rgb24[3]) +{ + return pixel_rgb24_to_y8_bt709(rgb24); +} + +static inline uint8_t pixel_rgb24_to_u8_bt709(const uint8_t rgb24[3]) +{ + int16_t r = rgb24[0], g = rgb24[1], b = rgb24[2]; + + return CLAMP(((Q21(-0.1006) * r + Q21(-0.3386) * g + Q21(+0.4392) * b) >> 21) + 128, + 0x00, 0xff); +} + +static inline uint8_t pixel_rgb24_to_v8_bt709(const uint8_t rgb24[3]) +{ + int16_t r = rgb24[0], g = rgb24[1], b = rgb24[2]; + + return CLAMP(((Q21(+0.4392) * r + Q21(-0.3989) * g + Q21(-0.0403) * b) >> 21) + 128, + 0x00, 0xff); +} + +static inline void pixel_yuv24_to_rgb24_bt709(const uint8_t y, uint8_t u, uint8_t v, + uint8_t rgb24[3]) +{ + int32_t yy = (int32_t)y - 16, uu = (int32_t)u - 128, vv = (int32_t)v - 128; + + /* Y range [16:235], U/V range [16:240], RGB range[0:255] (full range) */ + rgb24[0] = CLAMP((Q21(+1.1644) * yy + Q21(+0.0000) * uu + Q21(+1.7928) * vv) >> 21, + 0x00, 0xff); + rgb24[1] = CLAMP((Q21(+1.1644) * yy + Q21(-0.2133) * uu + Q21(-0.5330) * vv) >> 21, + 0x00, 0xff); + rgb24[2] = CLAMP((Q21(+1.1644) * yy + Q21(+2.1124) * uu + Q21(+0.0000) * vv) >> 21, + 0x00, 0xff); +} + +#undef Q21 + +__weak void pixel_line_yuv24_to_rgb24_bt709(const uint8_t *yuv24, uint8_t *rgb24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 3, o += 3) { + pixel_yuv24_to_rgb24_bt709(yuv24[i + 0], yuv24[i + 1], yuv24[i + 2], &rgb24[o]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_yuv24_to_rgb24_bt709, + VIDEO_PIX_FMT_YUV24, VIDEO_PIX_FMT_RGB24); + +void pixel_line_rgb24_to_yuv24_bt709(const uint8_t *rgb24, uint8_t *yuv24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 3, o += 3) { + yuv24[o + 0] = pixel_rgb24_to_y8_bt709(&rgb24[i]); + yuv24[o + 1] = pixel_rgb24_to_u8_bt709(&rgb24[i]); + yuv24[o + 2] = pixel_rgb24_to_v8_bt709(&rgb24[i]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_yuv24_bt709, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_YUV24); + +__weak void pixel_line_yuv24_to_yuyv(const uint8_t *yuv24, uint8_t *yuyv, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w + 2 <= width; w += 2, i += 6, o += 4) { + /* Pixel 0 */ + yuyv[o + 0] = yuv24[i + 0]; + yuyv[o + 1] = yuv24[i + 1]; + /* Pixel 1 */ + yuyv[o + 2] = yuv24[i + 3]; + yuyv[o + 3] = yuv24[i + 5]; + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_yuv24_to_yuyv, + VIDEO_PIX_FMT_YUV24, VIDEO_PIX_FMT_YUYV); + +__weak void pixel_line_yuyv_to_yuv24(const uint8_t *yuyv, uint8_t *yuv24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w + 2 <= width; w += 2, i += 4, o += 6) { + /* Pixel 0 */ + yuv24[o + 0] = yuyv[i + 0]; + yuv24[o + 1] = yuyv[i + 1]; + yuv24[o + 2] = yuyv[i + 3]; + /* Pixel 1 */ + yuv24[o + 3] = yuyv[i + 2]; + yuv24[o + 4] = yuyv[i + 1]; + yuv24[o + 5] = yuyv[i + 3]; + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_yuyv_to_yuv24, + VIDEO_PIX_FMT_YUYV, VIDEO_PIX_FMT_YUV24); + +__weak void pixel_line_rgb24_to_yuyv_bt709(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w + 2 <= width; w += 2, i += 6, o += 4) { + /* Pixel 0 */ + yuyv[o + 0] = pixel_rgb24_to_y8_bt709(&rgb24[i + 0]); + yuyv[o + 1] = pixel_rgb24_to_u8_bt709(&rgb24[i + 0]); + /* Pixel 1 */ + yuyv[o + 2] = pixel_rgb24_to_y8_bt709(&rgb24[i + 3]); + yuyv[o + 3] = pixel_rgb24_to_v8_bt709(&rgb24[i + 3]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_yuyv_bt709, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_YUYV); + +__weak void pixel_line_yuyv_to_rgb24_bt709(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w + 2 <= width; w += 2, i += 4, o += 6) { + /* Pixel 0 */ + pixel_yuv24_to_rgb24_bt709(yuyv[i + 0], yuyv[i + 1], yuyv[i + 3], &rgb24[o + 0]); + /* Pixel 1 */ + pixel_yuv24_to_rgb24_bt709(yuyv[i + 2], yuyv[i + 1], yuyv[i + 3], &rgb24[o + 3]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_yuyv_to_rgb24_bt709, + VIDEO_PIX_FMT_YUYV, VIDEO_PIX_FMT_RGB24); + +__weak void pixel_line_y8_to_rgb24_bt709(const uint8_t *y8, uint8_t *rgb24, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 1, o += 3) { + pixel_yuv24_to_rgb24_bt709(y8[i], UINT8_MAX / 2, UINT8_MAX / 2, &rgb24[o]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_y8_to_rgb24_bt709, + VIDEO_PIX_FMT_GREY, VIDEO_PIX_FMT_RGB24); + +__weak void pixel_line_rgb24_to_y8_bt709(const uint8_t *rgb24, uint8_t *y8, uint16_t width) +{ + for (size_t i = 0, o = 0, w = 0; w < width; w++, i += 3, o += 1) { + y8[o] = pixel_rgb24_to_y8_bt709(&rgb24[i]); + } +} +PIXEL_DEFINE_CONVERT_OPERATION(pixel_line_rgb24_to_y8_bt709, + VIDEO_PIX_FMT_RGB24, VIDEO_PIX_FMT_GREY); diff --git a/lib/pixel/image.c b/lib/pixel/image.c new file mode 100644 index 0000000000000..dcc2d21fab7e5 --- /dev/null +++ b/lib/pixel/image.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +LOG_MODULE_REGISTER(pixel_image, CONFIG_PIXEL_LOG_LEVEL); + +K_HEAP_DEFINE(pixel_heap, CONFIG_PIXEL_HEAP_SIZE); + +static void pixel_image_free(struct pixel_image *img) +{ + struct pixel_operation *op, *tmp; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&img->operations, op, tmp, node) { + if (op->is_heap) { + k_heap_free(&pixel_heap, op->ring.buffer); + } + memset(op, 0x00, sizeof(*op)); + k_heap_free(&pixel_heap, op); + } + memset(&img->operations, 0x00, sizeof(img->operations)); +} + +int pixel_image_error(struct pixel_image *img, int err) +{ + if (err != 0 && img->err == 0) { + pixel_image_free(img); + img->err = err; + } + return err; +} + +static void *pixel_image_alloc(struct pixel_image *img, size_t size) +{ + void *mem; + + mem = k_heap_alloc(&pixel_heap, size, K_NO_WAIT); + if (mem == NULL) { + LOG_ERR("Out of memory whle allocating %zu bytes", size); + } + return mem; +} + +int pixel_image_add_operation(struct pixel_image *img, const struct pixel_operation *template, + size_t buffer_size, size_t threshold) +{ + struct pixel_operation *op; + + if (img->err) { + return -ECANCELED; + } + + if (template->fourcc_in != img->fourcc) { + LOG_ERR("Wrong format for this operation: image has %s, operation uses %s", + VIDEO_FOURCC_TO_STR(template->fourcc_in), VIDEO_FOURCC_TO_STR(img->fourcc)); + return pixel_image_error(img, -EINVAL); + } + + op = pixel_image_alloc(img, sizeof(*op)); + if (op == NULL) { + return pixel_image_error(img, -ENOMEM); + } + + memcpy(op, template, sizeof(*op)); + op->threshold = threshold; + op->width = img->width; + op->height = img->height; + op->ring.buffer = NULL; /* allocated later */ + op->ring.size = buffer_size; + + img->fourcc = op->fourcc_out; + + sys_slist_append(&img->operations, &op->node); + + return 0; +} + +int pixel_image_add_uncompressed(struct pixel_image *img, const struct pixel_operation *template) +{ + size_t bpp = video_bits_per_pixel(img->fourcc); + size_t size = template->window_size * img->width * bpp / BITS_PER_BYTE; + + __ASSERT(bpp > 0, "Unknown image pitch for format %s.", VIDEO_FOURCC_TO_STR(img->fourcc)); + + return pixel_image_add_operation(img, template, size, size); +} + +int pixel_image_process(struct pixel_image *img) +{ + struct pixel_operation *op; + uint8_t *p; + + if (img->err) { + return -ECANCELED; + } + + if (img->buffer == NULL) { + LOG_ERR("No input buffer configured"); + return pixel_image_error(img, -ENOBUFS); + } + + op = SYS_SLIST_PEEK_HEAD_CONTAINER(&img->operations, op, node); + if (op == NULL) { + LOG_ERR("No operation to perform on image"); + return pixel_image_error(img, -ENOSYS); + } + + if (ring_buf_capacity_get(&op->ring) < op->ring.size) { + LOG_ERR("Not enough space (%u) in input buffer to run the first operation (%u)", + ring_buf_capacity_get(&op->ring), op->ring.size); + return pixel_image_error(img, -ENOSPC); + } + + ring_buf_init(&op->ring, img->size, img->buffer); + ring_buf_put_claim(&op->ring, &p, img->size); + ring_buf_put_finish(&op->ring, img->size); + + while ((op = SYS_SLIST_PEEK_NEXT_CONTAINER(op, node)) != NULL) { + if (op->ring.buffer == NULL) { + op->ring.buffer = pixel_image_alloc(img, op->ring.size); + if (op->ring.buffer == NULL) { + return pixel_image_error(img, -ENOMEM); + } + op->is_heap = true; + } + } + + SYS_SLIST_FOR_EACH_CONTAINER(&img->operations, op, node) { + LOG_DBG("- %s %ux%u to %s, %s, threshold %u", + VIDEO_FOURCC_TO_STR(op->fourcc_in), op->width, op->height, + VIDEO_FOURCC_TO_STR(op->fourcc_out), op->name, op->threshold); + } + + op = SYS_SLIST_PEEK_HEAD_CONTAINER(&img->operations, op, node); + + pixel_operation_run(op); + pixel_image_free(img); + + return 0; +} + +void pixel_image_from_buffer(struct pixel_image *img, uint8_t *buffer, size_t size, + uint16_t width, uint16_t height, uint32_t fourcc) +{ + memset(img, 0x00, sizeof(*img)); + img->buffer = buffer; + img->size = size; + img->width = width; + img->height = height; + img->fourcc = fourcc; +} + +void pixel_image_from_vbuf(struct pixel_image *img, struct video_buffer *vbuf, + struct video_format *fmt) +{ + pixel_image_from_buffer(img, vbuf->buffer, vbuf->size, fmt->width, fmt->height, + fmt->pixelformat); +} + +int pixel_image_to_buffer(struct pixel_image *img, uint8_t *buffer, size_t size) +{ + struct pixel_operation *op; + int ret; + + if (img->err) { + return -ECANCELED; + } + + op = pixel_image_alloc(img, sizeof(struct pixel_operation)); + if (op == NULL) { + return pixel_image_error(img, -ENOMEM); + } + + memset(op, 0x00, sizeof(*op)); + op->name = __func__; + op->threshold = size; + op->fourcc_in = img->fourcc; + op->fourcc_out = img->fourcc; + op->width = img->width; + op->height = img->height; + op->ring.buffer = buffer; + op->ring.size = size; + + sys_slist_append(&img->operations, &op->node); + + ret = pixel_image_process(img); + + img->buffer = buffer; + img->size = size; + + return ret; +} + +int pixel_image_to_vbuf(struct pixel_image *img, struct video_buffer *vbuf) +{ + int ret; + + ret = pixel_image_to_buffer(img, vbuf->buffer, vbuf->size); + vbuf->bytesused = img->size; + + return ret; +} diff --git a/lib/pixel/kernel.c b/lib/pixel/kernel.c new file mode 100644 index 0000000000000..a7d158d70cbb4 --- /dev/null +++ b/lib/pixel/kernel.c @@ -0,0 +1,433 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_kernel, CONFIG_PIXEL_LOG_LEVEL); + +int pixel_image_kernel(struct pixel_image *img, uint32_t kernel_type, int kernel_size) +{ + const struct pixel_operation *op = NULL; + + STRUCT_SECTION_FOREACH_ALTERNATE(pixel_kernel, pixel_operation, tmp) { + if (tmp->fourcc_in == img->fourcc && tmp->type == kernel_type && + kernel_size == tmp->window_size) { + op = tmp; + break; + } + } + + if (op == NULL) { + LOG_ERR("Kernel operation %u of size %ux%u on %s data not found", + kernel_type, kernel_size, kernel_size, VIDEO_FOURCC_TO_STR(img->fourcc)); + return pixel_image_error(img, -ENOSYS); + } + + return pixel_image_add_uncompressed(img, op); +} + +/* Function that processes a 3x3 or 5x5 pixel block described by line buffers and column indexes */ +typedef void kernel_3x3_t(const uint8_t *in[3], int i0, int i1, int i2, + uint8_t *out, int o0, uint16_t base, const uint16_t *kernel); +typedef void kernel_5x5_t(const uint8_t *in[3], int i0, int i1, int i2, int i3, int i4, + uint8_t *out, int o0, uint16_t base, const uint16_t *kernel); + +/* Function that repeats a 3x3 or 5x5 block operation to each channel of a pixel format */ +typedef void pixfmt_3x3_t(const uint8_t *in[3], int i0, int i1, int i2, + uint8_t *out, int o0, uint16_t base, kernel_3x3_t *line_fn, + const uint16_t *kernel); +typedef void pixfmt_5x5_t(const uint8_t *in[5], int i0, int i1, int i2, int i3, int i4, + uint8_t *out, int o0, uint16_t base, kernel_5x5_t *line_fn, + const uint16_t *kernel); + +/* Function that repeats a 3x3 or 5x5 kernel operation over an entire line line */ +typedef void line_3x3_t(const uint8_t *in[3], uint8_t *out, uint16_t width); +typedef void line_5x5_t(const uint8_t *in[5], uint8_t *out, uint16_t width); + +/* + * Convolution kernels: multiply a grid of coefficient with the input data and um them to produce + * one output value. + */ + +static void pixel_convolve_3x3(const uint8_t *in[3], int i0, int i1, int i2, + uint8_t *out, int o0, uint16_t base, const uint16_t *kernel) +{ + int16_t result = 0; + int k = 0; + + /* Apply the coefficients on 3 rows */ + for (int h = 0; h < 3; h++) { + /* Apply the coefficients on 5 columns */ + result += in[h][base + i0] * kernel[k++]; /* line h column 0 */ + result += in[h][base + i1] * kernel[k++]; /* line h column 1 */ + result += in[h][base + i2] * kernel[k++]; /* line h column 2 */ + } + + /* Store the scaled-down output */ + out[base + o0] = result >> kernel[k]; +} + +static void pixel_convolve_5x5(const uint8_t *in[5], int i0, int i1, int i2, int i3, int i4, + uint8_t *out, int o0, uint16_t base, const uint16_t *kernel) +{ + int16_t result = 0; + int k = 0; + + /* Apply the coefficients on 5 rows */ + for (int h = 0; h < 5; h++) { + /* Apply the coefficients on 5 columns */ + result += in[h][base + i0] * kernel[k++]; /* line h column 0 */ + result += in[h][base + i1] * kernel[k++]; /* line h column 1 */ + result += in[h][base + i2] * kernel[k++]; /* line h column 2 */ + result += in[h][base + i3] * kernel[k++]; /* line h column 3 */ + result += in[h][base + i4] * kernel[k++]; /* line h column 4 */ + } + + /* Store the scaled-down output */ + out[base + o0] = result >> kernel[k]; +} + +/* + * Median kernels: find the median value of the input block and send it as output. The effect is to + * denoise the input image while preserving sharpness of the large color regions. + */ + +static inline uint8_t pixel_median(const uint8_t **in, int *idx, uint8_t size) +{ + uint8_t pivot_bot = 0x00; + uint8_t pivot_top = 0xff; + uint8_t num_higher; + int16_t median; + + /* Binary-search of the appropriate median value, 8 steps for 8-bit depth */ + for (int i = 0; i < 8; i++) { + num_higher = 0; + median = (pivot_top + pivot_bot) / 2; + + for (uint16_t h = 0; h < size; h++) { + for (uint16_t w = 0; w < size; w++) { + num_higher += in[h][idx[w]] > median; /* line h column w */ + } + } + + if (num_higher > size * size / 2) { + pivot_bot = median; + } else if (num_higher < size * size / 2) { + pivot_top = median; + } + } + + /* Output the median value */ + return (pivot_top + pivot_bot) / 2; +} + +static void pixel_median_3x3(const uint8_t *in[3], int i0, int i1, int i2, + uint8_t *out, int o0, uint16_t base, const uint16_t *unused) +{ + int idx[] = {base + i0, base + i1, base + i2}; + + out[base + o0] = pixel_median(in, idx, 3); +} + +static void pixel_median_5x5(const uint8_t *in[5], int i0, int i1, int i2, int i3, int i4, + uint8_t *out, int o0, uint16_t base, const uint16_t *unused) +{ + int idx[] = {base + i0, base + i1, base + i2, base + i3, base + i4}; + + out[base + o0] = pixel_median(in, idx, 5); +} + +/* + * Convert pixel offsets into byte offset, and repeat a kernel function for every channel of a + * pixel format. + */ + +static void pixel_kernel_rgb24_3x3(const uint8_t *in[3], int i0, int i1, int i2, + uint8_t *out, int o0, uint16_t base, kernel_3x3_t *line_fn, + const uint16_t *kernel) +{ + i0 *= 3, i1 *= 3, i2 *= 3, o0 *= 3, base *= 3; + line_fn(in, i0, i1, i2, out, o0, base + 0, kernel); /* R */ + line_fn(in, i0, i1, i2, out, o0, base + 1, kernel); /* G */ + line_fn(in, i0, i1, i2, out, o0, base + 2, kernel); /* B */ +} + +static void pixel_kernel_rgb24_5x5(const uint8_t *in[5], int i0, int i1, int i2, int i3, int i4, + uint8_t *out, int o0, uint16_t base, kernel_5x5_t *line_fn, + const uint16_t *kernel) +{ + i0 *= 3, i1 *= 3, i2 *= 3, i3 *= 3, i4 *= 3, o0 *= 3, base *= 3; + line_fn(in, i0, i1, i2, i3, i4, out, o0, base + 0, kernel); /* R */ + line_fn(in, i0, i1, i2, i3, i4, out, o0, base + 1, kernel); /* G */ + line_fn(in, i0, i1, i2, i3, i4, out, o0, base + 2, kernel); /* B */ +} + +/* + * Portable/default C implementation of line processing functions. They are inlined into + * line-conversion functions at the bottom of this file declared as __weak. + */ + +static inline void pixel_kernel_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width, + pixfmt_3x3_t *pixfmt_fn, kernel_3x3_t *line_fn, + const uint16_t *kernel) +{ + uint16_t w = 0; + + /* Edge case on first two columns */ + pixfmt_fn(in, 0, 0, 1, out, 0, w + 0, line_fn, kernel); + + /* process the entire line except the first two and last two columns (edge cases) */ + for (w = 0; w + 3 <= width; w++) { + pixfmt_fn(in, 0, 1, 2, out, 1, w, line_fn, kernel); + } + + /* Edge case on last two columns */ + pixfmt_fn(in, 0, 1, 1, out, 1, w, line_fn, kernel); +} + +static inline void pixel_kernel_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width, + pixfmt_5x5_t *pixfmt_fn, kernel_5x5_t *line_fn, + const uint16_t *kernel) +{ + uint16_t w = 0; + + /* Edge case on first two columns, repeat the left column to fill the blank */ + pixfmt_fn(in, 0, 0, 0, 1, 2, out, 0, w, line_fn, kernel); + pixfmt_fn(in, 0, 0, 1, 2, 3, out, 1, w, line_fn, kernel); + + /* process the entire line except the first two and last two columns (edge cases) */ + for (w = 0; w + 5 <= width; w++) { + pixfmt_fn(in, 0, 1, 2, 3, 4, out, 2, w, line_fn, kernel); + } + + /* Edge case on last two columns, repeat the right column to fill the blank */ + pixfmt_fn(in, 0, 1, 2, 3, 3, out, 2, w, line_fn, kernel); + pixfmt_fn(in, 1, 2, 3, 3, 3, out, 3, w, line_fn, kernel); +} + +/* + * Call a line-processing function on every line, handling the edge-cases on first line and last + * line by repeating the lines at the edge to fill the gaps. + */ + +void pixel_kernel_3x3_op(struct pixel_operation *op) +{ + uint16_t prev_line_offset = op->line_offset; + line_3x3_t *line_fn = op->arg; + const uint8_t *in[] = { + pixel_operation_get_input_line(op), + pixel_operation_peek_input_line(op), + pixel_operation_peek_input_line(op), + }; + + __ASSERT_NO_MSG(op->width >= 3); + __ASSERT_NO_MSG(op->height >= 3); + + /* Allow overflowing before the top by repeating the first line */ + if (prev_line_offset == 0) { + const uint8_t *top[] = {in[0], in[0], in[1]}; + + line_fn(top, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } + + /* Process one more line */ + line_fn(in, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + /* Allow overflowing after the bottom by repeating the last line */ + if (prev_line_offset + 3 >= op->height) { + const uint8_t *bot[] = {in[1], in[2], in[2]}; + + line_fn(bot, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + /* Flush the remaining lines that were used for lookahead context */ + pixel_operation_get_input_line(op); + pixel_operation_get_input_line(op); + } +} + +void pixel_kernel_5x5_op(struct pixel_operation *op) +{ + uint16_t prev_line_offset = op->line_offset; + line_5x5_t *line_fn = op->arg; + const uint8_t *in[] = { + pixel_operation_get_input_line(op), + pixel_operation_peek_input_line(op), + pixel_operation_peek_input_line(op), + pixel_operation_peek_input_line(op), + pixel_operation_peek_input_line(op), + }; + + __ASSERT_NO_MSG(op->width >= 5); + __ASSERT_NO_MSG(op->height >= 5); + + /* Allow overflowing before the top by repeating the first line */ + if (prev_line_offset == 0) { + const uint8_t *top[] = {in[0], in[0], in[0], in[1], in[2], in[3]}; + + line_fn(&top[0], pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + line_fn(&top[1], pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + } + + /* Process one more line */ + line_fn(in, pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + /* Allow overflowing after the bottom by repeating the last line */ + if (prev_line_offset + 5 >= op->height) { + const uint8_t *bot[] = {in[1], in[2], in[3], in[4], in[4], in[4]}; + + line_fn(&bot[0], pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + line_fn(&bot[1], pixel_operation_get_output_line(op), op->width); + pixel_operation_done(op); + + /* Flush the remaining lines that were used for lookahead context */ + pixel_operation_get_input_line(op); + pixel_operation_get_input_line(op); + pixel_operation_get_input_line(op); + pixel_operation_get_input_line(op); + } +} + +/* + * Declaration of convolution kernels, with the line-processing functions declared as __weak to + * allow them to be replaced with optimized versions + */ + +static const int16_t pixel_identity_3x3[] = { + 0, 0, 0, + 0, 1, 0, + 0, 0, 0, 0 +}; + +__weak void pixel_identity_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width) +{ + pixel_kernel_3x3(in, out, width, pixel_kernel_rgb24_3x3, pixel_convolve_3x3, + pixel_identity_3x3); +} +PIXEL_DEFINE_KERNEL_3X3_OPERATION(pixel_identity_rgb24_3x3, + PIXEL_KERNEL_IDENTITY, VIDEO_PIX_FMT_RGB24); + +static const int16_t pixel_identity_5x5[] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 +}; + +__weak void pixel_identity_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width) +{ + pixel_kernel_5x5(in, out, width, pixel_kernel_rgb24_5x5, pixel_convolve_5x5, + pixel_identity_5x5); +} +PIXEL_DEFINE_KERNEL_5X5_OPERATION(pixel_identity_rgb24_5x5, + PIXEL_KERNEL_IDENTITY, VIDEO_PIX_FMT_RGB24); + +static const int16_t pixel_edgedetect_3x3[] = { + -1, -1, -1, + -1, 8, -1, + -1, -1, -1, 0 +}; + +__weak void pixel_edgedetect_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width) +{ + pixel_kernel_3x3(in, out, width, pixel_kernel_rgb24_3x3, pixel_convolve_3x3, + pixel_edgedetect_3x3); +} +PIXEL_DEFINE_KERNEL_3X3_OPERATION(pixel_edgedetect_rgb24_3x3, + PIXEL_KERNEL_EDGE_DETECT, VIDEO_PIX_FMT_RGB24); + +static const int16_t pixel_gaussianblur_3x3[] = { + 1, 2, 1, + 2, 4, 2, + 1, 2, 1, 4 +}; + +__weak void pixel_gaussianblur_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width) +{ + pixel_kernel_3x3(in, out, width, pixel_kernel_rgb24_3x3, pixel_convolve_3x3, + pixel_gaussianblur_3x3); +} +PIXEL_DEFINE_KERNEL_3X3_OPERATION(pixel_gaussianblur_rgb24_3x3, + PIXEL_KERNEL_GAUSSIAN_BLUR, VIDEO_PIX_FMT_RGB24); + +static const int16_t pixel_gaussianblur_5x5[] = { + 1, 4, 6, 4, 1, + 4, 16, 24, 16, 4, + 6, 24, 36, 24, 6, + 4, 16, 24, 16, 4, + 1, 4, 6, 4, 1, 8 +}; + +__weak void pixel_gaussianblur_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width) +{ + pixel_kernel_5x5(in, out, width, pixel_kernel_rgb24_5x5, pixel_convolve_5x5, + pixel_gaussianblur_5x5); +} +PIXEL_DEFINE_KERNEL_5X5_OPERATION(pixel_gaussianblur_rgb24_5x5, + PIXEL_KERNEL_GAUSSIAN_BLUR, VIDEO_PIX_FMT_RGB24); + +static const int16_t pixel_sharpen_3x3[] = { + 0, -1, 0, + -1, 5, -1, + 0, -1, 0, 0 +}; + +__weak void pixel_sharpen_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width) +{ + pixel_kernel_3x3(in, out, width, pixel_kernel_rgb24_3x3, pixel_convolve_3x3, + pixel_sharpen_3x3); +} +PIXEL_DEFINE_KERNEL_3X3_OPERATION(pixel_sharpen_rgb24_3x3, + PIXEL_KERNEL_SHARPEN, VIDEO_PIX_FMT_RGB24); + +static const int16_t pixel_unsharp_5x5[] = { + -1, -4, -6, -4, -1, + -4, -16, -24, -16, -4, + -6, -24, 476, -24, -6, + -4, -16, -24, -16, -4, + -1, -4, -6, -4, -1, 8 +}; + +__weak void pixel_sharpen_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width) +{ + pixel_kernel_5x5(in, out, width, pixel_kernel_rgb24_5x5, pixel_convolve_5x5, + pixel_unsharp_5x5); +} +PIXEL_DEFINE_KERNEL_5X5_OPERATION(pixel_sharpen_rgb24_5x5, + PIXEL_KERNEL_SHARPEN, VIDEO_PIX_FMT_RGB24); + +/* + * Declaration of median kernels, with the line-processing functions declared as __weak to + * allow them to be replaced with optimized versions + */ + +__weak void pixel_median_rgb24_5x5(const uint8_t *in[5], uint8_t *out, uint16_t width) +{ + pixel_kernel_5x5(in, out, width, pixel_kernel_rgb24_5x5, pixel_median_5x5, NULL); +} + +PIXEL_DEFINE_KERNEL_5X5_OPERATION(pixel_median_rgb24_5x5, + PIXEL_KERNEL_DENOISE, VIDEO_PIX_FMT_RGB24); + +__weak void pixel_median_rgb24_3x3(const uint8_t *in[3], uint8_t *out, uint16_t width) +{ + pixel_kernel_3x3(in, out, width, pixel_kernel_rgb24_3x3, pixel_median_3x3, NULL); +} +PIXEL_DEFINE_KERNEL_3X3_OPERATION(pixel_median_rgb24_3x3, + PIXEL_KERNEL_DENOISE, VIDEO_PIX_FMT_RGB24); diff --git a/lib/pixel/pixel.ld b/lib/pixel/pixel.ld new file mode 100644 index 0000000000000..0a606969bf4e2 --- /dev/null +++ b/lib/pixel/pixel.ld @@ -0,0 +1,8 @@ +# Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +#include + +ITERABLE_SECTION_RAM(pixel_convert, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(pixel_resize, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(pixel_kernel, Z_LINK_ITERABLE_SUBALIGN) diff --git a/lib/pixel/print.c b/lib/pixel/print.c new file mode 100644 index 0000000000000..fdd2c88fa263b --- /dev/null +++ b/lib/pixel/print.c @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef CONFIG_PIXEL_PRINT_NONE +#define PIXEL_PRINT(...) +#endif + +#ifdef CONFIG_PIXEL_PRINT_PRINTF +#define PIXEL_PRINT(...) printf(__VA_ARGS__) +#endif + +#ifdef CONFIG_PIXEL_PRINT_PRINTK +#define PIXEL_PRINT(...) printk(__VA_ARGS__) +#endif + +#ifdef CONFIG_PIXEL_PRINT_SHELL +#define PIXEL_PRINT(...) shell_print(pixel_print_shell, __VA_ARGS__) +#endif + +static struct shell *pixel_print_shell; + +void pixel_print_set_shell(struct shell *sh) +{ + pixel_print_shell = sh; +} + +__unused static 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; +} + +__unused static uint8_t pixel_gray8_to_256color(uint8_t gray8) +{ + return 232 + gray8 * 24 / 256; +} + +static void pixel_print_truecolor(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) +{ + PIXEL_PRINT("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%um▄", + rgb24row0[0], rgb24row0[1], rgb24row0[2], + rgb24row1[0], rgb24row1[1], rgb24row1[2]); +} + +static void pixel_print_256color(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) +{ + PIXEL_PRINT("\e[48;5;%um\e[38;5;%um▄", + pixel_rgb24_to_256color(rgb24row0), + pixel_rgb24_to_256color(rgb24row1)); +} + +static void pixel_print_256gray(uint8_t gray8row0, uint8_t gray8row1) +{ + PIXEL_PRINT("\e[48;5;%um\e[38;5;%um▄", + pixel_gray8_to_256color(gray8row0), + pixel_gray8_to_256color(gray8row1)); +} + +typedef void fn_print_t(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]); + +typedef void fn_conv_t(const uint8_t *src, uint8_t *dst, uint16_t w); + +static void pixel_print(const uint8_t *src, size_t size, uint16_t width, uint16_t height, + fn_print_t *fn_print, fn_conv_t *fn_conv, int bitspp, int npix) +{ + size_t pitch = width * bitspp / BITS_PER_BYTE; + uint8_t nbytes = npix * bitspp / BITS_PER_BYTE; + + for (size_t i = 0, h = 0; h + 2 <= height; h += 2) { + for (size_t w = 0; w + npix <= width; w += npix, i += nbytes) { + uint8_t rgb24a[3 * 2], rgb24b[3 * 2]; + + __ASSERT_NO_MSG(npix <= 2); + + fn_conv(&src[i + pitch * 0], rgb24a, npix); + fn_conv(&src[i + pitch * 1], rgb24b, npix); + + if (i + pitch > size) { + PIXEL_PRINT("\e[m *** early end of buffer at %zu bytes ***\n", + size); + return; + } + + for (int n = 0; n < npix; n++) { + fn_print(&rgb24a[n * 3], &rgb24b[n * 3]); + } + } + PIXEL_PRINT("\e[m|\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +static void pixel_print_buffer(const uint8_t *buffer, size_t size, uint16_t width, uint16_t height, + uint32_t fourcc, fn_print_t *fn) +{ + switch (fourcc) { + case VIDEO_PIX_FMT_RGB24: + pixel_print(buffer, size, width, height, fn, pixel_line_rgb24_to_rgb24, + video_bits_per_pixel(fourcc), 1); + break; + case VIDEO_PIX_FMT_RGB565: + pixel_print(buffer, size, width, height, fn, pixel_line_rgb565le_to_rgb24, + video_bits_per_pixel(fourcc), 1); + break; + case VIDEO_PIX_FMT_RGB565X: + pixel_print(buffer, size, width, height, fn, pixel_line_rgb565be_to_rgb24, + video_bits_per_pixel(fourcc), 1); + break; + case VIDEO_PIX_FMT_RGB332: + pixel_print(buffer, size, width, height, fn, pixel_line_rgb332_to_rgb24, + video_bits_per_pixel(fourcc), 1); + break; + case VIDEO_PIX_FMT_YUYV: + pixel_print(buffer, size, width, height, fn, pixel_line_yuyv_to_rgb24_bt709, + video_bits_per_pixel(fourcc), 2); + break; + case VIDEO_PIX_FMT_YUV24: + pixel_print(buffer, size, width, height, fn, pixel_line_yuv24_to_rgb24_bt709, + video_bits_per_pixel(fourcc), 1); + break; + case VIDEO_PIX_FMT_RGGB8: + case VIDEO_PIX_FMT_BGGR8: + case VIDEO_PIX_FMT_GBRG8: + case VIDEO_PIX_FMT_GRBG8: + case VIDEO_PIX_FMT_GREY: + pixel_print(buffer, size, width, height, fn, pixel_line_y8_to_rgb24_bt709, + video_bits_per_pixel(fourcc), 1); + break; + default: + PIXEL_PRINT("Printing %s buffers not supported\n", VIDEO_FOURCC_TO_STR(fourcc)); + } +} + +void pixel_print_buffer_truecolor(const uint8_t *buffer, size_t size, uint16_t width, + uint16_t height, uint32_t fourcc) +{ + pixel_print_buffer(buffer, size, width, height, fourcc, pixel_print_truecolor); +} + +void pixel_print_buffer_256color(const uint8_t *buffer, size_t size, uint16_t width, + uint16_t height, uint32_t fourcc) +{ + pixel_print_buffer(buffer, size, width, height, fourcc, pixel_print_256color); +} + +void pixel_image_print_truecolor(struct pixel_image *img) +{ + pixel_print_buffer_truecolor(img->buffer, img->size, img->width, img->height, img->fourcc); +} + +void pixel_image_print_256color(struct pixel_image *img) +{ + pixel_print_buffer_256color(img->buffer, img->size, img->width, img->height, img->fourcc); +} + +void pixel_hexdump_raw8(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) { + PIXEL_PRINT("\e[m *** early end of buffer at %zu bytes ***\n", + size); + return; + } + + PIXEL_PRINT(" %02x", raw8[i]); + } + PIXEL_PRINT(" row%u\n", h); + } +} + +void pixel_hexdump_rgb24(const uint8_t *rgb24, size_t size, uint16_t width, uint16_t height) +{ + PIXEL_PRINT(" "); + for (uint16_t w = 0; w < width; w++) { + PIXEL_PRINT("col%-7u", w); + } + PIXEL_PRINT("\n"); + + for (uint16_t w = 0; w < width; w++) { + PIXEL_PRINT(" R G B "); + } + PIXEL_PRINT("\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) { + PIXEL_PRINT("\e[m *** early end of buffer at %zu bytes ***\n", + size); + return; + } + + PIXEL_PRINT(" %02x %02x %02x ", rgb24[i + 0], rgb24[i + 1], rgb24[i + 2]); + } + PIXEL_PRINT(" row%u\n", h); + } +} + +void pixel_hexdump_rgb565(const uint8_t *rgb565, size_t size, uint16_t width, uint16_t height) +{ + PIXEL_PRINT(" "); + for (uint16_t w = 0; w < width; w++) { + PIXEL_PRINT("col%-4u", w); + } + PIXEL_PRINT("\n"); + + for (uint16_t w = 0; w < width; w++) { + PIXEL_PRINT(" RGB565"); + } + PIXEL_PRINT("\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) { + PIXEL_PRINT("\e[m *** early end of buffer at %zu bytes ***\n", + size); + return; + } + + PIXEL_PRINT(" %02x %02x ", rgb565[i + 0], rgb565[i + 1]); + } + PIXEL_PRINT(" row%u\n", h); + } +} + +void pixel_hexdump_yuyv(const uint8_t *yuyv, size_t size, uint16_t width, uint16_t height) +{ + PIXEL_PRINT(" "); + for (uint16_t w = 0; w < width; w++) { + PIXEL_PRINT("col%-3u", w); + if ((w + 1) % 2 == 0) { + PIXEL_PRINT(" "); + } + } + PIXEL_PRINT("\n"); + + for (uint16_t w = 0; w < width; w++) { + PIXEL_PRINT(" %c%u", "YUYV"[w % 2 * 2 + 0], w % 2); + PIXEL_PRINT(" %c%u", "YUYV"[w % 2 * 2 + 1], w % 2); + if ((w + 1) % 2 == 0) { + PIXEL_PRINT(" "); + } + } + PIXEL_PRINT("\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) { + PIXEL_PRINT("\e[m *** early end of buffer at %zu bytes ***\n", + size); + return; + } + + PIXEL_PRINT(" %02x %02x", yuyv[i], yuyv[i + 1]); + if ((w + 1) % 2 == 0) { + PIXEL_PRINT(" "); + } + } + PIXEL_PRINT(" 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); + } + PIXEL_PRINT("\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); + } + PIXEL_PRINT("\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); + } + PIXEL_PRINT("\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 0000000000000..7675da5392c81 --- /dev/null +++ b/lib/pixel/resize.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_resize, CONFIG_PIXEL_LOG_LEVEL); + +int pixel_image_resize(struct pixel_image *img, uint16_t width, uint16_t height) +{ + const struct pixel_operation *op = NULL; + int ret; + + STRUCT_SECTION_FOREACH_ALTERNATE(pixel_resize, pixel_operation, tmp) { + if (tmp->fourcc_in == img->fourcc) { + op = tmp; + break; + } + } + + if (op == NULL) { + LOG_ERR("Resize operation for %s not found", VIDEO_FOURCC_TO_STR(img->fourcc)); + return pixel_image_error(img, -ENOSYS); + } + + ret = pixel_image_add_uncompressed(img, op); + img->width = width; + img->height = height; + return ret; +} + +static inline void pixel_resize_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_resize_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_resize_line(&src_buf[src_i], src_width, &dst_buf[dst_i], dst_width, + bits_per_pixel); + } +} + +__weak void pixel_resize_frame_raw24(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_resize_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 24); +} + +__weak void pixel_resize_frame_raw16(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_resize_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 16); +} + +__weak void pixel_resize_frame_raw8(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_resize_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 8); +} + +static inline void pixel_op_resize(struct pixel_operation *op, uint8_t bits_per_pixel) +{ + struct pixel_operation *next = SYS_SLIST_PEEK_NEXT_CONTAINER(op, node); + uint16_t prev_offset = (op->line_offset + 1) * next->height / op->height; + const uint8_t *line_in = pixel_operation_get_input_line(op); + uint16_t next_offset = (op->line_offset + 1) * next->height / op->height; + + for (uint16_t i = 0; prev_offset + i < next_offset; i++) { + pixel_resize_line(line_in, op->width, pixel_operation_get_output_line(op), + next->width, bits_per_pixel); + pixel_operation_done(op); + } +} + +__weak void pixel_op_resize_raw32(struct pixel_operation *op) +{ + pixel_op_resize(op, 32); +} +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw32, VIDEO_PIX_FMT_ABGR32); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw32, VIDEO_PIX_FMT_ARGB32); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw32, VIDEO_PIX_FMT_BGRA32); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw32, VIDEO_PIX_FMT_RGBA32); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw32, VIDEO_PIX_FMT_XRGB32); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw32, VIDEO_PIX_FMT_XYUV32); + +__weak void pixel_op_resize_raw24(struct pixel_operation *op) +{ + pixel_op_resize(op, 24); +} +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw24, VIDEO_PIX_FMT_BGR24); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw24, VIDEO_PIX_FMT_RGB24); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw24, VIDEO_PIX_FMT_YUV24); + +__weak void pixel_op_resize_raw16(struct pixel_operation *op) +{ + pixel_op_resize(op, 16); +} +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw16, VIDEO_PIX_FMT_RGB565); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw16, VIDEO_PIX_FMT_RGB565X); + +__weak void pixel_op_resize_raw8(struct pixel_operation *op) +{ + pixel_op_resize(op, 8); +} +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw8, VIDEO_PIX_FMT_GREY); +PIXEL_DEFINE_RESIZE_OPERATION(pixel_op_resize_raw8, VIDEO_PIX_FMT_RGB332); diff --git a/lib/pixel/stats.c b/lib/pixel/stats.c new file mode 100644 index 0000000000000..a63f716b7e879 --- /dev/null +++ b/lib/pixel/stats.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include + +#define PIXEL_IDX_R 0 +#define PIXEL_IDX_G 1 +#define PIXEL_IDX_B 2 + +static const uint8_t pixel_idx_rggb8[4] = {PIXEL_IDX_R, PIXEL_IDX_G, PIXEL_IDX_G, PIXEL_IDX_B}; +static const uint8_t pixel_idx_bggr8[4] = {PIXEL_IDX_B, PIXEL_IDX_G, PIXEL_IDX_G, PIXEL_IDX_R}; +static const uint8_t pixel_idx_gbrg8[4] = {PIXEL_IDX_G, PIXEL_IDX_B, PIXEL_IDX_R, PIXEL_IDX_G}; +static const uint8_t pixel_idx_grbg8[4] = {PIXEL_IDX_G, PIXEL_IDX_R, PIXEL_IDX_B, PIXEL_IDX_G}; + +/* 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_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 * 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]; +} + +static inline void pixel_sums_to_rgb24avg(uint32_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(uint32_t sums[3], uint8_t rgb24[3]) +{ + sums[0] += rgb24[0], sums[1] += rgb24[1]; + sums[2] += rgb24[2]; +} + +/* Channel average statistics */ + +void pixel_rgb24frame_to_rgb24avg(const uint8_t *buf, size_t size, uint8_t rgb24avg[3], + uint16_t nval) +{ + uint32_t sums[3] = {0, 0, 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) +{ + uint32_t sums[3] = {0, 0, 0}; + uint8_t rgb24[3]; + + for (uint16_t n = 0; n < nval; n++) { + 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_idx_rggb8); +} + +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_idx_bggr8); +} + +void pixel_gbrg8_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_idx_gbrg8); +} + +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_idx_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); + uint8_t rgb24[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++) { + 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); + uint8_t rgb24[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++) { + 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_idx_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_idx_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_idx_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_idx_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); + uint8_t rgb24[3]; + + __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++) { + pixel_sample_rgb24(buf, buf_size, rgb24); + pixel_y8hist_add_y8(y8hist, pixel_rgb24_get_luma_bt709(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); + uint8_t rgb24[3]; + + __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++) { + pixel_sample_bayer(buf, buf_size, width, rgb24, idx); + pixel_y8hist_add_y8(y8hist, pixel_rgb24_get_luma_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_idx_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_idx_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_idx_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_idx_grbg8); +} diff --git a/tests/lib/pixel/kernel/src/main.c b/tests/lib/pixel/kernel/src/main.c new file mode 100644 index 0000000000000..5c7e451680ea4 --- /dev/null +++ b/tests/lib/pixel/kernel/src/main.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include + +#define WIDTH 20 +#define HEIGHT 20 + +/* Input/output buffers */ +static uint8_t rgb24frame_in[WIDTH * HEIGHT * 3]; +static uint8_t rgb24frame_out[WIDTH * HEIGHT * 3]; + +static void run_kernel(uint32_t kernel_type, uint32_t kernel_size) +{ + struct pixel_image img; + int ret; + + pixel_image_from_buffer(&img, rgb24frame_in, sizeof(rgb24frame_in), WIDTH, HEIGHT, + VIDEO_PIX_FMT_RGB24); + + printf("input:\n"); + pixel_image_print_truecolor(&img); + + ret = pixel_image_kernel(&img, kernel_type, kernel_size); + zassert_ok(ret); + + ret = pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out)); + zassert_ok(ret); + + printf("output:\n"); + pixel_image_print_truecolor(&img); +} + +static void test_identity(uint32_t kernel_size) +{ + run_kernel(PIXEL_KERNEL_IDENTITY, kernel_size); + + for (uint16_t h = 0; h < HEIGHT; h++) { + for (uint16_t w = 0; w < WIDTH; w++) { + size_t i = h * WIDTH * 3 + w * 3; + + zassert_equal(rgb24frame_out[i + 0], rgb24frame_in[i + 0], + "channel R, row %u, col %u", h, w); + zassert_equal(rgb24frame_out[i + 1], rgb24frame_in[i + 1], + "channel G, row %u, col %u", h, w); + zassert_equal(rgb24frame_out[i + 2], rgb24frame_in[i + 2], + "channel B, row %u, col %u", h, w); + } + } +} + +static void test_median(uint32_t kernel_size) +{ + run_kernel(PIXEL_KERNEL_DENOISE, kernel_size); + + for (uint16_t h = 0; h < HEIGHT; h++) { + uint16_t w = 0; + + /* Left half */ + for (; w < WIDTH / 2 - 1; w++) { + size_t i = h * WIDTH * 3 + w * 3; + + zassert_equal(rgb24frame_out[i + 0], rgb24frame_out[i + 3], + "channel R, row %u, col %u", h, w); + zassert_equal(rgb24frame_out[i + 1], rgb24frame_out[i + 4], + "channel G, row %u, col %u", h, w); + zassert_equal(rgb24frame_out[i + 2], rgb24frame_out[i + 5], + "channel B, row %u, col %u", h, w); + } + + /* Left right */ + for (; w < WIDTH / 2 - 1; w++) { + size_t i = h * WIDTH * 3 + w * 3; + + zassert_equal(rgb24frame_out[i + 0], rgb24frame_out[i + 3], + "channel R, row %u, col %u", h, w); + zassert_equal(rgb24frame_out[i + 1], rgb24frame_out[i + 4], + "channel G, row %u, col %u", h, w); + zassert_equal(rgb24frame_out[i + 2], rgb24frame_out[i + 5], + "channel B, row %u, col %u", h, w); + } + } +} + +static void test_blur(uint32_t kernel_size, int blur_margin) +{ + run_kernel(PIXEL_KERNEL_GAUSSIAN_BLUR, kernel_size); + + for (uint16_t h = 0; h < HEIGHT; h++) { + uint16_t w = 0; + + for (; w < WIDTH - 1; w++) { + size_t i = h * WIDTH * 3 + w * 3; + + zassert_within(rgb24frame_out[i + 0], rgb24frame_out[i + 3], blur_margin, + "channel R, row %u, col %u", h, w); + zassert_within(rgb24frame_out[i + 1], rgb24frame_out[i + 4], blur_margin, + "channel G, row %u, col %u", h, w); + zassert_within(rgb24frame_out[i + 2], rgb24frame_out[i + 5], blur_margin, + "channel B, row %u, col %u", h, w); + } + } +} + +ZTEST(lib_pixel_kernel, test_pixel_identity_kernel) +{ + /* Generate test input data */ + for (uint16_t h = 0; h < HEIGHT; h++) { + for (uint16_t w = 0; w < WIDTH; w++) { + rgb24frame_in[h * WIDTH * 3 + w * 3 + 0] = w < WIDTH / 2 ? 0x00 : 0xff; + rgb24frame_in[h * WIDTH * 3 + w * 3 + 1] = (h % 3 + w % 3) / 4 * 0xff; + rgb24frame_in[h * WIDTH * 3 + w * 3 + 2] = h * 0xff / HEIGHT; + } + } + + test_identity(3); + test_identity(5); + + test_median(3); + test_median(5); + + test_blur(3, 128); + test_blur(5, 96); +} + +ZTEST_SUITE(lib_pixel_kernel, NULL, NULL, NULL, NULL, NULL); From 96cc37f89be3cf38d6283c48946c60df52955d3e Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 5 Mar 2025 01:42:48 +0000 Subject: [PATCH 5/7] samples: lib: pixel: show usage of the pixel library The newly introduced lib/pixel features utilities that help composing video pipelines together for the purpose of stream processing, as well as debug utilities. Signed-off-by: Josuah Demangeon --- samples/lib/lib.rst | 6 +++ samples/lib/pixel/image/CMakeLists.txt | 8 +++ samples/lib/pixel/image/README.rst | 51 +++++++++++++++++++ samples/lib/pixel/image/preview.png | Bin 0 -> 8752 bytes samples/lib/pixel/image/prj.conf | 6 +++ samples/lib/pixel/image/sample.yaml | 19 +++++++ samples/lib/pixel/image/src/main.c | 63 +++++++++++++++++++++++ samples/lib/pixel/pixel.rst | 15 ++++++ samples/lib/pixel/print/CMakeLists.txt | 8 +++ samples/lib/pixel/print/README.rst | 57 +++++++++++++++++++++ samples/lib/pixel/print/preview.png | Bin 0 -> 23842 bytes samples/lib/pixel/print/prj.conf | 6 +++ samples/lib/pixel/print/sample.yaml | 21 ++++++++ samples/lib/pixel/print/src/main.c | 68 +++++++++++++++++++++++++ samples/lib/pixel/stats/CMakeLists.txt | 8 +++ samples/lib/pixel/stats/README.rst | 57 +++++++++++++++++++++ samples/lib/pixel/stats/preview.png | Bin 0 -> 8486 bytes samples/lib/pixel/stats/prj.conf | 6 +++ samples/lib/pixel/stats/sample.yaml | 19 +++++++ samples/lib/pixel/stats/src/main.c | 58 +++++++++++++++++++++ 20 files changed, 476 insertions(+) create mode 100644 samples/lib/lib.rst create mode 100644 samples/lib/pixel/image/CMakeLists.txt create mode 100644 samples/lib/pixel/image/README.rst create mode 100644 samples/lib/pixel/image/preview.png create mode 100644 samples/lib/pixel/image/prj.conf create mode 100644 samples/lib/pixel/image/sample.yaml create mode 100644 samples/lib/pixel/image/src/main.c create mode 100644 samples/lib/pixel/pixel.rst create mode 100644 samples/lib/pixel/print/CMakeLists.txt create mode 100644 samples/lib/pixel/print/README.rst create mode 100644 samples/lib/pixel/print/preview.png create mode 100644 samples/lib/pixel/print/prj.conf create mode 100644 samples/lib/pixel/print/sample.yaml create mode 100644 samples/lib/pixel/print/src/main.c create mode 100644 samples/lib/pixel/stats/CMakeLists.txt create mode 100644 samples/lib/pixel/stats/README.rst create mode 100644 samples/lib/pixel/stats/preview.png create mode 100644 samples/lib/pixel/stats/prj.conf create mode 100644 samples/lib/pixel/stats/sample.yaml create mode 100644 samples/lib/pixel/stats/src/main.c diff --git a/samples/lib/lib.rst b/samples/lib/lib.rst new file mode 100644 index 0000000000000..89fd6e8db9cbd --- /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/image/CMakeLists.txt b/samples/lib/pixel/image/CMakeLists.txt new file mode 100644 index 0000000000000..daab365a4e9d2 --- /dev/null +++ b/samples/lib/pixel/image/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_image) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/image/README.rst b/samples/lib/pixel/image/README.rst new file mode 100644 index 0000000000000..0126080c6fa32 --- /dev/null +++ b/samples/lib/pixel/image/README.rst @@ -0,0 +1,51 @@ +.. zephyr:code-sample:: lib_pixel_image + :name: Pixel Imaging Library + + Image an image using subsampling. + +Overview +******** + +A sample showcasing how to perform arbitrary image manipulation using few memory with a +developer-friendly API. + +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/image + :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 + + *** Booting Zephyr OS build v4.1.0-3103-g25dca1b19885 *** + [00:00:00.000,000] app: input image, 32x8, 768 bytes: + ▄▄▄▄▄▄▄▄▄▄▄ shows-up ▄▄▄▄▄▄▄▄▄▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ as color ▄▄▄▄▄▄▄▄▄▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ on the ▄▄▄▄▄▄▄▄▄▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ terminal ▄▄▄▄▄▄▄▄▄▄▄| + [00:00:00.000,000] app: output image, 120x40, 14400 bytes: + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ shows-up ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ as color ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ on the ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄ terminal ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄[...]▄▄| + +.. image:: preview.png diff --git a/samples/lib/pixel/image/preview.png b/samples/lib/pixel/image/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5d4c2034c153e30083d58a2831abec9cfd4005 GIT binary patch literal 8752 zcmbtac|4SB|9-TG>YO%()Tu$`G$uM#lye%A+rBTK*OeM-P zLs=%qRzwnStiB|nV81w8_-{Z*)$iNPvK zSxEo@Rv`?JoCJU+WdN|qe#Ij2$x%7{7VyVn%rA)3D^{#%dvE#<0JZ^$BZp33Y2~xk zpQRi6EwlS6RVg9p-t)eQelPP6tD0$II@0FNqwt~?l@Jr9f|oCbb@lX5mKkXEElYfy zl0q?&`r`CMh-mL`f(ZEJb1Nu;-pzzh6@u%O5?%Py%QkODtObCeO*3OmnPQc?-_WPV z;}!id8CCBcN7H1ZjHP7Mp_4xWz@TM_5gt}SPaYfQ9HH7{3m+;=0CrOl#J((RtMx}D zfcp}72pCdb6ajVhmpv6Xe$G>-T^LWu3C~11O3_LC9`_K)1N&VO2zLGWt#jfEL~ zWdu4-6zulmy7k~H0619g7AqWu4(2f6qt6`W~R^Y=H~_IGGw* zP;Kz38ECACPQ_GF&{06QuKVRF!G2@v$qiVY&?;BCmt|BTgbV~~)uU&Il`102mF zzr!#4b3(hJBv-E?|1vfIE1_bfA}Vl%TW~R+AJ)h6rWcqV$xgq>(azh@=2Hd9Hh=){ zgeN)Ag+omvLidSo<#3~v3GonYGeZ)26vYv#?2~xa688SXZ%Y7UPj8paemZPss&53( zNU9$O0F_y;_`MIo{d(xU=OP4`x2fQeuA>TcMIXCjT8JaIwUO$>uu&e@{HXQqsR`JgeDA7&gOE$5na z17p(^b+kvG?cj*E5G@0;$nb#Jaepylkmdu8J@ou<+xeD*5zu2A7- zTjvjz*sa&YeoeYGDL&(#W7%loxNpM)(HGbv;4MqJ3ah^((3xKyy7-E(`x*YjwA5xf zzy1-vb7BJKgWZ{AWi2*lBhFm z0j2sZou!X*>s}A&l*(#nXF6nD`w7TO^}tu>OAKi&4rY)TrAm5<-A(KJFY^L>?3)nF z84A)ER9jAEoP^!ulNMA;!0`|LI_v5;!q&tP@oouwN{Z{@ccU?MIQi8Z%2UG{6Pp7D@k>00?0*6 zdl%#)*aY!o2-nD;_D=4Ci-N6jyiL;QUy?LUQ;bh*()7~_ewJCLN&$)a!ywt27>N*! zlpCMLmqk6UMqQ@yr!Wtf=c%C^6@6`;yl@&WXeV#DYHt|R!W(>xLi3@%Yn1>z?5ByU zzJ7+NK7H1Lr|vu1xUu#2URU#_0DvqyhVK>jY)`AU4|*IjK#n2}gNZ8P zJP?iVE&M#;Cp91#o6Ngv@hPlivSj??X1YrvKW9VKIrCvR940!xGYT_V{fZ>jwDT#P z(5er`E(Nv;ekZw89gU6^*1ahouPmGIijv-*n`Af@)-P5dUar-H+g;v=KaMpxh8-$KS`jUZgHH=_t}? zoUQ+lp}r|-cvf+;xwuRPeg@c;0pQlega4Qr6auD@E6d$PNb|al#^?QaIYjBFT*5CQ9sF z!ncd23TZo_6T)tKQHCxvzQ{}s$2{rOs%Y^XM>;I!NJ(${IMyZh*$}K`brQ)gCVSi1 zVDm#G4qp(Z@Y)3oCSM>+POjnX0uF{#MTnF@S8k3{7nkHSFm=qJH;$uuBU;Xyr#Nie zBe0arr963DP_2pbQ9hT-r+M^j9r4B%^SUp%C4E#^W~2HvnpNg{W!wgDA*W;kq8fIdv(#2TLBO``5jE@N|HGUKe; zmdwbi7ASv!*~@%)iPu-0_T|7*pqX(C61&NReyQ8?+Ll~v-N&OwFV3w`PQ8b-sK!gy zCt-Qf@rjt!0GIdbaJ12y5!UE3>ylF-iFDYoPb(w=F--yJjg08WeBJ zXM5LO%#EgHnU0%C1eHyvKj3W1cy~hm8$t`C&fAYLnG%V5{3UlJU080I_%-*tw)M6- zc%oorY=u6m7M^NdSIEzMmoirI@_+_{-mfyf=oN}M`<(`HSa70e=P`ypwaiNIfNYCh zh}?_CKpuukI(i7kQF-3wcc`R<-_1Tk^35x!4NurK_REqd6&m;o<7VgN_VlNJCUT4u zpG)!1yW9&ocm3BLqr_?=Im@5qPjRN<9I$fa$-Bv1q6YuGn^cd^7OhX+1+g4MfG@h}+Qh?3(`5@kuN$_4JGE^8{4VcDV^dztsToC5?+H_ETyTGE1f)>^N*5j9SuO%^h-8Zwou- zYEB+*wU^_YkeLHm&BrpQv0*pYuK{wZ8_Acps~2Xy$~w#uh3Kqecm7bL;G(zkS^#kH z5FXNvgq_uOj(HTV8%)t z`FIq?mvvjj+)g~MELde^p@|maSI6%8xz!?@%-k~OIdlXrlPSixOV%MLkH;NTJ)cK{ zcScDW2Zno&6pYi5t11?iZhVICV%jo$&8W~PL|2BNm79HVs1{@+L9c0CJa9v}CP5Qw zDJWnakHzIu(r!!?7W8DoR5QirS0geKnj^EU`#PL5jofX~TY=pu62vhiaV@zH&ZVkx z`M)|u&?l;;n=G~dm48t;*T}A@Tr0t(W8cb(&{83hN^M5i1W2yU=V>5a`Rqo?T;8If z#i&?F+iiV6qELY_X0qc)DC!zb1zp>3ZfKhv`$gPTdA91gW&Bp+#c<_9?o|#hvFHZ^C8I#&PYR?Jt#Rcex)s{Hf&PX#2+S|3wa=Nc5Bz-} z#92n7_|b(a&rfd0@sYK4$K4l~RyO!bzQ#JNsFX8n?UY6OZZpVsjPz^2TA6LHQf>0g zqYKWZ=jgGGmnSPr*hwWS=ikNLiChy>b-@8b62)#&^^(XBN!Yko+|$w84V3yOQ`~T3 zr_9f|T~Y%mgpPDp3#D@)BZxP2vYGoDSuhH+#Ftw2o%8pMBun zGS~S{(M#2;Ah&^{d4PLk&H9ORH86TjhI3Mp8;v6c9HhVlQmt30e!1mm{UEaCw|(hp zv~a)vC|%YiKJf0e6}xqB;IR;Yzj1l6EWiimdZugNIq3AB9k_G|&{jaK?MVBgb}!7i zBlR$)A+`A`=wVF}L$*^phDR<>;0wHjG3u`IbT%b3&PrfqN+F3P{5-RZ1SM1atr{uI zfS}}dkhKP53t}@ z%16(+am>Aa%$--`hDJptEM~f#W2;4;HI&drbWE|fK9x?_?T|gc$Hxlp-QlT`Le8-Q zfSs>mAfTSB&t4W68s$%MM7c(w70jAz1!w1isHT!BZ?tZvo0Y8r;pyt(Xj(vD?5@h& z7vrr2pIA=x?vz%{2zLH5^7U%ce--pEmT6S%NN|p?4?~j=Gl(wa6G`+f_4Sm@{qFgj z_r+t#ns55RE9=hk@BW;7Z=$U$@CE7)t+y4qd$canU`?nb6n%?EN)5`58qm70X$1i2 zz5I5a|M&N3Xz)RJ|C;Ymk<%C*Z#4vMdQ#QxMr}f(py1s(SbdKBo}U*8LOZ2+z=Z&I z5Y8gz;j&#hb83<_Wk#Dm;J6FNK{NMzYhjEQe0Ed7WT)72k*%>RC(_OMKpQdM+RdI9 zB3(A<*#pbQQTAYh4RWoiUDe_%ay+)ItEk0P#>|89V0cy0p3}lU&E3zkx41_v4YHs7 za(+lyr8-bPSAV*PuPZTQ`TEHUZ?U*%uQs-)p50_asl$>cyd;)#-pS^5tZlumd#y9uJNUHG!J-kYSeJMeJwqN{O*aOlLI{-;tfhkkJCbF zS#Oawtinf!S;*mbn5vYYAiYHsrOWMBxtBdm$VssfYao=kiQE>lN2v6g0K4-t=Ez4q zA7x!X!wQy&s;(L=1Dp&QI6Qo6LXa5EWqq!eBVj&uI0fdbm>ibrIGo!RLZ`Y1vxP{# zEJ)S9q&fv|VP$s;a!r(qeOJG+Ps6?Zus*li4eCS6>d!uD9_zPY5{~XY0|&a$Q4mMd zAae^$9&F%pzL8|QcTZE#W@YbOu}_$yt@MZ>=`N!pm!-vx_uQ?JX)e{+U%RCAr|tkB z-u|`;lx~M=zb@s(J++og2Ab$iwt3C^M=9D@CU+ja*6qmBvPRyCi>SOK5mY49sY)h} zUibgv;!ccuTI-0VUzMRyM^>FIygMl{2ecnG#B|X^oRS9s%PWC~AI=8VFvQ737`3o{ zyW=ma21+Hxl_hqA^w_rki@#2u>NIcI$iAAbVfn@XRW*!>q7K(1kD}>!eFOE>J0sQcxkCh@$ z(!9jKM0%D@Rt2)|?B_gRY<5!_nia0Q9Pl*653&@tnnY&nAMk#C@55rSI}_Qt3o%I# zY=Ex3nXl(;0d%)lv~D;E3unq#HGYa+EanYNlaQ+xG zR&QF-stAfr?cV>TP9PH;AI6cK?#)r9S%u1nPdrMu1~~#S)=>IJPeFERP?HWd|65z> z^41E^HM(kF(7N>Cp@cGx<@SL+hW$)p)c;~KaN(5EV$+;s^LgxX=2mMy{(cPa3~kX) z?&uS1iMyC=IA&W@omj*1+#Ft;%?-#jYsm{K8MwqZIwz{)aRP47?ACxUd7s#ply1Ff zJqAX16;v4Bo?sT_?Z|j`?5ThK8Drn{QfJ&u>1mE=0o28ebq%}2&l!bx&LF`7#Fv|c z1#QXBp8YHe;2bIUC)X7t8~jt73V*+3fGPUghXYNX zd&JWjqUm=g0I#MaYE9Q04lLl?^LXZ;Y<8k;O$)yBdH6I4+(0s0X;#fY0Wxr zHQmphQ%V1Tz;F|@Sl9v*&Ovo*-KW^j=QFZswIvazIFE0>X*kX#;I9{OW=WC0eOe=`JmaTW-d zGiEG}f6qb6ObLH45Tcsc&5hF<`Fnwd7RBzkT>JtyPao}^Hw7Kay$L>jm z0`TAt!A2ZGo5!z}H?W`qJ70r@KHo z+Z{M5+8XLJ#di-(w^jpn^4>gP&WF)APkI6YfeH&i)P@FwuLFpqCPyg0*j@cUvmQcm literal 0 HcmV?d00001 diff --git a/samples/lib/pixel/image/prj.conf b/samples/lib/pixel/image/prj.conf new file mode 100644 index 0000000000000..ee16fb9972b2d --- /dev/null +++ b/samples/lib/pixel/image/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/image/sample.yaml b/samples/lib/pixel/image/sample.yaml new file mode 100644 index 0000000000000..e0641c8459541 --- /dev/null +++ b/samples/lib/pixel/image/sample.yaml @@ -0,0 +1,19 @@ +sample: + description: Pixel image sample, image manipulation library + name: pixel image +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "input image, 32x8, 768 bytes:" + - "output image, 120x40, 14400 bytes:" +tests: + sample.pixel.image: + tags: pixel + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y diff --git a/samples/lib/pixel/image/src/main.c b/samples/lib/pixel/image/src/main.c new file mode 100644 index 0000000000000..2805959c433e7 --- /dev/null +++ b/samples/lib/pixel/image/src/main.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#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 rgb24frame_in[32 * 8 * 3]; +static uint8_t rgb24frame_out[120 * 40 * 3]; + +int main(void) +{ + const uint8_t beg[] = {0x00, 0x70, 0xc5}; + const uint8_t end[] = {0x79, 0x29, 0xd2}; + struct pixel_image img; + + /* Generate a smooth gradient for a small image */ + gradient(rgb24frame_in, sizeof(rgb24frame_in), beg, end); + + /* Open that buffer as an image type */ + pixel_image_from_buffer(&img, rgb24frame_in, sizeof(rgb24frame_in), 32, 8, + VIDEO_PIX_FMT_RGB24); + LOG_INF("input image, %ux%u, %zu bytes:", img.width, img.height, img.size); + pixel_image_print_truecolor(&img); + + /* Turn it into a tall vertical image, now displeasant "banding" artifacts appear */ + pixel_image_resize(&img, 5, 40); + + /* Try to attenuate it with a blur effect (comment this line to see the difference) */ + pixel_image_kernel(&img, PIXEL_KERNEL_GAUSSIAN_BLUR, 5); + + /* Stretch the gradient horizontally over the entire width of the output buffer */ + pixel_image_resize(&img, 120, 40); + + /* Save the image into the output buffer and check for errors */ + pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out)); + if (img.err != 0) { + LOG_ERR("Image has error %u: %s", img.err, strerror(-img.err)); + return img.err; + } + + /* Now that the imagme is exported, we can print it */ + LOG_INF("output image, %ux%u, %zu bytes:", img.width, img.height, img.size); + pixel_image_print_truecolor(&img); + + return 0; +} diff --git a/samples/lib/pixel/pixel.rst b/samples/lib/pixel/pixel.rst new file mode 100644 index 0000000000000..a202015abf0bf --- /dev/null +++ b/samples/lib/pixel/pixel.rst @@ -0,0 +1,15 @@ +.. 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. + +This helps debugging when other methods are not available. + +The ``truecolor`` printing functions give accurate 24-bit RGB colors but slower than the +``256color`` variants. diff --git a/samples/lib/pixel/print/CMakeLists.txt b/samples/lib/pixel/print/CMakeLists.txt new file mode 100644 index 0000000000000..1a296153270dc --- /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 0000000000000..4056ca0d33f22 --- /dev/null +++ b/samples/lib/pixel/print/README.rst @@ -0,0 +1,57 @@ +.. 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 way debug logs can be interleaved with small preview images for debug purposes. + +.. _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 + + *** Booting Zephyr OS build v4.1.0-2611-gfaa7b74cfda7 *** + [00:00:00.000,000] app: Printing the gradient #0070c5 -> #7929d2 + [00:00:00.000,000] app: hexdump: + col0 col1 col2 col3 col4 [...] col14 col15 + R G B R G B R G B R G B R G B [...] R G B R G B + 00 70 c5 00 6f c5 00 6f c5 00 6f c5 00 6f c5 [...] 00 6f c5 03 6d c5 row0 + 03 6d c5 04 6d c5 04 6d c5 04 6d c5 04 6d c5 [...] 04 6d c5 07 6b c5 row1 + 07 6b c5 07 6b c5 08 6b c5 08 6b c5 08 6b c5 [...] 07 6b c5 0b 69 c6 row2 + 0b 69 c6 0b 69 c6 0b 69 c6 0c 68 c6 0c 68 c6 [...] 0b 69 c6 0e 67 c6 row3 + 0f 67 c6 0f 66 c6 0f 66 c6 0f 66 c6 10 66 c6 [...] 0f 66 c6 12 65 c7 row4 + 12 64 c7 13 64 c7 13 64 c7 13 64 c7 13 64 c7 [...] 13 64 c7 16 62 c7 row5 + 16 62 c7 16 62 c7 17 62 c7 17 62 c7 17 62 c7 [...] 16 62 c7 1a 60 c7 row6 + [...] + [00:00:00.000,000] app: truecolor: + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| + ▄▄▄ shows-up ▄▄▄| + ▄▄▄ as color ▄▄▄| + ▄▄▄ on the ▄▄▄| + ▄▄▄ terminal ▄▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| + [...] + +.. image:: preview.png diff --git a/samples/lib/pixel/print/preview.png b/samples/lib/pixel/print/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac9b19137944202ff6866f672ff3cda38ba9d42 GIT binary patch literal 23842 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 ttbrpi;at`lrOxH~@gFtsq>DmHYB{x>0zMZy38 literal 0 HcmV?d00001 diff --git a/samples/lib/pixel/print/prj.conf b/samples/lib/pixel/print/prj.conf new file mode 100644 index 0000000000000..ee16fb9972b2d --- /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 0000000000000..f559a1cd313b4 --- /dev/null +++ b/samples/lib/pixel/print/sample.yaml @@ -0,0 +1,21 @@ +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 + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y diff --git a/samples/lib/pixel/print/src/main.c b/samples/lib/pixel/print/src/main.c new file mode 100644 index 0000000000000..1d7a75bd21b49 --- /dev/null +++ b/samples/lib/pixel/print/src/main.c @@ -0,0 +1,68 @@ +/* + * 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 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, +}; + +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_hexdump_rgb24(rgb24frame, sizeof(rgb24frame), 16, 32); + + LOG_INF("truecolor:"); + pixel_print_buffer_truecolor(rgb24frame, sizeof(rgb24frame), 16, 32, VIDEO_PIX_FMT_RGB24); + + LOG_INF("256color:"); + pixel_print_buffer_256color(rgb24frame, sizeof(rgb24frame), 16, 32, VIDEO_PIX_FMT_RGB24); +} + +void print_histogram(void) +{ + 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/stats/CMakeLists.txt b/samples/lib/pixel/stats/CMakeLists.txt new file mode 100644 index 0000000000000..1a296153270dc --- /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 0000000000000..c34c04c88ff49 --- /dev/null +++ b/samples/lib/pixel/stats/README.rst @@ -0,0 +1,57 @@ +.. 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 + + *** Booting Zephyr OS build v4.1.0-2611-gfaa7b74cfda7 *** + [00:00:00.000,000] app: Input image preview: + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| + [00:00:00.000,000] app: RGB histogram of the image + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 60 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 56 + ▄▄▄▄▄▄▄▄▄▄ shows-up ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 52 + ▄▄▄▄▄▄▄▄▄▄ as color ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 48 + ▄▄▄▄▄▄▄▄▄▄ on the ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 45 + ▄▄▄▄▄▄▄▄▄▄ terminal ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 41 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 37 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 33 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 30 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 26 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 22 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 18 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 15 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 11 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄| - 7 + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + [00:00:00.000,000] app: RGB channel averages of the image + [00:00:00.000,000] app: - R: 0x47/0xff + [00:00:00.000,000] app: - G: 0x88/0xff + [00:00:00.000,000] app: - B: 0xec/0xff + +.. image:: preview.png diff --git a/samples/lib/pixel/stats/preview.png b/samples/lib/pixel/stats/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..7419606ea70fd6b6dc7c8c3d178b67cb50b975f2 GIT binary patch literal 8486 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 TE9TWG+f$NPzh83K?8*NC`_(nN literal 0 HcmV?d00001 diff --git a/samples/lib/pixel/stats/prj.conf b/samples/lib/pixel/stats/prj.conf new file mode 100644 index 0000000000000..ee16fb9972b2d --- /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 0000000000000..9784924822736 --- /dev/null +++ b/samples/lib/pixel/stats/sample.yaml @@ -0,0 +1,19 @@ +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 + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y diff --git a/samples/lib/pixel/stats/src/main.c b/samples/lib/pixel/stats/src/main.c new file mode 100644 index 0000000000000..47e9fe531f0dd --- /dev/null +++ b/samples/lib/pixel/stats/src/main.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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_buffer_truecolor(image_rgb24, sizeof(image_rgb24), 20, 4, VIDEO_PIX_FMT_RGB24); + + 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; +} From 43f5a224b03f8c99cf04255d505b703854c5e16f Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 16 Mar 2025 02:35:19 +0000 Subject: [PATCH 6/7] tests: lib: pixel: add unit tests for format conversion The tests use data generated by the ffmpeg command line utilty in order to avoid bias by having the same person implementing the tests and the source code. Signed-off-by: Josuah Demangeon --- tests/lib/pixel/bayer/CMakeLists.txt | 8 + tests/lib/pixel/bayer/prj.conf | 4 + tests/lib/pixel/bayer/src/main.c | 79 +++++++++ tests/lib/pixel/bayer/testcase.yaml | 9 + tests/lib/pixel/convert/CMakeLists.txt | 8 + tests/lib/pixel/convert/prj.conf | 4 + tests/lib/pixel/convert/src/main.c | 235 +++++++++++++++++++++++++ tests/lib/pixel/convert/testcase.yaml | 9 + tests/lib/pixel/kernel/CMakeLists.txt | 8 + tests/lib/pixel/kernel/prj.conf | 4 + tests/lib/pixel/kernel/testcase.yaml | 9 + tests/lib/pixel/resize/CMakeLists.txt | 8 + tests/lib/pixel/resize/prj.conf | 4 + tests/lib/pixel/resize/src/main.c | 101 +++++++++++ tests/lib/pixel/resize/testcase.yaml | 9 + 15 files changed, 499 insertions(+) create mode 100644 tests/lib/pixel/bayer/CMakeLists.txt create mode 100644 tests/lib/pixel/bayer/prj.conf create mode 100644 tests/lib/pixel/bayer/src/main.c create mode 100644 tests/lib/pixel/bayer/testcase.yaml create mode 100644 tests/lib/pixel/convert/CMakeLists.txt create mode 100644 tests/lib/pixel/convert/prj.conf create mode 100644 tests/lib/pixel/convert/src/main.c create mode 100644 tests/lib/pixel/convert/testcase.yaml create mode 100644 tests/lib/pixel/kernel/CMakeLists.txt create mode 100644 tests/lib/pixel/kernel/prj.conf create mode 100644 tests/lib/pixel/kernel/testcase.yaml create mode 100644 tests/lib/pixel/resize/CMakeLists.txt create mode 100644 tests/lib/pixel/resize/prj.conf create mode 100644 tests/lib/pixel/resize/src/main.c create mode 100644 tests/lib/pixel/resize/testcase.yaml diff --git a/tests/lib/pixel/bayer/CMakeLists.txt b/tests/lib/pixel/bayer/CMakeLists.txt new file mode 100644 index 0000000000000..8780ed12afb96 --- /dev/null +++ b/tests/lib/pixel/bayer/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(lib_pixel_convert) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/pixel/bayer/prj.conf b/tests/lib/pixel/bayer/prj.conf new file mode 100644 index 0000000000000..c5261e0c19f60 --- /dev/null +++ b/tests/lib/pixel/bayer/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ASSERT=y +CONFIG_PIXEL_LOG_LEVEL_DBG=y +CONFIG_ZTEST=y +CONFIG_PIXEL=y diff --git a/tests/lib/pixel/bayer/src/main.c b/tests/lib/pixel/bayer/src/main.c new file mode 100644 index 0000000000000..bf106e02ed363 --- /dev/null +++ b/tests/lib/pixel/bayer/src/main.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include + +#define WIDTH 16 +#define HEIGHT 16 + +#define ERROR_MARGIN 13 + +static uint8_t bayerframe_in[WIDTH * HEIGHT * 1]; +static uint8_t rgb24frame_out[WIDTH * HEIGHT * 3]; + +void test_bayer(uint32_t fourcc, uint32_t window_size, uint32_t expected_color) +{ + uint8_t r = expected_color >> 16; + uint8_t g = expected_color >> 8; + uint8_t b = expected_color >> 0; + struct pixel_image img; + int ret; + + pixel_image_from_buffer(&img, bayerframe_in, sizeof(bayerframe_in), WIDTH, HEIGHT, fourcc); + + printf("input:\n"); + pixel_image_print_truecolor(&img); + + ret = pixel_image_debayer(&img, window_size); + zassert_ok(ret); + + pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out)); + + printf("output: (expecting #%06x, R:%02x G:%02x B:%02x)\n", expected_color, r, g, b); + pixel_image_print_truecolor(&img); + + for (int i = 0; i < sizeof(rgb24frame_out) / 3; i++) { + uint8_t out_r = rgb24frame_out[i * 3 + 0]; + uint8_t out_g = rgb24frame_out[i * 3 + 1]; + uint8_t out_b = rgb24frame_out[i * 3 + 2]; + char *s = VIDEO_FOURCC_TO_STR(fourcc); + + zassert_equal(r, out_r, "R: %s: expected 0x%02x, obtained 0x%02x", s, r, out_r); + zassert_equal(g, out_g, "G: %s: expected 0x%02x, obtained 0x%02x", s, g, out_g); + zassert_equal(b, out_b, "B: %s: expected 0x%02x, obtained 0x%02x", s, b, out_b); + } +} + +ZTEST(lib_pixel_bayer, test_pixel_bayer_operation) +{ + /* Generate test input data for 2x2 debayer */ + for (size_t h = 0; h < HEIGHT; h++) { + memset(bayerframe_in + h * WIDTH, h % 2 ? 0xff : 0x00, WIDTH * 1); + } + + test_bayer(VIDEO_PIX_FMT_RGGB8, 2, 0x007fff); + test_bayer(VIDEO_PIX_FMT_GRBG8, 2, 0x007fff); + test_bayer(VIDEO_PIX_FMT_BGGR8, 2, 0xff7f00); + test_bayer(VIDEO_PIX_FMT_GBRG8, 2, 0xff7f00); + + /* Generate test input data for 3x3 debayer */ + for (size_t h = 0; h < HEIGHT; h++) { + for (size_t w = 0; w < WIDTH; w++) { + bayerframe_in[h * WIDTH + w] = (h + w) % 2 ? 0xff : 0x00; + } + } + + test_bayer(VIDEO_PIX_FMT_RGGB8, 3, 0x00ff00); + test_bayer(VIDEO_PIX_FMT_GBRG8, 3, 0xff00ff); + test_bayer(VIDEO_PIX_FMT_BGGR8, 3, 0x00ff00); + test_bayer(VIDEO_PIX_FMT_GRBG8, 3, 0xff00ff); +} + +ZTEST_SUITE(lib_pixel_bayer, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/pixel/bayer/testcase.yaml b/tests/lib/pixel/bayer/testcase.yaml new file mode 100644 index 0000000000000..8e046a4aeadc2 --- /dev/null +++ b/tests/lib/pixel/bayer/testcase.yaml @@ -0,0 +1,9 @@ +tests: + libraries.pixel.bayer: + tags: + - pixel + integration_platforms: + - qemu_cortex_m3 + - native_sim + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y diff --git a/tests/lib/pixel/convert/CMakeLists.txt b/tests/lib/pixel/convert/CMakeLists.txt new file mode 100644 index 0000000000000..8780ed12afb96 --- /dev/null +++ b/tests/lib/pixel/convert/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(lib_pixel_convert) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/pixel/convert/prj.conf b/tests/lib/pixel/convert/prj.conf new file mode 100644 index 0000000000000..c5261e0c19f60 --- /dev/null +++ b/tests/lib/pixel/convert/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ASSERT=y +CONFIG_PIXEL_LOG_LEVEL_DBG=y +CONFIG_ZTEST=y +CONFIG_PIXEL=y diff --git a/tests/lib/pixel/convert/src/main.c b/tests/lib/pixel/convert/src/main.c new file mode 100644 index 0000000000000..d3d673bf36937 --- /dev/null +++ b/tests/lib/pixel/convert/src/main.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include + +#define WIDTH 16 +#define HEIGHT 16 + +#define ERROR_MARGIN 13 + +/* + * To get YUV BT.709 test data: + * + * ffmpeg -y -f lavfi -colorspace bt709 -i color=#RRGGBB:2x2:d=3,format=rgb24 \ + * -f rawvideo -pix_fmt yuyv422 - | hexdump -C + * + * To get RGB565 test data: + * + * ffmpeg -y -f lavfi -i color=#RRGGBB:2x2:d=3,format=rgb24 \ + * -f rawvideo -pix_fmt rgb565 - | hexdump -C + */ + +const struct color_ref { + uint8_t rgb24[3]; + uint8_t rgb565[2]; + uint8_t rgb332[1]; + uint8_t yuv24_bt709[3]; + uint8_t yuv24_bt601[3]; +} reference_data[] = { + + /* Primary colors */ + {{0x00, 0x00, 0x00}, {0x00, 0x00}, {0x00}, {0x10, 0x80, 0x80}, {0x10, 0x80, 0x80}}, + {{0x00, 0x00, 0xff}, {0x00, 0x1f}, {0x03}, {0x20, 0xf0, 0x76}, {0x29, 0xf1, 0x6e}}, + {{0x00, 0xff, 0x00}, {0x07, 0xe0}, {0x1c}, {0xad, 0x2a, 0x1a}, {0x9a, 0x2a, 0x35}}, + {{0x00, 0xff, 0xff}, {0x07, 0xff}, {0x1f}, {0xbc, 0x9a, 0x10}, {0xb4, 0xa0, 0x23}}, + {{0xff, 0x00, 0x00}, {0xf8, 0x00}, {0xe0}, {0x3f, 0x66, 0xf0}, {0x50, 0x5b, 0xee}}, + {{0xff, 0x00, 0xff}, {0xf8, 0x1f}, {0xe3}, {0x4e, 0xd6, 0xe6}, {0x69, 0xcb, 0xdc}}, + {{0xff, 0xff, 0x00}, {0xff, 0xe0}, {0xfc}, {0xdb, 0x10, 0x8a}, {0xd0, 0x0a, 0x93}}, + {{0xff, 0xff, 0xff}, {0xff, 0xff}, {0xff}, {0xeb, 0x80, 0x80}, {0xeb, 0x80, 0x80}}, + + /* Arbitrary colors */ + {{0x00, 0x70, 0xc5}, {0x03, 0x98}, {0x0f}, {0x61, 0xb1, 0x4b}, {0x5e, 0xb5, 0x4d}}, + {{0x33, 0x8d, 0xd1}, {0x3c, 0x7a}, {0x33}, {0x7d, 0xa7, 0x56}, {0x7b, 0xab, 0x57}}, + {{0x66, 0xa9, 0xdc}, {0x6d, 0x5b}, {0x77}, {0x98, 0x9d, 0x61}, {0x96, 0xa0, 0x61}}, + {{0x7d, 0xd2, 0xf7}, {0x86, 0x9e}, {0x7b}, {0xb7, 0x99, 0x59}, {0xb3, 0x9d, 0x5a}}, + {{0x97, 0xdb, 0xf9}, {0x9e, 0xde}, {0x9b}, {0xc2, 0x94, 0x61}, {0xbf, 0x97, 0x62}}, + {{0xb1, 0xe4, 0xfa}, {0xb7, 0x3f}, {0xbf}, {0xcc, 0x8f, 0x69}, {0xca, 0x91, 0x69}}, + {{0x79, 0x29, 0xd2}, {0x79, 0x5a}, {0x67}, {0x4c, 0xc2, 0x9c}, {0x57, 0xbf, 0x96}}, + {{0x94, 0x54, 0xdb}, {0x9a, 0xbb}, {0x8b}, {0x6c, 0xb5, 0x97}, {0x75, 0xb3, 0x92}}, + {{0xaf, 0x7f, 0xe4}, {0xb3, 0xfc}, {0xaf}, {0x8c, 0xa8, 0x91}, {0x93, 0xa6, 0x8d}}, +}; + +static uint8_t line_in[WIDTH * 4]; +static uint8_t line_out[WIDTH * 4]; + +void test_conversion(const uint8_t *pix_in, uint32_t fourcc_in, size_t pix_in_step, + const uint8_t *pix_out, uint32_t fourcc_out, size_t pix_out_step, + void (*fn)(const uint8_t *in, uint8_t *out, uint16_t width)) +{ + size_t pix_in_size = video_bits_per_pixel(fourcc_in) / BITS_PER_BYTE; + size_t pix_out_size = video_bits_per_pixel(fourcc_out) / BITS_PER_BYTE; + bool done = false; + + /* Fill the input line as much as possible */ + for (size_t w = 0; w < WIDTH; w += pix_in_step) { + memcpy(&line_in[w * pix_in_size], pix_in, pix_in_size * pix_in_step); + } + + /* Perform the conversion to test */ + fn(line_in, line_out, WIDTH); + + printf("\n"); + + printf("out:"); + for (int i = 0; i < pix_out_step * pix_out_size; i++) { + printf(" %02x", line_out[i]); + } + printf(" |"); + pixel_print_buffer_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2, fourcc_out); + + printf("ref:"); + for (int i = 0; i < pix_out_step * pix_out_size; i++) { + printf(" %02x", pix_out[i]); + } + printf(" |"); + pixel_print_buffer_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2, fourcc_in); + + /* Scan the result against the reference output pixel to make sure it worked */ + for (size_t w = 0; w < WIDTH; w += pix_out_step) { + for (int i = 0; w * pix_out_size + i < (w + pix_out_step) * pix_out_size; i++) { + zassert_within(line_out[w * pix_out_size + i], pix_out[i], 9, + "at %u: value 0x%02x, reference 0x%02x", + i, line_out[w * pix_out_size + i], pix_out[i]); + } + + /* Make sure we visited that loop */ + done = true; + } + + zassert_true(done); +} + +ZTEST(lib_pixel_convert, test_pixel_convert_line) +{ + for (size_t i = 0; i < ARRAY_SIZE(reference_data); i++) { + /* The current color we are testing */ + const struct color_ref *ref = &reference_data[i]; + + /* Generate very small buffers out of the reference tables */ + const uint8_t rgb24[] = { + ref->rgb24[0], + ref->rgb24[1], + ref->rgb24[2], + }; + const uint8_t rgb565be[] = { + ref->rgb565[0], + ref->rgb565[1], + }; + const uint8_t rgb565le[] = { + ref->rgb565[1], + ref->rgb565[0], + }; + const uint8_t rgb332[] = { + ref->rgb332[0], + }; + const uint8_t yuv24_bt709[] = { + ref->yuv24_bt709[0], + ref->yuv24_bt709[1], + ref->yuv24_bt709[2], + }; + const uint8_t yuyv_bt709[] = { + ref->yuv24_bt709[0], + ref->yuv24_bt709[1], + ref->yuv24_bt709[0], + ref->yuv24_bt709[2], + }; + + printf("\nColor #%02x%02x%02x\n", ref->rgb24[0], ref->rgb24[1], ref->rgb24[2]); + + test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, rgb565be, + VIDEO_PIX_FMT_RGB565X, 1, &pixel_line_rgb24_to_rgb565be); + test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, rgb565le, + VIDEO_PIX_FMT_RGB565, 1, &pixel_line_rgb24_to_rgb565le); + test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, rgb332, + VIDEO_PIX_FMT_RGB332, 1, &pixel_line_rgb24_to_rgb332); + test_conversion(rgb565be, VIDEO_PIX_FMT_RGB565X, 1, rgb24, + VIDEO_PIX_FMT_RGB24, 1, &pixel_line_rgb565be_to_rgb24); + test_conversion(rgb565le, VIDEO_PIX_FMT_RGB565, 1, rgb24, + VIDEO_PIX_FMT_RGB24, 1, &pixel_line_rgb565le_to_rgb24); + test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, yuyv_bt709, + VIDEO_PIX_FMT_YUYV, 2, &pixel_line_rgb24_to_yuyv_bt709); + test_conversion(yuyv_bt709, VIDEO_PIX_FMT_YUYV, 2, rgb24, + VIDEO_PIX_FMT_RGB24, 1, &pixel_line_yuyv_to_rgb24_bt709); + test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, yuv24_bt709, + VIDEO_PIX_FMT_YUV24, 1, &pixel_line_rgb24_to_yuv24_bt709); + test_conversion(yuv24_bt709, VIDEO_PIX_FMT_YUV24, 1, rgb24, + VIDEO_PIX_FMT_RGB24, 1, &pixel_line_yuv24_to_rgb24_bt709); + test_conversion(yuv24_bt709, VIDEO_PIX_FMT_YUV24, 1, yuyv_bt709, + VIDEO_PIX_FMT_YUYV, 2, &pixel_line_yuv24_to_yuyv); + test_conversion(yuyv_bt709, VIDEO_PIX_FMT_YUYV, 2, yuv24_bt709, + VIDEO_PIX_FMT_YUV24, 1, &pixel_line_yuyv_to_yuv24); + } +} + +static uint8_t rgb24frame_in[WIDTH * HEIGHT * 3]; +static uint8_t rgb24frame_out[WIDTH * HEIGHT * 3]; + +ZTEST(lib_pixel_convert, test_pixel_convert_operation) +{ + struct pixel_image img; + int ret; + + /* Generate test input data */ + for (size_t i = 0; i < sizeof(rgb24frame_in); i++) { + rgb24frame_in[i] = i / 3; + } + + pixel_image_from_buffer(&img, rgb24frame_in, sizeof(rgb24frame_in), + WIDTH, HEIGHT, VIDEO_PIX_FMT_RGB24); + + printf("input:\n"); + pixel_image_print_truecolor(&img); + + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24); + zassert_ok(ret); + + /* Test the RGB24 <-> RGB565 conversion */ + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB565); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24); + zassert_ok(ret); + + /* Test the RGB24 <-> RGB565X conversion */ + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB565X); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24); + zassert_ok(ret); + + /* Test the RGB24 <-> YUV24 conversion */ + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUV24); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24); + zassert_ok(ret); + + /* Test the YUYV <-> YUV24 conversion */ + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUYV); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUV24); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUYV); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24); + zassert_ok(ret); + + pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out)); + + printf("output:\n"); + pixel_image_print_truecolor(&img); + + for (int i = 0; i < sizeof(rgb24frame_out); i++) { + /* Precision is not 100% as some conversions steps are lossy */ + zassert_within(rgb24frame_in[i], rgb24frame_out[i], ERROR_MARGIN, + "Testing position %u", i); + } +} + +ZTEST_SUITE(lib_pixel_convert, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/pixel/convert/testcase.yaml b/tests/lib/pixel/convert/testcase.yaml new file mode 100644 index 0000000000000..9377cc1fa5689 --- /dev/null +++ b/tests/lib/pixel/convert/testcase.yaml @@ -0,0 +1,9 @@ +tests: + libraries.pixel.convert: + tags: + - pixel + integration_platforms: + - qemu_cortex_m3 + - native_sim + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y diff --git a/tests/lib/pixel/kernel/CMakeLists.txt b/tests/lib/pixel/kernel/CMakeLists.txt new file mode 100644 index 0000000000000..a7fc55f4a48e5 --- /dev/null +++ b/tests/lib/pixel/kernel/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(lib_pixel_kernel) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/pixel/kernel/prj.conf b/tests/lib/pixel/kernel/prj.conf new file mode 100644 index 0000000000000..c5261e0c19f60 --- /dev/null +++ b/tests/lib/pixel/kernel/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ASSERT=y +CONFIG_PIXEL_LOG_LEVEL_DBG=y +CONFIG_ZTEST=y +CONFIG_PIXEL=y diff --git a/tests/lib/pixel/kernel/testcase.yaml b/tests/lib/pixel/kernel/testcase.yaml new file mode 100644 index 0000000000000..c307fe1f5c4b3 --- /dev/null +++ b/tests/lib/pixel/kernel/testcase.yaml @@ -0,0 +1,9 @@ +tests: + libraries.pixel.kernel: + tags: + - pixel + integration_platforms: + - qemu_cortex_m3 + - native_sim + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y diff --git a/tests/lib/pixel/resize/CMakeLists.txt b/tests/lib/pixel/resize/CMakeLists.txt new file mode 100644 index 0000000000000..463b06b146d36 --- /dev/null +++ b/tests/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(lib_pixel_resize) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/pixel/resize/prj.conf b/tests/lib/pixel/resize/prj.conf new file mode 100644 index 0000000000000..c5261e0c19f60 --- /dev/null +++ b/tests/lib/pixel/resize/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ASSERT=y +CONFIG_PIXEL_LOG_LEVEL_DBG=y +CONFIG_ZTEST=y +CONFIG_PIXEL=y diff --git a/tests/lib/pixel/resize/src/main.c b/tests/lib/pixel/resize/src/main.c new file mode 100644 index 0000000000000..3af81a12fd85d --- /dev/null +++ b/tests/lib/pixel/resize/src/main.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +#define WIDTH_IN 6 +#define HEIGHT_IN 10 +#define PITCH_IN (WIDTH_IN * 3) + +#define WIDTH_OUT 4 +#define HEIGHT_OUT 22 +#define PITCH_OUT (WIDTH_OUT * 3) + +#define ERROR_MARGIN 9 + +/* Input/output buffers */ +static uint8_t rgb24frame_in[WIDTH_IN * HEIGHT_IN * 3]; +static uint8_t rgb24frame_out[WIDTH_OUT * HEIGHT_OUT * 3]; + +static void test_resize(uint32_t fourcc) +{ + struct pixel_image img; + size_t w = WIDTH_OUT; + size_t h = HEIGHT_OUT; + size_t p = PITCH_OUT; + int ret; + + pixel_image_from_buffer(&img, rgb24frame_in, sizeof(rgb24frame_in), WIDTH_IN, HEIGHT_IN, + VIDEO_PIX_FMT_RGB24); + + printf("input:\n"); + pixel_image_print_truecolor(&img); + + ret = pixel_image_convert(&img, fourcc); + zassert_ok(ret); + ret = pixel_image_resize(&img, WIDTH_OUT, HEIGHT_OUT); + zassert_ok(ret); + ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24); + zassert_ok(ret); + ret = pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out)); + zassert_ok(ret); + + printf("output:\n"); + pixel_image_print_truecolor(&img); + + /* Test top left quadramt */ + zassert_within(rgb24frame_out[(0) * p + (0) * 3 + 0], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(0) * p + (0) * 3 + 1], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(0) * p + (0) * 3 + 2], 0x7f, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 - 1) * p + (w / 2 - 1) * 3 + 0], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 - 1) * p + (w / 2 - 1) * 3 + 1], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 - 1) * p + (w / 2 - 1) * 3 + 2], 0x7f, ERROR_MARGIN); + + /* Test bottom left quadrant */ + zassert_within(rgb24frame_out[(h - 1) * p + (0) * 3 + 0], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h - 1) * p + (0) * 3 + 1], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h - 1) * p + (0) * 3 + 2], 0x7f, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 + 1) * p + (w / 2 - 1) * 3 + 0], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 + 1) * p + (w / 2 - 1) * 3 + 1], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 + 1) * p + (w / 2 - 1) * 3 + 2], 0x7f, ERROR_MARGIN); + + /* Test top right quadrant */ + zassert_within(rgb24frame_out[(0) * p + (w - 1) * 3 + 0], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(0) * p + (w - 1) * 3 + 1], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(0) * p + (w - 1) * 3 + 2], 0x7f, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 - 1) * p + (w / 2 + 1) * 3 + 0], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 - 1) * p + (w / 2 + 1) * 3 + 1], 0x00, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 - 1) * p + (w / 2 + 1) * 3 + 2], 0x7f, ERROR_MARGIN); + + /* Test bottom right quadrant */ + zassert_within(rgb24frame_out[(h - 1) * p + (w - 1) * 3 + 0], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h - 1) * p + (w - 1) * 3 + 1], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h - 1) * p + (w - 1) * 3 + 2], 0x7f, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 + 1) * p + (w / 2 + 1) * 3 + 0], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 + 1) * p + (w / 2 + 1) * 3 + 1], 0xff, ERROR_MARGIN); + zassert_within(rgb24frame_out[(h / 2 + 1) * p + (w / 2 + 1) * 3 + 2], 0x7f, ERROR_MARGIN); +} + +ZTEST(lib_pixel_resize, test_pixel_resize_operation) +{ + /* Generate test input data */ + for (uint16_t h = 0; h < HEIGHT_IN; h++) { + for (uint16_t w = 0; w < WIDTH_IN; w++) { + rgb24frame_in[h * PITCH_IN + w * 3 + 0] = w < WIDTH_IN / 2 ? 0x00 : 0xff; + rgb24frame_in[h * PITCH_IN + w * 3 + 1] = h < HEIGHT_IN / 2 ? 0x00 : 0xff; + rgb24frame_in[h * PITCH_IN + w * 3 + 2] = 0x7f; + } + } + + test_resize(VIDEO_PIX_FMT_RGB24); + test_resize(VIDEO_PIX_FMT_RGB565); + test_resize(VIDEO_PIX_FMT_RGB565X); +} + +ZTEST_SUITE(lib_pixel_resize, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/pixel/resize/testcase.yaml b/tests/lib/pixel/resize/testcase.yaml new file mode 100644 index 0000000000000..a51a5a197ae68 --- /dev/null +++ b/tests/lib/pixel/resize/testcase.yaml @@ -0,0 +1,9 @@ +tests: + libraries.pixel.resize: + tags: + - pixel + integration_platforms: + - qemu_cortex_m3 + - native_sim + extra_configs: + - CONFIG_PIXEL_PRINT_NONE=y From d8f6eb0e28241859d76582cb1bab69dec525c808 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 26 Mar 2025 13:09:42 +0000 Subject: [PATCH 7/7] MAINTAINERS: Add a "Pixel library" area Add "Pixel library" area, which covers the newly introduced lib/pixel and associated tests and samples. Signed-off-by: Josuah Demangeon --- MAINTAINERS.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 488e60da433c1..232e5f84a6723 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -3203,6 +3203,21 @@ PHYTEC Platforms: labels: - "platform: PHYTEC" +Pixel library: + status: maintained + maintainers: + - josuah + files: + - include/zephyr/pixel/ + - lib/pixel/ + - tests/lib/pixel/ + - samples/lib/pixel/ + labels: + - "area: Pixel Library" + tests: + - libraries.pixel + description: Library for pixel manipulation and image processing + PMCI: status: maintained maintainers: