From 1f601d06ae460f7b901e61185cc13f494a3d5328 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Sat, 18 Jan 2020 02:32:02 +0100 Subject: [PATCH] support Atari ST CA1 format (Crack Art) --- src/const.h | 1 + src/fileformats.h | 5 + src/helpfile.h | 1 + src/loadsave.c | 3 +- src/stformats.c | 419 +++++++++++++++++++++++++++++++++++++++- src/tests/testformats.c | 1 + 6 files changed, 428 insertions(+), 2 deletions(-) diff --git a/src/const.h b/src/const.h index cd686c7c..2e91f6f5 100644 --- a/src/const.h +++ b/src/const.h @@ -138,6 +138,7 @@ enum FILE_FORMATS FORMAT_SCx, ///< ColoRIX FORMAT_PI1, ///< Atari ST Degas FORMAT_PC1, ///< Atari ST Degas Elite + FORMAT_CA1, ///< Atari ST CrackArt FORMAT_CEL, ///< Atari ST Cyber Paint Cell FORMAT_NEO, ///< Atari ST NeoChrome FORMAT_TNY, ///< Atari ST Tiny Stuff diff --git a/src/fileformats.h b/src/fileformats.h index 0498f4a6..7eee999f 100644 --- a/src/fileformats.h +++ b/src/fileformats.h @@ -101,6 +101,11 @@ void Test_PC1(T_IO_Context *, FILE *); void Load_PC1(T_IO_Context *); void Save_PC1(T_IO_Context *); +// -- CA1 ------------------------------------------------------------------- +void Test_CA1(T_IO_Context *, FILE *); +void Load_CA1(T_IO_Context *); +void Save_CA1(T_IO_Context *); + // -- Tiny Stuff ------------------------------------------------------------ void Test_TNY(T_IO_Context *, FILE *); void Load_TNY(T_IO_Context *); diff --git a/src/helpfile.h b/src/helpfile.h index ed932315..4485945a 100644 --- a/src/helpfile.h +++ b/src/helpfile.h @@ -520,6 +520,7 @@ static const T_Help_table helptable_credits[] = HELP_TITLE(" FILE FORMATS CREDITS") HELP_TEXT ("") HELP_TEXT (" BMP,ICO : Microsoft") + HELP_TEXT (" CA1 : Crack Art (Jaybee and Roy)") HELP_TEXT (" CEL,KCF : K.O.S. (KISekae Set system)") HELP_TEXT (" CM5 : SyX") HELP_TEXT (" EGX : Targhan & Supersly / Cargosoft") diff --git a/src/loadsave.c b/src/loadsave.c index 5fecd051..dfc04f60 100644 --- a/src/loadsave.c +++ b/src/loadsave.c @@ -115,7 +115,7 @@ static void Save_ClipBoard_Image(T_IO_Context *); 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;cel;" - "pi1;pc1;pi2;pc2;pi3;pc3;neo;tny;tn1;tn2;tn3;tn4;" + "pi1;pc1;pi2;pc2;pi3;pc3;neo;tny;tn1;tn2;tn3;tn4;ca1;ca2;ca3;" "c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;prg;pmg;rpm;" "gpx;" "cpc;scr;win;pph;cm5;go1;" @@ -139,6 +139,7 @@ const T_Format File_formats[] = { {FORMAT_SCx, " sc?", Test_SCx, Load_SCx, Save_SCx, 0, 0, 0, "sc?", "sci;scq;scf;scn;sco"}, {FORMAT_PI1, " pi1", Test_PI1, Load_PI1, Save_PI1, 0, 0, 0, "pi1", "pi1;pi2;pi3"}, {FORMAT_PC1, " pc1", Test_PC1, Load_PC1, Save_PC1, 0, 0, 0, "pc1", "pc1;pc2;pc3"}, + {FORMAT_CA1, " ca1", Test_CA1, Load_CA1, Save_CA1, 0, 0, 0, "ca1", "ca1;ca2;ca3"}, {FORMAT_TNY, " tny", Test_TNY, Load_TNY, Save_TNY, 0, 0, 0, "tny", "tny;tn1;tn2;tn3;tn4"}, {FORMAT_CEL, " cel", Test_CEL, Load_CEL, Save_CEL, 0, 0, 0, "cel", "cel"}, {FORMAT_NEO, " neo", Test_NEO, Load_NEO, Save_NEO, 0, 0, 0, "neo", "neo"}, diff --git a/src/stformats.c b/src/stformats.c index 4f73e883..e7ff5a20 100644 --- a/src/stformats.c +++ b/src/stformats.c @@ -100,7 +100,7 @@ void PI2_8b_to_16p(const byte * src,byte * dest) //// CODAGE d'une partie d'IMAGE //// -void PI1_16p_to_8b(byte * src,byte * dest) +void PI1_16p_to_8b(const byte * src, byte * dest) { int i; // index du pixel à calculer word byte_mask; // Masque de codage @@ -1513,4 +1513,421 @@ error: Remove_file(context); } +/** + * test for CrackArt format. + * + * Test that the files starts with the "CA" signature, + * then 1 byte for the compressed flag (0 or 1), + * then 1 byte for the resolution (0=low, 1=med, 2=high) + */ +void Test_CA1(T_IO_Context * context, FILE * file) +{ + byte header[4]; + + (void)context; + File_error = 1; + if (!Read_bytes(file, header, 4)) + return; + if (header[0] == 'C' && header[1] == 'A') + { + if ((header[2] & 0xfe) == 0 && (header[3] < 3)) + File_error = 0; + } +} + +void Load_CA1(T_IO_Context * context) +{ + FILE * file; + byte sig[2]; + byte compressed; + byte res; + byte * buffer; + + File_error = 1; + buffer = GFX2_malloc(32000); + if (buffer == NULL) + return; + file = Open_file_read(context); + if (file == NULL) + { + free(buffer); + return; + } + if (Read_bytes(file, sig, 2) && Read_byte(file, &compressed) + && Read_byte(file, &res)) + { + unsigned long file_size; + short width = 640, height = 200; + enum PIXEL_RATIO ratio = PIXEL_SIMPLE; + byte bpp = 4; + + file_size = File_length_file(file); + GFX2_Log(GFX2_DEBUG, "Signature : '%c%c' %s res=%d\n", + sig[0], sig[1], compressed ? "compressed" : "", res); + switch (res) + { + case 0: + width = 320; + break; + case 1: + ratio = PIXEL_TALL; + bpp = 2; + break; + case 2: + height = 400; + bpp = 1; + } + File_error = 0; + + Pre_load(context, width, height, file_size, FORMAT_CA1, ratio, bpp); + if (File_error == 0) + { + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + memset(buffer, 0, 32); + if (res == 2) + { + // black & white + buffer[0] = 0xff; + buffer[1] = 0xff; + } + else + { + if (!Read_bytes(file, buffer, 1 << (bpp + 1))) + File_error = 1; + } + PI1_decode_palette(buffer, context->Palette); + if (compressed) + { + byte escape, delta; + word offset; + + if (!(Read_byte(file, &escape) && Read_byte(file, &delta) && Read_word_be(file, &offset))) + File_error = 1; + else if(offset != 0) + { + int i = 0, c = 0; + + GFX2_Log(GFX2_DEBUG, " escape=%02X delta=%02X offset=%hu\n", escape, delta, offset); + memset(buffer, delta, 32000); + while (c < 32000 && File_error == 0) + { + byte cmd, data; + word repeat; + + repeat = 0; + data = delta; + if (!Read_byte(file, &cmd)) + File_error = 1; + if (cmd == escape) + { + if (!Read_byte(file, &cmd)) + File_error = 1; + if (cmd == 0) + { + // byte count repeat + if (!Read_byte(file, &cmd) || !Read_byte(file, &data)) + File_error = 1; + repeat = cmd; + GFX2_Log(GFX2_DEBUG, "byte count repeat : 0x%02x,0x%02x,%hu,0x%02x\n", escape, 0, repeat, data); + } + else if (cmd == 1) + { + // word count repeat + if (!Read_word_be(file, &repeat) || !Read_byte(file, &data)) + File_error = 1; + GFX2_Log(GFX2_DEBUG, "word count repeat : 0x%02x,0x%02x,%hu,0x%02x\n", escape, cmd, repeat, data); + } + else if (cmd == 2) + { + if (!Read_byte(file, &cmd)) + File_error = 1; + else if (cmd == 0) + { + // ESC,02,00 => STOP code + GFX2_Log(GFX2_DEBUG, "STOP : 0x%02x,0x02,%02x\n", escape, cmd); + break; + } + else + { + // ESC,02,a,b => repeat (a << 8 + b + 1) x byte "delta" + repeat = cmd << 8; + if (!Read_byte(file, &cmd)) + File_error = 1; + repeat += cmd; + GFX2_Log(GFX2_DEBUG, "delta repeat : 0x%02x,%hu\n", escape, repeat); + } + } + else if (cmd == escape) + { + // ESC,ESC => 1 x byte "ESC" + data = cmd; + } + else + { + // ESC,a,b => repeat (a + 1) x byte b + repeat = cmd; + if (!Read_byte(file, &data)) + File_error = 1; + } + } + else + { + data = cmd; + } + // output bytes + do + { + buffer[i] = data; + i += offset; + c++; + if (i >= 32000) + i -= (32000 - 1); + } + while (repeat-- > 0); + } + GFX2_Log(GFX2_DEBUG, "finished : i=%d c=%d\n", i, c); + } + } + else + { + // not compressed + if (!Read_bytes(file, buffer, 32000)) + File_error = 1; + } + if (File_error == 0) + { + int line; + const byte * ptr = buffer; + int ncols; + + ncols = (res == 0) ? 20 : 40; + + for (line = 0; line < height; line++) + { + byte pixels[16]; + int col, x; + + for (col = 0; col < ncols; col++) + { + switch (res) + { + case 0: + PI1_8b_to_16p(ptr, pixels); + ptr += 8; + for (x = 0; x < 16; x++) + Set_pixel(context, col * 16 + x, line, pixels[x]); + break; + case 1: + PI2_8b_to_16p(ptr, pixels); + ptr += 4; + for (x = 0; x < 16; x++) + Set_pixel(context, col * 16 + x, line, pixels[x]); + break; + case 2: + for (x = 0 ; x < 16; x++) + Set_pixel(context, col * 16 + x, line, (ptr[(x >> 3)] >> (7 - (x & 7))) & 1); + ptr += 2; + } + } + } + } + } + } + fclose(file); + free(buffer); +} + +/** + * Save a 320x200 16c picture in CrackArt format + * + * @todo support medium and high resolution + */ +void Save_CA1(T_IO_Context * context) +{ + FILE * file; + byte * buffer; + byte res = 0; // 0 = low, 1 = med, 2 = high + byte compressed = 1; // 0 or 1 + int height = 200; + + File_error = 1; + buffer = GFX2_malloc(32000); + if (buffer == NULL) + return; + file = Open_file_write(context); + if (file == NULL) + { + free(buffer); + return; + } + if (Write_bytes(file, "CA", 2) && Write_byte(file, compressed) && Write_byte(file, res)) + { + PI1_code_palette(context->Palette, buffer); + if (Write_bytes(file, buffer, 32)) + { + int line; + byte * ptr = buffer; + + for (line = 0; line < height; line++) + { + byte pixels[16]; + int col, x; + + for (col = 0; col < 20; col++) + { + for (x = 0; x < 16; x++) + pixels[x] = Get_pixel(context, col * 16 + x, line); + PI1_16p_to_8b(pixels, ptr); + ptr += 8; + } + } + + if (compressed) + { + word freq[256]; + word max, min; + byte max_index, min_index; + byte escape; + word offset; + int i; + + memset(freq, 0, sizeof(freq)); + for (i = 0; i < 32000; i++) + freq[buffer[i]]++; + min = 65535; + min_index = 0; + max = 0; + max_index = 0; + for (i = 0; i < 256; i++) + { + if (freq[i] <= min && i > 2) + { + min = freq[i]; + min_index = (byte)i; + } + if (freq[i] > max) + { + max = freq[i]; + max_index = (byte)i; + } + } + GFX2_Log(GFX2_DEBUG, " 0x%02X (%hu times) 0x%02X (%hu times)\n", min_index, min, max_index, max); + escape = min_index; + offset = 160; // 80 in high res + if (Write_byte(file, escape) && Write_byte(file, max_index) && Write_word_be(file, offset)) + { + int c = 1; + byte current; + word count = 0; + File_error = 0; + i = offset; + current = buffer[0]; + while (c < 32000 && File_error == 0) + { + if (buffer[i] == current) + count++; + else + { + if (count < 3) + { + // Litteral + do + { + if (!Write_byte(file, current)) + File_error = 1; + if (current == escape) + Write_byte(file, current); + } + while (count-- && File_error == 0); + } + else + { + GFX2_Log(GFX2_DEBUG, "byte %02X x %hu\n", current, count); + if (count < 256) + { + // ESC,a,b => repeat (a + 1) x byte b + // with a > 2 + if (!(Write_byte(file, escape) + && Write_byte(file, count) + && Write_byte(file, current))) + File_error = 1; + } + else if (current == max_index) + { + // ESC,02,word count => repeat (count + 1) x byte "delta" + if (!(Write_byte(file, escape) + && Write_byte(file, 2) + && Write_word_be(file, count))) + File_error = 1; + } + else + { + // ESC,01,word count,data + if (!(Write_byte(file, escape) + && Write_byte(file, 1) + && Write_word_be(file, count) + && Write_byte(file, current))) + File_error = 1; + } + } + current = buffer[i]; + count = 0; + } + i += offset; + c++; + if (i >= 32000) + i -= (32000 - 1); + } + GFX2_Log(GFX2_DEBUG, "end: byte %02X x %hu\n", current, count); + if (count < 3) + { + do + { + Write_byte(file, current); + if (current == escape) + Write_byte(file, current); + } + while (count--); + } + else if (current == max_index) + { + // STOP code + if (!(Write_byte(file, escape) + && Write_byte(file, 2) + && Write_byte(file, 0))) + File_error = 1; + } + else if (count < 256) + { + // ESC,a,b => repeat (a + 1) x byte b + // with a > 2 + if (!(Write_byte(file, escape) + && Write_byte(file, count) + && Write_byte(file, current))) + File_error = 1; + } + else + { + // ESC,01,word count,data + if (!(Write_byte(file, escape) + && Write_byte(file, 1) + && Write_word_be(file, count) + && Write_byte(file, current))) + File_error = 1; + } + } + } + else + { + // uncompressed + if (Write_bytes(file, buffer, 32000)) + File_error = 0; + } + } + } + fclose(file); + free(buffer); +} + /* @} */ diff --git a/src/tests/testformats.c b/src/tests/testformats.c index 77fa5a4b..d308875f 100644 --- a/src/tests/testformats.c +++ b/src/tests/testformats.c @@ -70,6 +70,7 @@ static const struct { TESTFMTF(NEO, "atari_st/ATARIART.NEO", FLAG_16C) TESTFMTF(PC1, "atari_st/eunmiisa.pc1", FLAG_16C) TESTFMTF(PI1, "atari_st/evolutn.pi1", FLAG_16C) + TESTFMTF(CA1, "atari_st/GIANTS.CA1", FLAG_16C) TESTFMTF(TNY, "atari_st/rose.tny", FLAG_16C) TESTFMTL(FLI, "autodesk_FLI_FLC/2noppaa.fli") TESTFMT(BMP, "bmp/test8.bmp")