This gives a much clearer overview of the licensing. It also shows there are some problems: - Some files are under GPLv3 only - Some files have no known license at all.
1892 lines
49 KiB
C
1892 lines
49 KiB
C
/* vim:expandtab:ts=2 sw=2:
|
|
*/
|
|
/* Grafx2 - The Ultimate 256-color bitmap paint program
|
|
|
|
Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.
|
|
|
|
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 <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
///@file stformats.c
|
|
/// Formats for the Atari ST line of machines
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "fileformats.h"
|
|
#include "loadsavefuncs.h"
|
|
#include "io.h"
|
|
#include "misc.h"
|
|
#include "palette.h"
|
|
#include "gfx2log.h"
|
|
#include "gfx2mem.h"
|
|
#include "packbits.h"
|
|
|
|
/**
|
|
* @defgroup atarist Atari ST picture formats
|
|
* @ingroup loadsaveformats
|
|
*
|
|
* Support for Atari ST picture formats. The Atari ST has
|
|
* 3 video modes :
|
|
* - low res : 320x200 16 colors
|
|
* - med res : 640x200 4 colors
|
|
* - high res : 640x400 monochrome
|
|
*
|
|
* Supported formats :
|
|
* - PI1 : Degas
|
|
* - PC1 : Degas elite compressed
|
|
* - NEO : Neochrome
|
|
* - TNY : Tiny Stuff / Tiny View
|
|
*
|
|
* @{
|
|
*/
|
|
//////////////////////////////////// PI1 ////////////////////////////////////
|
|
|
|
//// DECODAGE d'une partie d'IMAGE ////
|
|
|
|
/**
|
|
* 4bpp Planar to chunky conversion.
|
|
*
|
|
* Reads 16 pixels from 4 words with Atari ST low resolution layout.
|
|
* @param src 8 bytes Atari ST screen memory
|
|
* @param dest 16 bytes buffer
|
|
*/
|
|
static void PI1_8b_to_16p(const byte * src, byte * dest)
|
|
{
|
|
int i; // index du pixel à calculer
|
|
word byte_mask; // Masque de decodage
|
|
word w0,w1,w2,w3; // Les 4 words bien ordonnés de la source
|
|
|
|
byte_mask=0x8000;
|
|
w0=(((word)src[0])<<8) | src[1];
|
|
w1=(((word)src[2])<<8) | src[3];
|
|
w2=(((word)src[4])<<8) | src[5];
|
|
w3=(((word)src[6])<<8) | src[7];
|
|
for (i=0;i<16;i++)
|
|
{
|
|
// Pour décoder le pixel n°i, il faut traiter les 4 words sur leur bit
|
|
// correspondant à celui du masque
|
|
|
|
dest[i]=((w0 & byte_mask)?0x01:0x00) |
|
|
((w1 & byte_mask)?0x02:0x00) |
|
|
((w2 & byte_mask)?0x04:0x00) |
|
|
((w3 & byte_mask)?0x08:0x00);
|
|
byte_mask>>=1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 2bpp Planar to chunky conversion.
|
|
*
|
|
* Reads 16 pixels from 2 words with Atari ST medium resolution layout.
|
|
* @param src 4 bytes Atari ST screen memory
|
|
* @param dest 16 bytes buffer
|
|
*/
|
|
static void PI2_4b_to_16p(const byte * src, byte * dest)
|
|
{
|
|
int i; // index du pixel à calculer
|
|
word mask; // Masque de decodage
|
|
word w0,w1;
|
|
|
|
w0=(((word)src[0])<<8) | src[1];
|
|
w1=(((word)src[2])<<8) | src[3];
|
|
mask=0x8000;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
dest[i] = ((w0 & mask) ? 0x01 : 0) | ((w1 & mask) ? 0x02 : 0);
|
|
mask >>= 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Chunky to 4bpp planar conversion.
|
|
*
|
|
* For ST Low resolution.
|
|
* @param src 16 bytes buffer
|
|
* @param dest 8 bytes (4 words in Atari ST memory screen layout)
|
|
*/
|
|
static void PI1_16p_to_8b(const byte * src, byte * dest)
|
|
{
|
|
int i; // index du pixel à calculer
|
|
word byte_mask; // Masque de codage
|
|
word w0,w1,w2,w3; // Les 4 words bien ordonnés de la destination
|
|
|
|
byte_mask=0x8000;
|
|
w0=w1=w2=w3=0;
|
|
for (i=0;i<16;i++)
|
|
{
|
|
// Pour coder le pixel n°i, il faut modifier les 4 words sur leur bit
|
|
// correspondant à celui du masque
|
|
|
|
w0|=(src[i] & 0x01)?byte_mask:0x00;
|
|
w1|=(src[i] & 0x02)?byte_mask:0x00;
|
|
w2|=(src[i] & 0x04)?byte_mask:0x00;
|
|
w3|=(src[i] & 0x08)?byte_mask:0x00;
|
|
byte_mask>>=1;
|
|
}
|
|
dest[0]=w0 >> 8;
|
|
dest[1]=w0 & 0x00FF;
|
|
dest[2]=w1 >> 8;
|
|
dest[3]=w1 & 0x00FF;
|
|
dest[4]=w2 >> 8;
|
|
dest[5]=w2 & 0x00FF;
|
|
dest[6]=w3 >> 8;
|
|
dest[7]=w3 & 0x00FF;
|
|
}
|
|
|
|
/**
|
|
* Decode the 16 colors Atari ST(E) palette
|
|
*
|
|
* @param src 32 bytes Atari ST(E) palette
|
|
* @param palette GrafX2 palette structure
|
|
*/
|
|
static void PI1_decode_palette(const byte * src, T_Components * palette)
|
|
{
|
|
int i; // Numéro de la couleur traitée
|
|
word w; // Word contenant le code
|
|
|
|
// Schéma d'un word =
|
|
//
|
|
// Low High
|
|
// VVVV RRRR | 0000 BBBB
|
|
// 0321 0321 | 0321
|
|
|
|
for (i=0;i<16;i++)
|
|
{
|
|
w = (word)src[0] << 8 | (word)src[1];
|
|
src += 2;
|
|
|
|
palette[i].R = (((w & 0x0700)>>7) | ((w & 0x0800) >> 11)) * 0x11 ;
|
|
palette[i].G = (((w & 0x0070)>>3) | ((w & 0x0080) >> 7)) * 0x11 ;
|
|
palette[i].B = (((w & 0x0007)<<1) | ((w & 0x0008) >> 3)) * 0x11 ;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Code a 16 colors Atari ST(E) palette.
|
|
*
|
|
* @param palette GrafX2 palette structure
|
|
* @param dest 32 bytes buffer
|
|
*/
|
|
void PI1_code_palette(const T_Components * palette, byte * dest)
|
|
{
|
|
int i; // Numéro de la couleur traitée
|
|
word w; // Word contenant le code
|
|
int plain_ST_colors = Get_palette_RGB_scale() == 8;
|
|
|
|
GFX2_Log(GFX2_DEBUG, "encoding ST%s palette (%d bits per component)\n",
|
|
plain_ST_colors ? "" : "e", plain_ST_colors ? 3 : 4);
|
|
// Schéma d'un word =
|
|
//
|
|
// Low High
|
|
// VVVV RRRR | 0000 BBBB
|
|
// 0321 0321 | 0321
|
|
|
|
for (i=0;i<16;i++)
|
|
{
|
|
w = ((word)(palette[i].R & 0xe0) << 3);
|
|
w |= ((word)(palette[i].G & 0xe0) >> 1);
|
|
w |= ((word)(palette[i].B & 0xe0) >> 5);
|
|
if (!plain_ST_colors)
|
|
{
|
|
// add the STe specific bit
|
|
w |= ((word)(palette[i].R & 0x10) << 7);
|
|
w |= ((word)(palette[i].G & 0x10) << 3);
|
|
w |= ((word)(palette[i].B & 0x10) >> 1);
|
|
}
|
|
*dest++ = (w >> 8);
|
|
*dest++ = (w & 0xff);
|
|
}
|
|
}
|
|
|
|
/// Load color cycling data from a PI1 or PC1 image (Degas Elite format)
|
|
static void PI1_load_ranges(T_IO_Context * context, const byte * buffer, int size)
|
|
{
|
|
int range;
|
|
|
|
if (buffer==NULL || size<32)
|
|
return;
|
|
|
|
for (range=0; range < 4; range ++)
|
|
{
|
|
word min_col, max_col, direction, delay;
|
|
|
|
min_col = (buffer[size - 32 + range*2 + 0] << 8) | buffer[size - 32 + range*2 + 1];
|
|
max_col = (buffer[size - 32 + range*2 + 8] << 8) | buffer[size - 32 + range*2 + 9];
|
|
direction = (buffer[size - 32 + range*2 + 16] << 8) | buffer[size - 32 + range*2 + 17];
|
|
delay = (buffer[size - 32 + range*2 + 24] << 8) | buffer[size - 32 + range*2 + 25];
|
|
|
|
if (max_col < min_col)
|
|
SWAP_WORDS(min_col,max_col)
|
|
|
|
GFX2_Log(GFX2_DEBUG, "Degas Color cycling : [#%d:#%d] direction=%d delay=%d\n", min_col, max_col, direction, delay);
|
|
// Sanity checks
|
|
if (min_col < 256 && max_col < 256 && direction < 3 && (direction == 1 || delay < 128))
|
|
{
|
|
int speed = 1;
|
|
if (delay < 128)
|
|
speed = 210/(128-delay);
|
|
// Grafx2's slider has a limit of COLOR_CYCLING_SPEED_MAX
|
|
if (speed > COLOR_CYCLING_SPEED_MAX)
|
|
speed = COLOR_CYCLING_SPEED_MAX;
|
|
context->Cycle_range[context->Color_cycles].Start=min_col;
|
|
context->Cycle_range[context->Color_cycles].End=max_col;
|
|
context->Cycle_range[context->Color_cycles].Inverse= (direction==0);
|
|
context->Cycle_range[context->Color_cycles].Speed=direction == 1 ? 0 : speed;
|
|
context->Color_cycles++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Saves color ranges from a PI1 or PC1 image (Degas Elite format)
|
|
void PI1_save_ranges(T_IO_Context * context, byte * buffer, int size)
|
|
{
|
|
// empty by default
|
|
memset(buffer+size - 32, 0, 32);
|
|
if (context->Color_cycles)
|
|
{
|
|
int i; // index in context->Cycle_range[] : < context->Color_cycles
|
|
int saved_range; // index in resulting buffer : < 4
|
|
|
|
for (i=0, saved_range=0; i<context->Color_cycles && saved_range<4; i++)
|
|
{
|
|
if (context->Cycle_range[i].Start < 16 && context->Cycle_range[i].End < 16)
|
|
{
|
|
int speed;
|
|
if (context->Cycle_range[i].Speed == 0)
|
|
speed = 0;
|
|
else if (context->Cycle_range[i].Speed == 1)
|
|
// has to "round" manually to closest valid number for this format
|
|
speed = 1;
|
|
else
|
|
speed = 128 - 210 / context->Cycle_range[i].Speed;
|
|
|
|
buffer[size - 32 + saved_range*2 + 1] = context->Cycle_range[i].Start;
|
|
buffer[size - 32 + saved_range*2 + 9] = context->Cycle_range[i].End;
|
|
buffer[size - 32 + saved_range*2 + 17] = (context->Cycle_range[i].Speed == 0) ? 1 : (context->Cycle_range[i].Inverse ? 0 : 2);
|
|
buffer[size - 32 + saved_range*2 + 25] = speed;
|
|
|
|
saved_range ++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test for Degas file format
|
|
void Test_PI1(T_IO_Context * context, FILE * file)
|
|
{
|
|
unsigned long size; // Taille du fichier
|
|
word resolution; // Résolution de l'image
|
|
|
|
(void)context;
|
|
File_error=1;
|
|
|
|
if (!Read_word_be(file,&resolution))
|
|
return;
|
|
|
|
size = File_length_file(file);
|
|
if ((size==32034) || (size==32066)) // size check
|
|
{
|
|
if (resolution < 3)
|
|
File_error=0;
|
|
}
|
|
}
|
|
|
|
|
|
/// Load Degas file format
|
|
void Load_PI1(T_IO_Context * context)
|
|
{
|
|
enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
|
|
word resolution;
|
|
word width, height;
|
|
FILE *file;
|
|
word x_pos,y_pos;
|
|
byte buffer[160];
|
|
byte * ptr;
|
|
byte pixels[320];
|
|
byte bpp;
|
|
|
|
File_error = 1;
|
|
file = Open_file_read(context);
|
|
if (file == NULL)
|
|
return;
|
|
|
|
if (!Read_word_be(file, &resolution))
|
|
return;
|
|
GFX2_Log(GFX2_DEBUG, "Degas UnCompressed. Resolution = %04x\n", resolution);
|
|
// Read palette
|
|
if (!Read_bytes(file, buffer, 32))
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
if (Config.Clear_palette)
|
|
memset(context->Palette,0,sizeof(T_Palette));
|
|
PI1_decode_palette(buffer, context->Palette);
|
|
|
|
switch (resolution)
|
|
{
|
|
case 0: // Low Res
|
|
width = 320;
|
|
height = 200;
|
|
bpp = 4;
|
|
break;
|
|
case 1: // Med Res
|
|
width = 640;
|
|
height = 200;
|
|
bpp = 2;
|
|
ratio = PIXEL_TALL;
|
|
break;
|
|
case 2: // High Res
|
|
width = 640;
|
|
height = 400;
|
|
bpp = 1;
|
|
break;
|
|
default:
|
|
fclose(file);
|
|
return;
|
|
}
|
|
Pre_load(context, width, height, File_length_file(file),FORMAT_PI1,ratio, bpp);
|
|
|
|
for (y_pos=0;y_pos<height;y_pos++)
|
|
{
|
|
if (!Read_bytes(file, buffer, (resolution == 2) ? 80 : 160))
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
ptr = buffer;
|
|
for (x_pos=0; x_pos < width;)
|
|
{
|
|
int i;
|
|
switch (resolution)
|
|
{
|
|
case 0:
|
|
PI1_8b_to_16p(ptr, pixels);
|
|
ptr += 8;
|
|
break;
|
|
case 1:
|
|
PI2_4b_to_16p(ptr, pixels);
|
|
ptr += 4;
|
|
break;
|
|
case 2:
|
|
for (i = 0; i < 8; i++)
|
|
pixels[i] = (ptr[0] & (0x80 >> i)) ? 1 : 0;
|
|
for (; i < 16; i++)
|
|
pixels[i] = (ptr[1] & (0x80 >> (i - 8))) ? 1 : 0;
|
|
ptr += 2;
|
|
}
|
|
for (i = 0; i < 16; i++)
|
|
Set_pixel(context, x_pos++, y_pos, pixels[i]);
|
|
}
|
|
}
|
|
// load color cycling information
|
|
if (Read_bytes(file, buffer, 32))
|
|
{
|
|
PI1_load_ranges(context, buffer, 32);
|
|
}
|
|
fclose(file);
|
|
File_error = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Save a PI1 Degas uncompressed picture
|
|
*
|
|
* @todo Support medium and high resolution
|
|
*/
|
|
void Save_PI1(T_IO_Context * context)
|
|
{
|
|
FILE *file;
|
|
short x_pos,y_pos;
|
|
byte * buffer;
|
|
byte * ptr;
|
|
byte pixels[320];
|
|
|
|
File_error=0;
|
|
// Ouverture du fichier
|
|
if ((file=Open_file_write(context)))
|
|
{
|
|
// allocation d'un buffer mémoire
|
|
buffer = GFX2_malloc(32034);
|
|
// Codage de la résolution
|
|
buffer[0]=0x00;
|
|
buffer[1]=0x00;
|
|
// Codage de la palette
|
|
PI1_code_palette(context->Palette, buffer+2);
|
|
// Codage de l'image
|
|
ptr=buffer+34;
|
|
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>>4);x_pos++)
|
|
{
|
|
PI1_16p_to_8b(pixels+(x_pos<<4),ptr);
|
|
ptr+=8;
|
|
}
|
|
}
|
|
|
|
if (Write_bytes(file,buffer,32034))
|
|
{
|
|
if (context->Color_cycles)
|
|
{
|
|
PI1_save_ranges(context, buffer, 32);
|
|
if (!Write_bytes(file,buffer,32))
|
|
File_error=1;
|
|
}
|
|
fclose(file);
|
|
}
|
|
else // Error d'écriture (disque plein ou protégé)
|
|
{
|
|
fclose(file);
|
|
Remove_file(context);
|
|
File_error=1;
|
|
}
|
|
// Libération du buffer mémoire
|
|
free(buffer);
|
|
buffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
fclose(file);
|
|
Remove_file(context);
|
|
File_error=1;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////// PC1 ////////////////////////////////////
|
|
|
|
/**
|
|
* 4bpp planar to chunky conversion.
|
|
* Converts 1 line (320 pixels) at a time.
|
|
*
|
|
* @param src0 40 bytes for plane 0
|
|
* @param src1 40 bytes for plane 1
|
|
* @param src2 40 bytes for plane 2
|
|
* @param src3 40 bytes for plane 3
|
|
* @param dest 320 bytes destination buffer
|
|
*/
|
|
static void PC1_4bp_to_1line(const byte * src0, const byte * src1,
|
|
const byte * src2, const byte * src3, byte * dest)
|
|
{
|
|
int i,j; // Compteurs
|
|
int ip; // index du pixel à calculer
|
|
byte byte_mask; // Masque de decodage
|
|
byte b0,b1,b2,b3; // Les 4 octets des plans bits sources
|
|
|
|
ip=0;
|
|
// Pour chacun des 40 octets des plans de bits
|
|
for (i=0;i<40;i++)
|
|
{
|
|
b0=src0[i];
|
|
b1=src1[i];
|
|
b2=src2[i];
|
|
b3=src3[i];
|
|
// Pour chacun des 8 bits des octets
|
|
byte_mask=0x80;
|
|
for (j=0;j<8;j++)
|
|
{
|
|
dest[ip++]=((b0 & byte_mask)?0x01:0x00) |
|
|
((b1 & byte_mask)?0x02:0x00) |
|
|
((b2 & byte_mask)?0x04:0x00) |
|
|
((b3 & byte_mask)?0x08:0x00);
|
|
byte_mask>>=1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Transformation d'1 ligne de pixels en 4 plans de bits
|
|
|
|
/**
|
|
* Convert 1 line of 320 pixel from chunky to 4bpp planar.
|
|
*
|
|
* @param src 320 bytes buffer
|
|
* @param dst0 40 bytes buffer for plane 0
|
|
* @param dst1 40 bytes buffer for plane 1
|
|
* @param dst2 40 bytes buffer for plane 2
|
|
* @param dst3 40 bytes buffer for plane 3
|
|
*/
|
|
static void PC1_1line_to_4bp(const byte * src,
|
|
byte * dst0, byte * dst1,
|
|
byte * dst2, byte * dst3)
|
|
{
|
|
int i,j; // Compteurs
|
|
int ip; // index du pixel à calculer
|
|
byte byte_mask; // Masque de decodage
|
|
byte b0,b1,b2,b3; // Les 4 octets des plans bits sources
|
|
|
|
ip=0;
|
|
// Pour chacun des 40 octets des plans de bits
|
|
for (i=0;i<40;i++)
|
|
{
|
|
// Pour chacun des 8 bits des octets
|
|
byte_mask=0x80;
|
|
b0=b1=b2=b3=0;
|
|
for (j=0;j<8;j++)
|
|
{
|
|
b0|=(src[ip] & 0x01)?byte_mask:0x00;
|
|
b1|=(src[ip] & 0x02)?byte_mask:0x00;
|
|
b2|=(src[ip] & 0x04)?byte_mask:0x00;
|
|
b3|=(src[ip] & 0x08)?byte_mask:0x00;
|
|
ip++;
|
|
byte_mask>>=1;
|
|
}
|
|
dst0[i]=b0;
|
|
dst1[i]=b1;
|
|
dst2[i]=b2;
|
|
dst3[i]=b3;
|
|
}
|
|
}
|
|
|
|
|
|
/// Test for Degas Elite Compressed format
|
|
void Test_PC1(T_IO_Context * context, FILE * file)
|
|
{
|
|
int size; // Taille du fichier
|
|
word resolution; // Résolution de l'image
|
|
|
|
(void)context;
|
|
File_error=1;
|
|
|
|
size = File_length_file(file);
|
|
if (!Read_word_be(file,&resolution))
|
|
return;
|
|
|
|
if ((size <= 32066) && (resolution & 0x8000))
|
|
{
|
|
if ((resolution & 0x7fff) < 3)
|
|
File_error=0;
|
|
}
|
|
}
|
|
|
|
|
|
/// Load Degas Elite compressed files
|
|
void Load_PC1(T_IO_Context * context)
|
|
{
|
|
enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
|
|
unsigned long size;
|
|
word width, height;
|
|
byte bpp;
|
|
FILE *file;
|
|
word x_pos,y_pos;
|
|
byte buffer[32];
|
|
byte * bufferdecomp;
|
|
byte * ptr;
|
|
byte pixels[640];
|
|
word resolution;
|
|
|
|
File_error = 1;
|
|
file = Open_file_read(context);
|
|
if (file == NULL)
|
|
return;
|
|
size = File_length_file(file);
|
|
|
|
if (!Read_word_be(file, &resolution))
|
|
return;
|
|
GFX2_Log(GFX2_DEBUG, "Degas Elite Compressed. Resolution = %04x\n", resolution);
|
|
// Read palette
|
|
if (!Read_bytes(file, buffer, 32))
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
if (Config.Clear_palette)
|
|
memset(context->Palette,0,sizeof(T_Palette));
|
|
PI1_decode_palette(buffer, context->Palette);
|
|
|
|
switch (resolution)
|
|
{
|
|
case 0x8000: // Low Res
|
|
width = 320;
|
|
height = 200;
|
|
bpp = 4;
|
|
break;
|
|
case 0x8001: // Med Res
|
|
width = 640;
|
|
height = 200;
|
|
bpp = 2;
|
|
ratio = PIXEL_TALL;
|
|
break;
|
|
case 0x8002: // High Res
|
|
width = 640;
|
|
height = 400;
|
|
bpp = 1;
|
|
break;
|
|
default:
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
bufferdecomp = GFX2_malloc(32000);
|
|
if (bufferdecomp == NULL)
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
// Initialisation de la preview
|
|
Pre_load(context, width, height, size, FORMAT_PC1, ratio, bpp);
|
|
|
|
if (PackBits_unpack_from_file(file, bufferdecomp, 32000) != PACKBITS_UNPACK_OK)
|
|
{
|
|
GFX2_Log(GFX2_INFO, "PackBits_unpack_from_file() failed\n");
|
|
free(bufferdecomp);
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
// Décodage de l'image
|
|
ptr=bufferdecomp;
|
|
for (y_pos = 0; y_pos < height; y_pos++)
|
|
{
|
|
// Décodage de la scanline
|
|
switch (resolution)
|
|
{
|
|
case 0x8000: // Low Res
|
|
PC1_4bp_to_1line(ptr,ptr+40,ptr+80,ptr+120,pixels);
|
|
ptr+=160;
|
|
break;
|
|
case 0x8001: // Med Res
|
|
x_pos = 0;
|
|
while (x_pos < width)
|
|
{
|
|
int i;
|
|
for (i = 7; i >= 0; i--, x_pos++)
|
|
pixels[x_pos] = ((ptr[0] >> i) & 1)
|
|
| (((ptr[80] >> i) << 1) & 2);
|
|
ptr++;
|
|
}
|
|
ptr += 80;
|
|
break;
|
|
case 0x8002: // High Res
|
|
x_pos = 0;
|
|
while (x_pos < width)
|
|
{
|
|
int i;
|
|
for (i = 7; i >= 0; i--)
|
|
pixels[x_pos++] = (*ptr >> i) & 1;
|
|
ptr++;
|
|
}
|
|
}
|
|
for (x_pos=0;x_pos<width;x_pos++)
|
|
Set_pixel(context, x_pos, y_pos, pixels[x_pos]);
|
|
}
|
|
// Try to load color cycling information
|
|
GFX2_Log(GFX2_DEBUG, "remaining bytes = %ld\n", size - ftell(file));
|
|
if (Read_bytes(file, buffer, 32))
|
|
{
|
|
PI1_load_ranges(context, buffer, 32);
|
|
}
|
|
free(bufferdecomp);
|
|
fclose(file);
|
|
File_error = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Save a Degas Elite compressed picture
|
|
*
|
|
* @todo support medium and high resolution
|
|
*/
|
|
void Save_PC1(T_IO_Context * context)
|
|
{
|
|
FILE *file;
|
|
short x_pos,y_pos;
|
|
byte buffer[32];
|
|
byte * bufferdecomp;
|
|
byte * ptr;
|
|
byte pixels[320];
|
|
|
|
File_error=0;
|
|
// Ouverture du fichier
|
|
if ((file=Open_file_write(context)))
|
|
{
|
|
// Allocation des buffers mémoire
|
|
bufferdecomp = GFX2_malloc(32000);
|
|
// Codage de la résolution
|
|
if (!Write_word_be(file, 0x8000))
|
|
File_error = 1;
|
|
// Codage de la palette
|
|
PI1_code_palette(context->Palette, buffer);
|
|
if (!Write_bytes(file, buffer, 32))
|
|
File_error = 1;
|
|
|
|
// Codage de l'image
|
|
ptr=bufferdecomp;
|
|
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);
|
|
}
|
|
|
|
// 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 :
|
|
<pre>
|
|
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
|
|
</pre>
|
|
|
|
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<height;y_pos++)
|
|
{
|
|
if (!Read_bytes(file, buffer, (resolution==2) ? 80 : 160))
|
|
goto error;
|
|
|
|
ptr = buffer;
|
|
for (x_pos = 0; x_pos < width; )
|
|
{
|
|
int i;
|
|
switch (resolution)
|
|
{
|
|
case 0:
|
|
PI1_8b_to_16p(ptr, pixels);
|
|
ptr += 8;
|
|
break;
|
|
case 1:
|
|
PI2_4b_to_16p(ptr, pixels);
|
|
ptr += 4;
|
|
break;
|
|
case 2:
|
|
for (i = 0; i < 8; i++)
|
|
pixels[i] = (ptr[0] & (0x80 >> 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);
|
|
}
|
|
|
|
/** @} */
|