/*
	images.c

	Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
	and the "Aleph One" developers.
 
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	This license is contained in the file "COPYING",
	which is included with this source code; it is available online at
	http://www.gnu.org/licenses/gpl.html

	Thursday, July 20, 1995 3:29:30 PM- rdm created.

Feb. 4, 2000 (Loren Petrich):
	Changed halt() to assert(false) for better debugging

Feb. 5, 2000 (Loren Petrich):
	Better handling of case of no scenario-file resource fork

Aug 21, 2000 (Loren Petrich):
	Added object-oriented file handling
	
	LoadedResource handles are assumed to always be locked,
	and HLock() and HUnlock() have been suppressed for that reason.

Jul 6, 2001 (Loren Petrich):
	Added Thomas Herzog's changes for loading Win32-version image chunks

Jan 25, 2002 (Br'fin (Jeremy Parsons)):
	Added TARGET_API_MAC_CARBON for Quicktime.h

Jul 31, 2002 (Loren Petrich)
	Added text-resource access in analogy with others' image- and sound-resource access;
	this is for supporting the M2-Win95 file format
 */

#include "cseries.h"
#include "FileHandler.h"

#include <stdlib.h>

#if defined(mac)
#if defined(EXPLICIT_CARBON_HEADER)
#  include <quicktime/Quicktime.h>
#else
#  include <Movies.h>
#endif
#endif

#include "interface.h"
#include "shell.h"
#include "images.h"
#include "screen.h"
#include "wad.h"
#include "screen_drawing.h"
#include "Logging.h"

#include "render.h"
#include "OGL_Render.h"
#include "OGL_Blitter.h"


// Constants
enum {
	_images_file_delta16= 1000,
	_images_file_delta32= 2000,
	_scenario_file_delta16= 10000,
	_scenario_file_delta32= 20000
};

// Structure for open image file
class image_file_t {
public:
	image_file_t() {}
	~image_file_t() {close_file();}

	bool open_file(FileSpecifier &file);
	void close_file(void);
	bool is_open(void);

	int determine_pict_resource_id(int base_id, int delta16, int delta32);

	bool has_pict(int id);
	bool has_clut(int id);

	bool get_pict(int id, LoadedResource &rsrc);
	bool get_clut(int id, LoadedResource &rsrc);
	bool get_snd(int id, LoadedResource &rsrc);
	bool get_text(int id, LoadedResource &rsrc);

private:
	bool has_rsrc(uint32 rsrc_type, uint32 wad_type, int id);
	bool get_rsrc(uint32 rsrc_type, uint32 wad_type, int id, LoadedResource &rsrc);

	bool make_rsrc_from_pict(void *data, size_t length, LoadedResource &rsrc, void *clut_data, size_t clut_length);
	bool make_rsrc_from_clut(void *data, size_t length, LoadedResource &rsrc);

	OpenedResourceFile rsrc_file;
	OpenedFile wad_file;
	wad_header wad_hdr;
};

#ifdef mac
// M2/Win picture structures (do not use in portable code)
typedef struct pict_head {
	screen_rectangle bounds;	/* screen_rectangle from screen_drawing.h */
	short depth;				/* 8 or 16 */
								/* pixel data follows here */
} pict_head;

typedef struct clut_record {
	short count;
	short unknown;
	short id;
	rgb_color colors[256];		/* rgb_color from cscluts.h */
} clut_record;
#endif

// Global variables
static image_file_t ImagesFile;
static image_file_t ScenarioFile;

// Prototypes
static void shutdown_images_handler(void);
static void draw_picture(LoadedResource &PictRsrc);


#include <SDL_endian.h>

#ifdef HAVE_SDL_IMAGE_H
#include <SDL_image.h>
#endif

#include "byte_swapping.h"
#include "screen_drawing.h"


// From screen_sdl.cpp
extern short interface_bit_depth;

// From screen_drawing_sdl.cpp
extern bool draw_clip_rect_active;
extern screen_rectangle draw_clip_rect;


/*
 *  Uncompress picture data, returns size of compressed image data that was read
 */

// Uncompress (and endian-correct) scan line compressed by PackBits RLE algorithm
template <class T>
static const uint8 *unpack_bits(const uint8 *src, int row_bytes, T *dst)
{
	// Read source count
	int src_count;
	if (row_bytes > 250) {
		src_count = (src[0] << 8) | src[1];
		src += 2;
	} else
		src_count = *src++;

	while (src_count > 0) {

		// Read flag/count byte
		int c = (int8)*src++;
		src_count--;
		if (c < 0) {

			// RLE compressed run
			int size = -c + 1;
			T data;
			if (sizeof(T) == 1) {
				data = *src++;
				src_count--;
			} else {
				data = (src[0] << 8) | src[1];
				src += 2;
				src_count -= 2;
			}
			for (int i=0; i<size; i++)
				*dst++ = data;

		} else {

			// Uncompressed run
			int size = c + 1;
			for (int i=0; i<size; i++) {
				T data;
				if (sizeof(T) == 1) {
					data = *src++;
					src_count--;
				} else {
					data = (src[0] << 8) | src[1];
					src += 2;
					src_count -= 2;
				}
				*dst++ = data;
			}
		}
	}
	return src;
}

// 8-bit picture, one scan line at a time
static int uncompress_rle8(const uint8 *src, int row_bytes, uint8 *dst, int dst_pitch, int height)
{
	const uint8 *start = src;
	for (int y=0; y<height; y++) {
		src = unpack_bits(src, row_bytes, dst);
		dst += dst_pitch;
	}
	return static_cast<int>(src - start);
}

// 16-bit picture, one scan line at a time, 16-bit chunks
static int uncompress_rle16(const uint8 *src, int row_bytes, uint8 *dst, int dst_pitch, int height)
{
	const uint8 *start = src;
	for (int y=0; y<height; y++) {
		src = unpack_bits(src, row_bytes, (uint16 *)dst);
		dst += dst_pitch;
	}
	return static_cast<int>(src - start);
}

static void copy_component_into_surface(const uint8 *src, uint8 *dst, int count, int component)
{
#ifdef ALEPHONE_LITTLE_ENDIAN
	dst += 2 - component;
#else
	dst += component + 1;
#endif
	while (count--) {
		*dst = *src++;
		dst += 4;
	}
}

