diff --git a/lib/GfxRenderer/Bitmap.cpp b/lib/GfxRenderer/Bitmap.cpp index 7c46df1c..4dbe8a25 100644 --- a/lib/GfxRenderer/Bitmap.cpp +++ b/lib/GfxRenderer/Bitmap.cpp @@ -257,6 +257,7 @@ BmpReaderError Bitmap::parseHeaders() { } // packed 2bpp output, 0 = black, 1 = dark gray, 2 = light gray, 3 = white +// TODO: This seems to only work sequentially, rowY is mostly ignored BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const { // Note: rowBuffer should be pre-allocated by the caller to size 'rowBytes' if (file.read(rowBuffer, rowBytes) != rowBytes) return BmpReaderError::ShortReadRow; diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 52cd6f4d..168e494e 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -152,18 +152,24 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height); } -void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, - const int maxHeight) const { +void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, + const float cropX, const float cropY) const { float scale = 1.0f; bool isScaled = false; - if (maxWidth > 0 && bitmap.getWidth() > maxWidth) { - scale = static_cast(maxWidth) / static_cast(bitmap.getWidth()); + 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(maxWidth) / static_cast((1.0f - cropX) * bitmap.getWidth()); isScaled = true; } - if (maxHeight > 0 && bitmap.getHeight() > maxHeight) { - scale = std::min(scale, static_cast(maxHeight) / static_cast(bitmap.getHeight())); + if (maxHeight > 0 && (1.0f - cropY) * bitmap.getHeight() > maxHeight) { + scale = std::min(scale, static_cast(maxHeight) / static_cast((1.0f - cropY) * bitmap.getHeight())); isScaled = true; } + Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale, isScaled ? "scaled" : "not scaled"); // 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 @@ -178,10 +184,10 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con return; } - for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) { + 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 = y + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY); + int screenY = y - cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY); if (isScaled) { screenY = std::floor(screenY * scale); } @@ -196,8 +202,13 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con return; } - for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) { - int screenX = x + bmpX; + if (bmpY < cropPixY) { + // Skip the row if it's outside the crop area + continue; + } + + for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++) { + int screenX = x + bmpX - cropPixX; if (isScaled) { screenX = std::floor(screenX * scale); } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index af4f2d50..e3e9558d 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -66,7 +66,8 @@ class GfxRenderer { void drawRect(int x, int y, int width, int height, bool state = true) const; void fillRect(int x, int y, int width, int height, bool state = true) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; - void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const; + void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, + float cropY = 0) const; // Text int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index 9c61ef0d..4bf26994 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -468,7 +468,10 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) { // Calculate scale to fit within target dimensions while maintaining aspect ratio const float scaleToFitWidth = static_cast(TARGET_MAX_WIDTH) / imageInfo.m_width; const float scaleToFitHeight = static_cast(TARGET_MAX_HEIGHT) / imageInfo.m_height; - const float scale = (scaleToFitWidth < scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight; + // const float scale = (scaleToFitWidth < scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight; + // For now, scale to the smaller dimension, so we can crop later. + // TODO: ideally, we already crop here. + const float scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight; outWidth = static_cast(imageInfo.m_width * scale); outHeight = static_cast(imageInfo.m_height * scale); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index bb38df68..f24cde5f 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -17,6 +17,7 @@ class CrossPointSettings { // Should match with SettingsActivity text enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3 }; + enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 }; // Status bar display type enum enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 }; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index cc9fa9d9..0b5b2679 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -142,18 +142,29 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { int x, y; const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); + float cropX = 0, cropY = 0; + Serial.printf("[%lu] [SLP] bitmap %d x %d, screen %d x %d\n", millis(), bitmap.getWidth(), bitmap.getHeight(), + pageWidth, pageHeight); if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) { // image will scale, make sure placement is right - const float ratio = static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); + float ratio = static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); const float screenRatio = static_cast(pageWidth) / static_cast(pageHeight); + Serial.printf("[%lu] [SLP] bitmap ratio: %f, screen ratio: %f\n", millis(), ratio, screenRatio); if (ratio > screenRatio) { // image wider than viewport ratio, scaled down image needs to be centered vertically + cropX = 1.0f - (screenRatio / ratio); + Serial.printf("[%lu] [SLP] Cropping bitmap x: %f\n", millis(), cropX); + ratio = (1 - cropX) * static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); x = 0; y = (pageHeight - pageWidth / ratio) / 2; } else { // image taller than viewport ratio, scaled down image needs to be centered horizontally + // try to crop + cropY = 1.0f - (ratio / screenRatio); + Serial.printf("[%lu] [SLP] Cropping bitmap y: %f\n", millis(), cropY); + ratio = static_cast(bitmap.getWidth()) / ((1 - cropY) * static_cast(bitmap.getHeight())); x = (pageWidth - pageHeight * ratio) / 2; y = 0; } @@ -164,20 +175,20 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { } renderer.clearScreen(); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); renderer.displayBuffer(EInkDisplay::HALF_REFRESH); if (bitmap.hasGreyscale()) { bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); renderer.copyGrayscaleLsbBuffers(); bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); renderer.copyGrayscaleMsbBuffers(); renderer.displayGrayBuffer();