| 1059 | } |
| 1060 | |
| 1061 | void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, |
| 1062 | const float cropX, const float cropY) const { |
| 1063 | if (fontCacheManager_ && fontCacheManager_->isScanning()) return; |
| 1064 | // For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit) |
| 1065 | if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) { |
| 1066 | drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight); |
| 1067 | return; |
| 1068 | } |
| 1069 | |
| 1070 | float scale = 1.0f; |
| 1071 | bool isScaled = false; |
| 1072 | int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f); |
| 1073 | int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f); |
| 1074 | LOG_DBG("GFX", "Cropping %dx%d by %dx%d pix, is %s", bitmap.getWidth(), bitmap.getHeight(), cropPixX, cropPixY, |
| 1075 | bitmap.isTopDown() ? "top-down" : "bottom-up"); |
| 1076 | |
| 1077 | const float croppedWidth = (1.0f - cropX) * static_cast<float>(bitmap.getWidth()); |
| 1078 | const float croppedHeight = (1.0f - cropY) * static_cast<float>(bitmap.getHeight()); |
| 1079 | bool hasTargetBounds = false; |
| 1080 | float fitScale = 1.0f; |
| 1081 | |
| 1082 | if (maxWidth > 0 && croppedWidth > 0.0f) { |
| 1083 | fitScale = static_cast<float>(maxWidth) / croppedWidth; |
| 1084 | hasTargetBounds = true; |
| 1085 | } |
| 1086 | |
| 1087 | if (maxHeight > 0 && croppedHeight > 0.0f) { |
| 1088 | const float heightScale = static_cast<float>(maxHeight) / croppedHeight; |
| 1089 | fitScale = hasTargetBounds ? std::min(fitScale, heightScale) : heightScale; |
| 1090 | hasTargetBounds = true; |
| 1091 | } |
| 1092 | |
| 1093 | if (hasTargetBounds && fitScale < 1.0f) { |
| 1094 | scale = fitScale; |
| 1095 | isScaled = true; |
| 1096 | } |
| 1097 | LOG_DBG("GFX", "Scaling by %f - %s", scale, isScaled ? "scaled" : "not scaled"); |
| 1098 | |
| 1099 | // Calculate output row size (2 bits per pixel, packed into bytes) |
| 1100 | // IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide |
| 1101 | const int outputRowSize = (bitmap.getWidth() + 3) / 4; |
| 1102 | auto* outputRow = static_cast<uint8_t*>(malloc(outputRowSize)); |
| 1103 | auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes())); |
| 1104 | |
| 1105 | if (!outputRow || !rowBytes) { |
| 1106 | LOG_ERR("GFX", "!! Failed to allocate BMP row buffers"); |
| 1107 | free(outputRow); |
| 1108 | free(rowBytes); |
| 1109 | return; |
| 1110 | } |
| 1111 | |
| 1112 | for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) { |
| 1113 | // The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative). |
| 1114 | // Screen's (0, 0) is the top-left corner. |
| 1115 | int screenY = -cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY); |
| 1116 | if (isScaled) { |
| 1117 | screenY = std::floor(screenY * scale); |
| 1118 | } |
no test coverage detected