Soften the image so it doesn't break

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);
    }
}

Bilinear image interpolation tutorial

Adafruit has also presented code for bicubic interpolation, which works better than bilinear, but is slower.

Why are you even discussing thermal cameras?

If you didn't bother to look, the links describe

for some definitions of "smoothen".

Good luck with your project.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.