fix cover art sizing options

This commit is contained in:
cottongin
2026-01-22 13:13:12 -05:00
parent 9493fb1f18
commit 6b533207e1
4 changed files with 154 additions and 107 deletions

View File

@@ -161,21 +161,30 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
}
float scale = 1.0f;
bool isScaled = false;
int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f);
int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f);
Serial.printf("[%lu] [GFX] Cropping %dx%d by %dx%d pix, is %s\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
cropPixX, cropPixY, bitmap.isTopDown() ? "top-down" : "bottom-up");
if (maxWidth > 0 && (1.0f - cropX) * bitmap.getWidth() > maxWidth) {
scale = static_cast<float>(maxWidth) / static_cast<float>((1.0f - cropX) * bitmap.getWidth());
isScaled = true;
// Calculate effective image dimensions after cropping
const int effectiveWidth = static_cast<int>((1.0f - cropX) * bitmap.getWidth());
const int effectiveHeight = static_cast<int>((1.0f - cropY) * bitmap.getHeight());
// Calculate scale to fit within maxWidth/maxHeight (supports both up and down scaling)
if (maxWidth > 0 && maxHeight > 0) {
const float scaleX = static_cast<float>(maxWidth) / static_cast<float>(effectiveWidth);
const float scaleY = static_cast<float>(maxHeight) / static_cast<float>(effectiveHeight);
scale = std::min(scaleX, scaleY);
} else if (maxWidth > 0) {
scale = static_cast<float>(maxWidth) / static_cast<float>(effectiveWidth);
} else if (maxHeight > 0) {
scale = static_cast<float>(maxHeight) / static_cast<float>(effectiveHeight);
}
if (maxHeight > 0 && (1.0f - cropY) * bitmap.getHeight() > maxHeight) {
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
isScaled = true;
}
Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale, isScaled ? "scaled" : "not scaled");
const bool isUpscaling = scale > 1.0f;
const bool isDownscaling = scale < 1.0f;
Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale,
isUpscaling ? "upscaling" : (isDownscaling ? "downscaling" : "no scaling"));
// Calculate output row size (2 bits per pixel, packed into bytes)
// IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide
@@ -190,18 +199,10 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
return;
}
for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) {
// The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative).
// Screen's (0, 0) is the top-left corner.
int screenY = -cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY);
if (isScaled) {
screenY = std::floor(screenY * scale);
}
screenY += y; // the offset should not be scaled
if (screenY >= getScreenHeight()) {
break;
}
// Track the last drawn Y position for upscaling (to fill gaps)
int lastDrawnY = -1;
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) {
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY);
free(outputRow);
@@ -209,36 +210,51 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
return;
}
if (screenY < 0) {
// Skip rows in the crop area
if (bmpY < cropPixY || bmpY >= bitmap.getHeight() - cropPixY) {
continue;
}
if (bmpY < cropPixY) {
// Skip the row if it's outside the crop area
continue;
}
// Calculate the source Y coordinate (relative to cropped area)
const int srcY = bmpY - cropPixY;
for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++) {
int screenX = bmpX - cropPixX;
if (isScaled) {
screenX = std::floor(screenX * scale);
}
screenX += x; // the offset should not be scaled
if (screenX >= getScreenWidth()) {
break;
}
if (screenX < 0) {
continue;
}
// The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative).
// Screen's (0, 0) is the top-left corner.
const int logicalY = bitmap.isTopDown() ? srcY : (effectiveHeight - 1 - srcY);
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
// Calculate screen Y position
const int screenYStart = y + static_cast<int>(std::floor(logicalY * scale));
// For upscaling, calculate the end position for this source row
const int screenYEnd = isUpscaling ? (y + static_cast<int>(std::floor((logicalY + 1) * scale))) : (screenYStart + 1);
if (renderMode == BW && val < 3) {
drawPixel(screenX, screenY);
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
drawPixel(screenX, screenY, false);
// Draw to all Y positions this source row maps to (for upscaling, this fills gaps)
for (int screenY = screenYStart; screenY < screenYEnd; screenY++) {
if (screenY < 0) continue;
if (screenY >= getScreenHeight()) break;
for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++) {
const int srcX = bmpX - cropPixX;
// Calculate screen X position
const int screenXStart = x + static_cast<int>(std::floor(srcX * scale));
// For upscaling, calculate the end position for this source pixel
const int screenXEnd = isUpscaling ? (x + static_cast<int>(std::floor((srcX + 1) * scale))) : (screenXStart + 1);
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
// Draw to all X positions this source pixel maps to (for upscaling, this fills gaps)
for (int screenX = screenXStart; screenX < screenXEnd; screenX++) {
if (screenX < 0) continue;
if (screenX >= getScreenWidth()) break;
if (renderMode == BW && val < 3) {
drawPixel(screenX, screenY);
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
drawPixel(screenX, screenY, false);
}
}
}
}
}
@@ -250,16 +266,20 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
const int maxHeight) const {
float scale = 1.0f;
bool isScaled = false;
if (maxWidth > 0 && bitmap.getWidth() > maxWidth) {
// Calculate scale to fit within maxWidth/maxHeight (supports both up and down scaling)
if (maxWidth > 0 && maxHeight > 0) {
const float scaleX = static_cast<float>(maxWidth) / static_cast<float>(bitmap.getWidth());
const float scaleY = static_cast<float>(maxHeight) / static_cast<float>(bitmap.getHeight());
scale = std::min(scaleX, scaleY);
} else if (maxWidth > 0) {
scale = static_cast<float>(maxWidth) / static_cast<float>(bitmap.getWidth());
isScaled = true;
}
if (maxHeight > 0 && bitmap.getHeight() > maxHeight) {
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>(bitmap.getHeight()));
isScaled = true;
} else if (maxHeight > 0) {
scale = static_cast<float>(maxHeight) / static_cast<float>(bitmap.getHeight());
}
const bool isUpscaling = scale > 1.0f;
// For 1-bit BMP, output is still 2-bit packed (for consistency with readNextRow)
const int outputRowSize = (bitmap.getWidth() + 3) / 4;
auto* outputRow = static_cast<uint8_t*>(malloc(outputRowSize));
@@ -282,33 +302,39 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y,
}
// Calculate screen Y based on whether BMP is top-down or bottom-up
const int bmpYOffset = bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY;
int screenY = y + (isScaled ? static_cast<int>(std::floor(bmpYOffset * scale)) : bmpYOffset);
if (screenY >= getScreenHeight()) {
continue; // Continue reading to keep row counter in sync
}
if (screenY < 0) {
continue;
}
const int logicalY = bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY;
for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) {
int screenX = x + (isScaled ? static_cast<int>(std::floor(bmpX * scale)) : bmpX);
if (screenX >= getScreenWidth()) {
break;
}
if (screenX < 0) {
continue;
}
// Calculate screen Y position
const int screenYStart = y + static_cast<int>(std::floor(logicalY * scale));
// For upscaling, calculate the end position for this source row
const int screenYEnd = isUpscaling ? (y + static_cast<int>(std::floor((logicalY + 1) * scale))) : (screenYStart + 1);
// Get 2-bit value (result of readNextRow quantization)
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
// Draw to all Y positions this source row maps to (for upscaling, this fills gaps)
for (int screenY = screenYStart; screenY < screenYEnd; screenY++) {
if (screenY < 0) continue;
if (screenY >= getScreenHeight()) continue;
// For 1-bit source: 0 or 1 -> map to black (0,1,2) or white (3)
// val < 3 means black pixel (draw it)
if (val < 3) {
drawPixel(screenX, screenY, true);
for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) {
// Calculate screen X position
const int screenXStart = x + static_cast<int>(std::floor(bmpX * scale));
// For upscaling, calculate the end position for this source pixel
const int screenXEnd = isUpscaling ? (x + static_cast<int>(std::floor((bmpX + 1) * scale))) : (screenXStart + 1);
// Get 2-bit value (result of readNextRow quantization)
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
// For 1-bit source: 0 or 1 -> map to black (0,1,2) or white (3)
// val < 3 means black pixel (draw it)
if (val < 3) {
// Draw to all X positions this source pixel maps to (for upscaling, this fills gaps)
for (int screenX = screenXStart; screenX < screenXEnd; screenX++) {
if (screenX < 0) continue;
if (screenX >= getScreenWidth()) break;
drawPixel(screenX, screenY, true);
}
}
// White pixels (val == 3) are not drawn (leave background)
}
// White pixels (val == 3) are not drawn (leave background)
}
}