The image background is not transparent

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

Hi @bimosora ,

just scanned the code on my Smartphone display...:wink:

Looks like the relevant part is the one quoted above. Transparency depends on the source format, it must be a 32 bit bitmap that has one byte per color R,G,B and one byte Alpha channel (the one that defines the transparency of the pixel).

Alpha = 0 is completely transparent (uses the background color) while Alpha = 255 is opaque (displays the original RGB color). In all other cases
the color is a function of RGB and Alpha as defined in the function "interpolateColor".

The transparency calculation does not change the resolution, just the color.

I just changed this part and everything is done and the image background can be transparent, now all that remains is to make the image clearer and not break up
before

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

after

  if (a < 255) {
    // Extract background RGB components
    uint8_t bgR = ((backgroundColor >> 11) & 0x1F) << 3;
    uint8_t bgG = ((backgroundColor >> 5) & 0x3F) << 2;
    uint8_t bgB = (backgroundColor & 0x1F) << 3;
    
    // Alpha blending formula: final = (foreground * alpha) + (background * (1 - alpha))
    float alphaFactor = a / 255.0;
    r = (r * alphaFactor) + (bgR * (1 - alphaFactor));
    g = (g * alphaFactor) + (bgG * (1 - alphaFactor));
    b = (b * alphaFactor) + (bgB * (1 - alphaFactor));
  }

If you still have problems with the resolution you might have a look at this part

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

Inside the function interpolateColor() the data of four pixels (2 x 2 ) are combined to one pixel depending on x_ratio and y_ratio.

[Edit]:
Started to analyze how the source image is transposed to the target image. If I'm not mistaken the source rows and source columns which are used for interpolation are calculated in a way that will skip original content if the source resolution is more than 2 times higher in x and/or y direction.

The method is saving time but may lead to visible problems (depending on the original content).

There are two solutions I can think of:

  • Easy: Rescale the pictures on your PC/Mac so that they fit to the display dimensions and remove the interpolation function (just leave the transparency code) .
  • More effort: Rewrite the scaling function in a way that a target pixel is calculated from all relevant source pixels rather than from only a few chosen source pixels.