From 089740f5829d8aecd095d38c0ac3c6988289320b Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Sat, 7 Dec 2019 15:25:14 +0100 Subject: [PATCH] move Load_MOTO/Save_MOTO etc to motoformats.c --- src/Makefile | 4 +- src/miscfileformats.c | 1355 --------------------------------------- src/motoformats.c | 1399 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1401 insertions(+), 1357 deletions(-) create mode 100644 src/motoformats.c diff --git a/src/Makefile b/src/Makefile index 32bbf40a..bd80ae4a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -809,7 +809,7 @@ OBJS = main.o init.o graph.o $(APIOBJ) misc.o special.o \ windows.o brush.o realpath.o mountlist.o input.o hotkeys.o \ transform.o pversion.o factory.o $(PLATFORMOBJ) \ loadsave.o loadsavefuncs.o \ - pngformat.o \ + pngformat.o motoformats.o \ fileformats.o miscfileformats.o libraw2crtc.o \ brush_ops.o buttons_effects.o layers.o \ oldies.o tiles.o colorred.o unicode.o gfx2surface.o \ @@ -821,7 +821,7 @@ endif TESTSOBJS = $(patsubst %.c,%.o,$(wildcard tests/*.c)) \ miscfileformats.o fileformats.o oldies.o libraw2crtc.o \ loadsavefuncs.o packbits.o tifformat.o c64load.o 6502.o \ - pngformat.o \ + pngformat.o motoformats.o \ op_c.o colorred.o \ unicode.o \ io.o realpath.o version.o pversion.o \ diff --git a/src/miscfileformats.c b/src/miscfileformats.c index 7f9b5eb3..5000a243 100644 --- a/src/miscfileformats.c +++ b/src/miscfileformats.c @@ -61,9 +61,6 @@ #include "fileformats.h" #include "gfx2mem.h" -extern char Program_version[]; // generated in pversion.c -extern const char SVN_revision[]; // generated in version.c - //////////////////////////////////// PAL //////////////////////////////////// // @@ -6198,1358 +6195,6 @@ void Load_FLI(T_IO_Context * context) fclose(file); } -/////////////////////////////// Thomson Files /////////////////////////////// - -/** - * Test for Thomson file - */ -void Test_MOTO(T_IO_Context * context, FILE * file) -{ - long file_size; - - file_size = File_length_file(file); - - File_error = 1; - if (file_size <= 10) - return; - switch (MOTO_Check_binary_file(file)) - { - case 0: // Not Thomson binary format - switch (file_size) - { - // Files in RAW formats (from TGA2teo) - case 8004: // 2 colors palette - case 8008: // 4 colors palette - case 8032: // 16 colors palette - { - char * filename; - char * path; - char * ext; - - // Check there are both FORME and COULEUR files - filename = strdup(context->File_name); - ext = strrchr(filename, '.'); - if (ext == NULL || ext == filename) - { - free(filename); - return; - } - if ((ext[-1] | 32) == 'c') - ext[-1] = (ext[-1] & 32) | 'P'; - else if ((ext[-1] | 32) == 'p') - ext[-1] = (ext[-1] & 32) | 'C'; - else - { - free(filename); - return; - } - path = Filepath_append_to_dir(context->File_directory, filename); - if (File_exists(path)) - File_error = 0; - free(path); - free(filename); - } - return; - default: - break; - } - break; - case 2: // MAP file (SAVEP/LOADP) - case 3: // TO autoloading picture - case 4: // MO autoloading picture - File_error = 0; - return; - } -} - -/** - * Load a picture for Thomson TO8/TO8D/TO9/TO9+/MO6 - * - * One of the supported format is the one produced by TGA2Teo : - * - Picture data is splitted into 2 files, one for each VRAM bank : - * - The first VRAM bank is called "forme" (shape). - * In 40col mode it stores pixels. - * - The second VRAM bank is called "couleur" (color). - * In 40col mode it store color indexes for foreground and background. - * - File extension is .BIN, character before extension is "P" for the first - * file, and "C" for the second. - * - The color palette is stored in both files after the data. - * - * The mode is detected thanks to the number of color in the palette : - * - 2 colors is 80col (640x200) - * - 4 colors is bitmap4 (320x200 4 colors) - * - 16 colors is either bitmap16 (160x200 16colors) - * or 40col (320x200 16 colors with 2 unique colors in each 8x1 pixels - * block). - * - * As it is not possible to disriminate bitmap16 and 40col, opening the "P" - * file sets bitmap16, opening the "C" file sets 40col. - * - * This function also supports .MAP files (with optional TO-SNAP extension) - * and our own "autoloading" BIN files. - * See http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO for - * a detailled description. - */ -void Load_MOTO(T_IO_Context * context) -{ - // FORME / COULEUR - FILE * file; - byte * vram_forme = NULL; - byte * vram_couleur = NULL; - long file_size; - int file_type; - int bx, x, y, i; - byte bpp = 4; - byte code; - word length, address; - int transpose = 1; // transpose the upper bits of the color plane bytes - // FFFFBBBB becomes bfFFFBBB (for TO7 compatibility) - enum MOTO_Graphic_Mode mode = MOTO_MODE_40col; - enum PIXEL_RATIO ratio = PIXEL_SIMPLE; - int width = 320, height = 200, columns = 40; - - File_error = 1; - file = Open_file_read(context); - if (file == NULL) - return; - file_size = File_length_file(file); - // Load default palette - if (Config.Clear_palette) - memset(context->Palette,0,sizeof(T_Palette)); - MOTO_set_TO7_palette(context->Palette); - - file_type = MOTO_Check_binary_file(file); - if (fseek(file, 0, SEEK_SET) < 0) - { - fclose(file); - return; - } - - if (file_type == 2) // MAP file - { - // http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 - byte map_mode, col_count, line_count; - byte * vram_current; - int end_marks; - - if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) - { - fclose(file); - return; - } - if (length < 5 || !(Read_byte(file,&map_mode) && Read_byte(file,&col_count) && Read_byte(file,&line_count))) - { - fclose(file); - return; - } - length -= 3; - columns = col_count + 1; - height = 8 * (line_count + 1); - switch(map_mode) - { - default: - case 0: // bitmap4 or 40col - width = 8 * columns; - mode = MOTO_MODE_40col; // default to 40col - bpp = 4; - break; - case 0x40: // bitmap16 - columns >>= 1; - width = 4 * columns; - mode = MOTO_MODE_bm16; - bpp = 4; - ratio = PIXEL_WIDE; - break; - case 0x80: // 80col - columns >>= 1; - width = 16 * columns; - mode = MOTO_MODE_80col; - bpp = 1; - ratio = PIXEL_TALL; - break; - } - GFX2_Log(GFX2_DEBUG, "Map mode &H%02X row=%u line=%u (%dx%d) %d\n", map_mode, col_count, line_count, width, height, columns * height); - vram_forme = GFX2_malloc(columns * height); - vram_couleur = GFX2_malloc(columns * height); - // Check extension (TO-SNAP / PPM / ???) - if (length > 36) - { - long pos_backup; - word data; - - pos_backup = ftell(file); - fseek(file, length-2, SEEK_CUR); // go to last word of chunk - Read_word_be(file, &data); - GFX2_Log(GFX2_DEBUG, "%04X\n", data); - switch (data) - { - case 0xA55A: // TO-SNAP - fseek(file, -40, SEEK_CUR); // go to begin of extension - Read_word_be(file, &data); // SCRMOD. 0=>40col, 1=>bm4, $40=>bm16, $80=>80col - GFX2_Log(GFX2_DEBUG, "SCRMOD=&H%04X ", data); - Read_word_be(file, &data); // Border color - GFX2_Log(GFX2_DEBUG, "BORDER=%u ", data); - Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc. - GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data); - if(data == 2) - { - mode = MOTO_MODE_bm4; - bpp = 2; - } - for (i = 0; i < 16; i++) - { - Read_word_be(file, &data); // Palette entry - if (data & 0x8000) data = ~data; - MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data); - } - snprintf(context->Comment, sizeof(context->Comment), "TO-SNAP .MAP file"); - break; - case 0x484C: // 'HL' PPM - fseek(file, -36, SEEK_CUR); // go to begin of extension - for (i = 0; i < 16; i++) - { - Read_word_be(file, &data); // Palette entry - if (data & 0x8000) data = ~data; - MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data); - } - Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc. - GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data); - if(data == 2) - { - mode = MOTO_MODE_bm4; - bpp = 2; - } - snprintf(context->Comment, sizeof(context->Comment), "PPM .MAP file"); - break; - default: - snprintf(context->Comment, sizeof(context->Comment), "standard .MAP file"); - } - fseek(file, pos_backup, SEEK_SET); // RESET Position - } - i = 0; - vram_current = vram_forme; - end_marks = 0; - while (length > 1) - { - byte byte1, byte2; - Read_byte(file,&byte1); - Read_byte(file,&byte2); - length-=2; - if(byte1 == 0) - { - if (byte2 == 0) - { - // end of vram stream - GFX2_Log(GFX2_DEBUG, "0000 i=%d length=%ld\n", i, length); - if (end_marks == 1) - break; - i = 0; - vram_current = vram_couleur; - end_marks++; - } - else while(byte2-- > 0 && length > 0) // copy - { - Read_byte(file,vram_current + i); - length--; - i += columns; // to the next line - if (i >= columns * height) - { - if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col) - i -= (columns * height - 1); // to the 1st line of the next column - else - { - i -= columns * height; // back to the 1st line of the current column - if (vram_current == vram_forme) // other VRAM - vram_current = vram_couleur; - else - { - vram_current = vram_forme; - i++; // next column - } - } - } - } - } - else while(byte1-- > 0) // run length - { - vram_current[i] = byte2; - i += columns; // to the next line - if (i >= columns * height) - { - if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col) - i -= (columns * height - 1); // to the 1st line of the next column - else - { - i -= columns * height; // back to the 1st line of the current column - if (vram_current == vram_forme) // other VRAM - vram_current = vram_couleur; - else - { - vram_current = vram_forme; - i++; // next column - } - } - } - } - } - fclose(file); - } - else if(file_type == 3 || file_type == 4) - { - if (file_type == 4) // MO file - { - transpose = 0; - MOTO_set_MO5_palette(context->Palette); - } - - do - { - if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) - { - if (vram_forme) - break; - fclose(file); - return; - } - // MO5/MO6 VRAM address is &H0000 - // TO7/TO8/TO9 VRAM addres is &H4000 - if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0)) - { - if (vram_forme == NULL) - { - vram_forme = calloc(8192, 1); - Read_bytes(file, vram_forme, length); - length = 0; - } - else if (vram_couleur == NULL) - { - vram_couleur = calloc(8192, 1); - Read_bytes(file, vram_couleur, length); - if (length >= 8032) - { - for (x = 0; x < 16; x++) - { - // 1 byte Blue (4 lower bits) - // 1 byte Green (4 upper bits) / Red (4 lower bits) - MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x], - vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]); - } - if (length >= 8064) - { - memcpy(context->Comment, vram_couleur + 8032, 32); - if (vram_couleur[8063] >= '0' && vram_couleur[8063] <= '3') - mode = vram_couleur[8063] - '0'; - } - context->Comment[COMMENT_SIZE] = '\0'; - } - length = 0; - } - } - if (length > 0) - fseek(file, length, SEEK_CUR); - } while(code == 0); - fclose(file); - switch (mode) - { - case MOTO_MODE_40col: // default - break; - case MOTO_MODE_bm4: - bpp = 2; - break; - case MOTO_MODE_80col: - bpp = 1; - width = 640; - ratio = PIXEL_TALL; - break; - case MOTO_MODE_bm16: - width = 160; - ratio = PIXEL_WIDE; - break; - } - } - else - { - char * filename; - char * path; - char * ext; - int n_colors; - - vram_forme = GFX2_malloc(file_size); - if (vram_forme == NULL) - { - fclose(file); - return; - } - if (!Read_bytes(file, vram_forme, file_size)) - { - free(vram_forme); - fclose(file); - return; - } - n_colors = (file_size - 8000) / 2; - switch(n_colors) - { - case 16: - bpp = 4; - // 16 colors : either 40col or bm16 mode ! - // select later - break; - case 4: - bpp = 2; - mode = MOTO_MODE_bm4; - break; - default: - bpp = 1; - mode = MOTO_MODE_80col; - width = 640; - ratio = PIXEL_TALL; - } - filename = strdup(context->File_name); - ext = strrchr(filename, '.'); - if (ext == NULL || ext == filename) - { - free(vram_forme); - free(filename); - return; - } - if ((ext[-1] | 32) == 'c') - { - vram_couleur = vram_forme; - vram_forme = NULL; - ext[-1] = (ext[-1] & 32) | 'P'; - } - else if ((ext[-1] | 32) == 'p') - { - ext[-1] = (ext[-1] & 32) | 'C'; - if (n_colors == 16) - { - mode = MOTO_MODE_bm16; - width = 160; - ratio = PIXEL_WIDE; - } - } - else - { - free(vram_forme); - free(filename); - return; - } - path = Filepath_append_to_dir(context->File_directory, filename); - file = fopen(path, "rb"); - if (file == NULL) - GFX2_Log(GFX2_ERROR, "Failed to open %s\n", path); - free(path); - free(filename); - if (vram_forme == NULL) - { - vram_forme = GFX2_malloc(file_size); - if (vram_forme == NULL) - { - free(vram_couleur); - fclose(file); - return; - } - Read_bytes(file,vram_forme,file_size); - } - else - { - vram_couleur = GFX2_malloc(file_size); - if (vram_couleur == NULL) - { - free(vram_forme); - fclose(file); - return; - } - Read_bytes(file,vram_couleur,file_size); - } - fclose(file); - GFX2_Log(GFX2_DEBUG, "MO/TO: %s,%s file_size=%ld n_colors=%d\n", context->File_name, filename, file_size, n_colors); - for (x = 0; x < n_colors; x++) - { - // 1 byte Blue (4 lower bits) - // 1 byte Green (4 upper bits) / Red (4 lower bits) - MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x], - vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]); - } - } - Pre_load(context, width, height, file_size, FORMAT_MOTO, ratio, bpp); - if (mode == MOTO_MODE_40col) - Set_image_mode(context, IMAGE_MODE_THOMSON); - File_error = 0; - i = 0; - for (y = 0; y < height; y++) - { - for (bx = 0; bx < columns; bx++) - { - byte couleur_forme; - byte couleur_fond; - byte forme, couleurs; - - forme = vram_forme[i]; - if (vram_couleur) - couleurs = vram_couleur[i]; - else - couleurs = (mode == MOTO_MODE_40col) ? 0x01 : 0x00; - i++; - switch(mode) - { - case MOTO_MODE_bm4: - for (x = bx*8; x < bx*8+8; x++) - { - Set_pixel(context, x, y, ((forme & 0x80) >> 6) | ((couleurs & 0x80) >> 7)); - forme <<= 1; - couleurs <<= 1; - } -#if 0 // the following would be for the alternate bm4 mode - for (x = bx*8; x < bx*8+4; x++) - { - Set_pixel(context, x, y, couleurs >> 6); - couleurs <<= 2; - } - for (x = bx*8 + 4; x < bx*8+8; x++) - { - Set_pixel(context, x, y, forme >> 6); - forme <<= 2; - } -#endif - break; - case MOTO_MODE_bm16: - Set_pixel(context, bx*4, y, forme >> 4); - Set_pixel(context, bx*4+1, y, forme & 0x0F); - Set_pixel(context, bx*4+2, y, couleurs >> 4); - Set_pixel(context, bx*4+3, y, couleurs & 0x0F); - break; - case MOTO_MODE_80col: - for (x = bx*16; x < bx*16+8; x++) - { - Set_pixel(context, x, y, (forme & 0x80) >> 7); - Set_pixel(context, x+8, y, (couleurs & 0x80) >> 7); - forme <<= 1; - couleurs <<= 1; - } - break; - case MOTO_MODE_40col: - default: - if (transpose) - { - // the color plane byte is bfFFFBBB (for TO7 compatibility) - // with the upper bits of both foreground (forme) and - // background (fond) inverted. - couleur_forme = ((couleurs & 0x78) >> 3) ^ 0x08; - couleur_fond = ((couleurs & 7) | ((couleurs & 0x80) >> 4)) ^ 0x08; - } - else - { - // MO5 : the color plane byte is FFFFBBBB - couleur_forme = couleurs >> 4; - couleur_fond = couleurs & 0x0F; - } - for (x = bx*8; x < bx*8+8; x++) - { - Set_pixel(context, x, y, (forme & 0x80)?couleur_forme:couleur_fond); - forme <<= 1; - } - } - } - } - free(vram_forme); - free(vram_couleur); -} - -/** - * Pack a stream of byte in the format used by Thomson MO/TO MAP files. - * - * - 00 cc xx yy .. : encodes a "copy run" (cc = bytes to copy) - * - cc xx : encodes a "repeat run" (cc > 0 : count) - */ -//#define MOTO_MAP_NOPACKING -unsigned int MOTO_MAP_pack(byte * packed, const byte * unpacked, unsigned int unpacked_len) -{ - unsigned int src; - unsigned int dst = 0; - unsigned int count; -#ifndef MOTO_MAP_NOPACKING - unsigned int repeat; - unsigned int i; - word * counts; -#endif - - GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack(%p, %p, %u)\n", packed, unpacked, unpacked_len); - if (unpacked_len == 0) - return 0; - if (unpacked_len == 1) - { - packed[0] = 1; - packed[1] = unpacked[0]; - return 2; - } -#ifdef MOTO_MAP_NOPACKING - // compression disabled - src = 0; - while ((unpacked_len - src) > 255) - { - packed[dst++] = 0; - packed[dst++] = 255; - memcpy(packed+dst, unpacked+src, 255); - dst += 255; - src += 255; - } - count = unpacked_len - src; - packed[dst++] = 0; - packed[dst++] = count; - memcpy(packed+dst, unpacked+src, count); - dst += count; - src += count; - return dst; -#else - counts = GFX2_malloc(sizeof(word) * (unpacked_len + 1)); - i = 0; - repeat = (unpacked[0] == unpacked[1]); - count = 2; - src = 2; - // 1st step : count lenght of the Copy runs and Repeat runs - while (src < unpacked_len) - { - if (repeat) - { - if (unpacked[src-1] == unpacked[src]) - count++; - else - { - // flush the repeat run - counts[i++] = count | 0x8000; // 0x8000 is the marker for repeat runs - count = 1; - repeat = 0; - } - } - else - { - if (unpacked[src-1] != unpacked[src]) - count++; - else if (count == 1) - { - count++; - repeat = 1; - } - else - { - // flush the copy run - counts[i++] = (count-1) | (count == 2 ? 0x8000 : 0); // mark copy run of 1 as repeat of 1 - count = 2; - repeat = 1; - } - } - src++; - } - // flush the last run - counts[i++] = ((repeat || count == 1) ? 0x8000 : 0) | count; - counts[i++] = 0; // end marker - // check consistency of counts - count = 0; - for (i = 0; counts[i] != 0; i++) - count += (counts[i] & ~0x8000); - if (count != unpacked_len) - GFX2_Log(GFX2_ERROR, "*** encoding error in MOTO_MAP_pack() *** count=%u unpacked_len=%u\n", - count, unpacked_len); - // output optimized packed stream - // repeat run are encoded cc xx - // copy run are encoded 00 cc xx xx xx xx - i = 0; - src = 0; - while (counts[i] != 0) - { - while (counts[i] & 0x8000) // repeat run - { - count = counts[i] & ~0x8000; - GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u repeat %u times %02x\n", src, i, count, unpacked[src]); - while(count > 255) - { - packed[dst++] = 255; - packed[dst++] = unpacked[src]; - count -= 255; - src += 255; - } - packed[dst++] = count; - packed[dst++] = unpacked[src]; - src += count; - i++; - } - while (counts[i] != 0 && !(counts[i] & 0x8000)) // copy run - { - // calculate the "savings" of repeat runs between 2 copy run - int savings = 0; - unsigned int j; - GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u copy %u bytes\n", src, i, counts[i]); - for (j = i + 1; counts[j] & 0x8000; j++) // check repeat runs until the next copy run - { - count = counts[j] & ~0x8000; - if (savings < 0 && (savings + (int)count - 2) > 0) - break; - savings += count - 2; // a repeat run outputs 2 bytes for count bytes of input - } - count = counts[i]; -GFX2_Log(GFX2_DEBUG, " savings=%d i=%u j=%u (counts[j]=0x%04x)\n", savings, i, j, counts[j]); - if (savings < 2 && (j > i + 1)) - { - unsigned int k; - if (counts[j] == 0) // go to the end of stream - { - for (k = i + 1; k < j; k++) - count += (counts[k] & ~0x8000); - GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extend copy from %u to %u\n", src, counts[i], count); - i = j - 1; - } - else - { - for (k = i + 1; k < j; k++) - count += (counts[k] & ~0x8000); - if (!(counts[j] & 0x8000)) - { // merge with the next copy run (and the repeat runs between) - GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u merge savings=%d\n", src, savings); - i = j; - counts[i] += count; - continue; - } - else - { // merge with the next few repeat runs - GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extends savings=%d\n", src, savings); - i = j - 1; - } - } - } - while (count > 255) - { - packed[dst++] = 0; - packed[dst++] = 255; - memcpy(packed+dst, unpacked+src, 255); - dst += 255; - src += 255; - count -= 255; - } - packed[dst++] = 0; - packed[dst++] = count; - memcpy(packed+dst, unpacked+src, count); - dst += count; - src += count; - i++; - } - } - free(counts); - return dst; -#endif -} - - -/** - * GUI window to choose Thomson MO/TO saving parameters - * - * @param[out] machine target machine - * @param[out] format file format (0 = BIN, 1 = MAP) - * @param[in,out] mode video mode @ref MOTO_Graphic_Mode - */ -static int Save_MOTO_window(enum MOTO_Machine_Type * machine, int * format, enum MOTO_Graphic_Mode * mode) -{ - int button; - T_Dropdown_button * machine_dd; - T_Dropdown_button * format_dd; - T_Dropdown_button * mode_dd; - static const char * mode_list[] = { "40col", "80col", "bm4", "bm16" }; - char text_info[24]; - - Open_window(200, 125, "Thomson MO/TO Saving"); - Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1 - Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2 - - Print_in_window(13,18,"Target Machine:",MC_Dark,MC_Light); - machine_dd = Window_set_dropdown_button(10,28,110,15,100, - (*mode == MOTO_MODE_40col) ? "TO7/TO7-70" : "TO9/TO8/TO9+", - 1, 0, 1, LEFT_SIDE,0); // 3 - if (*mode == MOTO_MODE_40col) - Window_dropdown_add_item(machine_dd, MACHINE_TO7, "TO7/TO7-70"); - Window_dropdown_add_item(machine_dd, MACHINE_TO8, "TO9/TO8/TO9+"); - if (*mode == MOTO_MODE_40col) - Window_dropdown_add_item(machine_dd, MACHINE_MO5, "MO5"); - Window_dropdown_add_item(machine_dd, MACHINE_MO6, "MO6"); - - Print_in_window(13,46,"Format:",MC_Dark,MC_Light); - format_dd = Window_set_dropdown_button(10,56,110,15,92,"BIN",1, 0, 1, LEFT_SIDE,0); // 4 - Window_dropdown_add_item(format_dd, 0, "BIN"); - Window_dropdown_add_item(format_dd, 1, "MAP/TO-SNAP"); - - Print_in_window(136,46,"Mode:",MC_Dark,MC_Light); - mode_dd = Window_set_dropdown_button(136,56,54,15,44,mode_list[*mode],1, 0, 1, LEFT_SIDE,0); // 5 - if (*mode == MOTO_MODE_40col) - Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); - if (*mode == MOTO_MODE_80col) - Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); - if (*mode == MOTO_MODE_40col) - Window_dropdown_add_item(mode_dd, MOTO_MODE_bm4, mode_list[MOTO_MODE_bm4]); - if (*mode == MOTO_MODE_bm16) - Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); - - Update_window_area(0,0,Window_width,Window_height); - Display_cursor(); - do - { - button = Window_clicked_button(); - if (Is_shortcut(Key, 0x100+BUTTON_HELP)) - { - Key = 0; - Window_help(BUTTON_SAVE, "THOMSON MO/TO FORMAT"); - } - else switch (button) - { - case 3: - *machine = (enum MOTO_Machine_Type)Window_attribute2; - break; - case 4: - *format = Window_attribute2; - break; - case 5: - *mode = (enum MOTO_Graphic_Mode)Window_attribute2; - break; - } - Hide_cursor(); - //"ABCDEFGHIJKLMNOPQRSTUVW" - memset(text_info, ' ', 23); - text_info[23] = '\0'; - if (*machine == MACHINE_TO7 || *machine == MACHINE_TO770 || *machine == MACHINE_MO5) - { - if (*mode != MOTO_MODE_40col) - snprintf(text_info, sizeof(text_info), "%s only supports 40col", - (*machine == MACHINE_MO5) ? "MO5" : "TO7"); - else if (*format == 1) - strncpy(text_info, "No TO-SNAP extension. ", sizeof(text_info)); - else - strncpy(text_info, "No palette to save. ", sizeof(text_info)); - } - Print_in_window(9, 80, text_info, MC_Dark, MC_Light); - Display_cursor(); - } while(button!=1 && button!=2); - - Close_window(); - Display_cursor(); - return button==1; -} - -/** - * Save a picture in MAP or BIN Thomson MO/TO file format. - * - * File format details : - * http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO - */ -void Save_MOTO(T_IO_Context * context) -{ - int transpose = 1; // transpose upper bits in "couleur" vram - enum MOTO_Machine_Type target_machine = MACHINE_TO7; - int format = 0; // 0 = BIN, 1 = MAP - enum MOTO_Graphic_Mode mode; - FILE * file = NULL; - byte * vram_forme; - byte * vram_couleur; - int i, x, y, bx; - word reg_prc = 0xE7C3; // PRC : TO7/8/9 0xE7C3 ; MO5/MO6 0xA7C0 - byte prc_value = 0x65;// Value to write to PRC to select VRAM bank - // MO5 : 0x51 - word vram_address = 0x4000; // 4000 on TO7/TO8/TO9, 0000 on MO5/MO6 - - File_error = 1; - - /** - * In the future we could support other resolution for .MAP - * format. - * And even in .BIN format, we could store less lines. */ - if (context->Height != 200) - { - Warning_message("must be 640x200, 320x200 or 160x200"); - return; - } - - switch (context->Width) - { - case 160: - mode = MOTO_MODE_bm16; - target_machine = MACHINE_TO8; - break; - case 640: - mode = MOTO_MODE_80col; - target_machine = MACHINE_TO8; - break; - case 320: - mode = MOTO_MODE_40col; // or bm4 - break; - default: - Warning_message("must be 640x200, 320x200 or 160x200"); - return; - } - - if (!Save_MOTO_window(&target_machine, &format, &mode)) - return; - - if (target_machine == MACHINE_MO5 || target_machine == MACHINE_MO6) - { - reg_prc = 0xA7C0; // PRC : MO5/MO6 0xA7C0 - prc_value = 0x51; - vram_address = 0; - transpose = 0; - } - - vram_forme = GFX2_malloc(8192); - vram_couleur = GFX2_malloc(8192); - switch (mode) - { - case MOTO_MODE_40col: - { - /** - * The 40col encoding algorithm is optimized for further vertical - * RLE packing. The "attibute" byte is kept as constant as possible - * between adjacent blocks. - */ - unsigned color_freq[16]; - unsigned max_freq = 0; - byte previous_fond = 0, previous_forme = 0; - byte most_used_color = 0; - - // search for most used color to prefer it as background color - for (i = 0; i < 16; i++) - color_freq[i] = 0; - for (y = 0; y < context->Height; y++) - { - for (x = 0; x < context->Width; x++) - { - byte col = Get_pixel(context, x, y); - if (col > 15) - { - Warning_with_format("color %u > 15 at pixel (%d,%d)", col, x, y); - goto error; - } - color_freq[col]++; - } - } - for (i = 0; i < 16; i++) - { - if (color_freq[i] > max_freq) - { - max_freq = color_freq[i]; - most_used_color = (byte)i; // most used color - } - } - previous_fond = most_used_color; - max_freq = 0; - for (i = 0; i < 16; i++) - { - if (i != most_used_color && color_freq[i] > max_freq) - { - max_freq = color_freq[i]; - previous_forme = (byte)i; // second most used color - } - } - GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme); - - if (target_machine == MACHINE_MO5) - { - /** - * For MO5 we use a different 40col algorithm - * to make sure the last pixel of a GPL and the first the next - * are both FORME or both FOND, else we get an ugly glitch on the - * EFGJ033 Gate Array MO5! - */ - byte forme_byte = 0; - byte couleur_byte = 0x10; - GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using MO5 algo\n"); - for (y = 0; y < context->Height; y++) - { - for (bx = 0; bx < 40; bx++) - { - byte fond = 0xff, forme = 0xff; - forme_byte &= 1; // Last bit of the previous FORME byte - x = bx*8; - if (forme_byte) - forme = Get_pixel(context, x, y); - else - fond = Get_pixel(context, x, y); - while (++x < bx * 8 + 8) - { - byte col = Get_pixel(context, x, y); - forme_byte <<= 1; - if (col == forme) - forme_byte |= 1; - else if (col != fond) - { - if (forme == 0xff) - { - forme_byte |= 1; - forme = col; - } - else if (fond == 0xff) - fond = col; - else - { - Warning_with_format("Constraint error at pixel (%d,%d)", x, y); - goto error; - } - } - } - if (forme != 0xff) - couleur_byte = (forme << 4) | (couleur_byte & 0x0f); - if (fond != 0xff) - couleur_byte = (couleur_byte & 0xf0) | fond; - vram_forme[bx+y*40] = forme_byte; - vram_couleur[bx+y*40] = couleur_byte; - } - } - } - else - { - GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using optimized algo\n"); - // encoding of each 8x1 block - for (bx = 0; bx < 40; bx++) - { - for (y = 0; y < context->Height; y++) - { - byte forme_byte = 1; - byte col; - byte c1, c1_count = 1; - byte c2 = 0xff, c2_count = 0; - byte fond, forme; - x = bx * 8; - c1 = Get_pixel(context, x, y); - while (++x < bx * 8 + 8) - { - forme_byte <<= 1; - col = Get_pixel(context, x, y); - if (col > 15) - { - Warning_with_format("color %d > 15 at pixel (%d,%d)", col, x, y); - goto error; - } - if (col == c1) - { - forme_byte |= 1; - c1_count++; - } - else - { - c2_count++; - if (c2 == 0xff) - c2 = col; - else if (col != c2) - { - Warning_with_format("constraint error at pixel (%d,%d)", x, y); - goto error; - } - } - } - if (c2 == 0xff) - { - // Only one color in the 8x1 block - if (c1 == previous_fond) - c2 = previous_forme; - else - c2 = previous_fond; - } - // select background color (fond) - // and foreground color (forme) - if (c1 == previous_fond) - { - fond = c1; - forme = c2; - forme_byte = ~forme_byte; - } - else if (c2 == previous_fond) - { - fond = c2; - forme = c1; - } - else if (c1 == most_used_color) - { - fond = c1; - forme = c2; - forme_byte = ~forme_byte; - } - else if (c2 == most_used_color) - { - fond = c2; - forme = c1; - } - else if (c1_count >= c2_count) - { - fond = c1; - forme = c2; - forme_byte = ~forme_byte; - } - else - { - fond = c2; - forme = c1; - } - // write to VRAM - vram_forme[bx+y*40] = forme_byte; - // transpose for TO7 compatibility - if (transpose) - vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0; - else - vram_couleur[bx+y*40] = fond | (forme << 4); - previous_fond = fond; - previous_forme = forme; - } - if (transpose) - { - previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4; - previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8; - } - else - { - previous_fond = vram_couleur[bx] & 15; - previous_forme = vram_couleur[bx] >> 4; - } - } - } - } - break; - case MOTO_MODE_80col: - for (bx = 0; bx < context->Width / 16; bx++) - { - for (y = 0; y < context->Height; y++) - { - byte val = 0; - for (x = bx * 16; x < bx*16 + 8; x++) - val = (val << 1) | Get_pixel(context, x, y); - vram_forme[y*(context->Width/16)+bx] = val; - for (; x < bx*16 + 16; x++) - val = (val << 1) | Get_pixel(context, x, y); - vram_couleur[y*(context->Width/16)+bx] = val; - } - } - break; - case MOTO_MODE_bm4: - for (y = 0; y < context->Height; y++) - { - for (bx = 0; bx < context->Width / 8; bx++) - { - byte val1 = 0, val2 = 0, pixel; - for (x = bx * 8; x < bx*8 + 8; x++) - { - pixel = Get_pixel(context, x, y); - if (pixel > 3) - { - Warning_with_format("color %d > 3 at pixel (%d,%d)", pixel, x, y); - goto error; - } - val1 = (val1 << 1) | (pixel >> 1); - val2 = (val2 << 1) | (pixel & 1); - } - vram_forme[y*(context->Width/8)+bx] = val1; - vram_couleur[y*(context->Width/8)+bx] = val2; - } - } - break; - case MOTO_MODE_bm16: - for (bx = 0; bx < context->Width / 4; bx++) - { - for (y = 0; y < context->Height; y++) - { - vram_forme[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4, y) << 4) | Get_pixel(context, bx*4+1, y); - vram_couleur[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4+2, y) << 4) | Get_pixel(context, bx*4+3, y); - } - } - break; - } - // palette - for (i = 0; i < 16; i++) - { - word to8color = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + i); - vram_forme[8000+i*2] = to8color >> 8; - vram_forme[8000+i*2+1] = to8color & 0xFF; - } - - file = Open_file_write(context); - if (file == NULL) - goto error; - - if (format == 0) // BIN - { - word chunk_length; - - if (target_machine == MACHINE_TO7 || target_machine == MACHINE_TO770 || target_machine == MACHINE_MO5) - chunk_length = 8000; // Do not save palette - else - { - chunk_length = 8000 + 32 + 32; // data + palette + comment - // Commentaire - if (context->Comment[0] != '\0') - strncpy((char *)vram_forme + 8032, context->Comment, 32); - else - snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision); - // also saves the video mode - vram_forme[8063] = '0' + mode; - memcpy(vram_couleur + 8000, vram_forme + 8000, 64); - } - - // Format BIN - // TO8/TO9 : set LGAMOD 0xE7DC 40col=0 bm4=0x21 80col=0x2a bm16=0x7b - if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value)) - goto error; - if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_forme)) - goto error; - prc_value &= 0xFE; // select color data - if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value)) - goto error; - if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_couleur)) - goto error; - if (!DECB_BIN_Add_End(file, 0x0000)) - goto error; - } - else - { - // format MAP with TO-SNAP extensions - byte * unpacked_data; - byte * packed_data; - - unpacked_data = GFX2_malloc(16*1024); - packed_data = GFX2_malloc(16*1024); - if (packed_data == NULL || unpacked_data == NULL) - { - GFX2_Log(GFX2_ERROR, "Failed to allocate 2x16kB of memory\n"); - free(packed_data); - free(unpacked_data); - goto error; - } - switch (mode) - { - case MOTO_MODE_40col: - case MOTO_MODE_bm4: - packed_data[0] = 0; // mode - packed_data[1] = (context->Width / 8) - 1; - break; - case MOTO_MODE_80col: - packed_data[0] = 0x80; // mode - packed_data[1] = (context->Width / 8) - 1; - break; - case MOTO_MODE_bm16: - packed_data[0] = 0x40; // mode - packed_data[1] = (context->Width / 2) - 1; - break; - } - packed_data[2] = (context->Height / 8) - 1; - // 1st step : put data to pack in a linear buffer - // 2nd step : pack data - i = 0; - switch (mode) - { - case MOTO_MODE_40col: - case MOTO_MODE_bm4: - for (bx = 0; bx <= packed_data[1]; bx++) - { - for (y = 0; y < context->Height; y++) - { - unpacked_data[i] = vram_forme[bx + y*(packed_data[1]+1)]; - unpacked_data[i+8192] = vram_couleur[bx + y*(packed_data[1]+1)]; - i++; - } - } - i = 3; - i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1)); - packed_data[i++] = 0; // ending of VRAM forme packing - packed_data[i++] = 0; - i += MOTO_MAP_pack(packed_data+i, unpacked_data + 8192, context->Height * (packed_data[1]+1)); - packed_data[i++] = 0; // ending of VRAM couleur packing - packed_data[i++] = 0; - break; - case MOTO_MODE_80col: - case MOTO_MODE_bm16: - for (bx = 0; bx < (packed_data[1] + 1) / 2; bx++) - { - for (y = 0; y < context->Height; y++) - unpacked_data[i++] = vram_forme[bx + y*(packed_data[1]+1)/2]; - for (y = 0; y < context->Height; y++) - unpacked_data[i++] = vram_couleur[bx + y*(packed_data[1]+1)/2]; - } - i = 3; - i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1)); - packed_data[i++] = 0; // ending of VRAM forme packing - packed_data[i++] = 0; - packed_data[i++] = 0; // ending of VRAM couleur packing - packed_data[i++] = 0; - break; - } - if (i&1) // align - packed_data[i++] = 0; - if (target_machine != MACHINE_TO7 && target_machine != MACHINE_TO770 && target_machine != MACHINE_MO5) - { - // add TO-SNAP extension - // see http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 - // bytes 0-1 : Hardware video mode (value of SCRMOD 0x605F) - packed_data[i++] = 0; - switch (mode) - { - case MOTO_MODE_40col: - packed_data[i++] = 0; - break; - case MOTO_MODE_bm4: - packed_data[i++] = 0x01; - break; - case MOTO_MODE_80col: - packed_data[i++] = 0x80; - break; - case MOTO_MODE_bm16: - packed_data[i++] = 0x40; - break; - } - // bytes 2-3 : Border color - packed_data[i++] = 0; - packed_data[i++] = 0; - // bytes 4-5 : BASIC video mode (CONSOLE,,,,X) - packed_data[i++] = 0; - switch (mode) - { - case MOTO_MODE_40col: - packed_data[i++] = 0; - break; - case MOTO_MODE_bm4: - packed_data[i++] = 2; - break; - case MOTO_MODE_80col: - packed_data[i++] = 1; - break; - case MOTO_MODE_bm16: - packed_data[i++] = 3; - break; - } - // bytes 6-37 : BGR palette - for (x = 0; x < 16; x++) - { - word bgr = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + x); - packed_data[i++] = bgr >> 8; - packed_data[i++] = bgr & 0xff; - } - // bytes 38-39 : TO-SNAP signature - packed_data[i++] = 0xA5; - packed_data[i++] = 0x5A; - } - - free(unpacked_data); - - if (!DECB_BIN_Add_Chunk(file, i, 0, packed_data) || - !DECB_BIN_Add_End(file, 0x0000)) - { - free(packed_data); - goto error; - } - free(packed_data); - } - fclose(file); - File_error = 0; - return; - -error: - free(vram_forme); - free(vram_couleur); - if (file) - fclose(file); - File_error = 1; -} - /////////////////////////////// Apple II Files ////////////////////////////// /** diff --git a/src/motoformats.c b/src/motoformats.c new file mode 100644 index 00000000..1b415a65 --- /dev/null +++ b/src/motoformats.c @@ -0,0 +1,1399 @@ +/* vim:expandtab:ts=2 sw=2: +*/ +/* Grafx2 - The Ultimate 256-color bitmap paint program + + Copyright 2018-2019 Thomas Bernard + Copyright 2011 Pawel Góralski + Copyright 2009 Petter Lindquist + Copyright 2008 Yves Rizoud + Copyright 2008 Franck Charlet + Copyright 2007-2011 Adrien Destugues + Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) + + Grafx2 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; version 2 + of the License. + + Grafx2 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. + + You should have received a copy of the GNU General Public License + along with Grafx2; if not, see +*/ + +///@file motoformats.c +/// Formats for the MO/TO Thomson machines + +#include +#include +#include "struct.h" +#include "io.h" +#include "loadsave.h" +#include "loadsavefuncs.h" +#include "fileformats.h" +#include "oldies.h" +#include "input.h" +#include "engine.h" +#include "screen.h" +#include "windows.h" +#include "help.h" +#include "gfx2mem.h" +#include "gfx2log.h" + +extern char Program_version[]; // generated in pversion.c +extern const char SVN_revision[]; // generated in version.c + +/////////////////////////////// Thomson Files /////////////////////////////// + +/** + * Test for Thomson file + */ +void Test_MOTO(T_IO_Context * context, FILE * file) +{ + long file_size; + + file_size = File_length_file(file); + + File_error = 1; + if (file_size <= 10) + return; + switch (MOTO_Check_binary_file(file)) + { + case 0: // Not Thomson binary format + switch (file_size) + { + // Files in RAW formats (from TGA2teo) + case 8004: // 2 colors palette + case 8008: // 4 colors palette + case 8032: // 16 colors palette + { + char * filename; + char * path; + char * ext; + + // Check there are both FORME and COULEUR files + filename = strdup(context->File_name); + ext = strrchr(filename, '.'); + if (ext == NULL || ext == filename) + { + free(filename); + return; + } + if ((ext[-1] | 32) == 'c') + ext[-1] = (ext[-1] & 32) | 'P'; + else if ((ext[-1] | 32) == 'p') + ext[-1] = (ext[-1] & 32) | 'C'; + else + { + free(filename); + return; + } + path = Filepath_append_to_dir(context->File_directory, filename); + if (File_exists(path)) + File_error = 0; + free(path); + free(filename); + } + return; + default: + break; + } + break; + case 2: // MAP file (SAVEP/LOADP) + case 3: // TO autoloading picture + case 4: // MO autoloading picture + File_error = 0; + return; + } +} + +/** + * Load a picture for Thomson TO8/TO8D/TO9/TO9+/MO6 + * + * One of the supported format is the one produced by TGA2Teo : + * - Picture data is splitted into 2 files, one for each VRAM bank : + * - The first VRAM bank is called "forme" (shape). + * In 40col mode it stores pixels. + * - The second VRAM bank is called "couleur" (color). + * In 40col mode it store color indexes for foreground and background. + * - File extension is .BIN, character before extension is "P" for the first + * file, and "C" for the second. + * - The color palette is stored in both files after the data. + * + * The mode is detected thanks to the number of color in the palette : + * - 2 colors is 80col (640x200) + * - 4 colors is bitmap4 (320x200 4 colors) + * - 16 colors is either bitmap16 (160x200 16colors) + * or 40col (320x200 16 colors with 2 unique colors in each 8x1 pixels + * block). + * + * As it is not possible to disriminate bitmap16 and 40col, opening the "P" + * file sets bitmap16, opening the "C" file sets 40col. + * + * This function also supports .MAP files (with optional TO-SNAP extension) + * and our own "autoloading" BIN files. + * See http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO for + * a detailled description. + */ +void Load_MOTO(T_IO_Context * context) +{ + // FORME / COULEUR + FILE * file; + byte * vram_forme = NULL; + byte * vram_couleur = NULL; + long file_size; + int file_type; + int bx, x, y, i; + byte bpp = 4; + byte code; + word length, address; + int transpose = 1; // transpose the upper bits of the color plane bytes + // FFFFBBBB becomes bfFFFBBB (for TO7 compatibility) + enum MOTO_Graphic_Mode mode = MOTO_MODE_40col; + enum PIXEL_RATIO ratio = PIXEL_SIMPLE; + int width = 320, height = 200, columns = 40; + + File_error = 1; + file = Open_file_read(context); + if (file == NULL) + return; + file_size = File_length_file(file); + // Load default palette + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + MOTO_set_TO7_palette(context->Palette); + + file_type = MOTO_Check_binary_file(file); + if (fseek(file, 0, SEEK_SET) < 0) + { + fclose(file); + return; + } + + if (file_type == 2) // MAP file + { + // http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 + byte map_mode, col_count, line_count; + byte * vram_current; + int end_marks; + + if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) + { + fclose(file); + return; + } + if (length < 5 || !(Read_byte(file,&map_mode) && Read_byte(file,&col_count) && Read_byte(file,&line_count))) + { + fclose(file); + return; + } + length -= 3; + columns = col_count + 1; + height = 8 * (line_count + 1); + switch(map_mode) + { + default: + case 0: // bitmap4 or 40col + width = 8 * columns; + mode = MOTO_MODE_40col; // default to 40col + bpp = 4; + break; + case 0x40: // bitmap16 + columns >>= 1; + width = 4 * columns; + mode = MOTO_MODE_bm16; + bpp = 4; + ratio = PIXEL_WIDE; + break; + case 0x80: // 80col + columns >>= 1; + width = 16 * columns; + mode = MOTO_MODE_80col; + bpp = 1; + ratio = PIXEL_TALL; + break; + } + GFX2_Log(GFX2_DEBUG, "Map mode &H%02X row=%u line=%u (%dx%d) %d\n", map_mode, col_count, line_count, width, height, columns * height); + vram_forme = GFX2_malloc(columns * height); + vram_couleur = GFX2_malloc(columns * height); + // Check extension (TO-SNAP / PPM / ???) + if (length > 36) + { + long pos_backup; + word data; + + pos_backup = ftell(file); + fseek(file, length-2, SEEK_CUR); // go to last word of chunk + Read_word_be(file, &data); + GFX2_Log(GFX2_DEBUG, "%04X\n", data); + switch (data) + { + case 0xA55A: // TO-SNAP + fseek(file, -40, SEEK_CUR); // go to begin of extension + Read_word_be(file, &data); // SCRMOD. 0=>40col, 1=>bm4, $40=>bm16, $80=>80col + GFX2_Log(GFX2_DEBUG, "SCRMOD=&H%04X ", data); + Read_word_be(file, &data); // Border color + GFX2_Log(GFX2_DEBUG, "BORDER=%u ", data); + Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc. + GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data); + if(data == 2) + { + mode = MOTO_MODE_bm4; + bpp = 2; + } + for (i = 0; i < 16; i++) + { + Read_word_be(file, &data); // Palette entry + if (data & 0x8000) data = ~data; + MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data); + } + snprintf(context->Comment, sizeof(context->Comment), "TO-SNAP .MAP file"); + break; + case 0x484C: // 'HL' PPM + fseek(file, -36, SEEK_CUR); // go to begin of extension + for (i = 0; i < 16; i++) + { + Read_word_be(file, &data); // Palette entry + if (data & 0x8000) data = ~data; + MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data); + } + Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc. + GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data); + if(data == 2) + { + mode = MOTO_MODE_bm4; + bpp = 2; + } + snprintf(context->Comment, sizeof(context->Comment), "PPM .MAP file"); + break; + default: + snprintf(context->Comment, sizeof(context->Comment), "standard .MAP file"); + } + fseek(file, pos_backup, SEEK_SET); // RESET Position + } + i = 0; + vram_current = vram_forme; + end_marks = 0; + while (length > 1) + { + byte byte1, byte2; + Read_byte(file,&byte1); + Read_byte(file,&byte2); + length-=2; + if(byte1 == 0) + { + if (byte2 == 0) + { + // end of vram stream + GFX2_Log(GFX2_DEBUG, "0000 i=%d length=%ld\n", i, length); + if (end_marks == 1) + break; + i = 0; + vram_current = vram_couleur; + end_marks++; + } + else while(byte2-- > 0 && length > 0) // copy + { + Read_byte(file,vram_current + i); + length--; + i += columns; // to the next line + if (i >= columns * height) + { + if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col) + i -= (columns * height - 1); // to the 1st line of the next column + else + { + i -= columns * height; // back to the 1st line of the current column + if (vram_current == vram_forme) // other VRAM + vram_current = vram_couleur; + else + { + vram_current = vram_forme; + i++; // next column + } + } + } + } + } + else while(byte1-- > 0) // run length + { + vram_current[i] = byte2; + i += columns; // to the next line + if (i >= columns * height) + { + if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col) + i -= (columns * height - 1); // to the 1st line of the next column + else + { + i -= columns * height; // back to the 1st line of the current column + if (vram_current == vram_forme) // other VRAM + vram_current = vram_couleur; + else + { + vram_current = vram_forme; + i++; // next column + } + } + } + } + } + fclose(file); + } + else if(file_type == 3 || file_type == 4) + { + if (file_type == 4) // MO file + { + transpose = 0; + MOTO_set_MO5_palette(context->Palette); + } + + do + { + if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) + { + if (vram_forme) + break; + fclose(file); + return; + } + // MO5/MO6 VRAM address is &H0000 + // TO7/TO8/TO9 VRAM addres is &H4000 + if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0)) + { + if (vram_forme == NULL) + { + vram_forme = calloc(8192, 1); + Read_bytes(file, vram_forme, length); + length = 0; + } + else if (vram_couleur == NULL) + { + vram_couleur = calloc(8192, 1); + Read_bytes(file, vram_couleur, length); + if (length >= 8032) + { + for (x = 0; x < 16; x++) + { + // 1 byte Blue (4 lower bits) + // 1 byte Green (4 upper bits) / Red (4 lower bits) + MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x], + vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]); + } + if (length >= 8064) + { + memcpy(context->Comment, vram_couleur + 8032, 32); + if (vram_couleur[8063] >= '0' && vram_couleur[8063] <= '3') + mode = vram_couleur[8063] - '0'; + } + context->Comment[COMMENT_SIZE] = '\0'; + } + length = 0; + } + } + if (length > 0) + fseek(file, length, SEEK_CUR); + } while(code == 0); + fclose(file); + switch (mode) + { + case MOTO_MODE_40col: // default + break; + case MOTO_MODE_bm4: + bpp = 2; + break; + case MOTO_MODE_80col: + bpp = 1; + width = 640; + ratio = PIXEL_TALL; + break; + case MOTO_MODE_bm16: + width = 160; + ratio = PIXEL_WIDE; + break; + } + } + else + { + char * filename; + char * path; + char * ext; + int n_colors; + + vram_forme = GFX2_malloc(file_size); + if (vram_forme == NULL) + { + fclose(file); + return; + } + if (!Read_bytes(file, vram_forme, file_size)) + { + free(vram_forme); + fclose(file); + return; + } + n_colors = (file_size - 8000) / 2; + switch(n_colors) + { + case 16: + bpp = 4; + // 16 colors : either 40col or bm16 mode ! + // select later + break; + case 4: + bpp = 2; + mode = MOTO_MODE_bm4; + break; + default: + bpp = 1; + mode = MOTO_MODE_80col; + width = 640; + ratio = PIXEL_TALL; + } + filename = strdup(context->File_name); + ext = strrchr(filename, '.'); + if (ext == NULL || ext == filename) + { + free(vram_forme); + free(filename); + return; + } + if ((ext[-1] | 32) == 'c') + { + vram_couleur = vram_forme; + vram_forme = NULL; + ext[-1] = (ext[-1] & 32) | 'P'; + } + else if ((ext[-1] | 32) == 'p') + { + ext[-1] = (ext[-1] & 32) | 'C'; + if (n_colors == 16) + { + mode = MOTO_MODE_bm16; + width = 160; + ratio = PIXEL_WIDE; + } + } + else + { + free(vram_forme); + free(filename); + return; + } + path = Filepath_append_to_dir(context->File_directory, filename); + file = fopen(path, "rb"); + if (file == NULL) + GFX2_Log(GFX2_ERROR, "Failed to open %s\n", path); + free(path); + free(filename); + if (vram_forme == NULL) + { + vram_forme = GFX2_malloc(file_size); + if (vram_forme == NULL) + { + free(vram_couleur); + fclose(file); + return; + } + Read_bytes(file,vram_forme,file_size); + } + else + { + vram_couleur = GFX2_malloc(file_size); + if (vram_couleur == NULL) + { + free(vram_forme); + fclose(file); + return; + } + Read_bytes(file,vram_couleur,file_size); + } + fclose(file); + GFX2_Log(GFX2_DEBUG, "MO/TO: %s,%s file_size=%ld n_colors=%d\n", context->File_name, filename, file_size, n_colors); + for (x = 0; x < n_colors; x++) + { + // 1 byte Blue (4 lower bits) + // 1 byte Green (4 upper bits) / Red (4 lower bits) + MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x], + vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]); + } + } + Pre_load(context, width, height, file_size, FORMAT_MOTO, ratio, bpp); + if (mode == MOTO_MODE_40col) + Set_image_mode(context, IMAGE_MODE_THOMSON); + File_error = 0; + i = 0; + for (y = 0; y < height; y++) + { + for (bx = 0; bx < columns; bx++) + { + byte couleur_forme; + byte couleur_fond; + byte forme, couleurs; + + forme = vram_forme[i]; + if (vram_couleur) + couleurs = vram_couleur[i]; + else + couleurs = (mode == MOTO_MODE_40col) ? 0x01 : 0x00; + i++; + switch(mode) + { + case MOTO_MODE_bm4: + for (x = bx*8; x < bx*8+8; x++) + { + Set_pixel(context, x, y, ((forme & 0x80) >> 6) | ((couleurs & 0x80) >> 7)); + forme <<= 1; + couleurs <<= 1; + } +#if 0 // the following would be for the alternate bm4 mode + for (x = bx*8; x < bx*8+4; x++) + { + Set_pixel(context, x, y, couleurs >> 6); + couleurs <<= 2; + } + for (x = bx*8 + 4; x < bx*8+8; x++) + { + Set_pixel(context, x, y, forme >> 6); + forme <<= 2; + } +#endif + break; + case MOTO_MODE_bm16: + Set_pixel(context, bx*4, y, forme >> 4); + Set_pixel(context, bx*4+1, y, forme & 0x0F); + Set_pixel(context, bx*4+2, y, couleurs >> 4); + Set_pixel(context, bx*4+3, y, couleurs & 0x0F); + break; + case MOTO_MODE_80col: + for (x = bx*16; x < bx*16+8; x++) + { + Set_pixel(context, x, y, (forme & 0x80) >> 7); + Set_pixel(context, x+8, y, (couleurs & 0x80) >> 7); + forme <<= 1; + couleurs <<= 1; + } + break; + case MOTO_MODE_40col: + default: + if (transpose) + { + // the color plane byte is bfFFFBBB (for TO7 compatibility) + // with the upper bits of both foreground (forme) and + // background (fond) inverted. + couleur_forme = ((couleurs & 0x78) >> 3) ^ 0x08; + couleur_fond = ((couleurs & 7) | ((couleurs & 0x80) >> 4)) ^ 0x08; + } + else + { + // MO5 : the color plane byte is FFFFBBBB + couleur_forme = couleurs >> 4; + couleur_fond = couleurs & 0x0F; + } + for (x = bx*8; x < bx*8+8; x++) + { + Set_pixel(context, x, y, (forme & 0x80)?couleur_forme:couleur_fond); + forme <<= 1; + } + } + } + } + free(vram_forme); + free(vram_couleur); +} + +/** + * Pack a stream of byte in the format used by Thomson MO/TO MAP files. + * + * - 00 cc xx yy .. : encodes a "copy run" (cc = bytes to copy) + * - cc xx : encodes a "repeat run" (cc > 0 : count) + */ +//#define MOTO_MAP_NOPACKING +unsigned int MOTO_MAP_pack(byte * packed, const byte * unpacked, unsigned int unpacked_len) +{ + unsigned int src; + unsigned int dst = 0; + unsigned int count; +#ifndef MOTO_MAP_NOPACKING + unsigned int repeat; + unsigned int i; + word * counts; +#endif + + GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack(%p, %p, %u)\n", packed, unpacked, unpacked_len); + if (unpacked_len == 0) + return 0; + if (unpacked_len == 1) + { + packed[0] = 1; + packed[1] = unpacked[0]; + return 2; + } +#ifdef MOTO_MAP_NOPACKING + // compression disabled + src = 0; + while ((unpacked_len - src) > 255) + { + packed[dst++] = 0; + packed[dst++] = 255; + memcpy(packed+dst, unpacked+src, 255); + dst += 255; + src += 255; + } + count = unpacked_len - src; + packed[dst++] = 0; + packed[dst++] = count; + memcpy(packed+dst, unpacked+src, count); + dst += count; + src += count; + return dst; +#else + counts = GFX2_malloc(sizeof(word) * (unpacked_len + 1)); + i = 0; + repeat = (unpacked[0] == unpacked[1]); + count = 2; + src = 2; + // 1st step : count lenght of the Copy runs and Repeat runs + while (src < unpacked_len) + { + if (repeat) + { + if (unpacked[src-1] == unpacked[src]) + count++; + else + { + // flush the repeat run + counts[i++] = count | 0x8000; // 0x8000 is the marker for repeat runs + count = 1; + repeat = 0; + } + } + else + { + if (unpacked[src-1] != unpacked[src]) + count++; + else if (count == 1) + { + count++; + repeat = 1; + } + else + { + // flush the copy run + counts[i++] = (count-1) | (count == 2 ? 0x8000 : 0); // mark copy run of 1 as repeat of 1 + count = 2; + repeat = 1; + } + } + src++; + } + // flush the last run + counts[i++] = ((repeat || count == 1) ? 0x8000 : 0) | count; + counts[i++] = 0; // end marker + // check consistency of counts + count = 0; + for (i = 0; counts[i] != 0; i++) + count += (counts[i] & ~0x8000); + if (count != unpacked_len) + GFX2_Log(GFX2_ERROR, "*** encoding error in MOTO_MAP_pack() *** count=%u unpacked_len=%u\n", + count, unpacked_len); + // output optimized packed stream + // repeat run are encoded cc xx + // copy run are encoded 00 cc xx xx xx xx + i = 0; + src = 0; + while (counts[i] != 0) + { + while (counts[i] & 0x8000) // repeat run + { + count = counts[i] & ~0x8000; + GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u repeat %u times %02x\n", src, i, count, unpacked[src]); + while(count > 255) + { + packed[dst++] = 255; + packed[dst++] = unpacked[src]; + count -= 255; + src += 255; + } + packed[dst++] = count; + packed[dst++] = unpacked[src]; + src += count; + i++; + } + while (counts[i] != 0 && !(counts[i] & 0x8000)) // copy run + { + // calculate the "savings" of repeat runs between 2 copy run + int savings = 0; + unsigned int j; + GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u copy %u bytes\n", src, i, counts[i]); + for (j = i + 1; counts[j] & 0x8000; j++) // check repeat runs until the next copy run + { + count = counts[j] & ~0x8000; + if (savings < 0 && (savings + (int)count - 2) > 0) + break; + savings += count - 2; // a repeat run outputs 2 bytes for count bytes of input + } + count = counts[i]; +GFX2_Log(GFX2_DEBUG, " savings=%d i=%u j=%u (counts[j]=0x%04x)\n", savings, i, j, counts[j]); + if (savings < 2 && (j > i + 1)) + { + unsigned int k; + if (counts[j] == 0) // go to the end of stream + { + for (k = i + 1; k < j; k++) + count += (counts[k] & ~0x8000); + GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extend copy from %u to %u\n", src, counts[i], count); + i = j - 1; + } + else + { + for (k = i + 1; k < j; k++) + count += (counts[k] & ~0x8000); + if (!(counts[j] & 0x8000)) + { // merge with the next copy run (and the repeat runs between) + GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u merge savings=%d\n", src, savings); + i = j; + counts[i] += count; + continue; + } + else + { // merge with the next few repeat runs + GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extends savings=%d\n", src, savings); + i = j - 1; + } + } + } + while (count > 255) + { + packed[dst++] = 0; + packed[dst++] = 255; + memcpy(packed+dst, unpacked+src, 255); + dst += 255; + src += 255; + count -= 255; + } + packed[dst++] = 0; + packed[dst++] = count; + memcpy(packed+dst, unpacked+src, count); + dst += count; + src += count; + i++; + } + } + free(counts); + return dst; +#endif +} + + +/** + * GUI window to choose Thomson MO/TO saving parameters + * + * @param[out] machine target machine + * @param[out] format file format (0 = BIN, 1 = MAP) + * @param[in,out] mode video mode @ref MOTO_Graphic_Mode + */ +static int Save_MOTO_window(enum MOTO_Machine_Type * machine, int * format, enum MOTO_Graphic_Mode * mode) +{ + int button; + T_Dropdown_button * machine_dd; + T_Dropdown_button * format_dd; + T_Dropdown_button * mode_dd; + static const char * mode_list[] = { "40col", "80col", "bm4", "bm16" }; + char text_info[24]; + + Open_window(200, 125, "Thomson MO/TO Saving"); + Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1 + Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2 + + Print_in_window(13,18,"Target Machine:",MC_Dark,MC_Light); + machine_dd = Window_set_dropdown_button(10,28,110,15,100, + (*mode == MOTO_MODE_40col) ? "TO7/TO7-70" : "TO9/TO8/TO9+", + 1, 0, 1, LEFT_SIDE,0); // 3 + if (*mode == MOTO_MODE_40col) + Window_dropdown_add_item(machine_dd, MACHINE_TO7, "TO7/TO7-70"); + Window_dropdown_add_item(machine_dd, MACHINE_TO8, "TO9/TO8/TO9+"); + if (*mode == MOTO_MODE_40col) + Window_dropdown_add_item(machine_dd, MACHINE_MO5, "MO5"); + Window_dropdown_add_item(machine_dd, MACHINE_MO6, "MO6"); + + Print_in_window(13,46,"Format:",MC_Dark,MC_Light); + format_dd = Window_set_dropdown_button(10,56,110,15,92,"BIN",1, 0, 1, LEFT_SIDE,0); // 4 + Window_dropdown_add_item(format_dd, 0, "BIN"); + Window_dropdown_add_item(format_dd, 1, "MAP/TO-SNAP"); + + Print_in_window(136,46,"Mode:",MC_Dark,MC_Light); + mode_dd = Window_set_dropdown_button(136,56,54,15,44,mode_list[*mode],1, 0, 1, LEFT_SIDE,0); // 5 + if (*mode == MOTO_MODE_40col) + Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); + if (*mode == MOTO_MODE_80col) + Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); + if (*mode == MOTO_MODE_40col) + Window_dropdown_add_item(mode_dd, MOTO_MODE_bm4, mode_list[MOTO_MODE_bm4]); + if (*mode == MOTO_MODE_bm16) + Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); + + Update_window_area(0,0,Window_width,Window_height); + Display_cursor(); + do + { + button = Window_clicked_button(); + if (Is_shortcut(Key, 0x100+BUTTON_HELP)) + { + Key = 0; + Window_help(BUTTON_SAVE, "THOMSON MO/TO FORMAT"); + } + else switch (button) + { + case 3: + *machine = (enum MOTO_Machine_Type)Window_attribute2; + break; + case 4: + *format = Window_attribute2; + break; + case 5: + *mode = (enum MOTO_Graphic_Mode)Window_attribute2; + break; + } + Hide_cursor(); + //"ABCDEFGHIJKLMNOPQRSTUVW" + memset(text_info, ' ', 23); + text_info[23] = '\0'; + if (*machine == MACHINE_TO7 || *machine == MACHINE_TO770 || *machine == MACHINE_MO5) + { + if (*mode != MOTO_MODE_40col) + snprintf(text_info, sizeof(text_info), "%s only supports 40col", + (*machine == MACHINE_MO5) ? "MO5" : "TO7"); + else if (*format == 1) + strncpy(text_info, "No TO-SNAP extension. ", sizeof(text_info)); + else + strncpy(text_info, "No palette to save. ", sizeof(text_info)); + } + Print_in_window(9, 80, text_info, MC_Dark, MC_Light); + Display_cursor(); + } while(button!=1 && button!=2); + + Close_window(); + Display_cursor(); + return button==1; +} + +/** + * Save a picture in MAP or BIN Thomson MO/TO file format. + * + * File format details : + * http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO + */ +void Save_MOTO(T_IO_Context * context) +{ + int transpose = 1; // transpose upper bits in "couleur" vram + enum MOTO_Machine_Type target_machine = MACHINE_TO7; + int format = 0; // 0 = BIN, 1 = MAP + enum MOTO_Graphic_Mode mode; + FILE * file = NULL; + byte * vram_forme; + byte * vram_couleur; + int i, x, y, bx; + word reg_prc = 0xE7C3; // PRC : TO7/8/9 0xE7C3 ; MO5/MO6 0xA7C0 + byte prc_value = 0x65;// Value to write to PRC to select VRAM bank + // MO5 : 0x51 + word vram_address = 0x4000; // 4000 on TO7/TO8/TO9, 0000 on MO5/MO6 + + File_error = 1; + + /** + * In the future we could support other resolution for .MAP + * format. + * And even in .BIN format, we could store less lines. */ + if (context->Height != 200) + { + Warning_message("must be 640x200, 320x200 or 160x200"); + return; + } + + switch (context->Width) + { + case 160: + mode = MOTO_MODE_bm16; + target_machine = MACHINE_TO8; + break; + case 640: + mode = MOTO_MODE_80col; + target_machine = MACHINE_TO8; + break; + case 320: + mode = MOTO_MODE_40col; // or bm4 + break; + default: + Warning_message("must be 640x200, 320x200 or 160x200"); + return; + } + + if (!Save_MOTO_window(&target_machine, &format, &mode)) + return; + + if (target_machine == MACHINE_MO5 || target_machine == MACHINE_MO6) + { + reg_prc = 0xA7C0; // PRC : MO5/MO6 0xA7C0 + prc_value = 0x51; + vram_address = 0; + transpose = 0; + } + + vram_forme = GFX2_malloc(8192); + vram_couleur = GFX2_malloc(8192); + switch (mode) + { + case MOTO_MODE_40col: + { + /** + * The 40col encoding algorithm is optimized for further vertical + * RLE packing. The "attibute" byte is kept as constant as possible + * between adjacent blocks. + */ + unsigned color_freq[16]; + unsigned max_freq = 0; + byte previous_fond = 0, previous_forme = 0; + byte most_used_color = 0; + + // search for most used color to prefer it as background color + for (i = 0; i < 16; i++) + color_freq[i] = 0; + for (y = 0; y < context->Height; y++) + { + for (x = 0; x < context->Width; x++) + { + byte col = Get_pixel(context, x, y); + if (col > 15) + { + Warning_with_format("color %u > 15 at pixel (%d,%d)", col, x, y); + goto error; + } + color_freq[col]++; + } + } + for (i = 0; i < 16; i++) + { + if (color_freq[i] > max_freq) + { + max_freq = color_freq[i]; + most_used_color = (byte)i; // most used color + } + } + previous_fond = most_used_color; + max_freq = 0; + for (i = 0; i < 16; i++) + { + if (i != most_used_color && color_freq[i] > max_freq) + { + max_freq = color_freq[i]; + previous_forme = (byte)i; // second most used color + } + } + GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme); + + if (target_machine == MACHINE_MO5) + { + /** + * For MO5 we use a different 40col algorithm + * to make sure the last pixel of a GPL and the first the next + * are both FORME or both FOND, else we get an ugly glitch on the + * EFGJ033 Gate Array MO5! + */ + byte forme_byte = 0; + byte couleur_byte = 0x10; + GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using MO5 algo\n"); + for (y = 0; y < context->Height; y++) + { + for (bx = 0; bx < 40; bx++) + { + byte fond = 0xff, forme = 0xff; + forme_byte &= 1; // Last bit of the previous FORME byte + x = bx*8; + if (forme_byte) + forme = Get_pixel(context, x, y); + else + fond = Get_pixel(context, x, y); + while (++x < bx * 8 + 8) + { + byte col = Get_pixel(context, x, y); + forme_byte <<= 1; + if (col == forme) + forme_byte |= 1; + else if (col != fond) + { + if (forme == 0xff) + { + forme_byte |= 1; + forme = col; + } + else if (fond == 0xff) + fond = col; + else + { + Warning_with_format("Constraint error at pixel (%d,%d)", x, y); + goto error; + } + } + } + if (forme != 0xff) + couleur_byte = (forme << 4) | (couleur_byte & 0x0f); + if (fond != 0xff) + couleur_byte = (couleur_byte & 0xf0) | fond; + vram_forme[bx+y*40] = forme_byte; + vram_couleur[bx+y*40] = couleur_byte; + } + } + } + else + { + GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using optimized algo\n"); + // encoding of each 8x1 block + for (bx = 0; bx < 40; bx++) + { + for (y = 0; y < context->Height; y++) + { + byte forme_byte = 1; + byte col; + byte c1, c1_count = 1; + byte c2 = 0xff, c2_count = 0; + byte fond, forme; + x = bx * 8; + c1 = Get_pixel(context, x, y); + while (++x < bx * 8 + 8) + { + forme_byte <<= 1; + col = Get_pixel(context, x, y); + if (col > 15) + { + Warning_with_format("color %d > 15 at pixel (%d,%d)", col, x, y); + goto error; + } + if (col == c1) + { + forme_byte |= 1; + c1_count++; + } + else + { + c2_count++; + if (c2 == 0xff) + c2 = col; + else if (col != c2) + { + Warning_with_format("constraint error at pixel (%d,%d)", x, y); + goto error; + } + } + } + if (c2 == 0xff) + { + // Only one color in the 8x1 block + if (c1 == previous_fond) + c2 = previous_forme; + else + c2 = previous_fond; + } + // select background color (fond) + // and foreground color (forme) + if (c1 == previous_fond) + { + fond = c1; + forme = c2; + forme_byte = ~forme_byte; + } + else if (c2 == previous_fond) + { + fond = c2; + forme = c1; + } + else if (c1 == most_used_color) + { + fond = c1; + forme = c2; + forme_byte = ~forme_byte; + } + else if (c2 == most_used_color) + { + fond = c2; + forme = c1; + } + else if (c1_count >= c2_count) + { + fond = c1; + forme = c2; + forme_byte = ~forme_byte; + } + else + { + fond = c2; + forme = c1; + } + // write to VRAM + vram_forme[bx+y*40] = forme_byte; + // transpose for TO7 compatibility + if (transpose) + vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0; + else + vram_couleur[bx+y*40] = fond | (forme << 4); + previous_fond = fond; + previous_forme = forme; + } + if (transpose) + { + previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4; + previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8; + } + else + { + previous_fond = vram_couleur[bx] & 15; + previous_forme = vram_couleur[bx] >> 4; + } + } + } + } + break; + case MOTO_MODE_80col: + for (bx = 0; bx < context->Width / 16; bx++) + { + for (y = 0; y < context->Height; y++) + { + byte val = 0; + for (x = bx * 16; x < bx*16 + 8; x++) + val = (val << 1) | Get_pixel(context, x, y); + vram_forme[y*(context->Width/16)+bx] = val; + for (; x < bx*16 + 16; x++) + val = (val << 1) | Get_pixel(context, x, y); + vram_couleur[y*(context->Width/16)+bx] = val; + } + } + break; + case MOTO_MODE_bm4: + for (y = 0; y < context->Height; y++) + { + for (bx = 0; bx < context->Width / 8; bx++) + { + byte val1 = 0, val2 = 0, pixel; + for (x = bx * 8; x < bx*8 + 8; x++) + { + pixel = Get_pixel(context, x, y); + if (pixel > 3) + { + Warning_with_format("color %d > 3 at pixel (%d,%d)", pixel, x, y); + goto error; + } + val1 = (val1 << 1) | (pixel >> 1); + val2 = (val2 << 1) | (pixel & 1); + } + vram_forme[y*(context->Width/8)+bx] = val1; + vram_couleur[y*(context->Width/8)+bx] = val2; + } + } + break; + case MOTO_MODE_bm16: + for (bx = 0; bx < context->Width / 4; bx++) + { + for (y = 0; y < context->Height; y++) + { + vram_forme[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4, y) << 4) | Get_pixel(context, bx*4+1, y); + vram_couleur[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4+2, y) << 4) | Get_pixel(context, bx*4+3, y); + } + } + break; + } + // palette + for (i = 0; i < 16; i++) + { + word to8color = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + i); + vram_forme[8000+i*2] = to8color >> 8; + vram_forme[8000+i*2+1] = to8color & 0xFF; + } + + file = Open_file_write(context); + if (file == NULL) + goto error; + + if (format == 0) // BIN + { + word chunk_length; + + if (target_machine == MACHINE_TO7 || target_machine == MACHINE_TO770 || target_machine == MACHINE_MO5) + chunk_length = 8000; // Do not save palette + else + { + chunk_length = 8000 + 32 + 32; // data + palette + comment + // Commentaire + if (context->Comment[0] != '\0') + strncpy((char *)vram_forme + 8032, context->Comment, 32); + else + snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision); + // also saves the video mode + vram_forme[8063] = '0' + mode; + memcpy(vram_couleur + 8000, vram_forme + 8000, 64); + } + + // Format BIN + // TO8/TO9 : set LGAMOD 0xE7DC 40col=0 bm4=0x21 80col=0x2a bm16=0x7b + if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value)) + goto error; + if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_forme)) + goto error; + prc_value &= 0xFE; // select color data + if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value)) + goto error; + if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_couleur)) + goto error; + if (!DECB_BIN_Add_End(file, 0x0000)) + goto error; + } + else + { + // format MAP with TO-SNAP extensions + byte * unpacked_data; + byte * packed_data; + + unpacked_data = GFX2_malloc(16*1024); + packed_data = GFX2_malloc(16*1024); + if (packed_data == NULL || unpacked_data == NULL) + { + GFX2_Log(GFX2_ERROR, "Failed to allocate 2x16kB of memory\n"); + free(packed_data); + free(unpacked_data); + goto error; + } + switch (mode) + { + case MOTO_MODE_40col: + case MOTO_MODE_bm4: + packed_data[0] = 0; // mode + packed_data[1] = (context->Width / 8) - 1; + break; + case MOTO_MODE_80col: + packed_data[0] = 0x80; // mode + packed_data[1] = (context->Width / 8) - 1; + break; + case MOTO_MODE_bm16: + packed_data[0] = 0x40; // mode + packed_data[1] = (context->Width / 2) - 1; + break; + } + packed_data[2] = (context->Height / 8) - 1; + // 1st step : put data to pack in a linear buffer + // 2nd step : pack data + i = 0; + switch (mode) + { + case MOTO_MODE_40col: + case MOTO_MODE_bm4: + for (bx = 0; bx <= packed_data[1]; bx++) + { + for (y = 0; y < context->Height; y++) + { + unpacked_data[i] = vram_forme[bx + y*(packed_data[1]+1)]; + unpacked_data[i+8192] = vram_couleur[bx + y*(packed_data[1]+1)]; + i++; + } + } + i = 3; + i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1)); + packed_data[i++] = 0; // ending of VRAM forme packing + packed_data[i++] = 0; + i += MOTO_MAP_pack(packed_data+i, unpacked_data + 8192, context->Height * (packed_data[1]+1)); + packed_data[i++] = 0; // ending of VRAM couleur packing + packed_data[i++] = 0; + break; + case MOTO_MODE_80col: + case MOTO_MODE_bm16: + for (bx = 0; bx < (packed_data[1] + 1) / 2; bx++) + { + for (y = 0; y < context->Height; y++) + unpacked_data[i++] = vram_forme[bx + y*(packed_data[1]+1)/2]; + for (y = 0; y < context->Height; y++) + unpacked_data[i++] = vram_couleur[bx + y*(packed_data[1]+1)/2]; + } + i = 3; + i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1)); + packed_data[i++] = 0; // ending of VRAM forme packing + packed_data[i++] = 0; + packed_data[i++] = 0; // ending of VRAM couleur packing + packed_data[i++] = 0; + break; + } + if (i&1) // align + packed_data[i++] = 0; + if (target_machine != MACHINE_TO7 && target_machine != MACHINE_TO770 && target_machine != MACHINE_MO5) + { + // add TO-SNAP extension + // see http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 + // bytes 0-1 : Hardware video mode (value of SCRMOD 0x605F) + packed_data[i++] = 0; + switch (mode) + { + case MOTO_MODE_40col: + packed_data[i++] = 0; + break; + case MOTO_MODE_bm4: + packed_data[i++] = 0x01; + break; + case MOTO_MODE_80col: + packed_data[i++] = 0x80; + break; + case MOTO_MODE_bm16: + packed_data[i++] = 0x40; + break; + } + // bytes 2-3 : Border color + packed_data[i++] = 0; + packed_data[i++] = 0; + // bytes 4-5 : BASIC video mode (CONSOLE,,,,X) + packed_data[i++] = 0; + switch (mode) + { + case MOTO_MODE_40col: + packed_data[i++] = 0; + break; + case MOTO_MODE_bm4: + packed_data[i++] = 2; + break; + case MOTO_MODE_80col: + packed_data[i++] = 1; + break; + case MOTO_MODE_bm16: + packed_data[i++] = 3; + break; + } + // bytes 6-37 : BGR palette + for (x = 0; x < 16; x++) + { + word bgr = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + x); + packed_data[i++] = bgr >> 8; + packed_data[i++] = bgr & 0xff; + } + // bytes 38-39 : TO-SNAP signature + packed_data[i++] = 0xA5; + packed_data[i++] = 0x5A; + } + + free(unpacked_data); + + if (!DECB_BIN_Add_Chunk(file, i, 0, packed_data) || + !DECB_BIN_Add_End(file, 0x0000)) + { + free(packed_data); + goto error; + } + free(packed_data); + } + fclose(file); + File_error = 0; + return; + +error: + free(vram_forme); + free(vram_couleur); + if (file) + fclose(file); + File_error = 1; +}