| 1 | // Aseprite TGA Library |
| 2 | // Copyright (C) 2020-2021 Igara Studio S.A. |
| 3 | // |
| 4 | // This file is released under the terms of the MIT license. |
| 5 | // Read LICENSE.txt for more information. |
| 6 | |
| 7 | #ifndef TGA_TGA_H_INCLUDED |
| 8 | #define TGA_TGA_H_INCLUDED |
| 9 | #pragma once |
| 10 | |
| 11 | #include <stdint.h> |
| 12 | |
| 13 | #include <cstdio> |
| 14 | #include <string> |
| 15 | #include <vector> |
| 16 | |
| 17 | namespace tga { |
| 18 | |
| 19 | enum ImageType { |
| 20 | NoImage = 0, |
| 21 | UncompressedIndexed = 1, |
| 22 | UncompressedRgb = 2, |
| 23 | UncompressedGray = 3, |
| 24 | RleIndexed = 9, |
| 25 | RleRgb = 10, |
| 26 | RleGray = 11, |
| 27 | }; |
| 28 | |
| 29 | typedef uint32_t color_t; |
| 30 | |
| 31 | const color_t color_r_shift = 0; |
| 32 | const color_t color_g_shift = 8; |
| 33 | const color_t color_b_shift = 16; |
| 34 | const color_t color_a_shift = 24; |
| 35 | const color_t color_r_mask = 0x000000ff; |
| 36 | const color_t color_g_mask = 0x0000ff00; |
| 37 | const color_t color_b_mask = 0x00ff0000; |
| 38 | const color_t color_rgb_mask = 0x00ffffff; |
| 39 | const color_t color_a_mask = 0xff000000; |
| 40 | |
| 41 | inline uint8_t getr(color_t c) { return (c >> color_r_shift) & 0xff; } |
| 42 | inline uint8_t getg(color_t c) { return (c >> color_g_shift) & 0xff; } |
| 43 | inline uint8_t getb(color_t c) { return (c >> color_b_shift) & 0xff; } |
| 44 | inline uint8_t geta(color_t c) { return (c >> color_a_shift) & 0xff; } |
| 45 | inline color_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) { |
| 46 | return ((r << color_r_shift) | |
| 47 | (g << color_g_shift) | |
| 48 | (b << color_b_shift) | |
| 49 | (a << color_a_shift)); |
| 50 | } |
| 51 | |
| 52 | class Colormap { |
| 53 | public: |
| 54 | Colormap() { } |
| 55 | Colormap(const int n) : m_color(n) { } |
| 56 | |
| 57 | int size() const { return int(m_color.size()); } |
| 58 | |
| 59 | const color_t& operator[](int i) const { |
| 60 | return m_color[i]; |
| 61 | } |
| 62 | |
| 63 | color_t& operator[](int i) { |
| 64 | return m_color[i]; |
| 65 | } |
| 66 | |
| 67 | bool operator==(const Colormap& o) const { |
| 68 | for (int i=0; i<int(m_color.size()); ++i) { |
| 69 | if (m_color[i] != o[i]) |
| 70 | return false; |
| 71 | } |
| 72 | return true; |
| 73 | } |
| 74 | |
| 75 | bool operator!=(const Colormap& o) const { |
| 76 | return !operator==(o); |
| 77 | } |
| 78 | |
| 79 | private: |
| 80 | std::vector<color_t> m_color; |
| 81 | }; |
| 82 | |
| 83 | struct Image { |
| 84 | uint8_t* pixels; |
| 85 | uint32_t bytesPerPixel; |
| 86 | uint32_t rowstride; |
| 87 | }; |
| 88 | |
| 89 | struct { |
| 90 | uint8_t ; |
| 91 | uint8_t ; |
| 92 | uint8_t ; |
| 93 | uint16_t ; |
| 94 | uint16_t ; |
| 95 | uint8_t ; |
| 96 | uint16_t ; |
| 97 | uint16_t ; |
| 98 | uint16_t ; |
| 99 | uint16_t ; |
| 100 | uint8_t ; |
| 101 | uint8_t ; |
| 102 | std::string ; |
| 103 | Colormap ; |
| 104 | |
| 105 | bool () const { return !(imageDescriptor & 0x10); } |
| 106 | bool () const { return (imageDescriptor & 0x20); } |
| 107 | |
| 108 | bool () const { |
| 109 | return (colormapLength > 0); |
| 110 | } |
| 111 | |
| 112 | bool () const { |
| 113 | return (imageType == UncompressedRgb || |
| 114 | imageType == RleRgb); |
| 115 | } |
| 116 | |
| 117 | bool () const { |
| 118 | return (imageType == UncompressedIndexed || |
| 119 | imageType == RleIndexed); |
| 120 | } |
| 121 | |
| 122 | bool () const { |
| 123 | return (imageType == UncompressedGray || |
| 124 | imageType == RleGray); |
| 125 | } |
| 126 | |
| 127 | bool () const { |
| 128 | return (imageType == UncompressedIndexed || |
| 129 | imageType == UncompressedRgb || |
| 130 | imageType == UncompressedGray); |
| 131 | } |
| 132 | |
| 133 | bool () const { |
| 134 | return (imageType == RleIndexed || |
| 135 | imageType == RleRgb || |
| 136 | imageType == RleGray); |
| 137 | } |
| 138 | |
| 139 | bool () const { |
| 140 | return |
| 141 | // Indexed with palette |
| 142 | (isIndexed() && bitsPerPixel == 8 && colormapType == 1) || |
| 143 | // Grayscale without palette |
| 144 | (isGray() && bitsPerPixel == 8 && colormapType == 0) || |
| 145 | // Non-indexed without palette |
| 146 | (bitsPerPixel > 8 && colormapType == 0); |
| 147 | } |
| 148 | |
| 149 | bool () const { |
| 150 | switch (imageType) { |
| 151 | case UncompressedIndexed: |
| 152 | case RleIndexed: |
| 153 | return (bitsPerPixel == 8); |
| 154 | case UncompressedRgb: |
| 155 | case RleRgb: |
| 156 | return (bitsPerPixel == 15 || |
| 157 | bitsPerPixel == 16 || |
| 158 | bitsPerPixel == 24 || |
| 159 | bitsPerPixel == 32); |
| 160 | case UncompressedGray: |
| 161 | case RleGray: |
| 162 | return (bitsPerPixel == 8); |
| 163 | } |
| 164 | return false; |
| 165 | } |
| 166 | |
| 167 | // Returns the number of bytes per pixel needed in an image |
| 168 | // created with this Header information. |
| 169 | int () const { |
| 170 | if (isRgb()) |
| 171 | return 4; |
| 172 | else |
| 173 | return 1; |
| 174 | } |
| 175 | |
| 176 | }; |
| 177 | |
| 178 | namespace details { |
| 179 | |
| 180 | class ImageIterator { |
| 181 | public: |
| 182 | ImageIterator(); |
| 183 | (const Header& , Image& image); |
| 184 | |
| 185 | // Put a pixel value into the image and advance the iterator. |
| 186 | template<typename T> |
| 187 | bool putPixel(const T value) { |
| 188 | *((T*)m_ptr) = value; |
| 189 | return advance(); |
| 190 | } |
| 191 | |
| 192 | // Get one pixel from the image and advance the iterator. |
| 193 | template<typename T> |
| 194 | T getPixel() { |
| 195 | T value = *((T*)m_ptr); |
| 196 | advance(); |
| 197 | return value; |
| 198 | } |
| 199 | |
| 200 | public: |
| 201 | bool advance(); |
| 202 | void calcPtr(); |
| 203 | |
| 204 | Image* m_image; |
| 205 | int m_x, m_y; |
| 206 | int m_w, m_h; |
| 207 | int m_dx, m_dy; |
| 208 | uint8_t* m_ptr; |
| 209 | }; |
| 210 | |
| 211 | } // namespace details |
| 212 | |
| 213 | class FileInterface { |
| 214 | public: |
| 215 | virtual ~FileInterface() { } |
| 216 | |
| 217 | // Returns true if we can read/write bytes from/into the file |
| 218 | virtual bool ok() const = 0; |
| 219 | |
| 220 | // Current position in the file |
| 221 | virtual size_t tell() = 0; |
| 222 | |
| 223 | // Jump to the given position in the file |
| 224 | virtual void seek(size_t absPos) = 0; |
| 225 | |
| 226 | // Returns the next byte in the file or 0 if ok() = false |
| 227 | virtual uint8_t read8() = 0; |
| 228 | |
| 229 | // Writes one byte in the file (or do nothing if ok() = false) |
| 230 | virtual void write8(uint8_t value) = 0; |
| 231 | }; |
| 232 | |
| 233 | class StdioFileInterface : public tga::FileInterface { |
| 234 | public: |
| 235 | StdioFileInterface(FILE* file); |
| 236 | bool ok() const override; |
| 237 | size_t tell() override; |
| 238 | void seek(size_t absPos) override; |
| 239 | uint8_t read8() override; |
| 240 | void write8(uint8_t value) override; |
| 241 | |
| 242 | private: |
| 243 | FILE* m_file; |
| 244 | bool m_ok; |
| 245 | }; |
| 246 | |
| 247 | class Delegate { |
| 248 | public: |
| 249 | virtual ~Delegate() {} |
| 250 | // Must return true if we should continue the decoding process. |
| 251 | virtual bool notifyProgress(double progress) = 0; |
| 252 | }; |
| 253 | |
| 254 | class Decoder { |
| 255 | public: |
| 256 | Decoder(FileInterface* file); |
| 257 | |
| 258 | bool hasAlpha() const { return m_hasAlpha; } |
| 259 | |
| 260 | // Reads the header + colormap (if the file has a |
| 261 | // colormap). Returns true if the header is valid. |
| 262 | bool (Header& ); |
| 263 | |
| 264 | // Reads the image. |
| 265 | bool (const Header& , |
| 266 | Image& image, |
| 267 | Delegate* delegate = nullptr); |
| 268 | |
| 269 | // Fixes alpha channel for images with invalid alpha values (this |
| 270 | // is optional, in case you want to preserve the original data |
| 271 | // from the file, don't use it). |
| 272 | void (const Header& , |
| 273 | Image& image); |
| 274 | |
| 275 | private: |
| 276 | void (Header& ); |
| 277 | |
| 278 | template<typename T> |
| 279 | bool readUncompressedData(const int w, uint32_t (Decoder::*readPixel)()); |
| 280 | |
| 281 | template<typename T> |
| 282 | bool readRleData(const int w, uint32_t (Decoder::*readPixel)()); |
| 283 | |
| 284 | uint8_t read8(); |
| 285 | uint16_t read16(); |
| 286 | uint32_t read32(); |
| 287 | |
| 288 | color_t read32AsRgb(); |
| 289 | color_t read24AsRgb(); |
| 290 | color_t read16AsRgb(); |
| 291 | color_t read8Color() { return (color_t)read8(); } |
| 292 | |
| 293 | FileInterface* m_file; |
| 294 | bool m_hasAlpha = false; |
| 295 | details::ImageIterator m_iterator; |
| 296 | }; |
| 297 | |
| 298 | class Encoder { |
| 299 | public: |
| 300 | Encoder(FileInterface* file); |
| 301 | |
| 302 | // Writes the header + colormap |
| 303 | void (const Header& ); |
| 304 | void (const Header& , |
| 305 | const Image& image, |
| 306 | Delegate* delegate = nullptr); |
| 307 | void (); |
| 308 | |
| 309 | private: |
| 310 | template<typename T> |
| 311 | void writeRleScanline(const int w, const Image& image, |
| 312 | void (Encoder::*writePixel)(color_t)); |
| 313 | |
| 314 | template<typename T> |
| 315 | void countRepeatedPixels(const int w, const Image& image, |
| 316 | int x0, int& offset, int& count); |
| 317 | |
| 318 | void write8(uint8_t value); |
| 319 | void write16(uint16_t value); |
| 320 | void write32(uint32_t value); |
| 321 | |
| 322 | void write8Color(color_t c) { write8(uint8_t(c)); } |
| 323 | void write16Rgb(color_t c); |
| 324 | void write24Rgb(color_t c); |
| 325 | void write32Rgb(color_t c); |
| 326 | |
| 327 | FileInterface* m_file; |
| 328 | bool m_hasAlpha = false; |
| 329 | details::ImageIterator m_iterator; |
| 330 | }; |
| 331 | |
| 332 | } // namespace tga |
| 333 | |
| 334 | #endif |
| 335 | |