where is the error code to make the image use a transparent background without destroying the image resolution?
// Helper function for bilinear interpolation with improved alpha handling
uint16_t interpolateColor(uint8_t r1, uint8_t g1, uint8_t b1, uint8_t a1,
uint8_t r2, uint8_t g2, uint8_t b2, uint8_t a2,
uint8_t r3, uint8_t g3, uint8_t b3, uint8_t a3,
uint8_t r4, uint8_t g4, uint8_t b4, uint8_t a4,
float x_ratio, float y_ratio) {
// Calculate weighted average of the four pixels
float r = (r1 * (1 - x_ratio) * (1 - y_ratio) +
r2 * x_ratio * (1 - y_ratio) +
r3 * (1 - x_ratio) * y_ratio +
r4 * x_ratio * y_ratio);
float g = (g1 * (1 - x_ratio) * (1 - y_ratio) +
g2 * x_ratio * (1 - y_ratio) +
g3 * (1 - x_ratio) * y_ratio +
g4 * x_ratio * y_ratio);
float b = (b1 * (1 - x_ratio) * (1 - y_ratio) +
b2 * x_ratio * (1 - y_ratio) +
b3 * (1 - x_ratio) * y_ratio +
b4 * x_ratio * y_ratio);
float a = (a1 * (1 - x_ratio) * (1 - y_ratio) +
a2 * x_ratio * (1 - y_ratio) +
a3 * (1 - x_ratio) * y_ratio +
a4 * x_ratio * y_ratio);
// Improved alpha handling
// You can adjust these background color values to match your display background
uint8_t bgR = 0; // Background red (255 for white)
uint8_t bgG = 0; // Background green (255 for white)
uint8_t bgB = 0; // Background blue (255 for white)
// If completely transparent, return background color
if (a == 0) return ((bgR >> 3) << 11) | ((bgG >> 2) << 5) | (bgB >> 3);
// For semi-transparent pixels, blend with background
if (a < 255) {
float alpha = a / 255.0;
r = r * alpha + bgR * (1 - alpha);
g = g * alpha + bgG * (1 - alpha);
b = b * alpha + bgB * (1 - alpha);
}
// Convert to RGB565 format
uint16_t color = ((uint8_t(r) >> 3) << 11) | ((uint8_t(g) >> 2) << 5) | (uint8_t(b) >> 3);
return color;
}
// Helper function to read 32-bit value
uint32_t read32(File &f) {
uint32_t result;
f.read((uint8_t *)&result, 4);
return result;
}
void drawBMP(const char *filename, int16_t x, int16_t y, int16_t targetWidth, int16_t targetHeight, bool forceExactSize = false, bool preserveAspectRatio = true) {
// Validate parameters
if (targetWidth <= 0 || targetHeight <= 0) {
Serial.println(F("Parameter ukuran tidak valid"));
return;
}
// Open BMP file
File bmpFile = LittleFS.open(filename, "r");
if (!bmpFile) {
Serial.println(F("Gagal membuka file BMP"));
return;
}
// Read BMP header
uint16_t bmpType;
bmpFile.seek(0);
bmpFile.read((uint8_t *)&bmpType, 2);
// Verify BMP signature
if (bmpType != 0x4D42) { // 'BM' in little-endian
Serial.println(F("File bukan format BMP yang valid"));
bmpFile.close();
return;
}
// Read important metadata
bmpFile.seek(0x0A);
uint32_t imageOffset = read32(bmpFile);
bmpFile.seek(0x12);
uint32_t imageWidth = read32(bmpFile);
uint32_t imageHeight = read32(bmpFile);
bmpFile.seek(0x1C);
uint16_t bitsPerPixel;
bmpFile.read((uint8_t *)&bitsPerPixel, 2);
// Check if BMP format is supported
if (bitsPerPixel != 24 && bitsPerPixel != 32) {
Serial.println(F("Format BMP tidak didukung (harus 24 atau 32-bit)"));
bmpFile.close();
return;
}
// Calculate bytes per pixel and row padding
uint8_t bytesPerPixel = bitsPerPixel / 8;
uint32_t rowSize = (imageWidth * bytesPerPixel + 3) & ~3; // Row size is padded to 4-byte boundary
// Calculate scaling
float scaleX = (float)targetWidth / imageWidth;
float scaleY = (float)targetHeight / imageHeight;
float scale;
// Improved scaling logic for small images
if (forceExactSize) {
// Use the specified dimensions exactly
scaleX = (float)targetWidth / imageWidth;
scaleY = (float)targetHeight / imageHeight;
} else {
// For small images, we might want to scale up to fill more of the target area
if (imageWidth <= targetWidth && imageHeight <= targetHeight) {
// Image is smaller than target area - don't scale down
scaleX = scaleY = 1.0f; // Maintain original size
} else {
// Image is larger than target area - scale down to fit while maintaining aspect ratio
scale = min(scaleX, scaleY);
scaleX = scaleY = scale;
}
}
int16_t actualWidth = round(imageWidth * scaleX);
int16_t actualHeight = round(imageHeight * scaleY);
// Ensure we have valid dimensions after scaling
if (actualWidth <= 0) actualWidth = 1;
if (actualHeight <= 0) actualHeight = 1;
// Center image in target area
int16_t startX = x + (targetWidth - actualWidth) / 2;
int16_t startY = y + (targetHeight - actualHeight) / 2;
// Ensure the image fits within the display borders
// If image would go outside border, adjust position but don't resize
if (startX < BORDER_START_X) {
startX = BORDER_START_X;
}
if (startY < BORDER_START_Y) {
startY = BORDER_START_Y;
}
// If image extends beyond border, we'll clip during drawing
bool willBeClipped = false;
if (startX + actualWidth > BORDER_START_X + BORDER_WIDTH ||
startY + actualHeight > BORDER_START_Y + BORDER_HEIGHT) {
willBeClipped = true;
}
// Allocate buffer for two rows to enable bilinear interpolation
uint8_t *lineBuffer1 = new uint8_t[rowSize];
uint8_t *lineBuffer2 = new uint8_t[rowSize];
if (!lineBuffer1 || !lineBuffer2) {
Serial.println(F("Gagal mengalokasikan buffer"));
if (lineBuffer1) delete[] lineBuffer1;
if (lineBuffer2) delete[] lineBuffer2;
bmpFile.close();
return;
}
// Use SPI transaction for faster drawing if available
#ifdef ESP32
tft.startWrite();
#endif
// Process image using bilinear interpolation
uint32_t lastSourceRow = 0xFFFFFFFF;
uint32_t lastSourceRowPlus1 = 0xFFFFFFFF;
// Draw the image row by row
for (int16_t row = 0; row < actualHeight; row++) {
int16_t screenY = startY + row;
// Skip rows that are outside the display area
if (screenY < BORDER_START_Y || screenY >= BORDER_START_Y + BORDER_HEIGHT) {
continue;
}
// Calculate source row position with improved scaling
float sourceRowRatio = (float)row / actualHeight;
uint32_t sourceRow = imageHeight - 1 - min((uint32_t)(sourceRowRatio * imageHeight), imageHeight - 1);
uint32_t sourceRowPlus1 = (sourceRow > 0) ? (sourceRow - 1) : 0;
float y_ratio = (sourceRowRatio * imageHeight) - floor(sourceRowRatio * imageHeight);
// Load the two rows needed for interpolation (if not already loaded)
if (sourceRow != lastSourceRow) {
bmpFile.seek(imageOffset + (sourceRow * rowSize));
bmpFile.read(lineBuffer1, rowSize);
lastSourceRow = sourceRow;
}
if (sourceRowPlus1 != lastSourceRowPlus1) {
bmpFile.seek(imageOffset + (sourceRowPlus1 * rowSize));
bmpFile.read(lineBuffer2, rowSize);
lastSourceRowPlus1 = sourceRowPlus1;
}
// Process each column in the current row
for (int16_t col = 0; col < actualWidth; col++) {
int16_t screenX = startX + col;
// Skip pixels that are outside the display area
if (screenX < BORDER_START_X || screenX >= BORDER_START_X + BORDER_WIDTH) {
continue;
}
// Calculate source column position with improved scaling
float sourceColRatio = (float)col / actualWidth;
uint32_t sourceCol = min((uint32_t)(sourceColRatio * imageWidth), imageWidth - 1);
uint32_t sourceColPlus1 = min(sourceCol + 1, imageWidth - 1);
float x_ratio = (sourceColRatio * imageWidth) - floor(sourceColRatio * imageWidth);
// Get the four pixels for bilinear interpolation
uint32_t idx1 = sourceCol * bytesPerPixel;
uint32_t idx2 = sourceColPlus1 * bytesPerPixel;
// Safety check for buffer overflow
if (idx1 >= rowSize) idx1 = rowSize - bytesPerPixel;
if (idx2 >= rowSize) idx2 = rowSize - bytesPerPixel;
uint8_t b1 = lineBuffer1[idx1], g1 = lineBuffer1[idx1 + 1], r1 = lineBuffer1[idx1 + 2];
uint8_t b2 = lineBuffer1[idx2], g2 = lineBuffer1[idx2 + 1], r2 = lineBuffer1[idx2 + 2];
uint8_t b3 = lineBuffer2[idx1], g3 = lineBuffer2[idx1 + 1], r3 = lineBuffer2[idx1 + 2];
uint8_t b4 = lineBuffer2[idx2], g4 = lineBuffer2[idx2 + 1], r4 = lineBuffer2[idx2 + 2];
// Default alpha values for 24-bit BMP (fully opaque)
uint8_t a1 = 255, a2 = 255, a3 = 255, a4 = 255;
// If it's a 32-bit BMP with alpha channel
if (bytesPerPixel == 4) {
a1 = lineBuffer1[idx1 + 3];
a2 = lineBuffer1[idx2 + 3];
a3 = lineBuffer2[idx1 + 3];
a4 = lineBuffer2[idx2 + 3];
}
// Interpolate between the four pixels
uint16_t color = interpolateColor(r1, g1, b1, a1, r2, g2, b2, a2,
r3, g3, b3, a3, r4, g4, b4, a4,
x_ratio, y_ratio);
// Direct drawing to screen
#ifdef ESP32
tft.writePixel(screenX, screenY, color);
#else
tft.drawPixel(screenX, screenY, color);
#endif
}
// Yield to prevent watchdog timer reset on ESP devices
#if defined(ESP8266) || defined(ESP32)
if (row % 10 == 0) yield();
#endif
}
// End SPI transaction if used
#ifdef ESP32
tft.endWrite();
#endif
// Clean up resources
delete[] lineBuffer1;
delete[] lineBuffer2;
bmpFile.close();
if (willBeClipped) {
Serial.println(F("Gambar berhasil ditampilkan (sebagian terpotong)"));
} else {
Serial.println(F("Gambar berhasil ditampilkan"));
}
}