В настоящее время я работаю над проектом, включающим ESP32. ESP32 получает изображение BMP со страницы PHP, которая преобразует JPG в BMP. Затем ESP32 необходимо преобразовать изображение BMP в данные RGB565 для отображения с использованием LVGL. Проблема не в дисплее или LVGL, стандартные изображения отображаются корректно. Однако когда преобразованное изображение отображается, оно узнаваемо, но цветные пиксели кажутся расположенными случайным образом. Преобразованное изображение BMP выглядит отлично при просмотре в Windows.
Вот коды, которые вызывают у меня упомянутую проблему.
PHP-код для получения и преобразования изображения:
<?php
// Function to make a request to the Mouser API and fetch the image
function fetchAndConvertImage() {
// API key for accessing the Mouser API
$apiKey = 'MY_API_KEY';
// URL for making a request to the Mouser API
$url = 'https://api.mouser.com/api/v1/search/keyword?apiKey=' . $apiKey;
// Data to be sent in the request
$requestData = array(
'SearchByKeywordRequest' => array(
'keyword' => 'Thermistor',
'records' => 0,
'startingRecord' => 0,
'searchOptions' => 'string',
'searchWithYourSignUpLanguage' => 'en'
)
);
// Encode the data in JSON format
$jsonData = json_encode($requestData);
// Set up the options for the POST request
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => $jsonData
)
);
// Create a context for the request
$context = stream_context_create($options);
// Make the request to the Mouser API
$response = file_get_contents($url, false, $context);
// Decode the JSON response
$responseData = json_decode($response, true);
// Extract the image path from the response
$imagePath = $responseData['SearchResults']['Parts'][0]['ImagePath'];
// Read the image data from the specified URL
$imageData = file_get_contents("$imagePath");
// Generate a unique file name
$fileName = uniqid('image_', true) . '.bmp';
$filePath = __DIR__ . '/' . $fileName;
// Get image dimensions
$image = imagecreatefromstring($imageData);
$width = imagesx($image);
$height = imagesy($image);
// Calculate row size including padding
$rowSize = floor(($width * 3 + 3) / 4) * 4; // Each pixel is 3 bytes (RGB), and we need to ensure each row is a multiple of 4 bytes
// BMP header
$bmpHeader = pack('c', 0x42) . pack('c', 0x4D); // Signature BM
$bmpHeader .= pack('V', 14 + 40 + ($rowSize * $height)); // File size
$bmpHeader .= pack('v', 0) . pack('v', 0); // Reserved
$bmpHeader .= pack('V', 14 + 40); // Data offset
$bmpHeader .= pack('V', 40); // Header size
$bmpHeader .= pack('V', $width); // Image width
$bmpHeader .= pack('V', $height); // Image height
$bmpHeader .= pack('v', 1); // Number of planes
$bmpHeader .= pack('v', 24); // Bits per pixel
$bmpHeader .= pack('V', 0); // Compression (0 = no compression)
$bmpHeader .= pack('V', 0); // Image size (can be 0 when no compression)
$bmpHeader .= pack('V', 0); // Horizontal resolution (pixels per meter)
$bmpHeader .= pack('V', 0); // Vertical resolution (pixels per meter)
$bmpHeader .= pack('V', 0); // Number of colors in the palette
$bmpHeader .= pack('V', 0); // Number of important colors
// Convert image data to BMP format
$bmpData = '';
for ($y = $height - 1; $y >= 0; --$y) {
for ($x = 0; $x < $width; ++$x) {
$rgb = imagecolorat($image, $x, $y);
$color = imagecolorsforindex($image, $rgb);
$bmpData .= pack('C', $color['blue']); // Blue
$bmpData .= pack('C', $color['green']); // Green
$bmpData .= pack('C', $color['red']); // Red
}
// Add padding to ensure row size is a multiple of 4 bytes
$paddingBytes = $rowSize - ($width * 3);
$bmpData .= str_repeat(pack('C', 0), $paddingBytes);
}
// Save BMP file
file_put_contents($filePath, $bmpHeader . $bmpData);
// Free memory
imagedestroy($image);
// Return file name
return $fileName;
}
// Call the function to fetch and convert the image
$imagePath = fetchAndConvertImage();
echo "https://theyoungmaker.ddns.net/opencomponent/$imagePath";
?>
Код ESP32 для получения и преобразования изображения BMP:
bool getImageFromURL(String url) {
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
WiFiClient* stream = http.getStreamPtr();
size_t totalLen = http.getSize();
size_t len = totalLen;
// Allocate buffer for the image data
uint8_t* bmp_data = (uint8_t*)malloc(len);
if (!bmp_data) {
Serial.println("Failed to allocate memory for image data");
http.end();
return false;
}
// Read data into buffer
size_t index = 0;
while (http.connected() && (len > 0 || len == -1)) {
size_t size = stream->available();
if (size) {
int c = stream->readBytes((char*)(bmp_data + index), size);
index += c;
if (len > 0) {
len -= c;
}
}
delay(1);
}
// Parse BMP header
if (bmp_data[0] != 'B' || bmp_data[1] != 'M') {
Serial.println("Not a valid BMP file");
free(bmp_data);
http.end();
return false;
}
uint32_t dataOffset = *((uint32_t*)(bmp_data + 10));
uint32_t width = *((uint32_t*)(bmp_data + 18));
uint32_t height = *((uint32_t*)(bmp_data + 22));
uint16_t bitsPerPixel = *((uint16_t*)(bmp_data + 28));
uint32_t compression = *((uint32_t*)(bmp_data + 30));
if (bitsPerPixel != 24 || compression != 0) {
Serial.println("Unsupported BMP format");
free(bmp_data);
http.end();
return false;
}
// Allocate buffer for the image data in 16-bit format (RGB565)
size_t imgSize = width * height * 2;
gui_img_nouveau_projet_png_data = (uint8_t*)malloc(imgSize);
if (!gui_img_nouveau_projet_png_data) {
Serial.println("Failed to allocate memory for image data");
free(bmp_data);
http.end();
return false;
}
// Convert pixel data to 16-bit (RGB565) format
uint8_t* src = bmp_data + dataOffset;
uint16_t* dst = (uint16_t*)gui_img_nouveau_projet_png_data;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t b = *src++;
uint8_t g = *src++;
uint8_t r = *src++;
*dst++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// Skip padding bytes if any
src += (4 - (width * 3) % 4) % 4;
}
// Set image descriptor data
gui_img_nouveau_projet_png.header.always_zero = 0;
gui_img_nouveau_projet_png.header.w = width;
gui_img_nouveau_projet_png.header.h = height;
gui_img_nouveau_projet_png.data_size = imgSize;
gui_img_nouveau_projet_png.header.cf = LV_IMG_CF_TRUE_COLOR;
gui_img_nouveau_projet_png.data = gui_img_nouveau_projet_png_data;
free(bmp_data);
http.end();
return true;
} else {
Serial.printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
http.end();
return false;
}
}
Кто-нибудь знает, почему возникает эта проблема? Я не уверен, связана ли проблема с преобразованием JPG в BMP на стороне сервера или с преобразованием BMP в RGB565 на ESP32. Любые идеи или предложения будут с благодарностью приняты.
Исходное изображение:
Изображение, отображаемое на дисплее:
Да, это совершенно нормально. Однако я обнаружил, что иногда, если заголовки BMP установлены неправильно, компьютер может просматривать их отлично, но esp32 считает, что это недопустимое изображение BMP.
Если все в порядке, то вы знаете, что проблема связана с кодом ESP32, а не с кодом сервера. Если вы знаете, что он генерирует неправильные заголовки BMP, значит, вы знаете, что код сервера работает неправильно...
Проблема в том, что я недостаточно знаю о BMP, чтобы даже знать, какой заголовок нужен и какие данные в нем (код в основном написан ChatGPT...). Но я почти уверен, что проблема связана с кодом esp32.
Вам нужно выяснить, является ли источником проблем ваш PHP или ESP32. Создайте несколько случайных BMP, как показано здесь stackoverflow.com/a/29011461/2836621, и посмотрите, правильно ли их отображает код ESP32. Или просто создайте простые градиенты и попробуйте отправить их magick -size 320x200 gradient:red-blue image.bmp, тогда вы увидите, правильно ли интерпретируются цвета и формы.
Я использовал тестовое изображение, как вы предложили, и все формы хорошие. единственное, что не так, это цвет. Я использовал следующее изображение: people.math.sc.edu/Burkardt/data/bmp/snail.bmp Но на дисплее оно имеет фиолетовый оттенок. Кто-нибудь думает, что это может быть из-за свопа (16-битного свопа), который понадобится?
Если вы создадите сплошное синее изображение с помощью ImageMagick, вы увидите, станет ли оно синим. magick -size 320x240 xc:blue blue.bmp Аналогично для зелёных и красных magick -size 320x240 xc:lime green.bmp и magick -size 320x240 xc:red red.bmp.






