#pragma once #include #include #include #include // 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; // `count` pixels of row `y` from column `x` on, three bytes each: red, green, blue. using ImagePixels = std::function; 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& 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