// 32-bit picture, one scan line, one component at a time
static int uncompress_rle32(const uint8 *src, int row_bytes, uint8 *dst, int dst_pitch, int height)
{
	uint8 *tmp = (uint8 *)malloc(row_bytes);
	if (tmp == NULL)
		return -1;

	const uint8 *start = src;

	int width = row_bytes / 4; 
	for (int y=0; y<height; y++) {
		src = unpack_bits(src, row_bytes, tmp);

		// "tmp" now contains "width" bytes of red, followed by "width"
		// bytes of green and "width" bytes of blue, so we have to copy them
		// into the surface in the right order
		copy_component_into_surface(tmp, dst, width, 0);
		copy_component_into_surface(tmp + width, dst, width, 1);
		copy_component_into_surface(tmp + width * 2, dst, width, 2);

		dst += dst_pitch;
	}

	free(tmp);

	return static_cast<int>(src - start);
}

static int uncompress_picture(const uint8 *src, int row_bytes, uint8 *dst, int dst_pitch, int depth, int height, int pack_type)
{
	// Depths <8 have to be color expanded to depth 8 after uncompressing,
	// so we uncompress into a temporary buffer
	uint8 *orig_dst = dst;
	int orig_dst_pitch = dst_pitch;
	if (depth < 8) {
		dst = (uint8 *)malloc(row_bytes * height);
		dst_pitch = row_bytes;
		if (dst == NULL)
			return -1;
	}

	int data_size = 0;

	if (row_bytes < 8) {

		// Uncompressed data
		const uint8 *p = src;
		uint8 *q = dst;
		for (int y=0; y<height; y++) {
			memcpy(q, p, MIN(row_bytes, dst_pitch));
			p += row_bytes;
			q += dst_pitch;
		}
		data_size = row_bytes * height;

	} else {

		// Compressed data
		if (depth <= 8) {

			// Indexed color
			if (pack_type == 1)
				goto no_packing;
			data_size = uncompress_rle8(src, row_bytes, dst, dst_pitch, height);

		} else {

			// Direct color
			if (pack_type == 0) {
				if (depth == 16)
					pack_type = 3;
				else if (depth == 32)
					pack_type = 4;

			}
			switch (pack_type) {
				case 1: {	// No packing
no_packing:			const uint8 *p = src;
					uint8 *q = dst;
					for (int y=0; y<height; y++) {
						memcpy(q, p, MIN(row_bytes, dst_pitch));
						p += row_bytes;
						q += dst_pitch;
					}
					data_size = row_bytes * height;
					if (depth == 16)
						byte_swap_memory(dst, _2byte, dst_pitch * height / 2);
					else if (depth == 32)
						byte_swap_memory(dst, _4byte, dst_pitch * height / 4);
					break;
				}
				case 3:		// Run-length encoding by 16-bit chunks
					data_size = uncompress_rle16(src, row_bytes, dst, dst_pitch, height);
					break;
				case 4:		// Run-length encoding one component at a time
					data_size = uncompress_rle32(src, row_bytes, dst, dst_pitch, height);
					break;
				default:
					fprintf(stderr, "Unimplemented packing type %d (depth %d) in PICT resource\n", pack_type, depth);
					data_size = -1;
					break;
			}
		}
	}

	// Color expansion 1/2/4->8 bits
	if (depth < 8) {
		const uint8 *p = dst;
		uint8 *q = orig_dst;

		// Source and destination may have different alignment restrictions,
		// don't run off the right of either
		int x_max = row_bytes;
		while (x_max * 8 / depth > orig_dst_pitch)
			x_max--;

		switch (depth) {
			case 1:
				for (int y=0; y<height; y++) {
					for (int x=0; x<x_max; x++) {
						uint8 b = p[x];
						q[x*8+0] = (b & 0x80) ? 0x01 : 0x00;
						q[x*8+1] = (b & 0x40) ? 0x01 : 0x00;
						q[x*8+2] = (b & 0x20) ? 0x01 : 0x00;
						q[x*8+3] = (b & 0x10) ? 0x01 : 0x00;
						q[x*8+4] = (b & 0x08) ? 0x01 : 0x00;
						q[x*8+5] = (b & 0x04) ? 0x01 : 0x00;
						q[x*8+6] = (b & 0x02) ? 0x01 : 0x00;
						q[x*8+7] = (b & 0x01) ? 0x01 : 0x00;
					}
					p += row_bytes;
					q += orig_dst_pitch;
				}
				break;
			case 2:
				for (int y=0; y<height; y++) {
					for (int x=0; x<x_max; x++) {
						uint8 b = p[x];
						q[x*4+0] = (b >> 6) & 0x03;
						q[x*4+1] = (b >> 4) & 0x03;
						q[x*4+2] = (b >> 2) & 0x03;
						q[x*4+3] = b & 0x03;
					}
					p += row_bytes;
					q += orig_dst_pitch;
				}
				break;
			case 4:
				for (int y=0; y<height; y++) {
					for (int x=0; x<x_max; x++) {
						uint8 b = p[x];
						q[x*2+0] = (b >> 4) & 0x0f;
						q[x*2+1] = b & 0x0f;
					}
					p += row_bytes;
					q += orig_dst_pitch;
				}
				break;
		}
		free(dst);
	}

	return data_size;
}

int get_pict_header_width(LoadedResource &rsrc)
{
	SDL_RWops *p = SDL_RWFromMem(rsrc.GetPointer(), (int) rsrc.GetLength());
	if (p)
	{
		SDL_RWseek(p, 8, SEEK_CUR);
		int width = SDL_ReadBE16(p);
		SDL_RWclose(p);
		return width;
	}
	return -1;
}

/*
 *  Convert picture resource to SDL surface
 */

