mod: Phase 3 — Re-port unmerged upstream PRs
Re-applied upstream PRs not yet merged to upstream/master: - #1055: Byte-level framebuffer writes (fillPhysicalHSpan*, optimized fillRect/drawLine/fillRectDither/fillPolygon) - #1027: Word-width cache (FNV-1a, 128-entry) and hyphenation early exit in ParsedText for 7-9% layout speedup - #1068: Already present in upstream — URL hyphenation fix - #1019: Already present in upstream — file extensions in browser - #1090/#1185/#1217: KOReader sync improvements — binary credential store, document hash caching, ChapterXPathIndexer integration - #1209: OPDS multi-server — OpdsBookBrowserActivity accepts OpdsServer, directory picker for downloads, download-complete prompt with open/back options - #857: Dictionary activities already ported in Phase 1/2 - #1003: Placeholder cover already integrated in Phase 2 Also fixed: STR_OFF i18n string, include paths, replaced Epub::isValidThumbnailBmp with Storage.exists, replaced StringUtils::checkFileExtension with FsHelpers equivalents. Made-with: Cursor
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include <Logging.h>
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
|
||||
if (fontData->groups != nullptr) {
|
||||
if (!fontDecompressor) {
|
||||
@@ -271,15 +273,34 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con
|
||||
if (y2 < y1) {
|
||||
std::swap(y1, y2);
|
||||
}
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
drawPixel(x1, y, state);
|
||||
// In Portrait/PortraitInverted a logical vertical line maps to a physical horizontal span.
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - x1, y1, y2, state);
|
||||
return;
|
||||
case PortraitInverted:
|
||||
fillPhysicalHSpan(x1, HalDisplay::DISPLAY_WIDTH - 1 - y2, HalDisplay::DISPLAY_WIDTH - 1 - y1, state);
|
||||
return;
|
||||
default:
|
||||
for (int y = y1; y <= y2; y++) drawPixel(x1, y, state);
|
||||
return;
|
||||
}
|
||||
} else if (y1 == y2) {
|
||||
if (x2 < x1) {
|
||||
std::swap(x1, x2);
|
||||
}
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
drawPixel(x, y1, state);
|
||||
// In Landscape a logical horizontal line maps to a physical horizontal span.
|
||||
switch (orientation) {
|
||||
case LandscapeCounterClockwise:
|
||||
fillPhysicalHSpan(y1, x1, x2, state);
|
||||
return;
|
||||
case LandscapeClockwise:
|
||||
fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - y1, HalDisplay::DISPLAY_WIDTH - 1 - x2,
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - x1, state);
|
||||
return;
|
||||
default:
|
||||
for (int x = x1; x <= x2; x++) drawPixel(x, y1, state);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Bresenham's line algorithm — integer arithmetic only
|
||||
@@ -408,9 +429,80 @@ void GfxRenderer::drawRoundedRect(const int x, const int y, const int width, con
|
||||
}
|
||||
}
|
||||
|
||||
// Write a patterned horizontal span directly into the physical framebuffer with byte-level operations.
|
||||
// Handles partial left/right bytes and fills the aligned middle with memset.
|
||||
// Bit layout: MSB-first (bit 7 = phyX=0, bit 0 = phyX=7); 0 bits = dark pixel, 1 bits = white pixel.
|
||||
void GfxRenderer::fillPhysicalHSpanByte(const int phyY, const int phyX_start, const int phyX_end,
|
||||
const uint8_t patternByte) const {
|
||||
const int cX0 = std::max(phyX_start, 0);
|
||||
const int cX1 = std::min(phyX_end, static_cast<int>(HalDisplay::DISPLAY_WIDTH) - 1);
|
||||
if (cX0 > cX1 || phyY < 0 || phyY >= static_cast<int>(HalDisplay::DISPLAY_HEIGHT)) return;
|
||||
|
||||
uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES;
|
||||
const int startByte = cX0 >> 3;
|
||||
const int endByte = cX1 >> 3;
|
||||
const int leftBits = cX0 & 7;
|
||||
const int rightBits = cX1 & 7;
|
||||
|
||||
if (startByte == endByte) {
|
||||
const uint8_t fillMask = (0xFF >> leftBits) & ~(0xFF >> (rightBits + 1));
|
||||
row[startByte] = (row[startByte] & ~fillMask) | (patternByte & fillMask);
|
||||
return;
|
||||
}
|
||||
|
||||
// Left partial byte
|
||||
if (leftBits != 0) {
|
||||
const uint8_t fillMask = 0xFF >> leftBits;
|
||||
row[startByte] = (row[startByte] & ~fillMask) | (patternByte & fillMask);
|
||||
}
|
||||
|
||||
// Full bytes in the middle
|
||||
const int fullStart = (leftBits == 0) ? startByte : startByte + 1;
|
||||
const int fullEnd = (rightBits == 7) ? endByte : endByte - 1;
|
||||
if (fullStart <= fullEnd) {
|
||||
memset(row + fullStart, patternByte, static_cast<size_t>(fullEnd - fullStart + 1));
|
||||
}
|
||||
|
||||
// Right partial byte
|
||||
if (rightBits != 7) {
|
||||
const uint8_t fillMask = ~(0xFF >> (rightBits + 1));
|
||||
row[endByte] = (row[endByte] & ~fillMask) | (patternByte & fillMask);
|
||||
}
|
||||
}
|
||||
|
||||
// Thin wrapper: state=true → 0x00 (all dark), false → 0xFF (all white).
|
||||
void GfxRenderer::fillPhysicalHSpan(const int phyY, const int phyX_start, const int phyX_end, const bool state) const {
|
||||
fillPhysicalHSpanByte(phyY, phyX_start, phyX_end, state ? 0x00 : 0xFF);
|
||||
}
|
||||
|
||||
void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
for (int fillY = y; fillY < y + height; fillY++) {
|
||||
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
// For each orientation, one logical dimension maps to a constant physical row, allowing the
|
||||
// perpendicular dimension to be written as a byte-level span — eliminating per-pixel overhead.
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
for (int lx = x; lx < x + width; lx++) {
|
||||
fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - lx, y, y + height - 1, state);
|
||||
}
|
||||
return;
|
||||
case PortraitInverted:
|
||||
for (int lx = x; lx < x + width; lx++) {
|
||||
fillPhysicalHSpan(lx, HalDisplay::DISPLAY_WIDTH - 1 - (y + height - 1), HalDisplay::DISPLAY_WIDTH - 1 - y,
|
||||
state);
|
||||
}
|
||||
return;
|
||||
case LandscapeCounterClockwise:
|
||||
for (int ly = y; ly < y + height; ly++) {
|
||||
fillPhysicalHSpan(ly, x, x + width - 1, state);
|
||||
}
|
||||
return;
|
||||
case LandscapeClockwise:
|
||||
for (int ly = y; ly < y + height; ly++) {
|
||||
fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - ly, HalDisplay::DISPLAY_WIDTH - 1 - (x + width - 1),
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - x, state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,17 +539,77 @@ void GfxRenderer::fillRectDither(const int x, const int y, const int width, cons
|
||||
fillRect(x, y, width, height, true);
|
||||
} else if (color == Color::White) {
|
||||
fillRect(x, y, width, height, false);
|
||||
} else if (color == Color::LightGray) {
|
||||
for (int fillY = y; fillY < y + height; fillY++) {
|
||||
for (int fillX = x; fillX < x + width; fillX++) {
|
||||
drawPixelDither<Color::LightGray>(fillX, fillY);
|
||||
}
|
||||
}
|
||||
} else if (color == Color::DarkGray) {
|
||||
for (int fillY = y; fillY < y + height; fillY++) {
|
||||
for (int fillX = x; fillX < x + width; fillX++) {
|
||||
drawPixelDither<Color::DarkGray>(fillX, fillY);
|
||||
}
|
||||
// Pattern: dark where (phyX + phyY) % 2 == 0 (alternating checkerboard).
|
||||
// Byte patterns (phyY even / phyY odd):
|
||||
// Portrait / PortraitInverted: 0xAA / 0x55
|
||||
// LandscapeCW / LandscapeCCW: 0x55 / 0xAA
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
for (int lx = x; lx < x + width; lx++) {
|
||||
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - lx;
|
||||
const uint8_t pb = (phyY % 2 == 0) ? 0xAA : 0x55;
|
||||
fillPhysicalHSpanByte(phyY, y, y + height - 1, pb);
|
||||
}
|
||||
return;
|
||||
case PortraitInverted:
|
||||
for (int lx = x; lx < x + width; lx++) {
|
||||
const int phyY = lx;
|
||||
const uint8_t pb = (phyY % 2 == 0) ? 0xAA : 0x55;
|
||||
fillPhysicalHSpanByte(phyY, HalDisplay::DISPLAY_WIDTH - 1 - (y + height - 1),
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - y, pb);
|
||||
}
|
||||
return;
|
||||
case LandscapeCounterClockwise:
|
||||
for (int ly = y; ly < y + height; ly++) {
|
||||
const int phyY = ly;
|
||||
const uint8_t pb = (phyY % 2 == 0) ? 0x55 : 0xAA;
|
||||
fillPhysicalHSpanByte(phyY, x, x + width - 1, pb);
|
||||
}
|
||||
return;
|
||||
case LandscapeClockwise:
|
||||
for (int ly = y; ly < y + height; ly++) {
|
||||
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - ly;
|
||||
const uint8_t pb = (phyY % 2 == 0) ? 0x55 : 0xAA;
|
||||
fillPhysicalHSpanByte(phyY, HalDisplay::DISPLAY_WIDTH - 1 - (x + width - 1),
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - x, pb);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (color == Color::LightGray) {
|
||||
// Pattern: dark where phyX % 2 == 0 && phyY % 2 == 0 (1-in-4 pixels dark).
|
||||
// Rows that would be all-white are skipped entirely.
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
for (int lx = x; lx < x + width; lx++) {
|
||||
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - lx;
|
||||
if (phyY % 2 == 0) continue;
|
||||
fillPhysicalHSpanByte(phyY, y, y + height - 1, 0x55);
|
||||
}
|
||||
return;
|
||||
case PortraitInverted:
|
||||
for (int lx = x; lx < x + width; lx++) {
|
||||
const int phyY = lx;
|
||||
if (phyY % 2 != 0) continue;
|
||||
fillPhysicalHSpanByte(phyY, HalDisplay::DISPLAY_WIDTH - 1 - (y + height - 1),
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - y, 0xAA);
|
||||
}
|
||||
return;
|
||||
case LandscapeCounterClockwise:
|
||||
for (int ly = y; ly < y + height; ly++) {
|
||||
const int phyY = ly;
|
||||
if (phyY % 2 != 0) continue;
|
||||
fillPhysicalHSpanByte(phyY, x, x + width - 1, 0x55);
|
||||
}
|
||||
return;
|
||||
case LandscapeClockwise:
|
||||
for (int ly = y; ly < y + height; ly++) {
|
||||
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - ly;
|
||||
if (phyY % 2 == 0) continue;
|
||||
fillPhysicalHSpanByte(phyY, HalDisplay::DISPLAY_WIDTH - 1 - (x + width - 1),
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - x, 0xAA);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -799,9 +951,16 @@ void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoi
|
||||
if (startX < 0) startX = 0;
|
||||
if (endX >= getScreenWidth()) endX = getScreenWidth() - 1;
|
||||
|
||||
// Draw horizontal line
|
||||
for (int x = startX; x <= endX; x++) {
|
||||
drawPixel(x, scanY, state);
|
||||
// In Landscape orientations, horizontal scanlines map to physical horizontal spans.
|
||||
if (orientation == LandscapeCounterClockwise) {
|
||||
fillPhysicalHSpan(scanY, startX, endX, state);
|
||||
} else if (orientation == LandscapeClockwise) {
|
||||
fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - scanY, HalDisplay::DISPLAY_WIDTH - 1 - endX,
|
||||
HalDisplay::DISPLAY_WIDTH - 1 - startX, state);
|
||||
} else {
|
||||
for (int px = startX; px <= endX; px++) {
|
||||
drawPixel(px, scanY, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,14 @@ class GfxRenderer {
|
||||
void drawPixelDither(int x, int y) const;
|
||||
template <Color color>
|
||||
void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir) const;
|
||||
// Write a patterned horizontal span directly to the physical framebuffer using byte-level operations.
|
||||
// phyY: physical row; phyX_start/phyX_end: inclusive physical column range.
|
||||
// patternByte is repeated across the span; partial edge bytes are blended with existing content.
|
||||
// Bit layout: MSB-first (bit 7 = phyX=0); 0 bits = dark pixel, 1 bits = white pixel.
|
||||
void fillPhysicalHSpanByte(int phyY, int phyX_start, int phyX_end, uint8_t patternByte) const;
|
||||
// Write a solid horizontal span directly to the physical framebuffer using byte-level operations.
|
||||
// Thin wrapper around fillPhysicalHSpanByte: state=true → 0x00 (dark), false → 0xFF (white).
|
||||
void fillPhysicalHSpan(int phyY, int phyX_start, int phyX_end, bool state) const;
|
||||
|
||||
public:
|
||||
explicit GfxRenderer(HalDisplay& halDisplay)
|
||||
|
||||
Reference in New Issue
Block a user