mirror of
https://github.com/espressif/esp-idf.git
synced 2026-01-19 11:45:49 +00:00
200 lines
7.5 KiB
C
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);
|
|
}
|