How to smoothen image so it doesn't break in Adafruit_ST7735 and Adafruit_GFX libraries regardless of size, I use bmp image?
void drawBMP(const char *filename, int16_t x, int16_t y, int16_t targetWidth, int16_t targetHeight, bool forceExactSize = false) {
// Initial parameter validation
if (x < 0 || y < 0 || targetWidth <= 0 || targetHeight <= 0) {
Serial.println("Invalid drawing parameters");
return;
}
File bmpFile = LittleFS.open(filename, "r");
if (!bmpFile) {
Serial.println("Failed to open BMP file");
return;
}
// Read BMP header
bmpFile.seek(0x0A);
uint32_t imageOffset = read32(bmpFile);
bmpFile.seek(0x12);
uint32_t imageWidth = read32(bmpFile);
uint32_t imageHeight = read32(bmpFile);
// Debug logging
//Serial.printf("Image dimensions: %dx%d\n", imageWidth, imageHeight);
//Serial.printf("Target dimensions: %dx%d\n", targetWidth, targetHeight);
// Calculate scaling factors
float scaleX = (float)targetWidth / imageWidth;
float scaleY = (float)targetHeight / imageHeight;
float scale;
if (forceExactSize) {
// Use separate X and Y scaling for exact size matching
// This prevents distortion for battery icon
scaleX = (float)targetWidth / imageWidth;
scaleY = (float)targetHeight / imageHeight;
} else {
// For normal icons, maintain aspect ratio
scale = min(scaleX, scaleY);
scaleX = scaleY = min(scale, 1.0f);
}
int16_t actualWidth = forceExactSize ? targetWidth : round(imageWidth * scaleX);
int16_t actualHeight = forceExactSize ? targetHeight : round(imageHeight * scaleY);
// Validate scaling results
if (actualWidth <= 0 || actualHeight <= 0) {
Serial.println("Invalid scaling result");
bmpFile.close();
return;
}
//Serial.printf("Actual dimensions after scaling: %dx%d\n", actualWidth, actualHeight);
// Center the image if not forcing exact size
if (!forceExactSize) {
x += (targetWidth - actualWidth) / 2;
y += (targetHeight - actualHeight) / 2;
// Ensure image stays within bounds
if (x + actualWidth > BORDER_START_X + BORDER_WIDTH) {
x = BORDER_START_X + BORDER_WIDTH - actualWidth;
}
if (y + actualHeight > BORDER_START_Y + BORDER_HEIGHT) {
y = BORDER_START_Y + BORDER_HEIGHT - actualHeight;
}
if (x < BORDER_START_X) {
x = BORDER_START_X;
}
if (y < BORDER_START_Y) {
y = BORDER_START_Y;
}
}
// Allocate line buffer
uint8_t* lineBuffer = new uint8_t[imageWidth * 4];
if (!lineBuffer) {
Serial.println("Failed to allocate line buffer");
bmpFile.close();
return;
}
// Draw the image
for (int16_t row = 0; row < actualHeight; row++) {
// Calculate source row position
float sourceRowRatio = (float)row / actualHeight;
int16_t sourceRow = imageHeight - 1 - (int16_t)(sourceRowRatio * imageHeight);
// Read source row data
bmpFile.seek(imageOffset + (sourceRow * imageWidth * 4));
bmpFile.read(lineBuffer, imageWidth * 4);
for (int16_t col = 0; col < actualWidth; col++) {
// Calculate source column position
float sourceColRatio = (float)col / actualWidth;
int16_t sourceCol = (int16_t)(sourceColRatio * imageWidth);
// Get RGBA values
uint8_t b = lineBuffer[sourceCol * 4];
uint8_t g = lineBuffer[sourceCol * 4 + 1];
uint8_t r = lineBuffer[sourceCol * 4 + 2];
uint8_t a = lineBuffer[sourceCol * 4 + 3];
// Only draw if pixel is visible
if (a > 127) {
int16_t screenX = x + col;
int16_t screenY = y + row;
// Check drawing boundaries
if (screenX >= BORDER_START_X &&
screenX < BORDER_START_X + BORDER_WIDTH &&
screenY >= BORDER_START_Y &&
screenY < BORDER_START_Y + BORDER_HEIGHT) {
// Convert color for display
uint8_t r5 = (r >> 3);
uint8_t g6 = (g >> 2);
uint8_t b5 = (b >> 3);
uint16_t color = (r5 << 11) | (g6 << 5) | b5;
tft.drawPixel(screenX, screenY, color);
}
}
}
}
delete[] lineBuffer;
bmpFile.close();
}
void displayBatteryStatus() {
// Adjust battery position relative to border
const int battX = BORDER_START_X + BORDER_WIDTH - BATT_IMG_WIDTH - CONTENT_MARGIN;
const int battY = BORDER_START_Y + CONTENT_MARGIN;
if (batteryStatus != prevBatteryStatus) {
tft.fillRect(battX, battY, BATT_IMG_WIDTH, BATT_IMG_HEIGHT, backgroundColor);
const char* batteryImage = batteryStatus == "100%" ? "/battery_100.bmp" :
batteryStatus == "50%" ? "/battery_50.bmp" :
batteryStatus == "20%" ? "/battery_20.bmp" : "/battery_0.bmp";
drawBMP(batteryImage, battX, battY, BATT_IMG_WIDTH, BATT_IMG_HEIGHT);
prevBatteryStatus = batteryStatus;
}
}
void drawMenuIcon(const char* iconPath, int y) {
int xCenter = BORDER_START_X + (BORDER_WIDTH - ICON_WIDTH) / 2;
y = BORDER_START_Y + y; // Hapus referensi ke MARGIN_TOP
if (LittleFS.exists(iconPath)) {
drawBMP(iconPath, xCenter, y, ICON_WIDTH, ICON_HEIGHT);
} else {
Serial.printf("Icon not found: %s\n", iconPath);
}
}
void drawSmallIcon(const char* iconPath, int x, int y) {
// Debug info
Serial.printf("Drawing icon at x:%d y:%d with size %dx%d\n",
x, y, SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
// Constrain position within border bounds
x = constrain(x, BORDER_START_X + CONTENT_MARGIN,
BORDER_START_X + BORDER_WIDTH - SMALL_ICON_WIDTH - CONTENT_MARGIN);
y = constrain(y, BORDER_START_Y + CONTENT_MARGIN,
BORDER_START_Y + BORDER_HEIGHT - SMALL_ICON_HEIGHT - CONTENT_MARGIN);
if (LittleFS.exists(iconPath)) {
drawBMP(iconPath, x, y, SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT);
} else {
Serial.printf("Small icon not found: %s\n", iconPath);
}
}