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 "sherlock/image_file.h"
24 #include "sherlock/screen.h"
25 #include "sherlock/sherlock.h"
26 #include "common/debug.h"
27 #include "common/memstream.h"
28
29 namespace Sherlock {
30
31 SherlockEngine *ImageFile::_vm;
32
setVm(SherlockEngine * vm)33 void ImageFile::setVm(SherlockEngine *vm) {
34 _vm = vm;
35 }
36
ImageFile()37 ImageFile::ImageFile() {
38 }
39
ImageFile(const Common::String & name,bool skipPal,bool animImages)40 ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) {
41 _name = name;
42 Common::SeekableReadStream *stream = _vm->_res->load(name);
43
44 Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
45 load(*stream, skipPal, animImages);
46
47 delete stream;
48 }
49
ImageFile(Common::SeekableReadStream & stream,bool skipPal)50 ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) {
51 Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
52 load(stream, skipPal, false);
53 }
54
~ImageFile()55 ImageFile::~ImageFile() {
56 for (uint idx = 0; idx < size(); ++idx) {
57 if (_frames[idx]._decoded)
58 _frames[idx]._frame.free();
59 }
60 }
61
operator [](uint index)62 ImageFrame& ImageFile::operator[](uint index) {
63 if (!_frames[index]._decoded) {
64 decodeFrame(_frames[index]);
65 }
66
67 return _frames[index];
68 }
69
size()70 uint ImageFile::size() {
71 return _frames.size();
72 }
73
push_back(const ImageFrame & frame)74 void ImageFile::push_back(const ImageFrame &frame) {
75 _frames.push_back(frame);
76 }
77
decodeFrame(ImageFrame & frame)78 void ImageFile::decodeFrame(ImageFrame &frame) {
79 Common::SeekableReadStream *stream = _vm->_res->load(_name);
80 stream->seek(frame._pos);
81 byte *data = new byte[frame._size + 4];
82 stream->read(data, frame._size);
83 Common::fill(data + frame._size, data + frame._size + 4, 0);
84 frame.decompressFrame(data, IS_ROSE_TATTOO);
85 delete[] data;
86 delete stream;
87 }
88
load(Common::SeekableReadStream & stream,bool skipPalette,bool animImages)89 void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) {
90 loadPalette(stream);
91
92 int streamSize = stream.size();
93 while (stream.pos() < streamSize) {
94 ImageFrame frame;
95 frame._width = stream.readUint16LE() + 1;
96 frame._height = stream.readUint16LE() + 1;
97 frame._paletteBase = stream.readByte();
98
99 if (animImages) {
100 // Animation cutscene image files use a 16-bit x offset
101 frame._offset.x = stream.readUint16LE();
102 frame._rleEncoded = (frame._offset.x & 0xff) == 1;
103 frame._offset.y = stream.readByte();
104 } else {
105 // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset
106 frame._rleEncoded = stream.readByte() == 1;
107 frame._offset.x = stream.readByte();
108 frame._offset.y = stream.readByte();
109 }
110
111 frame._rleEncoded = !skipPalette && frame._rleEncoded;
112
113 if (frame._paletteBase) {
114 // Nibble packed frame data
115 frame._size = (frame._width * frame._height) / 2;
116 } else if (frame._rleEncoded) {
117 // This size includes the header size, which we subtract
118 frame._size = stream.readUint16LE() - 11;
119 frame._rleMarker = stream.readByte();
120 } else {
121 // Uncompressed data
122 frame._size = frame._width * frame._height;
123 }
124
125 frame._pos = stream.pos();
126
127 if (_name.empty()) {
128 // Load data for frame and decompress it
129 frame._decoded = true;
130 byte *data1 = new byte[frame._size + 4];
131 stream.read(data1, frame._size);
132 Common::fill(data1 + frame._size, data1 + frame._size + 4, 0);
133 frame.decompressFrame(data1, IS_ROSE_TATTOO);
134 delete[] data1;
135 } else {
136 frame._decoded = false;
137
138 stream.seek(MIN(stream.pos() + frame._size, stream.size()));
139 }
140
141 push_back(frame);
142 }
143 }
144
loadPalette(Common::SeekableReadStream & stream)145 void ImageFile::loadPalette(Common::SeekableReadStream &stream) {
146 // Check for palette
147 uint16 width = stream.readUint16LE() + 1;
148 uint16 height = stream.readUint16LE() + 1;
149 byte paletteBase = stream.readByte();
150 byte rleEncoded = stream.readByte();
151 byte offsetX = stream.readByte();
152 byte offsetY = stream.readByte();
153 uint32 palSignature = 0;
154
155 if ((width == 390) && (height == 2) && (!paletteBase) && (!rleEncoded) && (!offsetX) && (!offsetY)) {
156 // We check for these specific values
157 // We can't do "width * height", because at least the first German+Spanish menu bar is 60 x 13
158 // which is 780, which is the size of the palette. We obviously don't want to detect it as palette.
159
160 // As another security measure, we also check for the signature text
161 palSignature = stream.readUint32BE();
162 if (palSignature != MKTAG('V', 'G', 'A', ' ')) {
163 // signature mismatch, rewind
164 stream.seek(-12, SEEK_CUR);
165 return;
166 }
167 // Found palette, so read it in
168 stream.seek(8, SEEK_CUR); // Skip over the rest of the signature text "VGA palette"
169 for (int idx = 0; idx < PALETTE_SIZE; ++idx)
170 _palette[idx] = VGA_COLOR_TRANS(stream.readByte());
171 } else {
172 // Not a palette, so rewind to start of frame data for normal frame processing
173 stream.seek(-8, SEEK_CUR);
174 }
175 }
176
decompressFrame(const byte * src,bool isRoseTattoo)177 void ImageFrame::decompressFrame(const byte *src, bool isRoseTattoo) {
178 _frame.create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
179 byte *dest = (byte *)_frame.getPixels();
180 Common::fill(dest, dest + _width * _height, 0xff);
181
182 if (_paletteBase) {
183 // Nibble-packed
184 for (uint idx = 0; idx < _size; ++idx, ++src) {
185 *dest++ = *src & 0xF;
186 *dest++ = (*src >> 4);
187 }
188 } else if (_rleEncoded && isRoseTattoo) {
189 // Rose Tattoo run length encoding doesn't use the RLE marker byte
190 for (int yp = 0; yp < _height; ++yp) {
191 int xSize = _width;
192 while (xSize > 0) {
193 // Skip a given number of pixels
194 byte skip = *src++;
195 dest += skip;
196 xSize -= skip;
197 if (!xSize)
198 break;
199
200 // Get a run length, and copy the following number of pixels
201 int rleCount = *src++;
202 xSize -= rleCount;
203 while (rleCount-- > 0)
204 *dest++ = *src++;
205 }
206 assert(xSize == 0);
207 }
208 } else if (_rleEncoded) {
209 // RLE encoded
210 int frameSize = _width * _height;
211 while (frameSize > 0) {
212 if (*src == _rleMarker) {
213 byte rleColor = src[1];
214 byte rleCount = MIN((int)src[2], frameSize);
215 src += 3;
216 frameSize -= rleCount;
217 while (rleCount--)
218 *dest++ = rleColor;
219 } else {
220 *dest++ = *src++;
221 --frameSize;
222 }
223 }
224 } else {
225 // Uncompressed frame
226 Common::copy(src, src + _width * _height, dest);
227 }
228 }
229
230 /*----------------------------------------------------------------*/
231
sDrawXSize(int scaleVal) const232 int ImageFrame::sDrawXSize(int scaleVal) const {
233 int width = _width;
234 int scale = scaleVal == 0 ? 1 : scaleVal;
235
236 if (scaleVal >= SCALE_THRESHOLD)
237 --width;
238
239 int result = width * SCALE_THRESHOLD / scale;
240 if (scaleVal >= SCALE_THRESHOLD)
241 ++result;
242
243 return result;
244 }
245
sDrawYSize(int scaleVal) const246 int ImageFrame::sDrawYSize(int scaleVal) const {
247 int height = _height;
248 int scale = scaleVal == 0 ? 1 : scaleVal;
249
250 if (scaleVal >= SCALE_THRESHOLD)
251 --height;
252
253 int result = height * SCALE_THRESHOLD / scale;
254 if (scaleVal >= SCALE_THRESHOLD)
255 ++result;
256
257 return result;
258 }
259
sDrawXOffset(int scaleVal) const260 int ImageFrame::sDrawXOffset(int scaleVal) const {
261 if (scaleVal == SCALE_THRESHOLD)
262 return _offset.x;
263
264 int width = _offset.x;
265 int scale = scaleVal == 0 ? 1 : scaleVal;
266
267 if (scaleVal >= SCALE_THRESHOLD)
268 --width;
269
270 int result = width * SCALE_THRESHOLD / scale;
271 if (scaleVal > SCALE_THRESHOLD)
272 ++result;
273
274 return result;
275 }
276
sDrawYOffset(int scaleVal) const277 int ImageFrame::sDrawYOffset(int scaleVal) const {
278 if (scaleVal == SCALE_THRESHOLD)
279 return _offset.y;
280
281 int height = _offset.y;
282 int scale = scaleVal == 0 ? 1 : scaleVal;
283
284 if (scaleVal >= SCALE_THRESHOLD)
285 --height;
286
287 int result = height * SCALE_THRESHOLD / scale;
288 if (scaleVal > SCALE_THRESHOLD)
289 ++result;
290
291 return result;
292 }
293
294 // *******************************************************
295
296 /*----------------------------------------------------------------*/
297
298 SherlockEngine *ImageFile3DO::_vm;
299
setVm(SherlockEngine * vm)300 void ImageFile3DO::setVm(SherlockEngine *vm) {
301 _vm = vm;
302 }
303
ImageFile3DO(const Common::String & name,ImageFile3DOType imageFile3DOType)304 ImageFile3DO::ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType) {
305 #if 0
306 Common::File *dataStream = new Common::File();
307
308 if (!dataStream->open(name)) {
309 error("unable to open %s\n", name.c_str());
310 }
311 #endif
312 Common::SeekableReadStream *dataStream = _vm->_res->load(name);
313
314 switch(imageFile3DOType) {
315 case kImageFile3DOType_Animation:
316 loadAnimationFile(*dataStream);
317 break;
318 case kImageFile3DOType_Cel:
319 case kImageFile3DOType_CelAnimation:
320 load3DOCelFile(*dataStream);
321 break;
322 case kImageFile3DOType_RoomFormat:
323 load3DOCelRoomData(*dataStream);
324 break;
325 case kImageFile3DOType_Font:
326 loadFont(*dataStream);
327 break;
328 default:
329 error("unknown Imagefile-3DO-Type");
330 break;
331 }
332
333 delete dataStream;
334 }
335
ImageFile3DO(Common::SeekableReadStream & stream,bool isRoomData)336 ImageFile3DO::ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData) {
337 if (!isRoomData) {
338 load(stream, isRoomData);
339 } else {
340 load3DOCelRoomData(stream);
341 }
342 }
343
decodeFrame(ImageFrame & frame)344 void ImageFile3DO::decodeFrame(ImageFrame &frame) {
345 error("ImageFile3DO: Frame should already have been decoded");
346 }
347
load(Common::SeekableReadStream & stream,bool isRoomData)348 void ImageFile3DO::load(Common::SeekableReadStream &stream, bool isRoomData) {
349 uint32 headerId = 0;
350
351 if (isRoomData) {
352 load3DOCelRoomData(stream);
353 return;
354 }
355
356 headerId = stream.readUint32BE();
357 assert(!stream.eos());
358
359 // Seek back to the start
360 stream.seek(-4, SEEK_CUR);
361
362 // Identify type of file
363 switch (headerId) {
364 case MKTAG('C', 'C', 'B', ' '):
365 case MKTAG('A', 'N', 'I', 'M'):
366 case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
367 // 3DO .cel (title1a.cel, etc.) or animation file (walk.anim)
368 load3DOCelFile(stream);
369 break;
370
371 default:
372 // Sherlock animation file (.3da files)
373 loadAnimationFile(stream);
374 break;
375 }
376 }
377
378 // 3DO uses RGB555, we use RGB565 internally so that more platforms are able to run us
convertPixel(uint16 pixel3DO)379 inline uint16 ImageFile3DO::convertPixel(uint16 pixel3DO) {
380 byte red = (pixel3DO >> 10) & 0x1F;
381 byte green = (pixel3DO >> 5) & 0x1F;
382 byte blue = pixel3DO & 0x1F;
383
384 return ((red << 11) | (green << 6) | (blue));
385 }
386
loadAnimationFile(Common::SeekableReadStream & stream)387 void ImageFile3DO::loadAnimationFile(Common::SeekableReadStream &stream) {
388 uint32 streamLeft = stream.size() - stream.pos();
389 uint32 celDataSize = 0;
390
391 while (streamLeft > 0) {
392 ImageFrame frame;
393
394 // We expect a basic header of 8 bytes
395 if (streamLeft < 8)
396 error("load3DOAnimationFile: expected animation header, not enough bytes");
397
398 celDataSize = stream.readUint16BE();
399
400 frame._decoded = true;
401 frame._width = stream.readUint16BE() + 1; // 2 bytes BE width
402 frame._height = stream.readByte() + 1; // 1 byte BE height
403 frame._paletteBase = 0;
404
405 frame._rleEncoded = true; // always compressed
406 if (frame._width & 0x8000) {
407 frame._width &= 0x7FFF;
408 celDataSize += 0x10000;
409 }
410
411 frame._offset.x = stream.readUint16BE();
412 frame._offset.y = stream.readByte();
413 frame._size = 0;
414 // Got header
415 streamLeft -= 8;
416
417 // cel data follows
418 if (streamLeft < celDataSize)
419 error("load3DOAnimationFile: expected cel data, not enough bytes");
420
421 //
422 // Load data for frame and decompress it
423 byte *data_ = new byte[celDataSize];
424 stream.read(data_, celDataSize);
425 streamLeft -= celDataSize;
426
427 // always 16 bits per pixel (RGB555)
428 decompress3DOCelFrame(frame, data_, celDataSize, 16, NULL);
429
430 delete[] data_;
431
432 push_back(frame);
433 }
434 }
435
436 static byte imagefile3DO_cel_bitsPerPixelLookupTable[8] = {
437 0, 1, 2, 4, 6, 8, 16, 0
438 };
439
440 // Reads a 3DO .cel/.anim file
load3DOCelFile(Common::SeekableReadStream & stream)441 void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) {
442 int32 streamSize = stream.size();
443 int32 chunkStartPos = 0;
444 uint32 chunkTag = 0;
445 uint32 chunkSize = 0;
446 byte *chunkDataPtr = NULL;
447
448 // ANIM chunk (animation header for animation files)
449 bool animFound = false;
450 uint32 animVersion = 0;
451 uint32 animType = 0;
452 uint32 animFrameCount = 1; // we expect 1 frame without an ANIM header
453 // CCB chunk (cel control block)
454 bool ccbFound = false;
455 uint32 ccbVersion = 0;
456 uint32 ccbFlags = 0;
457 bool ccbFlags_compressed = false;
458 uint16 ccbPPMP0 = 0;
459 uint16 ccbPPMP1 = 0;
460 uint32 ccbPRE0 = 0;
461 uint16 ccbPRE0_height = 0;
462 byte ccbPRE0_bitsPerPixel = 0;
463 uint32 ccbPRE1 = 0;
464 uint16 ccbPRE1_width = 0;
465 uint32 ccbWidth = 0;
466 uint32 ccbHeight = 0;
467 // pixel lookup table
468 bool plutFound = false;
469 uint32 plutCount = 0;
470 ImageFile3DOPixelLookupTable plutRGBlookupTable;
471
472 memset(&plutRGBlookupTable, 0, sizeof(plutRGBlookupTable));
473
474 while (!stream.err() && (stream.pos() < streamSize)) {
475 chunkStartPos = stream.pos();
476 chunkTag = stream.readUint32BE();
477 chunkSize = stream.readUint32BE();
478
479 if (stream.eos() || stream.err())
480 break;
481
482 if (chunkSize < 8)
483 error("load3DOCelFile: Invalid chunk size");
484
485 uint32 dataSize = chunkSize - 8;
486
487 switch (chunkTag) {
488 case MKTAG('A', 'N', 'I', 'M'):
489 // animation header
490 assert(dataSize >= 24);
491
492 if (animFound)
493 error("load3DOCelFile: multiple ANIM chunks not supported");
494
495 animFound = true;
496 animVersion = stream.readUint32BE();
497 animType = stream.readUint32BE();
498 animFrameCount = stream.readUint32BE();
499 // UINT32 - framerate (0x2000 in walk.anim???)
500 // UINT32 - starting frame (0 for walk.anim)
501 // UINT32 - number of loops (0 for walk.anim)
502
503 if (animVersion != 0)
504 error("load3DOCelFile: Unsupported animation file version");
505 if (animType != 1)
506 error("load3DOCelFile: Only single CCB animation files are supported");
507 break;
508
509 case MKTAG('C', 'C', 'B', ' '):
510 // CEL control block
511 assert(dataSize >= 72);
512
513 if (ccbFound)
514 error("load3DOCelFile: multiple CCB chunks not supported");
515
516 ccbFound = true;
517 ccbVersion = stream.readUint32BE();
518 ccbFlags = stream.readUint32BE();
519 stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
520 stream.skip(8 * 4); // skip over 8 offset fields
521 ccbPPMP0 = stream.readUint16BE();
522 ccbPPMP1 = stream.readUint16BE();
523 ccbPRE0 = stream.readUint32BE();
524 ccbPRE1 = stream.readUint32BE();
525 ccbWidth = stream.readUint32BE();
526 ccbHeight = stream.readUint32BE();
527
528 if (ccbVersion != 0)
529 error("load3DOCelFile: Unsupported CCB version");
530
531 if (ccbFlags & 0x200) // bit 9
532 ccbFlags_compressed = true;
533
534 // bit 5 of ccbFlags defines how RGB-black (0, 0, 0) will get treated
535 // = false -> RGB-black is treated as transparent
536 // = true -> RGB-black is treated as actual black
537 // atm we are always treating it as transparent
538 // it seems this bit is not set for any data of Sherlock Holmes
539
540 // PRE0 first 3 bits define how many bits per encoded pixel are used
541 ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
542 if (!ccbPRE0_bitsPerPixel)
543 error("load3DOCelFile: Invalid CCB PRE0 bits per pixel");
544
545 ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1;
546 ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1;
547 assert(ccbPRE0_height == ccbHeight);
548 assert(ccbPRE1_width == ccbWidth);
549 break;
550
551 case MKTAG('P', 'L', 'U', 'T'):
552 // pixel lookup table
553 // optional, not required for at least 16-bit pixel data
554 assert(dataSize >= 6);
555
556 if (!ccbFound)
557 error("load3DOCelFile: PLUT chunk found without CCB chunk");
558 if (plutFound)
559 error("load3DOCelFile: multiple PLUT chunks currently not supported");
560
561 plutFound = true;
562 plutCount = stream.readUint32BE();
563 // table follows, each entry is 16bit RGB555
564 assert(dataSize >= 4 + (plutCount * 2)); // security check
565 assert(plutCount <= 256); // security check
566
567 assert(plutCount <= 32); // PLUT should never contain more than 32 entries
568
569 for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) {
570 plutRGBlookupTable.pixelColor[plutColorNr] = stream.readUint16BE();
571 }
572
573 if (ccbPRE0_bitsPerPixel == 8) {
574 // In case we are getting 8-bits per pixel, we calculate the shades accordingly
575 // I'm not 100% sure if the calculation is correct. It's difficult to find information
576 // on this topic.
577 // The map uses this type of cel
578 assert(plutCount == 32); // And we expect 32 entries inside PLUT chunk
579
580 uint16 plutColorRGB = 0;
581 for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) {
582 plutColorRGB = plutRGBlookupTable.pixelColor[plutColorNr];
583
584 // Extract RGB values
585 byte plutColorRed = (plutColorRGB >> 10) & 0x1F;
586 byte plutColorGreen = (plutColorRGB >> 5) & 0x1F;
587 byte plutColorBlue = plutColorRGB & 0x1F;
588
589 byte shadeMultiplier = 2;
590 for (uint32 plutShadeNr = 1; plutShadeNr < 8; plutShadeNr++) {
591 uint16 shadedColorRGB;
592 byte shadedColorRed = (plutColorRed * shadeMultiplier) >> 3;
593 byte shadedColorGreen = (plutColorGreen * shadeMultiplier) >> 3;
594 byte shadedColorBlue = (plutColorBlue * shadeMultiplier) >> 3;
595
596 shadedColorRed = CLIP<byte>(shadedColorRed, 0, 0x1F);
597 shadedColorGreen = CLIP<byte>(shadedColorGreen, 0, 0x1F);
598 shadedColorBlue = CLIP<byte>(shadedColorBlue, 0, 0x1F);
599 shadedColorRGB = (shadedColorRed << 10) | (shadedColorGreen << 5) | shadedColorBlue;
600
601 plutRGBlookupTable.pixelColor[plutColorNr + (plutShadeNr << 5)] = shadedColorRGB;
602 shadeMultiplier++;
603 }
604 }
605 }
606 break;
607
608 case MKTAG('X', 'T', 'R', 'A'):
609 // Unknown contents, occurs right before PDAT
610 break;
611
612 case MKTAG('P', 'D', 'A', 'T'): {
613 // pixel data for one frame
614 // may be compressed or uncompressed pixels
615
616 if (ccbPRE0_bitsPerPixel != 16) {
617 // We require a pixel lookup table in case bits-per-pixel is lower than 16
618 if (!plutFound)
619 error("load3DOCelFile: bits per pixel < 16, but no pixel lookup table was found");
620 } else {
621 // But we don't like it in case bits-per-pixel is 16 and we find one
622 if (plutFound)
623 error("load3DOCelFile: bits per pixel == 16, but pixel lookup table was found as well");
624 }
625 // read data into memory
626 chunkDataPtr = new byte[dataSize];
627
628 stream.read(chunkDataPtr, dataSize);
629
630 // Set up frame
631 ImageFrame imageFrame;
632
633 imageFrame._decoded = true;
634 imageFrame._width = ccbWidth;
635 imageFrame._height = ccbHeight;
636 imageFrame._paletteBase = 0;
637 imageFrame._offset.x = 0;
638 imageFrame._offset.y = 0;
639 imageFrame._rleEncoded = ccbFlags_compressed;
640 imageFrame._size = 0;
641
642 // Decompress/copy this frame
643 if (!plutFound) {
644 decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, NULL);
645 } else {
646 decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, &plutRGBlookupTable);
647 }
648
649 delete[] chunkDataPtr;
650
651 push_back(imageFrame);
652 break;
653 }
654
655 case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
656 // unknown contents
657 break;
658
659 default:
660 error("Unsupported '%s' chunk in 3DO cel file", tag2str(chunkTag));
661 }
662
663 // Seek to end of chunk
664 stream.seek(chunkStartPos + chunkSize);
665 }
666
667 // Warning below being used to silence unused variable warnings for now
668 warning("TODO: Remove %d %d %d", animFrameCount, ccbPPMP0, ccbPPMP1);
669 }
670
671 // Reads 3DO .cel data (room file format)
load3DOCelRoomData(Common::SeekableReadStream & stream)672 void ImageFile3DO::load3DOCelRoomData(Common::SeekableReadStream &stream) {
673 uint32 streamLeft = stream.size() - stream.pos();
674 uint16 roomDataHeader_size = 0;
675 byte roomDataHeader_offsetX = 0;
676 byte roomDataHeader_offsetY = 0;
677
678 // CCB chunk (cel control block)
679 uint32 ccbFlags = 0;
680 bool ccbFlags_compressed = false;
681 uint16 ccbPPMP0 = 0;
682 uint16 ccbPPMP1 = 0;
683 uint32 ccbPRE0 = 0;
684 uint16 ccbPRE0_height = 0;
685 byte ccbPRE0_bitsPerPixel = 0;
686 uint32 ccbPRE1 = 0;
687 uint16 ccbPRE1_width = 0;
688 uint32 ccbWidth = 0;
689 uint32 ccbHeight = 0;
690 // cel data
691 uint32 celDataSize = 0;
692
693 while (streamLeft > 0) {
694 // We expect at least 8 bytes basic header
695 if (streamLeft < 8)
696 error("load3DOCelRoomData: expected room data header, not enough bytes");
697
698 // 3DO sherlock holmes room data header
699 stream.skip(4); // Possibly UINT16 width, UINT16 height?!?!
700 roomDataHeader_size = stream.readUint16BE();
701 roomDataHeader_offsetX = stream.readByte();
702 roomDataHeader_offsetY = stream.readByte();
703 streamLeft -= 8;
704
705 // We expect the header size specified in the basic header to be at least a raw CCB
706 if (roomDataHeader_size < 68)
707 error("load3DOCelRoomData: header size is too small");
708 // Check, that enough bytes for CCB are available
709 if (streamLeft < 68)
710 error("load3DOCelRoomData: expected raw cel control block, not enough bytes");
711
712 // 3DO raw cel control block
713 ccbFlags = stream.readUint32BE();
714 stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
715 stream.skip(8 * 4); // skip over 8 offset fields
716 ccbPPMP0 = stream.readUint16BE();
717 ccbPPMP1 = stream.readUint16BE();
718 ccbPRE0 = stream.readUint32BE();
719 ccbPRE1 = stream.readUint32BE();
720 ccbWidth = stream.readUint32BE();
721 ccbHeight = stream.readUint32BE();
722
723 if (ccbFlags & 0x200) // bit 9
724 ccbFlags_compressed = true;
725
726 // PRE0 first 3 bits define how many bits per encoded pixel are used
727 ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
728 if (!ccbPRE0_bitsPerPixel)
729 error("load3DOCelRoomData: Invalid CCB PRE0 bits per pixel");
730
731 ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1;
732 ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1;
733 assert(ccbPRE0_height == ccbHeight);
734 assert(ccbPRE1_width == ccbWidth);
735
736 if (ccbPRE0_bitsPerPixel != 16) {
737 // We currently support 16-bits per pixel in here
738 error("load3DOCelRoomData: bits per pixel < 16?!?!?");
739 }
740 // Got the raw CCB
741 streamLeft -= 68;
742
743 // cel data follows
744 // size field does not include the 8 byte header
745 celDataSize = roomDataHeader_size - 68;
746
747 if (streamLeft < celDataSize)
748 error("load3DOCelRoomData: expected cel data, not enough bytes");
749
750 // read data into memory
751 byte *celDataPtr = new byte[celDataSize];
752
753 stream.read(celDataPtr, celDataSize);
754 streamLeft -= celDataSize;
755
756 // Set up frame
757 {
758 ImageFrame imageFrame;
759
760 imageFrame._decoded = true;
761 imageFrame._width = ccbWidth;
762 imageFrame._height = ccbHeight;
763 imageFrame._paletteBase = 0;
764 imageFrame._offset.x = roomDataHeader_offsetX;
765 imageFrame._offset.y = roomDataHeader_offsetY;
766 imageFrame._rleEncoded = ccbFlags_compressed;
767 imageFrame._size = 0;
768
769 // Decompress/copy this frame
770 decompress3DOCelFrame(imageFrame, celDataPtr, celDataSize, ccbPRE0_bitsPerPixel, NULL);
771
772 delete[] celDataPtr;
773
774 push_back(imageFrame);
775 }
776 }
777
778 // Suppress compiler warning
779 warning("ccbPPMP0 = %d, ccbPPMP1 = %d", ccbPPMP0, ccbPPMP1);
780 }
781
782 static uint16 imagefile3DO_cel_bitsMask[17] = {
783 0,
784 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
785 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
786 };
787
788 // gets [bitCount] bits from dataPtr, going from MSB to LSB
celGetBits(const byte * & dataPtr,byte bitCount,byte & dataBitsLeft)789 inline uint16 ImageFile3DO::celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft) {
790 byte resultBitsLeft = bitCount;
791 uint16 result = 0;
792 byte currentByte = *dataPtr;
793
794 // Get bits of current byte
795 while (resultBitsLeft) {
796 if (resultBitsLeft < dataBitsLeft) {
797 // we need less than we have left
798 result |= (currentByte >> (dataBitsLeft - resultBitsLeft)) & imagefile3DO_cel_bitsMask[resultBitsLeft];
799 dataBitsLeft -= resultBitsLeft;
800 resultBitsLeft = 0;
801
802 } else {
803 // we need as much as we have left or more
804 resultBitsLeft -= dataBitsLeft;
805 result |= (currentByte & imagefile3DO_cel_bitsMask[dataBitsLeft]) << resultBitsLeft;
806
807 // Go to next byte
808 dataPtr++;
809 dataBitsLeft = 8;
810 if (resultBitsLeft) {
811 currentByte = *dataPtr;
812 }
813 }
814 }
815 return result;
816 }
817
818 // decompress/copy 3DO cel data
decompress3DOCelFrame(ImageFrame & frame,const byte * dataPtr,uint32 dataSize,byte bitsPerPixel,ImageFile3DOPixelLookupTable * pixelLookupTable)819 void ImageFile3DO::decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable) {
820 frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
821 uint16 *dest = (uint16 *)frame._frame.getPixels();
822 Common::fill(dest, dest + frame._width * frame._height, 0);
823
824 int frameHeightLeft = frame._height;
825 int frameWidthLeft = frame._width;
826 uint16 pixelCount = 0;
827 uint16 pixel = 0;
828
829 const byte *srcLineStart = dataPtr;
830 const byte *srcLineData = dataPtr;
831 byte srcLineDataBitsLeft = 0;
832 uint16 lineDWordSize = 0;
833 uint16 lineByteSize = 0;
834
835 if (bitsPerPixel == 16) {
836 // Must not use pixel lookup table on 16-bits-per-pixel data
837 assert(!pixelLookupTable);
838 }
839
840 if (frame._rleEncoded) {
841 // compressed
842 byte compressionType = 0;
843 byte compressionPixels = 0;
844
845 while (frameHeightLeft > 0) {
846 frameWidthLeft = frame._width;
847
848 if (bitsPerPixel >= 8) {
849 lineDWordSize = READ_BE_UINT16(srcLineStart);
850 srcLineData = srcLineStart + 2;
851 } else {
852 lineDWordSize = *srcLineStart;
853 srcLineData = srcLineStart + 1;
854 }
855 srcLineDataBitsLeft = 8;
856
857 lineDWordSize += 2;
858 lineByteSize = lineDWordSize * 4; // calculate compressed data size in bytes for current line
859
860 // debug
861 //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize);
862
863 while (frameWidthLeft > 0) {
864 // get 2 bits -> compressionType
865 // get 6 bits -> pixel count (0 = 1 pixel)
866 compressionType = celGetBits(srcLineData, 2, srcLineDataBitsLeft);
867 // 6 bits == length (0 = 1 pixel)
868 compressionPixels = celGetBits(srcLineData, 6, srcLineDataBitsLeft) + 1;
869
870 if (!compressionType) // end of line
871 break;
872
873 switch(compressionType) {
874 case 1: // simple copy
875 for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
876 pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
877 if (pixelLookupTable) {
878 pixel = pixelLookupTable->pixelColor[pixel];
879 }
880 *dest++ = convertPixel(pixel);
881 }
882 break;
883 case 2: // transparent
884 for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
885 *dest++ = 0;
886 }
887 break;
888 case 3: // duplicate pixels
889 pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
890 if (pixelLookupTable) {
891 pixel = pixelLookupTable->pixelColor[pixel];
892 }
893 pixel = convertPixel(pixel);
894 for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
895 *dest++ = pixel;
896 }
897 break;
898 default:
899 break;
900 }
901 frameWidthLeft -= compressionPixels;
902 }
903
904 assert(frameWidthLeft >= 0);
905
906 if (frameWidthLeft > 0) {
907 // still pixels left? skip them
908 dest += frameWidthLeft;
909 }
910
911 frameHeightLeft--;
912
913 // Seek to next line start
914 srcLineStart += lineByteSize;
915 }
916 } else {
917 // uncompressed
918 srcLineDataBitsLeft = 8;
919 lineDWordSize = ((frame._width * bitsPerPixel) + 31) >> 5;
920 lineByteSize = lineDWordSize * 4;
921 uint32 totalExpectedSize = lineByteSize * frame._height;
922
923 assert(totalExpectedSize <= dataSize); // security check
924
925 while (frameHeightLeft > 0) {
926 srcLineData = srcLineStart;
927 frameWidthLeft = frame._width;
928
929 while (frameWidthLeft > 0) {
930 pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
931 if (pixelLookupTable) {
932 pixel = pixelLookupTable->pixelColor[pixel];
933 }
934 *dest++ = convertPixel(pixel);
935
936 frameWidthLeft--;
937 }
938 frameHeightLeft--;
939
940 // Seek to next line start
941 srcLineStart += lineByteSize;
942 }
943 }
944 }
945
946 // Reads Sherlock Holmes 3DO font file
loadFont(Common::SeekableReadStream & stream)947 void ImageFile3DO::loadFont(Common::SeekableReadStream &stream) {
948 uint32 streamSize = stream.size();
949 uint32 header_offsetWidthTable = 0;
950 uint32 header_offsetBitsTable = 0;
951 uint32 header_fontHeight = 0;
952 uint32 header_bytesPerLine = 0;
953 uint32 header_maxChar = 0;
954 uint32 header_charCount = 0;
955
956 byte *widthTablePtr = NULL;
957 uint32 bitsTableSize = 0;
958 byte *bitsTablePtr = NULL;
959
960 stream.skip(2); // Unknown bytes
961 stream.skip(2); // Unknown bytes (0x000E)
962 header_offsetWidthTable = stream.readUint32BE();
963 header_offsetBitsTable = stream.readUint32BE();
964 stream.skip(4); // Unknown bytes (0x00000004)
965 header_fontHeight = stream.readUint32BE();
966 header_bytesPerLine = stream.readUint32BE();
967 header_maxChar = stream.readUint32BE();
968
969 assert(header_maxChar <= 255);
970 header_charCount = header_maxChar + 1;
971
972 // Allocate memory for width table
973 widthTablePtr = new byte[header_charCount];
974
975 stream.seek(header_offsetWidthTable);
976 stream.read(widthTablePtr, header_charCount);
977
978 // Allocate memory for the bits
979 assert(header_offsetBitsTable < streamSize); // Security check
980 bitsTableSize = streamSize - header_offsetBitsTable;
981 bitsTablePtr = new byte[bitsTableSize];
982 stream.read(bitsTablePtr, bitsTableSize);
983
984 // Now extract all characters
985 uint16 curChar = 0;
986 const byte *curBitsLinePtr = bitsTablePtr;
987 const byte *curBitsPtr = NULL;
988 byte curBitsLeft = 0;
989 uint32 curCharHeightLeft = 0;
990 uint32 curCharWidthLeft = 0;
991 byte curBits = 0;
992 byte curBitsReversed = 0;
993 byte curPosX = 0;
994
995 assert(bitsTableSize >= (header_maxChar * header_fontHeight * header_bytesPerLine)); // Security
996
997 // first frame needs to be "!" (33 decimal)
998 // our font code is subtracting 33 from the actual character code
999 curBitsLinePtr += (33 * (header_fontHeight * header_bytesPerLine));
1000
1001 for (curChar = 33; curChar < header_charCount; curChar++) {
1002 // create frame
1003 ImageFrame imageFrame;
1004
1005 imageFrame._decoded = true;
1006 imageFrame._width = widthTablePtr[curChar];
1007 imageFrame._height = header_fontHeight;
1008 imageFrame._paletteBase = 0;
1009 imageFrame._offset.x = 0;
1010 imageFrame._offset.y = 0;
1011 imageFrame._rleEncoded = false;
1012 imageFrame._size = 0;
1013
1014 // Extract pixels
1015 imageFrame._frame.create(imageFrame._width, imageFrame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
1016 uint16 *dest = (uint16 *)imageFrame._frame.getPixels();
1017 Common::fill(dest, dest + imageFrame._width * imageFrame._height, 0);
1018
1019 curCharHeightLeft = header_fontHeight;
1020 while (curCharHeightLeft) {
1021 curCharWidthLeft = widthTablePtr[curChar];
1022 curBitsPtr = curBitsLinePtr;
1023 curBitsLeft = 8;
1024 curPosX = 0;
1025
1026 while (curCharWidthLeft) {
1027 if (!(curPosX & 1)) {
1028 curBits = *curBitsPtr >> 4;
1029 } else {
1030 curBits = *curBitsPtr & 0x0F;
1031 curBitsPtr++;
1032 }
1033 // doing this properly is complicated
1034 // the 3DO has built-in anti-aliasing
1035 // this here at least results in somewhat readable text
1036 // TODO: make it better
1037 if (curBits) {
1038 curBitsReversed = (curBits >> 3) | ((curBits & 0x04) >> 1) | ((curBits & 0x02) << 1) | ((curBits & 0x01) << 3);
1039 curBits = 20 - curBits;
1040 }
1041
1042 byte curIntensity = curBits;
1043 *dest = (curIntensity << 11) | (curIntensity << 6) | curIntensity;
1044 dest++;
1045
1046 curCharWidthLeft--;
1047 curPosX++;
1048 }
1049
1050 curCharHeightLeft--;
1051 curBitsLinePtr += header_bytesPerLine;
1052 }
1053
1054 push_back(imageFrame);
1055 }
1056
1057 // Warning below being used to silence unused variable warnings for now
1058 warning("TODO: Remove %d %d", curBitsLeft, curBitsReversed);
1059
1060 delete[] bitsTablePtr;
1061 delete[] widthTablePtr;
1062 }
1063
1064 /*----------------------------------------------------------------*/
1065
StreamingImageFile()1066 StreamingImageFile::StreamingImageFile() {
1067 _frameNumber = 0;
1068 _stream = nullptr;
1069 _flags = 0;
1070 _scaleVal = 0;
1071 _zPlacement = 0;
1072 _compressed = false;
1073 _active = false;
1074 }
1075
~StreamingImageFile()1076 StreamingImageFile::~StreamingImageFile() {
1077 close();
1078 }
1079
load(Common::SeekableReadStream * stream,bool compressed)1080 void StreamingImageFile::load(Common::SeekableReadStream *stream, bool compressed) {
1081 _stream = stream;
1082 _compressed = compressed;
1083 _frameNumber = -1;
1084 _active = true;
1085 }
1086
close()1087 void StreamingImageFile::close() {
1088 delete _stream;
1089 _stream = nullptr;
1090 _frameNumber = -1;
1091 _active = false;
1092 _imageFrame._frame.free();
1093 }
1094
getNextFrame()1095 bool StreamingImageFile::getNextFrame() {
1096 // Don't proceed if we're already at the end of the stream
1097 assert(_stream);
1098 if (_stream->pos() >= _stream->size()) {
1099 _active = false;
1100 return false;
1101 }
1102
1103 // Increment frame number
1104 ++_frameNumber;
1105
1106 // If necessary, decompress the next frame
1107 Common::SeekableReadStream *frameStream = _stream;
1108 if (_compressed) {
1109 uint32 inSize = _stream->readUint32LE();
1110 Resources::decompressLZ(*_stream, _buffer, STREAMING_BUFFER_SIZE, inSize);
1111 frameStream = new Common::MemoryReadStream(_buffer, 11, DisposeAfterUse::NO);
1112 }
1113
1114 // Load the data for the frame
1115 _imageFrame._width = frameStream->readUint16LE() + 1;
1116 _imageFrame._height = frameStream->readUint16LE() + 1;
1117 _imageFrame._paletteBase = frameStream->readByte();
1118 _imageFrame._rleEncoded = frameStream->readByte() == 1;
1119 _imageFrame._offset.x = frameStream->readByte();
1120 _imageFrame._offset.y = frameStream->readByte();
1121 _imageFrame._size = frameStream->readUint16LE() - 11;
1122 _imageFrame._rleMarker = frameStream->readByte();
1123
1124 // Free the previous frame
1125 _imageFrame._frame.free();
1126
1127 // Decode the frame
1128 if (_compressed) {
1129 delete frameStream;
1130 _imageFrame.decompressFrame(_buffer + 11, true);
1131 } else {
1132 byte *data = new byte[_imageFrame._size];
1133 _stream->read(data, _imageFrame._size);
1134 _imageFrame.decompressFrame(_buffer + 11, true);
1135 delete[] data;
1136 }
1137
1138 return true;
1139 }
1140
1141 } // End of namespace Sherlock
1142