SDL_Surface *picture_to_surface(LoadedResource &rsrc)
{
	if (!rsrc.IsLoaded())
		return NULL;

	SDL_Surface *s = NULL;

	// Open stream to picture resource
	SDL_RWops *p = SDL_RWFromMem(rsrc.GetPointer(), (int)rsrc.GetLength());
	if (p == NULL)
		return NULL;
	SDL_RWseek(p, 2 /* 6 */, SEEK_CUR);		// picSize/top/left
	int top = SDL_ReadBE16(p), left = SDL_ReadBE16(p), bottom = SDL_ReadBE16(p), right = SDL_ReadBE16(p);
	int pic_height = /* SDL_ReadBE16(p) */ bottom - top;
	int pic_width = /* SDL_ReadBE16(p) */ right - left;
	//printf("pic_width %d, pic_height %d\n", pic_width, pic_height);

	// Read and parse picture opcodes
	bool done = false;
	while (!done) {
		uint16 opcode = SDL_ReadBE16(p);
		//printf("%04x\n", opcode);
		switch (opcode) {

			case 0x0000:	// NOP
			case 0x0011:	// VersionOp
			case 0x001c:	// HiliteMode
			case 0x001e:	// DefHilite
			case 0x0038:	// FrameSameRect
			case 0x0039:	// PaintSameRect
			case 0x003a:	// EraseSameRect
			case 0x003b:	// InvertSameRect
			case 0x003c:	// FillSameRect
			case 0x02ff:	// Version
				break;

			case 0x00ff:	// OpEndPic
				done = true;
				break;

			case 0x0001: {	// Clipping region
				uint16 size = SDL_ReadBE16(p);
				if (size & 1)
					size++;
				SDL_RWseek(p, size - 2, SEEK_CUR);
				break;
			}

			case 0x0003:	// TxFont
			case 0x0004:	// TxFace
			case 0x0005:	// TxMode
			case 0x0008:	// PnMode
			case 0x000d:	// TxSize
			case 0x0015:	// PnLocHFrac
			case 0x0016:	// ChExtra
			case 0x0023:	// ShortLineFrom
			case 0x00a0:	// ShortComment
				SDL_RWseek(p, 2, SEEK_CUR);
				break;

			case 0x0006:	// SpExtra
			case 0x0007:	// PnSize
			case 0x000b:	// OvSize
			case 0x000c:	// Origin
			case 0x000e:	// FgColor
			case 0x000f:	// BgColor
			case 0x0021:	// LineFrom
				SDL_RWseek(p, 4, SEEK_CUR);
				break;

			case 0x001a:	// RGBFgCol
			case 0x001b:	// RGBBkCol
			case 0x001d:	// HiliteColor
			case 0x001f:	// OpColor
			case 0x0022:	// ShortLine
				SDL_RWseek(p, 6, SEEK_CUR);
				break;

			case 0x0002:	// BkPat
			case 0x0009:	// PnPat
			case 0x000a:	// FillPat
			case 0x0010:	// TxRatio
			case 0x0020:	// Line
			case 0x0030:	// FrameRect
			case 0x0031:	// PaintRect
			case 0x0032:	// EraseRect
			case 0x0033:	// InvertRect
			case 0x0034:	// FillRect
				SDL_RWseek(p, 8, SEEK_CUR);
				break;

			case 0x0c00:	// HeaderOp
				SDL_RWseek(p, 24, SEEK_CUR);
				break;

			case 0x00a1: {	// LongComment
				SDL_RWseek(p, 2, SEEK_CUR);
				int size = SDL_ReadBE16(p);
				if (size & 1)
					size++;
				SDL_RWseek(p, size, SEEK_CUR);
				break;
			}

			case 0x0098:	// Packed CopyBits
			case 0x0099:	// Packed CopyBits with clipping region
			case 0x009a:	// Direct CopyBits
			case 0x009b: {	// Direct CopyBits with clipping region
                                        // 1. PixMap
                          if (opcode == 0x009a || opcode == 0x009b)
                            SDL_RWseek(p, 4, SEEK_CUR);		// pmBaseAddr
                          uint16 row_bytes = SDL_ReadBE16(p);	// the upper 2 bits are flags
                                                                //printf(" row_bytes %04x\n", row_bytes);
                          bool is_pixmap = ((row_bytes & 0x8000) != 0);
                          row_bytes &= 0x3fff;
                          uint16 top = SDL_ReadBE16(p);
                          uint16 left = SDL_ReadBE16(p);
                          uint16 height = SDL_ReadBE16(p) - top;
                          uint16 width = SDL_ReadBE16(p) - left;
                          uint16 pack_type, pixel_size;
                          if (is_pixmap) {
                            SDL_RWseek(p, 2, SEEK_CUR);			// pmVersion
                            pack_type = SDL_ReadBE16(p);
                            SDL_RWseek(p, 14, SEEK_CUR);		// packSize/hRes/vRes/pixelType
                            pixel_size = SDL_ReadBE16(p);
                            SDL_RWseek(p, 16, SEEK_CUR);		// cmpCount/cmpSize/planeBytes/pmTable/pmReserved
                          } else {
                            pack_type = 0;
                            pixel_size = 1;
                          }
                          //printf(" width %d, height %d, row_bytes %d, depth %d, pack_type %d\n", width, height, row_bytes, pixel_size, pack_type);
                          
                          // Allocate surface for picture
                          uint32 Rmask = 0, Gmask = 0, Bmask = 0;
                          int surface_depth = 8;
                          switch (pixel_size) {
                            case 1:
                            case 2:
                            case 4:
                            case 8:
                              Rmask = Gmask = Bmask = 0xff;
                              surface_depth = 8;	// SDL surfaces must be at least 8 bits depth, so we expand 1/2/4-bit pictures to 8-bit
                              break;
                            case 16:
                              Rmask = 0x7c00;
                              Gmask = 0x03e0;
                              Bmask = 0x001f;
                              surface_depth = 16;
                              break;
                            case 32:
                              Rmask = 0x00ff0000;
                              Gmask = 0x0000ff00;
                              Bmask = 0x000000ff;
                              surface_depth = 32;
                              break;
                            default:
                              fprintf(stderr, "Unsupported PICT depth %d\n", pixel_size);
                              done = true;
                              break;
                          }
                          if (done)
                            break;
                          SDL_Surface *bm = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, surface_depth, Rmask, Gmask, Bmask, 0);
                          if (bm == NULL) {
                            done = true;
                            break;
                          }
                          
                          // 2. ColorTable
                          if (is_pixmap && (opcode == 0x0098 || opcode == 0x0099)) {
                            SDL_Color colors[256];
                            SDL_RWseek(p, 4, SEEK_CUR);			// ctSeed
                            uint16 flags = SDL_ReadBE16(p);
                            int num_colors = SDL_ReadBE16(p) + 1;
                            for (int i=0; i<num_colors; i++) {
                              uint8 value = SDL_ReadBE16(p) & 0xff;
                              if (flags & 0x8000)
                                value = i;
                              colors[value].r = SDL_ReadBE16(p) >> 8;
                              colors[value].g = SDL_ReadBE16(p) >> 8;
                              colors[value].b = SDL_ReadBE16(p) >> 8;
                            }
                            SDL_SetColors(bm, colors, 0, 256);
                          }
                          
                          // 3. source/destination Rect and transfer mode
                          SDL_RWseek(p, 18, SEEK_CUR);
                          
                          // 4. clipping region
                          if (opcode == 0x0099 || opcode == 0x009b) {
                            uint16 rgn_size = SDL_ReadBE16(p);
                            SDL_RWseek(p, rgn_size - 2, SEEK_CUR);
                          }
                          
                          // 5. graphics data
                          int data_size = uncompress_picture((uint8 *)rsrc.GetPointer() + SDL_RWtell(p), row_bytes, (uint8 *)bm->pixels, bm->pitch, pixel_size, height, pack_type);
                          if (data_size < 0) {
                            done = true;
                            break;
                          }
                          if (data_size & 1)
                            data_size++;
                          SDL_RWseek(p, data_size, SEEK_CUR);
                          
                          // If there's already a surface, throw away the decoded image
                          // (actually, we could have skipped this entire opcode, but the
                          // only way to do this is to decode the image data).
                          // So we only draw the first image we encounter.
                          if( ! s ) {
                            int real = width > pic_width ? width : pic_width;
                            s = SDL_CreateRGBSurface(SDL_SWSURFACE, real, pic_height, 32,
#ifdef ALEPHONE_LITTLE_ENDIAN
                                                     0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
#else
                                                     0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
#endif
                                                     );
                          }
                          SDL_Rect R = {left,  top,  width, height  };
                          SDL_BlitSurface(bm, NULL, s, &R);
                          SDL_FreeSurface(bm);
                          //				if (s)
                          //					SDL_FreeSurface(bm);
                          //				else
                          //					s = bm;
                          break;
			}

#ifdef HAVE_SDL_IMAGE
			case 0x8200: {	// Compressed QuickTime image (we only handle JPEG compression)
				// 1. Header
				uint32 opcode_size = SDL_ReadBE32(p);
				if (opcode_size & 1)
					opcode_size++;
				uint32 opcode_start = SDL_RWtell(p);
				SDL_RWseek(p, 26, SEEK_CUR);	// version/matrix (hom. part)
				int offset_x = SDL_ReadBE16(p);
				SDL_RWseek(p, 2, SEEK_CUR);
				int offset_y = SDL_ReadBE16(p);
				SDL_RWseek(p, 6, SEEK_CUR);	// matrix (remaining part)
				uint32 matte_size = SDL_ReadBE32(p);
				SDL_RWseek(p, 22, SEEK_CUR);	// matteRec/mode/srcRect/accuracy
				uint32 mask_size = SDL_ReadBE32(p);

				// 2. Matte image description
				if (matte_size) {
					uint32 matte_id_size = SDL_ReadBE32(p);
					SDL_RWseek(p, matte_id_size - 4, SEEK_CUR);
				}

				// 3. Matte data
				SDL_RWseek(p, matte_size, SEEK_CUR);

				// 4. Mask region
				SDL_RWseek(p, mask_size, SEEK_CUR);

				// 5. Image description
				uint32 id_start = SDL_RWtell(p);
				uint32 id_size = SDL_ReadBE32(p);
				uint32 codec_type = SDL_ReadBE32(p);
				if (codec_type != FOUR_CHARS_TO_INT('j','p','e','g')) {
					fprintf(stderr, "Unsupported codec type %c%c%c%c\n", codec_type >> 24, codec_type >> 16, codec_type >> 8, codec_type);
					done = true;
					break;
				}
				SDL_RWseek(p, 36, SEEK_CUR);	// resvd1/resvd2/dataRefIndex/version/revisionLevel/vendor/temporalQuality/spatialQuality/width/height/hRes/vRes
				uint32 data_size = SDL_ReadBE32(p);
				SDL_RWseek(p, id_start + id_size, SEEK_SET);

				// Allocate surface for complete (but possibly banded) picture
				if (s == NULL) {
					s = SDL_CreateRGBSurface(SDL_SWSURFACE, pic_width, pic_height, 32,
#ifdef ALEPHONE_LITTLE_ENDIAN
								 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
#else
								 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
#endif
							);
					if (s == NULL) {
						done = true;
						break;
					}
				}

				// 6. Compressed image data
				SDL_RWops *img = SDL_RWFromMem((uint8 *)rsrc.GetPointer() + SDL_RWtell(p), data_size);
				if (img == NULL) {
					done = true;
					break;
				}
				SDL_Surface *bm = IMG_LoadTyped_RW(img, true, const_cast<char*>("JPG"));

				// Copy image (band) into surface
				if (bm) {
					SDL_Rect dst_rect = {offset_x, offset_y, bm->w, bm->h};
					SDL_BlitSurface(bm, NULL, s, &dst_rect);
					SDL_FreeSurface(bm);
				}

				SDL_RWseek(p, opcode_start + opcode_size, SEEK_SET);
				break;
			}
#endif

			default:
				if (opcode >= 0x0300 && opcode < 0x8000)
					SDL_RWseek(p, (opcode >> 8) * 2, SEEK_CUR);
				else if (opcode >= 0x8000 && opcode < 0x8100)
					break;
				else {
					fprintf(stderr, "Unimplemented opcode %04x in PICT resource\n", opcode);
					done = true;
				}
				break;
		}
	}

	// Close stream, return surface
	SDL_RWclose(p);
	return s;
}


