mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-19 07:37:41 +03:00
Compare commits
1 Commits
6f740dd81f
...
a31532cac2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a31532cac2 |
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on: push
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/pip
|
|
||||||
~/.platformio/.cache
|
|
||||||
key: ${{ runner.os }}-pio
|
|
||||||
- uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.14'
|
|
||||||
|
|
||||||
- name: Install PlatformIO Core
|
|
||||||
run: pip install --upgrade platformio
|
|
||||||
|
|
||||||
- name: Install clang-format-21
|
|
||||||
run: |
|
|
||||||
wget https://apt.llvm.org/llvm.sh
|
|
||||||
chmod +x llvm.sh
|
|
||||||
sudo ./llvm.sh 21
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y clang-format-21
|
|
||||||
|
|
||||||
- name: Run cppcheck
|
|
||||||
run: pio check --fail-on-defect medium --fail-on-defect high
|
|
||||||
|
|
||||||
- name: Run clang-format
|
|
||||||
run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
|
|
||||||
|
|
||||||
- name: Build CrossPoint
|
|
||||||
run: pio run
|
|
||||||
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
name: Compile Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/pip
|
|
||||||
~/.platformio/.cache
|
|
||||||
key: ${{ runner.os }}-pio
|
|
||||||
- uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.14'
|
|
||||||
|
|
||||||
- name: Install PlatformIO Core
|
|
||||||
run: pip install --upgrade platformio
|
|
||||||
|
|
||||||
- name: Build CrossPoint
|
|
||||||
run: pio run -e gh_release
|
|
||||||
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: CrossPoint-${{ github.ref_name }}
|
|
||||||
path: |
|
|
||||||
.pio/build/gh_release/bootloader.bin
|
|
||||||
.pio/build/gh_release/firmware.bin
|
|
||||||
.pio/build/gh_release/firmware.elf
|
|
||||||
.pio/build/gh_release/firmware.map
|
|
||||||
.pio/build/gh_release/partitions.bin
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
#include "BmpReader.h"
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
uint16_t BmpReader::readLE16(File& f) {
|
|
||||||
const int c0 = f.read();
|
|
||||||
const int c1 = f.read();
|
|
||||||
const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0);
|
|
||||||
const uint8_t b1 = (uint8_t)(c1 < 0 ? 0 : c1);
|
|
||||||
return (uint16_t)b0 | ((uint16_t)b1 << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t BmpReader::readLE32(File& f) {
|
|
||||||
const int c0 = f.read();
|
|
||||||
const int c1 = f.read();
|
|
||||||
const int c2 = f.read();
|
|
||||||
const int c3 = f.read();
|
|
||||||
|
|
||||||
const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0);
|
|
||||||
const uint8_t b1 = (uint8_t)(c1 < 0 ? 0 : c1);
|
|
||||||
const uint8_t b2 = (uint8_t)(c2 < 0 ? 0 : c2);
|
|
||||||
const uint8_t b3 = (uint8_t)(c3 < 0 ? 0 : c3);
|
|
||||||
|
|
||||||
return (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BmpReader::freeMonoBitmap(MonoBitmap& bmp) {
|
|
||||||
if (bmp.data) {
|
|
||||||
free(bmp.data);
|
|
||||||
bmp.data = nullptr;
|
|
||||||
}
|
|
||||||
bmp.width = 0;
|
|
||||||
bmp.height = 0;
|
|
||||||
bmp.len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* BmpReader::errorToString(BmpReaderError err) {
|
|
||||||
switch (err) {
|
|
||||||
case BmpReaderError::Ok:
|
|
||||||
return "Ok";
|
|
||||||
case BmpReaderError::FileInvalid:
|
|
||||||
return "FileInvalid";
|
|
||||||
case BmpReaderError::SeekStartFailed:
|
|
||||||
return "SeekStartFailed";
|
|
||||||
case BmpReaderError::NotBMP:
|
|
||||||
return "NotBMP (missing 'BM')";
|
|
||||||
case BmpReaderError::DIBTooSmall:
|
|
||||||
return "DIBTooSmall (<40 bytes)";
|
|
||||||
case BmpReaderError::BadPlanes:
|
|
||||||
return "BadPlanes (!= 1)";
|
|
||||||
case BmpReaderError::UnsupportedBpp:
|
|
||||||
return "UnsupportedBpp (expected 24, 32 or 1)";
|
|
||||||
case BmpReaderError::UnsupportedCompression:
|
|
||||||
return "UnsupportedCompression (expected BI_RGB or BI_BITFIELDS for 32bpp)";
|
|
||||||
case BmpReaderError::BadDimensions:
|
|
||||||
return "BadDimensions";
|
|
||||||
case BmpReaderError::SeekPixelDataFailed:
|
|
||||||
return "SeekPixelDataFailed";
|
|
||||||
case BmpReaderError::OomOutput:
|
|
||||||
return "OomOutput";
|
|
||||||
case BmpReaderError::OomRowBuffer:
|
|
||||||
return "OomRowBuffer";
|
|
||||||
case BmpReaderError::ShortReadRow:
|
|
||||||
return "ShortReadRow";
|
|
||||||
}
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
BmpReaderError BmpReader::read(File& file, MonoBitmap& out, uint8_t threshold) {
|
|
||||||
freeMonoBitmap(out);
|
|
||||||
|
|
||||||
if (!file) return BmpReaderError::FileInvalid;
|
|
||||||
if (!file.seek(0)) return BmpReaderError::SeekStartFailed;
|
|
||||||
|
|
||||||
// --- BMP FILE HEADER ---
|
|
||||||
const uint16_t bfType = readLE16(file);
|
|
||||||
if (bfType != 0x4D42) return BmpReaderError::NotBMP;
|
|
||||||
|
|
||||||
(void)readLE32(file);
|
|
||||||
(void)readLE16(file);
|
|
||||||
(void)readLE16(file);
|
|
||||||
const uint32_t bfOffBits = readLE32(file);
|
|
||||||
|
|
||||||
// --- DIB HEADER ---
|
|
||||||
const uint32_t biSize = readLE32(file);
|
|
||||||
if (biSize < 40) return BmpReaderError::DIBTooSmall;
|
|
||||||
|
|
||||||
const int32_t srcW = (int32_t)readLE32(file);
|
|
||||||
int32_t srcHRaw = (int32_t)readLE32(file);
|
|
||||||
const uint16_t planes = readLE16(file);
|
|
||||||
const uint16_t bpp = readLE16(file);
|
|
||||||
const uint32_t comp = readLE32(file);
|
|
||||||
const bool is24Bit = (bpp == 24);
|
|
||||||
const bool is32Bit = (bpp == 32);
|
|
||||||
const bool is8Bit = (bpp == 8);
|
|
||||||
const bool is1Bit = (bpp == 1);
|
|
||||||
|
|
||||||
if (planes != 1) return BmpReaderError::BadPlanes;
|
|
||||||
if (!is24Bit && !is32Bit && !is8Bit && !is1Bit) return BmpReaderError::UnsupportedBpp;
|
|
||||||
// Allow BI_RGB (0) for all, and BI_BITFIELDS (3) for 32bpp which is common for BGRA masks.
|
|
||||||
if (!(comp == 0 || (is32Bit && comp == 3))) return BmpReaderError::UnsupportedCompression;
|
|
||||||
|
|
||||||
(void)readLE32(file); // biSizeImage
|
|
||||||
(void)readLE32(file); // biXPelsPerMeter
|
|
||||||
(void)readLE32(file); // biYPelsPerMeter
|
|
||||||
const uint32_t clrUsed = readLE32(file);
|
|
||||||
(void)readLE32(file); // biClrImportant
|
|
||||||
|
|
||||||
if (srcW <= 0) return BmpReaderError::BadDimensions;
|
|
||||||
|
|
||||||
const bool topDown = (srcHRaw < 0);
|
|
||||||
const int32_t srcH = topDown ? -srcHRaw : srcHRaw;
|
|
||||||
if (srcH <= 0) return BmpReaderError::BadDimensions;
|
|
||||||
|
|
||||||
// Output dimensions after 90° CCW rotation
|
|
||||||
out.width = (int)srcH;
|
|
||||||
out.height = (int)srcW;
|
|
||||||
|
|
||||||
const size_t outBytesPerRow = (size_t)(out.width + 7) / 8;
|
|
||||||
out.len = outBytesPerRow * (size_t)out.height;
|
|
||||||
|
|
||||||
out.data = (uint8_t*)malloc(out.len);
|
|
||||||
if (!out.data) return BmpReaderError::OomOutput;
|
|
||||||
memset(out.data, 0xFF, out.len);
|
|
||||||
|
|
||||||
// Palette for 8-bit indexed images
|
|
||||||
uint8_t paletteLum[256];
|
|
||||||
if (is8Bit) {
|
|
||||||
for (int i = 0; i < 256; i++) paletteLum[i] = (uint8_t)i; // default grayscale ramp
|
|
||||||
uint32_t paletteCount = (clrUsed == 0) ? 256u : clrUsed;
|
|
||||||
if (paletteCount > 256u) paletteCount = 256u;
|
|
||||||
for (uint32_t i = 0; i < paletteCount; i++) {
|
|
||||||
const int b = file.read();
|
|
||||||
const int g = file.read();
|
|
||||||
const int r = file.read();
|
|
||||||
(void)file.read(); // reserved
|
|
||||||
const uint8_t bb = (uint8_t)(b < 0 ? 0 : b);
|
|
||||||
const uint8_t gg = (uint8_t)(g < 0 ? 0 : g);
|
|
||||||
const uint8_t rr = (uint8_t)(r < 0 ? 0 : r);
|
|
||||||
paletteLum[i] = (uint8_t)((77u * rr + 150u * gg + 29u * bb) >> 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source row stride (padded to 4 bytes)
|
|
||||||
uint32_t bytesPerPixel = 0u;
|
|
||||||
if (is8Bit) {
|
|
||||||
bytesPerPixel = 1u;
|
|
||||||
} else if (is32Bit) {
|
|
||||||
bytesPerPixel = 4u;
|
|
||||||
} else if (is24Bit) {
|
|
||||||
bytesPerPixel = 3u;
|
|
||||||
}
|
|
||||||
const uint32_t srcBytesPerRow =
|
|
||||||
is1Bit ? ((uint32_t)srcW + 7u) / 8u : (uint32_t)srcW * bytesPerPixel; // bpp==1 ignores bytesPerPixel
|
|
||||||
const uint32_t srcRowStride = (srcBytesPerRow + 3u) & ~3u;
|
|
||||||
|
|
||||||
if (!file.seek(bfOffBits)) {
|
|
||||||
freeMonoBitmap(out);
|
|
||||||
return BmpReaderError::SeekPixelDataFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* rowBuf = (uint8_t*)malloc(srcRowStride);
|
|
||||||
if (!rowBuf) {
|
|
||||||
freeMonoBitmap(out);
|
|
||||||
return BmpReaderError::OomRowBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int fileRow = 0; fileRow < (int)srcH; fileRow++) {
|
|
||||||
if (file.read(rowBuf, srcRowStride) != (int)srcRowStride) {
|
|
||||||
free(rowBuf);
|
|
||||||
freeMonoBitmap(out);
|
|
||||||
return BmpReaderError::ShortReadRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int srcY = topDown ? fileRow : ((int)srcH - 1 - fileRow);
|
|
||||||
|
|
||||||
for (int srcX = 0; srcX < (int)srcW; srcX++) {
|
|
||||||
bool isBlack;
|
|
||||||
if (is1Bit) {
|
|
||||||
const uint8_t byte = rowBuf[srcX >> 3];
|
|
||||||
const uint8_t mask = (uint8_t)(0x80u >> (srcX & 7));
|
|
||||||
const bool bitSet = (byte & mask) != 0;
|
|
||||||
// In 1bpp BMPs, palette index 0 is conventionally black and index 1 is white.
|
|
||||||
isBlack = !bitSet;
|
|
||||||
} else if (is8Bit) {
|
|
||||||
const uint8_t idx = rowBuf[srcX];
|
|
||||||
const uint8_t lum = paletteLum[idx];
|
|
||||||
isBlack = (lum < threshold);
|
|
||||||
} else {
|
|
||||||
const uint8_t* px = &rowBuf[srcX * bytesPerPixel];
|
|
||||||
const uint8_t b = px[0];
|
|
||||||
const uint8_t g = px[1];
|
|
||||||
const uint8_t r = px[2];
|
|
||||||
|
|
||||||
const uint8_t lum = (uint8_t)((77u * r + 150u * g + 29u * b) >> 8);
|
|
||||||
isBlack = (lum < threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 90° counter-clockwise: (x,y) -> (y, w-1-x)
|
|
||||||
const int outX = srcY;
|
|
||||||
const int outY = (int)srcW - 1 - srcX;
|
|
||||||
|
|
||||||
setMonoPixel(out.data, out.width, outX, outY, isBlack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(rowBuf);
|
|
||||||
return BmpReaderError::Ok;
|
|
||||||
}
|
|
||||||
174
lib/BmpToMono/BmpToMono.cpp
Normal file
174
lib/BmpToMono/BmpToMono.cpp
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#include "BmpToMono.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
uint16_t BmpToMono::readLE16(File& f) {
|
||||||
|
const int c0 = f.read();
|
||||||
|
const int c1 = f.read();
|
||||||
|
const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0);
|
||||||
|
const uint8_t b1 = (uint8_t)(c1 < 0 ? 0 : c1);
|
||||||
|
return (uint16_t)b0 | ((uint16_t)b1 << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BmpToMono::readLE32(File& f) {
|
||||||
|
const int c0 = f.read();
|
||||||
|
const int c1 = f.read();
|
||||||
|
const int c2 = f.read();
|
||||||
|
const int c3 = f.read();
|
||||||
|
|
||||||
|
const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0);
|
||||||
|
const uint8_t b1 = (uint8_t)(c1 < 0 ? 0 : c1);
|
||||||
|
const uint8_t b2 = (uint8_t)(c2 < 0 ? 0 : c2);
|
||||||
|
const uint8_t b3 = (uint8_t)(c3 < 0 ? 0 : c3);
|
||||||
|
|
||||||
|
return (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BmpToMono::freeMonoBitmap(MonoBitmap& bmp) {
|
||||||
|
if (bmp.data) {
|
||||||
|
free(bmp.data);
|
||||||
|
bmp.data = nullptr;
|
||||||
|
}
|
||||||
|
bmp.width = 0;
|
||||||
|
bmp.height = 0;
|
||||||
|
bmp.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* BmpToMono::errorToString(BmpToMonoError err) {
|
||||||
|
switch (err) {
|
||||||
|
case BmpToMonoError::Ok:
|
||||||
|
return "Ok";
|
||||||
|
case BmpToMonoError::FileInvalid:
|
||||||
|
return "FileInvalid";
|
||||||
|
case BmpToMonoError::SeekStartFailed:
|
||||||
|
return "SeekStartFailed";
|
||||||
|
case BmpToMonoError::NotBMP:
|
||||||
|
return "NotBMP (missing 'BM')";
|
||||||
|
case BmpToMonoError::DIBTooSmall:
|
||||||
|
return "DIBTooSmall (<40 bytes)";
|
||||||
|
case BmpToMonoError::BadPlanes:
|
||||||
|
return "BadPlanes (!= 1)";
|
||||||
|
case BmpToMonoError::UnsupportedBpp:
|
||||||
|
return "UnsupportedBpp (expected 24)";
|
||||||
|
case BmpToMonoError::UnsupportedCompression:
|
||||||
|
return "UnsupportedCompression (expected BI_RGB)";
|
||||||
|
case BmpToMonoError::BadDimensions:
|
||||||
|
return "BadDimensions";
|
||||||
|
case BmpToMonoError::SeekPixelDataFailed:
|
||||||
|
return "SeekPixelDataFailed";
|
||||||
|
case BmpToMonoError::OomOutput:
|
||||||
|
return "OomOutput";
|
||||||
|
case BmpToMonoError::OomRowBuffer:
|
||||||
|
return "OomRowBuffer";
|
||||||
|
case BmpToMonoError::ShortReadRow:
|
||||||
|
return "ShortReadRow";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
BmpToMonoError BmpToMono::convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold) {
|
||||||
|
return convert24BitImpl(file, out, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t threshold, bool rotate90CCW) {
|
||||||
|
freeMonoBitmap(out);
|
||||||
|
|
||||||
|
if (!f) return BmpToMonoError::FileInvalid;
|
||||||
|
if (!f.seek(0)) return BmpToMonoError::SeekStartFailed;
|
||||||
|
|
||||||
|
// --- BMP FILE HEADER ---
|
||||||
|
const uint16_t bfType = readLE16(f);
|
||||||
|
if (bfType != 0x4D42) return BmpToMonoError::NotBMP;
|
||||||
|
|
||||||
|
(void)readLE32(f);
|
||||||
|
(void)readLE16(f);
|
||||||
|
(void)readLE16(f);
|
||||||
|
const uint32_t bfOffBits = readLE32(f);
|
||||||
|
|
||||||
|
// --- DIB HEADER ---
|
||||||
|
const uint32_t biSize = readLE32(f);
|
||||||
|
if (biSize < 40) return BmpToMonoError::DIBTooSmall;
|
||||||
|
|
||||||
|
const int32_t srcW = (int32_t)readLE32(f);
|
||||||
|
int32_t srcHRaw = (int32_t)readLE32(f);
|
||||||
|
const uint16_t planes = readLE16(f);
|
||||||
|
const uint16_t bpp = readLE16(f);
|
||||||
|
const uint32_t comp = readLE32(f);
|
||||||
|
|
||||||
|
if (planes != 1) return BmpToMonoError::BadPlanes;
|
||||||
|
if (bpp != 24) return BmpToMonoError::UnsupportedBpp;
|
||||||
|
if (comp != 0) return BmpToMonoError::UnsupportedCompression;
|
||||||
|
|
||||||
|
(void)readLE32(f);
|
||||||
|
(void)readLE32(f);
|
||||||
|
(void)readLE32(f);
|
||||||
|
(void)readLE32(f);
|
||||||
|
(void)readLE32(f);
|
||||||
|
|
||||||
|
if (srcW <= 0) return BmpToMonoError::BadDimensions;
|
||||||
|
|
||||||
|
const bool topDown = (srcHRaw < 0);
|
||||||
|
const int32_t srcH = topDown ? -srcHRaw : srcHRaw;
|
||||||
|
if (srcH <= 0) return BmpToMonoError::BadDimensions;
|
||||||
|
|
||||||
|
// Output dimensions
|
||||||
|
out.width = rotate90CCW ? (int)srcH : (int)srcW;
|
||||||
|
out.height = rotate90CCW ? (int)srcW : (int)srcH;
|
||||||
|
|
||||||
|
const size_t outBytesPerRow = (size_t)(out.width + 7) / 8;
|
||||||
|
out.len = outBytesPerRow * (size_t)out.height;
|
||||||
|
|
||||||
|
out.data = (uint8_t*)malloc(out.len);
|
||||||
|
if (!out.data) return BmpToMonoError::OomOutput;
|
||||||
|
memset(out.data, 0xFF, out.len);
|
||||||
|
|
||||||
|
// Source row stride (padded to 4 bytes)
|
||||||
|
const uint32_t srcBytesPerRow24 = (uint32_t)srcW * 3u;
|
||||||
|
const uint32_t srcRowStride = (srcBytesPerRow24 + 3u) & ~3u;
|
||||||
|
|
||||||
|
if (!f.seek(bfOffBits)) {
|
||||||
|
freeMonoBitmap(out);
|
||||||
|
return BmpToMonoError::SeekPixelDataFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* rowBuf = (uint8_t*)malloc(srcRowStride);
|
||||||
|
if (!rowBuf) {
|
||||||
|
freeMonoBitmap(out);
|
||||||
|
return BmpToMonoError::OomRowBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int fileRow = 0; fileRow < (int)srcH; fileRow++) {
|
||||||
|
if (f.read(rowBuf, srcRowStride) != (int)srcRowStride) {
|
||||||
|
free(rowBuf);
|
||||||
|
freeMonoBitmap(out);
|
||||||
|
return BmpToMonoError::ShortReadRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int srcY = topDown ? fileRow : ((int)srcH - 1 - fileRow);
|
||||||
|
|
||||||
|
for (int srcX = 0; srcX < (int)srcW; srcX++) {
|
||||||
|
const uint8_t b = rowBuf[srcX * 3 + 0];
|
||||||
|
const uint8_t g = rowBuf[srcX * 3 + 1];
|
||||||
|
const uint8_t r = rowBuf[srcX * 3 + 2];
|
||||||
|
|
||||||
|
const uint8_t lum = (uint8_t)((77u * r + 150u * g + 29u * b) >> 8);
|
||||||
|
bool isBlack = (lum < threshold);
|
||||||
|
|
||||||
|
int outX, outY;
|
||||||
|
if (!rotate90CCW) {
|
||||||
|
outX = srcX;
|
||||||
|
outY = srcY;
|
||||||
|
} else {
|
||||||
|
// 90° counter-clockwise: (x,y) -> (y, w-1-x)
|
||||||
|
outX = srcY;
|
||||||
|
outY = (int)srcW - 1 - srcX;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMonoPixel(out.data, out.width, outX, outY, isBlack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(rowBuf);
|
||||||
|
return BmpToMonoError::Ok;
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@ struct MonoBitmap {
|
|||||||
uint8_t* data = nullptr; // row-aligned, MSB-first, 1=white 0=black
|
uint8_t* data = nullptr; // row-aligned, MSB-first, 1=white 0=black
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class BmpReaderError : uint8_t {
|
enum class BmpToMonoError : uint8_t {
|
||||||
Ok = 0,
|
Ok = 0,
|
||||||
FileInvalid,
|
FileInvalid,
|
||||||
SeekStartFailed,
|
SeekStartFailed,
|
||||||
@ -30,15 +30,14 @@ enum class BmpReaderError : uint8_t {
|
|||||||
ShortReadRow,
|
ShortReadRow,
|
||||||
};
|
};
|
||||||
|
|
||||||
class BmpReader {
|
class BmpToMono {
|
||||||
public:
|
public:
|
||||||
// Rotate 90° counter-clockwise: (w,h) -> (h,w)
|
// Rotate 90° counter-clockwise: (w,h) -> (h,w)
|
||||||
// Used for converting portrait BMP (480x800) into landscape framebuffer (800x480)
|
// Used for converting portrait BMP (480x800) into landscape framebuffer (800x480)
|
||||||
// Supports 8-bit, 24-bit, 32-bit color and 1-bit monochrome BMPs.
|
static BmpToMonoError convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold = 160);
|
||||||
static BmpReaderError read(File& file, MonoBitmap& out, uint8_t threshold = 160);
|
|
||||||
|
|
||||||
static void freeMonoBitmap(MonoBitmap& bmp);
|
static void freeMonoBitmap(MonoBitmap& bmp);
|
||||||
static const char* errorToString(BmpReaderError err);
|
static const char* errorToString(BmpToMonoError err);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static uint16_t readLE16(File& f);
|
static uint16_t readLE16(File& f);
|
||||||
@ -54,4 +53,6 @@ class BmpReader {
|
|||||||
else
|
else
|
||||||
buf[idx] |= mask;
|
buf[idx] |= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BmpToMonoError convert24BitImpl(File& file, MonoBitmap& out, uint8_t threshold, bool rotate90CW);
|
||||||
};
|
};
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
|
|
||||||
#include "BmpReader.h"
|
#include "BmpToMono.h"
|
||||||
|
|
||||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||||
|
|
||||||
@ -130,10 +130,10 @@ bool GfxRenderer::drawFullScreenBmp(File& file) {
|
|||||||
file.seek(0); // Ensure we're at the start of the file
|
file.seek(0); // Ensure we're at the start of the file
|
||||||
|
|
||||||
MonoBitmap bmp;
|
MonoBitmap bmp;
|
||||||
auto err = BmpReader::read(file, bmp);
|
auto err = BmpToMono::convert24BitRotate90CCW(file, bmp);
|
||||||
|
|
||||||
if (err != BmpReaderError::Ok) {
|
if (err != BmpToMonoError::Ok) {
|
||||||
Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpReader::errorToString(err));
|
Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpToMono::errorToString(err));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,14 +141,14 @@ bool GfxRenderer::drawFullScreenBmp(File& file) {
|
|||||||
if (bmp.width != EInkDisplay::DISPLAY_WIDTH || bmp.height != EInkDisplay::DISPLAY_HEIGHT) {
|
if (bmp.width != EInkDisplay::DISPLAY_WIDTH || bmp.height != EInkDisplay::DISPLAY_HEIGHT) {
|
||||||
Serial.printf("[%lu] [GFX] drawFullScreenBmp: rotated BMP size %dx%d does not match panel %dx%d\n", millis(),
|
Serial.printf("[%lu] [GFX] drawFullScreenBmp: rotated BMP size %dx%d does not match panel %dx%d\n", millis(),
|
||||||
bmp.width, bmp.height, EInkDisplay::DISPLAY_WIDTH, EInkDisplay::DISPLAY_HEIGHT);
|
bmp.width, bmp.height, EInkDisplay::DISPLAY_WIDTH, EInkDisplay::DISPLAY_HEIGHT);
|
||||||
BmpReader::freeMonoBitmap(bmp);
|
BmpToMono::freeMonoBitmap(bmp);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full-screen blit
|
// Full-screen blit
|
||||||
einkDisplay.drawImage(bmp.data, 0, 0, bmp.width, bmp.height);
|
einkDisplay.drawImage(bmp.data, 0, 0, bmp.width, bmp.height);
|
||||||
|
|
||||||
BmpReader::freeMonoBitmap(bmp);
|
BmpToMono::freeMonoBitmap(bmp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,6 @@ board = esp32-c3-devkitm-1
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
check_tool = cppcheck
|
|
||||||
check_skip_packages = yes
|
|
||||||
check_severity = medium, high
|
|
||||||
|
|
||||||
board_upload.flash_size = 16MB
|
board_upload.flash_size = 16MB
|
||||||
board_upload.maximum_size = 16777216
|
board_upload.maximum_size = 16777216
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user