grafX2/src/oldies.c
Adrien Destugues 32ec828835 Group all copyright statements in a single file.
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.
2020-12-19 21:56:33 +00:00

1094 lines
30 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/>
*/
#include <string.h>
#ifndef _MSC_VER
#include <strings.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include "struct.h"
#include "oldies.h"
#include "global.h"
#include "errors.h"
#include "io.h"
#include "misc.h"
#include "palette.h"
#include "pages.h"
#include "windows.h"
#include "layers.h"
#include "graph.h"
#include "bitcount.h"
#include "loadsavefuncs.h"
// I don't have round() in MSVC++ 2010 (_MSC_VER=1600)
// or in mintlib
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__MINT__)
#define round(x) floor(x+0.5)
#endif
static const struct {
enum IMAGE_MODES mode;
const char * label;
} image_modes_labels[] = {
{ IMAGE_MODE_LAYERED, "LAYERED" },
{ IMAGE_MODE_ANIMATION, "ANIMATION" },
{ IMAGE_MODE_ZX, "ZX_SPECTRUM" },
{ IMAGE_MODE_GBC, "GAMEBOYCOLOR" },
{ IMAGE_MODE_THOMSON, "THOMSON40COL" },
{ IMAGE_MODE_EGX, "CPC_EGX" },
{ IMAGE_MODE_EGX2, "CPC_EGX2" },
{ IMAGE_MODE_MODE5, "CPC_MODE5" },
{ IMAGE_MODE_RASTER, "CPC_RASTER" },
{ IMAGE_MODE_C64HIRES, "C64_HIRES" },
{ IMAGE_MODE_C64MULTI, "C64_MULTICOLOR" },
{ IMAGE_MODE_C64FLI, "C64_FLI" },
{ IMAGE_MODE_HGR, "APPLE2_HGR" },
{ IMAGE_MODE_DHGR, "APPLE2_DHGR" },
{ IMAGE_MODE_MEGADRIVE, "SEGAMEGADRIVE" },
{ IMAGE_MODE_TMS9918G2, "TMS9918_G2" },
};
const char * Constraint_mode_label(enum IMAGE_MODES mode)
{
unsigned int i;
for (i = 0; i < sizeof(image_modes_labels)/sizeof(image_modes_labels[0]); i++)
{
if (image_modes_labels[i].mode == mode)
return image_modes_labels[i].label;
}
return NULL;
}
int Constraint_mode_from_label(const char * label)
{
unsigned int i;
for (i = 0; i < sizeof(image_modes_labels)/sizeof(image_modes_labels[0]); i++)
{
if (0 == strcmp(image_modes_labels[i].label, label))
return (int)image_modes_labels[i].mode;
}
return -1;
}
int C64_FLI(T_IO_Context * context, byte *bitmap, byte *screen_ram, byte *color_ram, byte *background)
{
word used_colors[200][40];
word block_used_colors[25][40];
word line_used_colors[200];
byte used_colors_count[200][40];
dword usage[16];
word x,y,row,col;
int i;
byte line_color[200];
byte block_color[25][40];
word best_color_count;
byte best_color;
const byte no_color=16;
// Prerequisites
if (context->Nb_layers < 3)
return 1;
if (context->Width != 160 || context->Height != 200)
return 2;
memset(used_colors,0,200*40*sizeof(word));
memset(block_used_colors,0,25*40*sizeof(word));
memset(line_used_colors,0,200*sizeof(word));
memset(used_colors_count,0,200*40*sizeof(byte));
// Initialize these as "unset"
memset(line_color,no_color,200*sizeof(byte));
memset(block_color,no_color,25*40*sizeof(byte));
// Examine all 4-pixel blocks to fill used_colors[][]
Set_saving_layer(context, 2);
for (row=0;row<200;row++)
{
for (col=0;col<40;col++)
{
for (x=0;x<4;x++)
{
byte c = Get_pixel(context, col*4+x, row);
used_colors[row][col] |= 1<<c;
}
}
}
// Get "mandatory colors" from layer 1
Set_saving_layer(context, 0);
for (row=0;row<200;row++)
{
byte c = Get_pixel(context, 0, row);
if (c<16)
{
line_color[row]=c;
for (col=0;col<40;col++)
{
// Remove that color from the sets
used_colors[row][col] &= ~(1<<c);
}
}
}
// Get "mandatory colors" from layer 2
Set_saving_layer(context, 1);
for (row=0;row<200;row+=8)
{
for (col=0;col<40;col++)
{
byte c = Get_pixel(context, col*4, row);
if (c<16)
{
block_color[row/8][col]=c;
// Remove that color from the sets
for (y=0; y<8;y++)
used_colors[row+y][col] &= ~(1<<c);
}
}
}
// Now count the "remaining colors".
for (row=0;row<200;row++)
{
memset(usage,0,16*sizeof(dword));
for (col=0;col<40;col++)
{
used_colors_count[row][col] = count_set_bits(used_colors[row][col]);
// Count which colors are used 3 times
if (used_colors_count[row][col]==3)
{
for (i=0; i<16; i++)
if (used_colors[row][col] & (1<<i))
usage[i]+=1;
}
// Count which colors are used 4 times (important)
else if (used_colors_count[row][col]==4)
{
for (i=0; i<16; i++)
if (used_colors[row][col] & (1<<i))
usage[i]+=2;
}
}
// A complete line has been examined. Pick the color which has been tagged most, and set it as
// the line color (unless it already was set.)
if (line_color[row]==no_color)
{
best_color_count=0;
best_color=no_color;
for (i=0; i<16; i++)
{
if (usage[i]>best_color_count)
{
best_color_count=usage[i];
best_color=i;
}
}
line_color[row]=best_color;
// Remove that color from the sets
for (col=0;col<40;col++)
{
if (used_colors[row][col] & (1<<best_color))
{
used_colors[row][col] &= ~(1<<best_color);
// Update count
used_colors_count[row][col]--;
}
}
}
}
// Now check all 4*8 blocks
for (col=0;col<40;col++)
{
for (row=0;row<200;row+=8)
{
// If there wasn't a preset color for this block
if (block_color[row/8][col]==no_color)
{
word filter=0xFFFF;
// Count used colors
memset(usage,0,16*sizeof(dword));
for (y=0;y<8;y++)
{
if (used_colors_count[row+y][col]>2)
{
filter &= used_colors[row+y][col];
for (i=0; i<16; i++)
{
if (used_colors[row+y][col] & (1<<i))
usage[i]+=1;
}
}
}
if (filter != 0 && count_set_bits(filter) == 1)
{
// Only one color matched all needs: Use it.
i=1;
best_color=0;
while (! (filter & i))
{
best_color++;
i= i << 1;
}
}
else
{
if (filter==0)
{
// No color in common from multiple lines with 3+ colors...
// Keep them all for the usage sort.
filter=0xFFFF;
}
// Pick the color which has been tagged most, and set it as
// the block color
best_color_count=0;
best_color=no_color;
for (i=0; i<16; i++)
{
if (filter & (1<<i))
{
if (usage[i]>best_color_count)
{
best_color_count=usage[i];
best_color=i;
}
}
}
}
block_color[row/8][col]=best_color;
// Remove that color from the sets
for (y=0;y<8;y++)
{
if (used_colors[row+y][col] & (1<<best_color))
{
used_colors[row+y][col] &= ~(1<<best_color);
// Update count
used_colors_count[row+y][col]--;
}
}
}
}
}
// At this point, the following arrays are filled:
// - block_color[][]
// - line_color[]
// They contain either a color 0-16, or no_color if no color is mandatory.
// It's now possible to scan the whole image and choose how to encode it.
// TODO: Draw problematic places on layer 4, with one of the available colors
/*
if (bitmap!=NULL)
{
for (row=0;row<200;row++)
{
for (col=0;col<40;col++)
{
}
}
}
*/
// Output background
if (background!=NULL)
{
for (row=0;row<200;row++)
{
if (line_color[row]==no_color)
background[row]=0; // Unneeded line is black
else
background[row]=line_color[row];
}
}
// Output Color RAM
if (color_ram!=NULL)
{
for (col=0;col<40;col++)
{
for (row=0;row<25;row++)
{
if (block_color[row][col]==no_color)
color_ram[row*40+col]=0; // Unneeded block is black
else
color_ram[row*40+col]=block_color[row][col];
}
}
}
// Screen RAMs and Bitmap
Set_saving_layer(context, 2);
for(row=0; row<25; row++)
{
for(col=0; col<40; col++)
{
for(y=0; y<8; y++)
{
byte c1, c2;
// Find 2 colors in used_colors[row*8+y][col]
for (c1=0; c1<16 && !(used_colors[row*8+y][col] & (1<<c1)); c1++)
;
for (c2=c1+1; c2<16 && !(used_colors[row*8+y][col] & (1<<c2)); c2++)
;
if (c1>15)
c1=16;
if (c2>15)
c2=16;
// Output Screen RAMs
if (screen_ram!=NULL)
screen_ram[y*1024+row*40+col] = (c1&15) | ((c2&15)<<4);
// Output bitmap
if (bitmap!=NULL)
{
for(x=0; x<4; x++)
{
byte bits;
byte c = Get_pixel(context, col*4+x, row*8+y);
if (c==line_color[row*8+y])
// BG color
bits=0;
else if (c==block_color[row][col])
// block color
bits=3;
else if (c==c1)
// Color 1
bits=2;
else if (c==c2)
// Color 2
bits=1;
else // problem
bits=0;
// clear target bits
//bitmap[row*320+col*8+y] &= ~(3<<((3-x)*2));
// set them
bitmap[row*320+col*8+y] |= bits<<((3-x)*2);
}
}
}
}
}
//memset(background,3,200);
//memset(color_ram,5,8000);
//memset(screen_ram,(9<<4) | 7,8192);
return 0;
}
int C64_pixels_to_FLI(byte *bitmap, byte *screen_ram, byte *color_ram,
byte *background, const byte * pixels, long pitch, int errmode)
{
int bx, by; // 4x8 block coordinates
int cx, cy; // coordinates inside block
byte pixel;
int error_count = 0;
for (by = 0; by < 25; by++)
{
word background_possible[8];
word color_ram_possible[40];
for(cy = 0; cy < 8; cy++)
background_possible[cy] = 0xffff;
for(bx = 0; bx < 40; bx++)
color_ram_possible[bx] = 0xffff;
// first we try to find the background color of the 8 lines
// and the Color RAM (color 11) of the 40 blocks
for(cy = 0; cy < 8; cy++)
{
for(bx = 0; bx < 40; bx++)
{
word colors_used = 0;
int n_color_used;
for(cx = 0; cx < 4; cx++)
{
pixel = pixels[bx*4+cx + pitch*(by*8+cy)];
// if (pixel > 15) error
colors_used |= 1 << pixel;
}
n_color_used = count_set_bits(colors_used);
if (n_color_used == 4)
{
background_possible[cy] &= colors_used; // The background is among the color used in this 4x1 block
color_ram_possible[bx] &= colors_used; // The color 11 is among the color used in this 4x1 block
}
}
}
// Choose background (default is black #0)
for(cy = 0; cy < 8; cy++)
{
int n_possible_backgrounds = count_set_bits(background_possible[cy]);
if (n_possible_backgrounds == 0)
{
//ERROR
if (errmode == 0)
{
Warning_with_format("No possible background color for line %d.\n4x1 pixel blocks using 4 different colors must share at least one color.", by*8+cy);
return 1;
}
error_count++;
GFX2_Log(GFX2_INFO, "C64_pixels_to_FLI() no possible background for line %u. Default to #0.\n", by*8+cy);
// default to background color #0
if (background[by*8+cy] >= 16)
background[by*8+cy] = 0;
}
else
{
// if the caller gave us a "hint", check it is "possible"
if (background[by*8+cy] >= 16 || (background_possible[cy] & (1 << background[by*8+cy])) == 0)
background[by*8+cy] = count_trailing_zeros(background_possible[cy]); // pick the first possible
#ifdef _DEBUG
if (background[by*8+cy] != 0)
GFX2_Log(GFX2_DEBUG, " y=%d background_possible=$%04x (count=%d) background color=#%d\n",
by*8+cy, background_possible[cy], n_possible_backgrounds, background[by*8+cy]);
#endif
}
}
// 2nd pass for color RAM values
for(bx = 0; bx < 40; bx++)
{
word color_usage[16];
memset(color_usage, 0, sizeof(color_usage));
for(cy = 0; cy < 8; cy++)
{
word colors_used = 0;
int n_color_used;
for(cx = 0; cx < 4; cx++)
{
pixel = pixels[bx*4+cx + pitch*(by*8+cy)];
if (pixel < 16)
{
colors_used |= 1 << pixel;
color_usage[pixel]++;
}
}
colors_used &= ~(1 << background[by*8+cy]); // remove background
n_color_used = count_set_bits(colors_used);
if (n_color_used == 3)
{
color_ram_possible[bx] &= colors_used; // The color 11 is among the color used in this 4x1 block
}
}
// choose the color RAM values (default to #0)
if (color_ram_possible[bx] == 0)
{
if (errmode == 0)
{
Warning_with_format("No possible color RAM value for 4x8 block (%d,%d) coordinates (%d,%d)\nThe 8 4x1 blocks must share a color.", bx, by, bx*4, by*8);
return 1;
}
else
{
// Mark errors in Layer 4
for(cy = 0; cy < 8; cy++)
{
word colors_used = 0;
for(cx = 0; cx < 4; cx++)
{
pixel = pixels[bx*4+cx + pitch*(by*8+cy)];
if (pixel < 16 && pixel != background[by*8+cy])
colors_used |= 1 << pixel;
}
if (count_set_bits(colors_used) >= 3)
{
error_count++;
GFX2_Log(GFX2_INFO, "C64_pixels_to_FLI() too much colors in block at (%u to %u, %u)\n", bx*4, bx*4+3, by*7+cy);
for(cx = 0; cx < 4; cx++)
{
pixel = pixels[bx*4+cx + pitch*(by*8+cy)];
if (pixel < 16 && pixel != background[by*8+cy] && Main.backups->Pages->Nb_layers >= 4)
Pixel_in_layer(3, bx*4+cx, by*8+cy, 17);
}
}
}
}
}
else if (color_ram[by*40+bx] >= 16 || ((1 << color_ram[by*40+bx]) & color_ram_possible[bx]) == 0)
{
word possible = color_ram_possible[bx];
// avoid using same color as background
for(cy = 0; cy < 8; cy++)
possible &= ~(1 << background[by*8+cy]);
if (possible != 0)
{ // pickup most used color (but not background color)
byte col;
word max_usage = 0;
byte max_index = count_trailing_zeros(possible); // default to 1st color (except background)
for (col = max_index; col < 16; col++)
{
if ((possible & (1 << col)) != 0 && color_usage[col] > max_usage)
{
max_usage = color_usage[col];
max_index = col;
}
}
color_ram[by*40+bx] = max_index;
}
else
color_ram[by*40+bx] = count_trailing_zeros(color_ram_possible[bx]);
#ifdef _DEBUG
if (color_ram[by*40+bx] != 0)
GFX2_Log(GFX2_DEBUG, "bx=%d by=%d color_ram_possible=%04x color11=#%d\n",
bx, by, color_ram_possible[bx], color_ram[by*40+bx]);
#endif
}
}
// Now it is possible to encode Screen RAM and Bitmap
for(cy = 0; cy < 8; cy++)
{
for(bx = 0; bx < 40; bx++)
{
byte bits = 0;
byte c[4];
c[0] = background[by*8+cy]; // color 00 = background
c[1] = c[0]; // color 01 (defaulting to same as background)
c[2] = c[1]; // color 10 (defaulting to same as background)
c[3] = color_ram[by*40+bx]; // color 11 = color RAM value
for(cx = 0; cx < 4; cx++)
{
bits <<= 2;
pixel = pixels[bx*4+cx + pitch*(by*8+cy)];
if (pixel == c[0]) // background => color 00
continue;
if(pixel == c[3]) // color RAM value => color 11
bits |= 3;
else
{
if (c[1] == c[0])
c[1] = pixel; // set color 01 = upper nibble of screen RAM
if (pixel == c[1])
bits |= 1;
else
{
c[2] = pixel; // set color 02 = lower nibble of screen RAM
bits |= 2;
}
}
}
screen_ram[1024*cy + bx + by * 40] = (c[1] << 4) | c[2];
bitmap[(by*40 + bx)*8 + cy] = bits;
}
}
}
return error_count;
}
void C64_set_palette(T_Components * palette)
{
/// Set C64 Palette from http://www.pepto.de/projects/colorvic/
static const byte pal[48]={
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0x68, 0x37, 0x2B,
0x70, 0xA4, 0xB2,
0x6F, 0x3D, 0x86,
0x58, 0x8D, 0x43,
0x35, 0x28, 0x79,
0xB8, 0xC7, 0x6F,
0x6F, 0x4F, 0x25,
0x43, 0x39, 0x00,
0x9A, 0x67, 0x59,
0x44, 0x44, 0x44,
0x6C, 0x6C, 0x6C,
0x9A, 0xD2, 0x84,
0x6C, 0x5E, 0xB5,
0x95, 0x95, 0x95};
memcpy(palette, pal, 48);
/// Also set transparent color "16" as a dark grey that is distinguishable
/// from black, but darker than normal colors.
palette[16].R=20;
palette[16].G=20;
palette[16].B=20;
/// set color "17" as RED to see color clashes
palette[17].R=255;
palette[17].G=0;
palette[17].B=0;
}
void ZX_Spectrum_set_palette(T_Components * palette)
{
int i, intensity;
for (i = 0; i < 16; i++)
{
intensity = (i & 8) ? 255 : 205;
palette[i].G = ((i & 4) >> 2) * intensity;
palette[i].R = ((i & 2) >> 1) * intensity;
palette[i].B = (i & 1) * intensity;
}
// set color 17 for color clashes
palette[17].R = 255;
palette[17].G = 127;
palette[17].B = 127;
}
static const T_Components CPC_Hw_Palette[] = {
{0x6E, 0x7D, 0x6B}, // 0x40
{0x6E, 0x7B, 0x6B}, // 0x41
{0, 0xF3, 0x6B},
{0xF3, 0xF3, 0x6D},
{0, 2, 0x6B},
{0xF0, 2, 0x68},
{0, 0x78, 0x68},
{0xF3, 0x7D, 0x6B},
{0xF3, 0x02, 0x68}, // 0x48
{0xF3, 0xF3, 0x6B},
{0xF3, 0xF3, 0xD},
{255, 0xF3, 0xF9},
{0xF3, 5, 6},
{0xF3, 2, 0xF4},
{0xF3, 0x7D, 0xD},
{0xFA, 0x80, 0xF9},
{0x00, 0x02, 0x68}, // 0x50
{0x02, 0xF3, 0x6B},
{2, 0xF0, 1},
{0xF, 0xF3, 0xF2},
{0, 2, 1},
{0x0C, 2, 0xF4},
{2, 0x78, 1},
{0xC, 0x7B, 0xF4},
{0x69, 2, 0x68}, // 0x58
{0x71, 0xF3, 0x6B},
{0x71, 0xF5, 4},
{0x71, 0xF3, 0xF4},
{0x6C, 2, 1},
{0x6C, 2, 0xF2},
{0x6E, 0x7B, 1},
{0x6E, 0x7B, 0xF6}
};
/**
* The CPC firmware palette is ordered by luminosity
* see http://www.cpcwiki.eu/index.php/CPC_Palette
*/
static const byte CPC_Firmware_Colors[] = {
0x54, 0x44, 0x55, 0x5c, 0x58, 0x5d, 0x4c, 0x45, 0x4d,
0x56, 0x46, 0x57, 0x5e, 0x40, 0x5f, 0x4e, 0x47, 0x4f,
0x52, 0x42, 0x53, 0x5a, 0x59, 0x5b, 0x4a, 0x43, 0x4b
};
void CPC_set_HW_palette(T_Components * palette)
{
memcpy(palette, CPC_Hw_Palette, sizeof(CPC_Hw_Palette));
}
int CPC_compare_colors(T_Components * col1, T_Components * col2)
{
byte v1, v2;
/** The mapping used in this function is :
* - RGB value [0: 85] => CPC level 0
* - RGB value [86:171] => CPC level 1
* - RGB value [172:255] => CPC level 2 */
v1 = (col1->R / 86) << 4 | (col1->G / 86) << 2 | (col1->B / 86);
v2 = (col2->R / 86) << 4 | (col2->G / 86) << 2 | (col2->B / 86);
return v1 == v2;
}
int CPC_is_CPC_old_color(T_Components * col)
{
if (15 < col->R && col->R < 0x60)
return 0;
if (0x82 < col->R && col->R < 0xf0)
return 0;
if (15 < col->G && col->G < 0x60)
return 0;
if (0x82 < col->G && col->G < 0xf0)
return 0;
if (15 < col->B && col->B < 0x60)
return 0;
if (0x82 < col->B && col->B < 0xf0)
return 0;
return 1;
}
void CPC_set_default_BASIC_palette(T_Components * palette)
{
static const byte basic_colors[] = {
0x44/*0x50*/, 0x4a, 0x53, 0x4c,
0x4b, 0x54, 0x55, 0x4d,
0x46, 0x5e, 0x5f, 0x47,
0x52, 0x59, 0x4a, 0x47
};
unsigned int i;
/** in the default CPC Basic palette, INKs 14 and 15 are blinking,
* so that would be great to include theses in the colorcycling,
* but I don't see any way to make these blinking colors
* with the way GrafX2 handles color cycling */
for (i = 0; i < sizeof(basic_colors) / sizeof(byte); i++)
memcpy(palette + i,
CPC_Hw_Palette + basic_colors[i] - 0x40,
sizeof(T_Components));
}
byte CPC_Firmware_to_Hardware_color(byte fw_color)
{
if (fw_color <= 26)
return CPC_Firmware_Colors[fw_color];
return 0;
}
int CPC_check_AMSDOS(FILE * file, word * loading_address, word * exec_address, unsigned long * file_length)
{
int i;
byte data[128];
word checksum = 0;
fseek(file, 0, SEEK_SET);
if (!Read_bytes(file, data, 128))
{
// Rewind the file so it can be read normally
fseek(file, 0, SEEK_SET);
return 0;
}
// Rewind the file so it can be read normally
fseek(file, 0, SEEK_SET);
for (i = 1; i <= 11; i++) // check filename and extension
{
// sometimes digits are stored as 0x10 to 0x19 and '#' as 0x03
// I suppose it is bad lowercase to uppercase conversion from a tool
if ((data[i] >= 0x10 && data[i] <= 0x19) || (data[i] == 0x03) || (data[i] == 0x0B))
continue;
if (data[i] < ' ' || data[i] >= 0x7F) {
GFX2_Log(GFX2_DEBUG, "Not an AMSDOS file: name is invalid\n");
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;
}
for (i = 1; i <= 11; i++) // check filename and extension
{
// sometimes digits are stored as 0x10 to 0x19
if (data[i] >= 0x01 && data[i] <= 0x19)
data[i] += 0x20;
}
GFX2_Log(GFX2_DEBUG, "AMSDOS : user=%02X %.8s.%.3s %d %u(%u) bytes, load at $%04X exec $%04X checksum $%04X\n",
data[0],
(char *)(data + 1), (char *)(data + 9), data[18], /* Type*/
data[24] | (data[25] << 8), data[64] | (data[65] << 8) | (data[66] << 16),
data[21] | (data[22] << 8),
data[26] | (data[27] << 8), checksum);
if (loading_address)
*loading_address = data[21] | (data[22] << 8);
if (exec_address)
*exec_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 CPC_write_AMSDOS_header(FILE * file, const char * filename, const char * ext, byte type, word load_address, word exec_address, word file_length)
{
int i;
word checksum = 0;
byte header[128];
memset(header, 0, sizeof(header));
memset(header + 1, ' ', 11);
for (i = 0 ; i < 8; i++)
{
if (filename[i] == '\0' || filename[i] == '.')
break;
header[1+i] = (byte)toupper(filename[i]);
}
for (i = 0 ; i < 3; i++)
{
if (ext[i] == '\0' || ext[i] == '.')
break;
header[9+i] = (byte)toupper(ext[i]);
}
header[18] = type;
header[21] = load_address & 0xff;
header[22] = load_address >> 8;
header[24] = file_length & 0xff;
header[25] = file_length >> 8;
header[26] = exec_address & 0xff;
header[27] = exec_address >> 8;
header[64] = file_length & 0xff;
header[65] = file_length >> 8;
for (i = 0; i < 67; i++)
checksum += (word)header[i];
header[67] = checksum & 0xff;
header[68] = checksum >> 8;
GFX2_LogHexDump(GFX2_DEBUG, "AMSDOS ", header, 0, sizeof(header));
return Write_bytes(file, header, sizeof(header));
}
int DECB_Check_binary_file(FILE * f)
{
byte code;
word length, address;
long end;
// Check end of file
if (fseek(f, -5, SEEK_END) < 0)
return 0;
if (!(Read_byte(f, &code) && Read_word_be(f, &length) && Read_word_be(f, &address)))
return 0;
if (code != 0xff || length != 0)
return 0;
end = ftell(f);
if (end < 0)
return 0;
if (fseek(f, 0, SEEK_SET) < 0)
return 0;
// Read Binary structure
do
{
if (!(Read_byte(f, &code) && Read_word_be(f, &length) && Read_word_be(f, &address)))
return 0;
// skip chunk content
if (fseek(f, length, SEEK_CUR) < 0)
return 0;
}
while(code == 0);
if (code != 0xff)
return 0;
if (ftell(f) != end)
return 0;
return 1;
}
int MOTO_Check_binary_file(FILE * f)
{
int type = 1; // BIN
byte code;
word length, address;
if (!DECB_Check_binary_file(f))
return 0;
if (fseek(f, 0, SEEK_SET) < 0)
return 0;
// Read Binary structure
do
{
if (!(Read_byte(f, &code) && Read_word_be(f, &length) && Read_word_be(f, &address)))
return 0;
GFX2_Log(GFX2_DEBUG, "Thomson BIN &H%02X &H%04X &H%04X\n", code, length, address);
// VRAM is &H4000 on TO7/TO8/TO9/TO9+
if (length >= 8000 && length <= 8192 && address == 0x4000)
type = 3; // TO autoloading picture
else if (length > 16 && address == 0) // address is 0 for MAP files
{
byte map_header[3];
if (!Read_bytes(f, map_header, 3))
return 0;
length -= 3;
if ((map_header[0] & 0x3F) == 0 && (map_header[0] & 0xC0) != 0xC0) // 00, 40 or 80
{
GFX2_Log(GFX2_DEBUG, "Thomson MAP &H%02X %u %u\n", map_header[0], map_header[1], map_header[2]);
if (((map_header[1] < 80 && map_header[0] != 0) // <= 80col in 80col and bm16
|| map_header[1] < 40) // <= 40col in 40col
&& map_header[2] < 25) // <= 200 pixels high
type = 2; // MAP file (SAVEP/LOADP format)
}
if (type == 1)
{
if (length == 8000 || length == 8032 || length == 8064)
type = 4; // MO autoloading picture
}
}
else if (length == 1)
{
if(address == 0xE7C3) // TO7/TO8/TO9 6846 PRC
type = 3; // TO autoloading picture
else if(address == 0xA7C0) // MO5/MO6 PRC
type = 4; // MO autoloading picture
}
if (fseek(f, length, SEEK_CUR) < 0)
return 0;
}
while(code == 0);
return type;
}
int DECB_BIN_Add_Chunk(FILE * f, word size, word address, const byte * data)
{
return Write_byte(f, 0)
&& Write_word_be(f, size)
&& Write_word_be(f, address)
&& Write_bytes(f, data, size);
}
int DECB_BIN_Add_End(FILE * f, word address)
{
return Write_byte(f, 0xff)
&& Write_word_be(f, 0)
&& Write_word_be(f, address);
}
word MOTO_gamma_correct_RGB_to_MOTO(const T_Components * color)
{
word r, g, b;
double gamma = Config.MOTO_gamma / 10.0;
r = (word)round(pow(color->R / 255.0, gamma) * 15.0);
g = (word)round(pow(color->G / 255.0, gamma) * 15.0);
b = (word)round(pow(color->B / 255.0, gamma) * 15.0);
GFX2_Log(GFX2_DEBUG, "#%02x%02x%02x => &H%04X\n",
color->R, color->G, color->B,
b << 8 | g << 4 | r);
return b << 8 | g << 4 | r;
}
void MOTO_gamma_correct_MOTO_to_RGB(T_Components * color, word bgr)
{
double inv_gamma = 10.0 / Config.MOTO_gamma;
color->B = (byte)round(pow(((bgr >> 8)& 0x0F)/15.0, inv_gamma) * 255.0);
color->G = (byte)round(pow(((bgr >> 4)& 0x0F)/15.0, inv_gamma) * 255.0);
color->R = (byte)round(pow((bgr & 0x0F)/15.0, inv_gamma) * 255.0);
}
void MOTO_set_MO5_palette(T_Components * palette)
{
static const unsigned char mo5palette[48] = {
// Taken from https://16couleurs.wordpress.com/2013/03/31/archeologie-infographique-le-pixel-art-pour-thomson/
// http://pulkomandy.tk/wiki/doku.php?id=documentations:devices:gate.arrays#video_generation
0, 0, 0, 255, 85, 85, 0, 255, 0, 255, 255, 0,
85, 85, 255, 255, 0, 255, 0, 255, 255, 255, 255, 255,
170, 170, 170, 255, 170, 255, 170, 255, 170, 255, 255, 170,
85, 170, 255, 255, 170, 170, 170, 255, 255, 255, 170, 85
};
memcpy(palette, mo5palette, 48);
// set color 17 for color clashes
palette[17].R = 255;
palette[17].G = 0;
palette[17].B = 85;
}
void MOTO_set_TO7_palette(T_Components * palette)
{
int i;
static const word to8default_pal[16] = {
// BGR values Dumped from a TO8D with a BASIC 512 program :
// FOR I=0TO15:PRINT PALETTE(I):NEXT I
0x000, 0x00F, 0x0F0, 0x0FF, 0xF00, 0xF0F, 0xFF0, 0xFFF,
0x777, 0x33A, 0x3A3, 0x3AA, 0xA33, 0xA3A, 0xEE7, 0x07B
};
for (i = 0; i < 16; i++)
MOTO_gamma_correct_MOTO_to_RGB(palette + i, to8default_pal[i]);
// set color 17 for color clashes
palette[17].R = 255;
palette[17].G = 127;
palette[17].B = 127;
}
void HGR_set_palette(T_Components * palette)
{
static const T_Components hgr_palette[] = {
{ 0, 0, 0 }, // black
{ 0, 255, 0 }, // green
{ 255, 0, 255 }, // purple
{ 255, 255, 255 }, // white
{ 0, 0, 0 }, // black
{ 255, 80, 0 }, // orange
{ 0, 175, 255 }, // blue
{ 255, 255, 255 } // white
};
memcpy(palette, hgr_palette, sizeof(hgr_palette));
}
void DHGR_set_palette(T_Components * palette)
{
// Color Palette from the JACE emulator
static const T_Components dhgr_palette[] = {
{0,0,0}, /* 0 black */
{177,0,93}, /* 1 red */
{94,86,0}, /* 8 brown */
{255,86,0}, /* 9 orange */
{0,127,34}, /* 4 dk green */
{127,127,127},/* 5 gray */
{44,213,0}, /* 12 lt green */
{222,213,0}, /* 13 yellow */
{32,41,255}, /* 2 dk blue */
{210,41,255}, /* 3 purple */
{127,127,127},/* 10 grey */
{255,127,220},/* 11 pink */
{0,168,255}, /* 6 med blue */
{160,168,255},/* 7 lt blue */
{77,255,161}, /* 14 aqua */
{255,255,255} /* 15 white */
};
memcpy(palette, dhgr_palette, sizeof(dhgr_palette));
palette[16].R = 0;
palette[16].G = 0;
palette[16].B = 0;
palette[31].R = 255;
palette[31].G = 255;
palette[31].B = 255;
}
void MSX_set_palette(T_Components * palette)
{
static const T_Components MSX_palette[] = {
{0, 0, 0},
{32,219,32},
{109,255,109},
{32,32,255},
{48,109,255},
{182,32,32},
{73,219,255},
{255,32,32},
{255,109,109},
{219,219,32},
{219,219,146},
{32,146,32},
{219,73,182},
{182,182,182},
{255,255,255}
};
memcpy(palette + 1, MSX_palette, sizeof(MSX_palette));
// set color 0 (transparent) to a dark gray
palette[0].R = 32;
palette[0].G = 32;
palette[0].B = 32;
}