/*
 *  Rescale surface to given dimensions
 */

template <class T>
static void rescale(T *src_pixels, int src_pitch, T *dst_pixels, int dst_pitch, int width, int height, uint32 dx, uint32 dy)
{
	// Brute-force rescaling, no interpolation
	uint32 sy = 0;
	for (int y=0; y<height; y++) {
		T *p = src_pixels + src_pitch / sizeof(T) * (sy >> 16);
		uint32 sx = 0;
		for (int x=0; x<width; x++) {
			dst_pixels[x] = p[sx >> 16];
			sx += dx;
		}
		dst_pixels += dst_pitch / sizeof(T);
		sy += dy;
	}
}

SDL_Surface *rescale_surface(SDL_Surface *s, int width, int height)
{
	if (s == NULL)
		return NULL;

	SDL_Surface *s2 = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, s->format->Bmask, s->format->Amask);
	if (s2 == NULL)
		return NULL;

	uint32 dx = (s->w << 16) / width;
	uint32 dy = (s->h << 16) / height;

	switch (s->format->BytesPerPixel) {
		case 1:
			rescale((pixel8 *)s->pixels, s->pitch, (pixel8 *)s2->pixels, s2->pitch, width, height, dx, dy);
			break;
		case 2:
			rescale((pixel16 *)s->pixels, s->pitch, (pixel16 *)s2->pixels, s2->pitch, width, height, dx, dy);
			break;
		case 4:
			rescale((pixel32 *)s->pixels, s->pitch, (pixel32 *)s2->pixels, s2->pitch, width, height, dx, dy);
			break;
	}

	if (s->format->palette)
		SDL_SetColors(s2, s->format->palette->colors, 0, s->format->palette->ncolors);

	return s2;
}


/*
 *  Tile surface to fill given dimensions
 */

template <class T>
static void tile(T *src_pixels, int src_pitch, T *dst_pixels, int dst_pitch, int src_width, int src_height, int dst_width, int dst_height)
{
	T *p = src_pixels;
	int sy = 0;
	for (int y=0; y<dst_height; y++) {
		int sx = 0;
		for (int x=0; x<dst_width; x++) {
			dst_pixels[x] = p[sx];
			sx++;
			if (sx == src_width)
				sx = 0;
		}
		dst_pixels += dst_pitch / sizeof(T);
		sy++;
		if (sy == src_height) {
			sy = 0;
			p = src_pixels;
		} else
			p += src_pitch / sizeof(T);
	}
}

