diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bb7933 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.py[cod] +__pycache__/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c31e40..53f5cf5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ endif() set(LIBOPENUI_SRC libopenui_file.cpp bitmapbuffer.cpp + font.cpp window.cpp layer.cpp form.cpp diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 6a4ec41..6af09e2 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -26,31 +26,9 @@ #include "file_reader.h" #include "intconversions.h" -BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): - BitmapBufferBase(format, width, height, nullptr), - dataAllocated(true) +void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const Bitmap * bitmap, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch, float scale) { - data = (uint16_t *) malloc(align32(width * height * sizeof(uint16_t))); - dataEnd = data + (width * height); -} - -BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t * data): - BitmapBufferBase(format, width, height, data), - dataAllocated(false) -{ -} - -BitmapBuffer::~BitmapBuffer() -{ - if (dataAllocated) { - free(data); - } -} - -template -void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch, float scale) -{ - if (!data || !bmp) + if (!data || !bitmap) return; APPLY_OFFSET(); @@ -58,8 +36,8 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, if (x >= xmax || y >= ymax) return; - coord_t bmpw = bmp->width(); - coord_t bmph = bmp->height(); + coord_t bmpw = bitmap->width(); + coord_t bmph = bitmap->height(); if (srcw == 0) srcw = bmpw; @@ -92,10 +70,10 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, return; } - if (bmp->getFormat() == BMP_ARGB4444 || format == BMP_ARGB4444) - DMACopyAlphaBitmap(data, format == BMP_ARGB4444, _width, _height, x, y, bmp->getData(), bmp->getFormat() == BMP_ARGB4444, bmpw, bmph, srcx, srcy, srcw, srch); + if (bitmap->getFormat() == BMP_ARGB4444 || _format == BMP_ARGB4444) + DMACopyAlphaBitmap(data, _format == BMP_ARGB4444, _width, _height, x, y, bitmap->getData(), bitmap->getFormat() == BMP_ARGB4444, bmpw, bmph, srcx, srcy, srcw, srch); else - DMACopyBitmap(data, _width, _height, x, y, bmp->getData(), bmpw, bmph, srcx, srcy, srcw, srch); + DMACopyBitmap(data, _width, _height, x, y, bitmap->getData(), bmpw, bmph, srcx, srcy, srcw, srch); } else { if (x < xmin) { @@ -127,14 +105,14 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, if (y + scaledh > _height) scaledh = _height - y; - if (format == BMP_ARGB4444) { + if (getFormat() == BMP_ARGB4444) { for (int i = 0; i < scaledh; i++) { pixel_t * p = getPixelPtrAbs(x, y + i); - const pixel_t * qstart = bmp->getPixelPtrAbs(srcx, srcy + int(i / scale)); + const pixel_t * qstart = bitmap->getPixelPtrAbs(srcx, srcy + int(i / scale)); for (int j = 0; j < scaledw; j++) { const pixel_t * q = qstart; - q = bmp->getNextPixel(q, j / scale); - if (bmp->getFormat() == BMP_RGB565) { + q = bitmap->getNextPixel(q, j / scale); + if (bitmap->getFormat() == BMP_RGB565) { RGB_SPLIT(*q, r, g, b); drawPixel(p, ARGB_JOIN(0xF, r>>1, g>>2, b>>1)); } @@ -148,11 +126,11 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, else { for (int i = 0; i < scaledh; i++) { pixel_t * p = getPixelPtrAbs(x, y + i); - const pixel_t * qstart = bmp->getPixelPtrAbs(srcx, srcy + int(i / scale)); + const pixel_t * qstart = bitmap->getPixelPtrAbs(srcx, srcy + int(i / scale)); for (int j = 0; j < scaledw; j++) { const pixel_t * q = qstart; - q = bmp->getNextPixel(q, j / scale); - if (bmp->getFormat() == BMP_ARGB4444) { + q = bitmap->getNextPixel(q, j / scale); + if (bitmap->getFormat() == BMP_ARGB4444) { ARGB_SPLIT(*q, a, r, g, b); drawAlphaPixel(p, a, RGB_JOIN(r<<1, g<<2, b<<1)); } @@ -166,12 +144,7 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, } } -template void BitmapBuffer::drawBitmap(coord_t, coord_t, BitmapBufferBase const *, coord_t, coord_t, coord_t, coord_t, float); -template void BitmapBuffer::drawBitmap(coord_t, coord_t, const BitmapBuffer *, coord_t, coord_t, coord_t, coord_t, float); -template void BitmapBuffer::drawBitmap(coord_t, coord_t, const RLEBitmap *, coord_t, coord_t, coord_t, coord_t, float); - -template -void BitmapBuffer::drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) +void BitmapBuffer::drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) { if (bitmap) { auto scale = bitmap->getScale(w, h); @@ -181,11 +154,9 @@ void BitmapBuffer::drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coor } } -template void BitmapBuffer::drawScaledBitmap(const BitmapBuffer *, coord_t, coord_t, coord_t, coord_t); - void BitmapBuffer::drawAlphaPixel(pixel_t * p, uint8_t alpha, Color565 color) { - if (format == BMP_RGB565) { + if (_format == BMP_RGB565) { if (alpha == ALPHA_MAX) { drawPixel(p, color); } @@ -199,7 +170,7 @@ void BitmapBuffer::drawAlphaPixel(pixel_t * p, uint8_t alpha, Color565 color) drawPixel(p, RGB_JOIN(r, g, b)); } } - else if (format == BMP_ARGB4444) { + else if (_format == BMP_ARGB4444) { if (alpha == ALPHA_MAX) { drawPixel(p, RGB565_TO_ARGB4444(color, 0xFF)); } @@ -536,13 +507,13 @@ void BitmapBuffer::fillRectangle(coord_t x, coord_t y, coord_t w, coord_t h, pix void BitmapBuffer::drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color) { - if (format == BMP_RGB565) + if (_format == BMP_RGB565) fillRectangle(x, y, w, h, color); else fillRectangle(x, y, w, h, RGB565_TO_ARGB4444(color, 0xFF)); } -void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const BitmapMask * mask, Color565 color) +void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const Mask * mask, Color565 color) { coord_t maskHeight = mask->height(); while (h > 0) { @@ -728,7 +699,7 @@ class Slope int value; }; -void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * img, LcdColor color, int startAngle, int endAngle) +void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const Mask * mask, LcdColor color, int startAngle, int endAngle) { if (endAngle == startAngle) { endAngle += 1; @@ -739,10 +710,9 @@ void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * im auto rgb565 = COLOR_TO_RGB565(color); - auto bitmap = (BitmapData *)img; - coord_t width = bitmap->width(); - coord_t height = bitmap->height(); - const uint8_t * q = bitmap->data; + coord_t width = mask->width(); + coord_t height = mask->height(); + const uint8_t * q = mask->getData(); int w2 = width / 2; int h2 = height / 2; @@ -799,8 +769,7 @@ void BitmapBuffer::drawAnnulusSector(coord_t x, coord_t y, coord_t internalRadiu } } -template -void BitmapBuffer::drawMask(coord_t x, coord_t y, const T * mask, Color565 color, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch) +void BitmapBuffer::drawMask(coord_t x, coord_t y, const Mask * mask, Color565 color, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch) { if (!mask) return; @@ -848,14 +817,10 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const T * mask, Color565 color } auto rgb565 = COLOR_TO_RGB565(color); - DMACopyAlphaMask(data, format == BMP_ARGB4444, _width, _height, x, y, mask->getData(), maskWidth, maskHeight, srcx, srcy, srcw, srch, rgb565); + DMACopyAlphaMask(data, _format == BMP_ARGB4444, _width, _height, x, y, mask->getData(), maskWidth, maskHeight, srcx, srcy, srcw, srch, rgb565); } -template void BitmapBuffer::drawMask(coord_t, coord_t, const BitmapData *, Color565, coord_t, coord_t, coord_t, coord_t); -template void BitmapBuffer::drawMask(coord_t, coord_t, const BitmapMask *, Color565, coord_t, coord_t, coord_t, coord_t); -template void BitmapBuffer::drawMask(coord_t, coord_t, const StaticMask *, Color565, coord_t, coord_t, coord_t, coord_t); - -void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height) +void BitmapBuffer::drawMask(coord_t x, coord_t y, const Mask * mask, const Bitmap * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height) { if (!mask || !srcBitmap) return; @@ -899,12 +864,9 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const } } -uint8_t BitmapBuffer::drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color) +void BitmapBuffer::drawChar(coord_t x, coord_t y, const FontGlyph & glyph, LcdColor color) { - if (glyph.width) { - drawMask(x, y, glyph.font->getBitmapData(), color, glyph.offset, 0, glyph.width); - } - return glyph.width; + drawMask(x, y, glyph.data, color, glyph.offset, 0, glyph.width); } #define INCREMENT_POS(delta) do { if (flags & VERTICAL) y -= delta; else x += delta; } while(0) @@ -934,37 +896,103 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char * s, uint8_ coord_t & pos = (flags & VERTICAL) ? y : x; const coord_t orig_pos = pos; - for (int i = 0; len == 0 || i < len; ++i) { - unsigned int c = uint8_t(*s); - // TRACE("c = %d %o 0x%X '%c'", c, c, c, c); + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = getNextUnicodeChar(curr); if (!c) { break; } - else if (c >= CJK_BYTE1_MIN) { - // CJK char - auto glyph = font->getCJKChar(c, *++s); - // TRACE("CJK = %d", c); - uint8_t width = drawChar(x, y, glyph, color); - INCREMENT_POS(width + CHAR_SPACING); - } - else if (c >= 0x20) { - auto glyph = font->getChar(c); - uint8_t width = drawChar(x, y, glyph, color); - if (c >= '0' && c <= '9') - INCREMENT_POS(font->getChar('9').width + CHAR_SPACING); + + if (c == ' ') { + INCREMENT_POS(font->getSpaceWidth()); + continue; + } + + if (c == '\n') { + pos = orig_pos; + if (flags & VERTICAL) + x += height; else - INCREMENT_POS(width + CHAR_SPACING); + y += height; + continue; + } + + auto glyph = font->getGlyph(c); + // TRACE("c = '%lc' 0x%X offset=%d width=%d", c, c, glyph.offset, glyph.width); + if (glyph.width) { + drawChar(x, y, glyph, color); + INCREMENT_POS(glyph.width + font->getSpacing()); + } + else { + TRACE("Missing glyph '%lc' hex=0x%X", c, c); + INCREMENT_POS(font->getSpacing()); + } + } + + RESTORE_OFFSET(); + + return ((flags & RIGHT) ? orig_pos : pos) - offsetX; +} + + +coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags) +{ + MOVE_OFFSET(); + + auto font = getFont(flags); + int height = font->getHeight(); + + if (y + height <= ymin || y >= ymax) { + RESTORE_OFFSET(); + return x; + } + + if (flags & (RIGHT | CENTERED)) { + int width = font->getTextWidth(s, len); + if (flags & RIGHT) { + INCREMENT_POS(-width); + } + else if (flags & CENTERED) { + INCREMENT_POS(-width / 2); } - else if (c == '\n') { + } + + coord_t & pos = (flags & VERTICAL) ? y : x; + const coord_t orig_pos = pos; + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = *curr++; + + if (!c) { + break; + } + + if (c == ' ') { + INCREMENT_POS(font->getSpaceWidth()); + continue; + } + + if (c == '\n') { pos = orig_pos; if (flags & VERTICAL) x += height; else y += height; + continue; } - s++; + auto glyph = font->getGlyph(c); + // TRACE("c = '%lc' 0x%X offset=%d width=%d", c, c, glyph.offset, glyph.width); + if (glyph.width) { + drawChar(x, y, glyph, color); + INCREMENT_POS(glyph.width + font->getSpacing()); + } + else { + TRACE("Missing glyph '%lc' hex=0x%X", c, c); + INCREMENT_POS(font->getSpacing()); + } } RESTORE_OFFSET(); @@ -1050,20 +1078,20 @@ coord_t BitmapBuffer::drawNumber(coord_t x, coord_t y, int32_t val, LcdColor col //} // -BitmapBuffer * BitmapBuffer::load(const char * filename, int maxSize) +Bitmap * Bitmap::load(const char * path, int maxSize) { - auto ext = getFileExtension(filename); + auto ext = getFileExtension(path); if (ext && !strcmp(ext, ".bmp")) - return load_bmp(filename, maxSize); + return load_bmp(path, maxSize); else - return load_stb(filename, maxSize); + return load_stb(path, maxSize); } -BitmapMask * BitmapMask::load(const char * filename, int maxSize) +Mask * Mask::load(const char * path, int maxSize) { - BitmapBuffer * bitmap = BitmapBuffer::load(filename, maxSize); + auto bitmap = Bitmap::load(path, maxSize); if (bitmap) { - BitmapMask * result = BitmapMask::allocate(BMP_RGB565, bitmap->width(), bitmap->height()); + Mask * result = Mask::allocate(bitmap->width(), bitmap->height()); if (result) { auto * q = result->getData(); for (const auto * p = bitmap->getData(); p < bitmap->getDataEnd(); p++) { @@ -1076,10 +1104,51 @@ BitmapMask * BitmapMask::load(const char * filename, int maxSize) return nullptr; } +Mask * Mask::decodeRle(const uint8_t * data) +{ + auto width = *((uint16_t *)data); + auto height = *((uint16_t *)(data + 2)); + + auto result = Mask::allocate(width, height); + + uint8_t prevByte = 0; + bool prevByteValid = false; + + auto d = result->getData(); + auto destDataEnd = result->getDataEnd(); + + data += 4; + + while (d < destDataEnd) { + uint8_t byte = *data++; + *d++ = byte; + + if (prevByteValid && byte == prevByte) { + uint8_t count = *data++; + + if (d + count > destDataEnd) { + TRACE("RLE decode error: destination overflow!"); + delete result; + return nullptr; + } + + memset(d, byte, count); + d += count; + prevByteValid = false; + } + else { + prevByte = byte; + prevByteValid = true; + } + } + + return result; +} + BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, Color565 foreground, Color565 background, int maxSize) { BitmapBuffer * result = nullptr; - const auto * mask = BitmapMask::load(filename, maxSize); + const auto * mask = Mask::load(filename, maxSize); if (mask) { result = BitmapBuffer::allocate(BMP_RGB565, mask->width(), mask->height()); if (result) { @@ -1091,7 +1160,7 @@ BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, Color56 return result; } -BitmapBuffer * BitmapBuffer::load_bmp(const char * filename, int maxSize) +Bitmap * Bitmap::load_bmp(const char * filename, int maxSize) { uint8_t palette[16]; @@ -1174,7 +1243,7 @@ BitmapBuffer * BitmapBuffer::load_bmp(const char * filename, int maxSize) return nullptr; } - auto bmp = BitmapBuffer::allocate(BMP_RGB565, w, h); + auto bmp = Bitmap::allocate(BMP_RGB565, w, h); if (!bmp) { TRACE("Bitmap::load(%s) failed: malloc error", filename); return nullptr; @@ -1316,7 +1385,7 @@ void * stb_realloc(void *ptr, unsigned int oldsz, unsigned int newsz) #define STB_IMAGE_IMPLEMENTATION #include "thirdparty/stb/stb_image.h" -BitmapBuffer * BitmapBuffer::load_stb(const char * filename, int maxSize) +Bitmap * Bitmap::load_stb(const char * filename, int maxSize) { int w, h, n; unsigned char * img; @@ -1354,7 +1423,7 @@ BitmapBuffer * BitmapBuffer::load_stb(const char * filename, int maxSize) } // convert to RGB565 or ARGB4444 format - auto bmp = BitmapBuffer::allocate(n == 4 ? BMP_ARGB4444 : BMP_RGB565, w, h); + auto bmp = Bitmap::allocate(n == 4 ? BMP_ARGB4444 : BMP_RGB565, w, h); if (!bmp) { TRACE("Bitmap::load(%s) malloc failed", filename); stbi_image_free(img); diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 51b79e9..c9f45c4 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -22,14 +22,14 @@ #include #include #include -#include "bitmapdata.h" #include "libopenui_types.h" #include "libopenui_defines.h" #include "libopenui_depends.h" #include "libopenui_helpers.h" -#include "font.h" #include "debug.h" +class FontGlyph; + constexpr uint8_t SOLID = 0xFF; constexpr uint8_t DOTTED = 0x55; constexpr uint8_t DASHED = 0x33; @@ -49,138 +49,119 @@ enum BitmapFormat }; template -class BitmapBufferBase +class Raster { public: - BitmapBufferBase(uint8_t format, uint16_t width, uint16_t height, T * data): - format(format), - _width(width), - _height(height), - xmax(width), - ymax(height), - data(data), - dataEnd(data + (width * height)) - { - } - - BitmapBufferBase(uint8_t format, T * data): - format(format), - _width(*((uint16_t*)data)), - _height(*(((uint16_t*)data) + 1)), - xmax(_width), - ymax(_height), - data((T *)(((uint16_t *)data) + 2)), - dataEnd((T *)(((uint16_t *)data) + 2) + (_width * _height)) - { - } - - [[nodiscard]] inline bool isValid() const - { - return data != nullptr; - } - - inline void clearClippingRect() - { - xmin = 0; - xmax = _width; - ymin = 0; - ymax = _height; - } - - inline void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax) - { - this->xmin = max(0, xmin); - this->xmax = min(_width, xmax); - this->ymin = max(0, ymin); - this->ymax = min(_height, ymax); - } - - inline void setClippingRect(const rect_t & rect) - { - setClippingRect(rect.left(), rect.right(), rect.top(), rect.bottom()); - } - - inline void getClippingRect(coord_t & xmin, coord_t & xmax, coord_t & ymin, coord_t & ymax) const - { - xmin = this->xmin; - xmax = this->xmax; - ymin = this->ymin; - ymax = this->ymax; - } - - inline rect_t getClippingRect() const - { - return {xmin, ymin, xmax - xmin, ymax - ymin }; - } + using DataType = T; - inline void setOffsetX(coord_t offsetX) + Raster(const Raster & other): + _width(other._width), + _height(other._height), + data(other.data), + dataEnd(other.dataEnd), + dataAllocated(false) { - this->offsetX = offsetX; } - inline void setOffsetY(coord_t offsetY) + Raster(uint16_t width, uint16_t height): + _width(width), + _height(height), + data((T *)malloc(align32(width * height * sizeof(T)))), + dataEnd(data + (width * height)), + dataAllocated(true) { - this->offsetY = offsetY; } - inline void setOffset(coord_t offsetX, coord_t offsetY) + Raster(uint16_t width, uint16_t height, T * data): + _width(width), + _height(height), + data(data), + dataEnd(data + (width * height)), + dataAllocated(false) { - setOffsetX(offsetX); - setOffsetY(offsetY); } - inline void clearOffset() + ~Raster() { - setOffset(0, 0); + if (dataAllocated) { + free(data); + } } - inline void reset() + [[nodiscard]] inline uint16_t width() const { - clearOffset(); - clearClippingRect(); + return _width; } - [[nodiscard]] coord_t getOffsetX() const + [[nodiscard]] inline uint16_t height() const { - return offsetX; + return _height; } - [[nodiscard]] coord_t getOffsetY() const + [[nodiscard]] float getScale(coord_t w, coord_t h) const { - return offsetY; + auto vscale = float(h) / height(); + auto hscale = float(w) / width(); + return vscale < hscale ? vscale : hscale; } - [[nodiscard]] inline uint8_t getFormat() const + [[nodiscard]] inline T * getData() const { - return format; + return data; } - [[nodiscard]] inline uint16_t width() const + [[nodiscard]] inline T * getDataEnd() const { - return _width; + return dataEnd; } - [[nodiscard]] inline uint16_t height() const + [[nodiscard]] inline bool isValid() const { - return _height; + return data != nullptr; } - [[nodiscard]] inline T * getData() const + [[nodiscard]] uint32_t getDataSize() const { - return data; + return (const uint8_t *)dataEnd - (const uint8_t *)data; } - [[nodiscard]] inline T * getDataEnd() const + [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const { - return dataEnd; +#if LCD_ORIENTATION == 180 + x = _width - x - 1; + y = _height - y - 1; +#elif LCD_ORIENTATION == 270 + #if defined(LTDC_OFFSET_X) + if (isLcdFrameBuffer(data)) + return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; + else + return &data[x * _height + y]; + #else + return &data[x * _height + y]; + #endif +#endif + return &data[y * _width + x]; } - [[nodiscard]] uint32_t getDataSize() const + inline T * getPixelPtrAbs(coord_t x, coord_t y) { - return _width * _height * sizeof(T); +#if LCD_ORIENTATION == 180 + x = _width - x - 1; + y = _height - y - 1; +#elif LCD_ORIENTATION == 270 + #if defined(LTDC_OFFSET_X) + if (isLcdFrameBuffer(data)) + return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; + else + return &data[x * _height + y]; + #else + return &data[x * _height + y]; + #endif +#endif + return &data[y * _width + x]; } - inline T * getNextPixel(T * pixel, coord_t count = 1) + inline const T * getNextPixel(const T * pixel, coord_t count = 1) const { #if LCD_ORIENTATION == 180 return pixel - count; @@ -200,7 +181,7 @@ class BitmapBufferBase #endif } - inline const T * getNextPixel(const T * pixel, coord_t count = 1) const + inline T * getNextPixel(T * pixel, coord_t count = 1) { #if LCD_ORIENTATION == 180 return pixel - count; @@ -220,309 +201,368 @@ class BitmapBufferBase #endif } - inline T * getPixelPtrAbs(coord_t x, coord_t y) + protected: + uint16_t _width; + uint16_t _height; + T * data; + T * dataEnd; + bool dataAllocated = false; +}; + +class Mask: public Raster +{ + protected: + using Raster::Raster; + + public: + Mask(const uint8_t * data): + Raster(*((uint16_t *)(data + 0)), *((uint16_t *)(data + 2)), (uint8_t *)(data + 4)) { -#if LCD_ORIENTATION == 180 - x = _width - x - 1; - y = _height - y - 1; -#elif LCD_ORIENTATION == 270 - #if defined(LTDC_OFFSET_X) - if (isLcdFrameBuffer(data)) - return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; - else - return &data[x * (_height) + y]; - #else - return &data[x * (_height) + y]; - #endif -#endif - return &data[y * _width + x]; } - [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const + static Mask * allocate(uint16_t width, uint16_t height) { -#if LCD_ORIENTATION == 180 - x = _width - x - 1; - y = _height - y - 1; -#elif LCD_ORIENTATION == 270 - #if defined(LTDC_OFFSET_X) - if (isLcdFrameBuffer(data)) - return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; - else - return &data[x * (_height) + y]; - #else - return &data[x * (_height) + y]; - #endif -#endif - return &data[y * _width + x]; + auto result = new Mask(width, height); + if (result && !result->isValid()) { + delete result; + result = nullptr; + } + return result; } - template - C * horizontalFlip() const + static Mask * allocate(const Mask * from, uint16_t width, uint16_t height) { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } + return allocate(width, height); + } -#if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &data[(width() - 1 - x) * height() + y]; - *destData = *srcData; - } - } -#else - auto * srcData = data + width(); - auto * destData = result->data; - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - *(destData++) = *(--srcData); - } - srcData += 2 * width(); - } -#endif - return result; + static Mask * load(const char * path, int maxSize = -1); + + static Mask * decodeRle(const uint8_t * data); +}; + +class Bitmap: public Raster +{ + public: + Bitmap(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): + Raster(width, height, data), + _format(format) + { } - template - C * verticalFlip() const + Bitmap(const uint8_t * data): + Bitmap(*((uint32_t *)data), *((uint16_t *)(data + 4)), *((uint16_t *)(data + 6)), (pixel_t *)(data + 8)) { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } - -#if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &data[x * height() + height() - 1 - y]; - *destData = *srcData; - } - } -#else - auto * srcData = getDataEnd() - width(); - auto * destData = result->data; - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - *(destData++) = *(srcData++); - } - srcData -= 2 * width(); + } + + static Bitmap * allocate(uint8_t format, uint16_t width, uint16_t height) + { + auto result = new Bitmap(format, width, height); + if (result && !result->isValid()) { + delete result; + result = nullptr; } -#endif return result; } - template - C * rotate(float radians) const + static Bitmap * allocate(const Bitmap * from, uint16_t width, uint16_t height) { - float sine = sinf(radians); - float cosine = cosf(radians); + return allocate(from->getFormat(), width, height); + } - auto w = width(); - auto h = height(); + static Bitmap * load(const char * path, int maxSize = -1); - auto x0 = w / 2; - auto y0 = h / 2; + [[nodiscard]] uint8_t getFormat() const + { + return _format; + } - auto * result = C::allocate(format, w, h); - if (!result) { - return nullptr; - } + void setFormat(uint8_t format) + { + _format = format; + } - auto * srcData = data; - auto * destData = result->data; - - for (unsigned rX = 0; rX < w; rX++) { - coord_t dX = rX - x0; - for (unsigned rY = 0; rY < h; rY++) { - coord_t dY = rY - y0; - coord_t x = round(dX * cosine + dY * sine + x0); - if (x >= 0 && x < w) { - coord_t y = round(dY * cosine - dX * sine + y0); - if (y >= 0 && y < h) { - destData[rY * w + rX] = srcData[y * w + x]; - } - else { - destData[rY * w + rX] = 0; - } - } - else { - destData[rY * w + rX] = 0; - } - } - } + protected: + uint8_t _format; - return result; + Bitmap(uint8_t format, uint16_t width, uint16_t height): + Raster(width, height), + _format(format) + { } - template - C * rotate90() const - { - auto * result = C::allocate(format, height(), width()); - if (!result) { - return nullptr; + static Bitmap * load_bmp(const char * filename, int maxSize = -1); + static Bitmap * load_stb(const char * filename, int maxSize = -1); +}; + +template +C * invert(const C * source) +{ + auto result = C::allocate(source, source->width(), source->height()); + if (result) { + auto * srcData = source->getData(); + auto * destData = result->getData(); + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < source->width(); x++) { + destData[x] = 0xFF - srcData[x]; } + srcData += source->width(); + destData += source->width(); + } + } + return result; +} + +template +C * verticalFlip(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } + +#if LCD_ORIENTATION == 270 + auto sourceData = source->getData(); + auto sourceWidth = source->width(); + auto sourceHeight = source->height(); + auto resultData = result->getData(); + for (coord_t y = 0; y < sourceHeight; y++) { + for (coord_t x = 0; x < sourceWidth; x++) { + auto * p = &sourceData[x * sourceHeight + sourceHeight - 1 - y]; + auto * q = &resultData[x * sourceHeight + y]; + *q = *p; + } + } +#else + auto * srcData = source->getData() + source->width() * (source->height() - 1); + auto * destData = result->getData(); + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < source->width(); x++) { + *(destData++) = *(srcData++); + } + srcData -= 2 * source->width(); + } +#endif + return result; +} - #if LCD_ORIENTATION == 270 - auto * srcData = data; - auto * destData = result->data; - for (uint8_t y = 0; y < width(); y++) { - for (uint8_t x = 0; x < height(); x++) { - destData[x * width() + y] = srcData[y * height() + x]; +template +C * rotate(const C * source, float radians) +{ + float sine = sinf(radians); + float cosine = cosf(radians); + + auto w = source->width(); + auto h = source->height(); + + auto x0 = w / 2; + auto y0 = h / 2; + + auto * result = C::allocate(source, w, h); + if (!result) { + return nullptr; + } + + auto * srcData = source->getData(); + auto * destData = result->getData(); + + for (unsigned rX = 0; rX < w; rX++) { + coord_t dX = rX - x0; + for (unsigned rY = 0; rY < h; rY++) { + coord_t dY = rY - y0; + coord_t x = round(dX * cosine + dY * sine + x0); + if (x >= 0 && x < w) { + coord_t y = round(dY * cosine - dX * sine + y0); + if (y >= 0 && y < h) { + destData[rY * w + rX] = srcData[y * w + x]; } - } - #else - auto * destData = result->data + height(); - for (uint8_t y = 0; y < width(); y++) { - auto * srcData = data + y; - for (uint8_t x = 0; x < height(); x++) { - destData--; - *destData = *srcData; - srcData += width(); + else { + destData[rY * w + rX] = 0; } - destData += 2 * height(); } - #endif - return result; + else { + destData[rY * w + rX] = 0; + } } + } - template - C * rotate180() const - { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } + return result; +} - auto * srcData = data; - auto * destData = result->dataEnd; - while (destData > result->data) { - *(--destData) = *srcData++; - } +template +static C * horizontalFlip(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } - return result; +#if LCD_ORIENTATION == 270 + auto sourceData = source->getData(); + auto sourceWidth = source->width(); + auto sourceHeight = source->height(); + auto resultData = result->getData(); + for (coord_t y = 0; y < sourceHeight; y++) { + for (coord_t x = 0; x < sourceWidth; x++) { + auto * p = &sourceData[(sourceWidth - 1 - x) * sourceHeight + y]; + auto * q = &resultData[x * sourceHeight + y]; + *q = *p; + } + } +#else + auto sourceWidth = source->width(); + auto * srcData = source->getData() + sourceWidth; + auto * destData = result->getData(); + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < sourceWidth; x++) { + *(destData++) = *(--srcData); + } + srcData += 2 * sourceWidth; + } +#endif + return result; +} + +template +C * rotate90(const C * source) +{ + auto * result = C::allocate(source, source->height(), source->width()); + if (!result) { + return nullptr; + } + +#if LCD_ORIENTATION == 270 + auto * srcData = source->getData(); + auto * destData = result->getData(); + for (coord_t y = 0; y < source->width(); y++) { + for (coord_t x = 0; x < source->height(); x++) { + destData[x * source->width() + y] = srcData[y * source->height() + x]; } + } +#else + auto * destData = result->getData() + source->height(); + for (coord_t y = 0; y < source->width(); y++) { + auto * srcData = source->getData() + y; + for (coord_t x = 0; x < source->height(); x++) { + destData--; + *destData = *srcData; + srcData += source->width(); + } + destData += 2 * source->height(); + } +#endif + return result; +} - protected: - uint8_t format; - coord_t _width; - coord_t _height; - coord_t xmin = 0; - coord_t xmax; - coord_t ymin = 0; - coord_t ymax; - coord_t offsetX = 0; - coord_t offsetY = 0; - T * data; - T * dataEnd; -}; +template +C * rotate180(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } -typedef BitmapBufferBase Bitmap; -typedef BitmapBufferBase StaticMask; + auto * srcData = source->getData(); + auto * destData = result->getDataEnd(); + while (destData > result->getData()) { + *(--destData) = *srcData++; + } -class RLEBitmap: public BitmapBufferBase + return result; +} + +class BitmapBuffer: public Bitmap { public: - RLEBitmap(uint8_t format, const uint8_t * rleData) : - BitmapBufferBase(format, 0, 0, nullptr) + BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): + Bitmap(format, width, height, data), + xmax(width), + ymax(height) { - _width = *((uint16_t *)rleData); - _height = *(((uint16_t *)rleData) + 1); - uint32_t pixels = _width * _height; - data = (uint16_t*)malloc(align32(pixels * sizeof(uint16_t))); - decode((uint8_t *)data, pixels * sizeof(uint16_t), rleData + 4); - dataEnd = data + pixels; } - ~RLEBitmap() + BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): + Bitmap(format, width, height), + xmax(width), + ymax(height) { - free(data); } - - static int decode(uint8_t * dest, unsigned int destSize, const uint8_t * src) + + inline void clearClippingRect() { - uint8_t prevByte = 0; - bool prevByteValid = false; - - const uint8_t * destEnd = dest + destSize; - uint8_t * d = dest; - - while (d < destEnd) { - uint8_t byte = *src++; - *d++ = byte; - - if (prevByteValid && byte == prevByte) { - uint8_t count = *src++; - - if (d + count > destEnd) { - TRACE("rle_decode_8bit: destination overflow!\n"); - return -1; - } + xmin = 0; + xmax = this->_width; + ymin = 0; + ymax = this->_height; + } + + inline void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax) + { + this->xmin = max(0, xmin); + this->xmax = min(this->_width, xmax); + this->ymin = max(0, ymin); + this->ymax = min(this->_height, ymax); + } + + inline void setClippingRect(const rect_t & rect) + { + setClippingRect(rect.left(), rect.right(), rect.top(), rect.bottom()); + } + + inline void getClippingRect(coord_t & xmin, coord_t & xmax, coord_t & ymin, coord_t & ymax) const + { + xmin = this->xmin; + xmax = this->xmax; + ymin = this->ymin; + ymax = this->ymax; + } + + inline rect_t getClippingRect() const + { + return {xmin, ymin, xmax - xmin, ymax - ymin }; + } - memset(d, byte, count); - d += count; - prevByteValid = false; - } - else { - prevByte = byte; - prevByteValid = true; - } - } + inline void setOffsetX(coord_t offsetX) + { + this->offsetX = offsetX; + } + + inline void setOffsetY(coord_t offsetY) + { + this->offsetY = offsetY; + } - return d - dest; + inline void setOffset(coord_t offsetX, coord_t offsetY) + { + setOffsetX(offsetX); + setOffsetY(offsetY); } -}; -class BitmapMask: public BitmapBufferBase -{ - public: - static BitmapMask * allocate(uint8_t format, uint16_t width, uint16_t height) + inline void clearOffset() { - auto result = new BitmapMask(format, width, height); - if (result && !result->isValid()) { - delete result; - result = nullptr; - } - return result; + setOffset(0, 0); } - protected: - BitmapMask(uint8_t format, uint16_t width, uint16_t height): - BitmapBufferBase(format, width, height, (uint8_t *)malloc(align32(width * height))) + inline void reset() { + clearOffset(); + clearClippingRect(); } - public: - ~BitmapMask() + [[nodiscard]] coord_t getOffsetX() const { - free(data); + return offsetX; } - [[nodiscard]] BitmapMask * invert() const + [[nodiscard]] coord_t getOffsetY() const { - auto result = BitmapMask::allocate(format, width(), height()); - if (result) { - auto * srcData = data; - auto * destData = result->data; - for (auto y = 0; y < height(); y++) { - for (auto x = 0; x < width(); x++) { - destData[x] = 0xFF - srcData[x]; - } - srcData += width(); - destData += width(); - } - } - return result; + return offsetY; } - static BitmapMask * load(const char * filename, int maxSize = -1); -}; + [[nodiscard]] inline DataType * getDataEnd() const + { + return dataEnd; + } -class BitmapBuffer: public BitmapBufferBase -{ - public: static BitmapBuffer * allocate(uint8_t format, uint16_t width, uint16_t height) { auto result = new BitmapBuffer(format, width, height); @@ -533,24 +573,9 @@ class BitmapBuffer: public BitmapBufferBase return result; } - protected: - BitmapBuffer(uint8_t format, uint16_t width, uint16_t height); - - public: - BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t * data); - - ~BitmapBuffer(); - - [[nodiscard]] float getScale(coord_t w, coord_t h) const - { - auto vscale = float(h) / height(); - auto hscale = float(w) / width(); - return vscale < hscale ? vscale : hscale; - } - - inline void setFormat(uint8_t format) + static BitmapBuffer * allocate(const Bitmap * from, uint16_t width, uint16_t height) { - this->format = format; + return allocate(from->getFormat(), width, height); } inline void clear(Color565 color = 0 /*black*/) @@ -565,7 +590,7 @@ class BitmapBuffer: public BitmapBufferBase if (!applyPixelClippingRect(x, y)) return nullptr; - return BitmapBufferBase::getPixelPtrAbs(x, y); + return getPixelPtrAbs(x, y); } inline void drawPixel(coord_t x, coord_t y, pixel_t value) @@ -623,7 +648,7 @@ class BitmapBuffer: public BitmapBufferBase void drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color); - void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const BitmapMask * mask, Color565 color); + void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const Mask * mask, Color565 color); void drawCircle(coord_t x, coord_t y, coord_t radius, LcdColor color); @@ -635,24 +660,26 @@ class BitmapBuffer: public BitmapBufferBase void drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle); - void drawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdColor color, int startAngle, int endAngle); - - static BitmapBuffer * load(const char * filename, int maxSize = -1); + void drawBitmapPatternPie(coord_t x0, coord_t y0, const Mask * mask, LcdColor color, int startAngle, int endAngle); static BitmapBuffer * loadMaskOnBackground(const char * filename, Color565 foreground, Color565 background, int maxSize = -1); - template - void drawMask(coord_t x, coord_t y, const T * mask, Color565 color, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0); - - void drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); + void drawMask(coord_t x, coord_t y, const Mask * mask, Color565 color, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0); + void drawMask(coord_t x, coord_t y, const Mask * mask, const Bitmap * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); coord_t drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags = 0); + coord_t drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags = 0); coord_t drawText(coord_t x, coord_t y, const char * s, LcdColor color, LcdFlags flags = 0) { return drawSizedText(x, y, s, 0, color, flags); } + coord_t drawText(coord_t x, coord_t y, const wchar_t * s, LcdColor color, LcdFlags flags = 0) + { + return drawSizedText(x, y, s, 0, color, flags); + } + coord_t drawTextAtIndex(coord_t x, coord_t y, const char * s, uint8_t idx, LcdColor color, LcdFlags flags = 0) { char length = *s++; @@ -661,20 +688,22 @@ class BitmapBuffer: public BitmapBufferBase coord_t drawNumber(coord_t x, coord_t y, int32_t val, LcdColor color, LcdFlags flags = 0, uint8_t len = 0, const char * prefix = nullptr, const char * suffix = nullptr); - template - void drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0); - void copyFrom(const BitmapBuffer * other) { DMACopyBitmap(getData(), _width, _height, 0, 0, other->getData(), _width, _height, 0, 0, _width, _height); } - template - void drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); + void drawBitmap(coord_t x, coord_t y, const Bitmap * bitmap, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0); + + void drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); protected: - static BitmapBuffer * load_bmp(const char * filename, int maxSize = -1); - static BitmapBuffer * load_stb(const char * filename, int maxSize = -1); + coord_t xmin = 0; + coord_t xmax; + coord_t ymin = 0; + coord_t ymax; + coord_t offsetX = 0; + coord_t offsetY = 0; inline bool applyClippingRect(coord_t & x, coord_t & y, coord_t & w, coord_t & h) const { @@ -716,7 +745,7 @@ class BitmapBuffer: public BitmapBufferBase return applyClippingRect(x, y, w, h); } - uint8_t drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color); + void drawChar(coord_t x, coord_t y, const FontGlyph & glyph, LcdColor color); inline void drawPixel(pixel_t * p, pixel_t value) { @@ -760,7 +789,6 @@ class BitmapBuffer: public BitmapBufferBase void fillTopFlatTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t x2, coord_t y2, LcdColor color); private: - bool dataAllocated; #if defined(DEBUG) bool leakReported = false; #endif diff --git a/src/bitmapdata.h b/src/bitmapdata.h deleted file mode 100644 index c02ce84..0000000 --- a/src/bitmapdata.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) OpenTX - * - * Source: - * https://github.com/opentx/libopenui - * - * This file is a part of libopenui library. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - */ - -#pragma once - -#include - -struct BitmapData -{ - uint16_t _width; - uint16_t _height; - uint8_t data[]; - - uint16_t width() const - { - return _width; - } - - uint16_t height() const - { - return _height; - } - - const uint8_t * getData() const - { - return data; - } -}; diff --git a/src/bufferedwindow.h b/src/bufferedwindow.h index 4603c58..60b2833 100644 --- a/src/bufferedwindow.h +++ b/src/bufferedwindow.h @@ -80,7 +80,7 @@ class TransparentBufferedWindow: public BufferedWindow class TransparentBitmapBackground: public TransparentBufferedWindow { public: - TransparentBitmapBackground(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap): + TransparentBitmapBackground(Window * parent, const rect_t & rect, const Bitmap * bitmap): TransparentBufferedWindow(parent, rect, OPAQUE), background(bitmap) { @@ -94,7 +94,7 @@ class TransparentBitmapBackground: public TransparentBufferedWindow #endif protected: - const BitmapBuffer * background; + const Bitmap * background; void paintUpdate(BitmapBuffer * dc) override { diff --git a/src/choice.h b/src/choice.h index 798a227..65552fb 100644 --- a/src/choice.h +++ b/src/choice.h @@ -112,6 +112,11 @@ class Choice: public ChoiceBase bool onTouchEnd(coord_t x, coord_t y) override; #endif + void setGetValueHandler(std::function handler) + { + getValue = std::move(handler); + } + void setSetValueHandler(std::function handler) { setValue = std::move(handler); diff --git a/src/filechoice.cpp b/src/filechoice.cpp index c1a5c42..b55ebe4 100644 --- a/src/filechoice.cpp +++ b/src/filechoice.cpp @@ -117,7 +117,7 @@ bool FileChoice::openMenu() } } - new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, exclamationIcon); + new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, maskIconExclamation); return false; } diff --git a/src/font.cpp b/src/font.cpp new file mode 100644 index 0000000..2c7822b --- /dev/null +++ b/src/font.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#include +#include +#include "bitmapbuffer.h" +#include "font.h" +#include "debug.h" + +/* Font format + * 'F', 'N', 'T', '1' + * begin: 4bytes (glyphs index start) + * end: 4bytes (glyphs index end, not included) + * specs: 2bytes * (count + 1) + * data: bitmap data in RLE format + */ +bool Font::loadFile(const char * path) +{ + TRACE("Font::loadFile('%s')", path); + + auto file = (FIL *)malloc(sizeof(FIL)); + if (!file) { + return false; + } + + FRESULT result = f_open(file, path, FA_READ); + if (result != FR_OK) { + free(file); + return false; + } + + struct { + char fmt[4]; + char name[LEN_FONT_NAME + 1]; + uint8_t rangesCount; + uint8_t spacing; + uint8_t spaceWidth; + } header; + UINT read; + result = f_read(file, (uint8_t *)&header, sizeof(header), &read); + if (result != FR_OK || read != sizeof(header) || strncmp(header.fmt, "FNT1", sizeof(header.fmt)) != 0) { + TRACE("loadFont('%s'): invalid header", path); + f_close(file); + free(file); + return false; + } + + strlcpy(this->name, header.name, sizeof(this->name)); + this->spacing = header.spacing; + this->spaceWidth = header.spaceWidth; + + for (auto i = 0; i < header.rangesCount; i++) { + struct { + uint32_t begin; + uint32_t end; + uint32_t dataSize; + } rangeHeader; + + result = f_read(file, (uint8_t *)&rangeHeader, sizeof(rangeHeader), &read); + if (result != FR_OK || read != sizeof(rangeHeader) || rangeHeader.begin >= rangeHeader.end) { + TRACE("loadFont('%s'): invalid range header", path); + f_close(file); + free(file); + return false; + } + + uint32_t specsSize = sizeof(uint16_t) * (rangeHeader.end - rangeHeader.begin + 1); + auto specs = (uint16_t *)malloc(specsSize); + if (!specs) { + f_close(file); + free(file); + return false; + } + + result = f_read(file, (uint8_t *)specs, specsSize, &read); + if (result != FR_OK || read != specsSize) { + free(specs); + f_close(file); + free(file); + return false; + } + + auto data = (uint8_t *)malloc(rangeHeader.dataSize); + if (!data) { + free(specs); + f_close(file); + free(file); + return false; + } + + result = f_read(file, (uint8_t *)data, rangeHeader.dataSize, &read); + if (result != FR_OK || read != rangeHeader.dataSize) { + free(specs); + free(data); + f_close(file); + free(file); + return false; + } + + auto mask = Mask::decodeRle(data); + free(data); + if (mask) { + ranges.push_back({rangeHeader.begin, rangeHeader.end, mask, specs}); + } + } + + // TRACE("Ranges..."); + // for (auto & range: ranges) { + // TRACE(" %d-%d", range.begin, range.end); + // } + + f_close(file); + free(file); + + return true; +} diff --git a/src/font.h b/src/font.h index 624d1f2..6126665 100644 --- a/src/font.h +++ b/src/font.h @@ -19,56 +19,78 @@ #pragma once +#include +#include #include "libopenui_types.h" #include "libopenui_config.h" -#include "bitmapdata.h" +#include "bitmapbuffer.h" +#include "unicode.h" -constexpr uint8_t CJK_BYTE1_MIN = 0xFD; +constexpr uint8_t LEN_FONT_NAME = 8; -inline bool hasChineseChars(const char * str) -{ - while (*str) { - uint8_t c = *str++; - if (c >= CJK_BYTE1_MIN) { - return true; - } - } - return false; -} - -inline const char * findNextLine(const char * stack) +inline const char * findNextLine(const char * str) { while (true) { - const char * pos = strchr(stack, '\n'); - if (!pos) + auto current = str; + auto c = getNextUnicodeChar(str); + if (c == '\0') return nullptr; - if (pos == stack || *((uint8_t *)(pos - 1)) < CJK_BYTE1_MIN) - return pos; - stack = pos + 1; + else if (c == '\n') + return current; } } +class FontGlyph +{ + public: + const Mask * data; + unsigned offset; + uint8_t width; +}; + class Font { public: - struct Glyph + struct GlyphRange { - const Font * font; - unsigned offset; - uint8_t width; + uint32_t begin; + uint32_t end; + const Mask * data; + const uint16_t * specs; }; - Font(const char * name, uint16_t count, const BitmapData * data, const uint16_t * specs): - name(name), - count(count), - data(data), - specs(specs) + Font() = default; + + Font(const char * name) + { + strlcpy(this->name, name, sizeof(this->name)); + } + + Font(const char * name, uint8_t spacing, uint8_t spaceWidth, std::list ranges): + spacing(spacing), + spaceWidth(spaceWidth), + ranges(std::move(ranges)) { + strlcpy(this->name, name, sizeof(this->name)); + } + + bool loadFile(const char * path); + + void addGlyphs(const Font * other) + { + for (auto range: other->ranges) { + addRange(range); + } + } + + void addRange(const GlyphRange & range) + { + ranges.push_back(range); } coord_t getHeight() const { - return specs[0]; + return ranges.empty() ? 0 : ranges.front().data->height(); } const char * getName() const @@ -76,78 +98,147 @@ class Font return name; } - Glyph getGlyph(unsigned index) const + FontGlyph getGlyph(wchar_t c) const { - // TODO check index not over table - auto offset = specs[index + 1]; - return {this, offset, uint8_t(specs[index + 2] - offset)}; + for (auto & range: ranges) { + if (range.begin <= uint32_t(c) && uint32_t(c) < range.end) { + auto index = c - range.begin; + unsigned offset = range.specs[index]; + uint8_t width = range.specs[index + 1] - offset; + if (width > 0) { + return {range.data, offset, width}; + } + } + } + return {}; } - Glyph getChar(uint8_t c) const + bool hasGlyphs(const char * s) const { - if (c >= 0x20) { - return getGlyph(c - 0x20); - } - else { - return {this, 0, 0}; + auto curr = s; + while (true) { + auto c = getNextUnicodeChar(curr); + if (!c) { + return true; + } + if (c != '\n' && c != ' ') { + auto glyph = getGlyph(c); + if (glyph.width == 0) { + return false; + } + } } } - [[nodiscard]] Glyph getCJKChar(uint8_t byte1, uint8_t byte2) const + uint8_t getGlyphWidth(wchar_t c) const { - unsigned result = byte2 + ((byte1 - CJK_BYTE1_MIN) << 8u) - 1; - if (result >= 0x200) - result -= 1; - if (result >= 0x100) - result -= 1; - return getGlyph(CJK_FIRST_LETTER_INDEX + result); + if (c == ' ') { + return spaceWidth; + } + else { + auto glyph = getGlyph(c); + return glyph.width + spacing; + } } coord_t getTextWidth(const char * s, int len = 0) const { int currentLineWidth = 0; int result = 0; - for (int i = 0; len == 0 || i < len; ++i) { - unsigned c = uint8_t(*s); + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = getNextUnicodeChar(curr); if (!c) { break; } - else if (c >= CJK_BYTE1_MIN) { - currentLineWidth += getCJKChar(c, *++s).width + CHAR_SPACING; + if (c == '\n') { + if (currentLineWidth > result) + result = currentLineWidth; + currentLineWidth = 0; } - else if (c >= 0x20) { - if (c >= '0' && c <= '9') - currentLineWidth += getChar('9').width + CHAR_SPACING; - else - currentLineWidth += getChar(c).width + CHAR_SPACING; + else { + currentLineWidth += getGlyphWidth(c); + } + } + + return currentLineWidth > result ? currentLineWidth : result; + } + + coord_t getTextWidth(const wchar_t * s, int len = 0) const + { + int currentLineWidth = 0; + int result = 0; + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = *curr++; + if (!c) { + break; } - else if (c == '\n') { + if (c == '\n') { if (currentLineWidth > result) result = currentLineWidth; currentLineWidth = 0; } - - ++s; + else { + currentLineWidth += getGlyphWidth(c); + } } return currentLineWidth > result ? currentLineWidth : result; } - [[nodiscard]] const BitmapData * getBitmapData() const + uint8_t getSpacing() const + { + return spacing; + } + + uint8_t getSpaceWidth() const + { + return spaceWidth; + } + + wchar_t begin() const + { + wchar_t result = ' '; + for (auto & range: ranges) { + if (range.begin < uint32_t(result)) { + result = range.begin; + } + } + return result; + } + + wchar_t end() const + { + wchar_t result = ' '; + for (auto & range: ranges) { + if (range.end > uint32_t(result)) { + result = range.end; + } + } + return result; + } + + uint8_t isSubsetLoaded(const char * value) const { - return data; + return std::find_if(subsets.begin(), subsets.end(), [value](const char * s) { + return std::strcmp(s, value) == 0; + }) != subsets.end(); } - [[nodiscard]] bool hasCJKChars() const + void setSubsetLoaded(const char * value) { - return count > CJK_FIRST_LETTER_INDEX; + subsets.push_back(value); } protected: - const char * name; - uint16_t count; - const BitmapData * data; - const uint16_t * specs; + char name[LEN_FONT_NAME + 1]; + uint8_t spacing = 1; + uint8_t spaceWidth = 4; + std::list subsets; + std::list ranges; }; const Font * getFont(LcdFlags flags); @@ -158,7 +249,8 @@ inline coord_t getFontHeight(LcdFlags flags) return getFont(flags)->getHeight(); } -inline coord_t getTextWidth(const char * s, int len = 0, LcdFlags flags = 0) +template +inline coord_t getTextWidth(const T * s, int len = 0, LcdFlags flags = 0) { return getFont(flags)->getTextWidth(s, len); } diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index fdf66f0..b2bd5c1 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -22,87 +22,55 @@ using namespace ui; -TextKeyboard * TextKeyboard::_instance = nullptr; - -const uint8_t LBM_KEY_UPPERCASE[] = { -#include "mask_key_uppercase.lbm" -}; - -const uint8_t LBM_KEY_LOWERCASE[] = { -#include "mask_key_lowercase.lbm" -}; - -const uint8_t LBM_KEY_BACKSPACE[] = { -#include "mask_key_backspace.lbm" -}; - -const uint8_t LBM_KEY_LETTERS[] = { -#include "mask_key_letters.lbm" -}; - -const uint8_t LBM_KEY_NUMBERS[] = { -#include "mask_key_numbers.lbm" -}; - -const uint8_t LBM_KEY_SPACEBAR[] = { -#include "mask_key_spacebar.lbm" -}; - -const uint8_t * const LBM_SPECIAL_KEYS[] = { - LBM_KEY_BACKSPACE, - LBM_KEY_UPPERCASE, - LBM_KEY_LOWERCASE, - LBM_KEY_LETTERS, - LBM_KEY_NUMBERS, -}; +TextKeyboardBase * TextKeyboardBase::_instance = nullptr; const char * const KEYBOARD_QWERTY_LOWERCASE[] = { "qwertyuiop", " asdfghjkl", KEYBOARD_SET_UPPERCASE "zxcvbnm" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_AZERTY_LOWERCASE[] = { "azertyuiop", " qsdfghjklm", KEYBOARD_SET_UPPERCASE "wxcvbn," KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTZ_LOWERCASE[] = { "qwertzuiop", " asdfghjkl", KEYBOARD_SET_UPPERCASE "yxcvbnm" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTY_UPPERCASE[] = { "QWERTYUIOP", " ASDFGHJKL", KEYBOARD_SET_LOWERCASE "ZXCVBNM" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_AZERTY_UPPERCASE[] = { "AZERTYUIOP", " QSDFGHJKLM", KEYBOARD_SET_LOWERCASE "WXCVBN," KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTZ_UPPERCASE[] = { "QWERTZUIOP", " ASDFGHJKL", KEYBOARD_SET_LOWERCASE "YXCVBNM" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_NUMBERS[] = { "1234567890", - KEYBOARD_NUMBERS_FIRST_LINE_SPECIAL_CHARS, - KEYBOARD_NUMBERS_SECOND_LINE_SPECIAL_CHARS KEYBOARD_BACKSPACE, - KEYBOARD_SET_LETTERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SPECIAL_CHAR_1 KEYBOARD_SPECIAL_CHAR_2 KEYBOARD_SPECIAL_CHAR_3 KEYBOARD_SPECIAL_CHAR_4 KEYBOARD_SPECIAL_CHAR_5 KEYBOARD_SPECIAL_CHAR_6 KEYBOARD_SPECIAL_CHAR_7 KEYBOARD_SPECIAL_CHAR_8 KEYBOARD_SPECIAL_CHAR_9 KEYBOARD_SPECIAL_CHAR_10, + KEYBOARD_SPECIAL_CHAR_11 KEYBOARD_SPECIAL_CHAR_12 KEYBOARD_SPECIAL_CHAR_13 KEYBOARD_SPECIAL_CHAR_14 KEYBOARD_SPECIAL_CHAR_15 KEYBOARD_SPECIAL_CHAR_16 KEYBOARD_SPECIAL_CHAR_17 KEYBOARD_SPECIAL_CHAR_18 KEYBOARD_SPECIAL_CHAR_19 KEYBOARD_BACKSPACE, + KEYBOARD_SET_LETTERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const * KEYBOARDS[] = { @@ -115,15 +83,21 @@ const char * const * KEYBOARDS[] = { KEYBOARD_NUMBERS, }; -TextKeyboard::TextKeyboard(): - Keyboard(TEXT_KEYBOARD_HEIGHT) -{ -} - -TextKeyboard::~TextKeyboard() -{ - _instance = nullptr; -} +#if defined(LIBOPENUI_USE_DEFAULT_TEXT_KEYBOARD) +extern const Mask * maskKeyUppercase; +extern const Mask * maskKeyLowercase; +extern const Mask * maskKeyBackspace; +extern const Mask * maskKeyLetters; +extern const Mask * maskKeyNumbers; +extern const Mask * maskKeySpacebar; + +const Mask * const MASKS_SPECIAL_KEYS[] = { + maskKeyBackspace, + maskKeyUppercase, + maskKeyLowercase, + maskKeyLetters, + maskKeyNumbers, +}; void TextKeyboard::paint(BitmapBuffer * dc) { @@ -139,21 +113,22 @@ void TextKeyboard::paint(BitmapBuffer * dc) if (*c == ' ') { x += 15; } - else if (*c == KEYBOARD_SPACE[0]) { + else if (uint8_t(*c) <= SPECIAL_KEY_WITH_BITMAP_LAST) { + // special keys drawn with a bitmap + dc->drawMask(x, y, MASKS_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); + x += 45; + } + else if (*c == SPECIAL_KEY_SPACEBAR) { // spacebar - dc->drawMask(x, y, (const BitmapData *)LBM_KEY_SPACEBAR, DEFAULT_COLOR); + dc->drawMask(x, y, maskKeySpacebar, DEFAULT_COLOR); x += 135; } - else if (*c == KEYBOARD_ENTER[0]) { + else if (*c == SPECIAL_KEY_ENTER) { // enter dc->drawPlainFilledRectangle(x, y - 2, 80, 25, DISABLE_COLOR); - dc->drawText(x+40, y, "ENTER", DEFAULT_COLOR, CENTERED); + dc->drawText(x + 40, y, "ENTER", DEFAULT_COLOR, CENTERED); x += 80; } - else if (uint8_t(*c) <= KEYBOARD_SET_NUMBERS[0]) { - dc->drawMask(x, y, (const BitmapData *)LBM_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); - x += 45; - } else { dc->drawSizedText(x, y, c, 1, DEFAULT_COLOR); x += 30; @@ -170,19 +145,19 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) onKeyPress(); uint8_t row = max(0, y - 5) / 40; - const char * key = layout[row]; + const uint8_t * key = (uint8_t *)layout[row]; while (*key) { if (*key == ' ') { x -= 15; } - else if (*key == KEYBOARD_SPACE[0]) { + else if (*key == SPECIAL_KEY_SPACEBAR) { if (x <= 135) { pushEvent(EVT_VIRTUAL_KEY(' ')); return true; } x -= 135; } - else if (*key == KEYBOARD_ENTER[0]) { + else if (*key == SPECIAL_KEY_ENTER) { if (x <= 80) { // enter hide(); @@ -190,30 +165,29 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) } x -= 80; } - else if (uint8_t(*key) <= KEYBOARD_SET_NUMBERS[0]) { + else if (*key <= SPECIAL_KEY_WITH_BITMAP_LAST) { if (x <= 45) { - uint8_t specialKey = *key; - switch (specialKey) { + switch (*key) { case SPECIAL_KEY_BACKSPACE: // backspace events.push(EVT_VIRTUAL_KEY(SPECIAL_KEY_BACKSPACE)); break; - case SPECIAL_KEY_SET_LOWERCASE: - layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; - invalidate(); - break; case SPECIAL_KEY_SET_UPPERCASE: layoutIndex = getKeyboardLayout(); invalidate(); break; - case SPECIAL_KEY_SET_NUMBERS: - layoutIndex = KEYBOARD_LAYOUT_NUMBERS; + case SPECIAL_KEY_SET_LOWERCASE: + layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; case SPECIAL_KEY_SET_LETTERS: layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; + case SPECIAL_KEY_SET_NUMBERS: + layoutIndex = KEYBOARD_LAYOUT_NUMBERS; + invalidate(); + break; } break; } @@ -231,3 +205,4 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) return true; } +#endif diff --git a/src/keyboard_text.h b/src/keyboard_text.h index af330c8..c1cb9cc 100644 --- a/src/keyboard_text.h +++ b/src/keyboard_text.h @@ -22,8 +22,6 @@ #include "keyboard_base.h" #include "textedit.h" -extern const uint8_t LBM_KEY_SPACEBAR[]; -extern const uint8_t * const LBM_SPECIAL_KEYS[]; extern const char * const * KEYBOARDS[]; constexpr uint8_t LOWERCASE_OPTION = 1; @@ -48,50 +46,57 @@ enum KeyboardLayout namespace ui { -class TextKeyboard: public Keyboard +class TextKeyboardBase: public Keyboard { public: - TextKeyboard(); + TextKeyboardBase(): + Keyboard(TEXT_KEYBOARD_HEIGHT) + { + } - ~TextKeyboard() override; + ~TextKeyboardBase() override + { + _instance = nullptr; + } -#if defined(DEBUG_WINDOWS) - [[nodiscard]] std::string getName() const override + static void show(FormField * field) { - return "TextKeyboard"; + if (activeKeyboard != _instance) { + _instance->layoutIndex = KEYBOARD_LAYOUT_INDEX_START; + } + _instance->setField(field); } -#endif - static void setInstance(TextKeyboard * keyboard) + static void setInstance(TextKeyboardBase * keyboard) { _instance = keyboard; } - static TextKeyboard * instance() + static TextKeyboardBase * instance() { return _instance; } - static void show(TextEdit * field) +#if defined(DEBUG_WINDOWS) + [[nodiscard]] std::string getName() const override { - if (!_instance) { - _instance = new TextKeyboard(); - } - if (activeKeyboard != _instance) { - _instance->layoutIndex = KEYBOARD_LAYOUT_INDEX_START; - } - _instance->setField(field); + return "TextKeyboard"; } +#endif + protected: + static TextKeyboardBase * _instance; + unsigned layoutIndex = KEYBOARD_LAYOUT_INDEX_START; +}; + +class TextKeyboard: public TextKeyboardBase +{ + protected: void paint(BitmapBuffer * dc) override; #if defined(HARDWARE_TOUCH) bool onTouchEnd(coord_t x, coord_t y) override; #endif - - protected: - static TextKeyboard * _instance; - unsigned layoutIndex = KEYBOARD_LAYOUT_INDEX_START; }; } diff --git a/src/libopenui_compat.h b/src/libopenui_compat.h index a552df5..376235d 100644 --- a/src/libopenui_compat.h +++ b/src/libopenui_compat.h @@ -20,6 +20,7 @@ #pragma once #if defined(__MINGW32__) || !defined(__GNUC__) + #undef UNICODE #include #include #include diff --git a/src/libopenui_defines.h b/src/libopenui_defines.h index 10a0a35..f0c40e5 100644 --- a/src/libopenui_defines.h +++ b/src/libopenui_defines.h @@ -51,8 +51,8 @@ constexpr uint8_t DECIMALS(uint8_t value) return value << 4u; } -#define FONT_MASK 0x0F00u -#define FONTS_MAX_COUNT 16 +#define FONT_MASK 0x1F00u +#define MAX_FONTS 0x20 #define FONT_INDEX(flags) (((flags) & FONT_MASK) >> 8u) #define FONT(xx) (unsigned(FONT_ ## xx ## _INDEX) << 8u) diff --git a/src/libopenui_types.h b/src/libopenui_types.h index 6eea3ce..48e167b 100644 --- a/src/libopenui_types.h +++ b/src/libopenui_types.h @@ -104,4 +104,3 @@ typedef uint16_t event_t; typedef uint16_t Color565; typedef uint32_t LcdColor; typedef uint32_t WindowFlags; -typedef uint8_t charSuite[2]; diff --git a/src/menu.cpp b/src/menu.cpp index 44e8c6a..5089c63 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -235,7 +235,7 @@ void Menu::setTitle(std::string text) updatePosition(); } -void Menu::addLine(const std::string & text, const BitmapMask * mask, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addLine(const std::string & text, const Mask * mask, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addLine(text, mask, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (content->width() < MAX_MENUS_WIDTH) { diff --git a/src/menu.h b/src/menu.h index 690a28e..7af8796 100644 --- a/src/menu.h +++ b/src/menu.h @@ -42,7 +42,7 @@ class MenuBody: public Window friend class MenuBody; public: - MenuLine(std::string text, const BitmapMask * icon, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::string text, const Mask * icon, std::function onPress, std::function onSelect, std::function isChecked): text(std::move(text)), icon(icon), onPress(std::move(onPress)), @@ -65,7 +65,7 @@ class MenuBody: public Window protected: std::string text; - const BitmapMask * icon; + const Mask * icon; std::function drawLine; std::function onPress; std::function onSelect; @@ -106,7 +106,7 @@ class MenuBody: public Window bool onTouchEnd(coord_t x, coord_t y) override; #endif - void addLine(const std::string & text, const BitmapMask * icon, std::function onPress, std::function onSelect, std::function isChecked) + void addLine(const std::string & text, const Mask * icon, std::function onPress, std::function onSelect, std::function isChecked) { lines.emplace_back(text, icon, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (icon) @@ -211,7 +211,7 @@ class Menu: public ModalWindow void setTitle(std::string text); - void addLine(const std::string & text, const BitmapMask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addLine(const std::string & text, const Mask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void addLine(const std::string & text, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { addLine(text, nullptr, std::move(onPress), std::move(onSelect), std::move(isChecked)); diff --git a/src/menutoolbar.cpp b/src/menutoolbar.cpp index dd13169..a5610b9 100644 --- a/src/menutoolbar.cpp +++ b/src/menutoolbar.cpp @@ -26,9 +26,9 @@ void MenuToolbarButton::paint(BitmapBuffer * dc) { if (checked()) { dc->drawPlainFilledRectangle(MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_WIDTH - 2 * MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_WIDTH - 2 * MENUS_TOOLBAR_BUTTON_PADDING, FOCUS_BGCOLOR); - dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(STD))) / 2 + 1, &picto, 1, FOCUS_COLOR, CENTERED); + dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(M))) / 2 + 1, &picto, 1, FOCUS_COLOR, CENTERED); } else { - dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(STD))) / 2 + 1, &picto, 1, DEFAULT_COLOR, CENTERED); + dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(M))) / 2 + 1, &picto, 1, DEFAULT_COLOR, CENTERED); } } \ No newline at end of file diff --git a/src/modal_window.cpp b/src/modal_window.cpp index 35aa644..1b80c92 100644 --- a/src/modal_window.cpp +++ b/src/modal_window.cpp @@ -49,6 +49,6 @@ void ModalWindow::paint(BitmapBuffer * dc) void ModalWindowContent::paint(BitmapBuffer * dc) { dc->drawPlainFilledRectangle(0, 0, width(), POPUP_HEADER_HEIGHT, FOCUS_BGCOLOR); - dc->drawText(FIELD_PADDING_LEFT, (POPUP_HEADER_HEIGHT - getFontHeight(FONT(STD))) / 2, title.c_str(), FOCUS_COLOR); + dc->drawText(FIELD_PADDING_LEFT, (POPUP_HEADER_HEIGHT - getFontHeight(FONT(M))) / 2, title.c_str(), FOCUS_COLOR); dc->drawPlainFilledRectangle(0, POPUP_HEADER_HEIGHT, width(), height() - POPUP_HEADER_HEIGHT, DEFAULT_BGCOLOR); } diff --git a/src/roller.h b/src/roller.h index afd8cea..c8a44b2 100644 --- a/src/roller.h +++ b/src/roller.h @@ -93,7 +93,7 @@ class Roller: public Choice auto fgColor = DISABLE_COLOR; if (value == displayedValue) { - fgColor = FOCUS_COLOR | FONT(STD); + fgColor = FOCUS_COLOR | FONT(M); } unsigned valueIndex = displayedValue - vmin; diff --git a/src/static.h b/src/static.h index 97979df..f594421 100644 --- a/src/static.h +++ b/src/static.h @@ -21,6 +21,7 @@ #include "window.h" #include "button.h" // TODO just for BUTTON_BACKGROUND +#include "font.h" constexpr coord_t STATIC_TEXT_INTERLINE_HEIGHT = 2; @@ -111,7 +112,7 @@ class Subtitle: public StaticText { public: Subtitle(Window * parent, const rect_t & rect, const char * text): - StaticText(parent, rect, text, 0, FONT(BOLD)) + StaticText(parent, rect, text, 0, FONT(M_BOLD)) { } @@ -132,21 +133,14 @@ class StaticBitmap: public Window { } - StaticBitmap(Window * parent, const rect_t & rect, const char * filename, bool scale = false): - Window(parent, rect), - bitmap(BitmapBuffer::load(filename)), - scale(scale) - { - } - - StaticBitmap(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const Bitmap * bitmap, bool scale = false): Window(parent, rect), bitmap(bitmap), scale(scale) { } - StaticBitmap(Window * parent, const rect_t & rect, const BitmapMask * mask, LcdFlags color, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const Mask * mask, LcdFlags color, bool scale = false): Window(parent, rect), mask(mask), color(color), @@ -154,30 +148,6 @@ class StaticBitmap: public Window { } - void setBitmap(const char * filename) - { - setBitmap(BitmapBuffer::load(filename)); - } - - void setMaskColor(LcdFlags value) - { - color = value; - } - - void setBitmap(const BitmapBuffer * newBitmap) - { - delete bitmap; - bitmap = newBitmap; - invalidate(); - } - - void setMask(const BitmapMask * newMask) - { - delete mask; - mask = newMask; - invalidate(); - } - #if defined(DEBUG_WINDOWS) [[nodiscard]] std::string getName() const override { @@ -199,8 +169,8 @@ class StaticBitmap: public Window } protected: - const BitmapMask * mask = nullptr; - const BitmapBuffer * bitmap = nullptr; + const Mask * mask = nullptr; + const Bitmap * bitmap = nullptr; LcdFlags color = 0; bool scale = false; }; diff --git a/src/textedit.cpp b/src/textedit.cpp index d2d83bc..1488e37 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -48,7 +48,7 @@ void TextEdit::setEditMode(bool newEditMode) #if defined(SOFTWARE_KEYBOARD) if (editMode) { - TextKeyboard::show(this); + TextKeyboardBase::show(this); } #endif @@ -105,67 +105,60 @@ void TextEdit::trim() } } +#if defined(SOFTWARE_KEYBOARD) || defined(SIMULATION) +void TextEdit::onVirtualKeyEvent(event_t event) +{ + auto c = event & MSK_VIRTUAL_KEY; + if (c == SPECIAL_KEY_BACKSPACE) { + if (cursorPos > 0) { + auto pos = getUnicodeStringAtPosition(value, cursorPos - 1); + char * tmp = pos; + auto len = getUnicodeCharLength(getNextUnicodeChar(tmp)); + memmove(pos, pos + len, value + length - pos - len); + memset(value + length - len, 0, len); + --cursorPos; + invalidate(); + changed = true; + } + } + else { + auto len = getUnicodeCharLength(c); + if (strlen(value) + len <= length) { + insertUnicodeChar(value, cursorPos, c, length); + cursorPos++; + invalidate(); + changed = true; + } + } +} +#endif + void TextEdit::onEvent(event_t event) { TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), event); #if defined(SOFTWARE_KEYBOARD) || defined(SIMULATION) if (IS_VIRTUAL_KEY_EVENT(event)) { - uint8_t c = event & 0xFF; - if (c == SPECIAL_KEY_BACKSPACE) { - if (cursorPos > 0) { - memmove(value + cursorPos - 1, value + cursorPos, length - cursorPos); - value[length - 1] = '\0'; - --cursorPos; - invalidate(); - changed = true; - } - } -#if defined(KEYBOARD_DELETE) - else if (c == SPECIAL_KEY_DELETE) { - if (cursorPos < length - 1) { - memmove(value + cursorPos, value + cursorPos + 1, length - cursorPos - 1); - value[length - 1] = '\0'; - invalidate(); - changed = true; - } - } -#endif -#if defined(KEYBOARD_HOME) - else if (c == SPECIAL_KEY_HOME) { - setCursorPos(0); - } -#endif -#if defined(KEYBOARD_END) - else if (c == SPECIAL_KEY_END) { - setCursorPos(strlen(value)); - } -#endif - else if (cursorPos < length) { - memmove(value + cursorPos + 1, value + cursorPos, length - cursorPos - 1); - value[cursorPos++] = c; - invalidate(); - changed = true; - } + onVirtualKeyEvent(event); + return; } #endif #if defined(HARDWARE_KEYS) if (editMode) { - char previousChar = (cursorPos > 0 ? value[cursorPos - 1] : 0); - int c = (cursorPos < length ? value[cursorPos] : 0); - int v = c; + auto currentChar = getUnicodeCharAtPosition(value, cursorPos); + auto nextChar = currentChar; switch (event) { case EVT_ROTARY_RIGHT: for (int i = 0; i < ROTARY_ENCODER_SPEED(); i++) { - v = getNextChar(v, previousChar); + nextChar = getNextAvailableChar(nextChar); } break; case EVT_ROTARY_LEFT: for (int i = 0; i < ROTARY_ENCODER_SPEED(); i++) { - v = getPreviousChar(v); + nextChar = getPreviousAvailableChar(nextChar); } break; @@ -176,18 +169,11 @@ void TextEdit::onEvent(event_t event) break; case EVT_KEY_BREAK(KEY_RIGHT): - { -#if defined(SOFTWARE_KEYBOARD) - if (cursorPos < length && value[cursorPos] != '\0') { - setCursorPos(cursorPos + 1); - } -#else - if (cursorPos < length - 1 && value[cursorPos + 1] != '\0') { - setCursorPos(cursorPos + 1); + if (cursorPos < getUnicodeStringLength(value)) { + cursorPos++; + invalidate(); } -#endif break; - } case EVT_KEY_BREAK(KEY_ENTER): if (cursorPos < length - 1) { @@ -195,11 +181,12 @@ void TextEdit::onEvent(event_t event) value[cursorPos] = ' '; changed = true; } - setCursorPos(cursorPos + 1); + cursorPos++; if (value[cursorPos] == '\0') { value[cursorPos] = ' '; changed = true; } + invalidate(); } else { changeEnd(); @@ -211,7 +198,7 @@ void TextEdit::onEvent(event_t event) changeEnd(); FormField::onEvent(event); #if defined(HARDWARE_TOUCH) - TextKeyboard::hide(); + TextKeyboardBase::hide(); #endif break; @@ -239,12 +226,12 @@ void TextEdit::onEvent(event_t event) } case EVT_KEY_BREAK(KEY_UP): - v = toggleCase(v); + nextChar = toggleCase(nextChar); break; case EVT_KEY_LONG(KEY_LEFT): case EVT_KEY_LONG(KEY_RIGHT): - v = toggleCase(v); + nextChar = toggleCase(nextChar); if (event == EVT_KEY_LONG(KEY_LEFT)) { killEvents(KEY_LEFT); } @@ -263,9 +250,9 @@ void TextEdit::onEvent(event_t event) break; } - if (cursorPos < length && c != v) { + if (cursorPos < length && currentChar != nextChar) { // TRACE("value[%d] = %d", cursorPos, v); - value[cursorPos] = v; + // write(value[cursorPos] = nextChar; invalidate(); changed = true; } @@ -289,17 +276,17 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) } #if defined(SOFTWARE_KEYBOARD) - TextKeyboard::show(this); + TextKeyboardBase::show(this); #endif - auto font = getFont(FONT(STD)); + auto font = getFont(FONT(M)); coord_t rest = x; for (cursorPos = 0; cursorPos < length; cursorPos++) { char c = value[cursorPos]; if (c == '\0') break; - uint8_t w = font->getChar(c).width + 1; + uint8_t w = font->getGlyph(c).width + 1; if (rest < w) break; rest -= w; @@ -313,7 +300,7 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) void TextEdit::onFocusLost() { #if defined(SOFTWARE_KEYBOARD) - TextKeyboard::hide(); + TextKeyboardBase::hide(); #endif changeEnd(); diff --git a/src/textedit.h b/src/textedit.h index b60d3f7..7c905a7 100644 --- a/src/textedit.h +++ b/src/textedit.h @@ -19,6 +19,7 @@ #pragma once +#include #include "form.h" namespace ui { @@ -98,33 +99,29 @@ class TextEdit: public FormField } } - static uint8_t getNextChar(uint8_t c, uint8_t previous) + static uint32_t getNextAvailableChar(uint32_t index) { - if (c == ' ' || c == 0) - return (previous >= 'a' && previous <= 'z') ? 'a' : 'A'; - - for (auto & suite: charsSuite) { - if (c == suite[0]) - return suite[1]; + auto end = mFont.end(); + while (++index < uint32_t(end)) { + if (mFont.getGlyph(index).width > 0) { + return index; + } } - - return c + 1; + return index; } - static uint8_t getPreviousChar(uint8_t c) + static uint32_t getPreviousAvailableChar(uint32_t index) { - if (c == 'A') - return ' '; - - for (auto & suite: charsSuite) { - if (c == suite[1]) - return suite[0]; + auto begin = mFont.begin(); + while (--index >= uint32_t(begin)) { + if (mFont.getGlyph(index).width > 0) { + return index; + } } - - return c - 1; + return index; } - static uint8_t toggleCase(uint8_t c) + static wchar_t toggleCase(wchar_t c) { if (c >= 'A' && c <= 'Z') return c + 32; // tolower @@ -133,6 +130,8 @@ class TextEdit: public FormField else return c; } + + void onVirtualKeyEvent(event_t event); }; } diff --git a/src/theme.h b/src/theme.h index 5fe6b6c..d03ab72 100644 --- a/src/theme.h +++ b/src/theme.h @@ -54,7 +54,7 @@ class Theme virtual void drawChoice(BitmapBuffer * dc, ChoiceBase * choice, const char * str) const = 0; virtual void drawSlider(BitmapBuffer * dc, int vmin, int vmax, int value, const rect_t & rect, bool edit, bool focus) const = 0; virtual const BitmapBuffer * getIcon(uint8_t index, IconState state) const = 0; - virtual const BitmapMask * getIconMask(uint8_t index) const = 0; + virtual const Mask * getIconMask(uint8_t index) const = 0; virtual TextButton * createTextButton(FormGroup * parent, const rect_t & rect, std::string text, std::function pressHandler = nullptr, WindowFlags windowFlags = OPAQUE | BUTTON_BACKGROUND) const { diff --git a/src/unicode.h b/src/unicode.h new file mode 100644 index 0000000..d3f8d84 --- /dev/null +++ b/src/unicode.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#pragma once + +#include + +inline bool isUnicodeContinuationByte(uint8_t c) +{ + return (c & 0b11000000) == 0b10000000; +} + +template +inline uint32_t getNextUnicodeChar(T & s) +{ + uint8_t c1 = uint8_t(*s++); + if (c1 < 0x80) { + return c1; + } + else if (c1 < 0xE0) { + uint8_t c2 = uint8_t(*s++); + return ((c1 & 0b11111) << 6) + (c2 & 0b111111); + } + else if (c1 < 0xF0) { + uint8_t c2 = uint8_t(*s++); + uint8_t c3 = uint8_t(*s++); + return ((c1 & 0b1111) << 12) + ((c2 & 0b111111) << 6) + (c3 & 0b111111); + } + else { + uint8_t c2 = uint8_t(*s++); + uint8_t c3 = uint8_t(*s++); + uint8_t c4 = uint8_t(*s++); + return ((c1 & 0b111) << 18) + ((c2 & 0b111111) << 12) + ((c3 & 0b111111) << 6) + (c4 & 0b111111); + } +} + +template +inline uint32_t getPreviousUnicodeChar(T * & s) +{ + while (isUnicodeContinuationByte(*(--s))); + auto tmp = s; + return getNextUnicodeChar(tmp); +} + +inline uint32_t getUnicodeStringLength(const char * s, uint8_t size = 0) +{ + const char * pos = s; + uint32_t result = 0; + while (true) { + uint8_t c = *pos; + if (c == 0) + return result; + else if (c < 0x80) + pos += 1; + else if (c < 0xE0) + pos += 2; + else if (c < 0xF0) + pos += 3; + else + pos += 4; + if (size && pos >= s + size) + return result; + result++; + } + return result; +} + +template +inline T * getUnicodeStringAtPosition(T * s, uint8_t index) +{ + while (index-- > 0) { + uint8_t c = *s; + if (c == 0) + return nullptr; + else if (c < 0x80) + s += 1; + else if (c < 0xE0) + s += 2; + else if (c < 0xF0) + s += 3; + else + s += 4; + } + return s; +} + +template +inline uint32_t getUnicodeCharAtPosition(T * s, uint8_t index) +{ + auto pos = getUnicodeStringAtPosition(s, index); + return getNextUnicodeChar(pos); +} + + +inline uint8_t getUnicodeCharLength(uint32_t c) +{ + if (c < 0x80) + return 1; + else if (c <= 0b11111111111) + return 2; + else if (c <= 0b1111111111111111) + return 3; + else + return 4; +} + +inline void writeUnicodeChar(char * s, uint32_t c) +{ + if (c < 0x80) { + *s = c; + } + else if (c <= 0b11111111111) { + *s++ = 0xC0 | ((c >> 6) & 0b11111); + *s = 0b10000000 | (c & 0b111111); + } + else if (c <= 0b1111111111111111) { + *s++ = 0xE0 | ((c >> 12) & 0b1111); + *s++ = 0b10000000 | ((c >> 6) & 0b111111); + *s = 0b10000000 | (c & 0b111111); + } + else { + *s++ = 0xF0 | ((c >> 18) & 0b111); + *s++ = 0b10000000 | ((c >> 12) & 0b111111); + *s++ = 0b10000000 | ((c >> 6) & 0b111111); + *s = 0b10000000 | (c & 0b111111); + } +} + +inline void insertUnicodeChar(char * s, uint8_t position, uint32_t c, uint8_t maxLength) +{ + auto len = getUnicodeCharLength(c); + auto pos = getUnicodeStringAtPosition(s, position); + memmove(pos + len, pos, s + maxLength - pos - len); +} diff --git a/src/window.cpp b/src/window.cpp index 3965a27..345a61a 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -544,7 +544,7 @@ coord_t Window::adjustHeight() void Window::moveWindowsTop(coord_t y, coord_t delta) // NOLINT(misc-no-recursion) { if (delta != 0) { - if (getWindowFlags() & FORWARD_SCROLL) { + if (parent && (getWindowFlags() & FORWARD_SCROLL)) { parent->moveWindowsTop(bottom(), delta); } diff --git a/tools/encode-bitmap.py b/tools/encode_bitmap.py similarity index 57% rename from tools/encode-bitmap.py rename to tools/encode_bitmap.py index 7c8751c..28d4cce 100755 --- a/tools/encode-bitmap.py +++ b/tools/encode_bitmap.py @@ -2,11 +2,12 @@ import argparse from PIL import Image +import sys class RawMixin: def encode_byte(self, byte): - self.write(byte) + self.append_byte(byte) def encode_end(self): pass @@ -28,7 +29,7 @@ def eq_prev_byte(self, byte): def encode_byte(self, byte): if self.state == self.RLE_BYTE: - self.write(byte) + self.append_byte(byte) if self.eq_prev_byte(byte): self.state = self.RLE_SEQ self.count = 0 @@ -38,39 +39,64 @@ def encode_byte(self, byte): if self.eq_prev_byte(byte): self.count += 1 if self.count == 255: - self.write(self.count) + self.append_byte(self.count) self.prev_byte = None self.state = self.RLE_BYTE else: - self.write(self.count) - self.write(byte) + self.append_byte(self.count) + self.append_byte(byte) self.prev_byte = byte self.state = self.RLE_BYTE def encode_end(self): if self.state == self.RLE_SEQ: - self.write(self.count) + self.append_byte(self.count) class ImageEncoder: - def __init__(self, filename, size_format, orientation=0): - self.f = open(filename, "w") + def __init__(self, size_format, orientation=0): self.size_format = size_format self.orientation = orientation + self.bytes = [] - def write(self, value): - self.f.write("0x%02x," % value) + def append_byte(self, value): + self.bytes.append(value) + + def extend(self, values): + self.bytes.extend(values) - def write_size(self, width, height): + def append_short(self, value): + self.append_byte(value % 256) + self.append_byte(value // 256) + + def append_word(self, value): + self.append_short(value % 65536) + self.append_short(value // 65536) + + def append_format(self, value): + self.append_word(value) + + def append_size(self, width, height): if self.size_format == 2: - self.f.write("%d,%d,%d,%d,\n" % (width % 256, width // 256, height % 256, height // 256)) + self.append_short(width) + self.append_short(height) else: - self.f.write("%d,%d,\n" % (width, height)) + self.extend(width, height) + + @staticmethod + def guess_format(image): + if image.mode == "P": + print("Indexed bitmap, will use RGBA") + return "4/4/4/4" + elif image.mode == "RGBA": + return "4/4/4/4" + else: + return "5/6/5" def encode_1bit(self, image, rows): image = image.convert(mode='1') width, height = image.size - self.write_size(width, height // rows) + self.append_size(width, height // rows) for y in range(0, height, 8): for x in range(width): value = 0 @@ -83,13 +109,13 @@ def encode_1bit(self, image, rows): if image.getpixel((x, y + z)) == 0: value += 1 << z self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_4bits(self, image): image = image.convert(mode='L') width, height = image.size - self.write_size(width, height) + self.append_size(width, height) for y in range(0, height, 2): for x in range(width): value = 0xFF @@ -104,62 +130,74 @@ def encode_4bits(self, image): if gray2 & (1 << (4 + i)): value -= 1 << (4 + i) self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size - self.write_size(width, height) - if self.orientation == 270: + self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size + for y in range(height): for x in range(width): - for y in range(height): - value = 0xFF - self.get_pixel(image, x, y) - self.encode_byte(value) - self.f.write("\n") - else: - for y in range(height): - for x in range(width): - value = 0xFF - self.get_pixel(image, x, y) - self.encode_byte(value) - self.f.write("\n") + value = 0xFF - image.getpixel((x, y)) + self.encode_byte(value) self.encode_end() + return self.bytes def encode_5_6_5(self, image): width, height = image.size - self.write_size(width, height) + self.append_format(0) + self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size for y in range(height): for x in range(width): - pixel = self.get_pixel(image, x, y) + pixel = image.getpixel((x, y)) val = ((pixel[0] >> 3) << 11) + ((pixel[1] >> 2) << 5) + ((pixel[2] >> 3) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) self.encode_end() + return self.bytes def encode_4_4_4_4(self, image): width, height = image.size - self.write_size(width, height) + self.append_format(1) + self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size for y in range(height): for x in range(width): - pixel = self.get_pixel(image, x, y) + pixel = image.getpixel((x, y)) val = ((pixel[3] // 16) << 12) + ((pixel[0] // 16) << 8) + ((pixel[1] // 16) << 4) + ((pixel[2] // 16) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) self.encode_end() + return self.bytes def get_pixel(self, image, x, y): - if self.orientation == 180: - return image.getpixel((image.width - x - 1, image.height - y - 1)) - else: - return image.getpixel((x, y)) + return image.getpixel((x, y)) @staticmethod - def create(filename, size_format=1, orientation=0, encode_mixin=RawMixin): + def create(size_format=1, orientation=0, encode_mixin=RawMixin): class ResultClass(ImageEncoder, encode_mixin): def __init__(self, *args, **kwargs): ImageEncoder.__init__(self, *args, **kwargs) encode_mixin.__init__(self) - return ResultClass(filename, size_format, orientation) + return ResultClass(size_format, orientation) def main(): @@ -175,19 +213,25 @@ def main(): args = parser.parse_args() image = Image.open(args.input) - output = args.output - encoder = ImageEncoder.create(output, args.size_format, args.orientation, RleMixin if args.rle else RawMixin) - - if args.format == "1bit": - encoder.encode_1bit(image, args.rows) - elif args.format == "4bits": - encoder.encode_4bits(image) - elif args.format == "8bits": - encoder.encode_8bits(image) - elif args.format == "4/4/4/4": - encoder.encode_4_4_4_4(image) - elif args.format == "5/6/5": - encoder.encode_5_6_5(image) + encoder = ImageEncoder.create(args.size_format, args.orientation, RleMixin if args.rle else RawMixin) + + format = args.format + if format == "auto": + format = encoder.guess_format(image) + if format == "1bit": + bytes = encoder.encode_1bit(image, args.rows) + elif format == "4bits": + bytes = encoder.encode_4bits(image) + elif format == "8bits": + bytes = encoder.encode_8bits(image) + elif format == "4/4/4/4": + bytes = encoder.encode_4_4_4_4(image) + elif format == "5/6/5": + bytes = encoder.encode_5_6_5(image) + + with open(args.output, "w") as f: + for byte in bytes: + f.write("%d," % byte) if __name__ == "__main__":