1 | // Aseprite TGA Library |
2 | // Copyright (C) 2020 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 | #include "tga.h" |
8 | |
9 | #include <algorithm> |
10 | #include <cassert> |
11 | |
12 | namespace tga { |
13 | |
14 | template<typename T> |
15 | inline color_t get_pixel(Image& image, int x, int y) { |
16 | return *(T*)(image.pixels + y*image.rowstride + x*image.bytesPerPixel); |
17 | } |
18 | |
19 | Encoder::Encoder(FileInterface* file) |
20 | : m_file(file) |
21 | { |
22 | } |
23 | |
24 | void Encoder::(const Header& ) |
25 | { |
26 | write8(header.idLength); |
27 | write8(header.colormapType); |
28 | write8(header.imageType); |
29 | write16(header.colormapOrigin); |
30 | write16(header.colormapLength); |
31 | write8(header.colormapDepth); |
32 | write16(header.xOrigin); |
33 | write16(header.yOrigin); |
34 | write16(header.width); |
35 | write16(header.height); |
36 | write8(header.bitsPerPixel); |
37 | write8(header.imageDescriptor); |
38 | |
39 | m_hasAlpha = (header.bitsPerPixel == 16 || |
40 | header.bitsPerPixel == 32); |
41 | |
42 | assert(header.colormapLength == header.colormap.size()); |
43 | |
44 | // Write colormap |
45 | if (header.colormapType == 1) { |
46 | for (int i=0; i<header.colormap.size(); ++i) { |
47 | color_t c = header.colormap[i]; |
48 | switch (header.colormapDepth) { |
49 | case 15: |
50 | case 16: write16Rgb(c); break; |
51 | case 24: write24Rgb(c); break; |
52 | case 32: write32Rgb(c); break; |
53 | } |
54 | } |
55 | } |
56 | else { |
57 | assert(header.colormapLength == 0); |
58 | } |
59 | } |
60 | |
61 | void Encoder::() |
62 | { |
63 | const char* = "\0\0\0\0\0\0\0\0TRUEVISION-XFILE.\0" ; |
64 | for (int i=0; i<26; ++i) |
65 | write8(tga2_footer[i]); |
66 | } |
67 | |
68 | void Encoder::(const Header& , |
69 | const Image& image, |
70 | Delegate* delegate) |
71 | { |
72 | const int w = header.width; |
73 | const int h = header.height; |
74 | |
75 | m_iterator = details::ImageIterator(header, const_cast<Image&>(image)); |
76 | |
77 | switch (header.imageType) { |
78 | |
79 | case tga::UncompressedIndexed: |
80 | case tga::UncompressedGray: |
81 | for (int y=0; y<h; ++y) { |
82 | for (int x=0; x<w; ++x) |
83 | write8(m_iterator.getPixel<uint8_t>()); |
84 | |
85 | if (delegate && !delegate->notifyProgress(float(y) / float(h))) |
86 | return; |
87 | } |
88 | break; |
89 | |
90 | case tga::RleIndexed: |
91 | case tga::RleGray: |
92 | for (int y=0; y<h; ++y) { |
93 | writeRleScanline<uint8_t>(w, image, &Encoder::write8Color); |
94 | |
95 | if (delegate && !delegate->notifyProgress(float(y) / float(h))) |
96 | return; |
97 | } |
98 | break; |
99 | |
100 | case tga::UncompressedRgb: { |
101 | switch (header.bitsPerPixel) { |
102 | case 15: |
103 | case 16: |
104 | for (int y=0; y<h; ++y) { |
105 | for (int x=0; x<w; ++x) |
106 | write16Rgb(m_iterator.getPixel<uint32_t>()); |
107 | |
108 | if (delegate && !delegate->notifyProgress(float(y) / float(h))) |
109 | return; |
110 | } |
111 | break; |
112 | case 24: |
113 | for (int y=0; y<h; ++y) { |
114 | for (int x=0; x<w; ++x) |
115 | write24Rgb(m_iterator.getPixel<uint32_t>()); |
116 | |
117 | if (delegate && !delegate->notifyProgress(float(y) / float(h))) |
118 | return; |
119 | } |
120 | break; |
121 | case 32: |
122 | for (int y=0; y<h; ++y) { |
123 | for (int x=0; x<w; ++x) |
124 | write32Rgb(m_iterator.getPixel<uint32_t>()); |
125 | |
126 | if (delegate && !delegate->notifyProgress(float(y) / float(h))) |
127 | return; |
128 | } |
129 | break; |
130 | } |
131 | } |
132 | break; |
133 | |
134 | case tga::RleRgb: |
135 | for (int y=0; y<h; ++y) { |
136 | switch (header.bitsPerPixel) { |
137 | case 15: |
138 | case 16: writeRleScanline<uint32_t>(w, image, &Encoder::write16Rgb); break; |
139 | case 24: writeRleScanline<uint32_t>(w, image, &Encoder::write24Rgb); break; |
140 | case 32: writeRleScanline<uint32_t>(w, image, &Encoder::write32Rgb); break; |
141 | default: |
142 | assert(false); |
143 | return; |
144 | } |
145 | if (delegate && !delegate->notifyProgress(float(y) / float(h))) |
146 | return; |
147 | } |
148 | break; |
149 | |
150 | } |
151 | } |
152 | |
153 | template<typename T> |
154 | void Encoder::writeRleScanline(const int w, |
155 | const Image& image, |
156 | void (Encoder::*writePixel)(color_t)) |
157 | { |
158 | int x = 0; |
159 | while (x < w) { |
160 | int count, offset; |
161 | countRepeatedPixels<T>(w, image, x, offset, count); |
162 | |
163 | // Save a sequence of pixels with different colors |
164 | while (offset > 0) { |
165 | const int n = std::min(offset, 128); |
166 | |
167 | assert(n >= 1 && n <= 128); |
168 | write8(static_cast<uint8_t>(n - 1)); |
169 | for (int i=0; i<n; ++i) { |
170 | const color_t c = m_iterator.getPixel<T>(); |
171 | (this->*writePixel)(c); |
172 | } |
173 | offset -= n; |
174 | x += n; |
175 | } |
176 | |
177 | // Save a sequence of pixels with the same color |
178 | while (count*image.bytesPerPixel > 1+image.bytesPerPixel) { |
179 | const int n = std::min(count, 128); |
180 | const color_t c = m_iterator.getPixel<T>(); |
181 | |
182 | for (int i=1; i<n; ++i) { |
183 | const color_t c2 = m_iterator.getPixel<T>(); |
184 | assert(c == c2); |
185 | } |
186 | |
187 | assert(n >= 1 && n <= 128); |
188 | write8(0x80 | static_cast<uint8_t>(n - 1)); |
189 | (this->*writePixel)(c); |
190 | count -= n; |
191 | x += n; |
192 | } |
193 | } |
194 | assert(x == w); |
195 | } |
196 | |
197 | template<typename T> |
198 | void Encoder::countRepeatedPixels(const int w, |
199 | const Image& image, int x0, |
200 | int& offset, int& count) |
201 | { |
202 | auto it = m_iterator; |
203 | |
204 | for (int x=x0; x<w; ) { |
205 | const color_t p = it.getPixel<T>(); |
206 | |
207 | int u = x+1; |
208 | auto next_it = it; |
209 | |
210 | for (; u<w; ++u) { |
211 | const color_t q = it.getPixel<T>(); |
212 | if (p != q) |
213 | break; |
214 | } |
215 | |
216 | if ((u - x)*image.bytesPerPixel > 1+image.bytesPerPixel) { |
217 | offset = x - x0; |
218 | count = u - x; |
219 | return; |
220 | } |
221 | |
222 | ++x; |
223 | it = next_it; |
224 | } |
225 | |
226 | offset = w - x0; |
227 | count = 0; |
228 | } |
229 | |
230 | void Encoder::write8(uint8_t value) |
231 | { |
232 | m_file->write8(value); |
233 | } |
234 | |
235 | void Encoder::write16(uint16_t value) |
236 | { |
237 | // Little endian |
238 | m_file->write8(value & 0x00FF); |
239 | m_file->write8((value & 0xFF00) >> 8); |
240 | } |
241 | |
242 | void Encoder::write32(uint32_t value) |
243 | { |
244 | // Little endian |
245 | m_file->write8(value & 0xFF); |
246 | m_file->write8((value >> 8) & 0xFF); |
247 | m_file->write8((value >> 16) & 0xFF); |
248 | m_file->write8((value >> 24) & 0xFF); |
249 | } |
250 | |
251 | void Encoder::write16Rgb(color_t c) |
252 | { |
253 | const uint8_t r = getr(c); |
254 | const uint8_t g = getg(c); |
255 | const uint8_t b = getb(c); |
256 | const uint8_t a = geta(c); |
257 | const uint16_t v = |
258 | ((r>>3) << 10) | |
259 | ((g>>3) << 5) | |
260 | ((b>>3)) | |
261 | (m_hasAlpha && a >= 128 ? 0x8000: 0); // TODO configurable threshold |
262 | write16(v); |
263 | } |
264 | |
265 | void Encoder::write24Rgb(color_t c) |
266 | { |
267 | write8(getb(c)); |
268 | write8(getg(c)); |
269 | write8(getr(c)); |
270 | } |
271 | |
272 | void Encoder::write32Rgb(color_t c) |
273 | { |
274 | write8(getb(c)); |
275 | write8(getg(c)); |
276 | write8(getr(c)); |
277 | write8(geta(c)); |
278 | } |
279 | |
280 | } // namespace tga |
281 | |