SDL_Surface *tile_surface(SDL_Surface *s, int width, int height)
{
	if (s == NULL)
		return NULL;

	SDL_Surface *s2 = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, s->format->Bmask, s->format->Amask);
	if (s2 == NULL)
		return NULL;

	switch (s->format->BytesPerPixel) {
		case 1:
			tile((pixel8 *)s->pixels, s->pitch, (pixel8 *)s2->pixels, s2->pitch, s->w, s->h, width, height);
			break;
		case 2:
			tile((pixel16 *)s->pixels, s->pitch, (pixel16 *)s2->pixels, s2->pitch, s->w, s->h, width, height);
			break;
		case 3:
			tile((pixel8 *)s->pixels, s->pitch, (pixel8 *)s2->pixels, s2->pitch, s->w * 3, s->h, width * 3, height);
			break;
		case 4:
			tile((pixel32 *)s->pixels, s->pitch, (pixel32 *)s2->pixels, s2->pitch, s->w, s->h, width, height);
			break;
	}

	if (s->format->palette)
		SDL_SetColors(s2, s->format->palette->colors, 0, s->format->palette->ncolors);

	return s2;
}


/*
 *  Draw picture resource centered on screen
 */

extern SDL_Surface *draw_surface;	// from screen_drawing.cpp
//void draw_intro_screen(void);		// from screen.cpp

static void draw_picture(LoadedResource &rsrc)
{
	// Convert picture resource to surface, free resource
	SDL_Surface *s = picture_to_surface(rsrc);
	if (s == NULL)
		return;
	_set_port_to_intro();
	SDL_Surface *video = draw_surface;

	// Default source rectangle
	SDL_Rect src_rect = {0, 0, MIN(s->w, 640), MIN(s->h, 480)};

	// Center picture on screen
	SDL_Rect dst_rect = {(video->w - src_rect.w) / 2, (video->h - src_rect.h) / 2, s->w, s->h};
	if (dst_rect.x < 0)
		dst_rect.x = 0;
	if (dst_rect.y < 0)
		dst_rect.y = 0;

	// Clip if desired (only used for menu buttons)
	if (draw_clip_rect_active) {
		src_rect.w = dst_rect.w = draw_clip_rect.right - draw_clip_rect.left;
		src_rect.h = dst_rect.h = draw_clip_rect.bottom - draw_clip_rect.top;
		src_rect.x = draw_clip_rect.left - (640 - s->w) / 2;
		src_rect.y = draw_clip_rect.top - (480 - s->h) / 2;
		dst_rect.x += draw_clip_rect.left- (640 - s->w) / 2;
		dst_rect.y += draw_clip_rect.top - (480 - s->h) / 2;
	} else {
			// Clear destination to black
			SDL_FillRect(video, NULL, SDL_MapRGB(video->format, 0, 0, 0));
	}
	
	SDL_BlitSurface(s, &src_rect, video, &dst_rect);
	_restore_port();
	draw_intro_screen();
}


/*
 *  Get system color table
 */

struct color_table *build_8bit_system_color_table(void)
{
	// 6*6*6 RGB color cube
	color_table *table = new color_table;
	table->color_count = 6*6*6;
	int index = 0;
	for (int red=0; red<6; red++) {
		for (int green=0; green<6; green++) {
			for (int blue=0; blue<6; blue++) {
				uint8 r = red * 0x33;
				uint8 g = green * 0x33;
				uint8 b = blue * 0x33;
				table->colors[index].red = (r << 8) | r;
				table->colors[index].green = (g << 8) | g;
				table->colors[index].blue = (b << 8) | b;
				index++;
			}
		}
	}
	return table;
}


/*
 *  Scroll image across screen
 */

#define SCROLLING_SPEED (MACHINE_TICKS_PER_SECOND / 20)

void scroll_full_screen_pict_resource_from_scenario(int pict_resource_number, bool text_block)
{
	// Convert picture resource to surface, free resource
	LoadedResource rsrc;
	get_picture_resource_from_scenario(pict_resource_number, rsrc);
	SDL_Surface *s = picture_to_surface(rsrc);
	if (s == NULL)
		return;

	// Find out in which direction to scroll
	int picture_width = s->w;
	int picture_height = s->h;
	int screen_width = 640;
	int screen_height = 480;
	bool scroll_horizontal = picture_width > screen_width;
	bool scroll_vertical = picture_height > screen_height;

	if (scroll_horizontal || scroll_vertical) {

		// Flush events
		SDL_Event event;
		while (SDL_PollEvent(&event)) ;

		// Prepare source and destination rectangles
		SDL_Rect src_rect = {0, 0, scroll_horizontal ? screen_width : picture_width, scroll_vertical ? screen_height : picture_height};
		SDL_Rect dst_rect = {0, 0, screen_width, screen_height};

		// Scroll loop
		bool done = false, aborted = false;
		uint32 start_tick = SDL_GetTicks();
		do {

			int32 delta = (SDL_GetTicks() - start_tick) / (text_block ? (2 * SCROLLING_SPEED) : SCROLLING_SPEED);
			if (scroll_horizontal && delta > picture_width - screen_width) {
				delta = picture_width - screen_width;
				done = true;
			}
			if (scroll_vertical && delta > picture_height - screen_height) {
				delta = picture_height - screen_height;
				done = true;
			}

			// Blit part of picture
			src_rect.x = scroll_horizontal ? delta : 0;
			src_rect.y = scroll_vertical ? delta : 0;
			_set_port_to_intro();
			SDL_BlitSurface(s, &src_rect, draw_surface, &dst_rect);
			_restore_port();
			draw_intro_screen();

			// Give system time
			global_idle_proc();
			SDL_Delay(10);

			// Check for events to abort
			event.type = SDL_NOEVENT;
			SDL_PollEvent(&event);
			switch (event.type) {
				case SDL_MOUSEBUTTONDOWN:
				case SDL_KEYDOWN:
					aborted = true;
					break;
			}

		} while (!done && !aborted);
	}

	// Free surface
	SDL_FreeSurface(s);
}


/*
 *  Initialize image manager, open Images file
 */

void initialize_images_manager(void)
{
	FileSpecifier file;

  logContext("loading Images...");

	file.SetNameWithPath(getcstr(temporary, strFILENAMES, filenameIMAGES)); // _typecode_images
	
	if (!file.Exists())
		alert_user(fatalError, strERRORS, badExtraFileLocations, fnfErr);
	
	if (!ImagesFile.open_file(file))
		alert_user(fatalError, strERRORS, badExtraFileLocations, -1);

	atexit(shutdown_images_handler);
}


