SCR (Amstrad CPC) file format

- Implements Test_SCR() / Load_SCR() for standard formats pictures
- Save .PAL file in Save_SCR()
This commit is contained in:
Thomas Bernard 2018-11-22 10:16:26 +01:00
parent bf461a8a52
commit 67a1220085
No known key found for this signature in database
GPG Key ID: 0FF11B67A5C0863C
6 changed files with 367 additions and 52 deletions

View File

@ -112,6 +112,8 @@ void Load_C64(T_IO_Context *);
void Save_C64(T_IO_Context *);
// -- SCR (Amstrad CPC)
void Test_SCR(T_IO_Context *, FILE *);
void Load_SCR(T_IO_Context *);
void Save_SCR(T_IO_Context *);
// -- CM5 (Amstrad CPC)

View File

@ -64,6 +64,7 @@ unsigned char mode3interlace(T_IO_Context * context, unsigned char x, unsigned c
return mode3pixel[Get_pixel(context, x,y) & 3] << 3 | mode3pixel[Get_pixel(context,x+1,y) & 3] << 2;
}
///@ingroup cpc
unsigned char *raw2crtc(T_IO_Context *context, unsigned char mode, unsigned char r9, unsigned long *outSize, unsigned char *r1, unsigned char r12, unsigned char r13)
{
unsigned char *outBuffer;

View File

@ -90,6 +90,7 @@ const T_Format File_formats[] = {
{FORMAT_ALL_IMAGES, "(all)", NULL, NULL, NULL, 0, 0, 0, "",
"gif;png;bmp;2bp;pcx;pkm;iff;lbm;ilbm;sham;ham;ham6;ham8;acbm;pic;anim;img;sci;scq;scf;scn;sco;pi1;pc1;cel;neo;"
"c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;prg;pmg;rpm;"
"cpc;scr;win;"
"tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff;ico;ic2;cur;cm5;pph;info;flc;bin;map"},
{FORMAT_ALL_PALETTES, "(pal)", NULL, NULL, NULL, 1, 0, 0, "", "kcf;pal;gpl"},
{FORMAT_ALL_FILES, "(*.*)", NULL, NULL, NULL, 0, 0, 0, "", "*"},
@ -114,7 +115,7 @@ const T_Format File_formats[] = {
{FORMAT_GPL, " gpl", Test_GPL, Load_GPL, Save_GPL, 1, 0, 0, "gpl", "gpl"},
{FORMAT_C64, " c64", Test_C64, Load_C64, Save_C64, 0, 1, 0, "c64",
"c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;prg;pmg;rpm"},
{FORMAT_SCR, " cpc", NULL, NULL, Save_SCR, 0, 0, 0, "cpc", "cpc;scr"},
{FORMAT_SCR, " cpc", Test_SCR, Load_SCR, Save_SCR, 0, 0, 0, "scr", "cpc;scr;win"},
{FORMAT_CM5, " cm5", Test_CM5, Load_CM5, Save_CM5, 0, 0, 1, "cm5", "cm5"},
{FORMAT_PPH, " pph", Test_PPH, Load_PPH, Save_PPH, 0, 0, 1, "pph", "pph"},
{FORMAT_XPM, " xpm", NULL, NULL, Save_XPM, 0, 0, 0, "xpm", "xpm"},

View File

@ -3733,24 +3733,65 @@ void Save_C64(T_IO_Context * context)
/**
* Test for SCR file (Amstrad CPC)
*
* TODO
* SCR file format is from "Advanced OCP Art Studio" :
* http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats
*
* For now we check the presence of a valid PAL file.
* If the PAL file is not there the pixel data may still be valid.
* The file size depends on the screen resolution.
* An AMSDOS header would be a good indication but in some cases it may not
* be there.
*/
void Test_SCR(T_IO_Context * context, FILE * file)
{
/**
* Mmh... not sure what we could test. Any idea ?
* The palette file can be tested, if it exists and have the right size it's
* ok. But if it's not there the pixel data may still be valid. And we can't
* use the filesize as this depends on the screen format.
FILE * pal_file;
unsigned long pal_size;
byte mode, color_anim_flag;
* An AMSDOS header would be a good indication but in some cases it may not
* be there */
(void)context; // unused
(void)file;
(void)file;
File_error = 1;
// requires the PAL file
pal_file = Open_file_read_with_alternate_ext(context, "pal");
if (pal_file == NULL)
return;
pal_size = File_length_file(pal_file);
if (pal_size == 239+128)
{
if (!CPC_check_AMSDOS(pal_file, NULL, NULL))
{
fclose(pal_file);
return;
}
fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header
}
else if (pal_size != 239)
{
fclose(pal_file);
return;
}
if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag))
{
fclose(pal_file);
return;
}
GFX2_Log(GFX2_DEBUG, "Test_SCR() mode=%d color animation flag %02X\n", mode, color_anim_flag);
if (mode <= 2 && (color_anim_flag == 0 || color_anim_flag == 0xff))
File_error = 0;
fclose(pal_file);
}
/**
* TODO
* Load Advanced OCP Art Studio files (Amstrad CPC)
*
* Only standard resolution files (Mode 0 160x200, mode 1 320x200 and
* mode 2 640x200) are supported. The .PAL file presence is required.
* "MJH" RLE packing is supported.
*
* @todo Ask user for screen size (or register values) in order to support
* non standard resolutions.
*/
void Load_SCR(T_IO_Context * context)
{
@ -3772,61 +3813,283 @@ void Load_SCR(T_IO_Context * context)
// All this mess enforces us to load (and unpack if needed) the file to a
// temporary 32k buffer before actually decoding it.
FILE * pal_file, * file;
unsigned long file_size, amsdos_file_size = 0;
byte mode, color_anim_flag, color_anim_delay;
byte pal_data[236]; // 12 palettes of 16+1 colors + 16 excluded inks + 16 protected inks
word width, height = 200;
byte bpp;
enum PIXEL_RATIO ratio;
byte * pixel_data;
word x, y;
int i;
// 1) Seek for a palette
// 2) If palette found get screenmode from there, else ask user
// 3) ask user for screen size (or register values)
// 4) Load color data from palette (if found)
// 5) Close palette
// 6) Open the file
// 7) Run around the screen to untangle the pixeldata
// 8) Close the file
(void)context; // unused
File_error = 1;
// requires the PAL file
pal_file = Open_file_read_with_alternate_ext(context, "pal");
if (pal_file == NULL)
return;
file_size = File_length_file(pal_file);
if (file_size == 239+128)
{
if (!CPC_check_AMSDOS(pal_file, NULL, NULL))
{
fclose(pal_file);
return;
}
fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header
}
if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag)
|| !Read_byte(pal_file, &color_anim_delay) || !Read_bytes(pal_file, pal_data, 236))
{
GFX2_Log(GFX2_WARNING, "Load_SCR() failed to load .PAL file\n");
fclose(pal_file);
return;
}
fclose(pal_file);
GFX2_Log(GFX2_DEBUG, "Load_SCR() mode=%d color animation flag=%02X delay=%u\n",
mode, color_anim_flag, color_anim_delay);
switch (mode)
{
case 0:
width = 160;
bpp = 4;
ratio = PIXEL_WIDE;
break;
case 1:
width = 320;
bpp = 2;
ratio = PIXEL_SIMPLE;
break;
case 2:
width = 640;
bpp = 1;
ratio = PIXEL_TALL;
break;
default:
return; // unsupported
}
if (Config.Clear_palette)
memset(context->Palette,0,sizeof(T_Palette));
// Setup the palette (amstrad hardware palette)
CPC_set_HW_palette(context->Palette + 0x40);
// Set the palette for this picture
for (i = 0; i < 16; i++)
context->Palette[i] = context->Palette[pal_data[12*i]];
file = Open_file_read(context);
if (file == NULL)
return;
file_size = File_length_file(file);
if (CPC_check_AMSDOS(file, NULL, &amsdos_file_size))
{
if (file_size < (amsdos_file_size + 128))
{
GFX2_Log(GFX2_ERROR, "Load_SCR() mismatch in file size. AMSDOS file size %lu, should be %lu\n", amsdos_file_size, file_size - 128);
fclose(file);
return;
}
else if (file_size > (amsdos_file_size + 128))
GFX2_Log(GFX2_INFO, "Load_SCR() %lu extra bytes at end of file\n", file_size - 128 - amsdos_file_size);
fseek(file, 128, SEEK_SET); // right after AMSDOS header
}
else
fseek(file, 0, SEEK_SET);
Pre_load(context, width, height, file_size, FORMAT_SCR, ratio, bpp);
if(amsdos_file_size != 0)
file_size = amsdos_file_size;
pixel_data = malloc(16384);
if (file_size >= 16336 && file_size <= 16384)
Read_bytes(file, pixel_data, file_size);
else
{
byte sig[3];
word block_length;
// MJH packed format
i = 0;
do
{
if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length))
break;
if (0 != memcmp(sig, "MJH", 3))
break;
GFX2_Log(GFX2_DEBUG, " %.3s %u\n", sig, block_length);
file_size -= 5;
while (block_length > 0)
{
byte code;
if (!Read_byte(file, &code))
break;
file_size--;
if (code == 1)
{
byte repeat, value;
if (!Read_byte(file, &repeat) || !Read_byte(file, &value))
break;
file_size -= 2;
do
{
pixel_data[i++] = value;
block_length--;
}
while(--repeat != 0);
}
else
{
pixel_data[i++] = code;
block_length--;
}
}
}
while(file_size > 0 && i < 16384);
}
fclose(file);
for (y = 0; y < height; y++)
{
const byte * line;
line = pixel_data + ((y & 7) << 11) + ((y >> 3) * 80);
x = 0;
for (i = 0; i < 80; i++)
{
byte pixels = line[i];
switch (mode)
{
case 0:
Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2);
Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3);
break;
case 1:
do {
// upper nibble is 4 lower color bits, lower nibble is 4 upper color bits
Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2);
pixels <<= 1;
}
while ((x & 3) != 0);
break;
case 2:
do {
Set_pixel(context, x++, y, (pixels & 0x80) >> 7);
pixels <<= 1;
}
while ((x & 7) != 0);
}
}
}
free(pixel_data);
File_error = 0;
}
/**
* Save Amstrad SCR file
*
* guess mode from aspect ratio :
* - normal pixels are mode 1
* - wide pixels are mode 0
* - tall pixels are mode 2
*
* Mode and palette are stored in a .PAL file.
*
* The picture color index should be 0-15,
* The CPC Hardware palette is expected to be set (indexes 64 to 95)
*
* @todo Add possibility to set R9, R12, R13 values
* @todo Add OCP packing support
* @todo Add possibility to include AMSDOS header, with proper loading
* address guessed from r12/r13 values.
*/
void Save_SCR(T_IO_Context * context)
{
// TODO : Add possibility to set R9, R12, R13 values
// TODO : Add OCP packing support
// TODO : Add possibility to include AMSDOS header, with proper loading
// address guessed from r12/r13 values.
unsigned char* output;
unsigned long outsize;
unsigned char r1;
int cpc_mode;
FILE* file;
int i, j;
unsigned char* output;
unsigned long outsize = 0;
unsigned char r1 = 0;
int cpc_mode;
FILE* file;
switch(Pixel_ratio)
{
case PIXEL_WIDE:
case PIXEL_WIDE2:
cpc_mode = 0;
break;
case PIXEL_TALL:
case PIXEL_TALL2:
case PIXEL_TALL3:
cpc_mode = 2;
break;
default:
cpc_mode = 1;
break;
}
switch(Pixel_ratio)
{
case PIXEL_WIDE:
case PIXEL_WIDE2:
cpc_mode = 0;
break;
case PIXEL_TALL:
case PIXEL_TALL2:
case PIXEL_TALL3:
cpc_mode = 2;
break;
default:
cpc_mode = 1;
break;
}
output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0);
file = Open_file_write(context);
Write_bytes(file, output, outsize);
file = Open_file_write_with_alternate_ext(context, "pal");
if (file == NULL)
return;
if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0))
{
fclose(file);
return;
}
for (i = 0; i < 16; i++)
{
// search for the color in the HW palette (0x40-0x5F)
byte index = 0x40;
while ((index < 0x60) &&
(0 != memcmp(context->Palette + i, context->Palette + index, sizeof(T_Components))))
index++;
if (index >= 0x60)
{
GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i);
index = 0x54 - i; // default
}
for (j = 0; j < 12; j++) // write the same color for the 12 frames
{
Write_byte(file, index);
}
}
// border
for (j = 0; j < 12; j++)
{
Write_byte(file, 0x54); // black
}
// excluded inks
for (i = 0; i < 16; i++)
{
Write_byte(file, 0);
}
// protected inks
for (i = 0; i < 16; i++)
{
Write_byte(file, 0);
}
fclose(file);
free (output);
output = NULL;
output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0);
GFX2_Log(GFX2_DEBUG, "Save_SCR() output=%p outsize=%lu r1=$%02X\n", output, outsize, r1);
if (output == NULL)
return;
file = Open_file_write(context);
if (file == NULL)
File_error = 1;
else
{
File_error = 0;
if (!Write_bytes(file, output, outsize))
File_error = 1;
fclose(file);
}
free (output);
}
/**

View File

@ -504,6 +504,41 @@ void CPC_set_HW_palette(T_Components * palette)
memcpy(palette, CPC_Hw_Palette, sizeof(CPC_Hw_Palette));
}
int CPC_check_AMSDOS(FILE * file, word * loading_address, unsigned long * file_length)
{
int i;
byte data[128];
word checksum = 0;
fseek(file, 0, SEEK_SET);
if (!Read_bytes(file, data, 128))
return 0;
for (i = 1; i <= 11; i++) // check filename and extension
{
if (data[i] < ' ' || data[i] >= 0x7F)
return 0;
}
for (i = 0; i < 67; i++)
checksum += (word)data[i];
if (checksum != (data[67] | (data[68] << 8)))
{
GFX2_Log(GFX2_INFO, "AMSDOS header checksum mismatch %04X != %04X\n",
checksum, data[67] | (data[68] << 8));
return 0;
}
GFX2_Log(GFX2_DEBUG, "AMSDOS : user=%02X %.8s.%.3s %d %u(%u) bytes, load at $%04X checksum $%04X\n",
data[0],
(char *)(data + 1), (char *)(data + 9), data[18],
data[24] | (data[25] << 8), data[64] | (data[65] << 8) | (data[66] << 16),
data[26] | (data[27] << 8), checksum);
if (loading_address)
*loading_address = data[26] | (data[27] << 8);
if (file_length)
*file_length = data[64] | (data[65] << 8) | (data[66] << 16); // 24bit size
// *file_length = data[24] | (data[25] << 8); // 16bit size
return 1;
}
int DECB_Check_binary_file(FILE * f)
{
byte code;

View File

@ -73,6 +73,19 @@ void ZX_Spectrum_set_palette(T_Components * palette);
*/
void CPC_set_HW_palette(T_Components * palette);
/**
* Check AMSDOS header
*
* see http://www.cpcwiki.eu/index.php/AMSDOS_Header
*
* @param[in] file an open file
* @param[out] loading_address the loading address from the header
* @param[out] file_length the file length written in the header
* @return 0 if the file does not contain a valid AMSDOS header
* @return 1 if it does.
*/
int CPC_check_AMSDOS(FILE * file, word * loading_address, unsigned long * file_length);
/** @}*/
/** @defgroup decb DECB binary format