Public Access
Enter on a picture shows it: shrunk to fit the screen, or at its own size with Enter again and the arrows to move. Dithered to the screen's 256 colours; a colour the screen has exactly is left alone, so screenshots are shown as they are. The picture is decoded once, straight into the screen's buffer, and kept there (App::retainsContent): no copy in memory. Decoding runs on the storage task, so the keys keep working and a 12 megapixel photograph appears as it comes instead of tripping the watchdog. PNG, BMP and GIF are read by decoders of our own, host-tested against files made by Pillow; the PNG one needs 32 KB where the display library's needed 44 KB in one block, which the device often doesn't have. JPEG uses the library's TJpgDec. Also corrects two sentences that still gave 16 KB as the editing limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
87 lines
4.3 KiB
C++
87 lines
4.3 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
|
|
// Pictures for the Storage App (issue #45, F1 Q233-Q242): what a file is and how big, where each
|
|
// of its pixels goes on the screen, and the readers for BMP and the first frame of a GIF. PNG is in
|
|
// png_reader.h; JPEG is decoded on the device by the display library's decoder.
|
|
// Nothing here holds a picture: every reader hands its pixels on as it gets them.
|
|
namespace roro::files {
|
|
|
|
enum class ImageKind : uint8_t { None, Png, Jpeg, Bmp, Gif };
|
|
|
|
// Reads up to `len` bytes at `offset`; returns how many it got.
|
|
using ImageRead = std::function<size_t(uint32_t offset, uint8_t* into, size_t len)>;
|
|
// `count` pixels of row `y` from column `x` on, three bytes each: red, green, blue.
|
|
using ImagePixels = std::function<void(int x, int y, int count, const uint8_t* rgb)>;
|
|
|
|
ImageKind imageKindOfName(const std::string& name); // by its extension
|
|
ImageKind imageKindOfBytes(const uint8_t* head, size_t len); // by its first bytes (8 are enough)
|
|
const char* imageKindName(ImageKind kind);
|
|
|
|
struct ImageInfo {
|
|
ImageKind kind = ImageKind::None;
|
|
int width = 0, height = 0;
|
|
};
|
|
// "" and `out` filled, or why the file can't be shown.
|
|
std::string imageInfo(const ImageRead& read, uint32_t size, ImageInfo& out);
|
|
|
|
// A PNG the firmware's `screenshot` wrote (png_rgb332.h): its pixels are not compressed and each
|
|
// is already a colour of the screen. Where the first row's pixels start, or 0 if it isn't one.
|
|
// Row y's pixels are at that offset + y * (width + 1).
|
|
uint32_t screenshotPixelsAt(const ImageRead& read, uint32_t size, int width, int height);
|
|
|
|
// A colour as the screen has it (RRRGGGBB), dithered by where it lands: the screen has 8 levels of
|
|
// red and green and 4 of blue, and a photograph bands without it. A colour the screen has exactly
|
|
// comes out as itself wherever it lands, so a screenshot isn't touched.
|
|
uint8_t rgb332Dithered(uint8_t r, uint8_t g, uint8_t b, int x, int y);
|
|
|
|
// From a picture's pixels to the screen's.
|
|
struct ImageMap {
|
|
int viewX = 0, viewY = 0, viewW = 0, viewH = 0; // the part of the screen the picture may use
|
|
int offX = 0, offY = 0; // the picture's corner in it (negative: scrolled)
|
|
uint32_t scale = 65536; // screen pixels for one of the picture's, 16.16; never over 1
|
|
|
|
// Where the picture's pixel lands. False if it's outside the view, or if another pixel is the
|
|
// one drawn there (shrunk, each screen pixel takes the first of the picture's that falls on it).
|
|
bool at(int sx, int sy, int& tx, int& ty) const;
|
|
bool rowUsed(int sy) const; // does any pixel of this row land?
|
|
bool below(int sy) const; // this row and every one after it land under the view: nothing more to draw
|
|
};
|
|
|
|
// How a picture is looked at: whole, shrunk to fit if it has to be; or at its own size, a
|
|
// screenful at a time.
|
|
class ImageFrame {
|
|
public:
|
|
ImageFrame() = default;
|
|
ImageFrame(int width, int height, int viewX, int viewY, int viewW, int viewH);
|
|
|
|
bool bigger() const { return w_ > vw_ || h_ > vh_; } // than the view: there is something to zoom
|
|
bool actual() const { return actual_; }
|
|
void toggle();
|
|
bool pan(int dx, int dy); // half a view a step, at its own size only; false: nothing moved
|
|
int percent() const; // of its own size, as shown
|
|
int jpegShrink() const; // 0 to 3: the halvings a JPEG decoder may do first, the picture still at least as big as shown
|
|
// For pixels counted after `shrink` halvings (a JPEG's), or the picture's own.
|
|
ImageMap map(int shrink = 0) const;
|
|
|
|
private:
|
|
uint32_t fitScale() const;
|
|
|
|
int w_ = 0, h_ = 0, vx_ = 0, vy_ = 0, vw_ = 1, vh_ = 1, panX_ = 0, panY_ = 0;
|
|
bool actual_ = false;
|
|
};
|
|
|
|
// A BMP: 8 bits with a palette, 24 or 32 bits, not compressed. `rowNeeded` lets rows be skipped
|
|
// without being read. "" or why it can't be shown.
|
|
std::string readBmp(const ImageRead& read, uint32_t size, const ImagePixels& pixels, const std::function<bool(int y)>& rowNeeded = nullptr);
|
|
|
|
// The first picture of a GIF, interlaced or not; transparent pixels are not handed on. It needs
|
|
// 17 KB while it runs. "" or why it can't be shown.
|
|
std::string readGif(const ImageRead& read, uint32_t size, const ImagePixels& pixels);
|
|
|
|
} // namespace roro::files
|