From 18a0e275d185e52eb9f3a7b9ff152b161077efcc Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 4 Mar 2025 00:23:01 +0000 Subject: [PATCH 1/5] lib: pixel: Introduce a pixel and image processing library The "pixel" library aims facilitating the implementation of all image processing tasks, such as pixel conversion, with a focus on low-resource environments. The processing functions scale down to per-pixel "kernels" to line-based conversion to full streams of several frames. Signed-off-by: Josuah Demangeon --- include/zephyr/pixel/bayer.h | 248 ++++++++++++++++++++ include/zephyr/pixel/formats.h | 417 +++++++++++++++++++++++++++++++++ include/zephyr/pixel/print.h | 97 ++++++++ include/zephyr/pixel/resize.h | 75 ++++++ include/zephyr/pixel/stats.h | 121 ++++++++++ include/zephyr/pixel/stream.h | 161 +++++++++++++ lib/CMakeLists.txt | 1 + lib/Kconfig | 3 + lib/pixel/CMakeLists.txt | 12 + lib/pixel/Kconfig | 13 + lib/pixel/bayer.c | 237 +++++++++++++++++++ lib/pixel/formats.c | 122 ++++++++++ lib/pixel/print.c | 355 ++++++++++++++++++++++++++++ lib/pixel/resize.c | 60 +++++ lib/pixel/stats.c | 274 ++++++++++++++++++++++ lib/pixel/stream.c | 62 +++++ 16 files changed, 2258 insertions(+) create mode 100644 include/zephyr/pixel/bayer.h create mode 100644 include/zephyr/pixel/formats.h create mode 100644 include/zephyr/pixel/print.h create mode 100644 include/zephyr/pixel/resize.h create mode 100644 include/zephyr/pixel/stats.h create mode 100644 include/zephyr/pixel/stream.h create mode 100644 lib/pixel/CMakeLists.txt create mode 100644 lib/pixel/Kconfig create mode 100644 lib/pixel/bayer.c create mode 100644 lib/pixel/formats.c create mode 100644 lib/pixel/print.c create mode 100644 lib/pixel/resize.c create mode 100644 lib/pixel/stats.c create mode 100644 lib/pixel/stream.c diff --git a/include/zephyr/pixel/bayer.h b/include/zephyr/pixel/bayer.h new file mode 100644 index 000000000000..e4d97391531d --- /dev/null +++ b/include/zephyr/pixel/bayer.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_BAYER_H +#define ZEPHYR_INCLUDE_PIXEL_BAYER_H + +#include + +#include + +/** + * Pixel streams definitions. + * @{ + */ + +void pixel_rggb8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGGB8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rggb8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_grbg8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_GRBG8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_grbg8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_bggr8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_BGGR8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_bggr8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_gbrg8stream_to_rgb24stream_3x3(struct pixel_stream *strm); + +#define PIXEL_STREAM_GBRG8_TO_RGB24_3X3(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_gbrg8stream_to_rgb24stream_3x3, \ + (width), (height), (width) * 1, 3 * (width) * 1) + +void pixel_rggb8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGGB8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rggb8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +void pixel_gbrg8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_GBRG8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_gbrg8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +void pixel_bggr8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_BGGR8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_bggr8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +void pixel_grbg8stream_to_rgb24stream_2x2(struct pixel_stream *strm); + +#define PIXEL_STREAM_GRBG8_TO_RGB24_2X2(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_grbg8stream_to_rgb24stream_2x2, \ + (width), (height), (width) * 1, 2 * (width) * 1) + +/** @} */ + +/** + * @brief Bayer to RGB24 conversion, 3x3 variant. + * + * @param i0 Buffer of the input row number 0 in bayer format (1 byte per pixel). + * @param i1 Buffer of the input row number 1 in bayer format (1 byte per pixel). + * @param i2 Buffer of the input row number 2 in bayer format (1 byte per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** Variant for GRBG8 format */ +void pixel_grbg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** Variant for BGGR8 format */ +void pixel_bggr8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** Variant for GBRG8 format */ +void pixel_gbrg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *rgb24, size_t width); +/** @} */ + +/** + * @brief Bayer to RGB24 conversion, 2x2 variant. + * + * @param i0 Buffer of the input row number 0 in bayer format (1 byte per pixel). + * @param i1 Buffer of the input row number 1 in bayer format (1 byte per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** Variant for RGGB8 bayer format */ +void pixel_rggb8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** Variant for GBRG8 bayer format */ +void pixel_gbrg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** Variant for BGGR8 bayer format */ +void pixel_bggr8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** Variant for GRBG8 bayer format */ +void pixel_grbg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *rgb24, + size_t width); +/** @} */ + +/** + * @brief Perform a RGGB8 to RGB24 conversion, 3x3 variant. + * + * @param rgr0 Row 0 with 3 bytes of RGGB8 data + * @param gbg1 Row 1 with 3 bytes of RGGB8 data + * @param rgr2 Row 2 with 3 bytes of RGGB8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rggb8_to_rgb24_3x3(const uint8_t rgr0[3], const uint8_t gbg1[3], + const uint8_t rgr2[3], uint8_t rgb24[3]) +{ + rgb24[0] = ((uint16_t)rgr0[0] + rgr0[2] + rgr2[0] + rgr2[2]) / 4; + rgb24[1] = ((uint16_t)rgr0[1] + gbg1[2] + gbg1[0] + rgr2[1]) / 4; + rgb24[2] = gbg1[1]; +} + +/** + * @brief Perform a BGGR8 to RGB24 conversion, 3x3 variant. + * + * @param bgb0 Row 0 with 3 bytes of BGGR8 data + * @param grg1 Row 1 with 3 bytes of BGGR8 data + * @param bgb2 Row 2 with 3 bytes of BGGR8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_bggr8_to_rgb24_3x3(const uint8_t bgb0[3], const uint8_t grg1[3], + const uint8_t bgb2[3], uint8_t rgb24[3]) +{ + rgb24[0] = grg1[1]; + rgb24[1] = ((uint16_t)bgb0[1] + grg1[2] + grg1[0] + bgb2[1]) / 4; + rgb24[2] = ((uint16_t)bgb0[0] + bgb0[2] + bgb2[0] + bgb2[2]) / 4; +} + +/** + * @brief Perform a GRBG8 to RGB24 conversion, 3x3 variant. + * + * @param grg0 Row 0 with 3 bytes of GRBG8 data + * @param bgb1 Row 1 with 3 bytes of GRBG8 data + * @param grg2 Row 2 with 3 bytes of GRBG8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_grbg8_to_rgb24_3x3(const uint8_t grg0[3], const uint8_t bgb1[3], + const uint8_t grg2[3], uint8_t rgb24[3]) +{ + rgb24[0] = ((uint16_t)grg0[1] + grg2[1]) / 2; + rgb24[1] = bgb1[1]; + rgb24[2] = ((uint16_t)bgb1[0] + bgb1[2]) / 2; +} + +/** + * @brief Perform a GBRG8 to RGB24 conversion, 3x3 variant. + * + * @param gbg0 Row 0 with 3 bytes of GBRG8 data + * @param rgr1 Row 1 with 3 bytes of GBRG8 data + * @param gbg2 Row 2 with 3 bytes of GBRG8 data + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_gbrg8_to_rgb24_3x3(const uint8_t gbg0[3], const uint8_t rgr1[3], + const uint8_t gbg2[3], uint8_t rgb24[3]) +{ + rgb24[0] = ((uint16_t)rgr1[0] + rgr1[2]) / 2; + rgb24[1] = rgr1[1]; + rgb24[2] = ((uint16_t)gbg0[1] + gbg2[1]) / 2; +} + +/** + * @brief Perform a RGGB8 to RGB24 conversion, 2x2 variant. + * + * @param r0 Red value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param g1 Green value from the bayer pattern. + * @param b0 Blue value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rggb8_to_rgb24_2x2(uint8_t r0, uint8_t g0, uint8_t g1, uint8_t b0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +/** + * @brief Perform a GBRG8 to RGB24 conversion, 2x2 variant. + * + * @param g1 Green value from the bayer pattern. + * @param b0 Blue value from the bayer pattern. + * @param r0 Red value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_gbrg8_to_rgb24_2x2(uint8_t g1, uint8_t b0, uint8_t r0, uint8_t g0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +/** + * @brief Perform a BGGR8 to RGB24 conversion, 2x2 variant. + * + * @param b0 Blue value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param g1 Green value from the bayer pattern. + * @param r0 Red value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_bggr8_to_rgb24_2x2(uint8_t b0, uint8_t g0, uint8_t g1, uint8_t r0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +/** + * @brief Perform a GRBG8 to RGB24 conversion, 2x2 variant. + * + * @param g1 Green value from the bayer pattern. + * @param b0 Blue value from the bayer pattern. + * @param r0 Red value from the bayer pattern. + * @param g0 Green value from the bayer pattern. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_grbg8_to_rgb24_2x2(uint8_t g1, uint8_t r0, uint8_t b0, uint8_t g0, + uint8_t rgb24[3]) +{ + rgb24[0] = r0; + rgb24[1] = ((uint16_t)g0 + g1) / 2; + rgb24[2] = b0; +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_BAYER_H */ diff --git a/include/zephyr/pixel/formats.h b/include/zephyr/pixel/formats.h new file mode 100644 index 000000000000..1e9759e8e39a --- /dev/null +++ b/include/zephyr/pixel/formats.h @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_FORMATS_H +#define ZEPHYR_INCLUDE_PIXEL_FORMATS_H + +#include +#include + +#include +#include +#include + +/** + * Pixel stream definitions. + * @{ + */ + +void pixel_rgb24stream_to_rgb565lestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565LE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565lestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_rgb565bestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565BE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565bestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_rgb565lestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565LE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565lestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_rgb565bestream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_RGB565BE(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_rgb565bestream, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb565lestream_to_rgb24stream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB565LE_TO_RGB24(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb565lestream_to_rgb24stream, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_rgb565bestream_to_rgb24stream(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB565BE_TO_RGB24(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb565bestream_to_rgb24stream, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_yuyvstream_to_rgb24stream_bt601(struct pixel_stream *strm); + +#define PIXEL_STREAM_YUYV_TO_RGB24_BT601(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_yuyvstream_to_rgb24stream_bt601, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_yuyvstream_to_rgb24stream_bt709(struct pixel_stream *strm); + +#define PIXEL_STREAM_YUYV_TO_RGB24_BT709(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_yuyvstream_to_rgb24stream_bt709, \ + (width), (height), (width) * 2, 1 * (width) * 2) + +void pixel_rgb24stream_to_yuyvstream_bt601(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_YUYV_BT601(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_yuyvstream_bt601, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +void pixel_rgb24stream_to_yuyvstream_bt709(struct pixel_stream *strm); + +#define PIXEL_STREAM_RGB24_TO_YUYV_BT709(name, width, height) \ + PIXEL_STREAM_DEFINE(name, pixel_rgb24stream_to_yuyvstream_bt709, \ + (width), (height), (width) * 3, 1 * (width) * 3) + +/** @} */ + +/** + * @brief RGB565 little-endian to RGB24 conversion. + * + * @param rgb565le Buffer of the input row in RGB565 little-endian format (2 bytes per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb565leline_to_rgb24line(const uint8_t *rgb565le, uint8_t *rgb24, uint16_t width); + +/** + * @brief RGB565 big-endian to RGB24 conversion. + * + * @param rgb565be Buffer of the input row in RGB565 big-endian format (2 bytes per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb565beline_to_rgb24line(const uint8_t *rgb565be, uint8_t *rgb24, uint16_t width); + +/** + * @brief RGB24 to RGB565 little-endian conversion. + * + * @param rgb24 Buffer of the input row in RGB24 format (3 bytes per pixel). + * @param rgb565le Buffer of the output row in RGB565 little-endian format (2 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb24line_to_rgb565leline(const uint8_t *rgb24, uint8_t *rgb565le, uint16_t width); + +/** + * @brief RGB24 to RGB565 big-endian conversion. + * + * @param rgb24 Buffer of the input row in RGB24 format (3 bytes per pixel). + * @param rgb565be Buffer of the output row in RGB565 big-endian format (2 bytes per pixel). + * @param width Width of the lines in number of pixels. + */ +void pixel_rgb24line_to_rgb565beline(const uint8_t *rgb24, uint8_t *rgb565be, uint16_t width); + +/** + * @brief YUYV to RGB24 conversion. + * + * @param yuyv Buffer of the input row in YUYV format (2 bytes per pixel). + * @param rgb24 Buffer of the output row in RGB24 format (3 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** BT.601 variant */ +void pixel_yuyvline_to_rgb24line_bt601(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width); +/** BT.709 variant */ +void pixel_yuyvline_to_rgb24line_bt709(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width); +/** @} */ + +/** + * @brief RGB24 to YUYV conversion. + * + * @param rgb24 Buffer of the input row in RGB24 format (3 bytes per pixel). + * @param yuyv Buffer of the output row in YUYV format (2 bytes per pixel). + * @param width Width of the lines in number of pixels. + * @{ + */ +/** BT.601 variant */ +void pixel_rgb24line_to_yuyvline_bt601(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width); +/** BT.709 variant */ +void pixel_rgb24line_to_yuyvline_bt709(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width); +/** @} */ + +/* These constants are meant to be inlined by the compiler */ + +#define Q21(val) ((int32_t)((val) * (1 << 21))) + +static const int32_t pixel_yuv_to_r_bt709[] = {Q21(+1.0000), Q21(+0.0000), Q21(+1.5748)}; +static const int32_t pixel_yuv_to_g_bt709[] = {Q21(+1.0000), Q21(-0.1873), Q21(-0.4681)}; +static const int32_t pixel_yuv_to_b_bt709[] = {Q21(+1.0000), Q21(+1.8556), Q21(+0.0000)}; + +static const int32_t pixel_yuv_to_r_bt601[] = {Q21(+1.0000), Q21(+0.0000), Q21(+1.4020)}; +static const int32_t pixel_yuv_to_g_bt601[] = {Q21(+1.0000), Q21(-0.3441), Q21(-0.7141)}; +static const int32_t pixel_yuv_to_b_bt601[] = {Q21(+1.0000), Q21(+1.7720), Q21(+0.0000)}; + +static const int32_t pixel_rgb_to_y_bt709[] = {Q21(+0.2126), Q21(+0.7152), Q21(+0.0722), 0}; +static const int32_t pixel_rgb_to_u_bt709[] = {Q21(-0.1146), Q21(-0.3854), Q21(+0.5000), 128}; +static const int32_t pixel_rgb_to_v_bt709[] = {Q21(+0.5000), Q21(-0.4542), Q21(-0.0468), 128}; + +static const int32_t pixel_rgb_to_y_bt601[] = {Q21(+0.2570), Q21(+0.5040), Q21(+0.0980), 16}; +static const int32_t pixel_rgb_to_u_bt601[] = {Q21(-0.1480), Q21(-0.2910), Q21(+0.4390), 128}; +static const int32_t pixel_rgb_to_v_bt601[] = {Q21(+0.4390), Q21(-0.3680), Q21(-0.0710), 128}; + +static const int32_t pixel_rgb_to_y_jpeg[] = {Q21(+0.2990), Q21(+0.5870), Q21(+0.1140), 0}; +static const int32_t pixel_rgb_to_u_jpeg[] = {Q21(-0.1690), Q21(-0.3310), Q21(+0.5000), 128}; +static const int32_t pixel_rgb_to_v_jpeg[] = {Q21(+0.5000), Q21(-0.4190), Q21(-0.0810), 128}; + +#undef Q21 + +static inline uint8_t pixel_yuv24_to_r8g8b8(uint8_t y, uint8_t u, uint8_t v, const int32_t coeff[3]) +{ + int32_t y21 = coeff[0] * y; + int32_t u21 = coeff[1] * ((int32_t)u - 128); + int32_t v21 = coeff[2] * ((int32_t)v - 128); + int32_t x16 = (y21 + u21 + v21) >> 21; + + return CLAMP(x16, 0, 0xff); +} + +static inline uint8_t pixel_rgb24_to_y8u8v8(const uint8_t rgb24[3], const int32_t coeff[4]) +{ + int16_t x16 = (coeff[0] * rgb24[0] + coeff[1] * rgb24[1] + coeff[2] * rgb24[2]) >> 21; + + return CLAMP(x16 + coeff[3], 0, 0xff); +} + +/** + * @brief Convert a pixel from YUV24 format to RGB24 using the BT.601 profile. + * + * @param y The 8-bit luma channel value (aka Y). + * @param u The 8-bit chroma channel value (aka V or Cb). + * @param v The 8-bit chroma channel value (aka V or Cr). + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_yuv24_to_rgb24_bt601(uint8_t y, uint8_t u, uint8_t v, uint8_t rgb24[3]) +{ + rgb24[0] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_r_bt601); + rgb24[1] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_g_bt601); + rgb24[2] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_b_bt601); +} + +/** + * @brief Convert a pixel from YUV24 format to RGB24 using the BT.709 profile. + * + * @param y The 8-bit luma channel value (aka Y). + * @param u The 8-bit chroma channel value (aka V or Cb). + * @param v The 8-bit chroma channel value (aka V or Cr). + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_yuv24_to_rgb24_bt709(uint8_t y, uint8_t u, uint8_t v, uint8_t rgb24[3]) +{ + rgb24[0] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_r_bt709); + rgb24[1] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_g_bt709); + rgb24[2] = pixel_yuv24_to_r8g8b8(y, u, v, pixel_yuv_to_b_bt709); +} + +/** + * @brief Convert two YUYV pixels to two RGB24 pixels using the BT.601 profile. + * + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + */ +static inline void pixel_yuyv_to_rgb24x2_bt601(const uint8_t yuyv[4], uint8_t rgb24x2[6]) +{ + pixel_yuv24_to_rgb24_bt601(yuyv[0], yuyv[1], yuyv[3], &rgb24x2[0]); + pixel_yuv24_to_rgb24_bt601(yuyv[2], yuyv[1], yuyv[3], &rgb24x2[3]); +} + +/** + * @brief Convert two YUYV pixels to two RGB24 pixels. + * + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + */ +static inline void pixel_yuyv_to_rgb24x2_bt709(const uint8_t yuyv[4], uint8_t rgb24x2[6]) +{ + pixel_yuv24_to_rgb24_bt709(yuyv[0], yuyv[1], yuyv[3], &rgb24x2[0]); + pixel_yuv24_to_rgb24_bt709(yuyv[2], yuyv[1], yuyv[3], &rgb24x2[3]); +} + +/** + * @brief Convert a pixel from RGB24 to Y8 format (Y channel of YUV24) using the BT.601 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return The Y channel for this RGB value, also known as "perceived lightness". + */ +static inline uint8_t pixel_rgb24_to_y8_bt601(const uint8_t rgb24[3]) +{ + return pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt601); +} + +/** + * @brief Convert a pixel from RGB24 to Y8 format (Y channel of YUV24) using the BT.709 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return The Y channel for this RGB value, also known as "perceived lightness". + */ +static inline uint8_t pixel_rgb24_to_y8_bt709(const uint8_t rgb24[3]) +{ + return pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt709); +} + +/** + * @brief Convert a pixel from RGB24 format to YUV24 using the BT.601 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param yuv24 The pixel in YUV24 format: {Y, U, V} + */ +static inline void pixel_rgb24_to_yuv24_bt601(const uint8_t rgb24[3], uint8_t yuv24[3]) +{ + yuv24[0] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt601); + yuv24[1] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_u_bt601); + yuv24[2] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_v_bt601); +} + +/** + * @brief Convert a pixel from RGB24 format to YUV24 using the BT.709 profile. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param yuv24 The pixel in YUV24 format: {Y, U, V} + */ +static inline void pixel_rgb24_to_yuv24_bt709(const uint8_t rgb24[3], uint8_t yuv24[3]) +{ + yuv24[0] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_y_bt709); + yuv24[1] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_u_bt709); + yuv24[2] = pixel_rgb24_to_y8u8v8(rgb24, pixel_rgb_to_v_bt709); +} + +/** + * @brief Convert two pixels from RGB24 format to YUYV format using the BT.601 profile. + * + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + */ +static inline void pixel_rgb24x2_to_yuyv_bt601(const uint8_t rgb24x2[6], uint8_t yuyv[4]) +{ + yuyv[0] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_y_bt601); + yuyv[1] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_u_bt601); + yuyv[2] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_y_bt601); + yuyv[3] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_v_bt601); +} + +/** + * @brief Convert two pixels from RGB24 format to YUYV format using the BT.709 profile. + * + * @param rgb24x2 The 2 pixels in RGB24 format: {R, G, B, R, G, B} + * @param yuyv The 2 pixels in YUYV format: {Y, U, Y, V} + */ +static inline void pixel_rgb24x2_to_yuyv_bt709(const uint8_t rgb24x2[6], uint8_t yuyv[4]) +{ + yuyv[0] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_y_bt709); + yuyv[1] = pixel_rgb24_to_y8u8v8(&rgb24x2[0], pixel_rgb_to_u_bt709); + yuyv[2] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_y_bt709); + yuyv[3] = pixel_rgb24_to_y8u8v8(&rgb24x2[3], pixel_rgb_to_v_bt709); +} + +/** + * @brief Convert a pixel from RGB565 format to RGB24. + * + * @param rgb565 The pixel in RGB565 format, in CPU-native endianness + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rgb565_to_rgb24(uint16_t rgb565, uint8_t rgb24[3]) +{ + rgb24[0] = rgb565 >> (0 + 6 + 5) << 3; + rgb24[1] = rgb565 >> (0 + 0 + 5) << 2; + rgb24[2] = rgb565 >> (0 + 0 + 0) << 3; +} + +/** + * @brief Convert a pixel from RGB565 format to RGB24. + * + * @param rgb565le The pixel in RGB565 format, in little-endian. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rgb565le_to_rgb24(const uint8_t rgb565le[2], uint8_t rgb24[3]) +{ + pixel_rgb565_to_rgb24(sys_le16_to_cpu(*(uint16_t *)rgb565le), rgb24); +} + +/** + * @brief Convert a pixel from RGB565 big-endian format to RGB24. + * + * @param rgb565be The pixel in RGB565 format, in big-endian. + * @param rgb24 The pixel in RGB24 format: {R, G, B} + */ +static inline void pixel_rgb565be_to_rgb24(const uint8_t rgb565be[2], uint8_t rgb24[3]) +{ + pixel_rgb565_to_rgb24(sys_be16_to_cpu(*(uint16_t *)rgb565be), rgb24); +} + +/** + * @brief Convert a pixel from RGB24 format to RGB565. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return The pixel in RGB565 format, in CPU-native endianness + */ +static inline uint16_t pixel_rgb24_to_rgb565(const uint8_t rgb24[3]) +{ + uint16_t rgb565 = 0; + + rgb565 |= (uint16_t)rgb24[0] >> 3 << (0 + 6 + 5); + rgb565 |= (uint16_t)rgb24[1] >> 2 << (0 + 0 + 5); + rgb565 |= (uint16_t)rgb24[2] >> 3 << (0 + 0 + 0); + + return rgb565; +} + +/** + * @brief Convert a pixel from RGB24 format to RGB565 little-endian. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param rgb565le The pixel in RGB565 format, in little-endian. + */ +static inline void pixel_rgb24_to_rgb565le(const uint8_t rgb24[3], uint8_t rgb565le[2]) +{ + *(uint16_t *)rgb565le = sys_cpu_to_le16(pixel_rgb24_to_rgb565(rgb24)); +} + +/** + * @brief Convert a pixel from RGB24 format to RGB565 big-endian. + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @param rgb565be The pixel in RGB565 format, in big-endian. + */ +static inline void pixel_rgb24_to_rgb565be(const uint8_t rgb24[3], uint8_t rgb565be[2]) +{ + *(uint16_t *)rgb565be = sys_cpu_to_be16(pixel_rgb24_to_rgb565(rgb24)); +} + +/** + * @brief Convert a pixel from RGB24 to ANSI 8-bit format (color cube of 6x6x6 colors). + * + * @param rgb24 The pixel in RGB24 format: {R, G, B} + * @return Colors in ANSI escape code color format packed in a single 8-bit value. + */ +static inline uint8_t pixel_rgb24_to_256color(const uint8_t rgb24[3]) +{ + return 16 + rgb24[0] * 6 / 256 * 36 + rgb24[1] * 6 / 256 * 6 + rgb24[2] * 6 / 256 * 1; +} + +/** + * @brief Convert a pixel from GRAY8 to ANSI 8-bit black and white format (grayscale of 24 values) + * + * @param gray8 The pixel in GRAY8 format: 8-bit value. + * @return Colors in ANSI escape code color format packed in a single 8-bit value. + */ +static inline uint8_t pixel_gray8_to_256color(uint8_t gray8) +{ + return 232 + gray8 * 24 / 256; +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_FORMATS_H */ diff --git a/include/zephyr/pixel/print.h b/include/zephyr/pixel/print.h new file mode 100644 index 000000000000..d28f7adfeec1 --- /dev/null +++ b/include/zephyr/pixel/print.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_PRINT_H +#define ZEPHYR_INCLUDE_PIXEL_PRINT_H + +#include +#include + +/** + * Printing images to the terminal for debug and quick insights purpose. + * + * The 256COLOR variants use 256COLOR 8-bit RGB format, lower quality, more compact and supported. + * The TRUECOLOR variants use true 24-bit RGB24 colors, available on a wide range of terminals. + * + * @param buf Input buffer to display in the terminal. + * @param size Size of the input buffer in bytes. + * @param width Number of pixel of the input buffer in width + * @param height Max number of rows to print + * @{ + */ +/** Variant for RGB24 input using 256COLOR escape codes. */ +void pixel_print_rgb24frame_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB24 input using TRUECOLOR escape codes. */ +void pixel_print_rgb24frame_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565LE input using 256COLOR escape codes. */ +void pixel_print_rgb565leframe_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565LE input using TRUECOLOR escape codes. */ +void pixel_print_rgb565leframe_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565BE input using 256COLOR escape codes. */ +void pixel_print_rgb565beframe_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for RGB565BE input using TRUECOLOR escape codes. */ +void pixel_print_rgb565beframe_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT601 profile using 256COLOR escape codes. */ +void pixel_print_yuyvframe_bt601_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT601 profile using TRUECOLOR escape codes. */ +void pixel_print_yuyvframe_bt601_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT709 profile using 256COLOR escape codes. */ +void pixel_print_yuyvframe_bt709_256color(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** Variant for YUYV input with VT709 profile using TRUECOLOR escape codes. */ +void pixel_print_yuyvframe_bt709_truecolor(const uint8_t *buf, size_t size, uint16_t width, + uint16_t height); +/** @} */ + +/** + * Printing images to the terminal for debug and quick insights purpose. + * + * The 256COLOR variants use 256COLOR 8-bit RGB format, lower quality, more compact and supported. + * The TRUECOLOR variants use true 24-bit RGB24 colors, available on a wide range of terminals. + * + * @param buf Input buffer to display in the terminal. + * @param size Size of the input buffer in bytes. + * @param width Number of pixel of the input buffer in width + * @param height Max number of rows to print + * @{ + */ +/* RAW8 pixel format variant */ +void pixel_print_raw8frame_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/* RGB24 pixel format variant */ +void pixel_print_rgb24frame_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/* RGB565 pixel format variant */ +void pixel_print_rgb565frame_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/* YUYV pixel format variant */ +void pixel_print_yuyvframe_hex(const uint8_t *buf, size_t size, uint16_t width, uint16_t height); +/** @} */ + +/** + * Printing RGB histograms to the terminal for debug and quick insights purpose. + * + * @param rgb24hist Buffer storing 3 histograms one after the other, for the R, G, B channels. + * @param size Total number of buckets in total contained within @p rgb24hist all channels included. + * @param height Desired height of the chart in pixels. + */ +void pixel_print_rgb24hist(const uint16_t *rgb24hist, size_t size, uint16_t height); + +/** + * Printing Y histograms to the terminal for debug and quick insights purpose. + * + * @param y8hist Buffer storing the histogram for the Y (luma) channel. + * @param size Total number of buckets in total contained within @p hist. + * @param height Desired height of the chart in pixels. + */ +void pixel_print_y8hist(const uint16_t *y8hist, size_t size, uint16_t height); + +#endif /* ZEPHYR_INCLUDE_PIXEL_PRINT_H */ diff --git a/include/zephyr/pixel/resize.h b/include/zephyr/pixel/resize.h new file mode 100644 index 000000000000..10b4f6d2a928 --- /dev/null +++ b/include/zephyr/pixel/resize.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_RESIZE_H +#define ZEPHYR_INCLUDE_PIXEL_RESIZE_H + +#include + +/** + * @brief Step-by-step increment two indexes preserving their proportion. + * + * @param src_index Position to sample on the input image, adjusted to preserve the same ratio + * from @p src_width to @p dst_width and from @p src_index to @p dst_index. + * @param src_width Width of the input image to sample. + * @param dst_index Index through the destination image, always incremented by one. + * @param dst_width Targeted width for the resized output. + * @param debt Accumulator telling how much overshooting was done in either direction. + * It must be initialized to zero on first call. + */ +void pixel_subsample_step(size_t *src_index, size_t src_width, size_t *dst_index, size_t dst_width, + int32_t *debt); + +/** + * @brief Resize a line of pixel by compressing/stretching it horizontally. + * + * Subsampling is used as algorithm to compute the new pixels: fast but low quality. + * + * @param src_buf Input buffer to resize + * @param src_width Number of pixels to resize to a different format. + * @param dst_buf Output buffer in which the stretched/compressed is stored. + * @param dst_width Number of pixels in the output buffer. + * @{ + */ +/** RGB24 variant */ +void pixel_subsample_rgb24line(const uint8_t *src_buf, size_t src_width, uint8_t *dst_buf, + size_t dst_width); +/** RGB565 variant */ +void pixel_subsample_rgb565line(const uint8_t *src_buf, size_t src_width, uint8_t *dst_buf, + size_t dst_width); +/** YUYV variant */ +void pixel_subsample_yuyvline(const uint8_t *src_buf, size_t src_width, uint8_t *dst_buf, + size_t dst_width); +/** @} */ + +/** + * @brief Resize a buffer of pixels by compressing/stretching it horizontally and vertically. + * + * Subsampling is used as algorithm to compute the new pixels: fast but low quality. + * + * The aspect ratio is chosen by the width and height parameters, and preserving the proportions is + * a choice that the user can optionally make. + * + * @param src_buf Input buffer to resize + * @param src_width Width of the input in number of pixels. + * @param src_height Height of the input in number of pixels. + * @param dst_buf Output buffer in which the stretched/compressed is stored. + * @param dst_width Width of the outputin number of pixels. + * @param dst_height Height of the outputin number of pixels. + * @{ + */ +/* RGB24 variant */ +void pixel_subsample_rgb24frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height); +/* RGB565 variant */ +void pixel_subsample_rgb565frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height); +/* YUYV variant */ +void pixel_subsample_yuyvframe(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height); +/** @} */ + +#endif /* ZEPHYR_INCLUDE_PIXEL_RESIZE_H */ diff --git a/include/zephyr/pixel/stats.h b/include/zephyr/pixel/stats.h new file mode 100644 index 000000000000..9a1e901ec9d0 --- /dev/null +++ b/include/zephyr/pixel/stats.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_STATS_H +#define ZEPHYR_INCLUDE_PIXEL_STATS_H + +#include + +/** + * @brief Collect red, gren, blue channel averages of all pixels in an RGB24 frame. + * + * @param buf Buffer of pixels in RGB24 format (3 bytes per pixel) to collect the statistics from.. + * @param size Size of this input buffer. + * @param rgb24avg The channel averages stored as an RGB24 pixel. + * @param nval The number of values to collect in order to perform the statistics. + */ +void pixel_rgb24frame_to_rgb24avg(const uint8_t *buf, size_t size, uint8_t rgb24avg[3], + uint16_t nval); + +/** + * @brief Collect red, gren, blue channel averages of all pixels in a frame. + * + * @param buf Buffer of pixels in bayer format (1 byte per pixel) to collect the statistics from.. + * @param size Size of this input buffer. + * @param width Width of the lines in number of pixels. + * @param rgb24avg The channel averages stored as an RGB24 pixel. + * @param nval The number of values to collect in order to perform the statistics. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** Variant for BGGR8 format */ +void pixel_bggr8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** Variant for GBRG8 format */ +void pixel_gbrg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** Variant for GRBG8 format */ +void pixel_grbg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval); +/** @} */ + +/** + * @brief Collect an histogram for each of the red, gren, blue channels of an RGB24 frame. + * + * @param buf Buffer of pixels in RGB24 format (3 bytes per pixel) to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param rgb24hist Buffer storing 3 histograms one after the other, for the R, G, B channels. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + */ +void pixel_rgb24frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t *rgb24hist, + size_t hist_size, uint16_t nval); + +/** + * @brief Collect an histogram for each of the red, gren, blue channels of a frame. + * + * @param buf Buffer of pixels to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param width Width of the lines in number of pixels. + * @param rgb24hist Buffer storing 3 histograms one after the other, for the R, G, B channels. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** Variant for GBRG8 format */ +void pixel_gbrg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** Variant for BGGR8 format */ +void pixel_bggr8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** Variant for GRBG8 format */ +void pixel_grbg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval); +/** @} */ + +/** + * @brief Collect an histogram for the Y channel, obtained from the pixel values of the image. + * + * @param buf Buffer of pixels in RGB24 format (3 bytes per pixel) to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param y8hist Buffer storing the histogram for the Y (luma) channel. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + */ +void pixel_rgb24frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t *y8hist, + size_t hist_size, uint16_t nval); + +/** + * @brief Collect an histogram for the Y channel, obtained from the pixel values of the image. + * + * @param buf Buffer of pixels in bayer format (1 byte per pixel) to collect the statistics from.. + * @param buf_size Size of this input buffer. + * @param width Width of the lines in number of pixels. + * @param y8hist Buffer storing the histogram for the Y (luma) channel. + * @param hist_size Total number of buckets in the histogram, all channels included. + * @param nval The number of values to collect in order to perform the statistics. + * @{ + */ +/** Variant for RGGB8 format */ +void pixel_rggb8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** Variant for GBRG8 format */ +void pixel_gbrg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** Variant for BGGR8 format */ +void pixel_bggr8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** Variant for GRBG8 format */ +void pixel_grbg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval); +/** @} */ + +#endif /* ZEPHYR_INCLUDE_PIXEL_STATS_H */ diff --git a/include/zephyr/pixel/stream.h b/include/zephyr/pixel/stream.h new file mode 100644 index 000000000000..57b31773d675 --- /dev/null +++ b/include/zephyr/pixel/stream.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PIXEL_STREAM_H +#define ZEPHYR_INCLUDE_PIXEL_STREAM_H + +#include +#include +#include +#include + +struct pixel_stream { + /* Display name, useful for debugging the stream */ + uint8_t *name; + /* Ring buffer that keeps track of the position in bytes */ + struct ring_buf ring; + /* Number of bytes returned while asking for an input or output line */ + size_t pitch; + /* Current position within the frame */ + uint16_t line_offset; + /* Total number of lines in the frame */ + uint16_t height; + /* Connection to the next element of the stream */ + struct pixel_stream *next; + /* Function that performs the I/O */ + void (*run)(struct pixel_stream *strm); + /* Timestamp since the strm started working in CPU cycles */ + uint32_t start_time; + /* Total time spent working in this strm through the stream in CPU cycles */ + uint32_t total_time; +}; + +#define PIXEL_STREAM_DEFINE(_name, _run, _width, _height, _pitch, _bufsize) \ + struct pixel_stream _name = { \ + .name = "[" #_name " " #_run " " STRINGIFY(_width) "x" STRINGIFY(_height) "]", \ + .ring = RING_BUF_INIT((uint8_t [_bufsize]) {0}, _bufsize), \ + .pitch = (_pitch), \ + .height = (_height), \ + .run = (_run), \ + } + +/** + * @brief Load a buffer into the stream. + * + * The parameters such as line pitch or image height are to be configured inside each individual + * strm before calling this function. + * + * @param strm Pipeline strm into which load the buffer one pitch worth of data at a time. + * @param buf Buffer of data to load into the stream. + * @param size Total of bytes in this buffer. + */ +void pixel_stream_load(struct pixel_stream *strm, const uint8_t *buf, size_t size); + +static inline uint8_t *pixel_stream_peek_input_line(struct pixel_stream *strm) +{ + uint8_t *line; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + + size = ring_buf_get_claim(&strm->ring, &line, strm->pitch); + __ASSERT_NO_MSG(size == strm->pitch); + + return line; +} + +static inline const uint8_t *pixel_stream_get_input_lines(struct pixel_stream *strm, size_t nb) +{ + uint8_t *lines; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + + strm->line_offset += nb; + __ASSERT_NO_MSG(strm->line_offset <= strm->height); + + size = ring_buf_get_claim(&strm->ring, &lines, strm->pitch * nb); + ring_buf_get_finish(&strm->ring, size); + __ASSERT(size == strm->pitch * nb, + "%s asked for %zu input bytes, obtained only %u, total used %u, total free %u", + strm->name, strm->pitch * nb, size, ring_buf_size_get(&strm->ring), + ring_buf_space_get(&strm->ring)); + + return lines; +} + +static inline const uint8_t *pixel_stream_get_input_line(struct pixel_stream *strm) +{ + return pixel_stream_get_input_lines(strm, 1); +} + +static inline const uint8_t *pixel_stream_get_all_input(struct pixel_stream *strm) +{ + uint8_t *remaining; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + + strm->line_offset = strm->height; + __ASSERT_NO_MSG(strm->line_offset <= strm->height); + + size = ring_buf_get_claim(&strm->ring, &remaining, ring_buf_capacity_get(&strm->ring)); + ring_buf_get_finish(&strm->ring, size); + __ASSERT(size == ring_buf_capacity_get(&strm->ring), + "Could not dequeue the entire input buffer of %s, %u used, %u free", + strm->name, ring_buf_size_get(&strm->ring), ring_buf_space_get(&strm->ring)); + + return remaining; +} + +static inline uint8_t *pixel_stream_get_output_lines(struct pixel_stream *strm, size_t nb) +{ + uint8_t *lines; + uint32_t size; + + __ASSERT_NO_MSG(strm != NULL); + __ASSERT_NO_MSG(strm->next != NULL); + + size = ring_buf_put_claim(&strm->next->ring, &lines, strm->next->pitch * nb); + ring_buf_put_finish(&strm->next->ring, size); + __ASSERT(size == strm->next->pitch, + "%s asked for %zu output bytes, only have %u, total used %u, total free %u", + strm->name, strm->pitch * nb, size, ring_buf_size_get(&strm->ring), + ring_buf_space_get(&strm->ring)); + + return lines; +} + +static inline uint8_t *pixel_stream_get_output_line(struct pixel_stream *strm) +{ + return pixel_stream_get_output_lines(strm, 1); +} + +static inline void pixel_stream_done(struct pixel_stream *strm) +{ + __ASSERT_NO_MSG(strm != NULL); + + /* Ignore any "peek" operation done previouslyl */ + ring_buf_get_finish(&strm->ring, 0); + ring_buf_put_finish(&strm->ring, 0); + + /* Flush the timestamp to the counter */ + strm->total_time += strm->start_time - k_cycle_get_32(); + strm->start_time = k_cycle_get_32(); + + if (strm->next != NULL && strm->next->run && ring_buf_space_get(&strm->next->ring) == 0) { + /* Start the counter of the next stream */ + strm->next->start_time = k_cycle_get_32(); + + /* Run the next strm */ + strm->next->run(strm->next); + + /* Resuming to this strm, upgrade the start time */ + strm->start_time = k_cycle_get_32(); + } +} + +#endif /* ZEPHYR_INCLUDE_PIXEL_STREAM_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b945968c8fbe..d8de2732c0f5 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(heap) add_subdirectory(mem_blocks) add_subdirectory_ifdef(CONFIG_NET_BUF net_buf) add_subdirectory(os) +add_subdirectory_ifdef(CONFIG_PIXEL pixel) add_subdirectory(utils) add_subdirectory_ifdef(CONFIG_SMF smf) add_subdirectory_ifdef(CONFIG_OPENAMP open-amp) diff --git a/lib/Kconfig b/lib/Kconfig index ae97399b1995..bf5ae2371ee6 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -19,6 +19,8 @@ source "lib/net_buf/Kconfig" source "lib/os/Kconfig" +source "lib/pixel/Kconfig" + source "lib/posix/Kconfig" source "lib/open-amp/Kconfig" @@ -32,4 +34,5 @@ source "lib/runtime/Kconfig" source "lib/utils/Kconfig" source "lib/uuid/Kconfig" + endmenu diff --git a/lib/pixel/CMakeLists.txt b/lib/pixel/CMakeLists.txt new file mode 100644 index 000000000000..a80133e8181e --- /dev/null +++ b/lib/pixel/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +# zephyr-keep-sorted-start +zephyr_library_sources(bayer.c) +zephyr_library_sources(formats.c) +zephyr_library_sources(print.c) +zephyr_library_sources(resize.c) +zephyr_library_sources(stats.c) +zephyr_library_sources(stream.c) +# zephyr-keep-sorted-stop diff --git a/lib/pixel/Kconfig b/lib/pixel/Kconfig new file mode 100644 index 000000000000..33ea566a0443 --- /dev/null +++ b/lib/pixel/Kconfig @@ -0,0 +1,13 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +menuconfig PIXEL + bool "Pixel and Image Manipulation Library" + +if PIXEL + +module = PIXEL +module-str = pixel +source "subsys/logging/Kconfig.template.log_config" + +endif diff --git a/lib/pixel/bayer.c b/lib/pixel/bayer.c new file mode 100644 index 000000000000..7fdd41f43c95 --- /dev/null +++ b/lib/pixel/bayer.c @@ -0,0 +1,237 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#define FOLD_L_3X3(l0, l1, l2) \ + { \ + {l0[1], l0[0], l0[1]}, \ + {l1[1], l1[0], l1[1]}, \ + {l2[1], l2[0], l2[1]}, \ + } + +#define FOLD_R_3X3(l0, l1, l2, n) \ + { \ + {l0[(n) - 2], l0[(n) - 1], l0[(n) - 2]}, \ + {l1[(n) - 2], l1[(n) - 1], l1[(n) - 2]}, \ + {l2[(n) - 2], l2[(n) - 1], l2[(n) - 2]}, \ + } + +void pixel_rggb8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][3] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][3] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_grbg8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_rggb8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_grbg8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_rggb8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_grbg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_rggb8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_grbg8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_rggb8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_grbg8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_bggr8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_gbrg8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_bggr8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_gbrg8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_bggr8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_gbrg8line_to_rgb24line_3x3(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, + uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 4 && sz % 2 == 0); + uint8_t il[3][4] = FOLD_L_3X3(i0, i1, i2); + uint8_t ir[3][4] = FOLD_R_3X3(i0, i1, i2, sz); + + pixel_bggr8_to_rgb24_3x3(il[0], il[1], il[2], &o0[0]); + for (size_t i = 0, o = 3; i + 4 <= sz; i += 2, o += 6) { + pixel_gbrg8_to_rgb24_3x3(&i0[i + 0], &i1[i + 0], &i2[i + 0], &o0[o + 0]); + pixel_bggr8_to_rgb24_3x3(&i0[i + 1], &i1[i + 1], &i2[i + 1], &o0[o + 3]); + } + pixel_gbrg8_to_rgb24_3x3(ir[0], ir[1], ir[2], &o0[sz * 3 - 3]); +} + +void pixel_rggb8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_rggb8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_grbg8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_rggb8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_grbg8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +void pixel_gbrg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_gbrg8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_bggr8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_gbrg8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_bggr8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +void pixel_bggr8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_bggr8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_gbrg8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_bggr8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_gbrg8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +void pixel_grbg8line_to_rgb24line_2x2(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t sz) +{ + __ASSERT_NO_MSG(sz >= 2 && sz % 2 == 0); + + for (size_t i = 0, o = 0; i + 3 <= sz; i += 2, o += 6) { + pixel_grbg8_to_rgb24_2x2(i0[i + 0], i0[i + 1], i1[i + 0], i1[i + 1], &o0[o + 0]); + pixel_rggb8_to_rgb24_2x2(i0[i + 1], i0[i + 2], i1[i + 1], i1[i + 2], &o0[o + 3]); + } + pixel_grbg8_to_rgb24_2x2(i0[sz - 1], i0[sz - 2], i1[sz - 1], i1[sz - 2], &o0[sz * 3 - 6]); + pixel_rggb8_to_rgb24_2x2(i0[sz - 2], i0[sz - 1], i1[sz - 2], i1[sz - 1], &o0[sz * 3 - 3]); +} + +typedef void fn_3x3_t(const uint8_t *i0, const uint8_t *i1, const uint8_t *i2, uint8_t *o0, + size_t pitch); + +typedef void fn_2x2_t(const uint8_t *i0, const uint8_t *i1, uint8_t *o0, size_t pitch); + +static inline void pixel_bayerstream_to_rgb24stream_3x3(struct pixel_stream *strm, fn_3x3_t *fn0, + fn_3x3_t *fn1) +{ + uint8_t prev_line_offset = strm->line_offset; + const uint8_t *i0 = pixel_stream_get_input_line(strm); + const uint8_t *i1 = pixel_stream_peek_input_line(strm); + const uint8_t *i2 = pixel_stream_peek_input_line(strm); + + if (prev_line_offset == 0) { + fn1(i1, i0, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } + + if (prev_line_offset % 2 == 0) { + fn0(i0, i1, i2, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } else { + fn1(i0, i1, i2, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } + + if (strm->line_offset + 2 == strm->height) { + fn0(i1, i2, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + + pixel_stream_get_input_line(strm); + pixel_stream_get_input_line(strm); + } +} + +static inline void pixel_bayerstream_to_rgb24stream_2x2(struct pixel_stream *strm, fn_2x2_t *fn0, + fn_2x2_t *fn1) +{ + uint8_t prev_line_offset = strm->line_offset; + const uint8_t *i0 = pixel_stream_get_input_line(strm); + const uint8_t *i1 = pixel_stream_peek_input_line(strm); + + if (prev_line_offset % 2 == 0) { + fn0(i0, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } else { + fn1(i0, i1, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + } + + if (strm->line_offset + 1 == strm->height) { + fn0(i1, i0, pixel_stream_get_output_line(strm), strm->pitch); + pixel_stream_done(strm); + + pixel_stream_get_input_line(strm); + } +} + +void pixel_rggb8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_rggb8line_to_rgb24line_3x3, + &pixel_gbrg8line_to_rgb24line_3x3); +} + +void pixel_gbrg8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_gbrg8line_to_rgb24line_3x3, + &pixel_rggb8line_to_rgb24line_3x3); +} + +void pixel_bggr8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_bggr8line_to_rgb24line_3x3, + &pixel_grbg8line_to_rgb24line_3x3); +} + +void pixel_grbg8stream_to_rgb24stream_3x3(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_3x3(strm, &pixel_grbg8line_to_rgb24line_3x3, + &pixel_bggr8line_to_rgb24line_3x3); +} + +void pixel_rggb8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_rggb8line_to_rgb24line_2x2, + &pixel_gbrg8line_to_rgb24line_2x2); +} + +void pixel_gbrg8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_gbrg8line_to_rgb24line_2x2, + &pixel_rggb8line_to_rgb24line_2x2); +} + +void pixel_bggr8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_bggr8line_to_rgb24line_2x2, + &pixel_grbg8line_to_rgb24line_2x2); +} + +void pixel_grbg8stream_to_rgb24stream_2x2(struct pixel_stream *strm) +{ + pixel_bayerstream_to_rgb24stream_2x2(strm, &pixel_grbg8line_to_rgb24line_2x2, + pixel_bggr8line_to_rgb24line_2x2); +} diff --git a/lib/pixel/formats.c b/lib/pixel/formats.c new file mode 100644 index 000000000000..1365945c2af3 --- /dev/null +++ b/lib/pixel/formats.c @@ -0,0 +1,122 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +LOG_MODULE_REGISTER(pixel_formats, CONFIG_PIXEL_LOG_LEVEL); + +void pixel_rgb565leline_to_rgb24line(const uint8_t *rgb565, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb565le_to_rgb24(&rgb565[w * 2], &rgb24[w * 3]); + } +} + +void pixel_rgb565beline_to_rgb24line(const uint8_t *rgb565, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb565be_to_rgb24(&rgb565[w * 2], &rgb24[w * 3]); + } +} + +void pixel_rgb24line_to_rgb565leline(const uint8_t *rgb24, uint8_t *rgb565, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb24_to_rgb565le(&rgb24[w * 3], &rgb565[w * 2]); + } +} + +void pixel_rgb24line_to_rgb565beline(const uint8_t *rgb24, uint8_t *rgb565, uint16_t width) +{ + for (uint16_t w = 0; w < width; w++) { + pixel_rgb24_to_rgb565be(&rgb24[w * 3], &rgb565[w * 2]); + } +} + +void pixel_rgb24line_to_yuyvline_bt601(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_rgb24x2_to_yuyv_bt601(&rgb24[w * 3], &yuyv[w * 2]); + } +} + +void pixel_rgb24line_to_yuyvline_bt709(const uint8_t *rgb24, uint8_t *yuyv, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_rgb24x2_to_yuyv_bt709(&rgb24[w * 3], &yuyv[w * 2]); + } +} + +void pixel_yuyvline_to_rgb24line_bt601(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_yuyv_to_rgb24x2_bt601(&yuyv[w * 2], &rgb24[w * 3]); + } +} + +void pixel_yuyvline_to_rgb24line_bt709(const uint8_t *yuyv, uint8_t *rgb24, uint16_t width) +{ + for (uint16_t w = 0; w + 2 <= width; w += 2) { + pixel_yuyv_to_rgb24x2_bt709(&yuyv[w * 2], &rgb24[w * 3]); + } +} + +void pixel_rgb24stream_to_rgb565lestream(struct pixel_stream *strm) +{ + pixel_rgb24line_to_rgb565leline(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} + +void pixel_rgb24stream_to_rgb565bestream(struct pixel_stream *strm) +{ + pixel_rgb24line_to_rgb565beline(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} + +void pixel_rgb565lestream_to_rgb24stream(struct pixel_stream *strm) +{ + pixel_rgb565leline_to_rgb24line(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_rgb565bestream_to_rgb24stream(struct pixel_stream *strm) +{ + pixel_rgb565beline_to_rgb24line(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_yuyvstream_to_rgb24stream_bt601(struct pixel_stream *strm) +{ + pixel_yuyvline_to_rgb24line_bt601(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_yuyvstream_to_rgb24stream_bt709(struct pixel_stream *strm) +{ + pixel_yuyvline_to_rgb24line_bt709(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 2); + pixel_stream_done(strm); +} + +void pixel_rgb24stream_to_yuyvstream_bt601(struct pixel_stream *strm) +{ + pixel_rgb24line_to_yuyvline_bt601(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} + +void pixel_rgb24stream_to_yuyvstream_bt709(struct pixel_stream *strm) +{ + pixel_rgb24line_to_yuyvline_bt709(pixel_stream_get_input_line(strm), + pixel_stream_get_output_line(strm), strm->pitch / 3); + pixel_stream_done(strm); +} diff --git a/lib/pixel/print.c b/lib/pixel/print.c new file mode 100644 index 000000000000..3c6bf038dfac --- /dev/null +++ b/lib/pixel/print.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +static void pixel_print_truecolor(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) +{ + printf("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%um▄", rgb24row0[0], rgb24row0[1], rgb24row0[2], + rgb24row1[0], rgb24row1[1], rgb24row1[2]); +} + +static void pixel_print_256color(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) +{ + printf("\e[48;5;%um\e[38;5;%um▄", pixel_rgb24_to_256color(rgb24row0), + pixel_rgb24_to_256color(rgb24row1)); +} + +static void pixel_print_256gray(uint8_t gray8row0, uint8_t gray8row1) +{ + printf("\e[48;5;%um\e[38;5;%um▄", pixel_gray8_to_256color(gray8row0), + pixel_gray8_to_256color(gray8row1)); +} + +typedef void fn_rgb24_t(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]); + +static inline void pixel_print_rgb24(const uint8_t *rgb24, size_t size, uint16_t width, + uint16_t height, fn_rgb24_t *fn) +{ + size_t pitch = width * 3; + + for (size_t i = 0; height - 2 >= 0; height -= 2) { + for (size_t n = width; n > 0; n--, i += 3) { + if (i + pitch > size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + fn(&rgb24[i], &rgb24[i + pitch]); + } + printf("\e[m│\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +void pixel_print_rgb24frame_256color(const uint8_t *rgb24, size_t size, uint16_t width, + uint16_t height) +{ + pixel_print_rgb24(rgb24, size, width, height, pixel_print_256color); +} + +void pixel_print_rgb24frame_truecolor(const uint8_t *rgb24, size_t size, uint16_t width, + uint16_t height) +{ + pixel_print_rgb24(rgb24, size, width, height, pixel_print_truecolor); +} + +static inline void pixel_print_rgb565(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height, fn_rgb24_t *fn, bool big_endian) +{ + size_t pitch = width * 2; + + for (size_t i = 0; height - 2 >= 0; height -= 2) { + for (size_t n = width; n > 0; n--, i += 2) { + uint8_t rgb24[2][3]; + + if (i + pitch > size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + if (big_endian) { + pixel_rgb565be_to_rgb24(&rgb565[i + pitch * 0], rgb24[0]); + pixel_rgb565be_to_rgb24(&rgb565[i + pitch * 1], rgb24[1]); + } else { + pixel_rgb565le_to_rgb24(&rgb565[i + pitch * 0], rgb24[0]); + pixel_rgb565le_to_rgb24(&rgb565[i + pitch * 1], rgb24[1]); + } + + fn(rgb24[0], rgb24[1]); + } + printf("\e[m│\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +void pixel_print_rgb565leframe_256color(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_256color, false); +} + +void pixel_print_rgb565leframe_truecolor(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_truecolor, false); +} + +void pixel_print_rgb565beframe_256color(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_256color, true); +} + +void pixel_print_rgb565beframe_truecolor(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_rgb565(rgb565, size, width, height, pixel_print_truecolor, true); +} + +static inline void pixel_print_yuyv(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height, fn_rgb24_t *fn, int bt) +{ + size_t pitch = width * 2; + + for (size_t i = 0; height - 2 >= 0; height -= 2) { + for (size_t n = width; n >= 2; n -= 2, i += 4) { + uint8_t rgb24x2[2][6]; + + if (i + pitch > size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + switch (bt) { + case 601: + pixel_yuyv_to_rgb24x2_bt601(&yuyv[i + pitch * 0], rgb24x2[0]); + pixel_yuyv_to_rgb24x2_bt601(&yuyv[i + pitch * 1], rgb24x2[1]); + break; + case 709: + pixel_yuyv_to_rgb24x2_bt709(&yuyv[i + pitch * 0], rgb24x2[0]); + pixel_yuyv_to_rgb24x2_bt709(&yuyv[i + pitch * 1], rgb24x2[1]); + break; + default: + CODE_UNREACHABLE; + } + + fn(&rgb24x2[0][0], &rgb24x2[1][0]); + fn(&rgb24x2[0][3], &rgb24x2[1][3]); + } + printf("\e[m│\n"); + + /* Skip the second h being printed at the same time */ + i += pitch; + } +} + +void pixel_print_yuyvframe_bt601_256color(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_256color, 601); +} + +void pixel_print_yuyvframe_bt601_truecolor(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_truecolor, 601); +} + +void pixel_print_yuyvframe_bt709_256color(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_truecolor, 709); +} + +void pixel_print_yuyvframe_bt709_truecolor(const uint8_t *yuyv, size_t size, uint16_t width, + uint16_t height) +{ + return pixel_print_yuyv(yuyv, size, width, height, pixel_print_truecolor, 709); +} + +void pixel_print_raw8frame_hex(const uint8_t *raw8, size_t size, uint16_t width, uint16_t height) +{ + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 1 + w * 1; + + if (i >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x", raw8[i]); + } + printf(" row%u\n", h); + } +} + +void pixel_print_rgb24frame_hex(const uint8_t *rgb24, size_t size, uint16_t width, uint16_t height) +{ + printf(" "); + for (uint16_t w = 0; w < width; w++) { + printf("col%-7u", w); + } + printf("\n"); + + for (uint16_t w = 0; w < width; w++) { + printf(" R G B "); + } + printf("\n"); + + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 3 + w * 3; + + if (i + 2 >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x %02x %02x ", rgb24[i + 0], rgb24[i + 1], rgb24[i + 2]); + } + printf(" row%u\n", h); + } +} + +void pixel_print_rgb565frame_hex(const uint8_t *rgb565, size_t size, uint16_t width, + uint16_t height) +{ + printf(" "); + for (uint16_t w = 0; w < width; w++) { + printf("col%-4u", w); + } + printf("\n"); + + for (uint16_t w = 0; w < width; w++) { + printf(" RGB565"); + } + printf("\n"); + + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 2 + w * 2; + + if (i + 1 >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x %02x ", rgb565[i + 0], rgb565[i + 1]); + } + printf(" row%u\n", h); + } +} + +void pixel_print_yuyvframe_hex(const uint8_t *yuyv, size_t size, uint16_t width, uint16_t height) +{ + printf(" "); + for (uint16_t w = 0; w < width; w++) { + printf("col%-3u", w); + if ((w + 1) % 2 == 0) { + printf(" "); + } + } + printf("\n"); + + for (uint16_t w = 0; w < width; w++) { + printf(" %c%u", "YUYV"[w % 2 * 2 + 0], w % 2); + printf(" %c%u", "YUYV"[w % 2 * 2 + 1], w % 2); + if ((w + 1) % 2 == 0) { + printf(" "); + } + } + printf("\n"); + + for (uint16_t h = 0; h < height; h++) { + for (uint16_t w = 0; w < width; w++) { + size_t i = h * width * 2 + w * 2; + + if (i + 1 >= size) { + printf("\e[m *** early end of buffer at %zu bytes ***\n", size); + return; + } + + printf(" %02x %02x", yuyv[i], yuyv[i + 1]); + if ((w + 1) % 2 == 0) { + printf(" "); + } + } + printf(" row%u\n", h); + } +} + +static void pixel_print_hist_scale(size_t size) +{ + for (uint16_t i = 0; i < size; i++) { + pixel_print_256gray(0, i * 256 / size); + } + printf("\e[m\n"); +} + +void pixel_print_rgb24hist(const uint16_t *rgb24hist, size_t size, uint16_t height) +{ + const uint16_t *r8hist = &rgb24hist[size / 3 * 0]; + const uint16_t *g8hist = &rgb24hist[size / 3 * 1]; + const uint16_t *b8hist = &rgb24hist[size / 3 * 2]; + uint32_t max = 1; + + __ASSERT(size % 3 == 0, "Each of R, G, B channel should have the same size."); + + for (size_t i = 0; i < size; i++) { + max = rgb24hist[i] > max ? rgb24hist[i] : max; + } + + for (uint16_t h = height; h > 1; h--) { + for (size_t i = 0; i < size / 3; i++) { + uint8_t rgb24row0[3]; + uint8_t rgb24row1[3]; + + rgb24row0[0] = (r8hist[i] * height / max > h - 0) ? 0xff : 0x00; + rgb24row0[1] = (g8hist[i] * height / max > h - 0) ? 0xff : 0x00; + rgb24row0[2] = (b8hist[i] * height / max > h - 0) ? 0xff : 0x00; + rgb24row1[0] = (r8hist[i] * height / max > h - 1) ? 0xff : 0x00; + rgb24row1[1] = (g8hist[i] * height / max > h - 1) ? 0xff : 0x00; + rgb24row1[2] = (b8hist[i] * height / max > h - 1) ? 0xff : 0x00; + + pixel_print_256color(rgb24row0, rgb24row1); + } + printf("\e[m│ - %u\n", h * max / height); + } + + pixel_print_hist_scale(size / 3); +} + +void pixel_print_y8hist(const uint16_t *y8hist, size_t size, uint16_t height) +{ + uint32_t max = 1; + + for (size_t i = 0; i < size; i++) { + max = y8hist[i] > max ? y8hist[i] : max; + } + + for (uint16_t h = height; h > 1; h--) { + for (size_t i = 0; i < size; i++) { + uint8_t gray8row0 = (y8hist[i] * height / max > h - 0) ? 0xff : 0x00; + uint8_t gray8row1 = (y8hist[i] * height / max > h - 1) ? 0xff : 0x00; + + pixel_print_256gray(gray8row0, gray8row1); + } + printf("\e[m│ - %u\n", h * max / height); + } + + pixel_print_hist_scale(size); +} diff --git a/lib/pixel/resize.c b/lib/pixel/resize.c new file mode 100644 index 000000000000..0837eac7d860 --- /dev/null +++ b/lib/pixel/resize.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_resize, CONFIG_PIXEL_LOG_LEVEL); + +static inline void pixel_subsample_line(const uint8_t *src_buf, size_t src_width, + uint8_t *dst_buf, size_t dst_width, + uint8_t bits_per_pixel) +{ + for (size_t dst_w = 0; dst_w < dst_width; dst_w++) { + size_t src_w = dst_w * src_width / dst_width; + size_t src_i = src_w * bits_per_pixel / BITS_PER_BYTE; + size_t dst_i = dst_w * bits_per_pixel / BITS_PER_BYTE; + + memmove(&dst_buf[dst_i], &src_buf[src_i], bits_per_pixel / BITS_PER_BYTE); + } +} + +static inline void pixel_subsample_frame(const uint8_t *src_buf, size_t src_width, + size_t src_height, uint8_t *dst_buf, size_t dst_width, + size_t dst_height, uint8_t bits_per_pixel) +{ + for (size_t dst_h = 0; dst_h < dst_height; dst_h++) { + size_t src_h = dst_h * src_height / dst_height; + size_t src_i = src_h * src_width * bits_per_pixel / BITS_PER_BYTE; + size_t dst_i = dst_h * dst_width * bits_per_pixel / BITS_PER_BYTE; + + pixel_subsample_line(&src_buf[src_i], src_width, &dst_buf[dst_i], dst_width, + bits_per_pixel); + } +} + +void pixel_subsample_rgb24frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height) +{ + pixel_subsample_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 24); +} + +void pixel_subsample_rgb565frame(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height) +{ + pixel_subsample_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 16); +} + +void pixel_subsample_yuyvframe(const uint8_t *src_buf, size_t src_width, size_t src_height, + uint8_t *dst_buf, size_t dst_width, size_t dst_height) +{ + pixel_subsample_frame(src_buf, src_width, src_height, dst_buf, dst_width, dst_height, 16); +} diff --git a/lib/pixel/stats.c b/lib/pixel/stats.c new file mode 100644 index 000000000000..07b18f778043 --- /dev/null +++ b/lib/pixel/stats.c @@ -0,0 +1,274 @@ +/* + * Copyir (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +static const uint8_t pixel_rggb8[4] = {0, 1, 1, 2}; +static const uint8_t pixel_bggr8[4] = {2, 1, 1, 0}; +static const uint8_t pixel_gbrg8[4] = {1, 2, 0, 1}; +static const uint8_t pixel_grbg8[4] = {1, 0, 2, 1}; + +/* Extract a random value from the buffer */ + +static inline uint32_t pixel_rand(void) +{ + static uint32_t lcg_state; + + /* Linear Congruent Generator (LCG) are low-quality but very fast, here considered enough + * as even a fixed offset would have been enough.The % phase is skipped as there is already + * "% vbuf->bytesused" downstream in the code. + * + * The constants are from https://en.wikipedia.org/wiki/Linear_congruential_generator + */ + lcg_state = lcg_state * 1103515245 + 12345; + return lcg_state; +} + +static inline void pixel_sample_rgb24(const uint8_t *buf, size_t size, uint8_t rgb24[3]) +{ + uint32_t pos = pixel_rand() % size; + + /* Align on 24-bit pixel boundary */ + pos -= pos % 3; + + rgb24[0] = buf[pos + 0]; + rgb24[1] = buf[pos + 1]; + rgb24[2] = buf[pos + 2]; +} + +static inline void pixel_sums_to_rgb24avg(uint16_t sums[3], uint8_t rgb24avg[3], uint16_t nval) +{ + rgb24avg[0] = sums[0] / nval; + rgb24avg[1] = sums[1] / nval; + rgb24avg[2] = sums[2] / nval; +} + +static inline void pixel_sums_add_rgb24(uint16_t sums[3], uint8_t rgb24[3]) +{ + sums[0] += rgb24[0], sums[1] += rgb24[1]; + sums[2] += rgb24[2]; +} + +static inline void pixel_sample_bayer(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24[3], const uint8_t *idx) +{ + uint32_t pos = pixel_rand() % size; + + /* Make sure to be on even row and column position */ + pos -= pos % 2; + pos -= (pos / width % 2 == 0) ? 0 : width; + + rgb24[idx[0]] = buf[pos + 0]; + rgb24[idx[1]] = buf[pos + 1]; + rgb24[idx[2]] = buf[pos + width + 0]; + rgb24[idx[3]] = buf[pos + width + 1]; +} + +/* Channel average statistics */ + +void pixel_rgb24frame_to_rgb24avg(const uint8_t *buf, size_t size, uint8_t rgb24avg[3], + uint16_t nval) +{ + uint16_t sums[3] = {0}; + uint8_t rgb24[3]; + + for (uint16_t n = 0; n < nval; n++) { + pixel_sample_rgb24(buf, size, rgb24); + pixel_sums_add_rgb24(sums, rgb24); + } + pixel_sums_to_rgb24avg(sums, rgb24avg, nval); +} + +static inline void pixel_bayerframe_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval, + const uint8_t *idx) +{ + uint16_t sums[3] = {0}; + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_bayer(buf, size, width, rgb24, idx); + pixel_sums_add_rgb24(sums, rgb24); + } + pixel_sums_to_rgb24avg(sums, rgb24avg, nval); +} + +void pixel_rggb8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_bggr8); +} + +void pixel_bggr8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_bggr8); +} + +void pixel_gbrg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_grbg8); +} + +void pixel_grbg8frame_to_rgb24avg(const uint8_t *buf, size_t size, uint16_t width, + uint8_t rgb24avg[3], uint16_t nval) +{ + pixel_bayerframe_to_rgb24avg(buf, size, width, rgb24avg, nval, pixel_grbg8); +} + +/* RGB24 histogram statistics */ + +static inline void pixel_rgb24hist_add_rgb24(uint16_t *rgb24hist, uint8_t rgb24[3], + uint8_t bit_depth) +{ + uint16_t *r8hist = &rgb24hist[0 * (1 << bit_depth)], r8 = rgb24[0]; + uint16_t *g8hist = &rgb24hist[1 * (1 << bit_depth)], g8 = rgb24[1]; + uint16_t *b8hist = &rgb24hist[2 * (1 << bit_depth)], b8 = rgb24[2]; + + r8hist[r8 >> (BITS_PER_BYTE - bit_depth)]++; + g8hist[g8 >> (BITS_PER_BYTE - bit_depth)]++; + b8hist[b8 >> (BITS_PER_BYTE - bit_depth)]++; +} + +void pixel_rgb24frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t *rgb24hist, + size_t hist_size, uint16_t nval) +{ + uint8_t bit_depth = LOG2(hist_size / 3); + + __ASSERT(hist_size % 3 == 0, "Each of R, G, B channel should have the same size."); + __ASSERT(1 << bit_depth == hist_size / 3, "Each channel size should be a power of two."); + + memset(rgb24hist, 0x00, hist_size * sizeof(*rgb24hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_rgb24(buf, buf_size, rgb24); + pixel_rgb24hist_add_rgb24(rgb24hist, rgb24, bit_depth); + } +} + +static inline void pixel_bayerframe_to_rgb24hist(const uint8_t *buf, size_t buf_size, + uint16_t width, uint16_t *rgb24hist, + size_t hist_size, uint16_t nval, + const uint8_t *idx) +{ + uint8_t bit_depth = LOG2(hist_size / 3); + + __ASSERT(hist_size % 3 == 0, "Each of R, G, B channel should have the same size."); + __ASSERT(1 << bit_depth == hist_size / 3, "Each channel size should be a power of two."); + + memset(rgb24hist, 0x00, hist_size * sizeof(*rgb24hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_bayer(buf, buf_size, width, rgb24, idx); + pixel_rgb24hist_add_rgb24(rgb24hist, rgb24, bit_depth); + } +} + +void pixel_rggb8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_rggb8); +} + +void pixel_gbrg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_gbrg8); +} + +void pixel_bggr8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_bggr8); +} + +void pixel_grbg8frame_to_rgb24hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *rgb24hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_rgb24hist(buf, buf_size, width, rgb24hist, hist_size, nval, + pixel_grbg8); +} + +/* Y8 histogram statistics + * Use BT.709 (sRGB) as an arbitrary choice, instead of BT.601 like libcamera + */ + +static inline void pixel_y8hist_add_y8(uint16_t *y8hist, uint8_t y8, uint8_t bit_depth) +{ + y8hist[y8 >> (BITS_PER_BYTE - bit_depth)]++; +} + +void pixel_rgb24frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t *y8hist, + size_t hist_size, uint16_t nval) +{ + uint8_t bit_depth = LOG2(hist_size); + + __ASSERT(1 << bit_depth == hist_size, "Histogram channel size should be a power of two."); + + memset(y8hist, 0x00, hist_size * sizeof(*y8hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_rgb24(buf, buf_size, rgb24); + pixel_y8hist_add_y8(y8hist, pixel_rgb24_to_y8_bt601(rgb24), bit_depth); + } +} + +static inline void pixel_bayerframe_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval, + const uint8_t *idx) +{ + uint8_t bit_depth = LOG2(hist_size); + + __ASSERT(1 << bit_depth == hist_size, "Histogram channel size should be a power of two."); + + memset(y8hist, 0x00, hist_size * sizeof(*y8hist)); + + for (uint16_t n = 0; n < nval; n++) { + uint8_t rgb24[3]; + + pixel_sample_bayer(buf, buf_size, width, rgb24, idx); + pixel_y8hist_add_y8(y8hist, pixel_rgb24_to_y8_bt709(rgb24), bit_depth); + } +} + +void pixel_rggb8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_rggb8); +} + +void pixel_gbrg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_gbrg8); +} + +void pixel_bggr8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_bggr8); +} + +void pixel_grbg8frame_to_y8hist(const uint8_t *buf, size_t buf_size, uint16_t width, + uint16_t *y8hist, size_t hist_size, uint16_t nval) +{ + pixel_bayerframe_to_y8hist(buf, buf_size, width, y8hist, hist_size, nval, pixel_grbg8); +} diff --git a/lib/pixel/stream.c b/lib/pixel/stream.c new file mode 100644 index 000000000000..78acd2952e85 --- /dev/null +++ b/lib/pixel/stream.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(pixel_stream, CONFIG_PIXEL_LOG_LEVEL); + +static void _assert_complete(struct pixel_stream *strm, bool begin) +{ + for (; strm != NULL; strm = strm->next) { + if (strm->run == NULL) { + continue; + } + + __ASSERT(ring_buf_size_get(&strm->ring) == 0, + "Core %s did not empty its input buffer, %u bytes left", strm->name, + ring_buf_size_get(&strm->ring)); + + if (begin && strm->line_offset == 0) { + continue; + } + + __ASSERT(strm->line_offset == strm->height, + "Core %s did only process %u lines out of %u", strm->name, + strm->line_offset, strm->height); + } +} + +void pixel_stream_load(struct pixel_stream *strm, const uint8_t *buf, size_t size) +{ + struct pixel_stream prev = {.next = strm}; + + __ASSERT_NO_MSG(strm != NULL); + __ASSERT_NO_MSG(buf != NULL); + + _assert_complete(strm, true); + + LOG_DBG("Loading %zu bytes into this pipeline:", size); + + for (struct pixel_stream *x = strm; x != NULL; x = x->next) { + LOG_DBG("- %s", x->name); + x->line_offset = 0; + x->total_time = 0; + } + + for (size_t i = 0; i + strm->pitch <= size; i += strm->pitch) { + LOG_DBG("bytes %zu-%zu/%zu into %s", i, i + strm->pitch, size, strm->name); + ring_buf_put(&strm->ring, &buf[i], strm->pitch); + pixel_stream_done(&prev); + } + + LOG_DBG("Processed a full buffer of %zu bytes", size); + + _assert_complete(strm, false); +} From f956fcad87dff097889e0da56f292a7bb07bc7a3 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 5 Mar 2025 01:42:48 +0000 Subject: [PATCH 2/5] samples: lib: pixel: show usage of the pixel library The newly introduced lib/pixel features utilities that help composing video pipelines together for the purpose of stream processing, as well as debug utilities. Signed-off-by: Josuah Demangeon --- samples/lib/lib.rst | 6 ++ samples/lib/pixel/pixel.rst | 14 +++++ samples/lib/pixel/print/CMakeLists.txt | 8 +++ samples/lib/pixel/print/README.rst | 33 +++++++++++ samples/lib/pixel/print/prj.conf | 6 ++ samples/lib/pixel/print/sample.yaml | 19 +++++++ samples/lib/pixel/print/src/main.c | 67 +++++++++++++++++++++++ samples/lib/pixel/resize/CMakeLists.txt | 8 +++ samples/lib/pixel/resize/README.rst | 32 +++++++++++ samples/lib/pixel/resize/prj.conf | 6 ++ samples/lib/pixel/resize/sample.yaml | 17 ++++++ samples/lib/pixel/resize/src/main.c | 47 ++++++++++++++++ samples/lib/pixel/stats/CMakeLists.txt | 8 +++ samples/lib/pixel/stats/README.rst | 29 ++++++++++ samples/lib/pixel/stats/prj.conf | 6 ++ samples/lib/pixel/stats/sample.yaml | 17 ++++++ samples/lib/pixel/stats/src/main.c | 57 +++++++++++++++++++ samples/lib/pixel/stream/CMakeLists.txt | 8 +++ samples/lib/pixel/stream/README.rst | 31 +++++++++++ samples/lib/pixel/stream/prj.conf | 6 ++ samples/lib/pixel/stream/sample.yaml | 16 ++++++ samples/lib/pixel/stream/src/main.c | 73 +++++++++++++++++++++++++ 22 files changed, 514 insertions(+) create mode 100644 samples/lib/lib.rst create mode 100644 samples/lib/pixel/pixel.rst create mode 100644 samples/lib/pixel/print/CMakeLists.txt create mode 100644 samples/lib/pixel/print/README.rst create mode 100644 samples/lib/pixel/print/prj.conf create mode 100644 samples/lib/pixel/print/sample.yaml create mode 100644 samples/lib/pixel/print/src/main.c create mode 100644 samples/lib/pixel/resize/CMakeLists.txt create mode 100644 samples/lib/pixel/resize/README.rst create mode 100644 samples/lib/pixel/resize/prj.conf create mode 100644 samples/lib/pixel/resize/sample.yaml create mode 100644 samples/lib/pixel/resize/src/main.c create mode 100644 samples/lib/pixel/stats/CMakeLists.txt create mode 100644 samples/lib/pixel/stats/README.rst create mode 100644 samples/lib/pixel/stats/prj.conf create mode 100644 samples/lib/pixel/stats/sample.yaml create mode 100644 samples/lib/pixel/stats/src/main.c create mode 100644 samples/lib/pixel/stream/CMakeLists.txt create mode 100644 samples/lib/pixel/stream/README.rst create mode 100644 samples/lib/pixel/stream/prj.conf create mode 100644 samples/lib/pixel/stream/sample.yaml create mode 100644 samples/lib/pixel/stream/src/main.c diff --git a/samples/lib/lib.rst b/samples/lib/lib.rst new file mode 100644 index 000000000000..89fd6e8db9cb --- /dev/null +++ b/samples/lib/lib.rst @@ -0,0 +1,6 @@ +.. zephyr:code-sample-category:: lib + :name: Libraries + :show-listing: + :live-search: + + These samples demonstrate how to use the libraries present in Zephyr. diff --git a/samples/lib/pixel/pixel.rst b/samples/lib/pixel/pixel.rst new file mode 100644 index 000000000000..bd54c015bdd1 --- /dev/null +++ b/samples/lib/pixel/pixel.rst @@ -0,0 +1,14 @@ +.. zephyr:code-sample-category:: lib_pixel + :name: Pixel Library + :show-listing: + :live-search: + + These samples demonstrate how to use the Pixel processing library of Zephyr. + +These samples can be used as starting point for test benches that print an input image, +perform some custom processing, and print the color image back along with the logs directly +on the terminal. + +The color rendering of the ``truecolor`` printing functions will be accurate RGB output. + +This helps debugging individual functions before integrating it into a larger streams. diff --git a/samples/lib/pixel/print/CMakeLists.txt b/samples/lib/pixel/print/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/print/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/print/README.rst b/samples/lib/pixel/print/README.rst new file mode 100644 index 000000000000..9348bde56d7e --- /dev/null +++ b/samples/lib/pixel/print/README.rst @@ -0,0 +1,33 @@ +.. zephyr:code-sample:: lib_pixel_print + :name: Pixel Printiing Library + + Print images on the console. + +Overview +******** + +A sample showcasing how to make use of the pixel library to visualize an image or histogram data +by printing it out on the console using `ANSI escape codes`_. + +This allow interleaving debug logs with small previews of the image as a way to debug image data. + +.. _ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code + +Building and Running +******************** + +This application can be built and executed on QEMU as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/print + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/print/prj.conf b/samples/lib/pixel/print/prj.conf new file mode 100644 index 000000000000..ee16fb9972b2 --- /dev/null +++ b/samples/lib/pixel/print/prj.conf @@ -0,0 +1,6 @@ +CONFIG_PIXEL=y +CONFIG_ASSERT=y +CONFIG_LOG=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/print/sample.yaml b/samples/lib/pixel/print/sample.yaml new file mode 100644 index 000000000000..c57890e7b124 --- /dev/null +++ b/samples/lib/pixel/print/sample.yaml @@ -0,0 +1,19 @@ +sample: + description: Pixel Print sample, print images in the terminal for debug purpose + name: pixel print +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "truecolor:" + - "256color:" + - "hexdump:" + - "histogram" +tests: + sample.pixel.print: + tags: pixel diff --git a/samples/lib/pixel/print/src/main.c b/samples/lib/pixel/print/src/main.c new file mode 100644 index 000000000000..1e183964f762 --- /dev/null +++ b/samples/lib/pixel/print/src/main.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +static uint8_t rgb24frame[16 * 32 * 3]; + +void print_image(void) +{ + const uint8_t beg[] = {0x00, 0x70, 0xc5}; + const uint8_t end[] = {0x79, 0x29, 0xd2}; + + /* Generate an image with a gradient of the two colors above */ + for (size_t i = 0, size = sizeof(rgb24frame); i + 3 <= size; i += 3) { + rgb24frame[i + 0] = (beg[0] * (size - i) + end[0] * i) / size; + rgb24frame[i + 1] = (beg[1] * (size - i) + end[1] * i) / size; + rgb24frame[i + 2] = (beg[2] * (size - i) + end[2] * i) / size; + } + + LOG_INF("Printing the gradient #%02x%02x%02x -> #%02x%02x%02x", + beg[0], beg[1], beg[2], end[0], end[1], end[2]); + + LOG_INF("hexdump:"); + pixel_print_rgb24frame_hex(rgb24frame, sizeof(rgb24frame), 16, 32); + + LOG_INF("truecolor:"); + pixel_print_rgb24frame_truecolor(rgb24frame, sizeof(rgb24frame), 16, 32); + + LOG_INF("256color:"); + pixel_print_rgb24frame_256color(rgb24frame, sizeof(rgb24frame), 16, 32); +} + +void print_histogram(void) +{ + static const uint16_t rgb24hist[] = { + 9, 4, 7, 1, 0, 5, 1, 0, 0, 2, 2, 3, 0, 1, 3, 0, + 7, 6, 5, 1, 1, 4, 2, 0, 1, 2, 3, 4, 1, 1, 2, 2, + 8, 4, 7, 4, 2, 3, 1, 2, 2, 2, 2, 2, 0, 0, 1, 1, + }; + + static const uint16_t y8hist[] = { + 8, 5, 6, 2, 1, 4, 1, 1, 1, 2, 3, 3, 1, 1, 2, 1, + }; + + LOG_INF("Printing a histogram of %zu RGB buckets", ARRAY_SIZE(rgb24hist)); + pixel_print_rgb24hist(rgb24hist, ARRAY_SIZE(rgb24hist), 8); + + LOG_INF("Printing a histogram of %zu Y (luma) buckets", ARRAY_SIZE(y8hist)); + pixel_print_y8hist(y8hist, ARRAY_SIZE(y8hist), 8); +} + +int main(void) +{ + print_image(); + print_histogram(); + + return 0; +} diff --git a/samples/lib/pixel/resize/CMakeLists.txt b/samples/lib/pixel/resize/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/resize/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/resize/README.rst b/samples/lib/pixel/resize/README.rst new file mode 100644 index 000000000000..6986200cc30c --- /dev/null +++ b/samples/lib/pixel/resize/README.rst @@ -0,0 +1,32 @@ +.. zephyr:code-sample:: lib_pixel_resize + :name: Pixel Resizing Library + + Resize an image using subsampling. + +Overview +******** + +A sample showcasing how to make use of the pixel library to resize an input image to a smaller or +bigger output image, using the subsampling method. This helps generating a smaller preview of an +input image. + +The input and output are printed as preview images on the console. + +Building and Running +******************** + +This application can be built and executed on the native simulator as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/resize + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/resize/prj.conf b/samples/lib/pixel/resize/prj.conf new file mode 100644 index 000000000000..ee16fb9972b2 --- /dev/null +++ b/samples/lib/pixel/resize/prj.conf @@ -0,0 +1,6 @@ +CONFIG_PIXEL=y +CONFIG_ASSERT=y +CONFIG_LOG=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/resize/sample.yaml b/samples/lib/pixel/resize/sample.yaml new file mode 100644 index 000000000000..e0c7a342725a --- /dev/null +++ b/samples/lib/pixel/resize/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Pixel Resize sample, down-scale/up-scale an image + name: pixel resize +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "output image, bigger," + - "output image, smaller," +tests: + sample.pixel.resize: + tags: pixel diff --git a/samples/lib/pixel/resize/src/main.c b/samples/lib/pixel/resize/src/main.c new file mode 100644 index 000000000000..ce3cbac342da --- /dev/null +++ b/samples/lib/pixel/resize/src/main.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +static void gradient(uint8_t *rgb24buf, size_t size, const uint8_t beg[3], const uint8_t end[3]) +{ + for (int i = 0; i + 3 <= size; i += 3) { + rgb24buf[i + 0] = (beg[0] * (size - i) + end[0] * i) / size; + rgb24buf[i + 1] = (beg[1] * (size - i) + end[1] * i) / size; + rgb24buf[i + 2] = (beg[2] * (size - i) + end[2] * i) / size; + } +} + +static uint8_t rgb24frame0[32 * 16 * 3]; +static uint8_t rgb24frame1[120 * 20 * 3]; +static uint8_t rgb24frame2[10 * 10 * 3]; + +int main(void) +{ + const uint8_t beg[] = {0x00, 0x70, 0xc5}; + const uint8_t end[] = {0x79, 0x29, 0xd2}; + + LOG_INF("input image, 32x16, %zu bytes:", sizeof(rgb24frame0)); + gradient(rgb24frame0, sizeof(rgb24frame0), beg, end); + pixel_print_rgb24frame_truecolor(rgb24frame0, sizeof(rgb24frame0), 32, 16); + + LOG_INF("output image, bigger, 120x16, %zu bytes:", sizeof(rgb24frame1)); + pixel_subsample_rgb24frame(rgb24frame0, 32, 16, rgb24frame1, 120, 20); + pixel_print_rgb24frame_truecolor(rgb24frame1, sizeof(rgb24frame1), 120, 20); + + LOG_INF("output image, smaller, 10x10, %zu bytes:", sizeof(rgb24frame2)); + pixel_subsample_rgb24frame(rgb24frame0, 32, 16, rgb24frame2, 10, 10); + pixel_print_rgb24frame_truecolor(rgb24frame2, sizeof(rgb24frame2), 10, 10); + + return 0; +} diff --git a/samples/lib/pixel/stats/CMakeLists.txt b/samples/lib/pixel/stats/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/stats/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/stats/README.rst b/samples/lib/pixel/stats/README.rst new file mode 100644 index 000000000000..945bf0247892 --- /dev/null +++ b/samples/lib/pixel/stats/README.rst @@ -0,0 +1,29 @@ +.. zephyr:code-sample:: lib_pixel_stats + :name: Pixel Statistics Library + + Collect statistics of an image. + +Overview +******** + +A sample showcasing how to make use of the pixel library to collect statistics of an input image +buffer and display both the image and statistics out on the console. + +Building and Running +******************** + +This application can be built and executed on the native simulator as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/stats + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/stats/prj.conf b/samples/lib/pixel/stats/prj.conf new file mode 100644 index 000000000000..ee16fb9972b2 --- /dev/null +++ b/samples/lib/pixel/stats/prj.conf @@ -0,0 +1,6 @@ +CONFIG_PIXEL=y +CONFIG_ASSERT=y +CONFIG_LOG=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/stats/sample.yaml b/samples/lib/pixel/stats/sample.yaml new file mode 100644 index 000000000000..cdd9ed009fda --- /dev/null +++ b/samples/lib/pixel/stats/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Pixel Stats sample, collect statistics of an input buffer + name: pixel stats +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "RGB histogram of the image" + - "RGB channel averages of the image" +tests: + sample.pixel.stats: + tags: pixel diff --git a/samples/lib/pixel/stats/src/main.c b/samples/lib/pixel/stats/src/main.c new file mode 100644 index 000000000000..2c9ab8aa105e --- /dev/null +++ b/samples/lib/pixel/stats/src/main.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +#define NVAL 100 + +static const uint8_t image_rgb24[20 * 4 * 3] = { + 0x47, 0x84, 0xee, 0x46, 0x84, 0xee, 0x47, 0x84, 0xee, 0x46, 0x83, 0xee, 0x47, 0x84, 0xee, + 0x78, 0xaa, 0xec, 0x74, 0xb2, 0xe0, 0x67, 0xaa, 0xdd, 0x78, 0xb2, 0xef, 0x39, 0x8c, 0xf1, + 0x3a, 0x8c, 0xf2, 0x39, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, + 0x3a, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8a, 0xf1, + 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, 0x47, 0x82, 0xee, 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, + 0x47, 0x82, 0xed, 0x5d, 0x93, 0xed, 0x5f, 0x9d, 0xeb, 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, + 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, + 0x3b, 0x89, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x89, 0xf0, 0x3c, 0x89, 0xf0, 0x3c, 0x89, 0xf0, + 0x49, 0x82, 0xee, 0x49, 0x82, 0xed, 0x49, 0x82, 0xee, 0x48, 0x82, 0xed, 0x49, 0x82, 0xee, + 0x49, 0x82, 0xed, 0x73, 0x92, 0xe9, 0x50, 0x65, 0xd4, 0x4c, 0x93, 0xf2, 0x3c, 0x8a, 0xf1, + 0x3c, 0x8a, 0xf1, 0x3c, 0x8a, 0xf0, 0x3c, 0x8a, 0xf1, 0x3c, 0x89, 0xf0, 0x3d, 0x8a, 0xf1, + 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3e, 0x89, 0xf0, 0x3d, 0x89, 0xf0, + 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x49, 0x81, 0xed, + 0x71, 0x8c, 0xe5, 0x3e, 0x4c, 0xcc, 0x3d, 0x4c, 0xcb, 0x65, 0x85, 0xe1, 0x3d, 0x89, 0xf0, + 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3e, 0x88, 0xf0, + 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3f, 0x88, 0xf0, 0x3e, 0x88, 0xef, +}; + +static uint16_t rgb24hist[3 * 64]; +static uint8_t rgb24avg[3]; + +int main(void) +{ + LOG_INF("Input image preview:"); + pixel_print_rgb24frame_truecolor(image_rgb24, sizeof(image_rgb24), 20, 4); + + LOG_INF("RGB histogram of the image"); + pixel_rgb24frame_to_rgb24hist(image_rgb24, sizeof(image_rgb24), + rgb24hist, ARRAY_SIZE(rgb24hist), NVAL); + pixel_print_rgb24hist(rgb24hist, ARRAY_SIZE(rgb24hist), 16); + + LOG_INF("RGB channel averages of the image"); + pixel_rgb24frame_to_rgb24avg(image_rgb24, sizeof(image_rgb24), rgb24avg, NVAL); + LOG_INF("- R: 0x%02x/0xff", rgb24avg[0]); + LOG_INF("- G: 0x%02x/0xff", rgb24avg[1]); + LOG_INF("- B: 0x%02x/0xff", rgb24avg[2]); + + return 0; +} diff --git a/samples/lib/pixel/stream/CMakeLists.txt b/samples/lib/pixel/stream/CMakeLists.txt new file mode 100644 index 000000000000..1a296153270d --- /dev/null +++ b/samples/lib/pixel/stream/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pixel) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/lib/pixel/stream/README.rst b/samples/lib/pixel/stream/README.rst new file mode 100644 index 000000000000..56b2aeab75d8 --- /dev/null +++ b/samples/lib/pixel/stream/README.rst @@ -0,0 +1,31 @@ +.. zephyr:code-sample:: lib_pixel_stream + :name: Pixel Streaming Library + + Convert an input frame as a stream of lines. + +Overview +******** + +A sample showcasing how to make use of the pixel library to convert its content without requiring +large intermediate buffers, but only line buffers. + +The input and output will both be printed as preview images on the console. + +Building and Running +******************** + +This application can be built and executed on the native simulator as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/lib/pixel/stream + :host-os: unix + :board: native_sim + :goals: run + :compact: + +To build for another board, change "native_sim" above to that board's name. + +Sample Output +============= + +.. code-block:: console diff --git a/samples/lib/pixel/stream/prj.conf b/samples/lib/pixel/stream/prj.conf new file mode 100644 index 000000000000..e1495d188eed --- /dev/null +++ b/samples/lib/pixel/stream/prj.conf @@ -0,0 +1,6 @@ +CONFIG_ASSERT=y +CONFIG_LOG=y +CONFIG_PIXEL=y + +# Required to make sure the test harnesses catch the log output +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/lib/pixel/stream/sample.yaml b/samples/lib/pixel/stream/sample.yaml new file mode 100644 index 000000000000..1088f61b00ce --- /dev/null +++ b/samples/lib/pixel/stream/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: Pixel Stream sample, convert a stream of lines from a frame + name: pixel stream +common: + min_ram: 32 + tags: pixel + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "Output image preview:" +tests: + sample.lib.pixel.stream: + tags: pixel diff --git a/samples/lib/pixel/stream/src/main.c b/samples/lib/pixel/stream/src/main.c new file mode 100644 index 000000000000..5fbe520c5b13 --- /dev/null +++ b/samples/lib/pixel/stream/src/main.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +#define WIDTH 20 +#define HEIGHT 4 + +static const uint8_t rgb24frame[WIDTH * HEIGHT * 3] = { + 0x47, 0x84, 0xee, 0x46, 0x84, 0xee, 0x47, 0x84, 0xee, 0x46, 0x83, 0xee, 0x47, 0x84, 0xee, + 0x78, 0xaa, 0xec, 0x74, 0xb2, 0xe0, 0x67, 0xaa, 0xdd, 0x78, 0xb2, 0xef, 0x39, 0x8c, 0xf1, + 0x3a, 0x8c, 0xf2, 0x39, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, 0x3a, 0x8b, 0xf1, + 0x3a, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8b, 0xf1, 0x3b, 0x8a, 0xf1, + 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, 0x47, 0x82, 0xee, 0x47, 0x82, 0xed, 0x47, 0x82, 0xed, + 0x47, 0x82, 0xed, 0x5d, 0x93, 0xed, 0x5f, 0x9d, 0xeb, 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, + 0x3a, 0x8a, 0xf1, 0x3a, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x8a, 0xf0, + 0x3b, 0x89, 0xf0, 0x3b, 0x8a, 0xf0, 0x3b, 0x89, 0xf0, 0x3c, 0x89, 0xf0, 0x3c, 0x89, 0xf0, + 0x49, 0x82, 0xee, 0x49, 0x82, 0xed, 0x49, 0x82, 0xee, 0x48, 0x82, 0xed, 0x49, 0x82, 0xee, + 0x49, 0x82, 0xed, 0x73, 0x92, 0xe9, 0x50, 0x65, 0xd4, 0x4c, 0x93, 0xf2, 0x3c, 0x8a, 0xf1, + 0x3c, 0x8a, 0xf1, 0x3c, 0x8a, 0xf0, 0x3c, 0x8a, 0xf1, 0x3c, 0x89, 0xf0, 0x3d, 0x8a, 0xf1, + 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x89, 0xf0, 0x3e, 0x89, 0xf0, 0x3d, 0x89, 0xf0, + 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x4a, 0x81, 0xed, 0x49, 0x81, 0xed, 0x49, 0x81, 0xed, + 0x71, 0x8c, 0xe5, 0x3e, 0x4c, 0xcc, 0x3d, 0x4c, 0xcb, 0x65, 0x85, 0xe1, 0x3d, 0x89, 0xf0, + 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3d, 0x89, 0xf0, 0x3d, 0x88, 0xf0, 0x3e, 0x88, 0xf0, + 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3e, 0x88, 0xf0, 0x3f, 0x88, 0xf0, 0x3e, 0x88, 0xef, +}; +static uint8_t yuyvframe[WIDTH * HEIGHT * 2] = {0}; + +/* Stream conversion steps are declared at build time */ +PIXEL_STREAM_RGB24_TO_RGB565LE(step_rgb24_to_rgb565le, WIDTH, HEIGHT); +PIXEL_STREAM_RGB565LE_TO_RGB24(step_rgb565le_to_rgb24, WIDTH, HEIGHT); +PIXEL_STREAM_RGB24_TO_YUYV_BT709(step_rgb24_to_yuyv, WIDTH, HEIGHT); + +int main(void) +{ + struct pixel_stream root = {0}; + struct pixel_stream *step = &root; + struct pixel_stream step_export_yuyv = { + .ring = RING_BUF_INIT(yuyvframe, sizeof(yuyvframe)), + .pitch = WIDTH * 2, + .name = "[export_yuyv]", + }; + + LOG_INF("Input image preview:"); + pixel_print_rgb24frame_truecolor(rgb24frame, sizeof(rgb24frame), WIDTH, HEIGHT); + + /* Interconnection between the stream steps can be done arbitrarily at runtime */ + step = step->next = &step_rgb24_to_rgb565le; + step = step->next = &step_rgb565le_to_rgb24; + step = step->next = &step_rgb24_to_yuyv; + step = step->next = &step_export_yuyv; + pixel_stream_load(root.next, rgb24frame, sizeof(rgb24frame)); + + LOG_INF("Output image preview:"); + pixel_print_yuyvframe_bt709_truecolor(yuyvframe, sizeof(yuyvframe), WIDTH, HEIGHT); + + LOG_INF("Total time spent on each step:"); + for (step = root.next; step != NULL; step = step->next) { + LOG_INF(" %4u us on %s", k_cyc_to_us_ceil32(step->total_time), step->name); + } + + return 0; +} From 1dc47eb70b395a06b30e471ad65255adeedf7b3b Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sun, 9 Mar 2025 22:44:01 +0000 Subject: [PATCH 3/5] lib: pixel: print: remove all UTF-8 characters This is a temporary test for CI only. Signed-off-by: Josuah Demangeon --- lib/pixel/print.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pixel/print.c b/lib/pixel/print.c index 3c6bf038dfac..7c70cdf87979 100644 --- a/lib/pixel/print.c +++ b/lib/pixel/print.c @@ -14,19 +14,19 @@ static void pixel_print_truecolor(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) { - printf("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%um▄", rgb24row0[0], rgb24row0[1], rgb24row0[2], + printf("\e[48;2;%u;%u;%um\e[38;2;%u;%u;%umm", rgb24row0[0], rgb24row0[1], rgb24row0[2], rgb24row1[0], rgb24row1[1], rgb24row1[2]); } static void pixel_print_256color(const uint8_t rgb24row0[3], const uint8_t rgb24row1[3]) { - printf("\e[48;5;%um\e[38;5;%um▄", pixel_rgb24_to_256color(rgb24row0), + printf("\e[48;5;%um\e[38;5;%umm", pixel_rgb24_to_256color(rgb24row0), pixel_rgb24_to_256color(rgb24row1)); } static void pixel_print_256gray(uint8_t gray8row0, uint8_t gray8row1) { - printf("\e[48;5;%um\e[38;5;%um▄", pixel_gray8_to_256color(gray8row0), + printf("\e[48;5;%um\e[38;5;%umm", pixel_gray8_to_256color(gray8row0), pixel_gray8_to_256color(gray8row1)); } @@ -45,7 +45,7 @@ static inline void pixel_print_rgb24(const uint8_t *rgb24, size_t size, uint16_t } fn(&rgb24[i], &rgb24[i + pitch]); } - printf("\e[m│\n"); + printf("\e[m|\n"); /* Skip the second h being printed at the same time */ i += pitch; @@ -88,7 +88,7 @@ static inline void pixel_print_rgb565(const uint8_t *rgb565, size_t size, uint16 fn(rgb24[0], rgb24[1]); } - printf("\e[m│\n"); + printf("\e[m|\n"); /* Skip the second h being printed at the same time */ i += pitch; @@ -149,7 +149,7 @@ static inline void pixel_print_yuyv(const uint8_t *yuyv, size_t size, uint16_t w fn(&rgb24x2[0][0], &rgb24x2[1][0]); fn(&rgb24x2[0][3], &rgb24x2[1][3]); } - printf("\e[m│\n"); + printf("\e[m|\n"); /* Skip the second h being printed at the same time */ i += pitch; @@ -327,7 +327,7 @@ void pixel_print_rgb24hist(const uint16_t *rgb24hist, size_t size, uint16_t heig pixel_print_256color(rgb24row0, rgb24row1); } - printf("\e[m│ - %u\n", h * max / height); + printf("\e[m| - %u\n", h * max / height); } pixel_print_hist_scale(size / 3); @@ -348,7 +348,7 @@ void pixel_print_y8hist(const uint16_t *y8hist, size_t size, uint16_t height) pixel_print_256gray(gray8row0, gray8row1); } - printf("\e[m│ - %u\n", h * max / height); + printf("\e[m| - %u\n", h * max / height); } pixel_print_hist_scale(size); From 8339840d3834f2022a5b930bb7ca616736af98bd Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 20 Feb 2025 20:26:10 +0000 Subject: [PATCH 4/5] drivers: video: introduce the RGB24 format This introduces the RGB 24-bit format as defined in the Linux header , including the bits-per-pixel size definition. Signed-off-by: Josuah Demangeon --- include/zephyr/drivers/video.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 67212110ebcd..9b1cceb344a6 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -1292,6 +1292,15 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, */ #define VIDEO_PIX_FMT_XRGB32 VIDEO_FOURCC('B', 'X', '2', '4') +/** + * Red, Green, Blue, one byte per channel, 24-bits in total. + * + * @verbatim + * | Rrrrrrrr Gggggggg Bbbbbbbb | ... + * @endverbatim + */ +#define VIDEO_PIX_FMT_RGB24 VIDEO_FOURCC('R', 'G', 'B', '3') + /** * @} */ From 42ddc5c01da1475ff8939f3681ce241e0fc5950d Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 5 Mar 2025 10:39:13 +0000 Subject: [PATCH 5/5] drivers: video: sw_isp: introduce a software-based ISP This adds a driver that converts video frames submitted into a different format, which supports bayer (RGGB8, BGGR8, GRBG8, GBRG8), RGB24, RGB565, YUYV input format and can convert them to RGB24 RGB565, and YUYV. Setting the video input and output formats will control the format conversion done by this software device. Signed-off-by: Josuah Demangeon --- drivers/video/CMakeLists.txt | 1 + drivers/video/Kconfig | 2 + drivers/video/Kconfig.sw_isp | 37 ++ drivers/video/video_sw_isp.c | 485 +++++++++++++++++++++++++ tests/drivers/build_all/video/prj.conf | 1 + 5 files changed, 526 insertions(+) create mode 100644 drivers/video/Kconfig.sw_isp create mode 100644 drivers/video/video_sw_isp.c diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 102a841648e9..7e7fa157971e 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_sources(video_device.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_MIPI_CSI2RX video_mcux_mipi_csi2rx.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_GENERATOR video_sw_generator.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_ISP video_sw_isp.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MT9M114 mt9m114.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7725 ov7725.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640 ov2640.c) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index a3b163443628..c826f59bd9d1 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -58,6 +58,8 @@ source "drivers/video/Kconfig.mcux_mipi_csi2rx" source "drivers/video/Kconfig.sw_generator" +source "drivers/video/Kconfig.sw_isp" + source "drivers/video/Kconfig.mt9m114" source "drivers/video/Kconfig.ov7725" diff --git a/drivers/video/Kconfig.sw_isp b/drivers/video/Kconfig.sw_isp new file mode 100644 index 000000000000..2fbf3bc94e5f --- /dev/null +++ b/drivers/video/Kconfig.sw_isp @@ -0,0 +1,37 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_SW_ISP + bool "Video Software Image Signal Processor (ISP)" + imply PIXEL + help + Enable a software-based image processing pipeline, providing the essential components + of an Image Signal Processsor (ISP) exposing the essential features of the "pixel" + library as a native Video API. + +config VIDEO_SW_ISP_STACK_SIZE + int "Stack size for the thread executing the pipelne" + default 1024 + help + Stack size for the thread executing the pipeline. All of the processing elements are not + allocated on the stack but use global variables instead. The default value is therefore + planning only for the temporary variables. + +config VIDEO_SW_ISP_THREAD_PRIORITY + int "Stack size for the thread executing the pipelne" + default 5 + help + Priority for the thread executing the pipeline. The default value is an intermediate + starting point to be tuned arbitrarily to fit the application. + +config VIDEO_SW_ISP_INPUT_WIDTH + int "Width of the input of the pipeline in pixels, used for buffer allocation purposees." + default 640 + help + The default value is VGA width, a widespread default resolution supported broadly. + +config VIDEO_SW_ISP_INPUT_HEIGHT + int "Height of the input of the pipeline in pixels, used for buffer allocation purposees." + default 480 + help + The default value is VGA height, a widespread default resolution supported broadly. diff --git a/drivers/video/video_sw_isp.c b/drivers/video/video_sw_isp.c new file mode 100644 index 000000000000..0d3a8207b5c7 --- /dev/null +++ b/drivers/video/video_sw_isp.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * Copyright (c) 2019, Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(video_sw_isp, CONFIG_VIDEO_LOG_LEVEL); + +#define WIDTH CONFIG_VIDEO_SW_ISP_INPUT_WIDTH +#define HEIGHT CONFIG_VIDEO_SW_ISP_INPUT_HEIGHT + +struct video_sw_isp_data { + const struct device *dev; + struct k_fifo fifo_input_in; + struct k_fifo fifo_input_out; + struct k_fifo fifo_output_in; + struct k_fifo fifo_output_out; + struct k_poll_signal *sig; + struct video_format fmt_in; + struct video_format fmt_out; + struct pixel_stream *first_step; + struct pixel_stream step_export; + struct k_mutex mutex; +}; + +#define VIDEO_SW_ISP_FORMAT_CAP(pixfmt) \ + { \ + .pixelformat = (pixfmt), \ + .width_min = WIDTH, \ + .width_max = WIDTH, \ + .height_min = HEIGHT, \ + .height_max = HEIGHT, \ + .width_step = 0, \ + .height_step = 0, \ + } + +static const struct video_format_cap fmts_in[] = { + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB24), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB565), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_YUYV), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGGB8), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_GRBG8), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_BGGR8), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_GBRG8), + {0}, +}; + +static const struct video_format_cap fmts_out[] = { + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB24), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_RGB565), + VIDEO_SW_ISP_FORMAT_CAP(VIDEO_PIX_FMT_YUYV), + {0}, +}; + +/* Bayer to RGB24 conversion */ +static PIXEL_STREAM_RGGB8_TO_RGB24_3X3(step_rggb8_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_GBRG8_TO_RGB24_3X3(step_gbrg8_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_BGGR8_TO_RGB24_3X3(step_bggr8_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_GRBG8_TO_RGB24_3X3(step_grbg8_to_rgb24, WIDTH, HEIGHT); + +/* Pixel format conversion */ +static PIXEL_STREAM_RGB24_TO_YUYV_BT709(step_rgb24_to_yuyv, WIDTH, HEIGHT); +static PIXEL_STREAM_YUYV_TO_RGB24_BT709(step_yuyv_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_RGB565LE_TO_RGB24(step_rgb565le_to_rgb24, WIDTH, HEIGHT); +static PIXEL_STREAM_RGB24_TO_RGB565LE(step_rgb24_to_rgb565le, WIDTH, HEIGHT); + +static void video_sw_isp_thread(void *p0, void *p1, void *p2) +{ + struct video_sw_isp_data *data = p0; + const struct device *dev = data->dev; + + while (true) { + struct video_buffer *input; + struct video_buffer *output; + struct video_format fmt_out = {0}; + + input = k_fifo_get(&data->fifo_input_in, K_FOREVER); + output = k_fifo_get(&data->fifo_output_in, K_FOREVER); + + /* Wait indefinitely if the stream is stopped */ + k_mutex_lock(&data->mutex, K_FOREVER); + + /* Do not block the thread calling video_set_stream(), check again next cycle */ + k_mutex_unlock(&data->mutex); + + video_get_format(dev, VIDEO_EP_OUT, &fmt_out); + + /* Load the video buffer into the last struct */ + ring_buf_init(&data->step_export.ring, output->size, output->buffer); + data->step_export.height = fmt_out.height; + data->step_export.pitch = fmt_out.pitch; + data->step_export.run = NULL; + pixel_stream_load(data->first_step, input->buffer, input->bytesused); + output->bytesused = ring_buf_size_get(&data->step_export.ring); + + LOG_DBG("Converted a buffer from %p (%u bytes) to %p (%u bytes)", input->buffer, + input->bytesused, output->buffer, output->bytesused); + + /* Move the buffer from submission to completion queue for the input endpoint */ + k_fifo_get(&data->fifo_input_in, K_NO_WAIT); + k_fifo_put(&data->fifo_input_out, input); + + /* Move the buffer from submission to completion queue for the output endpoint */ + k_fifo_get(&data->fifo_output_in, K_NO_WAIT); + k_fifo_put(&data->fifo_output_out, output); + + if (IS_ENABLED(CONFIG_POLL) && data->sig != NULL) { + k_poll_signal_raise(data->sig, VIDEO_BUF_DONE); + } + } +} + +static int video_sw_isp_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps) +{ + caps->min_vbuf_count = 0; + caps->min_line_count = 1; + caps->max_line_count = LINE_COUNT_HEIGHT; + + switch (ep) { + case VIDEO_EP_IN: + caps->format_caps = fmts_in; + return 0; + case VIDEO_EP_OUT: + caps->format_caps = fmts_out; + return 0; + default: + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } +} + +static int video_sw_isp_reconfigure(const struct device *dev) +{ + struct video_sw_isp_data *data = dev->data; + struct pixel_stream root = {0}; + struct pixel_stream *strm = &root; + struct video_format fmt_in = {0}; + struct video_format fmt_out = {0}; + uint32_t pixfmt; + int ret = 0; + + video_get_format(dev, VIDEO_EP_IN, &fmt_in); + video_get_format(dev, VIDEO_EP_OUT, &fmt_out); + pixfmt = fmt_in.pixelformat; + + /* Entering the Unpacking stage */ + + switch (pixfmt) { + case VIDEO_PIX_FMT_RGB565: + strm = strm->next = &step_rgb565le_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + } + + /* Entering the debayer stage */ + + switch (pixfmt) { + case VIDEO_PIX_FMT_RGGB8: + strm = strm->next = &step_rggb8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + case VIDEO_PIX_FMT_GRBG8: + strm = strm->next = &step_grbg8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + case VIDEO_PIX_FMT_BGGR8: + strm = strm->next = &step_bggr8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + case VIDEO_PIX_FMT_GBRG8: + strm = strm->next = &step_gbrg8_to_rgb24; + pixfmt = VIDEO_PIX_FMT_RGB24; + break; + } + + /* Entering the Output stage */ + + switch (fmt_out.pixelformat) { + case VIDEO_PIX_FMT_RGB565: + switch (pixfmt) { + case VIDEO_PIX_FMT_RGB565: + break; + case VIDEO_PIX_FMT_RGB24: + strm = strm->next = &step_rgb24_to_rgb565le; + break; + case VIDEO_PIX_FMT_YUYV: + strm = strm->next = &step_yuyv_to_rgb24; + strm = strm->next = &step_rgb24_to_rgb565le; + break; + default: + LOG_ERR("Cannot convert '%c%c%c%c' to RGB565LE", pixfmt >> 24, pixfmt >> 16, + pixfmt >> 8, pixfmt); + ret = -ENOTSUP; + goto end; + } + pixfmt = VIDEO_PIX_FMT_RGB565; + break; + case VIDEO_PIX_FMT_YUYV: + switch (pixfmt) { + case VIDEO_PIX_FMT_YUYV: + break; + case VIDEO_PIX_FMT_RGB24: + strm = strm->next = &step_rgb24_to_yuyv; + break; + default: + LOG_ERR("Cannot convert '%c%c%c%c' to YUYV", pixfmt >> 24, pixfmt >> 16, + pixfmt >> 8, pixfmt); + ret = -ENOTSUP; + goto end; + } + break; + case VIDEO_PIX_FMT_RGB24: + switch (pixfmt) { + case VIDEO_PIX_FMT_RGB24: + break; + case VIDEO_PIX_FMT_YUYV: + strm = strm->next = &step_yuyv_to_rgb24; + break; + default: + LOG_ERR("Cannot convert '%c%c%c%c' to RGB24", pixfmt >> 24, pixfmt >> 16, + pixfmt >> 8, pixfmt); + ret = -ENOTSUP; + goto end; + } + break; + default: + LOG_ERR("Unknown output format '%c%c%c%c'", fmt_out.pixelformat >> 24, + fmt_out.pixelformat >> 16, fmt_out.pixelformat >> 8, fmt_out.pixelformat); + ret = -ENOTSUP; + goto end; + } + + /* Entering the Export stage */ + + strm = strm->next = &data->step_export; + strm->name = "[export video_sw_isp " STRINGIFY(WIDTH) "x" STRINGIFY(HEIGHT) "]"; + +end: + data->first_step = root.next; + + LOG_INF("Current topology:"); + for (strm = data->first_step; strm != NULL; strm = strm->next) { + LOG_INF("- %s, pitch %u", strm->name, strm->pitch); + } + + return ret; +} + +static int video_sw_isp_set_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_sw_isp_data *data = dev->data; + const struct video_format_cap *cap; + size_t i; + int ret; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_OUT) { + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + LOG_DBG("Asking for format '%c%c%c%c' %ux%u", fmt->pixelformat >> 24, + fmt->pixelformat >> 16, fmt->pixelformat >> 8, fmt->pixelformat, fmt->width, + fmt->height); + + cap = (ep == VIDEO_EP_IN) ? &fmts_in[0] : &fmts_out[0]; + + for (i = 0; cap[i].pixelformat != 0; i++) { + if (fmt->pixelformat != cap[i].pixelformat && + IN_RANGE(fmt->width, cap[i].width_min, cap[i].width_max) && + IN_RANGE(fmt->height, cap[i].height_min, cap[i].height_max)) { + break; + } + } + + if (cap[i].pixelformat == 0) { + LOG_ERR("Trying to set an unsupported format '%c%c%c%c'", cap[i].pixelformat >> 24, + cap[i].pixelformat >> 16, cap[i].pixelformat >> 8, cap[i].pixelformat); + return -ENOTSUP; + } + + memcpy((ep == VIDEO_EP_IN) ? &data->fmt_in : &data->fmt_out, fmt, sizeof(*fmt)); + + ret = video_sw_isp_reconfigure(dev); + if (ret != 0) { + LOG_ERR("Failed to reconfigure the isp"); + return ret; + } + + return 0; +} + +static int video_sw_isp_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_sw_isp_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_OUT) { + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + memcpy(fmt, (ep == VIDEO_EP_IN) ? &data->fmt_in : &data->fmt_out, sizeof(*fmt)); + + return 0; +} + +static int video_sw_isp_flush(const struct device *dev, enum video_endpoint_id ep, bool cancel) +{ + struct video_sw_isp_data *data = dev->data; + struct video_buffer *vbuf; + + if (cancel) { + /* Skip all the buffers of the input endpointo, unprocessed */ + while ((vbuf = k_fifo_get(&data->fifo_input_in, K_NO_WAIT)) != NULL) { + k_fifo_put(&data->fifo_input_out, vbuf); + if (IS_ENABLED(CONFIG_POLL) && data->sig) { + k_poll_signal_raise(data->sig, VIDEO_BUF_ABORTED); + } + } + /* Skip all the buffers of the output endpoint, unprocessed */ + while ((vbuf = k_fifo_get(&data->fifo_output_in, K_NO_WAIT)) != NULL) { + k_fifo_put(&data->fifo_output_out, vbuf); + if (IS_ENABLED(CONFIG_POLL) && data->sig) { + k_poll_signal_raise(data->sig, VIDEO_BUF_ABORTED); + } + } + } else { + /* Wait for all buffer to be processed on the input endpointo */ + while (!k_fifo_is_empty(&data->fifo_input_in)) { + k_sleep(K_MSEC(1)); + } + /* Wait for all buffer to be processed on the output endpointo */ + while (!k_fifo_is_empty(&data->fifo_output_in)) { + k_sleep(K_MSEC(1)); + } + } + + return 0; +} + +static int video_sw_isp_enqueue(const struct device *dev, enum video_endpoint_id ep, + struct video_buffer *vbuf) +{ + struct video_sw_isp_data *data = dev->data; + + switch (ep) { + case VIDEO_EP_IN: + k_fifo_put(&data->fifo_input_in, vbuf); + break; + case VIDEO_EP_OUT: + k_fifo_put(&data->fifo_output_in, vbuf); + break; + default: + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + return 0; +} + +static int video_sw_isp_dequeue(const struct device *dev, enum video_endpoint_id ep, + struct video_buffer **vbuf, k_timeout_t timeout) +{ + struct video_sw_isp_data *data = dev->data; + + switch (ep) { + case VIDEO_EP_IN: + *vbuf = k_fifo_get(&data->fifo_input_out, timeout); + break; + case VIDEO_EP_OUT: + *vbuf = k_fifo_get(&data->fifo_output_out, timeout); + break; + default: + LOG_ERR("Need to specify input or output endpoint."); + return -EINVAL; + } + + if (*vbuf == NULL) { + LOG_DBG("Failed to dequeue %s buffer", ep == VIDEO_EP_IN ? "input" : "output"); + return -EAGAIN; + } + + return 0; +} + +static int video_sw_isp_set_stream(const struct device *dev, bool enable) +{ + struct video_sw_isp_data *data = dev->data; + + if (enable) { + /* Release the stream processing thread */ + k_mutex_unlock(&data->mutex); + } else { + /* This will stop the stream thread without blocking this thread for long */ + k_mutex_lock(&data->mutex, K_FOREVER); + } + + return 0; +} + +#ifdef CONFIG_POLL +static int video_sw_isp_set_signal(const struct device *dev, enum video_endpoint_id ep, + struct k_poll_signal *sig) +{ + struct video_sw_isp_data *data = dev->data; + + if (ep != VIDEO_EP_IN && ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + data->sig = sig; + + return 0; +} +#endif + +static DEVICE_API(video, video_sw_isp_driver_api) = { + .set_format = video_sw_isp_set_fmt, + .get_format = video_sw_isp_get_fmt, + .flush = video_sw_isp_flush, + .enqueue = video_sw_isp_enqueue, + .dequeue = video_sw_isp_dequeue, + .get_caps = video_sw_isp_get_caps, + .set_stream = video_sw_isp_set_stream, +#ifdef CONFIG_POLL + .set_signal = video_sw_isp_set_signal, +#endif +}; + +static int video_sw_isp_init(const struct device *dev) +{ + struct video_sw_isp_data *data = dev->data; + struct video_format fmt_in = { + .pixelformat = fmts_in[0].pixelformat, + .width = WIDTH, + .height = HEIGHT, + .pitch = WIDTH * video_bits_per_pixel(fmts_in[0].pixelformat) / BITS_PER_BYTE, + }; + struct video_format fmt_out = { + .pixelformat = fmts_out[0].pixelformat, + .width = WIDTH, + .height = HEIGHT, + .pitch = WIDTH * video_bits_per_pixel(fmts_out[0].pixelformat) / BITS_PER_BYTE, + }; + int ret; + + data->dev = dev; + k_fifo_init(&data->fifo_input_in); + k_fifo_init(&data->fifo_input_out); + k_fifo_init(&data->fifo_output_in); + k_fifo_init(&data->fifo_output_out); + + ret = video_sw_isp_set_fmt(dev, VIDEO_EP_IN, &fmt_in); + if (ret != 0) { + return ret; + } + + ret = video_sw_isp_set_fmt(dev, VIDEO_EP_OUT, &fmt_out); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct video_sw_isp_data video_sw_isp_data_0 = { + .fmt_in.pixelformat = VIDEO_PIX_FMT_RGB24, + .fmt_out.pixelformat = VIDEO_PIX_FMT_RGB24, +}; + +DEVICE_DEFINE(video_sw_isp, "VIDEO_SW_ISP", &video_sw_isp_init, NULL, &video_sw_isp_data_0, NULL, + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &video_sw_isp_driver_api); + +K_THREAD_DEFINE(video_sw_isp, CONFIG_VIDEO_SW_ISP_STACK_SIZE, video_sw_isp_thread, + &video_sw_isp_data_0, NULL, NULL, CONFIG_VIDEO_SW_ISP_THREAD_PRIORITY, 0, 0); diff --git a/tests/drivers/build_all/video/prj.conf b/tests/drivers/build_all/video/prj.conf index b96d041d3b40..60ee761e7fbe 100644 --- a/tests/drivers/build_all/video/prj.conf +++ b/tests/drivers/build_all/video/prj.conf @@ -3,3 +3,4 @@ CONFIG_TEST_USERSPACE=y CONFIG_GPIO=y CONFIG_I2C=y CONFIG_VIDEO=y +CONFIG_VIDEO_SW_ISP=y