From 603a37a3bfef5cfe251bd2a16a0e6cefce74e714 Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 10:25:37 +0100 Subject: [PATCH 1/7] WIP --- src/OpenStreetMap-esp32.cpp | 82 +++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 963c47e..ebbee3c 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -436,64 +436,68 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu return false; } - // BMP header (54 bytes) - uint16_t bfType = 0x4D42; // "BM" - uint32_t bfSize = 54 + map.width() * map.height() * 3; // Header + pixel data (3 bytes per pixel for RGB888) - uint16_t bfReserved = 0; + // BMP Header (54 bytes) + uint16_t bfType = 0x4D42; // "BM" + uint32_t biSizeImage = map.width() * map.height() * 3; // 3 bytes per pixel (RGB888) + uint32_t bfSize = 54 + biSizeImage; // Total file size uint32_t bfOffBits = 54; // Offset to pixel data uint32_t biSize = 40; // Info header size int32_t biWidth = map.width(); - int32_t biHeight = -map.height(); // Negative to flip vertically + int32_t biHeight = -map.height(); // Negative to store in top-down order uint16_t biPlanes = 1; uint16_t biBitCount = 24; // RGB888 format uint32_t biCompression = 0; - uint32_t biSizeImage = map.width() * map.height() * 3; // 3 bytes per pixel int32_t biXPelsPerMeter = 0; int32_t biYPelsPerMeter = 0; uint32_t biClrUsed = 0; uint32_t biClrImportant = 0; - // Write BMP header - file.write(reinterpret_cast(&bfType), sizeof(bfType)); - file.write(reinterpret_cast(&bfSize), sizeof(bfSize)); - file.write(reinterpret_cast(&bfReserved), sizeof(bfReserved)); - file.write(reinterpret_cast(&bfOffBits), sizeof(bfOffBits)); - - file.write(reinterpret_cast(&biSize), sizeof(biSize)); - file.write(reinterpret_cast(&biWidth), sizeof(biWidth)); - file.write(reinterpret_cast(&biHeight), sizeof(biHeight)); - file.write(reinterpret_cast(&biPlanes), sizeof(biPlanes)); - file.write(reinterpret_cast(&biBitCount), sizeof(biBitCount)); - file.write(reinterpret_cast(&biCompression), sizeof(biCompression)); - file.write(reinterpret_cast(&biSizeImage), sizeof(biSizeImage)); - file.write(reinterpret_cast(&biXPelsPerMeter), sizeof(biXPelsPerMeter)); - file.write(reinterpret_cast(&biYPelsPerMeter), sizeof(biYPelsPerMeter)); - file.write(reinterpret_cast(&biClrUsed), sizeof(biClrUsed)); - file.write(reinterpret_cast(&biClrImportant), sizeof(biClrImportant)); - - for (int y = 0; y < map.height(); y++) + // Write BMP header (Ensuring little-endian format) + auto writeLE = [&](uint32_t value, uint8_t size) { + file.write((uint8_t *)&value, size); + }; + + writeLE(bfType, 2); + writeLE(bfSize, 4); + writeLE(0, 2); // bfReserved + writeLE(0, 2); + writeLE(bfOffBits, 4); + + writeLE(biSize, 4); + writeLE(biWidth, 4); + writeLE(biHeight, 4); + writeLE(biPlanes, 2); + writeLE(biBitCount, 2); + writeLE(biCompression, 4); + writeLE(biSizeImage, 4); + writeLE(biXPelsPerMeter, 4); + writeLE(biYPelsPerMeter, 4); + writeLE(biClrUsed, 4); + writeLE(biClrImportant, 4); + + // Buffer to store one row of pixel data + uint8_t rowBuffer[map.width() * 3]; + + // Write pixel data (BMP stores bottom-to-top, so we iterate from last row) + for (int y = map.height() - 1; y >= 0; y--) { for (int x = 0; x < map.width(); x++) { - uint16_t rgb565Color = map.readPixel(x, y); // Read pixel color (RGB565 format) - uint8_t red5 = (rgb565Color >> 11) & 0x1F; - uint8_t green6 = (rgb565Color >> 5) & 0x3F; - uint8_t blue5 = rgb565Color & 0x1F; - - // Convert RGB565 to RGB888 - uint8_t red8 = (red5 * 255) / 31; - uint8_t green8 = (green6 * 255) / 63; - uint8_t blue8 = (blue5 * 255) / 31; - - file.write(blue8); - file.write(green8); - file.write(red8); + uint16_t rgb565Color = map.readPixel(x, y); + uint8_t red8 = ((rgb565Color >> 11) & 0x1F) * 255 / 31; + uint8_t green8 = ((rgb565Color >> 5) & 0x3F) * 255 / 63; + uint8_t blue8 = (rgb565Color & 0x1F) * 255 / 31; + + rowBuffer[x * 3] = blue8; + rowBuffer[x * 3 + 1] = green8; + rowBuffer[x * 3 + 2] = red8; } + file.write(rowBuffer, sizeof(rowBuffer)); // Write the entire row at once } file.close(); SD.end(); result = "Screenshot saved"; return true; -} \ No newline at end of file +} From d7175850facb190392500ad88486d5c949d6ca07 Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 10:35:01 +0100 Subject: [PATCH 2/7] Fixed c-style pointer cast --- src/OpenStreetMap-esp32.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index ebbee3c..49ade08 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -455,7 +455,8 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu // Write BMP header (Ensuring little-endian format) auto writeLE = [&](uint32_t value, uint8_t size) { - file.write((uint8_t *)&value, size); + for (uint8_t i = 0; i < size; i++) + file.write(static_cast(value >> (8 * i))); }; writeLE(bfType, 2); From 81689c4300fa67ae52e96ffda6f1cba4b4cf4b81 Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 11:08:40 +0100 Subject: [PATCH 3/7] Store row pixel data in the heap --- src/OpenStreetMap-esp32.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 49ade08..452a923 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -437,10 +437,10 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu } // BMP Header (54 bytes) - uint16_t bfType = 0x4D42; // "BM" + uint16_t bfType = 0x4D42; // "BM" uint32_t biSizeImage = map.width() * map.height() * 3; // 3 bytes per pixel (RGB888) - uint32_t bfSize = 54 + biSizeImage; // Total file size - uint32_t bfOffBits = 54; // Offset to pixel data + uint32_t bfSize = 54 + biSizeImage; // Total file size + uint32_t bfOffBits = 54; // Offset to pixel data uint32_t biSize = 40; // Info header size int32_t biWidth = map.width(); @@ -454,7 +454,8 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu uint32_t biClrImportant = 0; // Write BMP header (Ensuring little-endian format) - auto writeLE = [&](uint32_t value, uint8_t size) { + auto writeLE = [&](uint32_t value, uint8_t size) + { for (uint8_t i = 0; i < size; i++) file.write(static_cast(value >> (8 * i))); }; @@ -477,11 +478,17 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu writeLE(biClrUsed, 4); writeLE(biClrImportant, 4); - // Buffer to store one row of pixel data - uint8_t rowBuffer[map.width() * 3]; + MemoryBuffer rowBuffer(map.width() * 3); + if (!rowBuffer.isAllocated()) + { + result = "Memory allocation failed"; + file.close(); + SD.end(); + return false; + } - // Write pixel data (BMP stores bottom-to-top, so we iterate from last row) - for (int y = map.height() - 1; y >= 0; y--) + uint8_t *buf = rowBuffer.get(); + for (int y = 0; y < map.height(); y++) { for (int x = 0; x < map.width(); x++) { @@ -490,11 +497,11 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu uint8_t green8 = ((rgb565Color >> 5) & 0x3F) * 255 / 63; uint8_t blue8 = (rgb565Color & 0x1F) * 255 / 31; - rowBuffer[x * 3] = blue8; - rowBuffer[x * 3 + 1] = green8; - rowBuffer[x * 3 + 2] = red8; + buf[x * 3] = blue8; + buf[x * 3 + 1] = green8; + buf[x * 3 + 2] = red8; } - file.write(rowBuffer, sizeof(rowBuffer)); // Write the entire row at once + file.write(buf, rowBuffer.size()); // Write entire row at once } file.close(); From e68c626a7afcb8e9651eac059d5684cf81f7d1ba Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 11:20:05 +0100 Subject: [PATCH 4/7] Cleanup --- src/OpenStreetMap-esp32.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 452a923..f81377f 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -418,20 +418,20 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu if (!map.getBuffer()) { - result = "No data in map!"; + result = "No data in map"; return false; } if (!SD.begin(sdPin)) { - result = "SD Card mount failed!"; + result = "SD Card mount failed"; return false; } File file = SD.open(filename, FILE_WRITE); if (!file) { - result = "Failed to open file!"; + result = "Failed to open file"; SD.end(); return false; } @@ -481,7 +481,7 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu MemoryBuffer rowBuffer(map.width() * 3); if (!rowBuffer.isAllocated()) { - result = "Memory allocation failed"; + result = "Row buffer allocation failed"; file.close(); SD.end(); return false; From 8ec8507ff490d7a9df09b592c41a9cfc595fb459 Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 12:41:53 +0100 Subject: [PATCH 5/7] Added `frequency` as a argument to saveMap() --- README.md | 14 +++++++++----- src/OpenStreetMap-esp32.cpp | 4 ++-- src/OpenStreetMap-esp32.h | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1ad1a85..bdd5996 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,15 @@ The downloaded tile cache gets large very quickly -128kB per tile- so a ESP32 wi ```c++ void setResolution(uint16_t w, uint16_t h); ``` +- If no resolution is set, a 320 by 240 map will be returned by `fetchMap`. #### Resize cache ```c++ bool resizeTilesCache(uint8_t numberOfTiles); ``` -**Note**: Each tile is 128 kB. +- The cache is cleared before resizing. +- Each tile is 128 kB. #### Free the memory used by the tile cache @@ -47,11 +49,13 @@ bool fetchMap(LGFX_Sprite &map, double longitude, double latitude, uint8_t zoom) #### Save a map to SD card ```c++ -bool saveMap(const char *filename, LGFX_Sprite &display, String &result, uint8_t sdPin = SS) +bool saveMap(const char *filename, LGFX_Sprite &map, String &result, + uint8_t sdPin = SS, uint32_t frequency = 4000000) ``` -`filename` should start with `/` for example `/map.bmp` -`sdPin` is optional and used to set a `SS/CS` pin for the SD slot. -`result` returns something like `SD Card mount failed!` or `Screenshot saved`. +- `filename` should start with `/` for example `/map.bmp` or `/images/map.bmp` +- `result` returns something like `SD Card mount failed` or `Screenshot saved`. +- `sdPin` is optional and used to set a `SS/CS` pin for the SD slot. +- `frequency` is optional and used to set the SD speed. ## License differences between this library and the map data diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index f81377f..382ed20 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -412,7 +412,7 @@ bool OpenStreetMap::downloadAndDecodeTile(CachedTile &tile, uint32_t x, uint32_t return true; } -bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin) +bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin, uint32_t frequency) { log_i("Saving map, this may take a while..."); @@ -422,7 +422,7 @@ bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &resu return false; } - if (!SD.begin(sdPin)) + if (!SD.begin(sdPin, SPI, frequency)) { result = "SD Card mount failed"; return false; diff --git a/src/OpenStreetMap-esp32.h b/src/OpenStreetMap-esp32.h index de6ca8c..5712345 100644 --- a/src/OpenStreetMap-esp32.h +++ b/src/OpenStreetMap-esp32.h @@ -56,7 +56,7 @@ class OpenStreetMap bool resizeTilesCache(uint8_t numberOfTiles); void freeTilesCache(); bool fetchMap(LGFX_Sprite &sprite, double longitude, double latitude, uint8_t zoom); - bool saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin = SS); + bool saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin = SS, uint32_t frequency = 4000000); private: static OpenStreetMap *currentInstance; From 946265f9693cc48368d6ecab6af83651b76d471b Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 12:45:21 +0100 Subject: [PATCH 6/7] Fixed codacy warnings --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index bdd5996..9bb64f0 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,17 @@ The downloaded tile cache gets large very quickly -128kB per tile- so a ESP32 wi ```c++ void setResolution(uint16_t w, uint16_t h); ``` -- If no resolution is set, a 320 by 240 map will be returned by `fetchMap`. + +- If no resolution is set, a 320 by 240 map will be returned by `fetchMap`. #### Resize cache ```c++ bool resizeTilesCache(uint8_t numberOfTiles); ``` -- The cache is cleared before resizing. -- Each tile is 128 kB. + +- The cache is cleared before resizing. +- Each tile is 128 kB. #### Free the memory used by the tile cache @@ -52,10 +54,10 @@ bool fetchMap(LGFX_Sprite &map, double longitude, double latitude, uint8_t zoom) bool saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin = SS, uint32_t frequency = 4000000) ``` -- `filename` should start with `/` for example `/map.bmp` or `/images/map.bmp` -- `result` returns something like `SD Card mount failed` or `Screenshot saved`. -- `sdPin` is optional and used to set a `SS/CS` pin for the SD slot. -- `frequency` is optional and used to set the SD speed. +- `filename` should start with `/` for example `/map.bmp` or `/images/map.bmp` +- `result` returns something like `SD Card mount failed` or `Screenshot saved`. +- `sdPin` is optional and used to set a `SS/CS` pin for the SD slot. +- `frequency` is optional and used to set the SD speed. ## License differences between this library and the map data From 28461cfa9ade20aa5bd88a88074ab6064c95b05d Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 21 Mar 2025 12:47:38 +0100 Subject: [PATCH 7/7] Fixed codacy warning --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9bb64f0..fb9de23 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ bool fetchMap(LGFX_Sprite &map, double longitude, double latitude, uint8_t zoom) bool saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin = SS, uint32_t frequency = 4000000) ``` + - `filename` should start with `/` for example `/map.bmp` or `/images/map.bmp` - `result` returns something like `SD Card mount failed` or `Screenshot saved`. - `sdPin` is optional and used to set a `SS/CS` pin for the SD slot.