mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +03:00
style: apply clang-format-21 formatting
This commit is contained in:
parent
99702a342c
commit
41eabba0d4
@ -61,7 +61,7 @@ bool Page::serialize(FsFile& file) const {
|
|||||||
for (const auto& el : elements) {
|
for (const auto& el : elements) {
|
||||||
// Use getTag() method to determine type
|
// Use getTag() method to determine type
|
||||||
serialization::writePod(file, static_cast<uint8_t>(el->getTag()));
|
serialization::writePod(file, static_cast<uint8_t>(el->getTag()));
|
||||||
|
|
||||||
if (!el->serialize(file)) {
|
if (!el->serialize(file)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "blocks/TextBlock.h"
|
|
||||||
#include "blocks/ImageBlock.h"
|
#include "blocks/ImageBlock.h"
|
||||||
|
#include "blocks/TextBlock.h"
|
||||||
|
|
||||||
enum PageElementTag : uint8_t {
|
enum PageElementTag : uint8_t {
|
||||||
TAG_PageLine = 1,
|
TAG_PageLine = 1,
|
||||||
@ -41,7 +41,7 @@ class PageLine final : public PageElement {
|
|||||||
class PageImage final : public PageElement {
|
class PageImage final : public PageElement {
|
||||||
std::shared_ptr<ImageBlock> imageBlock;
|
std::shared_ptr<ImageBlock> imageBlock;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
||||||
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
#include "ImageBlock.h"
|
#include "ImageBlock.h"
|
||||||
#include "../converters/ImageDecoderFactory.h"
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <FsHelpers.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
|
#include "../converters/ImageDecoderFactory.h"
|
||||||
|
|
||||||
// Cache file format:
|
// Cache file format:
|
||||||
// - uint16_t width
|
// - uint16_t width
|
||||||
@ -19,8 +21,7 @@ bool ImageBlock::imageExists() const {
|
|||||||
return SdMan.openFileForRead("IMG", imagePath, file);
|
return SdMan.openFileForRead("IMG", imagePath, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageBlock::layout(GfxRenderer& renderer) {
|
void ImageBlock::layout(GfxRenderer& renderer) {}
|
||||||
}
|
|
||||||
|
|
||||||
static std::string getCachePath(const std::string& imagePath) {
|
static std::string getCachePath(const std::string& imagePath) {
|
||||||
// Replace extension with .pxc (pixel cache)
|
// Replace extension with .pxc (pixel cache)
|
||||||
@ -31,7 +32,8 @@ static std::string getCachePath(const std::string& imagePath) {
|
|||||||
return imagePath + ".pxc";
|
return imagePath + ".pxc";
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth, int expectedHeight) {
|
static bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
|
||||||
|
int expectedHeight) {
|
||||||
FsFile cacheFile;
|
FsFile cacheFile;
|
||||||
if (!SdMan.openFileForRead("IMG", cachePath, cacheFile)) {
|
if (!SdMan.openFileForRead("IMG", cachePath, cacheFile)) {
|
||||||
return false;
|
return false;
|
||||||
@ -47,8 +49,8 @@ static bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath,
|
|||||||
int widthDiff = abs(cachedWidth - expectedWidth);
|
int widthDiff = abs(cachedWidth - expectedWidth);
|
||||||
int heightDiff = abs(cachedHeight - expectedHeight);
|
int heightDiff = abs(cachedHeight - expectedHeight);
|
||||||
if (widthDiff > 1 || heightDiff > 1) {
|
if (widthDiff > 1 || heightDiff > 1) {
|
||||||
Serial.printf("[%lu] [IMG] Cache dimension mismatch: %dx%d vs %dx%d\n", millis(),
|
Serial.printf("[%lu] [IMG] Cache dimension mismatch: %dx%d vs %dx%d\n", millis(), cachedWidth, cachedHeight,
|
||||||
cachedWidth, cachedHeight, expectedWidth, expectedHeight);
|
expectedWidth, expectedHeight);
|
||||||
cacheFile.close();
|
cacheFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -99,8 +101,8 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|||||||
|
|
||||||
// Bounds check render position using logical screen dimensions
|
// Bounds check render position using logical screen dimensions
|
||||||
if (x < 0 || y < 0 || x + width > screenWidth || y + height > screenHeight) {
|
if (x < 0 || y < 0 || x + width > screenWidth || y + height > screenHeight) {
|
||||||
Serial.printf("[%lu] [IMG] Invalid render position: (%d,%d) size (%dx%d) screen (%dx%d)\n",
|
Serial.printf("[%lu] [IMG] Invalid render position: (%d,%d) size (%dx%d) screen (%dx%d)\n", millis(), x, y, width,
|
||||||
millis(), x, y, width, height, screenWidth, screenHeight);
|
height, screenWidth, screenHeight);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <SdFat.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <SdFat.h>
|
|
||||||
|
|
||||||
#include "Block.h"
|
#include "Block.h"
|
||||||
|
|
||||||
class ImageBlock final : public Block {
|
class ImageBlock final : public Block {
|
||||||
public:
|
public:
|
||||||
ImageBlock(const std::string& imagePath, int16_t width, int16_t height);
|
ImageBlock(const std::string& imagePath, int16_t width, int16_t height);
|
||||||
~ImageBlock() override = default;
|
~ImageBlock() override = default;
|
||||||
|
|
||||||
const std::string& getImagePath() const { return imagePath; }
|
const std::string& getImagePath() const { return imagePath; }
|
||||||
int16_t getWidth() const { return width; }
|
int16_t getWidth() const { return width; }
|
||||||
int16_t getHeight() const { return height; }
|
int16_t getHeight() const { return height; }
|
||||||
|
|
||||||
bool imageExists() const;
|
bool imageExists() const;
|
||||||
|
|
||||||
void layout(GfxRenderer& renderer) override;
|
void layout(GfxRenderer& renderer) override;
|
||||||
BlockType getType() override { return IMAGE_BLOCK; }
|
BlockType getType() override { return IMAGE_BLOCK; }
|
||||||
bool isEmpty() override { return false; }
|
bool isEmpty() override { return false; }
|
||||||
|
|
||||||
void render(GfxRenderer& renderer, const int x, const int y);
|
void render(GfxRenderer& renderer, const int x, const int y);
|
||||||
bool serialize(FsFile& file);
|
bool serialize(FsFile& file);
|
||||||
static std::unique_ptr<ImageBlock> deserialize(FsFile& file);
|
static std::unique_ptr<ImageBlock> deserialize(FsFile& file);
|
||||||
|
|||||||
@ -4,10 +4,10 @@ void FramebufferWriter::setPixel(int x, int y, bool isBlack) {
|
|||||||
if (x < 0 || x >= DISPLAY_WIDTH || y < 0 || y >= DISPLAY_HEIGHT) {
|
if (x < 0 || x >= DISPLAY_WIDTH || y < 0 || y >= DISPLAY_HEIGHT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint16_t byteIndex = y * DISPLAY_WIDTH_BYTES + (x / 8);
|
const uint16_t byteIndex = y * DISPLAY_WIDTH_BYTES + (x / 8);
|
||||||
const uint8_t bitPosition = 7 - (x % 8);
|
const uint8_t bitPosition = 7 - (x % 8);
|
||||||
|
|
||||||
if (isBlack) {
|
if (isBlack) {
|
||||||
frameBuffer[byteIndex] &= ~(1 << bitPosition);
|
frameBuffer[byteIndex] &= ~(1 << bitPosition);
|
||||||
} else {
|
} else {
|
||||||
@ -19,10 +19,10 @@ void FramebufferWriter::setPixel2Bit(int x, int y, uint8_t value) {
|
|||||||
if (x < 0 || x >= DISPLAY_WIDTH || y < 0 || y >= DISPLAY_HEIGHT || value > 3) {
|
if (x < 0 || x >= DISPLAY_WIDTH || y < 0 || y >= DISPLAY_HEIGHT || value > 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint16_t byteIndex = y * DISPLAY_WIDTH_BYTES + (x / 8);
|
const uint16_t byteIndex = y * DISPLAY_WIDTH_BYTES + (x / 8);
|
||||||
const uint8_t bitPosition = 7 - (x % 8);
|
const uint8_t bitPosition = 7 - (x % 8);
|
||||||
|
|
||||||
if (value < 2) {
|
if (value < 2) {
|
||||||
frameBuffer[byteIndex] &= ~(1 << bitPosition);
|
frameBuffer[byteIndex] &= ~(1 << bitPosition);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -2,18 +2,18 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
class FramebufferWriter {
|
class FramebufferWriter {
|
||||||
private:
|
private:
|
||||||
uint8_t* frameBuffer;
|
uint8_t* frameBuffer;
|
||||||
static constexpr int DISPLAY_WIDTH = 800;
|
static constexpr int DISPLAY_WIDTH = 800;
|
||||||
static constexpr int DISPLAY_WIDTH_BYTES = DISPLAY_WIDTH / 8; // 100
|
static constexpr int DISPLAY_WIDTH_BYTES = DISPLAY_WIDTH / 8; // 100
|
||||||
static constexpr int DISPLAY_HEIGHT = 480;
|
static constexpr int DISPLAY_HEIGHT = 480;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FramebufferWriter(uint8_t* framebuffer) : frameBuffer(framebuffer) {}
|
explicit FramebufferWriter(uint8_t* framebuffer) : frameBuffer(framebuffer) {}
|
||||||
|
|
||||||
// Simple pixel setting for 1-bit rendering
|
// Simple pixel setting for 1-bit rendering
|
||||||
void setPixel(int x, int y, bool isBlack);
|
void setPixel(int x, int y, bool isBlack);
|
||||||
|
|
||||||
// 2-bit grayscale pixel setting (for dual-pass rendering)
|
// 2-bit grayscale pixel setting (for dual-pass rendering)
|
||||||
void setPixel2Bit(int x, int y, uint8_t value); // value: 0-3
|
void setPixel2Bit(int x, int y, uint8_t value); // value: 0-3
|
||||||
};
|
};
|
||||||
@ -1,21 +1,24 @@
|
|||||||
#include "ImageDecoderFactory.h"
|
#include "ImageDecoderFactory.h"
|
||||||
#include "JpegToFramebufferConverter.h"
|
|
||||||
#include "PngToFramebufferConverter.h"
|
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "JpegToFramebufferConverter.h"
|
||||||
|
#include "PngToFramebufferConverter.h"
|
||||||
|
|
||||||
std::unique_ptr<JpegToFramebufferConverter> ImageDecoderFactory::jpegDecoder = nullptr;
|
std::unique_ptr<JpegToFramebufferConverter> ImageDecoderFactory::jpegDecoder = nullptr;
|
||||||
std::unique_ptr<PngToFramebufferConverter> ImageDecoderFactory::pngDecoder = nullptr;
|
std::unique_ptr<PngToFramebufferConverter> ImageDecoderFactory::pngDecoder = nullptr;
|
||||||
bool ImageDecoderFactory::initialized = false;
|
bool ImageDecoderFactory::initialized = false;
|
||||||
|
|
||||||
void ImageDecoderFactory::initialize() {
|
void ImageDecoderFactory::initialize() {
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
|
|
||||||
jpegDecoder = std::unique_ptr<JpegToFramebufferConverter>(new JpegToFramebufferConverter());
|
jpegDecoder = std::unique_ptr<JpegToFramebufferConverter>(new JpegToFramebufferConverter());
|
||||||
pngDecoder = std::unique_ptr<PngToFramebufferConverter>(new PngToFramebufferConverter());
|
pngDecoder = std::unique_ptr<PngToFramebufferConverter>(new PngToFramebufferConverter());
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
Serial.printf("[%lu] [DEC] Image decoder factory initialized\n", millis());
|
Serial.printf("[%lu] [DEC] Image decoder factory initialized\n", millis());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,14 +10,14 @@ class JpegToFramebufferConverter;
|
|||||||
class PngToFramebufferConverter;
|
class PngToFramebufferConverter;
|
||||||
|
|
||||||
class ImageDecoderFactory {
|
class ImageDecoderFactory {
|
||||||
public:
|
public:
|
||||||
static void initialize();
|
static void initialize();
|
||||||
// Returns non-owning pointer - factory owns the decoder lifetime
|
// Returns non-owning pointer - factory owns the decoder lifetime
|
||||||
static ImageToFramebufferDecoder* getDecoder(const std::string& imagePath);
|
static ImageToFramebufferDecoder* getDecoder(const std::string& imagePath);
|
||||||
static bool isFormatSupported(const std::string& imagePath);
|
static bool isFormatSupported(const std::string& imagePath);
|
||||||
static std::vector<std::string> getSupportedFormats();
|
static std::vector<std::string> getSupportedFormats();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static std::unique_ptr<JpegToFramebufferConverter> jpegDecoder;
|
static std::unique_ptr<JpegToFramebufferConverter> jpegDecoder;
|
||||||
static std::unique_ptr<PngToFramebufferConverter> pngDecoder;
|
static std::unique_ptr<PngToFramebufferConverter> pngDecoder;
|
||||||
static bool initialized;
|
static bool initialized;
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
#include "ImageToFramebufferDecoder.h"
|
#include "ImageToFramebufferDecoder.h"
|
||||||
#include <HardwareSerial.h>
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
|
||||||
bool ImageToFramebufferDecoder::validateImageDimensions(int width, int height, const std::string& format) {
|
bool ImageToFramebufferDecoder::validateImageDimensions(int width, int height, const std::string& format) {
|
||||||
if (width > MAX_SOURCE_WIDTH || height > MAX_SOURCE_HEIGHT) {
|
if (width > MAX_SOURCE_WIDTH || height > MAX_SOURCE_HEIGHT) {
|
||||||
Serial.printf("[%lu] [IMG] Image too large (%dx%d %s), max supported: %dx%d\n",
|
Serial.printf("[%lu] [IMG] Image too large (%dx%d %s), max supported: %dx%d\n", millis(), width, height,
|
||||||
millis(), width, height, format.c_str(), MAX_SOURCE_WIDTH, MAX_SOURCE_HEIGHT);
|
format.c_str(), MAX_SOURCE_WIDTH, MAX_SOURCE_HEIGHT);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageToFramebufferDecoder::warnUnsupportedFeature(const std::string& feature, const std::string& imagePath) {
|
void ImageToFramebufferDecoder::warnUnsupportedFeature(const std::string& feature, const std::string& imagePath) {
|
||||||
Serial.printf("[%lu] [IMG] Warning: Unsupported feature '%s' in image '%s'. Image may not display correctly.\n",
|
Serial.printf("[%lu] [IMG] Warning: Unsupported feature '%s' in image '%s'. Image may not display correctly.\n",
|
||||||
millis(), feature.c_str(), imagePath.c_str());
|
millis(), feature.c_str(), imagePath.c_str());
|
||||||
}
|
}
|
||||||
@ -21,21 +21,17 @@ struct RenderConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ImageToFramebufferDecoder {
|
class ImageToFramebufferDecoder {
|
||||||
public:
|
public:
|
||||||
virtual ~ImageToFramebufferDecoder() = default;
|
virtual ~ImageToFramebufferDecoder() = default;
|
||||||
|
|
||||||
virtual bool decodeToFramebuffer(
|
virtual bool decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer, const RenderConfig& config) = 0;
|
||||||
const std::string& imagePath,
|
|
||||||
GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config
|
|
||||||
) = 0;
|
|
||||||
|
|
||||||
virtual bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const = 0;
|
virtual bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const = 0;
|
||||||
|
|
||||||
virtual bool supportsFormat(const std::string& extension) const = 0;
|
virtual bool supportsFormat(const std::string& extension) const = 0;
|
||||||
virtual const char* getFormatName() const = 0;
|
virtual const char* getFormatName() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Size validation helpers
|
// Size validation helpers
|
||||||
static constexpr int MAX_SOURCE_WIDTH = 2048;
|
static constexpr int MAX_SOURCE_WIDTH = 2048;
|
||||||
static constexpr int MAX_SOURCE_HEIGHT = 1536;
|
static constexpr int MAX_SOURCE_HEIGHT = 1536;
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
#include "JpegToFramebufferConverter.h"
|
#include "JpegToFramebufferConverter.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SdFat.h>
|
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
#include <SdFat.h>
|
||||||
#include <picojpeg.h>
|
#include <picojpeg.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -68,8 +70,8 @@ struct PixelCache {
|
|||||||
cacheFile.write(buffer, bytesPerRow * height);
|
cacheFile.write(buffer, bytesPerRow * height);
|
||||||
cacheFile.close();
|
cacheFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [JPG] Cache written: %s (%dx%d, %d bytes)\n", millis(),
|
Serial.printf("[%lu] [JPG] Cache written: %s (%dx%d, %d bytes)\n", millis(), cachePath.c_str(), width, height,
|
||||||
cachePath.c_str(), width, height, 4 + bytesPerRow * height);
|
4 + bytesPerRow * height);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,9 +112,9 @@ bool JpegToFramebufferConverter::getDimensionsStatic(const std::string& imagePat
|
|||||||
uint8_t JpegToFramebufferConverter::applyAtkinsonDithering(uint8_t gray, int x, int y, int width) {
|
uint8_t JpegToFramebufferConverter::applyAtkinsonDithering(uint8_t gray, int x, int y, int width) {
|
||||||
int16_t error = gray - (gray < 128 ? 0 : 255);
|
int16_t error = gray - (gray < 128 ? 0 : 255);
|
||||||
uint8_t newGray = gray - error;
|
uint8_t newGray = gray - error;
|
||||||
|
|
||||||
int8_t fraction = error >> 3;
|
int8_t fraction = error >> 3;
|
||||||
|
|
||||||
if (x + 1 < width && y + 1 < 512) ditherErrors[y + 1][(x + 1) % 3] += fraction;
|
if (x + 1 < width && y + 1 < 512) ditherErrors[y + 1][(x + 1) % 3] += fraction;
|
||||||
if (x + 2 < width && y + 1 < 512) ditherErrors[y + 1][(x + 2) % 3] += fraction;
|
if (x + 2 < width && y + 1 < 512) ditherErrors[y + 1][(x + 2) % 3] += fraction;
|
||||||
if (x + 1 < width) ditherErrors[y][(x + 1) % 3] += fraction;
|
if (x + 1 < width) ditherErrors[y][(x + 1) % 3] += fraction;
|
||||||
@ -120,11 +122,11 @@ uint8_t JpegToFramebufferConverter::applyAtkinsonDithering(uint8_t gray, int x,
|
|||||||
if (x - 1 >= 0 && x + 1 < width && y + 1 < 512) ditherErrors[y + 1][(x - 1 + 1) % 3] += fraction;
|
if (x - 1 >= 0 && x + 1 < width && y + 1 < 512) ditherErrors[y + 1][(x - 1 + 1) % 3] += fraction;
|
||||||
if (x - 1 >= 0 && y + 1 < 512) ditherErrors[y + 1][(x - 1) % 3] += fraction;
|
if (x - 1 >= 0 && y + 1 < 512) ditherErrors[y + 1][(x - 1) % 3] += fraction;
|
||||||
if (x + 1 < width && y + 2 < 512) ditherErrors[y + 2][(x + 1) % 3] += fraction;
|
if (x + 1 < width && y + 2 < 512) ditherErrors[y + 2][(x + 1) % 3] += fraction;
|
||||||
|
|
||||||
int16_t adjustedGray = newGray + ditherErrors[y][x % 3];
|
int16_t adjustedGray = newGray + ditherErrors[y][x % 3];
|
||||||
if (adjustedGray < 0) adjustedGray = 0;
|
if (adjustedGray < 0) adjustedGray = 0;
|
||||||
if (adjustedGray > 255) adjustedGray = 255;
|
if (adjustedGray > 255) adjustedGray = 255;
|
||||||
|
|
||||||
uint8_t outputGray;
|
uint8_t outputGray;
|
||||||
if (adjustedGray < 64) {
|
if (adjustedGray < 64) {
|
||||||
outputGray = 0;
|
outputGray = 0;
|
||||||
@ -135,17 +137,14 @@ uint8_t JpegToFramebufferConverter::applyAtkinsonDithering(uint8_t gray, int x,
|
|||||||
} else {
|
} else {
|
||||||
outputGray = 3;
|
outputGray = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
ditherErrors[y][x % 3] = adjustedGray - (outputGray * 85);
|
ditherErrors[y][x % 3] = adjustedGray - (outputGray * 85);
|
||||||
|
|
||||||
return outputGray;
|
return outputGray;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JpegToFramebufferConverter::decodeToFramebuffer(
|
bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer,
|
||||||
const std::string& imagePath,
|
const RenderConfig& config) {
|
||||||
GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config
|
|
||||||
) {
|
|
||||||
Serial.printf("[%lu] [JPG] Decoding JPEG: %s\n", millis(), imagePath.c_str());
|
Serial.printf("[%lu] [JPG] Decoding JPEG: %s\n", millis(), imagePath.c_str());
|
||||||
|
|
||||||
FsFile file;
|
FsFile file;
|
||||||
@ -172,12 +171,11 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate scale factor to fit within maxWidth/maxHeight
|
// Calculate scale factor to fit within maxWidth/maxHeight
|
||||||
float scaleX = (config.maxWidth > 0 && imageInfo.m_width > config.maxWidth)
|
float scaleX =
|
||||||
? (float)config.maxWidth / imageInfo.m_width
|
(config.maxWidth > 0 && imageInfo.m_width > config.maxWidth) ? (float)config.maxWidth / imageInfo.m_width : 1.0f;
|
||||||
: 1.0f;
|
|
||||||
float scaleY = (config.maxHeight > 0 && imageInfo.m_height > config.maxHeight)
|
float scaleY = (config.maxHeight > 0 && imageInfo.m_height > config.maxHeight)
|
||||||
? (float)config.maxHeight / imageInfo.m_height
|
? (float)config.maxHeight / imageInfo.m_height
|
||||||
: 1.0f;
|
: 1.0f;
|
||||||
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
||||||
if (scale > 1.0f) scale = 1.0f;
|
if (scale > 1.0f) scale = 1.0f;
|
||||||
|
|
||||||
@ -185,8 +183,8 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
int destHeight = (int)(imageInfo.m_height * scale);
|
int destHeight = (int)(imageInfo.m_height * scale);
|
||||||
|
|
||||||
Serial.printf("[%lu] [JPG] JPEG %dx%d -> %dx%d (scale %.2f), scan type: %d, MCU: %dx%d\n", millis(),
|
Serial.printf("[%lu] [JPG] JPEG %dx%d -> %dx%d (scale %.2f), scan type: %d, MCU: %dx%d\n", millis(),
|
||||||
imageInfo.m_width, imageInfo.m_height, destWidth, destHeight, scale,
|
imageInfo.m_width, imageInfo.m_height, destWidth, destHeight, scale, imageInfo.m_scanType,
|
||||||
imageInfo.m_scanType, imageInfo.m_MCUWidth, imageInfo.m_MCUHeight);
|
imageInfo.m_MCUWidth, imageInfo.m_MCUHeight);
|
||||||
|
|
||||||
if (!imageInfo.m_pMCUBufR || !imageInfo.m_pMCUBufG || !imageInfo.m_pMCUBufB) {
|
if (!imageInfo.m_pMCUBufR || !imageInfo.m_pMCUBufG || !imageInfo.m_pMCUBufB) {
|
||||||
Serial.printf("[%lu] [JPG] Null buffer pointers in imageInfo\n", millis());
|
Serial.printf("[%lu] [JPG] Null buffer pointers in imageInfo\n", millis());
|
||||||
@ -236,7 +234,8 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
int destX = config.x + (int)(srcX * scale);
|
int destX = config.x + (int)(srcX * scale);
|
||||||
if (destX >= screenWidth || destX >= config.x + destWidth) continue;
|
if (destX >= screenWidth || destX >= config.x + destWidth) continue;
|
||||||
uint8_t gray = imageInfo.m_pMCUBufR[row * 8 + col];
|
uint8_t gray = imageInfo.m_pMCUBufR[row * 8 + col];
|
||||||
uint8_t dithered = config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
uint8_t dithered =
|
||||||
|
config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
||||||
if (dithered > 3) dithered = 3;
|
if (dithered > 3) dithered = 3;
|
||||||
renderer.drawPixel(destX, destY, dithered < 2);
|
renderer.drawPixel(destX, destY, dithered < 2);
|
||||||
if (caching) cache.setPixel(destX, destY, dithered);
|
if (caching) cache.setPixel(destX, destY, dithered);
|
||||||
@ -257,7 +256,8 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
uint8_t g = imageInfo.m_pMCUBufG[row * 8 + col];
|
uint8_t g = imageInfo.m_pMCUBufG[row * 8 + col];
|
||||||
uint8_t b = imageInfo.m_pMCUBufB[row * 8 + col];
|
uint8_t b = imageInfo.m_pMCUBufB[row * 8 + col];
|
||||||
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
||||||
uint8_t dithered = config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
uint8_t dithered =
|
||||||
|
config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
||||||
if (dithered > 3) dithered = 3;
|
if (dithered > 3) dithered = 3;
|
||||||
renderer.drawPixel(destX, destY, dithered < 2);
|
renderer.drawPixel(destX, destY, dithered < 2);
|
||||||
if (caching) cache.setPixel(destX, destY, dithered);
|
if (caching) cache.setPixel(destX, destY, dithered);
|
||||||
@ -280,7 +280,8 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
uint8_t g = imageInfo.m_pMCUBufG[blockIndex * 64 + pixelIndex];
|
uint8_t g = imageInfo.m_pMCUBufG[blockIndex * 64 + pixelIndex];
|
||||||
uint8_t b = imageInfo.m_pMCUBufB[blockIndex * 64 + pixelIndex];
|
uint8_t b = imageInfo.m_pMCUBufB[blockIndex * 64 + pixelIndex];
|
||||||
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
||||||
uint8_t dithered = config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
uint8_t dithered =
|
||||||
|
config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
||||||
if (dithered > 3) dithered = 3;
|
if (dithered > 3) dithered = 3;
|
||||||
renderer.drawPixel(destX, destY, dithered < 2);
|
renderer.drawPixel(destX, destY, dithered < 2);
|
||||||
if (caching) cache.setPixel(destX, destY, dithered);
|
if (caching) cache.setPixel(destX, destY, dithered);
|
||||||
@ -303,7 +304,8 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
uint8_t g = imageInfo.m_pMCUBufG[blockIndex * 128 + pixelIndex];
|
uint8_t g = imageInfo.m_pMCUBufG[blockIndex * 128 + pixelIndex];
|
||||||
uint8_t b = imageInfo.m_pMCUBufB[blockIndex * 128 + pixelIndex];
|
uint8_t b = imageInfo.m_pMCUBufB[blockIndex * 128 + pixelIndex];
|
||||||
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
||||||
uint8_t dithered = config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
uint8_t dithered =
|
||||||
|
config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
||||||
if (dithered > 3) dithered = 3;
|
if (dithered > 3) dithered = 3;
|
||||||
renderer.drawPixel(destX, destY, dithered < 2);
|
renderer.drawPixel(destX, destY, dithered < 2);
|
||||||
if (caching) cache.setPixel(destX, destY, dithered);
|
if (caching) cache.setPixel(destX, destY, dithered);
|
||||||
@ -329,7 +331,8 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
uint8_t g = imageInfo.m_pMCUBufG[blockOffset + pixelIndex];
|
uint8_t g = imageInfo.m_pMCUBufG[blockOffset + pixelIndex];
|
||||||
uint8_t b = imageInfo.m_pMCUBufB[blockOffset + pixelIndex];
|
uint8_t b = imageInfo.m_pMCUBufB[blockOffset + pixelIndex];
|
||||||
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
||||||
uint8_t dithered = config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
uint8_t dithered =
|
||||||
|
config.useDithering ? applyAtkinsonDithering(gray, destX, destY, screenWidth) : gray / 85;
|
||||||
if (dithered > 3) dithered = 3;
|
if (dithered > 3) dithered = 3;
|
||||||
renderer.drawPixel(destX, destY, dithered < 2);
|
renderer.drawPixel(destX, destY, dithered < 2);
|
||||||
if (caching) cache.setPixel(destX, destY, dithered);
|
if (caching) cache.setPixel(destX, destY, dithered);
|
||||||
@ -356,14 +359,10 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char JpegToFramebufferConverter::jpegReadCallback(
|
unsigned char JpegToFramebufferConverter::jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
||||||
unsigned char* pBuf,
|
unsigned char* pBytes_actually_read, void* pCallback_data) {
|
||||||
unsigned char buf_size,
|
|
||||||
unsigned char* pBytes_actually_read,
|
|
||||||
void* pCallback_data
|
|
||||||
) {
|
|
||||||
JpegContext* context = reinterpret_cast<JpegContext*>(pCallback_data);
|
JpegContext* context = reinterpret_cast<JpegContext*>(pCallback_data);
|
||||||
|
|
||||||
if (context->bufferPos >= context->bufferFilled) {
|
if (context->bufferPos >= context->bufferFilled) {
|
||||||
int readCount = context->file.read(context->buffer, sizeof(context->buffer));
|
int readCount = context->file.read(context->buffer, sizeof(context->buffer));
|
||||||
if (readCount <= 0) {
|
if (readCount <= 0) {
|
||||||
@ -373,14 +372,14 @@ unsigned char JpegToFramebufferConverter::jpegReadCallback(
|
|||||||
context->bufferFilled = readCount;
|
context->bufferFilled = readCount;
|
||||||
context->bufferPos = 0;
|
context->bufferPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int bytesAvailable = context->bufferFilled - context->bufferPos;
|
unsigned int bytesAvailable = context->bufferFilled - context->bufferPos;
|
||||||
unsigned int bytesToCopy = (bytesAvailable < buf_size) ? bytesAvailable : buf_size;
|
unsigned int bytesToCopy = (bytesAvailable < buf_size) ? bytesAvailable : buf_size;
|
||||||
|
|
||||||
memcpy(pBuf, &context->buffer[context->bufferPos], bytesToCopy);
|
memcpy(pBuf, &context->buffer[context->bufferPos], bytesToCopy);
|
||||||
context->bufferPos += bytesToCopy;
|
context->bufferPos += bytesToCopy;
|
||||||
*pBytes_actually_read = bytesToCopy;
|
*pBytes_actually_read = bytesToCopy;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "ImageToFramebufferDecoder.h"
|
#include "ImageToFramebufferDecoder.h"
|
||||||
|
|
||||||
class JpegToFramebufferConverter final : public ImageToFramebufferDecoder {
|
class JpegToFramebufferConverter final : public ImageToFramebufferDecoder {
|
||||||
public:
|
public:
|
||||||
static bool getDimensionsStatic(const std::string& imagePath, ImageDimensions& out);
|
static bool getDimensionsStatic(const std::string& imagePath, ImageDimensions& out);
|
||||||
|
|
||||||
bool decodeToFramebuffer(
|
bool decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer, const RenderConfig& config) override;
|
||||||
const std::string& imagePath,
|
|
||||||
GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config
|
|
||||||
) override;
|
|
||||||
|
|
||||||
bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const override {
|
bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const override {
|
||||||
return getDimensionsStatic(imagePath, dims);
|
return getDimensionsStatic(imagePath, dims);
|
||||||
@ -21,12 +18,8 @@ public:
|
|||||||
bool supportsFormat(const std::string& extension) const override;
|
bool supportsFormat(const std::string& extension) const override;
|
||||||
const char* getFormatName() const override { return "JPEG"; }
|
const char* getFormatName() const override { return "JPEG"; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t applyAtkinsonDithering(uint8_t gray, int x, int y, int width);
|
uint8_t applyAtkinsonDithering(uint8_t gray, int x, int y, int width);
|
||||||
static unsigned char jpegReadCallback(
|
static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
||||||
unsigned char* pBuf,
|
unsigned char* pBytes_actually_read, void* pCallback_data);
|
||||||
unsigned char buf_size,
|
|
||||||
unsigned char* pBytes_actually_read,
|
|
||||||
void* pCallback_data
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
#include "PngToFramebufferConverter.h"
|
#include "PngToFramebufferConverter.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SdFat.h>
|
|
||||||
#include <SDCardManager.h>
|
|
||||||
#include <PNGdec.h>
|
#include <PNGdec.h>
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
#include <SdFat.h>
|
||||||
|
|
||||||
static FsFile* gPngFile = nullptr;
|
static FsFile* gPngFile = nullptr;
|
||||||
|
|
||||||
static void* pngOpenForDims(const char* filename, int32_t* size) {
|
static void* pngOpenForDims(const char* filename, int32_t* size) { return gPngFile; }
|
||||||
return gPngFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pngCloseForDims(void* handle) {
|
static void pngCloseForDims(void* handle) {}
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t pngReadForDims(PNGFILE* pFile, uint8_t* pBuf, int32_t len) {
|
static int32_t pngReadForDims(PNGFILE* pFile, uint8_t* pBuf, int32_t len) {
|
||||||
if (!gPngFile) return 0;
|
if (!gPngFile) return 0;
|
||||||
@ -91,9 +89,9 @@ static void cacheSetPixel(int screenX, int screenY, uint8_t value) {
|
|||||||
static uint8_t applyAtkinsonDithering(uint8_t gray, int x, int y, int width) {
|
static uint8_t applyAtkinsonDithering(uint8_t gray, int x, int y, int width) {
|
||||||
int16_t error = gray - (gray < 128 ? 0 : 255);
|
int16_t error = gray - (gray < 128 ? 0 : 255);
|
||||||
uint8_t newGray = gray - error;
|
uint8_t newGray = gray - error;
|
||||||
|
|
||||||
int8_t fraction = error >> 3;
|
int8_t fraction = error >> 3;
|
||||||
|
|
||||||
if (x + 1 < width) ditherErrors[y + 1][(x + 1) % 3] += fraction;
|
if (x + 1 < width) ditherErrors[y + 1][(x + 1) % 3] += fraction;
|
||||||
if (x + 2 < width) ditherErrors[y + 1][(x + 2) % 3] += fraction;
|
if (x + 2 < width) ditherErrors[y + 1][(x + 2) % 3] += fraction;
|
||||||
if (x + 1 < width) ditherErrors[y][(x + 1) % 3] += fraction;
|
if (x + 1 < width) ditherErrors[y][(x + 1) % 3] += fraction;
|
||||||
@ -101,11 +99,11 @@ static uint8_t applyAtkinsonDithering(uint8_t gray, int x, int y, int width) {
|
|||||||
if (x - 1 >= 0 && x + 1 < width) ditherErrors[y + 1][(x - 1 + 1) % 3] += fraction;
|
if (x - 1 >= 0 && x + 1 < width) ditherErrors[y + 1][(x - 1 + 1) % 3] += fraction;
|
||||||
if (x - 1 >= 0) ditherErrors[y + 1][(x - 1) % 3] += fraction;
|
if (x - 1 >= 0) ditherErrors[y + 1][(x - 1) % 3] += fraction;
|
||||||
if (x + 1 < width) ditherErrors[y + 2][(x + 1) % 3] += fraction;
|
if (x + 1 < width) ditherErrors[y + 2][(x + 1) % 3] += fraction;
|
||||||
|
|
||||||
int16_t adjustedGray = newGray + ditherErrors[y][x % 3];
|
int16_t adjustedGray = newGray + ditherErrors[y][x % 3];
|
||||||
if (adjustedGray < 0) adjustedGray = 0;
|
if (adjustedGray < 0) adjustedGray = 0;
|
||||||
if (adjustedGray > 255) adjustedGray = 255;
|
if (adjustedGray > 255) adjustedGray = 255;
|
||||||
|
|
||||||
uint8_t outputGray;
|
uint8_t outputGray;
|
||||||
if (adjustedGray < 64) {
|
if (adjustedGray < 64) {
|
||||||
outputGray = 0;
|
outputGray = 0;
|
||||||
@ -116,9 +114,9 @@ static uint8_t applyAtkinsonDithering(uint8_t gray, int x, int y, int width) {
|
|||||||
} else {
|
} else {
|
||||||
outputGray = 3;
|
outputGray = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
ditherErrors[y][x % 3] = adjustedGray - (outputGray * 85);
|
ditherErrors[y][x % 3] = adjustedGray - (outputGray * 85);
|
||||||
|
|
||||||
return outputGray;
|
return outputGray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,30 +155,27 @@ static uint8_t getGrayFromPixel(uint8_t* pPixels, int x, int pixelType, uint8_t*
|
|||||||
case PNG_PIXEL_GRAYSCALE:
|
case PNG_PIXEL_GRAYSCALE:
|
||||||
return pPixels[x];
|
return pPixels[x];
|
||||||
|
|
||||||
case PNG_PIXEL_TRUECOLOR:
|
case PNG_PIXEL_TRUECOLOR: {
|
||||||
{
|
uint8_t* p = &pPixels[x * 3];
|
||||||
uint8_t* p = &pPixels[x * 3];
|
return (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
case PNG_PIXEL_INDEXED: {
|
||||||
|
uint8_t paletteIndex = pPixels[x];
|
||||||
|
if (palette) {
|
||||||
|
uint8_t* p = &palette[paletteIndex * 3];
|
||||||
return (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
return (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
}
|
}
|
||||||
|
return paletteIndex;
|
||||||
case PNG_PIXEL_INDEXED:
|
}
|
||||||
{
|
|
||||||
uint8_t paletteIndex = pPixels[x];
|
|
||||||
if (palette) {
|
|
||||||
uint8_t* p = &palette[paletteIndex * 3];
|
|
||||||
return (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
|
||||||
}
|
|
||||||
return paletteIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
case PNG_PIXEL_GRAY_ALPHA:
|
case PNG_PIXEL_GRAY_ALPHA:
|
||||||
return pPixels[x * 2];
|
return pPixels[x * 2];
|
||||||
|
|
||||||
case PNG_PIXEL_TRUECOLOR_ALPHA:
|
case PNG_PIXEL_TRUECOLOR_ALPHA: {
|
||||||
{
|
uint8_t* p = &pPixels[x * 4];
|
||||||
uint8_t* p = &pPixels[x * 4];
|
return (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
return (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 128;
|
return 128;
|
||||||
@ -232,11 +227,8 @@ int pngDrawCallback(PNGDRAW* pDraw) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PngToFramebufferConverter::decodeToFramebuffer(
|
bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer,
|
||||||
const std::string& imagePath,
|
const RenderConfig& config) {
|
||||||
GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config
|
|
||||||
) {
|
|
||||||
Serial.printf("[%lu] [PNG] Decoding PNG: %s\n", millis(), imagePath.c_str());
|
Serial.printf("[%lu] [PNG] Decoding PNG: %s\n", millis(), imagePath.c_str());
|
||||||
|
|
||||||
FsFile file;
|
FsFile file;
|
||||||
@ -280,8 +272,8 @@ bool PngToFramebufferConverter::decodeToFramebuffer(
|
|||||||
gDstHeight = (int)(gSrcHeight * gScale);
|
gDstHeight = (int)(gSrcHeight * gScale);
|
||||||
gLastDstY = -1; // Reset row tracking
|
gLastDstY = -1; // Reset row tracking
|
||||||
|
|
||||||
Serial.printf("[%lu] [PNG] PNG %dx%d -> %dx%d (scale %.2f), bpp: %d\n", millis(),
|
Serial.printf("[%lu] [PNG] PNG %dx%d -> %dx%d (scale %.2f), bpp: %d\n", millis(), gSrcWidth, gSrcHeight, gDstWidth,
|
||||||
gSrcWidth, gSrcHeight, gDstWidth, gDstHeight, gScale, png.getBpp());
|
gDstHeight, gScale, png.getBpp());
|
||||||
|
|
||||||
if (png.getBpp() != 8) {
|
if (png.getBpp() != 8) {
|
||||||
warnUnsupportedFeature("bit depth (" + std::to_string(png.getBpp()) + "bpp)", imagePath);
|
warnUnsupportedFeature("bit depth (" + std::to_string(png.getBpp()) + "bpp)", imagePath);
|
||||||
@ -303,7 +295,8 @@ bool PngToFramebufferConverter::decodeToFramebuffer(
|
|||||||
gCacheBuffer = (uint8_t*)malloc(bufferSize);
|
gCacheBuffer = (uint8_t*)malloc(bufferSize);
|
||||||
if (gCacheBuffer) {
|
if (gCacheBuffer) {
|
||||||
memset(gCacheBuffer, 0, bufferSize);
|
memset(gCacheBuffer, 0, bufferSize);
|
||||||
Serial.printf("[%lu] [PNG] Allocated cache buffer: %d bytes for %dx%d\n", millis(), bufferSize, gCacheWidth, gCacheHeight);
|
Serial.printf("[%lu] [PNG] Allocated cache buffer: %d bytes for %dx%d\n", millis(), bufferSize, gCacheWidth,
|
||||||
|
gCacheHeight);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [PNG] Failed to allocate cache buffer, continuing without caching\n", millis());
|
Serial.printf("[%lu] [PNG] Failed to allocate cache buffer, continuing without caching\n", millis());
|
||||||
caching = false;
|
caching = false;
|
||||||
@ -338,8 +331,8 @@ bool PngToFramebufferConverter::decodeToFramebuffer(
|
|||||||
cacheFile.write(&h, 2);
|
cacheFile.write(&h, 2);
|
||||||
cacheFile.write(gCacheBuffer, gCacheBytesPerRow * gCacheHeight);
|
cacheFile.write(gCacheBuffer, gCacheBytesPerRow * gCacheHeight);
|
||||||
cacheFile.close();
|
cacheFile.close();
|
||||||
Serial.printf("[%lu] [PNG] Cache written: %s (%dx%d, %d bytes)\n", millis(),
|
Serial.printf("[%lu] [PNG] Cache written: %s (%dx%d, %d bytes)\n", millis(), config.cachePath.c_str(),
|
||||||
config.cachePath.c_str(), gCacheWidth, gCacheHeight, 4 + gCacheBytesPerRow * gCacheHeight);
|
gCacheWidth, gCacheHeight, 4 + gCacheBytesPerRow * gCacheHeight);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [PNG] Failed to open cache file for writing: %s\n", millis(), config.cachePath.c_str());
|
Serial.printf("[%lu] [PNG] Failed to open cache file for writing: %s\n", millis(), config.cachePath.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ImageToFramebufferDecoder.h"
|
|
||||||
#include <PNGdec.h>
|
#include <PNGdec.h>
|
||||||
|
|
||||||
|
#include "ImageToFramebufferDecoder.h"
|
||||||
|
|
||||||
class PngToFramebufferConverter final : public ImageToFramebufferDecoder {
|
class PngToFramebufferConverter final : public ImageToFramebufferDecoder {
|
||||||
public:
|
public:
|
||||||
static bool getDimensionsStatic(const std::string& imagePath, ImageDimensions& out);
|
static bool getDimensionsStatic(const std::string& imagePath, ImageDimensions& out);
|
||||||
|
|
||||||
bool decodeToFramebuffer(
|
bool decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer, const RenderConfig& config) override;
|
||||||
const std::string& imagePath,
|
|
||||||
GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config
|
|
||||||
) override;
|
|
||||||
|
|
||||||
bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const override {
|
bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const override {
|
||||||
return getDimensionsStatic(imagePath, dims);
|
return getDimensionsStatic(imagePath, dims);
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
#include "../Page.h"
|
|
||||||
#include "../converters/ImageToFramebufferDecoder.h"
|
|
||||||
#include "../converters/ImageDecoderFactory.h"
|
|
||||||
#include "../../Epub.h"
|
#include "../../Epub.h"
|
||||||
|
#include "../Page.h"
|
||||||
|
#include "../converters/ImageDecoderFactory.h"
|
||||||
|
#include "../converters/ImageToFramebufferDecoder.h"
|
||||||
|
|
||||||
const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"};
|
const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"};
|
||||||
constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
|
constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
|
||||||
@ -91,22 +91,22 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
alt = atts[i + 1];
|
alt = atts[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!src.empty()) {
|
if (!src.empty()) {
|
||||||
Serial.printf("[%lu] [EHP] Found image: src=%s\n", millis(), src.c_str());
|
Serial.printf("[%lu] [EHP] Found image: src=%s\n", millis(), src.c_str());
|
||||||
|
|
||||||
// Get the spine item's href to resolve the relative path
|
// Get the spine item's href to resolve the relative path
|
||||||
size_t lastUnderscore = self->filepath.rfind('_');
|
size_t lastUnderscore = self->filepath.rfind('_');
|
||||||
if (lastUnderscore != std::string::npos && lastUnderscore > 0) {
|
if (lastUnderscore != std::string::npos && lastUnderscore > 0) {
|
||||||
std::string indexStr = self->filepath.substr(lastUnderscore + 1);
|
std::string indexStr = self->filepath.substr(lastUnderscore + 1);
|
||||||
indexStr.resize(indexStr.find('.'));
|
indexStr.resize(indexStr.find('.'));
|
||||||
int spineIndex = atoi(indexStr.c_str());
|
int spineIndex = atoi(indexStr.c_str());
|
||||||
|
|
||||||
const auto& spineItem = self->epub->getSpineItem(spineIndex);
|
const auto& spineItem = self->epub->getSpineItem(spineIndex);
|
||||||
std::string htmlHref = spineItem.href;
|
std::string htmlHref = spineItem.href;
|
||||||
size_t lastSlash = htmlHref.find_last_of('/');
|
size_t lastSlash = htmlHref.find_last_of('/');
|
||||||
std::string htmlDir = (lastSlash != std::string::npos) ? htmlHref.substr(0, lastSlash + 1) : "";
|
std::string htmlDir = (lastSlash != std::string::npos) ? htmlHref.substr(0, lastSlash + 1) : "";
|
||||||
|
|
||||||
// Resolve the image path relative to the HTML file
|
// Resolve the image path relative to the HTML file
|
||||||
std::string imageHref = src;
|
std::string imageHref = src;
|
||||||
while (imageHref.find("../") == 0) {
|
while (imageHref.find("../") == 0) {
|
||||||
@ -117,15 +117,16 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::string resolvedPath = htmlDir + imageHref;
|
std::string resolvedPath = htmlDir + imageHref;
|
||||||
|
|
||||||
// Create a unique filename for the cached image
|
// Create a unique filename for the cached image
|
||||||
std::string ext;
|
std::string ext;
|
||||||
size_t extPos = resolvedPath.rfind('.');
|
size_t extPos = resolvedPath.rfind('.');
|
||||||
if (extPos != std::string::npos) {
|
if (extPos != std::string::npos) {
|
||||||
ext = resolvedPath.substr(extPos);
|
ext = resolvedPath.substr(extPos);
|
||||||
}
|
}
|
||||||
std::string cachedImagePath = self->epub->getCachePath() + "/img_" + std::to_string(spineIndex) + "_" + std::to_string(self->imageCounter++) + ext;
|
std::string cachedImagePath = self->epub->getCachePath() + "/img_" + std::to_string(spineIndex) + "_" +
|
||||||
|
std::to_string(self->imageCounter++) + ext;
|
||||||
|
|
||||||
// Extract image to cache file
|
// Extract image to cache file
|
||||||
FsFile cachedImageFile;
|
FsFile cachedImageFile;
|
||||||
bool extractSuccess = false;
|
bool extractSuccess = false;
|
||||||
@ -135,7 +136,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
cachedImageFile.close();
|
cachedImageFile.close();
|
||||||
delay(50); // Give SD card time to sync
|
delay(50); // Give SD card time to sync
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractSuccess) {
|
if (extractSuccess) {
|
||||||
// Get image dimensions
|
// Get image dimensions
|
||||||
ImageDimensions dims = {0, 0};
|
ImageDimensions dims = {0, 0};
|
||||||
@ -154,8 +155,9 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
int displayWidth = (int)(dims.width * scale);
|
int displayWidth = (int)(dims.width * scale);
|
||||||
int displayHeight = (int)(dims.height * scale);
|
int displayHeight = (int)(dims.height * scale);
|
||||||
|
|
||||||
Serial.printf("[%lu] [EHP] Display size: %dx%d (scale %.2f)\n", millis(), displayWidth, displayHeight, scale);
|
Serial.printf("[%lu] [EHP] Display size: %dx%d (scale %.2f)\n", millis(), displayWidth, displayHeight,
|
||||||
|
scale);
|
||||||
|
|
||||||
// Create page for image
|
// Create page for image
|
||||||
if (self->currentPage && !self->currentPage->elements.empty()) {
|
if (self->currentPage && !self->currentPage->elements.empty()) {
|
||||||
self->completePageFn(std::move(self->currentPage));
|
self->completePageFn(std::move(self->currentPage));
|
||||||
@ -173,7 +175,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
}
|
}
|
||||||
self->currentPageNextY = 0;
|
self->currentPageNextY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create ImageBlock and add to page
|
// Create ImageBlock and add to page
|
||||||
auto imageBlock = std::make_shared<ImageBlock>(cachedImagePath, displayWidth, displayHeight);
|
auto imageBlock = std::make_shared<ImageBlock>(cachedImagePath, displayWidth, displayHeight);
|
||||||
if (!imageBlock) {
|
if (!imageBlock) {
|
||||||
@ -188,7 +190,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
}
|
}
|
||||||
self->currentPage->elements.push_back(pageImage);
|
self->currentPage->elements.push_back(pageImage);
|
||||||
self->currentPageNextY += displayHeight;
|
self->currentPageNextY += displayHeight;
|
||||||
|
|
||||||
self->depth += 1;
|
self->depth += 1;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@ -200,7 +202,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to alt text if image processing fails
|
// Fallback to alt text if image processing fails
|
||||||
if (!alt.empty()) {
|
if (!alt.empty()) {
|
||||||
alt = "[Image: " + alt + "]";
|
alt = "[Image: " + alt + "]";
|
||||||
@ -210,7 +212,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
self->characterData(userData, alt.c_str(), alt.length());
|
self->characterData(userData, alt.c_str(), alt.length());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No alt text, skip
|
// No alt text, skip
|
||||||
self->skipUntilDepth = self->depth;
|
self->skipUntilDepth = self->depth;
|
||||||
self->depth += 1;
|
self->depth += 1;
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "../ParsedText.h"
|
#include "../ParsedText.h"
|
||||||
#include "../blocks/TextBlock.h"
|
|
||||||
#include "../blocks/ImageBlock.h"
|
#include "../blocks/ImageBlock.h"
|
||||||
|
#include "../blocks/TextBlock.h"
|
||||||
|
|
||||||
class Page;
|
class Page;
|
||||||
class GfxRenderer;
|
class GfxRenderer;
|
||||||
@ -50,8 +50,8 @@ class ChapterHtmlSlimParser {
|
|||||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChapterHtmlSlimParser(std::shared_ptr<Epub> epub, const std::string& filepath, GfxRenderer& renderer, const int fontId,
|
explicit ChapterHtmlSlimParser(std::shared_ptr<Epub> epub, const std::string& filepath, GfxRenderer& renderer,
|
||||||
const float lineCompression, const bool extraParagraphSpacing,
|
const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
||||||
const std::function<void(std::unique_ptr<Page>)>& completePageFn,
|
const std::function<void(std::unique_ptr<Page>)>& completePageFn,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user