Мне удалось правильно отобразить изображение, поменяв местами часть изображения, а затем перевернув его по вертикали. Вот исправленная функция:
bool getImageFromURL(String url) {
HTTPClient http;
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
WiFiClient* stream = http.getStreamPtr();
size_t totalLen = http.getSize();
size_t len = totalLen;
// Allocate buffer for the image data
uint8_t* bmp_data = (uint8_t*)malloc(len);
if (!bmp_data) {
Serial.println("Failed to allocate memory for image data");
http.end();
return false;
}
// Read data into buffer
size_t index = 0;
while (http.connected() && (len > 0 || len == -1)) {
size_t size = stream->available();
if (size) {
int c = stream->readBytes((char*)(bmp_data + index), size);
index += c;
if (len > 0) {
len -= c;
}
}
delay(1);
}
// Parse BMP header
if (bmp_data[0] != 'B' || bmp_data[1] != 'M') {
Serial.println("Not a valid BMP file");
free(bmp_data);
http.end();
return false;
}
uint32_t dataOffset = *((uint32_t*)(bmp_data + 10));
uint32_t width = *((uint32_t*)(bmp_data + 18));
uint32_t height = *((uint32_t*)(bmp_data + 22));
uint16_t bitsPerPixel = *((uint16_t*)(bmp_data + 28));
uint32_t compression = *((uint32_t*)(bmp_data + 30));
if (bitsPerPixel != 24 || compression != 0) {
Serial.println("Unsupported BMP format");
free(bmp_data);
http.end();
return false;
}
// Allocate buffer for the image data in 16-bit format (RGB565)
size_t imgSize = width * height * 2;
gui_img_nouveau_projet_png_data = (uint8_t*)malloc(imgSize);
if (!gui_img_nouveau_projet_png_data) {
Serial.println("Failed to allocate memory for image data");
free(bmp_data);
http.end();
return false;
}
// Convert pixel data to 16-bit (RGB565) format with byte swapping
uint8_t* src = bmp_data + dataOffset;
uint16_t* dst = (uint16_t*)gui_img_nouveau_projet_png_data;
// Calculate padding
size_t rowSize = (width * 3 + 3) & ~3; // Each row is aligned to a multiple of 4 bytes
size_t padding = rowSize - width * 3;
for (int y = 0; y < height; y++) {
uint8_t* rowSrc = src + (height - 1 - y) * rowSize;
for (int x = 0; x < width; x++) {
uint8_t b = *rowSrc++;
uint8_t g = *rowSrc++;
uint8_t r = *rowSrc++;
uint16_t pixel = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
// Swap bytes
pixel = (pixel >> 8) | (pixel << 8);
*dst++ = pixel;
}
// Skip padding bytes
rowSrc += padding;
}
// Set image descriptor data
gui_img_nouveau_projet_png.header.always_zero = 0;
gui_img_nouveau_projet_png.header.w = width;
gui_img_nouveau_projet_png.header.h = height;
gui_img_nouveau_projet_png.data_size = imgSize;
gui_img_nouveau_projet_png.header.cf = LV_IMG_CF_TRUE_COLOR;
gui_img_nouveau_projet_png.data = gui_img_nouveau_projet_png_data;
free(bmp_data);
http.end();
return true;
} else {
Serial.printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
http.end();
return false;
}
}
Является ли BMP, создаваемый сервером, правильным, если вы посмотрите на него на другом устройстве? Это было бы самое первое, что нужно проверить.