Files

200 lines
7.5 KiB
C

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_lcd_panel_ops.h"
#include "esp_check.h"
#include "example_crop.h"
#include "example_buffer.h"
#include "example_config.h"
static const char *TAG = "example_crop";
esp_err_t example_isp_crop_init(example_pingpong_buffer_ctx_t *ctx, int crop_h_res, int crop_v_res)
{
ESP_RETURN_ON_FALSE(ctx != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
ctx->crop_h_res = crop_h_res;
ctx->crop_v_res = crop_v_res;
ctx->pending_buffer = NULL;
ctx->frame_ready_sem = xSemaphoreCreateBinary();
ESP_RETURN_ON_FALSE(ctx->frame_ready_sem != NULL, ESP_ERR_NO_MEM, TAG, "Failed to create frame ready semaphore");
return ESP_OK;
}
/**
* @brief Process frame: Add blank areas to fill full screen resolution
*
* Algorithm: Fill from bottom to top to avoid overwriting crop data
* - Cropped image is placed at original position (relative to full frame)
* - Other areas are filled with white (0xFFFF for RGB565)
*
* Illustration of crop operation:
*
* @verbatim
* Full Frame (h_res x v_res) Cropped Area
* ┌─────────────────────────┐ ┌───────────────────────┐
* │(0,0) │ │(crop_left, │
* │ Blank Area │ │ crop_top) │
* │ ┌──────────────┐ │ │ │
* │ │ │ │ ===> │ │
* │ │ CROP Area │ │ │ CROP Area │
* │ │ │ │ │ │
* │ └──────────────┘ │ │ │
* │ (h_res, │ │ (crop_right, │
* │ v_res) │ │ crop_bottom)│
* └─────────────────────────┘ └───────────────────────┘
* crop_width x crop_height
* @endverbatim
*
* @param[in,out] buffer Frame buffer to process (contains cropped image at start)
* @param[in] ctx Ping-pong buffer context
*
* @note This function is necessary to ensure that the display matches the cropped image resolution
*/
static void example_isp_process_frame_with_blanks(void *buffer, example_pingpong_buffer_ctx_t *ctx)
{
if (ctx == NULL || buffer == NULL) {
return;
}
if (ctx->crop_v_res == ctx->v_res) {
// No cropping, no need to process
return;
}
uint16_t *fb = (uint16_t *)buffer;
const int crop_left = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H;
const int crop_top = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V;
const int crop_right = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H;
const int crop_bottom = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V;
const int crop_width = crop_right - crop_left + 1;
const int full_width = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES;
const int full_height = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES;
// Helper macros for pixel indexing
#define SRC_PIXEL(x, y) fb[(y) * crop_width + (x)]
#define DST_PIXEL(x, y) fb[(y) * full_width + (x)]
// ========== Step 1: Fill bottom blank region [crop_bottom+1, full_height) ==========
if (crop_bottom + 1 < full_height) {
memset(&DST_PIXEL(0, crop_bottom + 1),
0xFF,
full_width * (full_height - crop_bottom - 1) * EXAMPLE_RGB565_BYTES_PER_PIXEL);
}
// ========== Step 2: Process cropped region [crop_top, crop_bottom] ==========
for (int y = crop_bottom; y >= crop_top; y--) {
int src_y = y - crop_top; // Corresponding row in cropped data (0-based)
// Fill right blank region first (crop_right+1, full_width)
if (crop_right + 1 < full_width) {
memset(&DST_PIXEL(crop_right + 1, y),
0xFF,
(full_width - crop_right - 1) * EXAMPLE_RGB565_BYTES_PER_PIXEL);
}
// Copy crop data from source to destination
memcpy(&DST_PIXEL(crop_left, y),
&SRC_PIXEL(0, src_y),
crop_width * EXAMPLE_RGB565_BYTES_PER_PIXEL);
// Fill left blank region [0, crop_left)
if (crop_left > 0) {
memset(&DST_PIXEL(0, y),
0xFF,
crop_left * EXAMPLE_RGB565_BYTES_PER_PIXEL);
}
}
// ========== Step 3: Fill top blank region [0, crop_top) ==========
if (crop_top > 0) {
memset(&DST_PIXEL(0, 0),
0xFF,
full_width * crop_top * EXAMPLE_RGB565_BYTES_PER_PIXEL);
}
#undef SRC_PIXEL
#undef DST_PIXEL
}
/**
* @brief Frame processing task
*
* This task processes frames when CROP is enabled:
* - Waits for frame ready signal from ISR
* - Processes the frame (adds blank areas if needed)
* - Swaps buffers
* - Triggers display update
*/
static void example_frame_processing_task(void *arg)
{
example_pingpong_buffer_ctx_t *ctx = (example_pingpong_buffer_ctx_t *)arg;
while (1) {
// Wait for frame ready signal from ISR
if (xSemaphoreTake(ctx->frame_ready_sem, portMAX_DELAY) == pdTRUE) {
// Process the frame: add blank areas if needed
example_isp_process_frame_with_blanks(ctx->pending_buffer, ctx);
// Ping-Pong switch: swap CSI write buffer and DSI display buffer
example_isp_buffer_swap(ctx);
// Trigger buffer switch by calling draw_bitmap
// DPI driver will detect which buffer we're using and switch to it
esp_err_t ret = esp_lcd_panel_draw_bitmap(ctx->panel,
0, 0,
ctx->h_res,
ctx->crop_v_res,
ctx->pending_buffer);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to draw bitmap: %d", ret);
}
ESP_LOGD(TAG, "Frame displayed: %p", ctx->pending_buffer);
}
}
}
esp_err_t example_isp_crop_start_frame_processing_task(example_pingpong_buffer_ctx_t *ctx,
int task_priority,
int core_id)
{
if (ctx == NULL) {
return ESP_ERR_INVALID_ARG;
}
BaseType_t ret = xTaskCreatePinnedToCore(example_frame_processing_task,
"frame_proc",
4096,
ctx,
task_priority,
NULL,
core_id);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create frame processing task");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Frame processing task created");
return ESP_OK;
}
bool example_isp_crop_frame_ready_routine(example_pingpong_buffer_ctx_t *ctx, void *buffer)
{
if (ctx == NULL || buffer == NULL) {
return false;
}
BaseType_t high_task_wakeup = pdFALSE;
ctx->pending_buffer = buffer;
xSemaphoreGiveFromISR(ctx->frame_ready_sem, &high_task_wakeup);
return (high_task_wakeup == pdTRUE);
}