/* 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-2019 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 cpcformats.c /// Formats for the Amstrad CPC / CPC Plus computers #include #include #include #include "global.h" #include "fileformats.h" #include "io.h" #include "loadsavefuncs.h" #include "libraw2crtc.h" #include "oldies.h" #include "gfx2mem.h" #include "gfx2log.h" /** * @defgroup cpcformats Amstrad CPC/CPC+ picture formats * @ingroup loadsaveformats * * Support for Amstrad CPC/CPC+ picture formats. The Amstrad CPC has * 3 video modes : * - mode 0 : 160x200 16 colors * - mode 1 : 320x200 4 colors * - mode 2 : 640x200 2 colors * * Supported formats : * - GO1/GO2 : GraphOS * - SCR : OCP Art Studio / iMPdraw v2 / etc. * - CM5 : Mode 5 Viewer * - PPH : Perfect Pix * * @{ */ /** * Test for SCR file (Amstrad CPC) * * SCR file format is originally from "Advanced OCP Art Studio" : * http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats * * .WIN "window" format is also supported. * * SCR files are normally just a dump of the 16K of video memory. So they are * essentially 16 kilobytes of pixel data without any header. To make things * more fun, there is an optional compression. This all makes detection a bit * fuzzy. However there are various things we can still check: * * - Presence of a valid PAL file. If the PAL file is not there the pixel data * may still be valid. The PAL file size depends on the screen mode (number * of colors). * - An AMSDOS header is a good indication but in some cases it may not * be there. * - Some tools embed the palette and mode (and usually some kind of loader * code) in the SCR file, we can also detect these. */ void Test_SCR(T_IO_Context * context, FILE * file) { // http://orgams.wikidot.com/le-format-impdraw-v2 // http://orgams.wikidot.com/les-fichiers-win-compatibles-ocp-art-studio FILE * pal_file; unsigned long pal_size, file_size; byte mode, color_anim_flag; word loading_address = 0; word exec_address = 0; File_error = 1; if (CPC_check_AMSDOS(file, &loading_address, &exec_address, &file_size)) { if (loading_address == 0x170) // iMPdraw v2 { byte buffer[0x90]; fseek(file, 128, SEEK_SET); // right after AMSDOS header Read_bytes(file, buffer, 0x90); GFX2_LogHexDump(GFX2_DEBUG, "", buffer, 0, 0x90); File_error = 0; return; } else if (loading_address == 0x200 && exec_address == 0x811 && file_size > 16000) { // convimgcpc File_error = 0; return; } else if (loading_address == 0xc000 && file_size > 16000) { File_error = 0; return; } else if (loading_address == 0x0040 && exec_address == 0x8000) { File_error = 0; return; } } else file_size = File_length_file(file); if (file_size > 16384*2) return; // requires the PAL file pal_file = Open_file_read_with_alternate_ext(context, "pal"); if (pal_file == NULL) return; /** @todo the palette data can be hidden in the 48 "empty" bytes * every 2048 bytes of a standard resolution SCR file. * So we should detect the hidden Z80 code and load them. * Load address of file is C000. Z80 code :
* C7D0: 3a d0 d7 cd 1c bd 21 d1 d7 46 48 cd 38 bc af 21 | :.....!..FH.8..!
* C7E0: d1 d7 46 48 f5 e5 cd 32 bc e1 f1 23 3c fe 10 20 | ..FH...2...#<..
* C7F0: f1 c3 18 bb 00 00 00 00 00 00 00 00 00 00 00 00 | ................
* mode and palette :
* D7D0: 00 1a 00 0c 03 0b 01 0d 17 10 02 0f 09 19 06 00 | ................
* https://gitlab.com/GrafX2/grafX2/merge_requests/121#note_119964168 */ if (CPC_check_AMSDOS(pal_file, NULL, NULL, &pal_size)) fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header else { pal_size = File_length_file(pal_file); fseek(pal_file, 0, SEEK_SET); } 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); } /** * Load Advanced OCP Art Studio files (Amstrad CPC) * * Standard resolution files (Mode 0 160x200, mode 1 320x200 and * mode 2 640x200) are supported. The .PAL file is loaded if available. * "MJH" RLE packing is supported. * Embedded CRTC registers and palette data from various tools is also * supported. * * .WIN "window" format is also loaded here and allows different picture sizes. */ void Load_SCR(T_IO_Context * context) { // The Amstrad CPC screen memory is mapped in a weird mode, somewhere // between bitmap and textmode. Basically the only way to decode this is to // emulate the video chip and read the bytes as needed... // Moreover, the hardware allows the screen to have any size from 8x1 to // 800x273 pixels, and there is no indication of that in the file besides // its size. It can also use any of the 3 screen modes. Fortunately this // last bit of information is stored in the palette file. // Oh, and BTW, the picture can be offset, and it's even usual to do it, // because letting 128 pixels unused at the beginning of the file make it a // lot easier to handle screens using more than 16K of VRam. // The pixel encoding change with the video mode so we have to know that // before attempting to load anything... // As if this wasn't enough, Advanced OCP Art Studio, the reference tool on // Amstrad, can use RLE packing when saving files, meaning we also have to // handle that. // 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 real_file_size, file_size, amsdos_file_size = 0; word addr; word load_address = 0x4000; // default for OCP Art studio word exec_address = 0; word display_start = 0x4000; 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 * cpc_ram; word x, y; int i; byte sig[3]; word block_length; word win_width, win_height; int is_win = 0; int columns = 80; int cpc_plus = 0; const byte * cpc_plus_pal = NULL; File_error = 1; // requires the PAL file for OCP Art studio files pal_file = Open_file_read_with_alternate_ext(context, "pal"); if (pal_file != NULL) { file_size = File_length_file(pal_file); if (CPC_check_AMSDOS(pal_file, NULL, NULL, &file_size)) fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header else fseek(pal_file, 0, SEEK_SET); 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); } else { memset(pal_data, 0, sizeof(pal_data)); } file = Open_file_read(context); if (file == NULL) return; file_size = File_length_file(file); real_file_size = file_size; if (CPC_check_AMSDOS(file, &load_address, &exec_address, &amsdos_file_size)) { display_start = load_address; 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 snprintf(context->Comment, COMMENT_SIZE, "AMSDOS load@ &%04X exec@ &%04X", load_address, exec_address); file_size = amsdos_file_size; } else fseek(file, 0, SEEK_SET); if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) { fclose(file); return; } fseek(file, -5, SEEK_CUR); cpc_ram = GFX2_malloc(64*1024); memset(cpc_ram, 0, 64*1024); if (0 != memcmp(sig, "MJH", 3) || block_length > 16384) { // raw data Read_bytes(file, cpc_ram + load_address, file_size); i = file_size; } else { // 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 block %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 { cpc_ram[load_address + i++] = value; block_length--; } while(--repeat != 0); } else { cpc_ram[load_address + i++] = code; block_length--; } } GFX2_Log(GFX2_DEBUG, " unpacked %d bytes. remaining bytes in file=%lu\n", i, file_size); } while(file_size > 0 && i < 16384); } fclose(file); if (i > 5) { win_width = cpc_ram[load_address + i - 4] + (cpc_ram[load_address + i - 3] << 8); // in bits win_height = cpc_ram[load_address + i - 2]; if (((win_width + 7) >> 3) * win_height + 5 == i) // that's a WIN file ! { width = win_width >> (2 - mode); height = win_height; is_win = 1; columns = (win_width + 7) >> 3; GFX2_Log(GFX2_DEBUG, ".WIN file detected len=%d (%d,%d) %dcols %02X %02X %02X %02X %02X\n", i, width, height, columns, cpc_ram[load_address + i - 5], cpc_ram[load_address + i - 4], cpc_ram[load_address + i - 3], cpc_ram[load_address + i - 2], cpc_ram[load_address + i - 1]); } else { GFX2_Log(GFX2_DEBUG, ".SCR file. Data length %d\n", i); if (load_address == 0x170) { // fichier iMPdraw v2 // http://orgams.wikidot.com/le-format-impdraw-v2 GFX2_Log(GFX2_DEBUG, "Detected \"%s\"\n", cpc_ram + load_address + 6); mode = cpc_ram[load_address + 0x14] - 0x0e; cpc_plus = cpc_ram[load_address + 0x3c]; GFX2_Log(GFX2_DEBUG, "Mode %d CPC %d\n", (int)mode, (int)cpc_plus); for (addr = load_address + 0x1d; cpc_ram[addr] < 16; addr += 2) { GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers switch(cpc_ram[addr]) { case 1: columns = cpc_ram[addr+1] * 2; break; case 6: height = cpc_ram[addr+1] * 8; break; case 12: display_start = ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); GFX2_Log(GFX2_DEBUG, " display_start &H%04X\n", display_start); } } snprintf(context->Comment, COMMENT_SIZE, "%s mode %d %s", cpc_ram + load_address + 7, mode, cpc_plus ? "CPC+" : ""); if (cpc_plus) { // palette at 0x801 (mode at 0x800 ?) GFX2_LogHexDump(GFX2_DEBUG, "", cpc_ram, 0x800, 0x21); cpc_plus_pal = cpc_ram + 0x801; } else { int j; // palette at 0x7f00 GFX2_LogHexDump(GFX2_DEBUG, "", cpc_ram, 0x7f00, 16); for (j = 0; j < 16; j++) pal_data[12*j] = cpc_ram[0x7f00 + j]; } } else if (load_address == 0x200 && exec_address == 0x811) { /* from HARLEY.SCR : 0800 00 = mode 0801-0810 palette (Firmware colors) 0811 21 47 08 LD HL,0847 ; OVERSCAN_REG_VALUES 0814 cd 36 08 CALL 0836 ; LOAD_CRTC_REGS 0817 3a 00 08 LD A,(0800) ; MODE 081a cd 1c bd CALL BD1C ; Set screen mode 081d 21 01 08 LD HL,0801 ; PALETTE 0820 af XOR A LOOP: 0821 4e LD C,(HL) 0822 41 LD B,C 0823 f5 PUSH AF 0824 e5 PUSH HL 0825 cd 32 bc CALL BC32 ; SET ink A to color B,C 0828 e1 POP HL 0829 f1 POP AF 082a 23 INC HL 082b 3c INC A 082c fe 10 CMP 10 082e 20 f1 JR NZ,0821 ; LOOP 0830 cd 18 bb CALL BB18 ; Wait key press 0833 21 55 08 LD HL,0855 ; STANDARD_REG_VALUES LOAD_CRTC_REGS: 0836 01 00 bc LD BC,BC00 LOOP_CRTC: 0839 7e LD A,(HL) 083a a7 AND A 083b c8 RET Z 083c ed 79 OUT (C),A 083e 04 INC B 083f 23 INC HL 0840 7e LD A,(HL) 0841 ed 79 OUT (C),A 0843 23 INC HL 0844 05 DEC B 0845 18 f2 JR 0839 ; LOOP_CRTC OVERSCAN_REG_VALUES: 0847 01 30 02 32 06 22 07 23 0c 0d 0d 00 00 00 STANDARD_REG_VALUES: 0855 01 28 02 2e 06 19 07 1e 0c 30 00 00 */ int j; mode = cpc_ram[0x800]; for (j = 0; j < 16; j++) pal_data[12*j] = CPC_Firmware_to_Hardware_color(cpc_ram[0x801 + j]); addr = 0x847; if (cpc_ram[0x80bb] == 1) addr = 0x80bb; for (; cpc_ram[addr] > 0 && cpc_ram[addr] < 16; addr += 2) { GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers switch(cpc_ram[addr]) { case 1: columns = cpc_ram[addr+1] * 2; break; case 6: height = cpc_ram[addr+1] * 8; break; case 12: display_start = (display_start & 0x01ff) | ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); break; case 13: display_start = (display_start & 0xfe00) | (cpc_ram[addr+1] << 1); } } snprintf(context->Comment, COMMENT_SIZE, "ConvImgCPC mode %d", mode); } else if (load_address == 0xC000 && cpc_ram[0xc7d0] != 0) { mode = cpc_ram[0xD7D0]; if ((mode & 0xc0) == 0x80) { // value sent to gate array : // bits 7 & 6 = function : // 10 => select mode, ROM conf and interrupt control // bit 4 : interrupt control // bit 3 : disable upper ROM (C000 = Basic) // bit 2 : disable lower ROM (0000 = Firmware) // bits 1&0 : screen mode mode &= 3; cpc_plus_pal = cpc_ram + 0xD7D1; } else { int j; for (j = 0; j < 16; j++) pal_data[12*j] = CPC_Firmware_to_Hardware_color(cpc_ram[0xD7D1 + j]); } snprintf(context->Comment, COMMENT_SIZE, "loader@ &%04X mode %d %s", exec_address, mode, cpc_plus_pal ? "CPC+" : ""); } else if (load_address == 0x0040 && exec_address == 0x8000) { mode = cpc_ram[0x8008] & 3; memset(cpc_ram, cpc_ram[0x40], 0x40); for (addr = 0x8094; cpc_ram[addr] < 16 && cpc_ram[addr] != 0; addr += 2) { GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers switch(cpc_ram[addr]) { case 1: columns = cpc_ram[addr+1] * 2; break; case 6: height = cpc_ram[addr+1] * 8; break; case 12: display_start = (display_start & 0x01ff) | ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); break; case 13: display_start = (display_start & 0xfe00) | (cpc_ram[addr+1] << 1); } } GFX2_Log(GFX2_DEBUG, " display_start &H%04X\n", display_start); cpc_plus_pal = cpc_ram + 0x80a0; } if (i >= 30000) { height = 272; columns = 96; } } } switch (mode) { case 0: width = columns * 2; bpp = 4; ratio = PIXEL_WIDE; break; case 1: width = columns * 4; bpp = 2; ratio = PIXEL_SIMPLE; break; case 2: width = columns * 8; 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 if (cpc_plus_pal) { for (i = 0; i < 16; i++) { context->Palette[i].G = cpc_plus_pal[i*2 + 1] * 0x11; context->Palette[i].R = (cpc_plus_pal[i*2] >> 4) * 0x11; context->Palette[i].B = (cpc_plus_pal[i*2] & 15) * 0x11; } } else { int ocp_plus_palette = 1; for (i = 0; i < 16 && ocp_plus_palette; i++) { byte inkr, inkb, inkg; int j; // OCP+ palettes use the 3 first slots and have the 9 remaining set to 0 for (j = 3; j < 12; j++) { if (pal_data[12*i+j] != 0) { ocp_plus_palette = 0; break; } } inkr = (context->Palette[pal_data[12*i]].G / 86) * 9 + (context->Palette[pal_data[12*i]].R / 86) * 3 + (context->Palette[pal_data[12*i]].B / 86); inkb = (context->Palette[pal_data[12*i+1]].G / 86) * 9 + (context->Palette[pal_data[12*i+1]].R / 86) * 3 + (context->Palette[pal_data[12*i+1]].B / 86); inkg = (context->Palette[pal_data[12*i+2]].G / 86) * 9 + (context->Palette[pal_data[12*i+2]].R / 86) * 3 + (context->Palette[pal_data[12*i+2]].B / 86); if (inkr < 11 || inkb < 11 || inkg < 11) ocp_plus_palette = 0; } if (ocp_plus_palette) { GFX2_Log(GFX2_DEBUG, "OCP+ Palette detected\n"); for (i = 0; i < 16; i++) { byte inkr, inkb, inkg; inkr = (context->Palette[pal_data[12*i]].G / 86) * 9 + (context->Palette[pal_data[12*i]].R / 86) * 3 + (context->Palette[pal_data[12*i]].B / 86); inkb = (context->Palette[pal_data[12*i+1]].G / 86) * 9 + (context->Palette[pal_data[12*i+1]].R / 86) * 3 + (context->Palette[pal_data[12*i+1]].B / 86); inkg = (context->Palette[pal_data[12*i+2]].G / 86) * 9 + (context->Palette[pal_data[12*i+2]].R / 86) * 3 + (context->Palette[pal_data[12*i+2]].B / 86); context->Palette[i].R = (26 - inkr) * 0x11; context->Palette[i].G = (26 - inkg) * 0x11; context->Palette[i].B = (26 - inkb) * 0x11; } } else { // Standard OCP palette for (i = 0; i < 16; i++) context->Palette[i] = context->Palette[pal_data[12*i]]; } } File_error = 0; Pre_load(context, width, height, real_file_size, FORMAT_SCR, ratio, bpp); if (!is_win) { // Standard resolution files have the 200 lines stored in block // of 25 lines of 80 bytes = 2000 bytes every 2048 bytes. // so there are 48 bytes unused every 2048 bytes... for (y = 0; y < 8; y++) { addr = display_start + 0x800 * y; if (y > 0 && (display_start & 0x7ff)) { if (!GFX2_is_mem_filled_with(cpc_ram + (addr & 0xf800), 0, display_start & 0x7ff)) GFX2_LogHexDump(GFX2_DEBUG, "SCR1 ", cpc_ram, addr & 0xf800, display_start & 0x7ff); } addr += (height >> 3) * columns; block_length = (height >> 3) * columns + (display_start & 0x7ff); if (block_length <= 0x800) { block_length = 0x800 - block_length; if (!GFX2_is_mem_filled_with(cpc_ram + addr, 0, block_length)) GFX2_LogHexDump(GFX2_DEBUG, "SCR2 ", cpc_ram, addr, block_length); } else { block_length = 0x1000 - block_length; if (!GFX2_is_mem_filled_with(cpc_ram + addr + 0x4000, 0, block_length)) GFX2_LogHexDump(GFX2_DEBUG, "SCR2 ", cpc_ram, addr + 0x4000, block_length); } } //for (j = 0; j < i; j += 2048) // GFX2_LogHexDump(GFX2_DEBUG, "SCR ", cpc_ram, load_address + j + 2000, 48); } GFX2_Log(GFX2_DEBUG, " display_start &H%04X columns=%u\n", display_start, columns); for (y = 0; y < height; y++) { x = 0; for (i = 0; i < columns; i++) { byte pixels; if (is_win) addr = display_start + y * columns + i; else { addr = display_start + ((y >> 3) * columns) + i; addr = (addr & 0xC7FF) + ((addr & 0x800) << 3); addr += (y & 7) << 11; } pixels = cpc_ram[addr]; 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(cpc_ram); } #include "impdraw_loader.h" #include "cpc_scr_simple_loader.h" /** * 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 for compatibility * with OCP Art studio. * * The picture color index should be 0-15, * The CPC Hardware palette is expected to be set (indexes 64 to 95) * * If the picture is using overscan (more than 16384 bytes) * we produce a iMPdraw v2 format autoloading file. * see http://orgams.wikidot.com/le-format-impdraw-v2 * * If the picture is not using overscan (standard resolutions, * < 16384 screen buffer) a BASIC loader is saved. * * @todo Add possibility to set R9 value * @todo Add OCP packing support */ void Save_SCR(T_IO_Context * context) { int i, j; unsigned char* output; unsigned long outsize = 0; unsigned char r1 = 0; // Horizontal Displayed. Standard value is 40 (=80 bytes) int cpc_mode; FILE* file; int cpc_plus_pal = 0; unsigned short load_address = 0xC000; // Standard CPC screen address unsigned short exec_address = 0xC7D0; int overscan; byte cpc_hw_pal[16]; byte r12 = 0x0C | 0x30; // set Display Start Address at C000 byte r13 = 0; switch(context->Ratio) { case PIXEL_WIDE: case PIXEL_WIDE2: cpc_mode = 0; // 16 colors, 2 pixels per byte overscan = (context->Width * context->Height) > (16384 * 2); break; case PIXEL_TALL: case PIXEL_TALL2: case PIXEL_TALL3: cpc_mode = 2; // 2 colors, 8 pixels per byte overscan = (context->Width * context->Height) > (16384 * 8); break; default: cpc_mode = 1; // 4 colors, 4 pixels per byte overscan = (context->Width * context->Height) > (16384 * 4); break; } if (overscan) { // format iMP v2 load_address = 0x170; // BASIC program at 0x170 // picture at 0x200 r12 = 0x0C | (0x200 >> 9); r13 = (0x200 >> 1) & 0xff; exec_address = 0; // BASIC program } CPC_set_HW_palette(context->Palette + 0x40); for (i = 0; i < 16; i++) { // search for the color in the HW palette (0x40-0x5F) byte index = 0x40; while ((index < 0x60) && !CPC_compare_colors(context->Palette + i, context->Palette + index)) index++; if (index >= 0x60) { GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i); cpc_plus_pal = 1; index = 0x54 - i; // default } cpc_hw_pal[i] = index; if (!CPC_is_CPC_old_color(context->Palette + i)) cpc_plus_pal = 1; } file = Open_file_write_with_alternate_ext(context, "pal"); if (file == NULL) return; CPC_write_AMSDOS_header(file, context->File_name, "pal", 2, 0x8809, 0x8809, 239); if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0)) { fclose(file); return; } for (i = 0; i < 16; i++) { for (j = 0; j < 12; j++) // write the same color for the 12 frames { Write_byte(file, cpc_hw_pal[i]); } } // 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); output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, r12, r13); 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; CPC_write_AMSDOS_header(file, context->File_name, "SCR", overscan ? 0 : 2, load_address, exec_address, overscan ? 32400 : outsize); if (overscan) { // write iMPdraw loader byte buffer[0x90]; memcpy(buffer, impdraw_loader, 0x90); buffer[0x184 - 0x170] = 0x0e + cpc_mode; buffer[0x18e - 0x170] = r1; //buffer[0x190 - 0x170] = // R2 ? buffer[0x192 - 0x170] = context->Height / 8; // r6 //buffer[0x194 - 0x170] = // r7 ? buffer[0x196 - 0x170] = r12; buffer[0x198 - 0x170] = r13; buffer[0x1ac - 0x170] = cpc_plus_pal; if (!Write_bytes(file, buffer, 0x90)) File_error = 1; output = realloc(output, 32400 - 0x90); memset(output + outsize, 0, 32400 - 0x90 - outsize); outsize = 32400 - 0x90; memcpy(output + 0x7F00 - 0x200, cpc_hw_pal, 16); for (i = 0; i < 16; i++) { output[0x601 + i*2] = (context->Palette[i].R & 0xf0) | (context->Palette[i].B >> 4); output[0x602 + i*2] = context->Palette[i].G >> 4; } } else { memcpy(output + exec_address - load_address, cpc_scr_simple_loader, sizeof(cpc_scr_simple_loader)); output[0xd7d0 - load_address] = cpc_mode; //memcpy(output + 0xd7d1 - load_address, cpc_hw_pal, 16); for (i = 0; i < 16; i++) { output[0xd7d1 - load_address + i] = (context->Palette[i].G / 86) * 9 + (context->Palette[i].R / 86) * 3 + (context->Palette[i].B / 86); } } if (!Write_bytes(file, output, outsize)) File_error = 1; fclose(file); if (!overscan) { file = Open_file_write_with_alternate_ext(context, "BAS"); if (file != NULL) { byte buffer[128]; memset(buffer, 0, sizeof(buffer)); buffer[2] = 10; // basic line number buffer[4] = 0xad; // MODE buffer[5] = ' '; buffer[6] = 0x0e + cpc_mode; buffer[7] = 0x01; // : buffer[8] = 0xa8; // LOAD buffer[9] = '"'; for (i = 0; i < 8; i++) { if (context->File_name[i] == '\0' || context->File_name[i] == '.') break; buffer[10+i] = (byte)toupper(context->File_name[i]); } memcpy(buffer + 10 + i, ".SCR\"", 5); i += 15; buffer[i++] = 0x01; // : buffer[i++] = 0x83; // CALL buffer[i++] = 0x1c; // & buffer[i++] = 0xd0; buffer[i++] = 0xc7; buffer[i++] = 0x00; buffer[0] = (byte)i; // length of line data CPC_write_AMSDOS_header(file, context->File_name, "BAS", 0, 0x170, 0, i + 2); if (!Write_bytes(file, buffer, 128)) File_error = 1; fclose(file); } } } free (output); } /** * Test for GO1/GO2/KIT - Amstrad Plus Graphos * * This format is made of 3 files * .KIT hold the palette in "Kit4096" format. There are 16 colors each stored * as 12 bit RGB in RB0G order. * .GO1 and GO2 hold each half of the picture (top and bottom) * The file always cover the whole display of the Plus (192*272 mode 0 pixels) * Only mode 0 is possible. */ void Test_GOS(T_IO_Context * context, FILE * file) { FILE *file_oddeve; unsigned long file_size = 0; if (!CPC_check_AMSDOS(file, NULL, NULL, &file_size)) file_size = File_length_file(file); if (file_size < 16383 || file_size > 16384) { File_error = 1; return; } file_oddeve = Open_file_read_with_alternate_ext(context, "GO2"); if (file_oddeve == NULL) { File_error = 2; return; } if (!CPC_check_AMSDOS(file_oddeve, NULL, NULL, &file_size)) file_size = File_length_file(file_oddeve); fclose(file_oddeve); if (file_size < 16383 || file_size > 16384) { File_error = 3; return; } File_error = 0; } /** * Load GO1/GO2/KIT - Amstrad CPC Plus Graphos */ void Load_GOS(T_IO_Context* context) { FILE *file; unsigned long file_size; int i; int x, y; byte * pixel_data; if (!(file = Open_file_read(context))) { File_error = 1; return; } if (CPC_check_AMSDOS(file, NULL, NULL, &file_size)) fseek(file, 128, SEEK_SET); // right after AMSDOS header else file_size = File_length_file(file); Pre_load(context, 192, 272, file_size, FORMAT_GOS, PIXEL_WIDE, 4); // load pixels pixel_data = GFX2_malloc(16384); memset(pixel_data, 0, 16384); Read_bytes(file, pixel_data, file_size); i = 0; for (y = 0; y < 168; y++) { x = 0; while (x < 192) { byte pixels = pixel_data[i]; 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); i++; } i += 0x800; if (i > 0x3FFF) { i -= 0x4000; } else { i -= 192 / 2; } } fclose(file); // load pixels from GO2 file = Open_file_read_with_alternate_ext(context, "GO2"); if (CPC_check_AMSDOS(file, NULL, NULL, &file_size)) fseek(file, 128, SEEK_SET); // right after AMSDOS header Read_bytes(file, pixel_data, file_size); i = 0; for (y = 168; y < 272; y++) { x = 0; while (x < 192) { byte pixels = pixel_data[i]; 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); i++; } i += 0x800; if (i > 0x3FFF) { i -= 0x4000; } else { i -= 192 / 2; } } fclose(file); free(pixel_data); file = Open_file_read_with_alternate_ext(context, "KIT"); if (file == NULL) { // There is no palette, but that's fine, we can still load the pixels // Setup a default grayscale palette for (i = 0; i < 16; i++) { context->Palette[i].R = i * 0x11; context->Palette[i].G = i * 0x11; context->Palette[i].B = i * 0x11; } return; } if (CPC_check_AMSDOS(file, NULL, NULL, &file_size)) { fseek(file, 128, SEEK_SET); // right after AMSDOS header } else { file_size = File_length_file(file); } if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); File_error = 0; if (file_size == 32) { for (i = 0; i < 16; i++) { uint16_t word; if (!Read_word_le(file, &word)) { File_error = 2; break; } context->Palette[i].R = ((word >> 4) & 0xF) * 0x11; context->Palette[i].G = ((word >> 8) & 0xF) * 0x11; context->Palette[i].B = ((word >> 0) & 0xF) * 0x11; } } else { // Setup the palette (amstrad hardware palette) CPC_set_HW_palette(context->Palette + 0x40); for (i = 0; i < 16; i++) { byte ink; if (!Read_byte(file, &ink)) { File_error = 2; break; } context->Palette[i] = context->Palette[ink]; } } fclose(file); } void Save_GOS(T_IO_Context* context) { FILE* file; int i; unsigned char* output; unsigned long outsize = 0; unsigned char r1 = 0; // TODO check picture dimensions (GOS is a fixed resolution format) // For now, force the size context->Width = 192; context->Height = 168; // Convert the first half // convert and save page 1 file = Open_file_write(context); if (file == NULL) return; output = raw2crtc(context, 0, 7, &outsize, &r1, 0, 0); File_error = 0; if (!Write_bytes(file, output, outsize)) File_error = 1; // Pad to expected size GFX2_Log(GFX2_DEBUG, "written %lu bytes, padding to 16384\n", outsize); if (outsize >= 8192) { memset(output, 0, 16384 - outsize); if (!Write_bytes(file, output, 16384 - outsize)) File_error = 1; } free(output); fclose(file); if (File_error) return; // convert and save page 2 // Advance context to second half of picture context->Target_address += context->Pitch * 168; context->Height = 104; file = Open_file_write_with_alternate_ext(context, "GO2"); if (file == NULL) { File_error = 1; return; } output = raw2crtc(context, 0, 7, &outsize, &r1, 0, 0); File_error = 0; if (!Write_bytes(file, output, outsize)) File_error = 1; // Pad to expected size GFX2_Log(GFX2_DEBUG, "written %lu bytes, padding to 16384\n", outsize); if (outsize >= 8192) { memset(output, 0, 16384 - outsize); if (!Write_bytes(file, output, 16384 - outsize)) File_error = 1; } free(output); fclose(file); if (File_error) return; file = Open_file_write_with_alternate_ext(context, "KIT"); for (i = 0; i < 16 && File_error == 0; i++) { uint16_t word; word = (context->Palette[i].R & 0xF0) | ((context->Palette[i].G & 0xF0) << 4) | (context->Palette[i].B >> 4); if (!Write_word_le(file, word)) File_error = 2; } fclose(file); } /** * Test for CM5 - Amstrad CPC "Mode 5" picture * * This is a format designed by SyX. * There is one .GFX file in the usual amstrad format * and a .CM5 file with the palette, which varies over time. * * CM5 file is 2049 bytes, GFX is 18432 bytes. * * @todo check CM5 contains only valid values [0x40-0x5f] */ void Test_CM5(T_IO_Context * context, FILE * file) { // check cm5 file size == 2049 bytes FILE *file_gfx; unsigned long file_size; File_error = 1; if (!CPC_check_AMSDOS(file, NULL, NULL, &file_size)) file_size = File_length_file(file); if (file_size != 2049) return; // check existence of a .GFX file with the same name file_gfx = Open_file_read_with_alternate_ext(context, "gfx"); if (file_gfx == NULL) return; if (!CPC_check_AMSDOS(file_gfx, NULL, NULL, &file_size)) file_size = File_length_file(file_gfx); fclose(file_gfx); if (file_size != 18432) return; File_error = 0; } /** * Load Amstrad CPC "Mode 5" picture * * Only support 288x256 resolution as the Mode 5 Viewer app only handles this * single resolution. * @see https://www.cpc-power.com/index.php?page=detail&num=12905 * @see https://github.com/cpcsdk/cpctools/blob/master/resources/mode5_viewer/fx_mode5.s */ void Load_CM5(T_IO_Context* context) { // Ensure "8bit" constraint mode is switched on // Set palette to the CPC hardware colors // Load the palette data to the 4 colorlayers FILE *file; byte value = 0; int mod=0; short line = 0; int tx, ty; // for preview : byte ink0; byte ink1[256]; byte ink2[256]; byte ink3[256*6]; if (!(file = Open_file_read(context))) { File_error = 1; return; } Pre_load(context, 48*6, 256, 2049, FORMAT_CM5, PIXEL_SIMPLE, 5); if (Config.Clear_palette) { memset(context->Palette,0,sizeof(T_Palette)); // setup colors 0,1,2,3 to see something in the thumbnail preview of layer 5 context->Palette[1].R = 60; context->Palette[2].B = 60; context->Palette[3].G = 60; } // Setup the palette (amstrad hardware palette) CPC_set_HW_palette(context->Palette + 0x40); First_color_in_palette = 64; if (CPC_check_AMSDOS(file, NULL, NULL, NULL)) fseek(file, 128, SEEK_SET); // seek after AMSDOS header if (!Read_byte(file, &ink0)) File_error = 2; // This forces the creation of 5 layers total : // Needed because the "pixel" functions will seek layer 4 Set_loading_layer(context, 4); // Now select layer 1 again Set_loading_layer(context, 0); if (context->Type == CONTEXT_MAIN_IMAGE) { Set_image_mode(context, IMAGE_MODE_MODE5); // Fill layer with color we just read (Layer 1 - INK 0) for(ty=0; tyHeight; ty++) for(tx=0; txWidth; tx++) Set_pixel(context, tx, ty, ink0); } for (line = 0; line < 256; ) { if (!Read_byte(file, &value)) { File_error = 1; break; } switch(mod) { case 0: // This is color for layer 2 - INK 1 Set_loading_layer(context, 1); for(tx=0; txWidth; tx++) Set_pixel(context, tx, line, value); ink1[line] = value; break; case 1: // This is color for layer 3 - INK 2 Set_loading_layer(context, 2); for(tx=0; txWidth; tx++) Set_pixel(context, tx, line, value); ink2[line] = value; break; default: // This is color for a block in layer 4 - INK 3 Set_loading_layer(context, 3); for(tx=(mod-2)*48; tx<(mod-1)*48; tx++) Set_pixel(context, tx, line, value); ink3[line*6+(mod-2)] = value; break; } mod++; if (mod > 7) { mod = 0; line++; } } fclose(file); // Load the pixeldata to the 5th layer file = Open_file_read_with_alternate_ext(context, "gfx"); if (file == NULL) { File_error = 1; return; } if (CPC_check_AMSDOS(file, NULL, NULL, NULL)) fseek(file, 128, SEEK_SET); // seek after AMSDOS header Set_loading_layer(context, 4); if (context->Type == CONTEXT_PREVIEW) for (ty = 0; ty < 256; ty++) for (tx = 0; tx < 48*6; ) { Read_byte(file, &value); for (mod = 0; mod < 4; mod++, tx++, value <<= 1) { switch(3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))) // INK { case 0: Set_pixel(context, tx, ty, ink0); break; case 1: Set_pixel(context, tx, ty, ink1[ty]); break; case 2: Set_pixel(context, tx, ty, ink2[ty]); break; default: Set_pixel(context, tx, ty, ink3[ty*6+(tx/48)]); } } } else for (ty = 0; ty < 256; ty++) for (tx = 0; tx < 48*6; ) { Read_byte(file, &value); Set_pixel(context, tx++, ty, 3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))); Set_pixel(context, tx++, ty, 3 ^ (((value&0x40) >> 6) | ((value&0x4)>>1))); Set_pixel(context, tx++, ty, 3 ^ (((value&0x20) >> 5) | ((value&0x2)>>0))); Set_pixel(context, tx++, ty, 3 ^ (((value&0x10) >> 4) | ((value&0x1)<<1))); } fclose(file); } /** * Save a CPC Mode 5 picture. * Resolution is fixed to 288x256. * The pictures uses 5 layers. 4 for defining the "inks" * the 5th to select one of the 4 inks. * * - Layer 1 : 1 color Only * - Layer 2 and 3 : 1 color/line * - Layer 4 : 1 color / 48x1 block * - Layer 5 : CPC mode 2 288x256 picture. * * The .CM5 file contains the inks from layers 1-4, * the .GFX file contains the pixel data in linear fashion * @todo Check picture has 5 layers * @todo Check the constraints on the layers * @see https://www.cpc-power.com/index.php?page=detail&num=12905 */ void Save_CM5(T_IO_Context* context) { FILE* file; int tx, ty; if (!(file = Open_file_write(context))) { File_error = 1; return; } // Write layer 0 Set_saving_layer(context, 0); Write_byte(file, Get_pixel(context, 0, 0)); for(ty = 0; ty < 256; ty++) { Set_saving_layer(context, 1); Write_byte(file, Get_pixel(context, 0, ty)); Set_saving_layer(context, 2); Write_byte(file, Get_pixel(context, 0, ty)); Set_saving_layer(context, 3); for(tx = 0; tx < 6; tx++) { Write_byte(file, Get_pixel(context, tx*48, ty)); } } fclose(file); // Now the pixeldata if (!(file = Open_file_write_with_alternate_ext(context, "gfx"))) { File_error = 2; return; } Set_saving_layer(context, 4); for (ty = 0; ty < 256; ty++) { for (tx = 0; tx < 48*6; tx+=4) { byte code = 0; byte pixel; pixel = 3-Get_pixel(context, tx+3, ty); code |= (pixel&2)>>1 | ((pixel & 1)<<4); pixel = 3-Get_pixel(context, tx+2, ty); code |= ((pixel&2)<<0) | ((pixel & 1)<<5); pixel = 3-Get_pixel(context, tx+1, ty); code |= ((pixel&2)<<1) | ((pixel & 1)<<6); pixel = 3-Get_pixel(context, tx, ty); code |= ((pixel&2)<<2) | ((pixel & 1)<<7); Write_byte(file, code); } } fclose(file); File_error = 0; } /** * Amstrad CPC 'PPH' for Perfect Pix. * This is a format designed by Rhino. * There are 3 modes: * - Mode 'R': 1:1 pixels, 16 colors from the CPC 27 color palette. * (this is implemented on CPC as two pictures with wide pixels, the "odd" one * being shifted half a pixel to the right), and flipping) * - Mode 'B0': wide pixels, up to 126 out of 378 colors. * (this is implemented as two pictures with wide pixels, sharing the same 16 * color palette, and flipping) * - Mode 'B1': 1:1 pixels, 1 fixed color, up to 34 palettes of 9 colors * (actually 4 colors + flipping) * * - The standard CPC formats can also be encapsulated into a PPH file. * * @see http://www.pouet.net/prod.php?which=67770#c766959 */ void Test_PPH(T_IO_Context * context, FILE * file) { FILE *file_oddeve; byte buffer[6]; unsigned long file_size; unsigned int w, h; unsigned int expected; File_error = 1; // First check file size is large enough to hold the header file_size = File_length_file(file); if (file_size < 11) { File_error = 1; return; } // File is large enough for the header, now check if the data makes some sense if (!Read_bytes(file, buffer, 6)) return; if (buffer[0] > 5) { // Unknown mode File_error = 2; return; } w = buffer[1] | (buffer[2] << 8); if (w < 2 || w > 384) { // Invalid width File_error = 3; return; } h = buffer[3] | (buffer[4] << 8); if (h < 1 || h > 272) { // Invalid height File_error = 4; return; } if (buffer[5] < 1 || buffer[5] > 28) { // Invalid palettes count File_error = 5; return; } expected = 6; // Size of header switch(buffer[0]) { case 0: case 3: case 4: // Palette size should be 16 bytes, only 1 palette. if (buffer[5] != 1) { File_error = 7; return; } expected += 16; break; case 1: case 5: expected += buffer[5] * 5 - 1; break; case 2: // Palette size should be 2 bytes if (buffer[5] != 1) { File_error = 7; return; } expected += 2; break; } if (file_size != expected) { File_error = 6; return; } // check existence of .ODD/.EVE files with the same name // and the right size expected = w * h / 4; file_oddeve = Open_file_read_with_alternate_ext(context, "odd"); if (file_oddeve == NULL) return; file_size = File_length_file(file_oddeve); fclose (file_oddeve); if (file_size != expected) { File_error = 8; return; } file_oddeve = Open_file_read_with_alternate_ext(context, "eve"); if (file_oddeve == NULL) return; file_size = File_length_file(file_oddeve); fclose(file_oddeve); if (file_size != expected) { File_error = 8; return; } File_error = 0; } static uint8_t pph_blend(uint8_t a, uint8_t b) { uint32_t h,l; if (a > b) { h = a; l = b; } else { h = b; l = a; } return (23 * h + 9 * l) / 32; } void Load_PPH(T_IO_Context* context) { FILE *file; FILE *feven; // Read in the header uint8_t mode; uint16_t width; uint16_t height; uint8_t npal; int i,j; uint8_t a,b,c,d; int file_size; uint8_t pl[16]; static const T_Components CPCPAL[27] = { { 0x00, 0x02, 0x01 }, { 0x00, 0x02, 0x6B }, { 0x0C, 0x02, 0xF4 }, { 0x6C, 0x02, 0x01 }, { 0x69, 0x02, 0x68 }, { 0x6C, 0x02, 0xF2 }, { 0xF3, 0x05, 0x06 }, { 0xF0, 0x02, 0x68 }, { 0xF3, 0x02, 0xF4 }, { 0x02, 0x78, 0x01 }, { 0x00, 0x78, 0x68 }, { 0x0C, 0x7B, 0xF4 }, { 0x6E, 0x7B, 0x01 }, { 0x6E, 0x7D, 0x6B }, { 0x6E, 0x7B, 0xF6 }, { 0xF3, 0x7D, 0x0D }, { 0xF3, 0x7D, 0x6B }, { 0xFA, 0x80, 0xF9 }, { 0x02, 0xF0, 0x01 }, { 0x00, 0xF3, 0x6B }, { 0x0F, 0xF3, 0xF2 }, { 0x71, 0xF5, 0x04 }, { 0x71, 0xF3, 0x6B }, { 0x71, 0xF3, 0xF4 }, { 0xF3, 0xF3, 0x0D }, { 0xF3, 0xF3, 0x6D }, { 0xFF, 0xF3, 0xF9 } }; if (!(file = Open_file_read(context))) { File_error = 1; return; } file_size=File_length_file(file); Read_byte(file, &mode); Read_word_le(file, &width); Read_word_le(file, &height); Read_byte(file, &npal); if (npal > 16) npal = 16; // Switch to the proper aspect ratio switch (mode) { case 0: case 4: context->Ratio = PIXEL_WIDE; width /= 2; break; case 2: context->Ratio = PIXEL_TALL; break; case 1: case 5: case 3: context->Ratio = PIXEL_SIMPLE; break; } Pre_load(context, width, height, file_size, FORMAT_PPH, context->Ratio, 0); context->Width = width; context->Height = height; // First of all, detect the mode // 0, 1, 2 > Load as with SCR files? // R(3) > Load as single layer, square pixels, 16 colors // B0(4) > Load as single layer, wide pixels, expand palette with colorcycling // B1(5) > Load as ??? // Maybe special mode similar to mode5, with 2 layers + auto-flicker? switch (mode) { case 0: case 3: // R // 16-color palette for (i = 0; i < 16; i++) { uint8_t color; Read_byte(file, &color); context->Palette[i] = CPCPAL[color]; } break; case 1: case 5: // B1 { // Single or multiple 4-color palettes uint8_t base[4]; for (j = 0; j < npal; j++) { for (i = 0; i < 4; i++) { Read_byte(file,&base[i]); } for (i = 0; i < 16; i++) { context->Palette[i + 16*j].R = pph_blend( CPCPAL[base[i & 3]].R, CPCPAL[base[i >> 2]].R); context->Palette[i + 16*j].G = pph_blend( CPCPAL[base[i & 3]].G, CPCPAL[base[i >> 2]].G); context->Palette[i + 16*j].B = pph_blend( CPCPAL[base[i & 3]].B, CPCPAL[base[i >> 2]].B); } // TODO this byte marks where this palette stops being used and the // next starts. We must handle this! Read_byte(file,&pl[j]); } pl[npal - 1] = 255; break; } case 2: // Single 2-color palette break; case 4: // B0 { // Single 16-color palette + flipping, need to expand palette and // setup colorcycling ranges. uint8_t base[16]; for (i = 0; i < 16; i++) { Read_byte(file,&base[i]); } for (i = 0; i < 256; i++) { context->Palette[i].R = pph_blend( CPCPAL[base[i & 15]].R, CPCPAL[base[i >> 4]].R); context->Palette[i].G = pph_blend( CPCPAL[base[i & 15]].G, CPCPAL[base[i >> 4]].G); context->Palette[i].B = pph_blend( CPCPAL[base[i & 15]].B, CPCPAL[base[i >> 4]].B); } } break; } fclose(file); // Load the picture data // There are two pages, each storing bytes in the CPC vram format but lines in // linear order. file = Open_file_read_with_alternate_ext(context, "odd"); if (file == NULL) { File_error = 3; return; } feven = Open_file_read_with_alternate_ext(context, "eve"); if (feven == NULL) { File_error = 4; fclose(file); return; } c = 0; d = 0; for (j = 0; j < height; j++) { for (i = 0; i < width;) { uint8_t even, odd; Read_byte(feven, &even); Read_byte(file, &odd); switch (mode) { case 4: a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); a <<= 4; a |= ((odd & 0x02) << 2) | (( odd & 0x08) >> 2) | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); b = ((even & 0x01) << 3) | ((even & 0x04) >> 1) | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); b <<= 4; b |= ((odd & 0x01) << 3) | (( odd & 0x04) >> 1) | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); Set_pixel(context, i++, j, a); Set_pixel(context, i++, j, b); break; case 3: a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); b = (( odd & 0x02) << 2) | (( odd & 0x08) >> 2) | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); c = ((even & 0x01) << 3) | ((even & 0x04) >> 1) | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); d = (( odd & 0x01) << 3) | (( odd & 0x04) >> 1) | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); Set_pixel(context, i++, j, j & 1 ? b : a); Set_pixel(context, i++, j, j & 1 ? a : b); Set_pixel(context, i++, j, j & 1 ? d : c); Set_pixel(context, i++, j, j & 1 ? c : d); break; case 5: if (d >= pl[c]) { d = 0; c++; } a = ((even & 0x80) >> 6) | ((even & 0x08) >> 3); b = (( odd & 0x80) >> 6) | (( odd & 0x08) >> 3); Set_pixel(context, i++, j, a + (b << 2) + c * 16); a = ((even & 0x40) >> 5) | ((even & 0x04) >> 2); b = (( odd & 0x40) >> 5) | (( odd & 0x04) >> 2); Set_pixel(context, i++, j, a + (b << 2) + c * 16); a = ((even & 0x20) >> 4) | ((even & 0x02) >> 1); b = (( odd & 0x20) >> 4) | (( odd & 0x02) >> 1); Set_pixel(context, i++, j, a + (b << 2) + c * 16); a = ((even & 0x10) >> 3) | ((even & 0x01) >> 0); b = (( odd & 0x10) >> 3) | (( odd & 0x01) >> 0); Set_pixel(context, i++, j, a + (b << 2) + c * 16); break; default: File_error = 2; return; } } d++; } fclose(file); fclose(feven); File_error = 0; } /** * Not yet implemented */ void Save_PPH(T_IO_Context* context) { (void)context; // unused // TODO // Detect mode // Wide pixels => B0 (4) // Square pixels: // - 16 colors used => R // - more colors used => B1 (if <16 colors per line) // Check palette // B0: use diagonal: 0, 17, 34, ... (assume the other are mixes) // R: use 16 used colors (or 16 first?) // B1: find the 16 colors used in a line? Or assume they are in-order already? } /* @} */