/*
 *  Shutdown image manager
 */

static void shutdown_images_handler(void)
{
	ScenarioFile.close_file();
	ImagesFile.close_file();
}


/*
 *  Set map file to load images from
 */

void set_scenario_images_file(FileSpecifier &file)
{
	ScenarioFile.open_file(file);
}


/*
 *  Open/close image file
 */

bool image_file_t::open_file(FileSpecifier &file)
{
	close_file();
	
	// Try to open as a resource file
	if (!file.Open(rsrc_file)) {
	
		// This failed, maybe it's a wad file (M2 Win95 style)
		if (!open_wad_file_for_reading(file, wad_file)
		 || !read_wad_header(wad_file, &wad_hdr)) {

			// This also failed, bail out
			wad_file.Close();
			return false;
		}
	} // Try to open wad file, too
	else if (!wad_file.IsOpen()) {
		if (open_wad_file_for_reading(file, wad_file)) {
			if (!read_wad_header(wad_file, &wad_hdr)) {
				
				wad_file.Close();
			}
		}
	}
	
	return true;
}

void image_file_t::close_file(void)
{
	rsrc_file.Close();
	wad_file.Close();
}

bool image_file_t::is_open(void)
{
	return rsrc_file.IsOpen() || wad_file.IsOpen();
}


/*
 *  Get resource from file
 */

bool image_file_t::has_rsrc(uint32 rsrc_type, uint32 wad_type, int id)
{
	// Check for resource in resource file
	if (rsrc_file.IsOpen())
	{
		if (rsrc_file.Check(rsrc_type, id))
			return true;
	}
	
	// Check for resource in wad file
	if (wad_file.IsOpen()) {
		wad_data *d = read_indexed_wad_from_file(wad_file, &wad_hdr, id, true);
		if (d) {
			bool success = false;
			size_t len;
			if (extract_type_from_wad(d, wad_type, &len))
				success = true;
			free_wad(d);
			return success;
		}
	}
	
	return false;
}

bool image_file_t::has_pict(int id)
{
	return has_rsrc(FOUR_CHARS_TO_INT('P','I','C','T'), FOUR_CHARS_TO_INT('P','I','C','T'), id) || has_rsrc(FOUR_CHARS_TO_INT('P','I','C','T'), FOUR_CHARS_TO_INT('p','i','c','t'), id);
}

bool image_file_t::has_clut(int id)
{
	return has_rsrc(FOUR_CHARS_TO_INT('c','l','u','t'), FOUR_CHARS_TO_INT('c','l','u','t'), id);
}

bool image_file_t::get_rsrc(uint32 rsrc_type, uint32 wad_type, int id, LoadedResource &rsrc)
{
	// Get resource from resource file
	if (rsrc_file.IsOpen())
	{
		if (rsrc_file.Get(rsrc_type, id, rsrc))
			return true;
	}
	
	// Get resource from wad file
	if (wad_file.IsOpen()) {
		wad_data *d = read_indexed_wad_from_file(wad_file, &wad_hdr, id, true);
		if (d) {
			bool success = false;
			size_t raw_length;
			void *raw = extract_type_from_wad(d, wad_type, &raw_length);
			if (raw)
			{
				if (rsrc_type == FOUR_CHARS_TO_INT('P','I','C','T'))
				{
					if (wad_type == FOUR_CHARS_TO_INT('P','I','C','T'))
					{
						void *pict_data = malloc(raw_length);
						memcpy(pict_data, raw, raw_length);
						rsrc.SetData(pict_data, raw_length);
						success = true;
					}
					else
					{
						size_t clut_length;
						void *clut_data = extract_type_from_wad(d, FOUR_CHARS_TO_INT('c','l','u','t'), &clut_length);
						success = make_rsrc_from_pict(raw, raw_length, rsrc, clut_data, clut_length);
					}
				}
				else if (rsrc_type == FOUR_CHARS_TO_INT('c','l','u','t'))
					success = make_rsrc_from_clut(raw, raw_length, rsrc);
				else if (rsrc_type == FOUR_CHARS_TO_INT('s','n','d',' '))
				{
					void *snd_data = malloc(raw_length);
					memcpy(snd_data, raw, raw_length);
					rsrc.SetData(snd_data, raw_length);
					success = true;
				}
				else if (rsrc_type == FOUR_CHARS_TO_INT('T','E','X','T'))
				{
					void *text_data = malloc(raw_length);
					memcpy(text_data, raw, raw_length);
					rsrc.SetData(text_data, raw_length);
					success = true;
				}
			}
			free_wad(d);
			return success;
		}
	}
	
	return false;
}

bool image_file_t::get_pict(int id, LoadedResource &rsrc)
{
	return get_rsrc(FOUR_CHARS_TO_INT('P','I','C','T'), FOUR_CHARS_TO_INT('P','I','C','T'), id, rsrc) || get_rsrc(FOUR_CHARS_TO_INT('P','I','C','T'), FOUR_CHARS_TO_INT('p','i','c','t'), id, rsrc);
}

bool image_file_t::get_clut(int id, LoadedResource &rsrc)
{
	return get_rsrc(FOUR_CHARS_TO_INT('c','l','u','t'), FOUR_CHARS_TO_INT('c','l','u','t'), id, rsrc);
}

bool image_file_t::get_snd(int id, LoadedResource &rsrc)
{
	return get_rsrc(FOUR_CHARS_TO_INT('s','n','d',' '), FOUR_CHARS_TO_INT('s','n','d',' '), id, rsrc);
}

bool image_file_t::get_text(int id, LoadedResource &rsrc)
{
	return get_rsrc(FOUR_CHARS_TO_INT('T','E','X','T'), FOUR_CHARS_TO_INT('t','e','x','t'), id, rsrc);
}


/*
 *  Get/draw image from Images file
 */

bool get_picture_resource_from_images(int base_resource, LoadedResource &PictRsrc)
{
	assert(ImagesFile.is_open());

	int RsrcID = ImagesFile.determine_pict_resource_id(
		base_resource, _images_file_delta16, _images_file_delta32);
	return ImagesFile.get_pict(RsrcID, PictRsrc);
}

bool images_picture_exists(int base_resource)
{
	assert(ImagesFile.is_open());

	int RsrcID = ImagesFile.determine_pict_resource_id(
		base_resource, _images_file_delta16, _images_file_delta32);
	return ImagesFile.has_pict(RsrcID);
}

