Width);x_pos++)
pixels[x_pos]=Get_pixel(context, x_pos,y_pos);
}
// Encodage de la scanline
PC1_1line_to_4bp(pixels,ptr,ptr+40,ptr+80,ptr+120);
ptr+=160;
}
// Compression du buffer
if (PackBits_pack_buffer(file, bufferdecomp, 32000) < 0)
File_error = 1;
PI1_save_ranges(context, buffer, 32);
if (Write_bytes(file, buffer, 32))
{
fclose(file);
}
else // Error d'écriture (disque plein ou protégé)
{
fclose(file);
Remove_file(context);
File_error=1;
}
// Libération des buffers mémoire
free(bufferdecomp);
bufferdecomp = NULL;
}
else
{
fclose(file);
Remove_file(context);
File_error=1;
}
}
//////////////////////////////////// NEO ////////////////////////////////////
/**
NeoChrome Format :
1 word flag word [always 0]
1 word resolution [0 = low res, 1 = medium res, 2 = high res]
16 words palette
12 bytes filename [usually " . "]
1 word color animation limits. High bit (bit 15) set if color
animation data is valid. Low byte contains color animation
limits (4 most significant bits are left/lower limit,
4 least significant bits are right/upper limit).
1 word color animation speed and direction. High bit (bit 15) set
if animation is on. Low order byte is # vblanks per step.
If negative, scroll is left (decreasing). Number of vblanks
between cycles is |x| - 1
1 word # of color steps (as defined in previous word) to display
picture before going to the next. (For use in slide shows)
1 word image X offset [unused, always 0]
1 word image Y offset [unused, always 0]
1 word image width [unused, always 320]
1 word image height [unused, always 200]
33 words reserved for future expansion
32000 bytes pixel data
Dali *.SD0 (ST low resolution)
*.SD1 (ST medium resolution)
*.SD2 (ST high resolution)
Files do not seem to have any resolution or bit plane info stored in them. The file
extension seems to be the only way to determine the contents.
1 long file id? [always 0]
16 words palette
92 bytes reserved? [usually 0]
*/
void Test_NEO(T_IO_Context * context, FILE * file)
{
word flag;
word resolution; // Atari ST resolution
(void)context;
File_error=1;
if (File_length_file(file) != 32128)
return;
if (!Read_word_be(file,&flag))
return;
// Flag word : always 0
if (flag != 0)
return;
if (!Read_word_be(file,&resolution))
return;
// 0 = STlow, 1 = STmed, 2 = SThigh
if (resolution==0 || resolution==1 || resolution==2)
File_error = 0;
}
/// Load Neochrome file format
void Load_NEO(T_IO_Context * context)
{
enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
word flag;
word resolution; // Atari ST resolution
word width, height;
byte bpp;
word color_cycling_range, color_cycling_delay;
word display_time;
word image_width, image_height, image_X_pos, image_Y_pos;
FILE *file;
word x_pos,y_pos;
byte * ptr;
byte buffer[160];
byte pixels[16];
File_error = 1;
file = Open_file_read(context);
if (file == NULL)
return;
if (!Read_word_be(file,&flag))
goto error;
// Flag word : always 0
if (flag != 0)
goto error;
if (!Read_word_be(file,&resolution))
goto error;
switch (resolution)
{
case 0:
width = 320;
height = 200;
bpp = 4;
break;
case 1:
width = 640;
height = 200;
bpp = 2;
ratio = PIXEL_TALL;
break;
case 2:
width = 640;
height = 400;
bpp = 1;
break;
default:
goto error;
}
Pre_load(context, width, height, File_length_file(file), FORMAT_NEO, ratio, bpp);
if (!Read_bytes(file,buffer,32))
goto error;
// Initialisation de la palette
if (Config.Clear_palette)
memset(context->Palette, 0, sizeof(T_Palette));
PI1_decode_palette(buffer, context->Palette);
if (!Read_bytes(file, buffer, 12))
goto error;
buffer[12] = '\0';
GFX2_Log(GFX2_DEBUG, "NEO resolution %u name=\"%s\"\n", resolution, (char *)buffer);
if (!Read_word_be(file, &color_cycling_range)
|| !Read_word_be(file, &color_cycling_delay)
|| !Read_word_be(file, &display_time))
goto error;
GFX2_Log(GFX2_DEBUG, " Color cycling : %04x %04x. Time to show %u\n", color_cycling_range, color_cycling_delay, display_time);
if (color_cycling_range & 0x8000)
{
context->Cycle_range[context->Color_cycles].Start = (color_cycling_range & 0x00f0) >> 4;
context->Cycle_range[context->Color_cycles].End = (color_cycling_range & 0x000f);
if (color_cycling_delay & 0x8000)
{
// color cycling on
color_cycling_delay &= 0xff;
if (color_cycling_delay & 0x0080)
{
context->Cycle_range[context->Color_cycles].Inverse = 1;
color_cycling_delay = 256 - color_cycling_delay;
}
else
context->Cycle_range[context->Color_cycles].Inverse = 0;
// Speed resolution is 0.2856Hz
// NEO color_cycling_delay is in 50Hz VBL
// Speed = (50/delay) / 0.2856 = 175 / delay
if (color_cycling_delay != 0)
context->Cycle_range[context->Color_cycles].Speed = 175 / color_cycling_delay;
else
context->Cycle_range[context->Color_cycles].Speed = COLOR_CYCLING_SPEED_MAX; // fastest
if (context->Cycle_range[context->Color_cycles].Speed > COLOR_CYCLING_SPEED_MAX)
context->Cycle_range[context->Color_cycles].Speed = COLOR_CYCLING_SPEED_MAX;
}
else
context->Cycle_range[context->Color_cycles].Speed = 0; // cycling off
context->Color_cycles++;
}
if (!Read_word_be(file, &image_X_pos) || !Read_word_be(file, &image_Y_pos)
|| !Read_word_be(file, &image_width) || !Read_word_be(file, &image_height))
goto error;
GFX2_Log(GFX2_DEBUG, " pos (%u,%u) size %ux%u\n", image_X_pos, image_Y_pos, image_width, image_height);
if (!Read_bytes(file, buffer, 128-4-32-12-6-8))
goto error;
GFX2_LogHexDump(GFX2_DEBUG, "NEO ", buffer, 0, 128-4-32-12-6-8);
// Chargement/décompression de l'image
for (y_pos=0;y_pos> i)) ? 1 : 0;
for (; i < 16; i++)
pixels[i] = (ptr[1] & (0x80 >> (i - 8))) ? 1 : 0;
ptr += 2;
break;
default:
goto error;
}
for (i = 0; i < 16; i++)
Set_pixel(context, x_pos++, y_pos, pixels[i]);
}
}
File_error = 0; // everything was ok
error:
fclose(file);
}
/**
* Save in NeoChrome format
*
* @todo support medium and high resolution
*/
void Save_NEO(T_IO_Context * context)
{
word resolution = 0;
FILE *file = NULL;
short x_pos,y_pos;
word color_cycling_range = 0, color_cycling_delay = 0;
word display_time = 0;
word image_width = 320, image_height = 200;
byte buffer[32];
byte pixels[320];
char * ext;
int i, j;
File_error = 1;
file = Open_file_write(context);
if (file == NULL)
return;
// flags and resolution
if (!Write_word_be(file, 0) || !Write_word_be(file, resolution))
goto error;
// palette
PI1_code_palette(context->Palette, buffer);
if (!Write_bytes(file, buffer, 16*2))
goto error;
// file name
i = 0;
j = 0;
ext = strrchr(context->File_name, '.');
while (j < 8 && ext != (context->File_name + i))
{
byte c = context->File_name[i++];
if (c == 0)
break;
if (c >= 'a' && c <= 'z')
c -= 32;
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_'))
buffer[j++] = c;
}
while (j < 8)
buffer[j++] = ' ';
buffer[j++] = '.';
if (ext != NULL)
{
i = 0;
while (j < 12)
{
byte c = ext[i++];
if (c == 0)
break;
if (c >= 'a' && c <= 'z')
c -= 32;
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_'))
buffer[j++] = c;
}
}
while (j < 12)
buffer[j++] = ' ';
if (!Write_bytes(file, buffer, 12))
goto error;
// Save the 1st valid Color cycling range
for (i = 0; i < context->Color_cycles; i++)
{
if (context->Cycle_range[i].Start < 16 && context->Cycle_range[i].End < 16)
{
color_cycling_range = 0x8000 | (context->Cycle_range[i].Start << 4) | context->Cycle_range[i].End;
if (context->Cycle_range[i].Speed > 0)
{
color_cycling_delay = 175 / context->Cycle_range[i].Speed;
if (color_cycling_delay > 0 && context->Cycle_range[i].Inverse)
color_cycling_delay = 256 - color_cycling_delay;
color_cycling_delay |= 0x8000;
}
break;
}
}
if (!Write_word_be(file, color_cycling_range) || !Write_word_be(file, color_cycling_delay) || !Write_word_be(file, display_time))
goto error;
// Save image position and size
if (!Write_word_be(file, 0) || !Write_word_be(file, 0)
|| !Write_word_be(file, image_width) || !Write_word_be(file, image_height))
goto error;
// Fill with 128 bytes header with 0's
// a few files have the string "NEO!" at offset 124 (0x7C)
for (i = ftell(file); i < 128; i++)
{
if (!Write_byte(file, 0))
goto error;
}
// image coding
for (y_pos=0;y_pos<200;y_pos++)
{
// Codage de la ligne
memset(pixels, 0, 320);
if (y_pos < context->Height)
{
for (x_pos = 0; (x_pos < 320) && (x_pos < context->Width); x_pos++)
pixels[x_pos] = Get_pixel(context, x_pos, y_pos);
}
for (x_pos=0; x_pos < 320; x_pos += 16)
{
PI1_16p_to_8b(pixels + x_pos, buffer);
if (!Write_bytes(file, buffer, 8))
goto error;
}
}
fclose(file);
File_error = 0;
return;
error:
if (file != NULL)
fclose(file);
Remove_file(context);
}
void Test_TNY(T_IO_Context * context, FILE * file)
{
unsigned long file_size;
unsigned long theorical_size;
byte res; // 0 = Low, 1 = midres, 2 = hires. +3 for color cycling
word control_bytes;
word data_words;
(void)context;
File_error = 1;
file_size = File_length_file(file);
if (file_size > 32044)
return;
if (!Read_byte(file, &res))
return;
if (res >= 6)
return;
if (res >= 3)
{
if (fseek(file, 4, SEEK_CUR) < 0) // skip color cycling info
return;
theorical_size = 41;
}
else
theorical_size = 37;
if (fseek(file, 16*2, SEEK_CUR) < 0) // skip palette
return;
if (!Read_word_be(file, &control_bytes) || !Read_word_be(file, &data_words))
return;
theorical_size += control_bytes + data_words * 2;
if (theorical_size <= file_size && file_size < theorical_size + 512)
{
GFX2_Log(GFX2_DEBUG, "TNY res=%d %hu control bytes, %hu data words\n",
(int)res, control_bytes, data_words);
File_error = 0;
}
}
void Load_TNY(T_IO_Context * context)
{
FILE * file;
byte res; // 0 = Low, 1 = midres, 2 = hires. +3 for color cycling
word control_bytes;
byte * control;
word data_words;
byte * data;
byte buffer[32000 + 2];
File_error = 1;
file = Open_file_read(context);
if (file == NULL)
return;
if (Read_byte(file, &res) && (res < 6))
{
enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
word width = 640, height = 200;
byte bpp = 4;
byte cycling_range = 0;
byte cycling_speed = 0;
word duration = 0;
switch (res)
{
case 0:
case 3:
width = 320;
break;
case 1:
case 4:
bpp = 2;
ratio = PIXEL_TALL;
break;
case 2:
case 5:
bpp = 1;
height = 400;
break;
}
if (res >= 3)
{
if( !Read_byte(file, &cycling_range)
|| !Read_byte(file, &cycling_speed)
|| !Read_word_be(file, &duration))
{
fclose(file);
return;
}
GFX2_Log(GFX2_DEBUG, "TNY Color Cycling : %02x speed=%02x duration=%d\n",
cycling_range, cycling_speed, duration);
}
// Read palette
if ( !Read_bytes(file, buffer, 32)
|| !Read_word_be(file, &control_bytes)
|| !Read_word_be(file, &data_words))
{
fclose(file);
return;
}
control = GFX2_malloc(control_bytes);
data = GFX2_malloc(data_words * 2);
if (control == NULL || data == NULL)
{
fclose(file);
return;
}
if ( Read_bytes(file, control, control_bytes)
&& Read_bytes(file, data, data_words * 2))
{
byte cb;
word cc, count;
int src, dst;
int line;
File_error = 0;
Pre_load(context, width, height, File_length_file(file), FORMAT_TNY, ratio, bpp);
// Set palette
if (Config.Clear_palette)
memset(context->Palette, 0, sizeof(T_Palette));
PI1_decode_palette(buffer, context->Palette);
if (res >= 3)
{
context->Cycle_range[context->Color_cycles].Start = (cycling_range & 0xf0) >> 4;
context->Cycle_range[context->Color_cycles].End = (cycling_range & 0x0f);
if (cycling_speed & 0x80)
{
context->Cycle_range[context->Color_cycles].Inverse = 1;
cycling_speed = 256 - cycling_speed;
}
else
context->Cycle_range[context->Color_cycles].Inverse = 0;
context->Cycle_range[context->Color_cycles].Speed = 175 / cycling_speed;
context->Color_cycles++;
}
for (cc = 0, src = 0, dst = 0; cc < control_bytes && dst < 32000; )
{
cb = control[cc++];
if (cb & 0x80)
{
// copy
do {
buffer[dst++] = data[src++];
buffer[dst++] = data[src++];
} while(++cb != 0);
}
else switch(cb)
{
case 0: // repeat (word count)
count = control[cc++] << 8;
count += control[cc++];
while (count-- > 0)
{
buffer[dst++] = data[src];
buffer[dst++] = data[src+1];
}
src += 2;
break;
case 1: // copy (word count)
count = control[cc++] << 8;
count += control[cc++];
memcpy(buffer+dst, data+src, count * 2);
dst += count * 2;
src += count * 2;
break;
default: // repeat next data word
while(cb-- > 0)
{
buffer[dst++] = data[src];
buffer[dst++] = data[src+1];
}
src += 2;
}
}
GFX2_Log(GFX2_DEBUG, " %d %d ; %d %d ; %d\n", cc, control_bytes, src, data_words * 2, dst);
/**
* Tiny stuff packs the ST RAM in 4 planes of 8000 bytes
* Data is organized by column in each plane
* Warning : this is the same organization for all 3 ST Video modes
*/
for (line = 0; line < 200; line++)
{
int col;
for (col = 0; col < 20; col++)
{
byte planar[8];
byte pixels[16];
src = (line + col * 200) * 2;
for (dst = 0; dst < 8;)
{
planar[dst++] = buffer[src];
planar[dst++] = buffer[src+1];
src += 8000;
}
switch(res)
{
case 0:
case 3:
PI1_8b_to_16p(planar, pixels);
for (dst = 0 ; dst < 16; dst++)
Set_pixel(context, col * 16 + dst, line, pixels[dst]);
break;
case 1:
case 4:
PI2_4b_to_16p(planar, pixels);
for (dst = 0 ; dst < 16; dst++)
Set_pixel(context, col * 32 + dst, line, pixels[dst]);
PI2_4b_to_16p(planar + 4, pixels);
for (dst = 0 ; dst < 16; dst++)
Set_pixel(context, col * 32 + 16 + dst, line, pixels[dst]);
break;
case 2:
case 5:
for (dst = 0 ; dst < 64; dst++)
Set_pixel(context, (col % 10) * 64 + dst, line * 2 + (col / 10),
(planar[(dst >> 3)] >> (7 - (dst & 7))) & 1);
}
}
}
}
free(control);
free(data);
}
fclose(file);
}
/**
* Save in Tiny compressed format.
*
* @todo support medium and high resolution
*/
void Save_TNY(T_IO_Context * context)
{
byte res = 0;
FILE * file;
byte buffer[32000];
byte control[16000];
byte data[32000];
word cc = 0, dc = 0; // control count, data count
int line, dst, src;
File_error = 1;
file = Open_file_write(context);
if (file == NULL)
return;
// TODO : detect color cycling
if (!Write_byte(file, res)) // resolution +3 with color cycling
goto error;
// palette
PI1_code_palette(context->Palette, buffer);
if (!Write_bytes(file, buffer, 16*2))
goto error;
/**
* fill the buffer with the special Tiny Stuff organization
* @see Load_TNY
*/
for (line = 0; line < 200; line++)
{
int col;
for (col = 0; col < 20; col++)
{
byte planar[8];
// Low res
PI1_16p_to_8b(context->Target_address + line * context->Pitch + col*16, planar);
dst = (line + col * 200) * 2;
for (src = 0; src < 8;)
{
buffer[dst] = planar[src++];
buffer[dst+1] = planar[src++];
dst += 8000;
}
}
}
#define WORDS_EQU(p1, p2) (((p1)[0] == (p2)[0]) && ((p1)[1] == (p2)[1]))
// now the compression
for (src = 0; src < 32000; )
{
word count;
// count repeat
count = 0;
while ((src + count * 2) < 32000 && WORDS_EQU(buffer + src, buffer + src + count * 2))
count++;
if (count > 127)
{
//GFX2_Log(GFX2_DEBUG, "%5d REPEAT %d %02x%02x\n", src, count, buffer[src+1], buffer[src]);
control[cc++] = 0; // repeat word
control[cc++] = count >> 8;
control[cc++] = count & 0xff;
data[dc * 2] = buffer[src];
data[dc * 2 + 1] = buffer[src+1];
dc++;
src += count * 2;
count = 1;
}
else if (count > 1)
{
// TODO: merge a repeat count of 2 between 2 copy count ?
//GFX2_Log(GFX2_DEBUG, "%5d REPEAT %d %02x%02x\n", src, count, buffer[src+1], buffer[src]);
control[cc++] = (byte)count;
data[dc * 2] = buffer[src];
data[dc * 2 + 1] = buffer[src+1];
dc++;
src += count * 2;
count = 1;
}
if (src >= 32000)
break;
// count copy
while ((src + count * 2) < 32000 && !WORDS_EQU(buffer + src + (count - 1) * 2, buffer + src + count * 2))
count++;
if ((src + count * 2) < 32000)
count--;
if (count > 128)
{
//GFX2_Log(GFX2_DEBUG, "%5d COPY %d %02x%02x %02x%02x...\n",
// src, count, buffer[src+1], buffer[src], buffer[src+3], buffer[src+2]);
control[cc++] = 1; // copy word
control[cc++] = count >> 8;
control[cc++] = count & 0xff;
memcpy(data + dc * 2, buffer + src, count * 2);
dc += count;
src += count * 2;
}
else if (count > 0)
{
//GFX2_Log(GFX2_DEBUG, "%5d COPY %d %02x%02x ...\n",
// src, count, buffer[src+1], buffer[src]);
control[cc++] = (byte)(256 - count); // copy byte
memcpy(data + dc * 2, buffer + src, count * 2);
dc += count;
src += count * 2;
}
}
if (!Write_word_be(file, cc) || !Write_word_be(file, dc))
goto error;
if (!Write_bytes(file, control, cc) || !Write_bytes(file, data, dc * 2))
goto error;
fclose(file);
File_error = 0;
return;
error:
if (file != NULL)
fclose(file);
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_4b_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
*
* @see http://www.atari-wiki.com/index.php/CrackArt_file_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);
}
/** @} */