From 67a12200858b886cfb7c0cabdfdf3a58af0bab81 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Thu, 22 Nov 2018 10:16:26 +0100 Subject: [PATCH] SCR (Amstrad CPC) file format - Implements Test_SCR() / Load_SCR() for standard formats pictures - Save .PAL file in Save_SCR() --- src/fileformats.h | 2 + src/libraw2crtc.c | 1 + src/loadsave.c | 3 +- src/miscfileformats.c | 365 ++++++++++++++++++++++++++++++++++++------ src/oldies.c | 35 ++++ src/oldies.h | 13 ++ 6 files changed, 367 insertions(+), 52 deletions(-) diff --git a/src/fileformats.h b/src/fileformats.h index 50b9384f..3bbe4f1d 100644 --- a/src/fileformats.h +++ b/src/fileformats.h @@ -112,6 +112,8 @@ void Load_C64(T_IO_Context *); void Save_C64(T_IO_Context *); // -- SCR (Amstrad CPC) +void Test_SCR(T_IO_Context *, FILE *); +void Load_SCR(T_IO_Context *); void Save_SCR(T_IO_Context *); // -- CM5 (Amstrad CPC) diff --git a/src/libraw2crtc.c b/src/libraw2crtc.c index 81727237..f45159a0 100644 --- a/src/libraw2crtc.c +++ b/src/libraw2crtc.c @@ -64,6 +64,7 @@ unsigned char mode3interlace(T_IO_Context * context, unsigned char x, unsigned c return mode3pixel[Get_pixel(context, x,y) & 3] << 3 | mode3pixel[Get_pixel(context,x+1,y) & 3] << 2; } +///@ingroup cpc unsigned char *raw2crtc(T_IO_Context *context, unsigned char mode, unsigned char r9, unsigned long *outSize, unsigned char *r1, unsigned char r12, unsigned char r13) { unsigned char *outBuffer; diff --git a/src/loadsave.c b/src/loadsave.c index 5b021be5..4dbf3de3 100644 --- a/src/loadsave.c +++ b/src/loadsave.c @@ -90,6 +90,7 @@ const T_Format File_formats[] = { {FORMAT_ALL_IMAGES, "(all)", NULL, NULL, NULL, 0, 0, 0, "", "gif;png;bmp;2bp;pcx;pkm;iff;lbm;ilbm;sham;ham;ham6;ham8;acbm;pic;anim;img;sci;scq;scf;scn;sco;pi1;pc1;cel;neo;" "c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;prg;pmg;rpm;" + "cpc;scr;win;" "tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff;ico;ic2;cur;cm5;pph;info;flc;bin;map"}, {FORMAT_ALL_PALETTES, "(pal)", NULL, NULL, NULL, 1, 0, 0, "", "kcf;pal;gpl"}, {FORMAT_ALL_FILES, "(*.*)", NULL, NULL, NULL, 0, 0, 0, "", "*"}, @@ -114,7 +115,7 @@ const T_Format File_formats[] = { {FORMAT_GPL, " gpl", Test_GPL, Load_GPL, Save_GPL, 1, 0, 0, "gpl", "gpl"}, {FORMAT_C64, " c64", Test_C64, Load_C64, Save_C64, 0, 1, 0, "c64", "c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;prg;pmg;rpm"}, - {FORMAT_SCR, " cpc", NULL, NULL, Save_SCR, 0, 0, 0, "cpc", "cpc;scr"}, + {FORMAT_SCR, " cpc", Test_SCR, Load_SCR, Save_SCR, 0, 0, 0, "scr", "cpc;scr;win"}, {FORMAT_CM5, " cm5", Test_CM5, Load_CM5, Save_CM5, 0, 0, 1, "cm5", "cm5"}, {FORMAT_PPH, " pph", Test_PPH, Load_PPH, Save_PPH, 0, 0, 1, "pph", "pph"}, {FORMAT_XPM, " xpm", NULL, NULL, Save_XPM, 0, 0, 0, "xpm", "xpm"}, diff --git a/src/miscfileformats.c b/src/miscfileformats.c index 23680640..ee440825 100644 --- a/src/miscfileformats.c +++ b/src/miscfileformats.c @@ -3733,24 +3733,65 @@ void Save_C64(T_IO_Context * context) /** * Test for SCR file (Amstrad CPC) * - * TODO + * SCR file format is from "Advanced OCP Art Studio" : + * http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats + * + * For now we check the presence of a valid PAL file. + * If the PAL file is not there the pixel data may still be valid. + * The file size depends on the screen resolution. + * An AMSDOS header would be a good indication but in some cases it may not + * be there. */ void Test_SCR(T_IO_Context * context, FILE * file) { - /** - * Mmh... not sure what we could test. Any idea ? - * The palette file can be tested, if it exists and have the right size it's - * ok. But if it's not there the pixel data may still be valid. And we can't - * use the filesize as this depends on the screen format. + FILE * pal_file; + unsigned long pal_size; + byte mode, color_anim_flag; - * An AMSDOS header would be a good indication but in some cases it may not - * be there */ - (void)context; // unused - (void)file; + (void)file; + + File_error = 1; + // requires the PAL file + pal_file = Open_file_read_with_alternate_ext(context, "pal"); + if (pal_file == NULL) + return; + + pal_size = File_length_file(pal_file); + if (pal_size == 239+128) + { + if (!CPC_check_AMSDOS(pal_file, NULL, NULL)) + { + fclose(pal_file); + return; + } + fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header + } + else if (pal_size != 239) + { + fclose(pal_file); + return; + } + + if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag)) + { + fclose(pal_file); + return; + } + GFX2_Log(GFX2_DEBUG, "Test_SCR() mode=%d color animation flag %02X\n", mode, color_anim_flag); + if (mode <= 2 && (color_anim_flag == 0 || color_anim_flag == 0xff)) + File_error = 0; + fclose(pal_file); } /** - * TODO + * Load Advanced OCP Art Studio files (Amstrad CPC) + * + * Only standard resolution files (Mode 0 160x200, mode 1 320x200 and + * mode 2 640x200) are supported. The .PAL file presence is required. + * "MJH" RLE packing is supported. + * + * @todo Ask user for screen size (or register values) in order to support + * non standard resolutions. */ void Load_SCR(T_IO_Context * context) { @@ -3772,61 +3813,283 @@ void Load_SCR(T_IO_Context * context) // All this mess enforces us to load (and unpack if needed) the file to a // temporary 32k buffer before actually decoding it. + FILE * pal_file, * file; + unsigned long file_size, amsdos_file_size = 0; + byte mode, color_anim_flag, color_anim_delay; + byte pal_data[236]; // 12 palettes of 16+1 colors + 16 excluded inks + 16 protected inks + word width, height = 200; + byte bpp; + enum PIXEL_RATIO ratio; + byte * pixel_data; + word x, y; + int i; - // 1) Seek for a palette - // 2) If palette found get screenmode from there, else ask user - // 3) ask user for screen size (or register values) - // 4) Load color data from palette (if found) - // 5) Close palette - // 6) Open the file - // 7) Run around the screen to untangle the pixeldata - // 8) Close the file - (void)context; // unused + File_error = 1; + // requires the PAL file + pal_file = Open_file_read_with_alternate_ext(context, "pal"); + if (pal_file == NULL) + return; + file_size = File_length_file(pal_file); + if (file_size == 239+128) + { + if (!CPC_check_AMSDOS(pal_file, NULL, NULL)) + { + fclose(pal_file); + return; + } + fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header + } + if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag) + || !Read_byte(pal_file, &color_anim_delay) || !Read_bytes(pal_file, pal_data, 236)) + { + GFX2_Log(GFX2_WARNING, "Load_SCR() failed to load .PAL file\n"); + fclose(pal_file); + return; + } + fclose(pal_file); + GFX2_Log(GFX2_DEBUG, "Load_SCR() mode=%d color animation flag=%02X delay=%u\n", + mode, color_anim_flag, color_anim_delay); + switch (mode) + { + case 0: + width = 160; + bpp = 4; + ratio = PIXEL_WIDE; + break; + case 1: + width = 320; + bpp = 2; + ratio = PIXEL_SIMPLE; + break; + case 2: + width = 640; + bpp = 1; + ratio = PIXEL_TALL; + break; + default: + return; // unsupported + } + + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + // Setup the palette (amstrad hardware palette) + CPC_set_HW_palette(context->Palette + 0x40); + + // Set the palette for this picture + for (i = 0; i < 16; i++) + context->Palette[i] = context->Palette[pal_data[12*i]]; + + file = Open_file_read(context); + if (file == NULL) + return; + file_size = File_length_file(file); + if (CPC_check_AMSDOS(file, NULL, &amsdos_file_size)) + { + if (file_size < (amsdos_file_size + 128)) + { + GFX2_Log(GFX2_ERROR, "Load_SCR() mismatch in file size. AMSDOS file size %lu, should be %lu\n", amsdos_file_size, file_size - 128); + fclose(file); + return; + } + else if (file_size > (amsdos_file_size + 128)) + GFX2_Log(GFX2_INFO, "Load_SCR() %lu extra bytes at end of file\n", file_size - 128 - amsdos_file_size); + fseek(file, 128, SEEK_SET); // right after AMSDOS header + } + else + fseek(file, 0, SEEK_SET); + Pre_load(context, width, height, file_size, FORMAT_SCR, ratio, bpp); + if(amsdos_file_size != 0) + file_size = amsdos_file_size; + + pixel_data = malloc(16384); + + if (file_size >= 16336 && file_size <= 16384) + Read_bytes(file, pixel_data, file_size); + else + { + byte sig[3]; + word block_length; + // MJH packed format + i = 0; + do + { + if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) + break; + if (0 != memcmp(sig, "MJH", 3)) + break; + GFX2_Log(GFX2_DEBUG, " %.3s %u\n", sig, block_length); + file_size -= 5; + while (block_length > 0) + { + byte code; + if (!Read_byte(file, &code)) + break; + file_size--; + if (code == 1) + { + byte repeat, value; + if (!Read_byte(file, &repeat) || !Read_byte(file, &value)) + break; + file_size -= 2; + do + { + pixel_data[i++] = value; + block_length--; + } + while(--repeat != 0); + } + else + { + pixel_data[i++] = code; + block_length--; + } + } + } + while(file_size > 0 && i < 16384); + } + fclose(file); + + for (y = 0; y < height; y++) + { + const byte * line; + + line = pixel_data + ((y & 7) << 11) + ((y >> 3) * 80); + x = 0; + for (i = 0; i < 80; i++) + { + byte pixels = line[i]; + switch (mode) + { + case 0: + Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); + Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); + break; + case 1: + do { + // upper nibble is 4 lower color bits, lower nibble is 4 upper color bits + Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2); + pixels <<= 1; + } + while ((x & 3) != 0); + break; + case 2: + do { + Set_pixel(context, x++, y, (pixels & 0x80) >> 7); + pixels <<= 1; + } + while ((x & 7) != 0); + } + } + } + + free(pixel_data); + + File_error = 0; } /** * Save Amstrad SCR file + * + * guess mode from aspect ratio : + * - normal pixels are mode 1 + * - wide pixels are mode 0 + * - tall pixels are mode 2 + * + * Mode and palette are stored in a .PAL file. + * + * The picture color index should be 0-15, + * The CPC Hardware palette is expected to be set (indexes 64 to 95) + * + * @todo Add possibility to set R9, R12, R13 values + * @todo Add OCP packing support + * @todo Add possibility to include AMSDOS header, with proper loading + * address guessed from r12/r13 values. */ void Save_SCR(T_IO_Context * context) { - // TODO : Add possibility to set R9, R12, R13 values - // TODO : Add OCP packing support - // TODO : Add possibility to include AMSDOS header, with proper loading - // address guessed from r12/r13 values. - - unsigned char* output; - unsigned long outsize; - unsigned char r1; - int cpc_mode; - FILE* file; + int i, j; + unsigned char* output; + unsigned long outsize = 0; + unsigned char r1 = 0; + int cpc_mode; + FILE* file; - switch(Pixel_ratio) - { - case PIXEL_WIDE: - case PIXEL_WIDE2: - cpc_mode = 0; - break; - case PIXEL_TALL: - case PIXEL_TALL2: - case PIXEL_TALL3: - cpc_mode = 2; - break; - default: - cpc_mode = 1; - break; - } + switch(Pixel_ratio) + { + case PIXEL_WIDE: + case PIXEL_WIDE2: + cpc_mode = 0; + break; + case PIXEL_TALL: + case PIXEL_TALL2: + case PIXEL_TALL3: + cpc_mode = 2; + break; + default: + cpc_mode = 1; + break; + } - output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0); - - file = Open_file_write(context); - Write_bytes(file, output, outsize); + file = Open_file_write_with_alternate_ext(context, "pal"); + if (file == NULL) + return; + if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0)) + { fclose(file); + return; + } + for (i = 0; i < 16; i++) + { + // search for the color in the HW palette (0x40-0x5F) + byte index = 0x40; + while ((index < 0x60) && + (0 != memcmp(context->Palette + i, context->Palette + index, sizeof(T_Components)))) + index++; + if (index >= 0x60) + { + GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i); + index = 0x54 - i; // default + } + for (j = 0; j < 12; j++) // write the same color for the 12 frames + { + Write_byte(file, index); + } + } + // border + for (j = 0; j < 12; j++) + { + Write_byte(file, 0x54); // black + } + // excluded inks + for (i = 0; i < 16; i++) + { + Write_byte(file, 0); + } + // protected inks + for (i = 0; i < 16; i++) + { + Write_byte(file, 0); + } + fclose(file); - free (output); - output = NULL; + output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0); + GFX2_Log(GFX2_DEBUG, "Save_SCR() output=%p outsize=%lu r1=$%02X\n", output, outsize, r1); + if (output == NULL) + return; + + file = Open_file_write(context); + if (file == NULL) + File_error = 1; + else + { File_error = 0; + if (!Write_bytes(file, output, outsize)) + File_error = 1; + fclose(file); + } + free (output); } /** diff --git a/src/oldies.c b/src/oldies.c index 0c684e32..f684cec4 100644 --- a/src/oldies.c +++ b/src/oldies.c @@ -504,6 +504,41 @@ void CPC_set_HW_palette(T_Components * palette) memcpy(palette, CPC_Hw_Palette, sizeof(CPC_Hw_Palette)); } +int CPC_check_AMSDOS(FILE * file, word * loading_address, unsigned long * file_length) +{ + int i; + byte data[128]; + word checksum = 0; + + fseek(file, 0, SEEK_SET); + if (!Read_bytes(file, data, 128)) + return 0; + for (i = 1; i <= 11; i++) // check filename and extension + { + if (data[i] < ' ' || data[i] >= 0x7F) + return 0; + } + for (i = 0; i < 67; i++) + checksum += (word)data[i]; + if (checksum != (data[67] | (data[68] << 8))) + { + GFX2_Log(GFX2_INFO, "AMSDOS header checksum mismatch %04X != %04X\n", + checksum, data[67] | (data[68] << 8)); + return 0; + } + GFX2_Log(GFX2_DEBUG, "AMSDOS : user=%02X %.8s.%.3s %d %u(%u) bytes, load at $%04X checksum $%04X\n", + data[0], + (char *)(data + 1), (char *)(data + 9), data[18], + data[24] | (data[25] << 8), data[64] | (data[65] << 8) | (data[66] << 16), + data[26] | (data[27] << 8), checksum); + if (loading_address) + *loading_address = data[26] | (data[27] << 8); + if (file_length) + *file_length = data[64] | (data[65] << 8) | (data[66] << 16); // 24bit size + // *file_length = data[24] | (data[25] << 8); // 16bit size + return 1; +} + int DECB_Check_binary_file(FILE * f) { byte code; diff --git a/src/oldies.h b/src/oldies.h index 5638725a..bbb6277f 100644 --- a/src/oldies.h +++ b/src/oldies.h @@ -73,6 +73,19 @@ void ZX_Spectrum_set_palette(T_Components * palette); */ void CPC_set_HW_palette(T_Components * palette); +/** + * Check AMSDOS header + * + * see http://www.cpcwiki.eu/index.php/AMSDOS_Header + * + * @param[in] file an open file + * @param[out] loading_address the loading address from the header + * @param[out] file_length the file length written in the header + * @return 0 if the file does not contain a valid AMSDOS header + * @return 1 if it does. + */ +int CPC_check_AMSDOS(FILE * file, word * loading_address, unsigned long * file_length); + /** @}*/ /** @defgroup decb DECB binary format