void draw_full_screen_pict_resource_from_images(int pict_resource_number)
{
	LoadedResource PictRsrc;
	get_picture_resource_from_images(pict_resource_number, PictRsrc);
	draw_picture(PictRsrc);
}


/*
 *  Get/draw image from scenario
 */

bool get_picture_resource_from_scenario(int base_resource, LoadedResource &PictRsrc)
{
	if (!ScenarioFile.is_open())
		return false;

	int RsrcID = ScenarioFile.determine_pict_resource_id(
		base_resource, _scenario_file_delta16, _scenario_file_delta32);

	bool success = ScenarioFile.get_pict(RsrcID, PictRsrc);
#ifdef mac
	if (success) {
		Handle PictHdl = PictRsrc.GetHandle();
		if (PictHdl) HNoPurge(PictHdl);
	}
#endif
	return success;
}

bool scenario_picture_exists(int base_resource)
{
	if (!ScenarioFile.is_open())
		return false;

	int RsrcID = ScenarioFile.determine_pict_resource_id(
		base_resource, _scenario_file_delta16, _scenario_file_delta32);
	return ScenarioFile.has_pict(RsrcID);
}

void draw_full_screen_pict_resource_from_scenario(int pict_resource_number)
{
	LoadedResource PictRsrc;
	get_picture_resource_from_scenario(pict_resource_number, PictRsrc);
	draw_picture(PictRsrc);
}


/*
 *  Get sound resource from scenario
 */

bool get_sound_resource_from_scenario(int resource_number, LoadedResource &SoundRsrc)
{
	if (!ScenarioFile.is_open())
		return false;

	bool success = ScenarioFile.get_snd(resource_number, SoundRsrc);
#ifdef mac
	if (success) {
		Handle SndHdl = SoundRsrc.GetHandle();
		if (SndHdl) HNoPurge(SndHdl);
	}
#endif
	return success;
}


// LP: do the same for text resources

bool get_text_resource_from_scenario(int resource_number, LoadedResource &TextRsrc)
{
	if (!ScenarioFile.is_open())
		return false;

	bool success = ScenarioFile.get_text(resource_number, TextRsrc);
#ifdef mac
	if (success) {
		Handle TxtHdl = TextRsrc.GetHandle();
		if (TxtHdl) HNoPurge(TxtHdl);
	}
#endif
	return success;
}


/*
 *  Calculate color table for image
 */

struct color_table *calculate_picture_clut(int CLUTSource, int pict_resource_number)
{
	struct color_table *picture_table = NULL;

	// Select the source
	image_file_t *OFilePtr = NULL;
	switch (CLUTSource) {
		case CLUTSource_Images:
			OFilePtr = &ImagesFile;
			break;
		
		case CLUTSource_Scenario:
			OFilePtr = &ScenarioFile;
			break;
	
		default:
			vassert(false, csprintf(temporary, "Invalid resource-file selector: %d", CLUTSource));
			break;
	}
	
	// Load CLUT resource
	LoadedResource CLUT_Rsrc;
	if (OFilePtr->get_clut(pict_resource_number, CLUT_Rsrc)) {

#ifdef mac
		Handle resource = CLUT_Rsrc.GetHandle();
		HNoPurge(resource);
#endif

		// Allocate color table
		picture_table = new color_table;

		// Convert MacOS CLUT resource to color table
		if (interface_bit_depth == 8)
			build_color_table(picture_table, CLUT_Rsrc);
		else
			build_direct_color_table(picture_table, interface_bit_depth);
	}

	return picture_table;
}


/*
 *  Determine ID for picture resource
 */

int image_file_t::determine_pict_resource_id(int base_id, int delta16, int delta32)
{
	int actual_id = base_id;
	bool done = false;
	int bit_depth = interface_bit_depth;

	while (!done) {
		int next_bit_depth;
	
		actual_id = base_id;
		switch(bit_depth) {
			case 8:	
				next_bit_depth = 0; 
				break;
				
			case 16: 
				next_bit_depth = 8;
				actual_id += delta16; 
				break;
				
			case 32: 
				next_bit_depth = 16;
				actual_id += delta32;	
				break;
				
			default: 
				assert(false);
				break;
		}
		
		if (has_pict(actual_id))
			done = true;

		if (!done) {
			if (next_bit_depth)
				bit_depth = next_bit_depth;
			else {
				// Didn't find it. Return the 8 bit version and bail..
				done = true;
			}
		}
	}
	return actual_id;
}


/*
 *  Convert picture and CLUT data from wad file to PICT resource
 */

#ifdef mac

// MacOS version
bool image_file_t::make_rsrc_from_pict(void *data, size_t length, LoadedResource &rsrc, void *clut_data, size_t clut_length)
{
	pict_head		*head;
	PicHandle		picture;
	Rect			bounds;
	short			i;
	GWorldPtr		gworld;
	PixMapHandle	pixmap;
	void			*pict_data;
	void			*clut;
	uint8			*p, *q;
	CTabHandle		clut_handle;
	CGrafPtr		saveport;
	
	if (length < 10)
		return false;
	
	head = (pict_head *)data;
	
	bounds.left		= head->bounds.left;
	bounds.top		= head->bounds.top;
	bounds.right	= head->bounds.right;
	bounds.bottom	= head->bounds.bottom;
	
	if (head->depth == 8)
	{
		
		clut = malloc(8 + 256 * 8);
		
		memset(clut, 0, 8 + 256 * 8);
		
		p = (uint8 *)clut_data;
		q = (uint8 *)clut;
		
		// 1. Header
		q[6] = p[0]; // color count
		q[7] = p[1];
		p += 6;
		q += 8;

		// 2. Color table
		for (i = 0; i < 256; i++)
		{
			q++;
			*q++ = i;		// value
			*q++ = *p++;	// red
			*q++ = *p++;
			*q++ = *p++;	// green
			*q++ = *p++;
			*q++ = *p++;	// blue
			*q++ = *p++;
		}
		
		clut_handle = (CTabHandle)NewHandleClear(8 + 256 * 8);
		
		PtrToHand(clut, (Handle *)&clut_handle, 8 + 256 * 8);
		
		QTNewGWorldFromPtr(&gworld, k8IndexedPixelFormat, &bounds, clut_handle, NULL, 0, (uint8 *)data + 10, head->bounds.right - head->bounds.left);
		
		free(clut);
		DisposeHandle((Handle)clut_handle);
	}
	else if (head->depth == 16)
	{
		QTNewGWorldFromPtr(&gworld, k16BE555PixelFormat, &bounds, NULL, NULL, 0, (uint8 *)data + 10, (head->bounds.right - head->bounds.left) * 2);
	}
	else if (head->depth == 24)
	{
		QTNewGWorldFromPtr(&gworld, k24RGBPixelFormat, &bounds, NULL, NULL, 0, (uint8 *)data + 10, (head->bounds.right - head->bounds.left) * 3);
	}
	else if (head->depth == 32)
	{
		QTNewGWorldFromPtr(&gworld, k32ARGBPixelFormat, &bounds, NULL, NULL, 0, (uint8 *)data + 10, (head->bounds.right - head->bounds.left) * 4);
	}
	else
	{
		return false;
	}
	
	PenNormal();
	
	GetGWorld(&saveport, NULL);
	SetGWorld(gworld, NULL);
	
	ForeColor(blackColor);
	BackColor(whiteColor);
	
	pixmap = GetGWorldPixMap(gworld);
	LockPixels(pixmap);
	
	picture = OpenPicture(&bounds);
	
	if (picture != NULL)
    {
        CopyBits((BitMap *)*pixmap, (BitMap *)*pixmap, &bounds, &bounds, srcCopy, NULL);
        ClosePicture();
    }
    
    if (picture != NULL)
    {
	    UnlockPixels(pixmap);
	    SetGWorld(saveport, NULL);
	    DisposeGWorld(gworld);
		
		HLock((Handle)picture);
		pict_data = malloc(GetHandleSize((Handle)picture));
		memcpy(pict_data, *picture, GetHandleSize((Handle)picture));
		
		rsrc.SetData(pict_data, GetHandleSize((Handle)picture));
		KillPicture(picture);
	}
	
	return true;
}

