1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "graphics/thumbnail.h"
24 #include "graphics/scaler.h"
25 #include "graphics/colormasks.h"
26 #include "common/endian.h"
27 #include "common/algorithm.h"
28 #include "common/system.h"
29 #include "common/stream.h"
30 #include "common/textconsole.h"
31
32 namespace Graphics {
33
34 namespace {
35 #define THMB_VERSION 2
36
37 struct ThumbnailHeader {
38 uint32 type;
39 uint32 size;
40 byte version;
41 uint16 width, height;
42 PixelFormat format;
43 };
44
45 #define ThumbnailHeaderSize (4+4+1+2+2+(1+4+4))
46
47 enum HeaderState {
48 /// There is no header present
49 kHeaderNone,
50 /// The header present only has reliable values for version and size
51 kHeaderUnsupported,
52 /// The header is present and the version is supported
53 kHeaderPresent
54 };
55
loadHeader(Common::SeekableReadStream & in,ThumbnailHeader & header,bool outputWarnings)56 HeaderState loadHeader(Common::SeekableReadStream &in, ThumbnailHeader &header, bool outputWarnings) {
57 header.type = in.readUint32BE();
58 // We also accept the bad 'BMHT' header here, for the sake of compatibility
59 // with some older savegames which were written incorrectly due to a bug in
60 // ScummVM which wrote the thumb header type incorrectly on LE systems.
61 if (header.type != MKTAG('T','H','M','B') && header.type != MKTAG('B','M','H','T')) {
62 if (outputWarnings)
63 warning("couldn't find thumbnail header type");
64 return kHeaderNone;
65 }
66
67 header.size = in.readUint32BE();
68 header.version = in.readByte();
69
70 // Do a check whether any read errors had occurred. If so we cannot use the
71 // values obtained for size and version because they might be bad.
72 if (in.err() || in.eos()) {
73 // TODO: We fake that there is no header. This is actually not quite
74 // correct since we found the start of the header and then things
75 // started to break. Right no we leave detection of this to the client.
76 // Since this case is caused by broken files, the client code should
77 // catch it anyway... If there is a nicer solution here, we should
78 // implement it.
79 return kHeaderNone;
80 }
81
82 if (header.version > THMB_VERSION) {
83 if (outputWarnings)
84 warning("trying to load a newer thumbnail version: %d instead of %d", header.version, THMB_VERSION);
85 return kHeaderUnsupported;
86 }
87
88 header.width = in.readUint16BE();
89 header.height = in.readUint16BE();
90 header.format.bytesPerPixel = in.readByte();
91 // Starting from version 2 on we serialize the whole PixelFormat.
92 if (header.version >= 2) {
93 header.format.rLoss = in.readByte();
94 header.format.gLoss = in.readByte();
95 header.format.bLoss = in.readByte();
96 header.format.aLoss = in.readByte();
97
98 header.format.rShift = in.readByte();
99 header.format.gShift = in.readByte();
100 header.format.bShift = in.readByte();
101 header.format.aShift = in.readByte();
102 } else {
103 // Version 1 used a hardcoded RGB565.
104 header.format = createPixelFormat<565>();
105 }
106
107 if (in.err() || in.eos()) {
108 // When we reached this point we know that at least the size and
109 // version field was loaded successfully, thus we tell this header
110 // is not supported and silently hope that the client code is
111 // prepared to handle read errors.
112 return kHeaderUnsupported;
113 } else {
114 return kHeaderPresent;
115 }
116 }
117 } // end of anonymous namespace
118
checkThumbnailHeader(Common::SeekableReadStream & in)119 bool checkThumbnailHeader(Common::SeekableReadStream &in) {
120 uint32 position = in.pos();
121 ThumbnailHeader header;
122
123 // TODO: It is not clear whether this is the best semantics. Now
124 // checkThumbnailHeader will return true even when the thumbnail header
125 // found is actually not usable. However, most engines seem to use this
126 // to detect the presence of any header and if there is none it wont even
127 // try to skip it. Thus, this looks like the best solution for now...
128 bool hasHeader = (loadHeader(in, header, false) != kHeaderNone);
129
130 in.seek(position, SEEK_SET);
131
132 return hasHeader;
133 }
134
skipThumbnail(Common::SeekableReadStream & in)135 bool skipThumbnail(Common::SeekableReadStream &in) {
136 uint32 position = in.pos();
137 ThumbnailHeader header;
138
139 // We can skip unsupported and supported headers. So we only seek back
140 // to the old position in case there is no header at all.
141 if (loadHeader(in, header, false) == kHeaderNone) {
142 in.seek(position, SEEK_SET);
143 return false;
144 }
145
146 in.seek(header.size - (in.pos() - position), SEEK_CUR);
147 return true;
148 }
149
loadThumbnail(Common::SeekableReadStream & in)150 Graphics::Surface *loadThumbnail(Common::SeekableReadStream &in) {
151 const uint32 position = in.pos();
152 ThumbnailHeader header;
153 HeaderState headerState = loadHeader(in, header, true);
154
155 // Try to handle unsupported/broken headers gracefully. If there is no
156 // header at all, we seek back and return at this point. If there is an
157 // unsupported/broken header, we skip the actual data and return. The
158 // downside is that we might reset the end of stream flag with this and
159 // the client code would not be able to notice a read past the end of the
160 // stream at this point then.
161 if (headerState == kHeaderNone) {
162 in.seek(position, SEEK_SET);
163 return 0;
164 } else if (headerState == kHeaderUnsupported) {
165 in.seek(header.size - (in.pos() - position), SEEK_CUR);
166 return 0;
167 }
168
169 if (header.format.bytesPerPixel != 2 && header.format.bytesPerPixel != 4) {
170 warning("trying to load thumbnail with unsupported bit depth %d", header.format.bytesPerPixel);
171 return 0;
172 }
173
174 Graphics::Surface *const to = new Graphics::Surface();
175 to->create(header.width, header.height, header.format);
176
177 for (int y = 0; y < to->h; ++y) {
178 switch (header.format.bytesPerPixel) {
179 case 2: {
180 uint16 *pixels = (uint16 *)to->getBasePtr(0, y);
181 for (uint x = 0; x < to->w; ++x) {
182 *pixels++ = in.readUint16BE();
183 }
184 } break;
185
186 case 4: {
187 uint32 *pixels = (uint32 *)to->getBasePtr(0, y);
188 for (uint x = 0; x < to->w; ++x) {
189 *pixels++ = in.readUint32BE();
190 }
191 } break;
192
193 default:
194 assert(0);
195 }
196 }
197 return to;
198 }
199
saveThumbnail(Common::WriteStream & out)200 bool saveThumbnail(Common::WriteStream &out) {
201 Graphics::Surface thumb;
202
203 if (!createThumbnailFromScreen(&thumb)) {
204 warning("Couldn't create thumbnail from screen, aborting thumbnail save");
205 return false;
206 }
207
208 bool success = saveThumbnail(out, thumb);
209 thumb.free();
210
211 return success;
212 }
213
saveThumbnail(Common::WriteStream & out,const Graphics::Surface & thumb)214 bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb) {
215 if (thumb.format.bytesPerPixel != 2 && thumb.format.bytesPerPixel != 4) {
216 warning("trying to save thumbnail with bpp %u", thumb.format.bytesPerPixel);
217 return false;
218 }
219
220 ThumbnailHeader header;
221 header.type = MKTAG('T','H','M','B');
222 header.size = ThumbnailHeaderSize + thumb.w*thumb.h*thumb.format.bytesPerPixel;
223 header.version = THMB_VERSION;
224 header.width = thumb.w;
225 header.height = thumb.h;
226
227 out.writeUint32BE(header.type);
228 out.writeUint32BE(header.size);
229 out.writeByte(header.version);
230 out.writeUint16BE(header.width);
231 out.writeUint16BE(header.height);
232
233 // Serialize the PixelFormat
234 out.writeByte(thumb.format.bytesPerPixel);
235 out.writeByte(thumb.format.rLoss);
236 out.writeByte(thumb.format.gLoss);
237 out.writeByte(thumb.format.bLoss);
238 out.writeByte(thumb.format.aLoss);
239 out.writeByte(thumb.format.rShift);
240 out.writeByte(thumb.format.gShift);
241 out.writeByte(thumb.format.bShift);
242 out.writeByte(thumb.format.aShift);
243
244 // Serialize the pixel data
245 for (uint y = 0; y < thumb.h; ++y) {
246 switch (thumb.format.bytesPerPixel) {
247 case 2: {
248 const uint16 *pixels = (const uint16 *)thumb.getBasePtr(0, y);
249 for (uint x = 0; x < thumb.w; ++x) {
250 out.writeUint16BE(*pixels++);
251 }
252 } break;
253
254 case 4: {
255 const uint32 *pixels = (const uint32 *)thumb.getBasePtr(0, y);
256 for (uint x = 0; x < thumb.w; ++x) {
257 out.writeUint32BE(*pixels++);
258 }
259 } break;
260
261 default:
262 assert(0);
263 }
264 }
265
266 return true;
267 }
268
269
270 /**
271 * Returns an array indicating which pixels of a source image horizontally or vertically get
272 * included in a scaled image
273 */
scaleLine(int size,int srcSize)274 int *scaleLine(int size, int srcSize) {
275 int scale = 100 * size / srcSize;
276 assert(scale > 0);
277 int *v = new int[size];
278 Common::fill(v, &v[size], 0);
279
280 int distCtr = 0;
281 int *destP = v;
282 for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
283 distCtr += scale;
284 while (distCtr >= 100) {
285 assert(destP < &v[size]);
286 *destP++ = distIndex;
287 distCtr -= 100;
288 }
289 }
290
291 return v;
292 }
293
scale(const Graphics::Surface & srcImage,int xSize,int ySize)294 Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize) {
295 Graphics::Surface *s = new Graphics::Surface();
296 s->create(xSize, ySize, srcImage.format);
297
298 int *horizUsage = scaleLine(xSize, srcImage.w);
299 int *vertUsage = scaleLine(ySize, srcImage.h);
300
301 // Loop to create scaled version
302 for (int yp = 0; yp < ySize; ++yp) {
303 const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
304 byte *destP = (byte *)s->getBasePtr(0, yp);
305
306 for (int xp = 0; xp < xSize; ++xp) {
307 const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.format.bytesPerPixel);
308 for (int byteCtr = 0; byteCtr < srcImage.format.bytesPerPixel; ++byteCtr) {
309 *destP++ = *tempSrcP++;
310 }
311 }
312 }
313
314 // Delete arrays and return surface
315 delete[] horizUsage;
316 delete[] vertUsage;
317 return s;
318 }
319
320 } // End of namespace Graphics
321