#else

// SDL version
bool image_file_t::make_rsrc_from_pict(void *data, size_t length, LoadedResource &rsrc, void *clut_data, size_t clut_length)
{
	if (length < 10)
		return false;

	// Extract size and depth
	uint8 *p = (uint8 *)data;
	int height = (p[4] << 8) + p[5];
	int width = (p[6] << 8) + p[7];
	int depth = (p[8] << 8) + p[9];
	if (depth != 8 && depth != 16)
		return false;

	// 8-bit depth requires CLUT
	if (depth == 8) {
		if (clut_data == NULL || clut_length != 6 + 256 * 6)
			return false;
	}

	// size(2), rect(8), versionOp(2), version(2), headerOp(26)
	int output_length = 2 + 8 + 2 + 2 + 26;
	int row_bytes;
	if (depth == 8) {
		// opcode(2), pixMap(46), colorTable(8+256*8), srcRect/dstRect/mode(18), data(variable)
		row_bytes = width;
		output_length += 2 + 46 + 8+256*8 + 18;
	} else {
		// opcode(2), pixMap(50), srcRect/dstRect/mode(18), data(variable)
		row_bytes = width * 2;
		output_length += 2 + 50 + 18;
	}
	// data(variable), opEndPic(2)
	output_length += row_bytes * height + 2;

	// Allocate memory for Mac PICT resource
	void *pict_rsrc = malloc(output_length);
	if (pict_rsrc == NULL)
		return false;
	memset(pict_rsrc, 0, output_length);

	// Convert pict tag to Mac PICT resource
	uint8 *q = (uint8 *)pict_rsrc;

	// 1. PICT header
	q[0] = output_length >> 8;
	q[1] = output_length;
	memcpy(q + 2, p, 8);
	q += 10;

	// 2. VersionOp/Version/HeaderOp
	q[0] = 0x00; q[1] = 0x11; // versionOp
	q[2] = 0x02; q[3] = 0xff; // version
	q[4] = 0x0c; q[5] = 0x00; // headerOp
	q[6] = 0xff; q[7] = 0xfe; // header version
	q[11] = 0x48; // hRes
	q[15] = 0x48; // vRes
	memcpy(q + 18, p, 8);
	q += 30;

	// 3. opcode
	if (depth == 8) {
		q[0] = 0x00; q[1] = 0x98;	// PackBitsRect
		q += 2;
	} else {
		q[0] = 0x00; q[1] = 0x9a;	// DirectBitsRect
		q += 6; // skip pmBaseAddr
	}

	// 4. PixMap
	q[0] = (row_bytes >> 8) | 0x80;
	q[1] = row_bytes;
	memcpy(q + 2, p, 8);
	q[13] = 0x01; // packType = unpacked
	q[19] = 0x48; // hRes
	q[23] = 0x48; // vRes
	q[27] = (depth == 8 ? 0 : 0x10); // pixelType
	q[29] = depth; // pixelSize
	q[31] = (depth == 8 ? 1 : 3); // cmpCount
	q[33] = (depth == 8 ? 8 : 5); // cmpSize
	q += 46;

	// 5. ColorTable
	if (depth == 8) {
		q[7] = 0xff; // ctSize
		q += 8;
		uint8 *p = (uint8 *)clut_data + 6;
		for (int i=0; i<256; i++) {
			q++;
			*q++ = i;	// value
			*q++ = *p++;	// red
			*q++ = *p++;
			*q++ = *p++;	// green
			*q++ = *p++;
			*q++ = *p++;	// blue
			*q++ = *p++;
		}
	}

	// 6. source/destination Rect and transfer mode
	memcpy(q, p, 8);
	memcpy(q + 8, p, 8);
	q += 18;

	// 7. graphics data
	memcpy(q, p + 10, row_bytes * height);
	q += row_bytes * height;

	// 8. OpEndPic
	q[0] = 0x00;
	q[1] = 0xff;

	rsrc.SetData(pict_rsrc, output_length);
	return true;
}

#endif

bool image_file_t::make_rsrc_from_clut(void *data, size_t length, LoadedResource &rsrc)
{
	const size_t input_length = 6 + 256 * 6;	// 6 bytes header, 256 entries with 6 bytes each
	const size_t output_length = 8 + 256 * 8;	// 8 bytes header, 256 entries with 8 bytes each

	if (length != input_length)
		return false;

	// Allocate memory for Mac CLUT resource
	void *clut_rsrc = malloc(output_length);
	if (clut_rsrc == NULL)
		return false;
	memset(clut_rsrc, 0, output_length);

	// Convert clut tag to Mac CLUT resource
	uint8 *p = (uint8 *)data;
	uint8 *q = (uint8 *)clut_rsrc;

	// 1. Header
	q[6] = p[0]; // color count
	q[7] = p[1];
	p += 6;
	q += 8;

	// 2. Color table
	for (int i=0; i<256; i++) {
		q++;
		*q++ = i;		// value
		*q++ = *p++;	// red
		*q++ = *p++;
		*q++ = *p++;	// green
		*q++ = *p++;
		*q++ = *p++;	// blue
		*q++ = *p++;
	}

	rsrc.SetData(clut_rsrc, output_length);
	return true;
}
