apple2gs_shr_converter/src_a2b/a2fcbmp.c

8600 lines
293 KiB
C
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ------------------------------------------------------------------------ */
/* A2FCBMP.C (C) Copyright Bill Buckels 2012-2015 */
/* All Rights Reserved. */
/* */
/* Licence Agreement */
/* ----------------- */
/* */
/* You have a royalty-free right to use, modify, reproduce and */
/* distribute this source code in any way you find useful, */
/* provided that you agree that Bill Buckels has no warranty obligations */
/* or liability resulting from said distribution in any way whatsoever. */
/* If you don't agree, remove this source code from your computer now. */
/* */
/* Written by : Bill Buckels */
/* Email: bbuckels@mts.net */
/* */
/* Purpose : This utility will allow you to convert from */
/* Apple II Double Hi-Res 140 x 192 x 16 color images to: */
/* */
/* 280 x 192 x 24 Bit Windows .BMP Files - Default */
/* 140 x 192 x 24 Bit Windows .BMP Files - Option "140" */
/* */
/* Variable sized color BMP output from DHR Image Fragments in both */
/* nominal sizes listed above is also supported. */
/* */
/* Option "P" (p0-p16) Color Mapping is in one of 16 optional palettes.*/
/* The palette numbers are the same as Bmp2DHR: */
/* */
/* 0 - Kegs32 (RGB) */
/* 1 - CiderPress (RGB) */
/* 2 - Old AppleWin (NTSC ?) */
/* 3 - New AppleWin (NTSC ?) */
/* 4 - Wikipedia (NTSC) */
/* 5 - tohgr (NTSC) */
/* palette 6 is a user palette file */
/* palettes 7-11 are IBM-PC legacy palettes */
/* palette 12 is Super Convert RGB palette */
/* palette 13 is Jace NTSC palette */
/* palette 14 is Cybernesto's VBMP NTSC palette */
/* palette 15 is merged RGB-NTSC colors (p5+p12)/2 */
/* palette 16 is tohgr's old NTSC colors */
/* */
/* This utility will also allow you to convert from */
/* Apple II Double Hi-Res 560 x 192 monochrome images to: */
/* */
/* 560 x 384 monochrome Windows .BMP Files - Option "384" */
/* 560 x 192 monochrome Windows .BMP Files - Option "192" */
/* */
/* This utility also converts from an AppleWin "Mono" */
/* screen capture 256 color BMP to any of the Above. */
/* */
/* During conversion of the AppleWin 256 color BMP it also */
/* generates Apple II Double Hi-Res images and renames the */
/* AppleWin BMP. */
/* */
/* Revision : 3.0 Added AppleWin Support and odds and ends. */
/* 4.0 Added Different Palettes */
/* Added Long FileNames for 32 bit compilation */
/* Added optional Ciderpress tags for Long Filenames */
/* Added support for XPACK DHR image fragments */
/* Added support for Sheldon Simms DHGR naming for A2FC */
/* 5.0 Expanded Features and output beyond DHGR */
/* 6.0 May 2015 */
/* 7.0 July 2015 */
/* 8.0 November 2015 */
/* ------------------------------------------------------------------------ */
/* Originally Written in Large Model 16 bit Microsoft C (MSC) Version 8.00c */
/* Cleaned-up c++ code comments for old Turbo C 2.0 */
/* 16-bit MS-DOS version now compiles under both */
/* MSC and Turbo C */
/* ------------------------------------------------------------------------ */
/* Added Conditional Compilation for MinGW and gcc */
/* Added Conditional Compilation etc. for 32 bit support */
/* 32-bit version now compiles under Visual Studio Win32 */
/* and MinGW Win32 and gcc with support for long names. */
/* Should now run everywhere (Windows, Linux, OSX) */
/* ------------------------------------------------------------------------ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <math.h>
#define LOBLACK 0
#define LORED 1
#define LODKBLUE 2
#define LOPURPLE 3
#define LODKGREEN 4
#define LOGRAY 5
#define LOMEDBLUE 6
#define LOLTBLUE 7
#define LOBROWN 8
#define LOORANGE 9
#define LOGREY 10
#define LOPINK 11
#define LOLTGREEN 12
#define LOYELLOW 13
#define LOAQUA 14
#define LOWHITE 15
/* dither types */
#define FLOYDSTEINBERG 1
#define JARVIS 2
#define STUCKI 3
#define ATKINSON 4
#define BURKES 5
#define SIERRA 6
#define SIERRATWO 7
#define SIERRALITE 8
#define BUCKELS 9
#define ATKINSON2 10
/* ------------------------------------------------------------------------ */
/* Declarations, Vars. etc. */
/* ------------------------------------------------------------------------ */
/* options flags */
int longnames = 1, bmp = 0, bm2 = 0, bmp3 = 0, a2fc = 0, auxbin = 0, tohgr = 0, dhr = 0, frag = 0,
mono = 0, doublepixel = 1, applesoft = 0, palnumber = 5, doublegrey = 0, vbmp = 0,
dither = 0, dithertype = 9, randomdither = 0, errorsum = 0, outline = 0, lores = 0, doublelores = 0, tags=0, dosheader=0;
int shr = 0, shrgrey = 0, usegscolors = 0, usegspalette = 0, hsl = 1, shrpalette = 15, brooks = 0, shrmode = 0, shrpalettes = 1,
shr256 = 0, useimagetone = 0, usepalettedistance = 0, quietmode = 1, m2s = 0, shrinput = 0, mix256 = 0,
imnumpalettes = 0, fourbit = 0, fourplay = 0, fourpal = 0;
double desaturate[16];
char fullname[256], shortname[256], outname[256], mainfile[256],auxfile[256],a2fcfile[256];
char *palname[] = {
"Kegs32 RGB",
"CiderPress RGB",
"Old AppleWin NTSC",
"New AppleWin NTSC",
"Wikipedia NTSC",
"tohgr NTSC DHGR",
"Imported",
"Legacy Canvas",
"Legacy Win16",
"Legacy Win32",
"Legacy VGA BIOS",
"Legacy VGA PCX",
"Super Convert RGB",
"Jace NTSC",
"Cybernesto-Munafo NTSC",
"Pseudo Palette",
"tohgr NTSC HGR"};
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;
typedef short sshort;
/* put this here for now */
/* shr colors start */
uchar ColorsUsed[16][16][16];
int shrcolorcount = 0;
/* for checking duplicate 12-bit colors in 24-bit conversion palettes */
/* expand as necessary */
int shrdupedebug = 0, shrdupes = 0, shrdupecount = 0;
void shrcolorsused(uchar r, uchar g, uchar b)
{
r = r >> 4;
g = g >> 4;
b = b >> 4;
if (ColorsUsed[r][g][b] == 0) {
ColorsUsed[r][g][b] = 1;
shrcolorcount++;
}
else if (shrdupedebug != 0 && r !=0 && g != 0 && b != 0) {
/* do not count black as dupes */
if (ColorsUsed[r][g][b] == 1) {
ColorsUsed[r][g][b] = 2;
shrdupes = 1;
}
shrdupecount++;
}
}
void clearcolorsused()
{
shrcolorcount = shrdupecount = shrdupes = 0;
memset(&ColorsUsed[0][0][0],0,4096);
}
/* shr colors end */
/* Bitmap Header structures */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagRGBQUAD
#else
typedef struct tagRGBQUAD
#endif
{
unsigned char rgbBlue;
unsigned char rgbGreen;
unsigned char rgbRed;
unsigned char rgbReserved;
} RGBQUAD;
/* Bitmap Header structures */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADER
#else
typedef struct tagBITMAPINFOHEADER
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
} BITMAPINFOHEADER;
/* Not officially documented - BITMAPV2INFOHEADER */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADERV2
#else
typedef struct tagBITMAPINFOHEADERV2
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
ulong biRedMask;
ulong biGreenMask;
ulong biBlueMask;
} BITMAPINFOHEADERV2;
/* Not officially documented - BITMAPV3INFOHEADER */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADERV3
#else
typedef struct tagBITMAPINFOHEADERV3
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
ulong biRedMask;
ulong biGreenMask;
ulong biBlueMask;
ulong biAlphaMask;
} BITMAPINFOHEADERV3;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADERV4
#else
typedef struct tagBITMAPINFOHEADERV4
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
ulong biRedMask;
ulong biGreenMask;
ulong biBlueMask;
ulong biAlphaMask;
ulong biCSType;
uchar biEndpoints[36]; /* CIEXYZTRIPLE */
ulong biGammaRed;
ulong biGammaGreen;
ulong biGammaBlue;
} BITMAPINFOHEADERV4;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADERV5
#else
typedef struct tagBITMAPINFOHEADERV5
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
ulong biRedMask;
ulong biGreenMask;
ulong biBlueMask;
ulong biAlphaMask;
ulong biCSType;
uchar biEndpoints[36]; /* CIEXYZTRIPLE */
ulong biGammaRed;
ulong biGammaGreen;
ulong biGammaBlue;
ulong biIntent;
ulong biProfileData;
ulong biProfileSize;
ulong biReserved;
} BITMAPINFOHEADERV5;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPFILEHEADER
#else
typedef struct tagBITMAPFILEHEADER
#endif
{
uchar bfType[2];
ulong bfSize;
ushort bfReserved1;
ushort bfReserved2;
ulong bfOffBits;
} BITMAPFILEHEADER;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBMPHEADER
#else
typedef struct tagBMPHEADER
#endif
{
BITMAPFILEHEADER bfi;
BITMAPINFOHEADER bmi;
} BMPHEADER;
/* picfile trailer structure */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagPICFILE
#else
typedef struct tagPICFILE
#endif
{
uchar scb[200];
uchar padding[56];
uchar pal[200][32]; /* note the PIC file has 16 palette entries */
} PICFILE;
/* constant for the biCompression field */
#define BI_RGB 0L
/* static structures for processing bmp input files */
BITMAPFILEHEADER BitMapFileHeader;
RGBQUAD sbmp[256]; /* super vga - applewin */
sshort palettesused = 0, palettecolorsused[16];
BITMAPINFOHEADER bmi;
/* support for BMP version 4 but not sure about other versions */
BITMAPINFOHEADERV5 bmiV5;
/* DHR Image Fragments - Sprites */
BMPHEADER mybmp;
ushort bmpwidth = 0, bmpheight = 0, fragwidth = 140, fragheight = 192, fragx = 0, fragy = 0;
/* SHR PIC file */
PICFILE mypic;
#define ASCIIZ 0
#define CRETURN 13
#define LFEED 10
uchar *szTextTitle =
"A2FCBmp(C) Version 8.0 Copyright Bill Buckels 2012-2015\n"
"All Rights Reserved.";
#define SUCCESS 0
#define VALID SUCCESS
#define FAILURE -1
#define INVALID FAILURE
#define NUM_RGB_COLORS 3
#define NUM_VGA_COLORS 16
/* 280 x 192 - the default output format */
uchar BMP_header[] ={
0x42, 0x4D, 0x36, 0x76, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0xC0, 0x00,
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x76, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/* 140 x 192 - verbatim output format */
uchar BMP140_header[] ={
0x42, 0x4D, 0x36, 0x3B, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x8C, 0x00, 0x00, 0x00, 0xC0, 0x00,
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3B, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/* monochrome output routines - ported from dmono */
/* headers for monochrome BMP files */
/* 560 x 384 - the default output format */
uchar mono384[62] ={
0x42, 0x4D, 0x3E, 0x6C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x80, 0x01,
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00};
/* 560 x 192 - verbatim output format */
uchar mono192[62] ={
0x42, 0x4D, 0x3E, 0x36, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0xC0, 0x00,
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00};
uchar msk[]={0x80,0x40,0x20,0x10,0x8,0x4,0x2,0x1};
uchar bmpscanline[1680], bmpscanline2[1680];
uchar buf560[560];
/* built-in palette options */
/* based on DHGR, DLGR, LGR colors */
/* LGR color order */
uchar kegs32colors[16][3] = {
0,0,0, /* black */
221,0,51, /* red */
0,0,153, /* dk blue */
221,0,221, /* purple */
0,119,0, /* dk green */
85,85,85, /* gray */
34,34,255, /* med blue */
102,170,255, /* lt blue */
136,85,34, /* brown */
255,102,0, /* orange */
170,170,170, /* grey */
255,153,136, /* pink */
0,221,0, /* lt green */
255,255,0, /* yellow */
0,255,153, /* aqua */
255,255,255};/* white */
uchar ciderpresscolors[16][3] = {
0,0,0, /* black */
221,0,51, /* red */
0,0,153, /* dk blue */
221,34,221, /* purple */
0,119,34, /* dk green */
85,85,85, /* gray */
34,34,255, /* med blue */
102,170,255, /* lt blue */
136,85,0, /* brown */
255,102,0, /* orange */
170,170,170, /* grey */
255,153,136, /* pink */
17,221,0, /* lt green */
255,255,0, /* yellow */
68,255,153, /* aqua */
255,255,255};/* white */
uchar awinoldcolors[16][3] = {
0,0,0, /* black */
208,0,48, /* red */
0,0,128, /* dk blue */
255,0,255, /* purple */
0,128,0, /* dk green */
128,128,128, /* gray */
0,0,255, /* med blue */
96,160,255, /* lt blue */
128,80,0, /* brown */
255,128,0, /* orange */
192,192,192, /* grey */
255,144,128, /* pink */
0,255,0, /* lt green */
255,255,0, /* yellow */
64,255,144, /* aqua */
255,255,255};/* white */
uchar awinnewcolors[16][3] = {
0,0,0, /* black */
157,9,102, /* red */
42,42,229, /* dk blue */
199,52,255, /* purple */
0,118,26, /* dk green */
128,128,128, /* gray */
13,161,255, /* med blue */
170,170,255, /* lt blue */
85,85,0, /* brown */
242,94,0, /* orange */
192,192,192, /* grey */
255,137,229, /* pink */
56,203,0, /* lt green */
213,213,26, /* yellow */
98,246,153, /* aqua */
255,255,255};/* white */
/* http://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes */
uchar wikipedia[16][3] = {
0,0,0, /* black */
114,38,64, /* red */
64,51,127, /* dk blue */
228,52,254, /* purple */
14,89,64, /* dk green */
128,128,128, /* gray */
27,154,254, /* med blue */
191,179,255, /* lt blue */
64,76,0, /* brown */
228,101,1, /* orange */
128,128,128, /* grey */
241,166,191, /* pink */
27,203,1, /* lt green */
191,204,128, /* yellow */
141,217,191, /* aqua */
255,255,255};/* white */
/* http://wsxyz.net/tohgr.html */
/* Sheldon Simm's palette from todhr */
/*
Sheldon is clipping the black and white ranges.
The usual reason for doing this is for dirty images (caused by poor digitizing or
sampling or re-sampling color depth loss due to scaling). By using a clipping threshold
at either end of the rgb range, blacks that are not quite black don't match to some other
dark color and whites that are not quite white don't match to some other light color.
I too put this in place as a clipping option when I wrote Bmp2SHR about a year ago in my
Brooks output routines but it worked a little differently. I didn't build it into a
palette for one thing.
Sheldon's weighting favours clipping blue gun values at both ends of the range.
Red is clipped more than green in the high range, and green is clipped more than
red in the low range.
static Pixel pal[] = {
{ 1, 4, 8}, // 0 black
{ 32, 54, 212}, // 1 dk blue
{ 51, 111, 0}, // 2 dk green
{ 7, 168, 225}, // 3 med blue
{ 99, 77, 0}, // 4 brown
{ 126, 126, 126}, // 5 gray
{ 67, 200, 0}, // 6 lt green
{ 93, 248, 133}, // 7 aqua
{ 148, 12, 125}, // 8 red
{ 188, 55, 255}, // 9 purple
{ 126, 126, 126}, // A grey
{ 158, 172, 255}, // B lt blue
{ 249, 86, 29}, // C orange
{ 255, 129, 236}, // D pink
{ 221, 206, 23}, // E yellow
{ 248, 250, 244} // F white
Also note that I use the array name grpal. This was used as a palette in
one of Sheldon's previous versions. Since I have propagated this array name
to my code and my code is working fine with it, I have no plans to change it
to maintain currency with Sheldon's code.
*/
uchar grpal[16][3] = {
0,0,0, /* black */
148,12,125, /* red - hgr 0*/
32,54,212, /* dk blue - hgr 0 */
188,55,255, /* purple - default HGR overlay color */
51,111,0, /* dk green - hgr 0 */
126,126,126, /* gray - hgr 0 */
7,168,225, /* med blue */
158,172,255, /* lt blue - hgr 0 - alternate HGR overlay color*/
99,77,0, /* brown - hgr 0 */
249,86,29, /* orange */
126,126,126, /* grey - hgr 0 */
255,129,236, /* pink - hgr 0 */
67,200,0, /* lt green */
221,206,23, /* yellow - hgr 0 */
93,248,133, /* aqua - hgr 0 */
255,255,255};/* white */
/* Sheldon still uses the RGB values from his old palette for HGR conversion */
uchar hgrpal[16][3] = {
0x00,0x00,0x00, /* black */
0xad,0x18,0x28, /* red */
0x55,0x1b,0xe1, /* dk blue */
0xe8,0x2c,0xf8, /* purple 232,44,248 - default hgr overlay color */
0x01,0x73,0x63, /* dk green */
0x7e,0x82,0x7f, /* gray */
0x34,0x85,0xfc, /* med blue - 52,133,252 - alternate HGR overlay color */
0xd1,0x95,0xff, /* lt blue */
0x33,0x6f,0x00, /* brown */
0xd0,0x81,0x01, /* orange */
0x7f,0x7e,0x77, /* grey */
0xfe,0x93,0xa3, /* pink */
0x1d,0xd6,0x09, /* lt green */
0xae,0xea,0x22, /* yellow */
0x5b,0xeb,0xd9, /* aqua */
0xff,0xff,0xff};/* white */
/* imported palette file */
uchar rgbUser[16][3];
uchar SuperConvert[16][3] = {
0,0,0, /* black */
221,0,51, /* red */
0,0,153, /* dk blue */
221,0,221, /* purple */
0,119,0, /* dk green */
85,85,85, /* gray */
34,34,255, /* med blue */
102,170,255, /* lt blue */
136,85,34, /* brown */
255,102,0, /* orange */
170,170,170, /* grey */
255,153,136, /* pink */
0,221,0, /* lt green */
255,255,0, /* yellow */
0,255,153, /* aqua */
255,255,255};/* white */
uchar Jace[16][3] = {
0,0,0, /* black */
177,0,93, /* red */
32,41,255, /* dk blue */
210,41,255, /* purple */
0,127,34, /* dk green */
127,127,127, /* gray */
0,168,255, /* med blue */
160,168,255, /* lt blue */
94,86,0, /* brown */
255,86,0, /* orange */
127,127,127, /* grey */
255,127,220, /* pink */
44,213,0, /* lt green */
222,213,0, /* yellow */
77,255,161, /* aqua */
255,255,255}; /* white */
/* https://github.com/cybernesto/VBMP/wiki/Converting-a-picture-into-a-DHGR-color-image-using-the-GIMP */
/* Robert Munafo - http://mrob.com/pub/xapple2/colors.html */
uchar Cybernesto[16][3] = {
0, 0, 0, /* 0 Black */
227, 30, 96, /* 1 Magenta */
96, 78,189, /* 8 Dark Blue */
255, 68,253, /* 9 Violet */
0,163, 96, /* 4 Dark Green */
156,156,156, /* 5 Grey1 */
20,207,253, /* 12 Medium Blue */
208,195,255, /* 13 Light Blue */
96,114, 3, /* 2 Brown */
255,106, 60, /* 3 Orange */
156,156,156, /* 10 Grey2 */
255,160,208, /* 11 Pink */
20,245, 60, /* 6 Green */
208,221,141, /* 7 Yellow */
114,255,208, /* 14 Aqua */
255,255,255}; /* 15 White */
/* merged palette */
/* initial values are the average values of p5 NTSC and p12 RGB */
uchar PseudoPalette[16][3] = {
0,0,0,
184,6,88,
16,27,182,
204,27,238,
25,115,0,
105,105,105,
20,101,240,
130,171,255,
117,81,17,
252,94,14,
148,148,148,
255,141,186,
33,210,0,
238,230,11,
46,251,143,
255,255,255};
/* goes with the canvas bmp that I put out there with AppleX */
uchar rgbCanvasArray[16][3] = {
0 , 0 , 0 ,
208, 0 , 48 ,
0 , 0 , 128,
255, 0 , 255,
0 , 128, 0 ,
128, 128, 128,
0 , 0 , 255,
96 , 160, 255,
128, 80 , 0 ,
255, 128, 0 ,
192, 192, 192,
255, 144, 128,
0 , 255, 0 ,
255, 255, 0 ,
64 , 255, 144,
255, 255, 255};
/* this might work with old Win16 16 color BMP's */
uchar rgbBmpArray[16][3] = {
0 ,0 , 0 ,
191,0 , 0 ,
0 ,0 , 191,
191,0 , 191,
0 ,191, 0 ,
128,128, 128,
0 ,191, 191,
0 ,0 , 255,
191,191, 0 ,
255,0 , 0 ,
192,192, 192,
255,0 , 255,
0 ,255, 0 ,
255,255, 0 ,
0 ,255, 255,
255,255, 255};
/* this might work with new Win32 16 color BMP's */
uchar rgbXmpArray[16][3] = {
0 , 0 , 0 ,
128, 0 , 0 ,
0 , 0 , 128,
128, 0 , 128,
0 , 128, 0 ,
128, 128, 128,
0 , 128, 128,
0 , 0 , 255,
128, 128, 0 ,
255, 0 , 0 ,
192, 192, 192,
255, 0 , 255,
0 , 255, 0 ,
255, 255, 0 ,
0 , 255, 255,
255, 255, 255};
/* from the bios in some PC I had */
uchar rgbVgaArray[16][3] = {
0 , 0 , 0 ,
255, 0 , 0 ,
0 , 0 , 255,
255, 0 , 255,
0 , 255, 0 ,
85 , 85 , 85 ,
0 , 255, 255,
85 , 85 , 255,
255, 255, 0 ,
255, 85 , 85 ,
192, 192, 192,
255, 85 , 255,
85 , 255, 85 ,
255, 255, 85 ,
85 , 255, 255,
255, 255, 255};
/* some old ZSoft VGA Pcx Colors */
uchar rgbPcxArray[16][3] = {
0 , 0 , 0 ,
170, 0 , 0 ,
0 , 0 , 170,
170, 0 , 170,
0 , 170, 0 ,
85 , 85 , 85 ,
0 , 170, 170,
85 , 85 , 255,
170, 170, 0 ,
255, 85 , 85 ,
170, 170, 170,
255, 85 , 255,
85 , 255, 85 ,
255, 255, 85 ,
85 , 255, 255,
255, 255, 255};
/* our working copy of the apple II double hires colors */
/* this is in Apple II lo-res color order */
/* todhr palette */
uchar rgbArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
0,0,0, /* black */
148,12,125, /* red */
32,54,212, /* dk blue */
188,55,255, /* purple */
51,111,0, /* dk green */
126,126,126, /* gray */
7,168,225, /* med blue */
158,172,255, /* lt blue */
99,77,0, /* brown */
249,86,29, /* orange */
126,126,126, /* grey */
255,129,236, /* pink */
67,200,0, /* lt green */
221,206,23, /* yellow */
93,248,133, /* aqua */
255,255,255};/* white */
char *colornames[] = {
"black",
"red",
"dkblue",
"purple",
"dkgreen",
"gray",
"medblue",
"ltblue",
"brown",
"orange",
"grey",
"pink",
"ltgreen",
"yellow",
"aqua",
"white"};
/* for SHR output */
/* 200 brooks palettes */
uchar rgbArrays[200][16][3];
uchar rgbUsed[200][16];
double rgbDistance[200][16];
/* save palettes for palette distance resassignment */
uchar savepalettes[200][16][3];
uchar savescb[200];
/* 16 pic palettes */
uchar rgb256Arrays[200][16][3];
uchar rgb256Used[16][16];
double rgb256Distance[16][16];
/* all possible EGA ECD Values in Indexed Order */
uchar rgbEgaArray[64][3] = {
0x00, 0x00, 0x00,
0x00, 0x00, 0xaa,
0x00, 0xaa, 0x00,
0x00, 0xaa, 0xaa,
0xaa, 0x00, 0x00,
0xaa, 0x00, 0xaa,
0xaa, 0xaa, 0x00,
0xaa, 0xaa, 0xaa,
0x00, 0x00, 0x55,
0x00, 0x00, 0xff,
0x00, 0xaa, 0x55,
0x00, 0xaa, 0xff,
0xaa, 0x00, 0x55,
0xaa, 0x00, 0xff,
0xaa, 0xaa, 0x55,
0xaa, 0xaa, 0xff,
0x00, 0x55, 0x00,
0x00, 0x55, 0xaa,
0x00, 0xff, 0x00,
0x00, 0xff, 0xaa,
0xaa, 0x55, 0x00,
0xaa, 0x55, 0xaa,
0xaa, 0xff, 0x00,
0xaa, 0xff, 0xaa,
0x00, 0x55, 0x55,
0x00, 0x55, 0xff,
0x00, 0xff, 0x55,
0x00, 0xff, 0xff,
0xaa, 0x55, 0x55,
0xaa, 0x55, 0xff,
0xaa, 0xff, 0x55,
0xaa, 0xff, 0xff,
0x55, 0x00, 0x00,
0x55, 0x00, 0xaa,
0x55, 0xaa, 0x00,
0x55, 0xaa, 0xaa,
0xff, 0x00, 0x00,
0xff, 0x00, 0xaa,
0xff, 0xaa, 0x00,
0xff, 0xaa, 0xaa,
0x55, 0x00, 0x55,
0x55, 0x00, 0xff,
0x55, 0xaa, 0x55,
0x55, 0xaa, 0xff,
0xff, 0x00, 0x55,
0xff, 0x00, 0xff,
0xff, 0xaa, 0x55,
0xff, 0xaa, 0xff,
0x55, 0x55, 0x00,
0x55, 0x55, 0xaa,
0x55, 0xff, 0x00,
0x55, 0xff, 0xaa,
0xff, 0x55, 0x00,
0xff, 0x55, 0xaa,
0xff, 0xff, 0x00,
0xff, 0xff, 0xaa,
0x55, 0x55, 0x55,
0x55, 0x55, 0xff,
0x55, 0xff, 0x55,
0x55, 0xff, 0xff,
0xff, 0x55, 0x55,
0xff, 0x55, 0xff,
0xff, 0xff, 0x55,
0xff, 0xff, 0xff};
sshort rgb2ega(uchar red, uchar green, uchar blue)
{
/* Converts from 24 bit color to Ega color index
EGA with ECD 16 color registers (0-63) 1 byte each */
sshort idx = 0;
/* find gun value index for EGA table comparison using thresholds */
if (red < 43) red = 0; /* 0x00 */
else if (red < 128) red = 85; /* 0x55 */
else if (red < 224) red = 170; /* 0xaa */
else red = 255; /* 0xff */
if (green < 43) green = 0;
else if (green < 128) green = 85;
else if (green < 213) green = 170;
else green = 255;
if (blue < 43) blue = 0;
else if (blue < 128) blue = 85;
else if (blue < 213) blue = 170;
else blue = 255;
/* values are now identicalized so use comparators in arrays to
determine ega color index
visual verification can be found at:
standard - http:en.wikipedia.org/wiki/Enhanced_Graphics_Adapter
extended - http:en.wikipedia.org/wiki/Image:EGA_Table.PNG
discussion about bit patterns and visuals also there */
for (idx = 0; idx < 64; idx++) {
if (red == rgbEgaArray[idx][0] &&
green == rgbEgaArray[idx][1] &&
blue == rgbEgaArray[idx][2]) return idx;
}
/* never gets to here */
return 0;
}
sshort globalthreshold = 4, rhold = 4, ghold = 4, bhold = 4;
/* set thresholds for color reduction on 24 bit values */
uchar sethold(uchar ch, sshort hold)
{
if (hold == 0) return ch;
/* just hand-bombed some hardcoded color thresholds here
they probably work just about as well as anything */
switch(hold) {
case 0:
if (ch < 192) ch = 0;
else ch = 255;
break;
case 1:
if (ch < 64) ch = 0;
else ch = 255;
break;
case 2:
if (ch < 128) ch = 0;
else ch = 255;
break;
case 3:
/* adjust gun values using EGA-like thresholds */
/* mode640 optimized threshold option */
if (ch < 85) ch = 0; /* 0x00 */
else if (ch < 170) ch = 128;
else ch = 255; /* 0xff */
break;
case 4:
/* adjust gun values using EGA-like thresholds */
/* threshold default option */
if (ch < 43) ch = 0; /* 0x00 */
else if (ch < 128) ch = 85; /* 0x55 */
else if (ch < 224) ch = 170; /* 0xaa */
else ch = 255; /* 0xff */
break;
/* additional thresholds */
case 5:
if (ch < 25) ch = 0; /* 0x00 */
else if (ch < 76) ch = 51;
else if (ch < 127) ch = 102;
else if (ch < 178) ch = 153;
else if (ch < 229) ch = 204;
else ch = 255; /* 0xff */
break;
case 6:
/* mode320 optimized threshold option */
if (ch < 0x15) ch = 0; /* 0x00 */
else if (ch < 0x3F) ch = 0x2A;
else if (ch < 0x69) ch = 0x54;
else if (ch < 0x93) ch = 0x7E;
else if (ch < 0xbd) ch = 0xa8;
else if (ch < 0xe7) ch = 0xd6;
else ch = 255; /* 0xff */
break;
case 7:
if (ch < 18) ch = 0; /* 0x00 */
else if (ch < 54) ch = 36;
else if (ch < 90) ch = 72;
else if (ch < 126) ch = 108;
else if (ch < 162) ch = 144;
else if (ch < 198) ch = 180;
else if (ch < 234) ch = 216;
else ch = 255; /* 0xff */
break;
case 8:
if (ch < 0x10) ch = 0; /* 0x00 */
else if (ch < 0x30) ch = 0x20;
else if (ch < 0x50) ch = 0x40;
else if (ch < 0x70) ch = 0x60;
else if (ch < 0x90) ch = 0x80;
else if (ch < 0xb0) ch = 0xa0;
else if (ch < 0xd0) ch = 0xc0;
else if (ch < 0xf0) ch = 0xe0;
else ch = 255; /* 0xff */
break;
case 9:
if (ch < 14) ch = 0; /* 0x00 */
else if (ch < 42) ch = 28;
else if (ch < 70) ch = 56;
else if (ch < 98) ch = 84;
else if (ch < 126) ch = 112;
else if (ch < 154) ch = 140;
else if (ch < 182) ch = 168;
else if (ch < 210) ch = 196;
else if (ch < 238) ch = 224;
else ch = 255; /* 0xff */
break;
case 10:
if (ch < 10) ch = 0; /* 0x00 */
else if (ch < 35) ch = 25;
else if (ch < 60) ch = 50;
else if (ch < 85) ch = 75;
else if (ch < 110) ch = 100;
else if (ch < 135) ch = 125;
else if (ch < 160) ch = 150;
else if (ch < 185) ch = 175;
else if (ch < 210) ch = 200;
else if (ch < 235) ch = 225;
else ch = 255; /* 0xff */
break;
case 11:
if (ch < 10) ch = 0; /* 0x00 */
else if (ch < 33) ch = 23;
else if (ch < 56) ch = 46;
else if (ch < 79) ch = 69;
else if (ch < 102) ch = 92;
else if (ch < 125) ch = 115;
else if (ch < 148) ch = 138;
else if (ch < 171) ch = 161;
else if (ch < 194) ch = 184;
else if (ch < 217) ch = 207;
else if (ch < 240) ch = 230;
else ch = 255; /* 0xff */
break;
}
return ch;
}
unsigned char RemapLoToHi[16] = {
LOBLACK,
LORED,
LOBROWN,
LOORANGE,
LODKGREEN,
LOGRAY,
LOLTGREEN,
LOYELLOW,
LODKBLUE,
LOPURPLE,
LOGREY,
LOPINK,
LOMEDBLUE,
LOLTBLUE,
LOAQUA,
LOWHITE};
/* Apple 2 Double Hires Format */
/* provides base address for page1 hires scanlines */
unsigned HB[]={
0x2000, 0x2400, 0x2800, 0x2C00, 0x3000, 0x3400, 0x3800, 0x3C00,
0x2080, 0x2480, 0x2880, 0x2C80, 0x3080, 0x3480, 0x3880, 0x3C80,
0x2100, 0x2500, 0x2900, 0x2D00, 0x3100, 0x3500, 0x3900, 0x3D00,
0x2180, 0x2580, 0x2980, 0x2D80, 0x3180, 0x3580, 0x3980, 0x3D80,
0x2200, 0x2600, 0x2A00, 0x2E00, 0x3200, 0x3600, 0x3A00, 0x3E00,
0x2280, 0x2680, 0x2A80, 0x2E80, 0x3280, 0x3680, 0x3A80, 0x3E80,
0x2300, 0x2700, 0x2B00, 0x2F00, 0x3300, 0x3700, 0x3B00, 0x3F00,
0x2380, 0x2780, 0x2B80, 0x2F80, 0x3380, 0x3780, 0x3B80, 0x3F80,
0x2028, 0x2428, 0x2828, 0x2C28, 0x3028, 0x3428, 0x3828, 0x3C28,
0x20A8, 0x24A8, 0x28A8, 0x2CA8, 0x30A8, 0x34A8, 0x38A8, 0x3CA8,
0x2128, 0x2528, 0x2928, 0x2D28, 0x3128, 0x3528, 0x3928, 0x3D28,
0x21A8, 0x25A8, 0x29A8, 0x2DA8, 0x31A8, 0x35A8, 0x39A8, 0x3DA8,
0x2228, 0x2628, 0x2A28, 0x2E28, 0x3228, 0x3628, 0x3A28, 0x3E28,
0x22A8, 0x26A8, 0x2AA8, 0x2EA8, 0x32A8, 0x36A8, 0x3AA8, 0x3EA8,
0x2328, 0x2728, 0x2B28, 0x2F28, 0x3328, 0x3728, 0x3B28, 0x3F28,
0x23A8, 0x27A8, 0x2BA8, 0x2FA8, 0x33A8, 0x37A8, 0x3BA8, 0x3FA8,
0x2050, 0x2450, 0x2850, 0x2C50, 0x3050, 0x3450, 0x3850, 0x3C50,
0x20D0, 0x24D0, 0x28D0, 0x2CD0, 0x30D0, 0x34D0, 0x38D0, 0x3CD0,
0x2150, 0x2550, 0x2950, 0x2D50, 0x3150, 0x3550, 0x3950, 0x3D50,
0x21D0, 0x25D0, 0x29D0, 0x2DD0, 0x31D0, 0x35D0, 0x39D0, 0x3DD0,
0x2250, 0x2650, 0x2A50, 0x2E50, 0x3250, 0x3650, 0x3A50, 0x3E50,
0x22D0, 0x26D0, 0x2AD0, 0x2ED0, 0x32D0, 0x36D0, 0x3AD0, 0x3ED0,
0x2350, 0x2750, 0x2B50, 0x2F50, 0x3350, 0x3750, 0x3B50, 0x3F50,
0x23D0, 0x27D0, 0x2BD0, 0x2FD0, 0x33D0, 0x37D0, 0x3BD0, 0x3FD0};
unsigned char *dhrbuf=NULL;
/*
The following is logically reordered to match the lores
color order...
Repeated
Binary
Color aux1 main1 aux2 main2 Pattern
Black 00 00 00 00 0000
Magenta 08 11 22 44 0001
Dark Blue 11 22 44 08 1000
Violet 19 33 66 4C 1001
Dark Green 22 44 08 11 0100
Grey1 2A 55 2A 55 0101
Medium Blue 33 66 4C 19 1100
Light Blue 3B 77 6E 5D 1101
Brown 44 08 11 22 0010
Orange 4C 19 33 66 0011
Grey2 55 2A 55 2A 1010
Pink 5D 3B 77 6E 1011
Green 66 4C 19 33 0110
Yellow 6E 5D 3B 77 0111
Aqua 77 6E 5D 3B 1110
White 7F 7F 7F 7F 1111
*/
/* repeated binary pattern */
unsigned char dhrbits[16][28] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* Black */
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, /* Magenta */
1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0, /* Dark Blue */
1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1, /* Violet */
0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0, /* Dark Green */
0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, /* Grey1 */
1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0, /* Medium Blue */
1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1, /* Light Blue */
0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0, /* Brown */
0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, /* Orange */
1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0, /* Grey2 */
1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1, /* Pink */
0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, /* Green */
0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, /* Yellow */
1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0, /* Aqua */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; /* White */
/* the following array is based on the above */
unsigned char dhrbytes[16][4] = {
0x00,0x00,0x00,0x00,
0x08,0x11,0x22,0x44,
0x11,0x22,0x44,0x08,
0x19,0x33,0x66,0x4C,
0x22,0x44,0x08,0x11,
0x2A,0x55,0x2A,0x55,
0x33,0x66,0x4C,0x19,
0x3B,0x77,0x6E,0x5D,
0x44,0x08,0x11,0x22,
0x4C,0x19,0x33,0x66,
0x55,0x2A,0x55,0x2A,
0x5D,0x3B,0x77,0x6E,
0x66,0x4C,0x19,0x33,
0x6E,0x5D,0x3B,0x77,
0x77,0x6E,0x5D,0x3B,
0x7F,0x7F,0x7F,0x7F};
/* the following is used to remap
double lo res 4 bit colors
from bank 0 to bank 1 */
unsigned char dloauxcolor[16] = {
0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15};
/* the following is used to remap
double lo res 4 bit colors
from bank 1 to bank 0 */
unsigned char dlomaincolor[16] = {
0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15};
/* -------------------------------------------------------------- */
/* local random number generator */
/* -------------------------------------------------------------- */
/* http://stackoverflow.com/questions/7602919/how-do-i-generate-random-numbers-without-rand-function */
ushort RandomSeed = (ushort)0xACE1;
ushort my_random()
{
ushort bit = ((RandomSeed >> 0) ^ (RandomSeed >> 2) ^ (RandomSeed >> 3) ^ (RandomSeed >> 5) ) & 1;
RandomSeed = (RandomSeed >> 1) | (bit << 15);
return RandomSeed;
}
/* ------------------------------------------------------------- */
/* RandomRange */
/* returns a random number in a usuable range (1 to MaxValue) */
/* args */
/* MaxValue - the highest number we want */
/* ------------------------------------------------------------- */
int RandomRange(int iMaxValue)
{
int iRetVal;
do {
/* get random number */
iRetVal = (int)my_random();
/* get a positive value */
if (iRetVal < 0) iRetVal *= -1;
} while(iRetVal < 1);
/* use modulus of MaxValue if not in range */
if (iRetVal > iMaxValue)
iRetVal = (iRetVal%iMaxValue)+1;
return iRetVal; /* return a value in range */
}
/* convert from native format to Apple II format short integer */
ushort Motorola16(ushort val)
{
uchar buf[2];
ushort *ptr;
/* msb in smallest address */
buf[0] = (uchar) (val % 256); val = val/256;
buf[1] = (uchar) (val % 256);
ptr = (ushort *)&buf[0];
val = ptr[0];
return val;
}
/* this is also used to write width, height pairs for LGR and DLGR file headers
when the file is wider than 255 pixels */
void WriteDosHeader(FILE *fp, ushort fl, ushort fa)
{
fa = Motorola16(fa);/* file bload address - not including this header */
fl = Motorola16(fl);/* file length - not including this header */
fwrite((char *)&fa,sizeof(ushort),1,fp);
fwrite((char *)&fl,sizeof(ushort),1,fp);
}
/* upper case name for Apple II Output */
void ucase(char *str)
{
int idx;
for (idx = 0; str[idx] != (char)0; idx++) {
str[idx] = toupper(str[idx]);
}
}
/* sets up a line oriented write buffer for lores and double lo-res files */
/* this uses a different LGR and DLGR buffer organization method than Bmp2DHR */
void setlopixel(unsigned char color,int x, int y)
{
unsigned char *crt, c1, c2;
int y1, offset;
if (doublelores == 1) {
if (x%2 == 0) {
/* auxiliary memory uses a different color index value */
color = dloauxcolor[color];
/* first 160 bytes goes to auxiliary memory (even pixels) */
offset = (x/2);
}
else {
/* next 160 bytes goes to main memory (odd pixels) */
offset = 160 + (x/2);
}
}
else {
offset = x;
}
y1 = y / 2;
c2 = (unsigned char ) (color & 15);
if (y%2 == 0) {
/* even rows in low nibble */
/* mask value to preserve high nibble */
c1 = 240;
}
else {
/* odd rows in high nibble */
/* mask value to preserve low nibble */
c1 = 15;
c2 = c2 * 16;
}
/* each paired scanline is offset by 320 bytes in our write buffer */
offset += (y1 * 320);
crt = (unsigned char *)&dhrbuf[offset];
crt[0] &= c1;
crt[0] |= c2;
}
uchar getlopixel(int x, int y)
{
unsigned char *crt, color;
int y1, offset;
if (doublelores == 1) {
if (x%2 == 0) {
/* first 160 bytes goes to auxiliary memory (even pixels) */
offset = (x/2);
}
else {
/* next 160 bytes goes to main memory (odd pixels) */
offset = 160 + (x/2);
}
}
else {
offset = x;
}
y1 = y / 2;
/* each paired scanline is offset by 320 bytes in our write buffer */
offset += (y1 * 320);
crt = (unsigned char *)&dhrbuf[offset];
if (y%2 == 0) {
/* even rows in low nibble */
color = crt[0] & 15;
}
else {
/* odd rows in high nibble */
color = crt[0] >> 4;
}
if (doublelores == 1 && x%2 == 0) {
/* auxiliary memory uses a different color index value */
color = dlomaincolor[color];
}
return color;
}
ushort WriteDIBHeader(FILE *fp, ushort pixels, ushort rasters)
{
ushort outpacket;
int c;
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)sizeof(BITMAPINFOHEADER);
mybmp.bmi.biWidth = (ulong)pixels;
mybmp.bmi.biHeight = (ulong)rasters;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
/* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */
outpacket = (ushort)mybmp.bmi.biWidth * 3;
while (outpacket%4 != 0)outpacket++;
mybmp.bmi.biSizeImage = (ulong)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the output BMP */
c = fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
if (c!= 1)outpacket = 0;
return outpacket;
}
/* DHGR file input Helper Function for HGR raster format image fragments */
int read_dhr(uchar *basename)
{
FILE *fp;
uchar infile[256], lodebuf[80];
int c,y,status=INVALID,width,height,packet;
unsigned dest;
sprintf(infile,"%s.DHR",basename);
fp = fopen(infile,"rb");
if (NULL == fp && longnames == 1) {
sprintf(infile,"%s.DHR#062000",basename);
fp = fopen(infile,"rb");
}
if (NULL == fp)return INVALID;
memset(dhrbuf,0,16384);
for (;;) {
/* read 5 byte header */
c = fread(lodebuf,1,5,fp);
if (c != 5) break;
if (lodebuf[0] != 'D' || lodebuf[1] != 'H' || lodebuf[2] != 'R') break;
width = (int)lodebuf[3];
height = (int)lodebuf[4];
/* must be in a valid range */
if (width < 4 || width > 80) break;
if (height< 1 || height > 192) break;
/* set some globals for BMP output */
bmpwidth = (ushort)((width / 4) * 7);
bmpheight = (ushort) height;
status = SUCCESS;
packet = width / 2;
for (y = 0;y < height;y++) {
c = fread(lodebuf,1,width,fp);
if (c!= width) {
status = INVALID;
break;
}
dest = HB[y];
/* move to auxiliary screen memory */
memcpy((char *)&dhrbuf[dest-0x2000],(char *)&lodebuf[0],packet);
/* move to main screen memory */
memcpy((char *)&dhrbuf[dest],(char *)&lodebuf[packet],packet);
}
break;
}
fclose(fp);
return status;
}
/* DHGR file input Helper Function for AUX, BIN file pairs */
/* a double hi-res color pixel can occur at any one of 7 positions */
/* in a 4 byte block which spans aux and main screen memory */
/* the horizontal resolution is 140 pixels */
/* read 2 input files */
int read_binaux(uchar *basename)
{
FILE *fp;
uchar infile[256];
/* the bsaved images are split into two files
the first file is loaded into aux mem */
sprintf(infile,"%s.AUX",basename);
fp = fopen(infile,"rb");
if (NULL == fp && longnames == 1) {
sprintf(infile,"%s.AUX#062000",basename);
fp = fopen(infile,"rb");
}
if (NULL == fp)return INVALID;
fread(dhrbuf,1,8192,fp);
fclose(fp);
/* the second file is loaded into main mem */
sprintf(infile,"%s.BIN",basename);
fp = fopen(infile,"rb");
if (NULL == fp && longnames == 1) {
sprintf(infile,"%s.BIN#062000",basename);
fp = fopen(infile,"rb");
}
if (NULL == fp)return INVALID;
fread(&dhrbuf[8192],1,8192,fp);
fclose(fp);
return SUCCESS;
}
/* DHGR file input Helper Function for A2FC files */
/* read one input file */
int read_2fc(uchar *basename)
{
FILE *fp;
uchar infile[256];
if (longnames == 0) {
sprintf(infile,"%s.2FC",basename);
fp = fopen(infile,"rb");
}
else {
for (;;) {
/* support for longnames and ciderpress tags */
/* support for tohgr output */
if (tohgr == 0)
sprintf(infile,"%s.2FC",basename);
else
sprintf(infile,"%s.dhgr",basename);
fp = fopen(infile,"rb");
if (NULL != fp)break;
if (tohgr == 0) {
if (mono == 0) sprintf(infile,"%s.A2FC",basename);
else sprintf(infile,"%s.A2FM",basename);
}
else {
sprintf(infile,"%s.DHGR",basename);
}
fp = fopen(infile,"rb");
if (NULL != fp)break;
if (tohgr == 0)
sprintf(infile,"%s.2FC#062000",basename);
else
sprintf(infile,"%s.dhgr#062000",basename);
fp = fopen(infile,"rb");
if (NULL != fp)break;
if (tohgr == 0) {
if (mono == 0) sprintf(infile,"%s.A2FC#062000",basename);
else sprintf(infile,"%s.A2FM#062000",basename);
}
else {
sprintf(infile,"%s.DHGR#062000",basename);
}
fp = fopen(infile,"rb");
break;
}
}
if (NULL == fp)return INVALID;
fread(dhrbuf,1,16384,fp);
fclose(fp);
return SUCCESS;
}
/* Color DHGR Pixel-Mapping Helper Function */
/* returns the Apple II Double Hi-res drawcolor 0-15 */
/* a double hi-res color pixel can occur at any one of 7 positions */
/* in a 4 byte block which spans aux and main screen memory */
/* the horizontal resolution is 140 pixels */
int dhrgetpixel(int x,int y)
{
int xoff, pattern, idx;
unsigned char *ptraux, *ptrmain,c1, c2, d1, d2;
if (x < 0 || x > 139 || y < 0 || y > 192) return INVALID;
pattern = (x%7);
xoff = HB[y] + ((x/7) * 2);
ptraux = (unsigned char *) &dhrbuf[xoff-0x2000];
ptrmain = (unsigned char *) &dhrbuf[xoff];
switch(pattern)
{
/* left this here for reference
unsigned char dhrpattern[7][4] = {
0,0,0,0,
0,0,0,1,
1,1,1,1,
1,1,2,2,
2,2,2,2,
2,3,3,3,
3,3,3,3};
*/
/* compare colors in the input file to color patterns and return drawcolor */
/* somewhat inelegant but lazy to read and debug if a problem */
case 0: c1 = ptraux[0] &0x0f;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][0] & 0x0f;
if (d1 == c1) return idx;
}
break;
case 1: c1 = ptraux[0] & 0x70;
c2 = ptrmain[0] & 0x01;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][0] & 0x70;
d2 = dhrbytes[idx][1] & 0x01;
if (d1 == c1 && d2 == c2) return idx;
}
break;
case 2: c1 = ptrmain[0] & 0x1e;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][1] & 0x1e;
if (d1 == c1) return idx;
}
break;
case 3: c1 = ptrmain[0] & 0x60;
c2 = ptraux[1] & 0x03;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][1] & 0x60;
d2 = dhrbytes[idx][2] & 0x03;
if (d1 == c1 && d2 == c2) return idx;
}
break;
case 4: c1 = ptraux[1] & 0x3c;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][2] & 0x3c;
if (d1 == c1) return idx;
}
break;
case 5: c1 = ptraux[1] & 0x40;
c2 = ptrmain[1] & 0x07;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][2] & 0x40;
d2 = dhrbytes[idx][3] & 0x07;
if (d1 == c1 && d2 == c2) return idx;
}
break;
case 6: c1 = ptrmain[1] & 0x78;
for (idx = 0; idx < 16; idx++) {
d1 = dhrbytes[idx][3] & 0x78;
if (d1 == c1) return idx;
}
break;
}
return INVALID;
}
ushort WriteVbmpHeader(FILE *fp)
{
ushort outpacket;
int c, i, j;
/* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */
outpacket = (ushort)72;
if (mono != 0) {
c = fwrite(mono192,1,sizeof(mono192),fp);
if (c!= sizeof(mono192))return 0;
return outpacket;
}
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)140;
mybmp.bmi.biHeight = (ulong)192;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 4;
mybmp.bmi.biCompression = (ulong) BI_RGB;
mybmp.bmi.biSizeImage = (ulong)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER) + sizeof(RGBQUAD) * 16;
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the output BMP */
c = fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
if (c!= 1)return 0;
for (i=0;i<16;i++) {
j = RemapLoToHi[i];
sbmp[i].rgbRed = rgbArray[j][0];
sbmp[i].rgbGreen = rgbArray[j][1];
sbmp[i].rgbBlue = rgbArray[j][2];
sbmp[i].rgbReserved = 0;
}
/* write the palette for the output bmp */
c = fwrite((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*16,1,fp);
if (c!= 1)return 0;
return outpacket;
}
/* writes VBMP compatible 140 x 192 x 16 color bmp or VBMP compatible 560 x 192 monochrome bmp */
int WriteVBMPFile(unsigned char *vbmpfile)
{
FILE *fp;
uchar ch;
int x,x1,y,y2,idx,j,packet=72;
fp = fopen(vbmpfile,"wb");
if (fp == NULL) {
printf("Error opening %s for writing!\n",vbmpfile);
return INVALID;
}
if (WriteVbmpHeader(fp) == 0) {
fclose(fp);
remove(vbmpfile);
printf("Error writing header to %s!\n",vbmpfile);
return INVALID;
}
memset(&bmpscanline[0],0,packet);
/* write 4 bit packed scanlines */
/* remap from LORES color order to DHGR color order */
/* VBMP does not use the colors in the palette, just the color order */
y2 = 191;
for (y = 0; y< 192; y++) {
for (x = 0, x1=0; x < 140; x++) {
if (x%2 == 0) {
idx = dhrgetpixel(x,y2);
/* range check */
if (idx < 0 || idx > 15)idx = 0; /* default black */
j = RemapLoToHi[idx];
ch = (uchar)j << 4;
}
else {
idx = dhrgetpixel(x,y2);
/* range check */
if (idx < 0 || idx > 15)idx = 0; /* default black */
j = RemapLoToHi[idx];
bmpscanline[x1] = ch | (uchar)j; x1++;
}
}
fwrite((char *)&bmpscanline[0],1,packet,fp);
y2 -= 1;
}
fclose(fp);
return SUCCESS;
}
/* Color Output Helper Function */
int save_to_bmp24(uchar *basename)
{
FILE *fp;
uchar outfile[256], tempr, tempg, tempb;
int x,x1,y,y2,idx,packet,xoffset=0,yoffset=0;
sprintf(outfile,"%s.bmp",basename);
if (vbmp == 1) return WriteVBMPFile(outfile);
if (frag == 1) {
/* create BMP image fragment from full-screen Apple II input */
dhr = 1;
bmpwidth = fragwidth;
bmpheight = fragheight;
xoffset = fragx;
yoffset = fragy;
}
fp = fopen(outfile,"wb");
if (NULL == fp)return INVALID;
/* write rgb triples and double each pixel to preserve the aspect ratio */
if (doublepixel == 1) {
/* write header for 280 x 192 x 24 bit bmp */
if (dhr == 1) packet = (int)WriteDIBHeader(fp,bmpwidth*2,bmpheight);
else fwrite(BMP_header,1,sizeof(BMP_header),fp);
}
else {
/* write header for 140 x 192 x 24 bit bmp */
if (dhr == 1) packet = (int)WriteDIBHeader(fp,bmpwidth,bmpheight);
else fwrite(BMP140_header,1,sizeof(BMP140_header),fp);
}
if (dhr == 1) {
memset(&bmpscanline[0],0,840);
y2 = (bmpheight) - 1;
for (y = 0; y< bmpheight; y++) {
for (x = 0, x1=0; x < bmpwidth; x++) {
idx = dhrgetpixel(x+xoffset,y2+yoffset);
if (idx < 0 || idx > 15)idx = 0;
tempr = rgbArray[idx][0];
tempg = rgbArray[idx][1];
tempb = rgbArray[idx][2];
bmpscanline[x1] = tempb; x1++;
bmpscanline[x1] = tempg; x1++;
bmpscanline[x1] = tempr; x1++;
if (doublepixel == 1) {
bmpscanline[x1] = tempb; x1++;
bmpscanline[x1] = tempg; x1++;
bmpscanline[x1] = tempr; x1++;
}
}
fwrite((char *)&bmpscanline[0],1,packet,fp);
y2 -= 1;
}
}
else {
y2 = 191;
for (y = 0; y< 192; y++) {
for (x = 0; x < 140; x++) {
idx = dhrgetpixel(x,y2);
/* range check */
if (idx < 0 || idx > 15)idx = 0; /* default to black */
tempr = rgbArray[idx][0];
tempg = rgbArray[idx][1];
tempb = rgbArray[idx][2];
/* reverse order */
fputc(tempb, fp);
fputc(tempg, fp);
fputc(tempr, fp);
if (doublepixel == 1) {
/* double-up */
fputc(tempb, fp);
fputc(tempg, fp);
fputc(tempr, fp);
}
}
y2 -= 1;
}
}
fclose(fp);
return SUCCESS;
}
/* encodes apple II dhgr scanline into buffer */
int applewinbits(int y)
{
int xoff,idx,jdx;
unsigned char *ptraux, *ptrmain, bits[7];
xoff = HB[y];
ptraux = (unsigned char *) &dhrbuf[xoff-0x2000];
ptrmain = (unsigned char *) &dhrbuf[xoff];
xoff = 0;
for (idx = 0; idx < 40; idx++) {
for (jdx = 0; jdx < 7; jdx++) {
bits[jdx] = bmpscanline[xoff]; xoff++;
}
ptraux[idx] = (bits[6]<<6|bits[5]<<5|bits[4]<<4|
bits[3]<<3|bits[2]<<2|bits[1]<<1|bits[0]);
for (jdx = 0; jdx < 7; jdx++) {
bits[jdx] = bmpscanline[xoff]; xoff++;
}
ptrmain[idx] = (bits[6]<<6|bits[5]<<5|bits[4]<<4|
bits[3]<<3|bits[2]<<2|bits[1]<<1|bits[0]);
}
return SUCCESS;
}
/* routines for optional SHR grey-scaling using hsl */
/* by default, average RGB is used */
/*
http://www.vbaccelerator.com/home/VB/Code/vbMedia/Colour_Models/Hue__Luminance_and_Saturation/article.asp
*/
/* Creative Commons Section Start */
float Minimum(float rr, float rg, float rb)
{
if (rr < rg) {
if (rr < rb)return rr;
return rb;
}
if (rb < rg) return rb;
return rg;
}
float Maximum(float rr, float rg, float rb)
{
if (rr > rg) {
if (rr > rb)return rr;
return rb;
}
if (rb > rg) return rb;
return rg;
}
void rgb2hsl(uchar r,uchar g,uchar b,float *h,float *s,float *l)
{
float rr,rg,rb,min,max,delta;
rr = (float) r;
rg = (float) g;
rb = (float) b;
rr /= 255;
rg /= 255;
rb /= 255;
max = Maximum(rr, rg, rb);
min = Minimum(rr, rg, rb);
l[0] = (max + min) / 2;
if (max == min) {
/* achromatic - grey */
s[0] = h[0] = (float)0.0;
}
else {
/* chromatic - colors */
if (l[0] <= 0.5) s[0] = (max - min) / (max + min);
else s[0] = (max - min) / (2.0 - max - min);
delta = max - min;
if (rr == max) h[0] = (rg - rb) / delta;
else if (rg == max) h[0] = 2.0 + (rb - rr) / delta;
else if (rb == max) h[0] = 4.0 + (rr - rg) / delta;
}
}
void hsl2rgb(float h,float s,float l,uchar *r,uchar *g,uchar *b)
{
float rr,rg,rb,min,max;
if (s == 0) {
/* achromatic - greyscale */
rr = rg = rb = l;
}
else {
/* chromatic - colors */
if (l <= 0.5) min = l * (1.0 - s);
else min = l - s * (1.0 - l);
max = 2.0 * l - min;
if (h < 1) {
rr = max;
if (h < 0) {
rg = min;
rb = rg - h * (max - min);
}
else {
rb = min;
rg = h * (max - min) + rb;
}
}
else if (h < 3) {
rg = max;
if (h < 2) {
rb = min;
rr = rb - (h - 2) * (max - min);
}
else {
rr = min;
rb = (h - 2.0) * (max - min) + rr;
}
}
else {
rb = max;
if (h < 4) {
rr = min;
rg = rr - (h - 4) * (max - min);
}
else {
rg = min;
rr = (h - 4.0) * (max - min) + rg;
}
}
}
r[0] = (uchar)(float)(rr * 255);
g[0] = (uchar)(float)(rg * 255);
b[0] = (uchar)(float)(rb * 255);
}
/* Creative Commons Section End */
/* set luma to different values for closest color */
/* read this first - https://en.wikipedia.org/wiki/YCbCr */
/* also read https://en.wikipedia.org/wiki/Color_space */
/* also read https://en.wikipedia.org/wiki/RGB_color_space */
int lumaREQ = 601, lumaRED = 299, lumaGREEN = 587, lumaBLUE = 114;
double dlumaRED, dlumaGREEN, dlumaBLUE;
/* strip line feeds from ascii file lines... */
void nocr(char *ptr) {
int idx;
for (idx = 0; ptr[idx] != 0; idx++)
if (ptr[idx] == LFEED || ptr[idx] == CRETURN || ptr[idx] == '#')
ptr[idx] = 0;
}
void setluma()
{
FILE *fp;
char buf[128];
double dred, dgreen, dblue;
int status = -1;
/* optional text file with 3-lines,
each with a luma coefficient for red,green and blue respectively */
/* over-rides hard-coded luma values
this is for someone who really knows what they are doing */
fp = fopen("luma.txt","r");
if (NULL != fp) {
for (;;) {
/* read custom luma values */
if (NULL == fgets(buf, 128, fp)) break;
nocr(buf);
dred = atof(buf);
if (NULL == fgets(buf, 128, fp)) break;
nocr(buf);
dgreen = atof(buf);
if (NULL == fgets(buf, 128, fp)) break;
nocr(buf);
dblue = atof(buf);
status = -2;
/* generous range check */
if (dred > 0.999 || dred < 0.001) break;
if (dgreen > 0.999 || dgreen < 0.001) break;
if (dblue > 0.999 || dblue < 0.001) break;
/* set custom luma values */
dlumaRED = dred; lumaRED = (int)(dred * 1000);
dlumaGREEN = dgreen; lumaGREEN = (int)(dgreen * 1000);
dlumaBLUE = dblue; lumaBLUE = (int)(dblue * 1000);
status = SUCCESS;
break;
}
fclose(fp);
if (status == -1) puts("luma.txt: read error!");
else if (status == -2) puts("luma.txt: invalid coefficient!");
else puts("luma.txt: external coefficients in effect!");
}
if (status == SUCCESS) return;
switch(lumaREQ)
{
/* HDMI II - Rec. 2020 specifies that if a luma (Y') signal is made that it
uses the R<>G<EFBFBD>B<EFBFBD> coefficients
0.2627 for red, 0.6780 for green, and 0.0593 for blue */
case 2020:
lumaRED = 263; lumaGREEN = 678; lumaBLUE = 59;
dlumaRED = 0.2627; dlumaGREEN = 0.6780; dlumaBLUE = 0.0593;
break;
case 240: /* SMPTE 240M transitional coefficients */
/* http://www.chromapure.com/colorscience-decoding.asp */
lumaRED = 212; lumaGREEN = 701; lumaBLUE = 87;
dlumaRED = 0.2124; dlumaGREEN = 0.7011; dlumaBLUE = 0.0866;
break;
case 911: /* Sheldon Simms - tohgr - probably not useful */
lumaRED = 77; lumaGREEN = 151; lumaBLUE = 28;
dlumaRED = 0.077;dlumaGREEN = 0.151; dlumaBLUE = 0.028;
break;
case 411: /* The GIMP color managed */
/* https://mail.gnome.org/archives/gimp-user-list/2013-November/msg00173.html */
/* sRGB color managed */
/* http://ninedegreesbelow.com/photography/srgb-luminance.html */
lumaRED = 223; lumaGREEN = 717; lumaBLUE = 61;
dlumaRED = 0.2225; dlumaGREEN = 0.7169; dlumaBLUE = 0.0606;
break;
case 709: /* CCIR 709 - modern */
/* ImageMagick non-color managed */
/* also The GIMP 2.8 - http://fossies.org/dox/gimp-2.8.14/gimprgb_8h_source.html */
/* also PhotoShop
http://www.beneaththewaves.net/Photography/Secrets_of_Photoshops_Colour_Blend_Mode_Revealed_Sort_Of.html
*/
lumaRED = 213; lumaGREEN = 715; lumaBLUE = 72;
dlumaRED = 0.212656;dlumaGREEN = 0.715158; dlumaBLUE = 0.072186;
break;
case 601: /* CCIR 601 - most digital standard definition formats */
/* for monitors having phosphors that were contemporary
at the introduction of NTSC television in 1953.
/* these coefficients do not accurately compute luminance for contemporary monitors */
default: lumaRED = 299; lumaGREEN = 587; lumaBLUE = 114;
dlumaRED = 0.298839; dlumaGREEN = 0.586811; dlumaBLUE = 0.114350;
break;
}
}
double rgbLuma[16], rgbDouble[16][3];
int brooksline = 999;
/* intialize the values for the current palette */
void InitDoubleArrays()
{
int i;
double dr, dg, db;
/* array for matching closest color in palette */
for (i=0;i<16;i++) {
rgbDouble[i][0] = dr = (double) rgbArray[i][0];
rgbDouble[i][1] = dg = (double) rgbArray[i][1];
rgbDouble[i][2] = db = (double) rgbArray[i][2];
rgbLuma[i] = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
}
}
/* for SHR output */
/* intialize the closest color palette for each scanline */
void InitDoubleLineArrays(int y)
{
int i;
double dr, dg, db;
/* array for matching closest color in palette */
for (i=0;i<16;i++) {
rgbDouble[i][0] = dr = (double) rgbArrays[y][i][0];
rgbDouble[i][1] = dg = (double) rgbArrays[y][i][1];
rgbDouble[i][2] = db = (double) rgbArrays[y][i][2];
rgbLuma[i] = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
}
brooksline = y;
}
void InitDoubleLine256Arrays(int idx)
{
int i;
double dr, dg, db;
/* array for matching closest color in palette */
for (i=0;i<16;i++) {
rgbDouble[i][0] = dr = (double) rgb256Arrays[idx][i][0];
rgbDouble[i][1] = dg = (double) rgb256Arrays[idx][i][1];
rgbDouble[i][2] = db = (double) rgb256Arrays[idx][i][2];
rgbLuma[i] = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
}
for (i=0,brooksline=0;i<200;i++) {
if (idx == (int)mypic.scb[i]) {
brooksline = i;
break;
}
}
}
double globaldistance = 0.0, indexdistance = 0.0, brooksdistance = 0.0;
/* use CCIR 601 luminosity to get color distance value */
uchar GetColorDistance(uchar r, uchar g, uchar b, uchar idx)
{
uchar drawcolor, i;
double dr, dg, db, diffR, diffG, diffB, luma, lumadiff, distance, prevdistance;
indexdistance = 0.0;
/* use nearest color */
dr = (double)r;
dg = (double)g;
db = (double)b;
luma = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
lumadiff = rgbLuma[0]-luma;
/* Compare the difference of RGB values, weigh by CCIR 601 luminosity */
/* set palette index to color with shortest distance */
/* get color distance to first palette color */
diffR = (rgbDouble[0][0]-dr)/255.0;
diffG = (rgbDouble[0][1]-dg)/255.0;
diffB = (rgbDouble[0][2]-db)/255.0;
prevdistance = (diffR*diffR*dlumaRED + diffG*diffG*dlumaGREEN + diffB*diffB*dlumaGREEN)*0.75
+ lumadiff*lumadiff;
/* set palette index to first color */
drawcolor = 0;
/* get color distance to rest of palette colors */
for (i=1;i<16;i++) {
if (i<15) {
if (i != idx) continue;
}
/* get color distance of this index */
lumadiff = rgbLuma[i]-luma;
diffR = (rgbDouble[i][0]-dr)/255.0;
diffG = (rgbDouble[i][1]-dg)/255.0;
diffB = (rgbDouble[i][2]-db)/255.0;
distance = (diffR*diffR*dlumaRED + diffG*diffG*dlumaGREEN + diffB*diffB*dlumaGREEN)*0.75
+ lumadiff*lumadiff;
/* if distance is smaller use this index */
if (distance < prevdistance) {
prevdistance = distance;
drawcolor = (uchar)i;
}
}
indexdistance = prevdistance;
return drawcolor;
}
/* use CCIR 601 luminosity to get closest color in current palette */
/* based on palette that has been selected for conversion */
uchar GetClosestColor(uchar r, uchar g, uchar b)
{
uchar drawcolor;
double dr, dg, db, diffR, diffG, diffB, luma, lumadiff, distance, prevdistance;
int i,j=brooksline;
globaldistance = 0.0;
/* look for exact match */
for (i=0;i<16;i++) {
if (brooksline == 999) {
if (r == rgbArray[i][0] && g == rgbArray[i][1] && b == rgbArray[i][2]) return (uchar)i;
}
else {
if (r == rgbArrays[j][i][0] && g == rgbArrays[j][i][1] && b == rgbArrays[j][i][2]) return (uchar)i;
}
}
/* if no exact match use nearest color */
dr = (double)r;
dg = (double)g;
db = (double)b;
luma = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
lumadiff = rgbLuma[0]-luma;
/* Compare the difference of RGB values, weigh by CCIR 601 luminosity */
/* set palette index to color with shortest distance */
/* get color distance to first palette color */
diffR = (rgbDouble[0][0]-dr)/255.0;
diffG = (rgbDouble[0][1]-dg)/255.0;
diffB = (rgbDouble[0][2]-db)/255.0;
prevdistance = (diffR*diffR*dlumaRED + diffG*diffG*dlumaGREEN + diffB*diffB*dlumaGREEN)*0.75
+ lumadiff*lumadiff;
/* set palette index to first color */
drawcolor = 0;
/* get color distance to rest of palette colors */
for (i=1;i<16;i++) {
/* get color distance of this index */
lumadiff = rgbLuma[i]-luma;
diffR = (rgbDouble[i][0]-dr)/255.0;
diffG = (rgbDouble[i][1]-dg)/255.0;
diffB = (rgbDouble[i][2]-db)/255.0;
distance = (diffR*diffR*dlumaRED + diffG*diffG*dlumaGREEN + diffB*diffB*dlumaBLUE)*0.75
+ lumadiff*lumadiff;
/* if distance is smaller use this index */
if (distance < prevdistance) {
prevdistance = distance;
drawcolor = (uchar)i;
}
}
globaldistance = prevdistance;
return drawcolor;
}
/* use CCIR 601 luminosity to get closest color in current palette */
/* based on palette that has been selected for conversion */
uchar GetClosest256Color(uchar r, uchar g, uchar b, int palno)
{
uchar drawcolor;
double dr, dg, db, diffR, diffG, diffB, luma, lumadiff, distance, prevdistance;
int i,j=palno;
globaldistance = 0.0;
/* look for exact match */
for (i=0;i<16;i++) {
if (palno == 999) {
if (r == rgbArray[i][0] && g == rgbArray[i][1] && b == rgbArray[i][2]) return (uchar)i;
}
else {
if (r == rgb256Arrays[j][i][0] && g == rgb256Arrays[j][i][1] && b == rgb256Arrays[j][i][2]) return (uchar)i;
}
}
/* if no exact match use nearest color */
dr = (double)r;
dg = (double)g;
db = (double)b;
luma = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
lumadiff = rgbLuma[0]-luma;
/* Compare the difference of RGB values, weigh by CCIR 601 luminosity */
/* set palette index to color with shortest distance */
/* get color distance to first palette color */
diffR = (rgbDouble[0][0]-dr)/255.0;
diffG = (rgbDouble[0][1]-dg)/255.0;
diffB = (rgbDouble[0][2]-db)/255.0;
prevdistance = (diffR*diffR*dlumaRED + diffG*diffG*dlumaGREEN + diffB*diffB*dlumaGREEN)*0.75
+ lumadiff*lumadiff;
/* set palette index to first color */
drawcolor = 0;
/* get color distance to rest of palette colors */
for (i=1;i<16;i++) {
/* get color distance of this index */
lumadiff = rgbLuma[i]-luma;
diffR = (rgbDouble[i][0]-dr)/255.0;
diffG = (rgbDouble[i][1]-dg)/255.0;
diffB = (rgbDouble[i][2]-db)/255.0;
distance = (diffR*diffR*dlumaRED + diffG*diffG*dlumaGREEN + diffB*diffB*0.114)*0.75
+ lumadiff*lumadiff;
/* if distance is smaller use this index */
if (distance < prevdistance) {
prevdistance = distance;
drawcolor = (uchar)i;
}
}
globaldistance = prevdistance;
return drawcolor;
}
/* routines to save to Apple 2 Double Hires Format */
/* a double hi-res pixel can occur at any one of 7 positions */
/* in a 4 byte block which spans aux and main screen memory */
/* the horizontal resolution is 140 pixels */
void dhrplot(int x,int y,uchar drawcolor)
{
int xoff, pattern;
uchar *ptraux, *ptrmain;
if (x < 0 || x > 139 || y < 0 || y > 192) return;
pattern = (x%7);
xoff = HB[y] + ((x/7) * 2);
ptraux = (uchar *) &dhrbuf[xoff-0x2000];
ptrmain = (uchar *) &dhrbuf[xoff];
switch(pattern)
{
/* left this here for reference
uchar dhrpattern[7][4] = {
0,0,0,0,
0,0,0,1,
1,1,1,1,
1,1,2,2,
2,2,2,2,
2,3,3,3,
3,3,3,3};
*/
case 0: ptraux[0] &= 0x70;
ptraux[0] |= dhrbytes[drawcolor][0] &0x0f;
break;
case 1: ptraux[0] &= 0x0f;
ptraux[0] |= dhrbytes[drawcolor][0] & 0x70;
ptrmain[0] &= 0x7e;
ptrmain[0] |= dhrbytes[drawcolor][1] & 0x01;
break;
case 2: ptrmain[0] &= 0x61;
ptrmain[0] |= dhrbytes[drawcolor][1] & 0x1e;
break;
case 3: ptrmain[0] &= 0x1f;
ptrmain[0] |= dhrbytes[drawcolor][1] & 0x60;
ptraux[1] &= 0x7c;
ptraux[1] |= dhrbytes[drawcolor][2] & 0x03;
break;
case 4: ptraux[1] &= 0x43;
ptraux[1] |= dhrbytes[drawcolor][2] & 0x3c;
break;
case 5: ptraux[1] &= 0x3f;
ptraux[1] |= dhrbytes[drawcolor][2] & 0x40;
ptrmain[1] &= 0x78;
ptrmain[1] |= dhrbytes[drawcolor][3] & 0x07;
break;
case 6: ptrmain[1] &= 0x07;
ptrmain[1] |= dhrbytes[drawcolor][3] & 0x78;
break;
}
}
/* monochrome DHGR - 560 x 192 */
unsigned char dhbmono[] = {0x7e,0x7d,0x7b,0x77,0x6f,0x5f,0x3f};
unsigned char dhwmono[] = {0x1,0x2,0x4,0x8,0x10,0x20,0x40};
void dhrmonoplot(int x, int y, uchar drawcolor)
{
int xoff, pixel;
uchar *ptr;
if (x < 0 || x > 559 || y < 0 || y > 192) return;
xoff = HB[y] + (x/14);
pixel = (x%14);
if (pixel > 6) {
/* main memory */
pixel -= 7;
ptr = (uchar *) &dhrbuf[xoff];
}
else {
/* auxiliary memory */
ptr = (uchar *) &dhrbuf[xoff-0x2000];
}
if (drawcolor != 0) {
/* white */
ptr[0] |= dhwmono[pixel]; /* inclusive OR */
}
else {
/* black */
ptr[0] &= dhbmono[pixel]; /* bitwise AND */
}
}
sshort redDither[640],greenDither[640],blueDither[640];
/* seed values from previous line */
sshort redSeed[640],greenSeed[640],blueSeed[640];
sshort redSeed2[640],greenSeed2[640],blueSeed2[640];
/* save and restore values for SHR palette matching */
sshort redSave[640],greenSave[640], blueSave[640];
/* in this implementation color bleed is fixed for my dither */
/* it will be either full (8/8) like Floyd-Steinberg or reduced 20% (8/10) */
int bleed = 8;
/* setting clip to 0 increases the potential amount of retained error */
/* error is accumulated in a short integer and may be negative or positive */
uchar AdjustShortPixel(int clip,sshort *buf,sshort value)
{
value = (sshort)(buf[0] + value);
if (clip != 0) {
if (value < 0) value = 0;
else if (value > 255) value = 255;
}
buf[0] = value;
if (clip == 0) {
if (value < 0) value = 0;
else if (value > 255) value = 255;
}
return (uchar) value;
}
/* this is set-up to handle image fragments as well as full-screen dithering */
/* nominal DHGR resolutions supported are:
140 x 192 - 4-bit pixels - same as Bmp2DHR and tohgr DHGR color
280 x 192 - 2-bit pixels - same as Bmp2DHR HGR monochrome (not sure about Outlaw Editor HGR color)
560 x 192 - 1-bit pixels - same as Bmp2DHR DHGR monochrome and Outlaw Editor DHGR color
This is somewhat confusing I admit.
To make things even more confusing this also handles LGR and DLGR, and SHR PIC files
*/
void BuckelsDither(int y, int width, int pixels)
{
sshort *colorptr, *seedptr, *seed2ptr, color_error, random_error, errbuf[6];
sshort red, green, blue, red_error, green_error, blue_error;
int i,x,x1,x2;
int total_difference, total_error, total_used;
uchar drawcolor,r,g,b,idx;
/* bits per pixel */
switch(pixels) {
case 4: if (width > 280) pixels = 1;
else if (width > 140) pixels = 2;
break;
case 2: if (width > 280) pixels = 1;
break;
default: pixels = 1;
}
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
if (brooks == 0) {
r = rgbArray[drawcolor][0];
g = rgbArray[drawcolor][1];
b = rgbArray[drawcolor][2];
}
else {
r = rgbArrays[y][drawcolor][0];
g = rgbArrays[y][drawcolor][1];
b = rgbArrays[y][drawcolor][2];
}
redDither[x] = (int)r;
greenDither[x] = (int)g;
blueDither[x] = (int)b;
/* the error is linear in this implementation */
/* - an integer is used so round-off of errors occurs
- also clipping of the error occurs under some circumstances
- no luminance consideration
- no gamma correction
*/
red_error = red - r;
green_error = green - g;
blue_error = blue - b;
for (i=0;i<3;i++) {
/* loop through all 3 RGB channels */
switch(i) {
case 0: colorptr = (sshort *)&redDither[0];
seedptr = (sshort *)&redSeed[0];
seed2ptr = (sshort *)&redSeed2[0];
color_error = red_error;
break;
case 1: colorptr = (sshort *)&greenDither[0];
seedptr = (sshort *)&greenSeed[0];
seed2ptr = (sshort *)&greenSeed2[0];
color_error = green_error;
break;
case 2: colorptr = (sshort *)&blueDither[0];
seedptr = (sshort *)&blueSeed[0];
seed2ptr = (sshort *)&blueSeed2[0];
color_error = blue_error;
break;
}
/* diffuse the error based on the dither */
switch(dithertype)
{
case FLOYDSTEINBERG:
/*
* 7
3 5 1 (1/16)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 16) / bleed;
total_used = (color_error * 3)/bleed;
total_used += (color_error * 5)/bleed;
total_used += (color_error * 1)/bleed;
total_used += (color_error * 7)/bleed;
total_difference = total_error - total_used;
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error * 7)/bleed)+total_difference);
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)((color_error * 3)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)((color_error * 1)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error * 5)/bleed));
break;
case ATKINSON:
case ATKINSON2:
/*
* 1 1
1 1 1
1 (1/8 - reduced bleed) or (1/6 full bleed even diffusion)
*/
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)(color_error/bleed));
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed));
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed));
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
break;
default:
/* buckels dither */
/* uses the same weighting pattern as Atkinson but with full weighting */
/*
* 2 1
1 2 1
1 (1/8)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
/* random dither automatically turns error summing on in which case
rounding errors are placed at random within the atkinson pattern */
errbuf[0] = errbuf[1] = errbuf[2] = errbuf[3] = errbuf[4] = errbuf[5] = 0;
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 8) / bleed;
total_used = (color_error * 2)/bleed;
total_used += (color_error * 2)/bleed;
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_difference = total_error - total_used;
if (randomdither == 0) {
/* next pixel */
errbuf[0] = total_difference;
}
else {
/* random pixel */
idx = RandomRange(6) - 1;
errbuf[idx] = total_difference;
}
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error*2)/bleed)+errbuf[0]);
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed)+errbuf[1]);
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed)+errbuf[2]);
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error*2)/bleed)+errbuf[3]);
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed)+errbuf[4]);
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
break;
}
}
}
/* for DHGR */
/* plot dithered scanline in DHGR buffer using selected conversion palette */
/* this supports single-bit, double-bit and 4-bit per pixel dithering */
/* effective nominal resolutions are 560 x 192, 280 x 192, and 140 x 192 */
/* for LGR and DLGR, resolution goes-up to 320 x 200 providing an image
that can be much larger than the screen and down to 1 x 1 for LGR and 2 x 1 for DLGR
for sprites that can be much smaller than the screen */
/* SHR output is also supported in 320 x 200 only */
for (x=0,x1=0,x2=0;x<width;x++) {
r = (uchar)redDither[x];
g = (uchar)greenDither[x];
b = (uchar)blueDither[x];
idx = GetClosestColor(r,g,b);
if (lores == 1) {
/* LGR and DLGR use a 1:1 verbatim dithering only */
/* SHR output is based on LGR and DLGR dithering so is verbatim also */
setlopixel((uchar)idx,x,y);
/* the code below is used only for DHGR dithering */
continue;
}
if (outline == 1) {
if (idx != 0) idx = 15;
}
for (i=0;i<pixels;i++) {
drawcolor = dhrbits[idx][x1];
if (x1 > 26) x1 = 0;
else x1++;
dhrmonoplot(x2,y,drawcolor);
x2++;
}
}
}
void BrooksDither(int y, int width)
{
sshort *colorptr, *seedptr, *seed2ptr, color_error, random_error, errbuf[6];
sshort red, green, blue, red_error, green_error, blue_error, heavy_error;
int i,x,x1,x2,y1,y2;
int total_difference, total_error, total_used;
uchar drawcolor,r,g,b,idx;
double besttotal, thistotal;
/* find the palette with the least total error */
for (y1 = 0; y1 < 200; y1++) {
thistotal = 0.0;
InitDoubleLineArrays(y1);
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
thistotal += globaldistance;
}
if (y1 == 0) {
y2 = y1;
besttotal = thistotal;
continue;
}
if (thistotal < besttotal) {
besttotal = thistotal;
y2 = y1;
}
}
/* set the palette with the least total error as the dithering palette */
memcpy(&savepalettes[y][0][0],&rgbArrays[y2][0][0],48);
InitDoubleLineArrays(y2);
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
r = rgbArrays[y2][drawcolor][0];
g = rgbArrays[y2][drawcolor][1];
b = rgbArrays[y2][drawcolor][2];
redDither[x] = (int)r;
greenDither[x] = (int)g;
blueDither[x] = (int)b;
/* the error is linear in this implementation */
/* - an integer is used so round-off of errors occurs
- also clipping of the error occurs under some circumstances
- no luminance consideration
- no gamma correction
*/
red_error = red - r;
green_error = green - g;
blue_error = blue - b;
for (i=0;i<3;i++) {
/* loop through all 3 RGB channels */
switch(i) {
case 0: colorptr = (sshort *)&redDither[0];
seedptr = (sshort *)&redSeed[0];
seed2ptr = (sshort *)&redSeed2[0];
color_error = red_error;
break;
case 1: colorptr = (sshort *)&greenDither[0];
seedptr = (sshort *)&greenSeed[0];
seed2ptr = (sshort *)&greenSeed2[0];
color_error = green_error;
break;
case 2: colorptr = (sshort *)&blueDither[0];
seedptr = (sshort *)&blueSeed[0];
seed2ptr = (sshort *)&blueSeed2[0];
color_error = blue_error;
break;
}
/* diffuse the error based on the dither */
switch(dithertype)
{
case FLOYDSTEINBERG:
/*
* 7
3 5 1 (1/16)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 16) / bleed;
total_used = (color_error * 3)/bleed;
total_used += (color_error * 5)/bleed;
total_used += (color_error * 1)/bleed;
total_used += (color_error * 7)/bleed;
total_difference = total_error - total_used;
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error * 7)/bleed)+total_difference);
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)((color_error * 3)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)((color_error * 1)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error * 5)/bleed));
break;
case ATKINSON:
case ATKINSON2:
/*
* 1 1
1 1 1
1 (1/8 - reduced bleed) or (1/6 full bleed even diffusion)
*/
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)(color_error/bleed));
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed));
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed));
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
break;
default:
/* buckels dither */
/* uses the same weighting pattern as Atkinson but with full weighting */
/*
* 2 1
1 2 1
1 (1/8)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
/* random dither automatically turns error summing on in which case
rounding errors are placed at random within the atkinson pattern */
errbuf[0] = errbuf[1] = errbuf[2] = errbuf[3] = errbuf[4] = errbuf[5] = 0;
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 8) / bleed;
total_used = (color_error * 2)/bleed;
total_used += (color_error * 2)/bleed;
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_difference = total_error - total_used;
if (randomdither == 0) {
/* next pixel */
errbuf[0] = total_difference;
}
else {
/* random pixel */
idx = RandomRange(6) - 1;
errbuf[idx] = total_difference;
}
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error*2)/bleed)+errbuf[0]);
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed)+errbuf[1]);
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed)+errbuf[2]);
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error*2)/bleed)+errbuf[3]);
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed)+errbuf[4]);
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
}
}
}
/* SHR output in 320 x 200 only */
for (x=0,x1=0,x2=0;x<width;x++) {
r = (uchar)redDither[x];
g = (uchar)greenDither[x];
b = (uchar)blueDither[x];
idx = GetClosestColor(r,g,b);
setlopixel((uchar)idx,x,y);
}
}
void PicDither(int y, int width)
{
sshort *colorptr, *seedptr, *seed2ptr, color_error, random_error, errbuf[6];
sshort red, green, blue, red_error, green_error, blue_error;
int i,x,x1,x2,y1,y2,saveline, maxpal = 16;
int total_difference, total_error, total_used;
uchar drawcolor,r,g,b,idx;
double besttotal, thistotal;
/* save original values */
/* not used yet - may not be needed */
/*
memcpy(&redSave[0],&redDither[0],width * sizeof(sshort));
memcpy(&greenSave[0],&greenDither[0],width * sizeof(sshort));
memcpy(&blueSave[0],&blueDither[0],width * sizeof(sshort));
*/
if (imnumpalettes == 8) maxpal = 8;
else if (imnumpalettes == 1) maxpal = 1;
/* find the palette with the least total error */
for (y1 = 0; y1 < maxpal; y1++) {
thistotal = 0.0;
InitDoubleLine256Arrays(y1);
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
thistotal += globaldistance;
}
if (y1 == 0) {
y2 = y1;
besttotal = thistotal;
saveline = brooksline;
continue;
}
if (thistotal < besttotal) {
besttotal = thistotal;
y2 = y1;
saveline = brooksline;
}
}
/* set the palette with the least total error as the dithering palette */
memcpy(&savepalettes[y][0][0],&rgb256Arrays[y2][0][0],48);
savescb[y] = y2;
InitDoubleLineArrays(saveline);
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
r = rgbArrays[saveline][drawcolor][0];
g = rgbArrays[saveline][drawcolor][1];
b = rgbArrays[saveline][drawcolor][2];
redDither[x] = (int)r;
greenDither[x] = (int)g;
blueDither[x] = (int)b;
/* the error is linear in this implementation */
/* - an integer is used so round-off of errors occurs
- also clipping of the error occurs under some circumstances
- no luminance consideration
- no gamma correction
*/
red_error = red - r;
green_error = green - g;
blue_error = blue - b;
for (i=0;i<3;i++) {
/* loop through all 3 RGB channels */
switch(i) {
case 0: colorptr = (sshort *)&redDither[0];
seedptr = (sshort *)&redSeed[0];
seed2ptr = (sshort *)&redSeed2[0];
color_error = red_error;
break;
case 1: colorptr = (sshort *)&greenDither[0];
seedptr = (sshort *)&greenSeed[0];
seed2ptr = (sshort *)&greenSeed2[0];
color_error = green_error;
break;
case 2: colorptr = (sshort *)&blueDither[0];
seedptr = (sshort *)&blueSeed[0];
seed2ptr = (sshort *)&blueSeed2[0];
color_error = blue_error;
break;
}
/* diffuse the error based on the dither */
switch(dithertype)
{
case FLOYDSTEINBERG:
/*
* 7
3 5 1 (1/16)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 16) / bleed;
total_used = (color_error * 3)/bleed;
total_used += (color_error * 5)/bleed;
total_used += (color_error * 1)/bleed;
total_used += (color_error * 7)/bleed;
total_difference = total_error - total_used;
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error * 7)/bleed)+total_difference);
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)((color_error * 3)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)((color_error * 1)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error * 5)/bleed));
break;
case ATKINSON:
case ATKINSON2:
/*
* 1 1
1 1 1
1 (1/8 - reduced bleed) or (1/6 full bleed even diffusion)
*/
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)(color_error/bleed));
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed));
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed));
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
break;
default:
/* buckels dither */
/* uses the same weighting pattern as Atkinson but with full weighting */
/*
* 2 1
1 2 1
1 (1/8)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
/* random dither automatically turns error summing on in which case
rounding errors are placed at random within the atkinson pattern */
errbuf[0] = errbuf[1] = errbuf[2] = errbuf[3] = errbuf[4] = errbuf[5] = 0;
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 8) / bleed;
total_used = (color_error * 2)/bleed;
total_used += (color_error * 2)/bleed;
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_difference = total_error - total_used;
if (randomdither == 0) {
/* next pixel */
errbuf[0] = total_difference;
}
else {
/* random pixel */
idx = RandomRange(6) - 1;
errbuf[idx] = total_difference;
}
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error*2)/bleed)+errbuf[0]);
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed)+errbuf[1]);
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed)+errbuf[2]);
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error*2)/bleed)+errbuf[3]);
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed)+errbuf[4]);
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
}
}
}
/* SHR output in 320 x 200 only */
for (x=0,x1=0,x2=0;x<width;x++) {
r = (uchar)redDither[x];
g = (uchar)greenDither[x];
b = (uchar)blueDither[x];
idx = GetClosestColor(r,g,b);
setlopixel((uchar)idx,x,y);
}
}
void BrooksPicDither(int y, int width)
{
sshort *colorptr, *seedptr, *seed2ptr, color_error;
sshort red, green, blue, red_error, green_error, blue_error;
int i,x,x1,x2,y1,y2,savepalette,usepalettemethod;
int total_difference, total_error, total_used;
uchar drawcolor,r,g,b,idx;
double besttotal, thistotal;
double pictotal;
/* save original values */
/* not used yet - may not be needed */
/*
memcpy(&redSave[0],&redDither[0],width * sizeof(sshort));
memcpy(&greenSave[0],&greenDither[0],width * sizeof(sshort));
memcpy(&blueSave[0],&blueDither[0],width * sizeof(sshort));
*/
/* find the palette with the least total error */
for (y1 = 0; y1 < 16; y1++) {
thistotal = 0.0;
InitDoubleLine256Arrays(y1);
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosest256Color((uchar)red,(uchar)green,(uchar)blue,y1);
thistotal += globaldistance;
}
if (y1 == 0) {
savepalette = y2 = y1;
pictotal = thistotal;
continue;
}
if (thistotal < pictotal) {
pictotal = thistotal;
savepalette = y2 = y1;
}
}
/* set the palette with the least total error as the initial dithering palette */
memcpy(&savepalettes[y][0][0],&rgb256Arrays[y2][0][0],48);
/* find the palette with the least total error */
for (y1 = 0; y1 < 200; y1++) {
thistotal = 0.0;
InitDoubleLineArrays(y1);
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
thistotal += globaldistance;
}
if (y1 == 0) {
y2 = y1;
besttotal = thistotal;
continue;
}
if (thistotal < besttotal) {
besttotal = thistotal;
y2 = y1;
}
}
/* set the palette with the least total error as the dithering palette */
/* hopefully this decreases banding and doesn't increase banding */
if (pictotal < besttotal) {
/* if the palette for image sections is closer then use it */
memcpy(&savepalettes[y][0][0],&rgb256Arrays[savepalette][0][0],48);
InitDoubleLine256Arrays(savepalette);
usepalettemethod = 1;
if (quietmode == 0) printf("pictotal %f is greater than besttotal %f for line %d\n",pictotal,besttotal,y);
}
else {
/* if the individual line palette is closer then use that instead */
memcpy(&savepalettes[y][0][0],&rgbArrays[y2][0][0],48);
InitDoubleLineArrays(y2);
usepalettemethod = 0;
}
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
if (usepalettemethod == 1) {
drawcolor = GetClosest256Color((uchar)red,(uchar)green,(uchar)blue,savepalette);
r = rgbArrays[savepalette][drawcolor][0];
g = rgbArrays[savepalette][drawcolor][1];
b = rgbArrays[savepalette][drawcolor][2];
}
else {
drawcolor = GetClosestColor((uchar)red,(uchar)green,(uchar)blue);
r = rgbArrays[y2][drawcolor][0];
g = rgbArrays[y2][drawcolor][1];
b = rgbArrays[y2][drawcolor][2];
}
redDither[x] = (int)r;
greenDither[x] = (int)g;
blueDither[x] = (int)b;
/* the error is linear in this implementation */
/* - an integer is used so round-off of errors occurs
- also clipping of the error occurs under some circumstances
- no luminance consideration
- no gamma correction
*/
red_error = red - r;
green_error = green - g;
blue_error = blue - b;
for (i=0;i<3;i++) {
/* loop through all 3 RGB channels */
switch(i) {
case 0: colorptr = (sshort *)&redDither[0];
seedptr = (sshort *)&redSeed[0];
seed2ptr = (sshort *)&redSeed2[0];
color_error = red_error;
break;
case 1: colorptr = (sshort *)&greenDither[0];
seedptr = (sshort *)&greenSeed[0];
seed2ptr = (sshort *)&greenSeed2[0];
color_error = green_error;
break;
case 2: colorptr = (sshort *)&blueDither[0];
seedptr = (sshort *)&blueSeed[0];
seed2ptr = (sshort *)&blueSeed2[0];
color_error = blue_error;
break;
}
/* buckels dither */
/* uses the same weighting pattern as Atkinson but with full weighting */
/*
* 2 1
1 2 1
1 (1/8)
*/
/* if error summing is turned-on add the accumulated rounding error
to the next pixel */
if (errorsum == 0) {
total_difference = 0;
}
else {
total_error = (color_error * 8) / bleed;
total_used = (color_error * 2)/bleed;
total_used += (color_error * 2)/bleed;
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_used += (color_error /bleed);
total_difference = total_error - total_used;
}
/* finish this line */
AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error*2)/bleed)+total_difference);
AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed));
/* seed next line forward */
if (x>0)AdjustShortPixel(0,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x],(sshort)((color_error*2)/bleed));
AdjustShortPixel(0,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed));
/* seed furthest line forward */
AdjustShortPixel(0,(sshort *)&seed2ptr[x],(sshort)(color_error/bleed));
}
}
/* SHR output in 320 x 200 only */
for (x=0,x1=0,x2=0;x<width;x++) {
r = (uchar)redDither[x];
g = (uchar)greenDither[x];
b = (uchar)blueDither[x];
if (usepalettemethod == 1) {
/* use the line palettes if they're better */
idx = GetClosestColor(r,g,b);
}
else {
/* use the section palettes if they're better */
idx = GetClosest256Color(r,g,b,savepalette);
}
setlopixel((uchar)idx,x,y);
}
}
/* save raster oriented DHGR image fragment
file format is 5 byte header
3 - upper case ID bytes 'D' 'H' 'R'
1 byte - width in bytes (multiples of 4 bytes - 7 pixels)
1 byte - height in rasters
followed by interleaved raster data
aux raster, main raster = (width in bytes)
aux raster, main raster = (width in bytes)
aux raster, main raster = (width in bytes)
etc...
*/
/* this version of savesprite is different from Bmp2DHR's and this
program's BMP image fragment output which both work at the pixel level for x origin.
this version of savesprite takes its orgin from the beginning of a
7 pixel (4 byte) block and pads with trailing pixels to the end of 4 pixel block.
Bmp2DHR pads sprites with trailing space in a background color if the input
BMP is not as wide as the dhgr 7 pixel (4 byte) block used by DHGR. This program
outputs BMP image fragments more or less the same way. If the apple II input file
is already a DHR fragment, whatever trailing color it has will be in the BMP. But if
all we are doing is "ripping" a fragment out of a full-screen file, the BMP will
be at the pixel level.
*/
int savesprite(char *spritefile)
{
FILE *fp;
int i, c, width, packet, x, y, x1, y1, xoff;
ushort fl;
uchar *ptraux, *ptrmain, ch;
/* start at the beginning of a 7-pixel block (based on a color image fragment) */
while (fragx%7 != 0) fragx--;
/* color image fragments must finish at the end of a 7-pixel block */
/* for monochrome image fragments this is a 28-pixel block */
/* while there is no technical reason why a monochrome fragment can't be loaded
on single byte boundaries, I didn't develop A2B to work this way and I also
didn't give monochrome much thought when I developed the DHM masking file variation
in Bmp2DHR. nor did I write my cc65 loader to handle a monochrome sprite
so I am just sticking with the color sprite format for now */
while (fragwidth%7 != 0) fragwidth++;
x1 = fragx + fragwidth;
y1 = fragy + fragheight;
if (x1 > 140) {
/* cursory check */
printf("Width %d is out of range for %s!\n",fragwidth, spritefile);
return INVALID;
}
if (y1 > 192) {
/* cursory check */
printf("Height %d is out of range for %s!\n",fragheight, spritefile);
return INVALID;
}
width = (int)((fragwidth / 7) * 4); /* 4 bytes = 7 pixels */
packet = (int)width / 2;
fp = fopen(spritefile,"wb");
if (NULL == fp) {
printf("Error Opening %s for writing!\n",spritefile);
return INVALID;
}
/* 5 byte header */
/* some kind of identifier */
fputc('D',fp);
fputc('H',fp);
fputc('R',fp);
fputc((uchar)width,fp); /* width in bytes */
fputc((uchar)fragheight,fp); /* height in scanlines */
width = (int)((fragx / 7) * 4); /* 4 bytes = 7 pixels */
x1 = (int)width / 2; /* starting byte in DHGR buffer */
y1 = (int)fragy; /* starting scanline in DHGR buffer */
for (y = 0; y < fragheight; y++,y1++) {
xoff = HB[y1] + x1;
ptraux = (uchar *) &dhrbuf[xoff-0x2000];
ptrmain = (uchar *) &dhrbuf[xoff];
/* aux raster */
c = fwrite((char *)&ptraux[0],1,packet,fp);
if (c!= packet) break;
/* main raster */
c = fwrite((char *)&ptrmain[0],1,packet,fp);
if (c!= packet) break;
}
fclose(fp);
y1 = fragy + fragheight -1;
x1 = fragx + fragwidth;
if (vbmp == 1) {
/* for preview (aka VBMP output) black-out the area around the fragment */
for (y = 0;y < 192; y++) {
if (y < fragy || y > y1) {
/* blank whole line above and below fragment */
for (x = 0; x < 140; x++) dhrplot(x,y,0);
continue;
}
/* blank left pillar before fragment */
for (x = 0; x < fragx; x++) dhrplot(x,y,0);
/* blank right pillar after fragment */
for (x = x1; x < 140; x++) dhrplot(x,y,0);
}
}
if (c!=packet) {
remove(spritefile);
printf("Error Writing %s!\n",spritefile);
return INVALID;
}
printf("%s created!\n",spritefile);
return SUCCESS;
}
/* from Sheldon Simms tohgr */
uchar gsColor (uchar c)
{
/*
c &= 0xF0;
c |= (c >> 4);
return c;
*/
/**/
if (c > 0xF6) return 0xFF;
if (c > 0xE5) return 0xEE;
if (c > 0xD4) return 0xDD;
if (c > 0xC3) return 0xCC;
if (c > 0xB2) return 0xBB;
if (c > 0xA1) return 0xAA;
if (c > 0x90) return 0x99;
if (c > 0x7F) return 0x88;
if (c > 0x6E) return 0x77;
if (c > 0x5D) return 0x66;
if (c > 0x4C) return 0x55;
if (c > 0x3B) return 0x44;
if (c > 0x2A) return 0x33;
if (c > 0x19) return 0x22;
if (c > 0x08) return 0x11;
return 0;
/**/
}
/* based on the above */
uchar LegalColor[16] = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff};
/* promote monochrome bmp lines to 24-bit bmp lines */
void ReformatMonoLine(ushort packet)
{
int i,j,k;
uchar b = 0, w = 255;
memcpy(&bmpscanline2[0],&bmpscanline[0],packet);
for(i=0,j=0;i<packet;i++)
{
for(k=0;k<8;k++)
{
if (bmpscanline2[i]&msk[k]) {
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] = w;
}
else {
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] = b;
}
j+=3;
}
}
}
/* promote 16 color and 256 color bmp lines to 24-bit bmp lines */
void ReformatVGALine()
{
sshort i, j, packet;
uchar ch;
memset(&bmpscanline2[0],0,1680);
if (bmi.biBitCount == 8) {
memcpy(&bmpscanline2[0],&bmpscanline[0],bmi.biWidth);
}
else {
packet = bmi.biWidth /2;
if (bmi.biWidth%2 != 0) packet++;
for (i=0,j=0;i<packet;i++) {
ch = bmpscanline[i] >> 4;
bmpscanline2[j] = ch; j++;
ch = bmpscanline[i] & 0xf;
bmpscanline2[j] = ch; j++;
}
}
memset(&bmpscanline[0],0,1680);
for (i=0,j=0;i<bmi.biWidth;i++) {
ch = bmpscanline2[i];
bmpscanline[j] = sbmp[ch].rgbBlue; j++;
bmpscanline[j] = sbmp[ch].rgbGreen; j++;
bmpscanline[j] = sbmp[ch].rgbRed; j++;
}
}
uchar rgbColorCount[16][3];
sshort rgbColorCounter = 0;
void ClearPalette16()
{
if (shr == 320) {
/* clear the memory for the shr trailer buffer */
memset((char *)&mypic.scb[0],0,sizeof(PICFILE));
shrmode = 4096;
if (bmi.biBitCount == 8) shrmode = 256;
if (bmi.biBitCount == 1 || mono == 1) {
shrmode = 2;
/* disable brooks if set */
brooks = 0;
}
if (bmi.biBitCount == 4 || shrgrey == 1) {
shrmode = 16;
/* disable brooks if set */
brooks = 0;
}
}
else {
/* disable brooks if set */
brooks = 0;
}
if (shr == 0 || mono == 1 || bmi.biBitCount == 4 || bmi.biBitCount == 1) {
rgbColorCounter = -1;
}
else {
rgbColorCounter = 0;
memset(&rgbColorCount[0][0],0,48);
}
}
int shr2=0;
void SetPalette16()
{
sshort i, count = rgbColorCounter;
/* if not setting a 16 color conversion palette for shr conversion return immediately */
/* if the line has more than 16 colors return immediately */
if (count == -1) {
return;
}
shrmode = 16;
/* disable brooks if set */
brooks = shr2 = 0;
memset(&rgbArray[0][0],0,48);
for (i = 0; i < count; i++) {
rgbArray[i][0] = rgbColorCount[i][0];
rgbArray[i][1] = rgbColorCount[i][1];
rgbArray[i][2] = rgbColorCount[i][2];
}
}
sshort BuildPalette16(uchar r, uchar g, uchar b)
{
sshort i, count = rgbColorCounter;
/* if not building a 16 color conversion palette for shr conversion return immediately */
/* if the line has more than 16 colors return immediately */
if (count == -1) return -1;
/* if the color is already in the palette
return the index */
for (i = 0; i < count; i++) {
if (r == rgbColorCount[i][0] &&
g == rgbColorCount[i][1] &&
b == rgbColorCount[i][2]) return i;
}
/* if we are out of colors then fail */
if (count > 15) {
rgbColorCounter = -1;
return -1;
}
/* add the color to the palette
since it wasn't already in there */
rgbColorCount[count][0] = r;
rgbColorCount[count][1] = g;
rgbColorCount[count][2] = b;
/* increment the palette count to the next
available color */
i = count;
count++;
rgbColorCounter = count;
/* return the color index for the entry
that was just added */
return i;
}
/* convert 16 color and 256 color bmps to 24 bit bmps */
/* convert Monochrome bmps to 24 bit bmps */
/* convert 24 bit bmps using IIgs color thresholds from tohgr */
FILE *ReformatBMP(FILE *fp)
{
FILE *fp2;
ushort packet,outpacket,y,x,x1;
sshort count = 0;
/* 16 color palette for verbatim SHR conversion */
ClearPalette16();
/* seek past extraneous info in header if any */
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
/* align on 4 byte boundaries */
if (bmi.biBitCount == 1) {
packet = bmi.biWidth / 8;
if (bmi.biWidth%8 != 0)packet++;
}
else if (bmi.biBitCount == 4) {
packet = bmi.biWidth / 2;
if (bmi.biWidth%2 != 0)packet++;
}
else if (bmi.biBitCount == 8) {
packet = bmi.biWidth;
}
else {
packet = bmi.biWidth * 3;
}
while ((packet % 4)!=0)packet++;
if((fp2=fopen("Reformat.bmp","wb"))==NULL) {
printf("Error Opening %s for writing!\n","Reformat.bmp");
fclose(fp);
return fp;
}
outpacket = WriteDIBHeader(fp2,(ushort)bmi.biWidth,(ushort)bmi.biHeight);
if (outpacket < 1) {
fclose(fp2);
remove("Reformat.bmp");
printf("Error writing header to %s!\n","Reformat.bmp");
return fp;
}
/* 16 color palette for verbatim SHR conversion of 8 bit and 24 bit BMPs if only 16 colors */
count = rgbColorCounter;
for (y=0;y<bmi.biHeight;y++) {
fread((char *)&bmpscanline[0],1,packet,fp);
if (bmi.biBitCount == 1) {
ReformatMonoLine(packet);
}
else if (bmi.biBitCount == 4 || bmi.biBitCount == 8) {
ReformatVGALine();
}
else if (usegscolors == 1) {
/* apply IIgs threshold colors from tohgr to 24-bit scanline */
/* this is not required for lower color depths because the palette colors
have already been adjusted */
for (x = 0; x < packet; x++) bmpscanline[x] = gsColor(bmpscanline[x]);
}
if (count != -1) {
/* 16 color palette for verbatim SHR conversion of 8 bit and 24 bit BMPs if only 16 colors */
/* line color count could be here as well */
for (x = 0,x1 = 0; x < bmi.biWidth; x++,x1+=3) {
count = BuildPalette16(bmpscanline[x1+2],bmpscanline[x1+1],bmpscanline[x1]);
if (count == -1) break;
}
}
fwrite((char *)&bmpscanline[0],1,outpacket,fp2);
}
fclose(fp2);
fclose(fp);
if((fp=fopen("Reformat.bmp","rb"))==NULL) {
printf("Error Opening %s for reading!\n","Reformat.bmp");
return fp;
}
/* read the header stuff into the appropriate structures */
fread((char *)&BitMapFileHeader.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
/* 16 color palette for verbatim SHR conversion of 8 bit and 24 bit BMPs if only 16 colors */
SetPalette16();
return fp;
}
int ReadHybrid(unsigned char *basename, unsigned char *newname)
{
FILE *fp;
int packet = INVALID, y,y1,x,x1,x2,j,k,width,height,pixels,bmpversion;
int reformat = bmp3;
float hue,saturation,luminance;
char bmpfile[256], outfile[256];
uchar r,g,b,drawcolor,idx;
ushort temp;
sprintf(bmpfile,"%s.bmp",basename);
if((fp=fopen(bmpfile,"rb"))==NULL) return INVALID;
/* read the header stuff into the appropriate structures,
it's likely a bmp file */
memset(&BitMapFileHeader.bfType,0,sizeof(BITMAPFILEHEADER));
memset(&bmi.biSize,0,sizeof(BITMAPINFOHEADER));
fread((char *)&BitMapFileHeader.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV2)|| bmi.biSize == sizeof(BITMAPINFOHEADERV3)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,(unsigned)bmi.biSize,1,fp);
bmpversion = 3;
reformat = 1;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
reformat = 1;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
/* https://msdn.microsoft.com/en-us/library/windows/desktop/dd183386%28v=vs.85%29.aspx */
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
reformat = 1;
}
else {
/* perhaps some unknown version that I don't support */
/* whether I support the other versions properly is also an interesting question */
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion == 0) {
fclose(fp);
printf("BMP version of %s not recognized!\n",bmpfile);
return SUCCESS;
}
if (bmi.biCompression==BI_RGB &&
BitMapFileHeader.bfType[0] == 'B' && BitMapFileHeader.bfType[1] == 'M' &&
bmi.biPlanes==1 && (bmi.biBitCount == 24 || bmi.biBitCount == 8 || bmi.biBitCount == 4 || bmi.biBitCount == 1)) {
height = (int) bmi.biHeight;
width = (int) bmi.biWidth;
switch(width) {
case 560: if (height == 192 || height == 384) {
packet = 1680;
pixels = 1;
}
break;
case 280: if (height != 192) break;
packet = 840;
pixels = 2;
break;
case 140: if (height != 192) break;
packet = 420;
pixels = 4;
break;
}
if (packet == INVALID) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported size for DHGR file output.\nExiting!", bmpfile);
return SUCCESS;
}
}
if (packet == INVALID) {
fclose(fp);
printf("%s is not in a supported format for DHGR file output.\nExiting!", bmpfile);
return SUCCESS;
}
if (bmi.biBitCount == 8 || bmi.biBitCount == 4 || bmi.biBitCount == 1) {
memset((char *)&sbmp[0].rgbBlue,0,sizeof(RGBQUAD)*256);
if (bmi.biBitCount == 1) fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*2,1,fp);
else if (bmi.biBitCount == 4) fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*16,1,fp);
else fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*256,1,fp);
if (bmi.biBitCount == 8 && width == 560 && height == 384) {
/* Legacy AppleWin screen-capture support for backward compatibility */
if (sbmp[17].rgbRed == 255 && sbmp[17].rgbGreen == 255 && sbmp[17].rgbBlue == 254) {
fclose(fp);
/* pass-through to old routine */
return INVALID;
}
}
fp = ReformatBMP(fp);
if (NULL == fp) return SUCCESS;
reformat = 1;
}
if (mono == 1) {
/* create a black and white palette */
memset(&rgbArray[0][0],0,45);
memset(&rgbArray[15][0],255,3);
if (hsl == 1) {
puts("achromatic hsl monochrome using hue channel de-saturation.");
}
else {
puts("average rgb monochrome using color channel scaling.");
}
}
/* initialize nearest color arrays */
InitDoubleArrays();
memset(&dhrbuf[0],0,16384); /* clear write buffer */
if (dither != 0) {
/* sizeof(sshort) * 640 */
memset(&redDither[0],0,1280);
memset(&greenDither[0],0,1280);
memset(&blueDither[0],0,1280);
memset(&redSeed[0],0,1280);
memset(&greenSeed[0],0,1280);
memset(&blueSeed[0],0,1280);
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
}
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
if (dither == 0) puts("non-dithered output");
else puts("dithered output");
/* bmp's are upside-down so conversion of scanlines is in
reverse order */
for(y=0,y1=191;y<192;y++,y1--)
{
fread((char *)&bmpscanline[0],1,packet,fp);
/* unconditional merging */
if (height == 384) {
fread((char *)&bmpscanline2[0],1,packet,fp);
for (j = 0; j < packet; j++) {
temp = (ushort)bmpscanline[j];
temp += bmpscanline2[j];
if ((temp % 2)!=0) temp++;
bmpscanline[j] = (uchar) (temp/2);
}
}
if (mono == 1) {
/* work from a greyscale if mono */
for (j = 0;j < width;j+=3) {
if (hsl == 1) {
/* achromatic greyscale */
/* de-saturate using the hsl color model */
rgb2hsl(bmpscanline[j+2],bmpscanline[j+1],bmpscanline[j],&hue,&saturation,&luminance);
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] =(uchar)(float)(luminance * 255);
}
else {
/* average RGB greyscale */
temp = (ushort)bmpscanline[j];
temp += bmpscanline[j+1];
temp += bmpscanline[j+2];
while ((temp % 3)!=0) temp++;
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] = (uchar) (temp/3);
}
}
}
if (dither == 0) {
/* if not dithering use direct pixel mapping */
/* this is especially useful when a pixel graphics image has been hand-built and
requires precise positioning */
j = 0;
for (x=0,x1=0,x2=0;x<width;x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
idx = GetClosestColor(r,g,b);
if (outline == 1) {
if (idx != 0) idx = 15;
}
for (k=0;k<pixels;k++) {
drawcolor = dhrbits[idx][x1];
if (x1 > 26) x1 = 0;
else x1++;
dhrmonoplot(x2,y1,drawcolor);
x2++;
}
}
}
else
{
/* add the current line to the r,g,b line buffers used for dithering */
for (x=0, x1 = 0; x1 < width; x1++) {
b = bmpscanline[x]; x++;
g = bmpscanline[x]; x++;
r = bmpscanline[x]; x++;
/* values are already seeded from previous 2 - line(s) */
AdjustShortPixel(1,(sshort *)&redDither[x1],(sshort)r);
AdjustShortPixel(1,(sshort *)&greenDither[x1],(sshort)g);
AdjustShortPixel(1,(sshort *)&blueDither[x1],(sshort)b);
}
/* dithering */
BuckelsDither(y1,width,pixels);
/* seed next line - promote nearest forward array to
current line */
memcpy(&redDither[0],&redSeed[0],1280);
memcpy(&greenDither[0],&greenSeed[0],1280);
memcpy(&blueDither[0],&blueSeed[0],1280);
/* seed first seed - promote furthest forward array
to nearest forward array */
memcpy(&redSeed[0],&redSeed2[0],1280);
memcpy(&greenSeed[0],&greenSeed2[0],1280);
memcpy(&blueSeed[0],&blueSeed2[0],1280);
/* clear last seed - furthest forward array */
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
}
}
fclose(fp);
if (reformat == 1) {
if (bmp3 == 0) {
remove("Reformat.bmp");
}
else {
sprintf(outfile,"%s.bm3", newname);
remove(outfile);
rename("Reformat.bmp",outfile);
}
}
/* keep output files to a minimum */
/* output Apple II files in one of 3 formats */
if (frag == 1) {
/* raster oriented top-down */
/* for C programs */
sprintf(outfile,"%s.DHR",newname);
ucase((char *)&outfile[0]);
savesprite(outfile);
}
else {
if (applesoft == 0) {
if (longnames == 0)sprintf(outfile,"%s.2FC", newname);
else {
if (mono == 1)
sprintf(outfile,"%s.A2FM", newname);
else
sprintf(outfile,"%s.A2FC", newname);
}
ucase((char *)&outfile[0]);
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return SUCCESS;
}
fwrite(dhrbuf,1,16384,fp);
fclose(fp);
printf("%s created.\n", outfile);
}
else {
/* output AUX,BIN - default is A2FC */
/* the bsaved images are split into two files
the first file is loaded into aux mem */
sprintf(outfile,"%s.AUX",newname);
ucase((char *)&outfile[0]);
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return SUCCESS;
}
fwrite(dhrbuf,1,8192,fp);
fclose(fp);
printf("%s created.\n", outfile);
/* the second file is loaded into main mem */
sprintf(outfile,"%s.BIN",newname);
ucase((char *)&outfile[0]);
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
sprintf(outfile,"%s.AUX",newname);
remove(outfile);
printf("removed %s.\n", outfile);
return SUCCESS;
}
fwrite(&dhrbuf[8192],1,8192,fp);
fclose(fp);
printf("%s created.\n", outfile);
}
}
if (vbmp == 1) {
if (longnames == 0)sprintf(outfile,"%s.vmp", newname);
else sprintf(outfile,"%s_VBMP.bmp", newname);
if (WriteVBMPFile(outfile) == SUCCESS) printf("%s created.\n", outfile);
}
return SUCCESS;
}
/* Input File Helper Function - called from main() */
/* Reads an AppleWin Monochrome Screen Capture (256 color BMP) */
/* Writes an Apple II Compatible DHGR A2FC file (or optionally AUX, BIN file pair) */
/* Renames AppleWin Screen Capture from BMP to BM2 */
/* The Apple II Compatible Output is then processed normally. */
int ReadAppleWin(unsigned char *basename, unsigned char *newname)
{
FILE *fp;
int packet = INVALID, y,i,j;
char bmpfile[256], outfile[256];
if (bm2 == 1) sprintf(bmpfile,"%s.bm2",basename);
else sprintf(bmpfile,"%s.bmp",basename);
if((fp=fopen(bmpfile,"rb"))==NULL) {
if (bm2 == 1) return INVALID;
sprintf(bmpfile,"%s.bm2",basename);
if((fp=fopen(bmpfile,"rb"))==NULL) {
return INVALID;
}
bm2 = 1;
}
/* read the header stuff into the appropriate structures,
it's likely a bmp file */
memset(&BitMapFileHeader.bfType,0,sizeof(BITMAPFILEHEADER));
memset(&bmi.biSize,0,sizeof(BITMAPINFOHEADER));
fread((char *)&BitMapFileHeader.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
/* check for an applewin bmp */
if (bmi.biCompression==BI_RGB &&
BitMapFileHeader.bfType[0] == 'B' && BitMapFileHeader.bfType[1] == 'M' &&
bmi.biPlanes==1 && bmi.biBitCount == 8 &&
bmi.biWidth == 560 && bmi.biHeight == 384) {
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*256,1,fp);
if (sbmp[17].rgbRed == 255 && sbmp[17].rgbGreen == 255 && sbmp[17].rgbBlue == 254) packet = 560;
}
if(packet == INVALID){
puts(szTextTitle);
printf("%s is not a Supported 560 x 384 AppleWin Screen Capture.\n", bmpfile);
fclose(fp);
free(dhrbuf);
exit(1);
}
memset(&dhrbuf[0],0,16384); /* clear write buffer */
i = 191; /* bmp's are upside-down
so conversion of scanlines is in
reverse order */
for(y=192;y>0;y--)
{
fread((char *)&bmpscanline[0],1,560,fp);
/* skip every second scanline */
fread((char *)&bmpscanline[0],1,560,fp);
for (j=0;j<560;j++) {
if (bmpscanline[j] != 0)bmpscanline[j] = 1;
}
applewinbits(i); /* encode 7 bit mono line */
i--;
if (i< 0) break;
}
fclose(fp);
/* keep output files to a minimum */
/* output Apple II files in one of 2 formats */
if (applesoft == 0) {
if (longnames == 0)sprintf(outfile,"%s.2FC", newname);
else sprintf(outfile,"%s.A2FC", newname);
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
free(dhrbuf);
exit(1);
}
fwrite(dhrbuf,1,16384,fp);
fclose(fp);
}
else {
/* output AUX,BIN - default is A2FC */
/* the bsaved images are split into two files
the first file is loaded into aux mem */
sprintf(outfile,"%s.AUX",newname);
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
exit(1);
}
fwrite(dhrbuf,1,8192,fp);
fclose(fp);
/* the second file is loaded into main mem */
sprintf(outfile,"%s.BIN",newname);
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
free(dhrbuf);
exit(1);
}
fwrite(&dhrbuf[8192],1,8192,fp);
fclose(fp);
}
/* save a back-up of the original AppleWin file */
/* if we are not already processing the back-up */
if (bm2 == 0) {
sprintf(outfile,"%s.bm2",basename);
remove(outfile);
rename(bmpfile,outfile);
}
return SUCCESS;
}
/* External Palette File - Color Output Helper functions */
/* External palette files must be in the Lo-Res Color Order.*/
/* These were put in place primarily to support:
1. The GIMP's gpl palette file format.
2. A simple CSV (comma separated value, comma delimited) file.
There is a small possibility that some people may use the JASC palette format
primarily because they may know it and it's easy.
Support for other types of palette files is speculative. Nobody might ever use
the others but the routines were already written so no harm to include the others.
I don't expect any of this to get much use anyway.
*/
/* ------------------------------------------------------------------------ */
/* START of palette reader and helper functions adapted from Clipshop */
/* ------------------------------------------------------------------------ */
/*
squeeze redundant whitespace from lines read-in from a palette file
(leave only a single space character)
this is important if the user has created their own palette file
by hand... since they may accidentally type more than one whitespace
between RGB values...
Also, phototsyler version 2 palette file lines are fixed width,
right justified so we need to massage these for our reader...
*/
void SqueezeLine(char *ptr)
{
int idx, jdx, len;
char buf[128];
idx = 0;
while (ptr[idx] == ' ')idx++; /* remove leading whitespace */
strcpy(buf, &ptr[idx]);
jdx = 0;
ptr[jdx] = ASCIIZ;
for (idx = 0; buf[idx] != ASCIIZ; idx++) {
if (buf[idx] == 9) buf[idx] = ' '; /* no tabs please */
if (buf[idx] == ',') buf[idx] = ' '; /* no commas please */
if (buf[idx] == ' ' && buf[idx +1] == ' ')
continue;
/* truncate if any non-numeric characters */
if ((buf[idx] < '0' || buf[idx] > '9') && buf[idx] != ' ')
buf[idx] = ASCIIZ;
ptr[jdx] = buf[idx]; jdx++;
ptr[jdx] = ASCIIZ;
}
/* remove trailing whitespace...
this occurrs during parsing of photostyler */
len = strlen(ptr);
while (len > 0) {
len--;
if (ptr[len] != ' ')
break;
ptr[len] = ASCIIZ;
}
}
/* split the RGB triple from a text line read-in from an
ascii palette file. */
int ReadPaletteLine(unsigned char *ptr, unsigned char *palptr, unsigned int colordepth)
{
int red, green, blue, idx, spaces = 0;
red = atoi(ptr);
if (red < 0 || red > 255) return INVALID;
/* there must be at least 3 fields */
for (idx = 0; ptr[idx] != 0; idx++) {
if (ptr[idx] == ' ' && ptr[idx+1] >= '0' && ptr[idx+1] <= '9') {
spaces++;
switch(spaces) {
case 1:
green = atoi(&ptr[idx+1]);
if (green < 0 || green > 255) return INVALID;
break;
case 2:
blue = atoi(&ptr[idx+1]);
if (blue < 0 || blue > 255) return INVALID;
break;
}
}
}
if (spaces<2)
return INVALID;
if (colordepth == 6) {
palptr[0] = (uchar)red << 2;
palptr[1] = (uchar)green << 2;
palptr[2] = (uchar)blue << 2;
}
else {
palptr[0] = (uchar)red;
palptr[1] = (uchar)green;
palptr[2] = (uchar)blue;
}
return SUCCESS;
}
/* check version if Paintshop palette since JASC may change someday */
/* also check Aldus version although that product is old... */
/* The Top Half of NeoPaint Windows Palettes are the same as their */
/* DOS palettes so we use the 6 bit color values and handle both */
/* file types the same way... so no worry about neopaint versions. */
char *Gimp = "GIMP Palette"; /* followed by RGB values and comments */
/* NeoPaint and PaintShop Pro headers
3 lines followed by RGB values */
char *NeoPaint = "NeoPaint Palette File";
char *PaintShop = "JASC-PAL";
char *PaintShopVersion = "0100";
/* Aldus photostyler
3 lines followed by RGB values */
char *AldusPal = "CWPAL";
char *AldusClr = "CWCLR"; /* partial palettes */
char *AldusVersion = "100";
#define GENERIC 1
#define GIMP 2
#define JASC 3
#define NEO 4
#define ALDUS 5
sshort GetUserPalette(char *name)
{
FILE *fp;
char buf[128];
int cnt=16;
sshort status = INVALID;
unsigned colordepth=8,paltype=GENERIC;
fp = fopen(name,"r");
if (fp == NULL) return status;
for (;;) {
if (NULL == fgets(buf, 128, fp)) {
fclose(fp);
break;
}
nocr(buf);
SqueezeLine(buf);
/* check for some known palette types */
if (strcmp(Gimp, buf)==0) paltype = GIMP;
else if (strcmp(PaintShop, buf)==0) paltype = JASC;
else if (strcmp(NeoPaint, buf)==0) {
colordepth = 6;
paltype = NEO;
}
else if (strcmp(AldusPal, buf) == 0 || strcmp(AldusClr, buf) == 0) {
paltype = ALDUS;
}
/* if not a known type then assume it's just a simple csv */
status = SUCCESS;
switch(paltype)
{
case GENERIC: rewind(fp); break;
case JASC:
case NEO:
case ALDUS:
/* check 2 remaining header lines */
status = INVALID;
if (NULL == fgets(buf, 128, fp)) break;
nocr(buf);
SqueezeLine(buf);
if (paltype == JASC && strcmp(PaintShopVersion, buf)!=0)break;
if (paltype == ALDUS && strcmp(AldusVersion, buf) != 0)break;
if (NULL == fgets(buf, 128, fp)) break;
cnt = atoi(buf);
if (cnt < 16) break;
status = SUCCESS;
}
if (status == INVALID) break;
memset(&rgbUser[0][0],0,48);
cnt = 0;
while (fgets(buf,128,fp) != NULL) {
if (buf[0] == '#') continue;
if (strlen(buf) < 5) continue;
nocr(buf);
SqueezeLine(buf);
if (INVALID == ReadPaletteLine(buf,(uchar *)&rgbUser[cnt][0],colordepth)) continue;
cnt++;
if (cnt > 15)break;
}
break;
}
fclose(fp);
if (cnt < 15) {
printf("%s contains only %d colors!",name,cnt);
}
if (status == INVALID) {
printf("%s is not a valid palette file!",name);
}
return status;
}
/* ------------------------------------------------------------------------ */
/* END of palette reader and helper functions adapted from Clipshop */
/* ------------------------------------------------------------------------ */
/* Color Output Helper Function */
/* select conversion palette based on command-line options */
/* matches Bmp2DHR */
int GetBuiltinPalette(uchar cmd, uchar value, int palidx)
{
int i,j, singlegrey = 0;
uchar r,g,b;
unsigned temp;
if (cmd != 'P') return -1;
if (value < 48 || value > 57) return -1;
switch(palidx) {
case 16:/* HGR conversion - optional palette from tohgr */
for (i=0;i<16;i++) {
rgbArray[i][0] = hgrpal[i][0];
rgbArray[i][1] = hgrpal[i][1];
rgbArray[i][2] = hgrpal[i][2];
}
break;
case 15: for (i=0;i<16;i++) {
rgbArray[i][0] = PseudoPalette[i][0];
rgbArray[i][1] = PseudoPalette[i][1];
rgbArray[i][2] = PseudoPalette[i][2];
}
break;
/* Cybernesto */
case 14: for (i=0;i<16;i++) {
rgbArray[i][0] = Cybernesto[i][0];
rgbArray[i][1] = Cybernesto[i][1];
rgbArray[i][2] = Cybernesto[i][2];
}
singlegrey = 1;
break;
/* Jace */
case 13: for (i=0;i<16;i++) {
rgbArray[i][0] = Jace[i][0];
rgbArray[i][1] = Jace[i][1];
rgbArray[i][2] = Jace[i][2];
}
singlegrey = 1;
break;
/* Super Convert */
case 12: for (i=0;i<16;i++) {
rgbArray[i][0] = SuperConvert[i][0];
rgbArray[i][1] = SuperConvert[i][1];
rgbArray[i][2] = SuperConvert[i][2];
}
break;
/* 5 legacy palettes from BMPA2FC */
case 11: for (i=0;i<16;i++) {
rgbArray[i][0] = rgbPcxArray[i][0];
rgbArray[i][1] = rgbPcxArray[i][1];
rgbArray[i][2] = rgbPcxArray[i][2];
}
break;
case 10: for (i=0;i<16;i++) {
rgbArray[i][0] = rgbVgaArray[i][0];
rgbArray[i][1] = rgbVgaArray[i][1];
rgbArray[i][2] = rgbVgaArray[i][2];
}
break;
case 9: for (i=0;i<16;i++) {
rgbArray[i][0] = rgbXmpArray[i][0];
rgbArray[i][1] = rgbXmpArray[i][1];
rgbArray[i][2] = rgbXmpArray[i][2];
}
break;
case 8: for (i=0;i<16;i++) {
rgbArray[i][0] = rgbBmpArray[i][0];
rgbArray[i][1] = rgbBmpArray[i][1];
rgbArray[i][2] = rgbBmpArray[i][2];
}
break;
case 7: for (i=0;i<16;i++) {
rgbArray[i][0] = rgbCanvasArray[i][0];
rgbArray[i][1] = rgbCanvasArray[i][1];
rgbArray[i][2] = rgbCanvasArray[i][2];
}
break;
/* user definable palette file */
case 6:
for (i=0;i<16;i++) {
rgbArray[i][0] = rgbUser[i][0];
rgbArray[i][1] = rgbUser[i][1];
rgbArray[i][2] = rgbUser[i][2];
}
break;
/* tohgr*/
case 5: for (i=0;i<16;i++) {
rgbArray[i][0] = grpal[i][0];
rgbArray[i][1] = grpal[i][1];
rgbArray[i][2] = grpal[i][2];
}
singlegrey = 1;
break;
/* wikipedia */
case 4: for (i=0;i<16;i++) {
rgbArray[i][0] = wikipedia[i][0];
rgbArray[i][1] = wikipedia[i][1];
rgbArray[i][2] = wikipedia[i][2];
}
singlegrey = 1;
break;
case 3: for (i=0;i<16;i++) {
rgbArray[i][0] = awinnewcolors[i][0];
rgbArray[i][1] = awinnewcolors[i][1];
rgbArray[i][2] = awinnewcolors[i][2];
}
break;
case 2: for (i=0;i<16;i++) {
rgbArray[i][0] = awinoldcolors[i][0];
rgbArray[i][1] = awinoldcolors[i][1];
rgbArray[i][2] = awinoldcolors[i][2];
}
break;
case 1: for (i=0;i<16;i++) {
rgbArray[i][0] = ciderpresscolors[i][0];
rgbArray[i][1] = ciderpresscolors[i][1];
rgbArray[i][2] = ciderpresscolors[i][2];
}
break;
case 0: for (i=0;i<16;i++) {
rgbArray[i][0] = kegs32colors[i][0];
rgbArray[i][1] = kegs32colors[i][1];
rgbArray[i][2] = kegs32colors[i][2];
}
break;
default: return -1;
}
/* grey conversion may be necessary if the Apple II Image
uses light grey and dark grey. */
if (doublegrey == 1) {
if (singlegrey != 0 && rgbArray[5][0] != 0) {
/* grey 1 = 5, grey2 = 10 */
/* use the median between the existing grey and white */
/* to approximate the color distance for this palette */
temp = 255;
temp += rgbArray[5][0];
rgbArray[10][0] = rgbArray[10][1] = rgbArray[10][2] = (uchar) (temp/2);
}
}
return palidx;
}
/* Monochrome Output Helper Function */
/* decodes apple II dhgr buffer into 8 bit monochrome scanline buffer */
int applebites(int y)
{
int xoff,idx;
unsigned char *ptraux, *ptrmain, ch;
xoff = HB[y];
ptraux = (unsigned char *) &dhrbuf[xoff-0x2000];
ptrmain = (unsigned char *) &dhrbuf[xoff];
xoff = 0;
for (idx = 0; idx < 40; idx++) {
ch = ptraux[idx];
buf560[xoff] = ((ch) &1); xoff++;
buf560[xoff] = ((ch >> 1) &1); xoff++;
buf560[xoff] = ((ch >> 2) &1); xoff++;
buf560[xoff] = ((ch >> 3) &1); xoff++;
buf560[xoff] = ((ch >> 4) &1); xoff++;
buf560[xoff] = ((ch >> 5) &1); xoff++;
buf560[xoff] = ((ch >> 6) &1); xoff++;
ch = ptrmain[idx];
buf560[xoff] = ((ch) &1); xoff++;
buf560[xoff] = ((ch >> 1) &1); xoff++;
buf560[xoff] = ((ch >> 2) &1); xoff++;
buf560[xoff] = ((ch >> 3) &1); xoff++;
buf560[xoff] = ((ch >> 4) &1); xoff++;
buf560[xoff] = ((ch >> 5) &1); xoff++;
buf560[xoff] = ((ch >> 6) &1); xoff++;
}
return SUCCESS;
}
/* Monochrome Output Helper Function */
/* encodes monochrome bmp scanline from 8 bit monochrome scanline buffer */
int ibmbites()
{
int i,j,k;
unsigned char bits[8];
j=0;
for(i=0;i<70;i++)
{
for(k=0;k<8;k++)
{
bits[k] = buf560[j]; j++;
}
bmpscanline[i] = (bits[0]<<7|bits[1]<<6|bits[2]<<5|bits[3]<<4|
bits[4]<<3|bits[5]<<2|bits[6]<<1|bits[7]);
}
return SUCCESS;
}
/* Monochrome Output Helper Function */
/* save a monochrome bmp file in either supported resolution */
int save_to_bmp(unsigned char *basename, int doublepixel)
{
FILE *fp;
unsigned char outfile[256];
int y,y2;
sprintf(outfile,"%s.bmp",basename);
if (vbmp == 1) return WriteVBMPFile(outfile);
fp = fopen(outfile,"wb");
if (NULL == fp)return INVALID;
/* write header */
if (doublepixel == 1)
fwrite(mono384,1,sizeof(mono384),fp);
else
fwrite(mono192,1,sizeof(mono192),fp);
/* write scanlines */
y2 = 191;
bmpscanline[70] = bmpscanline[71] = 0xff; /* white padding */
for (y = 0; y< 192; y++) {
applebites(y2);
ibmbites();
fwrite(bmpscanline,1,72,fp);
/* double-up */
if (doublepixel == 1)
fwrite(bmpscanline,1,72,fp);
y2 -= 1;
}
return SUCCESS;
}
uchar greyoveride[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
/* creates a IIgs mode320 PIC file with a single active palette */
/* also creates a IIgs mode320 PIC file with up to 16 active palettes */
/* also creates a IIgs mode3200 Brooks PIC file with 200 active palettes */
int SHR320_Output(char *outfile)
{
FILE *fp;
sshort i,j,k;
uchar r,g,b,idx;
int x, y;
float hue, saturation,luminance;
fp = fopen(outfile,"wb");
if (NULL == fp) return INVALID;
if (shrpalettes == 200 || shrpalettes == 16) {
for (i = 0; i < 16; i++) {
if (greyoveride[i] == 1 && quietmode == 0) printf("%s will be replaced with %d percent saturated equivalent!\n",colornames[i],(int)desaturate[i]);
}
/* palette is used for preview file for both 200 palette brooks and 16 palette pic */
for (y = 0; y < 200; y++) {
for (i = 0; i < 16; i++) {
if (usegspalette == 1) {
rgbArrays[y][i][0] = gsColor(rgbArrays[y][i][0]);
rgbArrays[y][i][1] = gsColor(rgbArrays[y][i][1]);
rgbArrays[y][i][2] = gsColor(rgbArrays[y][i][2]);
}
if (greyoveride[i] != 1) continue;
/* desaturate palette color if grey over-ride is in effect */
rgb2hsl(rgbArrays[y][i][0],rgbArrays[y][i][1],rgbArrays[y][i][2],&hue,&saturation,&luminance);
saturation = (saturation * desaturate[i]) / 100;
hsl2rgb(hue,saturation,luminance,&rgbArrays[y][i][0],&rgbArrays[y][i][1],&rgbArrays[y][i][2]);
}
}
if (shrpalettes == 16) {
/* palette is used only for 16 palette pic */
for (y = 0; y < 16; y++) {
for (i = 0; i < 16; i++) {
if (usegspalette == 1) {
rgb256Arrays[y][i][0] = gsColor(rgb256Arrays[y][i][0]);
rgb256Arrays[y][i][1] = gsColor(rgb256Arrays[y][i][1]);
rgb256Arrays[y][i][2] = gsColor(rgb256Arrays[y][i][2]);
}
if (greyoveride[i] != 1) continue;
/* desaturate palette color if grey over-ride is in effect */
rgb2hsl(rgb256Arrays[y][i][0],rgbArrays[y][i][1],rgbArrays[y][i][2],&hue,&saturation,&luminance);
saturation = (saturation * desaturate[i]) / 100;
hsl2rgb(hue,saturation,luminance,&rgb256Arrays[y][i][0],&rgb256Arrays[y][i][1],&rgb256Arrays[y][i][2]);
}
}
}
}
else {
if (usegspalette == 1) {
for (i=0;i<16;i++) {
rgbArray[i][0] = gsColor(rgbArray[i][0]);
rgbArray[i][1] = gsColor(rgbArray[i][1]);
rgbArray[i][2] = gsColor(rgbArray[i][2]);
}
}
}
/* creates brooks files, and 16 palette and single-palette SHR PIC files */
switch (shrpalettes) {
case 200:
/* brooks palettes - 1 for each line */
/* in sequential order - no scb's */
for (y=0;y<200;y++) {
/* Brooks Palette Lines are in reverse order; the color value for color 15 is stored first.*/
for (i = 0,j=30; i < 16;i++,j-=2) {
/* read BGR triples */
b = (uchar) (rgbArrays[y][i][2] >> 4);
g = (uchar) (rgbArrays[y][i][1] >> 4);
r = (uchar) (rgbArrays[y][i][0] >> 4);
/* encode $0RGB motorola unsigned short (LSB, MSB) for pic file into 2 bytes */
g = (uchar) (g << 4);
mypic.pal[y][j] = (uchar) (g | b);
mypic.pal[y][j+1] = r;
}
}
break;
case 16:
/* scbs are set outside of here */
for (j=0;j<16;j++) {
/* palettes 0 - 16 */
for (i=0;i< 16;i++) {
/* read BGR triples */
b = (uchar) (rgb256Arrays[j][i][2] >> 4);
g = (uchar) (rgb256Arrays[j][i][1] >> 4);
r = (uchar) (rgb256Arrays[j][i][0] >> 4);
/* offset from char to short */
k = i*2;
/* encode $0RGB motorola unsigned short (LSB, MSB) for pic file into 2 bytes */
g = (uchar) (g << 4);
mypic.pal[j][k] = (uchar) (g | b);
mypic.pal[j][k+1] = r;
}
}
break;
case 1:
default:
/* palette 0 - currently selected DHGR palette */
/* all scbs default to zero */
for (i=0;i< 16;i++) {
/* read BGR triples */
b = (uchar) (rgbArray[i][2] >> 4);
g = (uchar) (rgbArray[i][1] >> 4);
r = (uchar) (rgbArray[i][0] >> 4);
/* offset from char to short */
k = i*2;
/* encode $0RGB motorola unsigned short (LSB, MSB) for pic file into 2 bytes */
g = (uchar) (g << 4);
mypic.pal[0][k] = (uchar) (g | b);
mypic.pal[0][k+1] = r;
}
}
/* 200 lines of image data */
memset(&bmpscanline[0],0,160);
for(y=0;y<200;y++) {
/* build a packed pixel scanline */
/* this is the same as for Windows 16 color BMPs */
for (x = 0, i=0; x < 320; x++) {
idx = getlopixel(x,y);
/* range check */
if (idx > 15)idx = 0; /* default black */
if (x%2 == 0) {
r = (uchar)idx << 4;
}
else {
bmpscanline[i] = r | (uchar) idx; i++;
}
}
fwrite((char *)&bmpscanline[0],1,160,fp);
}
if (shrpalettes == 200) {
/* brooks */
fwrite((char *)&mypic.pal[0],6400,1,fp);
}
else {
/* PIC - scbs required followed by palettes only */
fwrite((char *)&mypic.scb[0],768,1,fp);
}
fclose(fp);
return SUCCESS;
}
/* brooks output is experimental at this point */
int brooks2 = 0, brooks3 = 0, brooks4 = 0, brooks5 = 0;
int useoriginalcolors = 0;
int usesixteencolors = 0, usefourteencolors = 0, useegacolors = 0;
/* brooks4 and brooks5 arrays */
uchar linecolors[320][4];
ushort linecount[320];
double linedistance[320];
uchar linepalette[16][4]; /* r,g,b,used */
ushort linecolorindex[16]; /* index in linecolors array for color info */
uchar brooks5colors[] = {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
char pcxheader[66], pcxfile[256];
/* this solves the problem of creating image specific conversion palettes especially for
single palette output of SHR files - it takes too long to create a palette file so I just
create a second image reduced to 16 colors and use that instead */
/* this gives me image level toning which generally works pretty well */
/* reads the palette from the BMP named on the option P - command line */
/* these can be created in the GIMP or any other editor that writes a compatible BMP file */
/* alternately, reads the palette from an 8-bit PCX file created by ImageMagick */
sshort GetBmpOrPcxPalette(char *name)
{
FILE *fp;
sshort status = INVALID, i, j, bmpversion;
ulong flen;
uchar temp;
/* if we are using an imagemagick pcx for a palette, read the pcx file */
/* ImageMagick also creates version 2 and 3 BMP files that use the standard header */
/*
C:\ImageMagick-6.9.1-1>convert blondeim.bmp -colors 16 -define format:bmp3 blondeim16.bmp
C:\ImageMagick-6.9.1-1>convert blondeim.bmp -colors 16 -define format:bmp2 blondeim16.bmp
C:\ImageMagick-6.9.1-1>convert blondeim.bmp -colors 16 -define format:pcx blonde.pcx
*/
strcpy(pcxfile,name);
j=999;
for (i=0;pcxfile[i]!=(char)0;i++) {
if (pcxfile[i] == '.') j = i;
}
if (j!=999) {
pcxfile[j] = 0;
strcat(pcxfile,".pcx");
for (;;) {
fp = fopen(pcxfile,"rb");
if (fp == NULL) break;
/* get the length of the file */
fseek(fp, 0L, 2);
flen = ftell(fp) - 769;
/* avoid empty files - file must have a header and a palette */
if (flen < (ulong)128) {
fclose(fp);
break;
}
/* read the PCX header for compatibility */
rewind(fp);
fread(pcxheader,66,1,fp);
/* PCX version 5 with 8-bits per pixel, run-length encoded with one color plane */
/* PCX version 5 with 1-bit per pixel, run-length encoded with one color plane */
if (pcxheader[0] != (char)10 || pcxheader[1] != (char)5 ||
pcxheader[2] != (char)1 || pcxheader[65] != (char)1) {
fclose(fp);
break;
}
/* seek to end of file and read the first sixteen palette entries */
fseek(fp,flen,SEEK_SET);
/* first check the palette header (it must be a formfeed character) */
fread(pcxheader, 1, 1, fp);
if (pcxheader[0] != (char) 12) {
fclose(fp);
break;
}
/* read the palette */
fread(&rgbUser[0][0],48,1,fp);
fclose(fp);
status = SUCCESS;
break;
}
}
if (status == SUCCESS) return SUCCESS;
fp = fopen(name,"rb");
if (fp == NULL) {
/* try to open an m2s palette file */
j=999;
for (i=0;pcxfile[i]!=(char)0;i++) {
if (pcxfile[i] == '.') j = i;
if (pcxfile[i] == '_') {
/* allow re-rendering of Simplifly output */
if (cmpstr((char *)&pcxfile[i],"_proc.bmp") == SUCCESS) {
j = i;
break;
}
}
}
if (j==999) return status;
pcxfile[j] = 0;
strcat(pcxfile,"_palette.bmp");
fp = fopen(pcxfile,"rb");
/* if we have tried everything that makes sense, then give it up */
if (fp == NULL)return status;
}
memset(&BitMapFileHeader.bfType,0,sizeof(BITMAPFILEHEADER));
memset(&bmi.biSize,0,sizeof(BITMAPINFOHEADER));
fread((char *)&BitMapFileHeader.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
/* version 4 and version 5 BMP's use a different header than a version 3 bmp and may be incompatible */
/* but initial tests show no sign of incompatibility */
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV2)|| bmi.biSize == sizeof(BITMAPINFOHEADERV3)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,(unsigned)bmi.biSize,1,fp);
bmpversion = 3;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
/* https://msdn.microsoft.com/en-us/library/windows/desktop/dd183386%28v=vs.85%29.aspx */
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
}
else {
/* perhaps some unknown version that I don't support */
/* whether I support the other versions properly is also an interesting question */
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion != 0) {
/* use any size at all - 16-color, 256-color, and 24-bit Version 3 BMP's are all supported here */
if (bmi.biCompression==BI_RGB && BitMapFileHeader.bfType[0] == 'B' && BitMapFileHeader.bfType[1] == 'M') {
if (bmi.biBitCount == 4 || bmi.biBitCount == 8) {
status = SUCCESS;
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*16,1,fp);
for (j=0;j<16;j++) {
rgbUser[j][0] = sbmp[j].rgbRed;
rgbUser[j][1] = sbmp[j].rgbGreen;
rgbUser[j][2] = sbmp[j].rgbBlue;
}
}
else if (bmi.biBitCount == 24) {
/* last of all, check for an m2s palette file */
/* or something similar */
status = SUCCESS;
fread(&rgbUser[0][0],48,1,fp);
/* reverse the Windows BGR triples to RGB triples */
for (j=0;j<16;j++) {
temp = rgbUser[j][0];
rgbUser[j][0] = rgbUser[j][2];
rgbUser[j][2] = temp;
}
}
}
}
fclose(fp);
return status;
}
/* converts to lores and double lo-res image fragments and backgrounds (primarily targeted at game development) */
/* also converts to SHR mode320 full-screen PIC files - Single Palette, 16-Palette, and 200 Palette (Brooks) format*/
int ConvertLoResAndSHR(unsigned char *basename, unsigned char *newname)
{
FILE *fp;
int packet = INVALID, y,y1,y2,x,i,j,k,width,height,reformat = bmp3, bmpversion =0,lidx,didx,count;
int outpacket, outputwidth, outputheight, offset;
char bmpfile[256], outfile[256];
uchar r,g,b,lr,lg,lb,red,green,blue,drawcolor,idx,toneindex;
ushort temp, fl, darkest,lightest,found,unused;
float hue,saturation,luminance;
sshort jdx;
sprintf(bmpfile,"%s.bmp",basename);
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("%s cannot be opened for input!\nIt probably does not exist or the filename was mis-spelled...\nExiting!\n");
return INVALID;
}
/* read the header stuff into the appropriate structures,
it's likely a bmp file */
memset(&BitMapFileHeader.bfType,0,sizeof(BITMAPFILEHEADER));
memset(&bmi.biSize,0,sizeof(BITMAPINFOHEADER));
fread((char *)&BitMapFileHeader.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV2)|| bmi.biSize == sizeof(BITMAPINFOHEADERV3)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,(unsigned)bmi.biSize,1,fp);
bmpversion = 3;
reformat = 1;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
reformat = 1;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
reformat = 1;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
}
else {
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion == 0) {
fclose(fp);
puts("BMP version not recognized!");
printf("bmi.biSize = %d - BITMAPINFOHEADER = %d\n",bmi.biSize, sizeof(BITMAPINFOHEADER));
return INVALID;
}
if (shr == 320) {
lores = 1;
doublelores = 0;
}
else {
usegscolors = shrgrey = 0;
}
if (bmi.biCompression==BI_RGB &&
BitMapFileHeader.bfType[0] == 'B' && BitMapFileHeader.bfType[1] == 'M' &&
bmi.biPlanes==1 && (bmi.biBitCount == 24 || bmi.biBitCount == 8 || bmi.biBitCount == 4 || bmi.biBitCount == 1)) {
if (shr == 320) {
if (bmi.biWidth != 320 || bmi.biHeight != 200) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported size for SHR mode320 file output.\nExiting!\n", bmpfile);
return INVALID;
}
}
if (bmi.biWidth > 320 || bmi.biHeight > 200) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported size for LGR or DLGR file output.\nExiting!\n", bmpfile);
return INVALID;
}
outputheight = height = (int) bmi.biHeight;
outputwidth = width = (int) bmi.biWidth;
if (outputheight%2 != 0) outputheight++;
outputheight = outputheight/2;
if (doublelores == 1) {
/* double lo-res files are paired-pixels */
/* they don't need to be and I may rethink this later */
/* they must also be loaded on paired origins because auxiliary memory
uses different color mapping than main memory
*/
if (outputwidth%2 != 0) outputwidth++;
outpacket = outputwidth / 2;
}
else outpacket = outputwidth;
packet = width * 3;
while (packet%4 != 0)packet++; /* BMP scanlines align on DWORD boundaries */
}
if (packet == INVALID) {
fclose(fp);
if (shr == 320) printf("%s is not in a supported format for SHR or BROOKS file output.\nExiting!\n", bmpfile);
else printf("%s is not in a supported format for LGR or DLGR file output.\nExiting!\n", bmpfile);
return INVALID;
}
if (shr == 320 || bmi.biBitCount == 8 || bmi.biBitCount == 4 || bmi.biBitCount == 1 || usegscolors == 1) {
memset((char *)&sbmp[0].rgbBlue,0,sizeof(RGBQUAD)*256);
if (bmi.biBitCount == 1) {
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*2,1,fp);
if (shr == 320) dither = 0;
}
else if (bmi.biBitCount == 4) {
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*16,1,fp);
if (shr == 320) {
/* for SHR output, 16 color BMP's are assumed to be palette matched and are
converted using the colors they were created with */
for (j=0;j<16;j++) {
if (usegscolors == 1) {
sbmp[j].rgbRed = gsColor(sbmp[j].rgbRed);
sbmp[j].rgbGreen = gsColor(sbmp[j].rgbGreen);
sbmp[j].rgbBlue = gsColor(sbmp[j].rgbBlue);
}
rgbArray[j][0] = sbmp[j].rgbRed;
rgbArray[j][1] = sbmp[j].rgbGreen;
rgbArray[j][2] = sbmp[j].rgbBlue;
}
}
}
else if (bmi.biBitCount == 8) {
/* for SHR no special consideration is provided for 256 color BMP's in this utility.
they are assumed to be converted GIF files or the equivalent and since we
are providing only a single palette they get the same treatment as a 24-bit BMP.
a color count routine is really required here to weed-out 16 color BMP's that
have been promoted to 256 color BMP's and 24-bit BMP's.
to do more for 256 color BMP's and 24-bit BMP's, palette routines that can be supported
by the standard SHR format's 16 colors per line and 16 palettes per PIC would be needed. */
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*256,1,fp);
if (usegscolors == 1) {
for (j=0;j<256;j++) {
sbmp[j].rgbRed = gsColor(sbmp[j].rgbRed);
sbmp[j].rgbGreen = gsColor(sbmp[j].rgbGreen);
sbmp[j].rgbBlue = gsColor(sbmp[j].rgbBlue);
}
}
}
fp = ReformatBMP(fp);
if (NULL == fp) return SUCCESS;
if (shr == 1) {
if (shrmode < 17) {
puts("Fixed palette (16 color) mapping will be used for conversion.");
if (mono == 0) puts("Dithering will have no effect.");
}
}
reformat = 1;
}
if (mono == 1) {
shr2 = 0;
if (hsl == 1) {
puts("achromatic hsl monochrome using hue channel de-saturation.");
}
else {
puts("average rgb monochrome using color channel scaling.");
}
if (shrgrey == 1) {
/* for SHR mode320 mono we use 16 levels of grey */
for (j=0;j<16;j++) {
rgbArray[j][0] = rgbArray[j][1] = rgbArray[j][2] = LegalColor[j];
}
puts("16 color greyscale conversion!");
}
else {
/* for LGR and DLGR fixed palette output and SHR 320 true monochrome */
/* create a black and white palette */
memset(&rgbArray[0][0],0,45);
memset(&rgbArray[15][0],255,3);
if(shr == 320) puts("2 color monochrome conversion!");
}
}
/* hard to say how this will work out */
if (mono == 0 && shrmode > 16) {
if (fourpal == 1) {
/* group on GS colors */
for (k=0;k<16;k++) {
rgbArray[k][0] = gsColor(rgbArray[k][0]);
rgbArray[k][1] = gsColor(rgbArray[k][1]);
rgbArray[k][2] = gsColor(rgbArray[k][2]);
}
}
}
/* initialize nearest color arrays */
/* for SHR this array will be used no matter what kind of output we eventually end-up with */
/* for LGR and DLGR there is only one fixed palette of Lo-Res colors so there are no additional palettes */
InitDoubleArrays();
if (mono == 0 && shr == 320 && useimagetone == 1) {
/* build initial palette of most used colors in each of our sixteen ranges */
for (j=0;j<16;j++) {
rgbArrays[0][j][0] = rgbArray[j][0];
rgbArrays[0][j][1] = rgbArray[j][1];
rgbArrays[0][j][2] = rgbArray[j][2];
rgbUsed[0][j] = 0; /* not used yet */
rgbDistance[0][j] = 0.0; /* initialize */
}
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
for(y=0,y1=height-1;y<height;y++,y1--)
{
fread((char *)&bmpscanline[0],1,packet,fp);
if (fourplay == 1) {
/* reduce palette */
for (k=0;k<packet;k++) bmpscanline[k] = gsColor(bmpscanline[k]);
}
for (j=0, x = 0; x < width; x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
idx = GetClosestColor(r,g,b);
for (toneindex = 0; toneindex < 16;toneindex++) {
idx = GetColorDistance(r,g,b,toneindex);
if (idx != toneindex) continue;
if (rgbUsed[0][toneindex] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[0][toneindex] = 1;
rgbDistance[0][toneindex] = indexdistance;
rgbArrays[0][toneindex][0] = r;
rgbArrays[0][toneindex][1] = g;
rgbArrays[0][toneindex][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (indexdistance < rgbDistance[0][toneindex]) {
rgbDistance[0][toneindex] = indexdistance;
rgbArrays[0][toneindex][0] = r;
rgbArrays[0][toneindex][1] = g;
rgbArrays[0][toneindex][2] = b;
}
}
}
}
for (j=0;j<16;j++) {
rgbArray[j][0] = rgbArrays[0][j][0];
rgbArray[j][1] = rgbArrays[0][j][1];
rgbArray[j][2] = rgbArrays[0][j][2];
}
InitDoubleArrays();
/* end of image tone */
}
if (shr2 != 0) {
brooks = 0;
/* build initial palette of most used colors in each of our sixteen ranges */
for (j=0;j<16;j++) {
rgbArrays[0][j][0] = rgbArray[j][0];
rgbArrays[0][j][1] = rgbArray[j][1];
rgbArrays[0][j][2] = rgbArray[j][2];
rgbUsed[0][j] = 0; /* not used yet */
rgbDistance[0][j] = 0.0; /* initialize */
}
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
for(y=0,y1=height-1;y<height;y++,y1--)
{
fread((char *)&bmpscanline[0],1,packet,fp);
if (fourplay == 1) {
/* reduce palette */
for (k=0;k<packet;k++) bmpscanline[k] = gsColor(bmpscanline[k]);
}
for (j=0, x = 0; x < width; x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
idx = GetClosestColor(r,g,b);
if (rgbUsed[0][idx] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[0][idx] = 1;
rgbDistance[0][idx] = globaldistance;
rgbArrays[0][idx][0] = r;
rgbArrays[0][idx][1] = g;
rgbArrays[0][idx][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (globaldistance < rgbDistance[0][idx]) {
rgbDistance[0][idx] = globaldistance;
rgbArrays[0][idx][0] = r;
rgbArrays[0][idx][1] = g;
rgbArrays[0][idx][2] = b;
}
}
}
for (j=0;j<16;j++) {
if (useimagetone == 0) {
if (rgbArray[j][0] == rgbArrays[0][j][0] &&
rgbArray[j][1] == rgbArrays[0][j][1] &&
rgbArray[j][2] == rgbArrays[0][j][2]) greyoveride[j] = 1;
}
rgbArray[j][0] = rgbArrays[0][j][0];
rgbArray[j][1] = rgbArrays[0][j][1];
rgbArray[j][2] = rgbArrays[0][j][2];
}
InitDoubleArrays();
/* end of shr2 */
}
/* if we are converting to SHR we now must decide on a conversion plan */
/* for SHR since all input is now 24-bit we need to build some palettes */
if (brooks != 0) {
if (useoriginalcolors == 0 && quietmode == 0 && mix256 == 0) {
puts("Initial Palette Color Values:");
for (i=0;i<16;i++) {
printf("Index %-2d: %-3d %-3d %-3d %s\n",i,rgbArray[i][0],rgbArray[i][1],rgbArray[i][2],colornames[i]);
}
}
shrpalettes = 200;
/* set initial values for conversion palettes */
/* use color space of currently selected DHGR palette for color grouping of 16 colors */
for (i=0;i<shrpalettes;i++) {
for (j=0;j<16;j++) {
if (useoriginalcolors == 0) {
rgbArrays[i][j][0] = rgbArray[j][0];
rgbArrays[i][j][1] = rgbArray[j][1];
rgbArrays[i][j][2] = rgbArray[j][2];
rgbUsed[i][j] = 0; /* not used */
rgbDistance[i][j] = 0.0; /* initialize */
}
else {
if (usefourteencolors == 1 && j==15) {
rgbArrays[i][j][0] = rgbArrays[i][j][1] = rgbArrays[i][j][2] = 255;
}
else {
rgbArrays[i][j][0] = rgbArrays[i][j][1] = rgbArrays[i][j][2] = 0;
}
}
}
}
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
for(y=0,y1=height-1;y<height;y++,y1--)
{
fread((char *)&bmpscanline[0],1,packet,fp);
if (fourplay == 1) {
/* reduce palette */
for (k=0;k<packet;k++) bmpscanline[k] = gsColor(bmpscanline[k]);
}
if (brooks4 == 1 || brooks5 == 1) {
/* this is a combination of population and original closest color */
/* step 1 */
/* first get closest color by population for 16 indexes */
/* this is different than brooks, brooks2, and brooks3 (grey replacement) which do not consider population */
count = 0;
/* accumulate all original colors into line array */
/* this a large working palette for the current line of all the original colors and their values */
for (j=0, x = 0, count = 0; x < width; x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
found = 0;
for (i=0;i<count;i++) {
/* compare to original color value */
if (linecolors[i][0] == r && linecolors[i][1] == g && linecolors[i][2] == b) {
/* if color matches, update color count for this index entry */
linecount[i] += 1;
found = 1;
break;
}
}
if (found != 1) {
/* if no matching colors found in line colors add a new entry */
/* store r,g,b, color index and color distance */
linecolors[count][0] = r;
linecolors[count][1] = g;
linecolors[count][2] = b;
linecolors[count][3] = GetClosestColor(r,g,b);
linedistance[count] = globaldistance;
linecount[count] = 1;
count++;
}
}
/* step 2 */
/* build initial palette of most used colors in each of our sixteen ranges */
memset(&linepalette[0][0],0,64);
memset(&linecolorindex[0],0,32);
for (j = 0; j < count; j++) {
idx = linecolors[j][3];
if (linepalette[idx][3] == 1) {
/* index is already in use so update if count is higher */
i = linecolorindex[idx];
/* reset index used to zero to force update if count is higher */
if (linecount[j] > linecount[i]) linepalette[idx][3] = 0;
}
if (linepalette[idx][3] == 0) {
/* initial palette entry */
linepalette[idx][0] = linecolors[j][0];
linepalette[idx][1] = linecolors[j][1];
linepalette[idx][2] = linecolors[j][2];
linepalette[idx][3] = 1; /* mark palette index as used */
linecolorindex[idx] = j; /* store the linecolors index */
continue;
}
}
/* mark used colors in the linecolors array */
for (idx=0,unused=0;idx<16;idx++) {
if (linepalette[idx][3] == 0) {
unused++;
continue;
}
brooks5colors[idx] = 1;
i = linecolorindex[idx];
linecount[i] = 0;
}
if (unused > 0) {
for (idx=0;idx<16;idx++) {
if (linepalette[idx][3] == 1) continue;
found = x = 0;
if (brooks5 == 1) x = 1;
for (j = 0; j < count; j++) {
if (linecount[j] > x) {
if (brooks5 == 1) {
i = linecolors[j][3];
/* restrict greys, blacks and whites to one entry */
if (i == LOBLACK || i == LOWHITE ||
i == LOGRAY || i == LOGREY) continue;
if (brooks5colors[i] < 1) continue;
}
i = j;
x = linecount[j];
found = 1;
}
}
if (found == 1) {
j = linecolors[i][3];
brooks5colors[j] = 0;
linepalette[idx][0] = linecolors[i][0];
linepalette[idx][1] = linecolors[i][1];
linepalette[idx][2] = linecolors[i][2];
linepalette[idx][3] = 1; /* mark palette index as used */
linecolorindex[idx] = i; /* store the linecolors index */
linecount[i] = 0; /* mark color used */
unused--;
}
}
}
/* if we still need colors just add them */
if (brooks5 == 1 && unused > 0) {
for (idx=0;idx<16;idx++) {
if (linepalette[idx][3] == 1) continue;
found = x = 0;
for (j = 0; j < count; j++) {
if (linecount[j] > x) {
i = j;
x = linecount[j];
found = 1;
}
}
if (found == 1) {
j = linecolors[i][3];
brooks5colors[j] = 0;
linepalette[idx][0] = linecolors[i][0];
linepalette[idx][1] = linecolors[i][1];
linepalette[idx][2] = linecolors[i][2];
linepalette[idx][3] = 1; /* mark palette index as used */
linecolorindex[idx] = i; /* store the linecolors index */
linecount[i] = 0; /* mark color used */
unused--;
}
}
}
for (idx=0;idx<16;idx++) {
rgbArrays[y1][idx][0] = linepalette[idx][0];
rgbArrays[y1][idx][1] = linepalette[idx][1];
rgbArrays[y1][idx][2] = linepalette[idx][2];
}
continue;
}
if (useoriginalcolors == 1) {
count = 0;
for (j=0, x = 0; x < width; x++) {
/* store original colors and working colors */
blue = b = bmpscanline[j]; j++;
green = g = bmpscanline[j]; j++;
red = r = bmpscanline[j]; j++;
/* if we are using a threshold color, assign it to our working colors */
if (useegacolors == 1) {
r = sethold(r,rhold);
g = sethold(g,rhold);
b = sethold(b,rhold);
}
found = 0;
if (usefourteencolors == 1) {
/* force black and white into palette */
idx = GetClosestColor(r,g,b);
if (idx == 15) r = g = b = red = blue = green = 255;
else if (idx == 0) r = g = b = red = green = blue = 0;
}
for (i=0;i<count;i++) {
if (useegacolors == 1) {
/* compare on threshold colors */
lr = sethold(linecolors[i][0],rhold);
lg = sethold(linecolors[i][1],ghold);
lb = sethold(linecolors[i][2],bhold);
}
else {
/* compare on full original color value */
lr = linecolors[i][0];
lg = linecolors[i][1];
lb = linecolors[i][2];
}
if (lr == r && lg == g && lb == b) {
if (useegacolors == 1) {
idx = GetClosestColor(red,green,blue);
if (globaldistance < linedistance[i]) {
/* update rgb values if closer to current palette than first values added */
linecolors[count][0] = red;
linecolors[count][1] = green;
linecolors[count][2] = blue;
linecolors[count][3] = GetClosestColor(red,green,blue);
linedistance[count] = globaldistance;
}
}
linecount[i] += 1;
found = 1;
break;
}
}
if (found != 1) {
linecolors[count][0] = red;
linecolors[count][1] = green;
linecolors[count][2] = blue;
linecolors[count][3] = GetClosestColor(red,green,blue);
linedistance[count] = globaldistance;
linecount[count] = 1;
count++;
}
}
if (usesixteencolors == 0 && usefourteencolors == 0) {
/* get darkest original color */
/* get lightest original color */
darkest = (ushort)linecolors[0][0];
darkest += linecolors[0][1];
darkest += linecolors[0][2];
lightest = darkest;
lidx = didx = 0;
for (i=1;i<count;i++) {
/* walk through the colors and find darkest and lightest colors */
temp = (ushort)linecolors[i][0];
temp += linecolors[i][1];
temp += linecolors[i][2];
if (temp < darkest) {
darkest = temp;
didx = i;
}
else if (temp > lightest) {
lightest = temp;
lidx = i;
}
}
for (i=0;i<15;i++) {
/* set the first 15 colors to the darkest color initially */
rgbArrays[y1][i][0] = linecolors[didx][0];
rgbArrays[y1][i][1] = linecolors[didx][1];
rgbArrays[y1][i][2] = linecolors[didx][2];
}
/* set the last color to the lightest color initially */
rgbArrays[y1][15][0] = linecolors[lidx][0];
rgbArrays[y1][15][1] = linecolors[lidx][1];
rgbArrays[y1][15][2] = linecolors[lidx][2];
linecount[lidx] = linecount[didx] = 0;
}
/* add up to 14 (or 16) most-used colors between the darkest and lightest to this palette */
if (usesixteencolors == 1) {
j = 0;
lidx = 15;
}
else {
j = 1;
lidx = 14;
}
for (i = width;i > -1; i--) {
for (x=0;x<count;x++) {
if (linecount[x] == i) {
linecount[x] = 0;
if (usefourteencolors == 1) {
/* if palette is already preset for black and white no point in adding again */
if (linecolors[x][0] == 0 && linecolors[x][1] == 0 && linecolors[x][2] == 0) continue;
if (linecolors[x][0] == 255 && linecolors[x][1] == 255 && linecolors[x][2] == 255) continue;
}
rgbArrays[y1][j][0] = linecolors[x][0];
rgbArrays[y1][j][1] = linecolors[x][1];
rgbArrays[y1][j][2] = linecolors[x][2];
j++;
if (j>14) break;
}
}
if (j>14) break;
}
continue;
/* original colors continues the loop */
}
for (j=0, x = 0; x < width; x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
idx = GetClosestColor(r,g,b);
if (rgbUsed[y1][idx] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[y1][idx] = 1;
rgbDistance[y1][idx] = globaldistance;
rgbArrays[y1][idx][0] = r;
rgbArrays[y1][idx][1] = g;
rgbArrays[y1][idx][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (globaldistance < rgbDistance[y1][idx]) {
rgbDistance[y1][idx] = globaldistance;
rgbArrays[y1][idx][0] = r;
rgbArrays[y1][idx][1] = g;
rgbArrays[y1][idx][2] = b;
}
}
}
/* fill-in actual image colors in the unused range */
if (useoriginalcolors == 0 && (brooks2 == 1 || brooks3 == 1)) {
/* go through each scanline in the image looking for palette entries that have not been set */
/* the theory is that colors from the original image will match more closely than the colors
from the currently selected DHGR palette */
/* go through these in index order */
for (i=0;i<16;i++) {
/* found = 1; */
for (y = 0; y < 200; y++) {
/* if no image color check the other lines */
if (rgbUsed[y][i] == 0) {
found = 1;
y1 = y2 = y;
for (;;)
{
/* expand upwards and downwards looking for an actual image color */
y1--;
y2++;
/* if we have expanded past the bottom and top of the image then give-up
for all the scanlines because this means that this color is not in
the image */
if (y1 < 0 && y2 > 200) {
found = 0;
break;
}
/* if one of the lines above has this palette entry then use it */
if (y1 > -1 && rgbUsed[y1][i] != 0) {
rgbUsed[y][i] = 1;
rgbArrays[y][i][0]=rgbArrays[y1][i][0];
rgbArrays[y][i][1]=rgbArrays[y1][i][1];
rgbArrays[y][i][2]=rgbArrays[y1][i][2];
break;
}
/* if one of the lines below has this palette entry then use it */
if (y2 < 200 && rgbUsed[y2][i] != 0) {
rgbUsed[y][i] = 1;
rgbArrays[y][i][0]=rgbArrays[y2][i][0];
rgbArrays[y][i][1]=rgbArrays[y2][i][1];
rgbArrays[y][i][2]=rgbArrays[y2][i][2];
break;
}
}
}
if (found == 0) {
if (brooks3 == 1 || brooks2 == 1) {
if (useimagetone == 1) break;
if (brooks3 == 1) greyoveride[i] = 1;
break;
}
}
}
}
}
if (shr256 == 1) {
shrpalettes = 16;
mix256 = 0;
for (i=0;i<shrpalettes;i++) {
for (j=0;j<16;j++) {
rgb256Arrays[i][j][0] = rgbArray[j][0];
rgb256Arrays[i][j][1] = rgbArray[j][1];
rgb256Arrays[i][j][2] = rgbArray[j][2];
rgbUsed[i][j] = 0; /* not used */
rgbDistance[i][j] = 0.0; /* initialize */
}
}
/* first pass */
for (i = 0, y = 0; i < 8; i++, y+=25) {
y1 = y;
y2 = y + 13;
j = i * 2;
/* temporary - hard-coded palette assignment */
/* temporary ends */
/* palette for 13 lines */
for (x = y1; x < y2; x++) {
for (idx = 0; idx < 16; idx++) {
r = rgbArrays[x][idx][0];
g = rgbArrays[x][idx][1];
b = rgbArrays[x][idx][2];
drawcolor = GetClosestColor(r,g,b);
if (rgbUsed[j][drawcolor] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[j][drawcolor] = 1;
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (globaldistance < rgbDistance[j][drawcolor]) {
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
}
}
}
for (idx = 0; idx < 16; idx++) {
/* put new palette back in old palette */
rgbArrays[y1][idx][0] = rgb256Arrays[j][idx][0];
rgbArrays[y1][idx][1] = rgb256Arrays[j][idx][1];
rgbArrays[y1][idx][2] = rgb256Arrays[j][idx][2];
}
/* palette for 12 lines */
y1 = y + 13;
y2 = y + 25;
j = (i*2) + 1;
for (x = y1; x < y2; x++) {
for (idx = 0; idx < 16; idx++) {
r = rgbArrays[x][idx][0];
g = rgbArrays[x][idx][1];
b = rgbArrays[x][idx][2];
drawcolor = GetClosestColor(r,g,b);
if (rgbUsed[j][drawcolor] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[j][drawcolor] = 1;
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (globaldistance < rgbDistance[j][drawcolor]) {
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
}
}
}
for (idx = 0; idx < 16; idx++) {
/* put new palette back in old palette */
rgbArrays[y1][idx][0] = rgb256Arrays[j][idx][0];
rgbArrays[y1][idx][1] = rgb256Arrays[j][idx][1];
rgbArrays[y1][idx][2] = rgb256Arrays[j][idx][2];
}
}
/* second pass */
for (i = 0, y = 0; i < 8; i++, y+=25) {
y1 = y;
y2 = y + 13;
j = i * 2;
/* temporary - hard-coded palette assignment */
for (idx = 0; idx < 16; idx++) {
/* palettes from 25 scanlines */
/* every 1st of 13 palettes */
rgb256Arrays[j][idx][0] = rgbArrays[y1][idx][0];
rgb256Arrays[j][idx][1] = rgbArrays[y1][idx][1];
rgb256Arrays[j][idx][2] = rgbArrays[y1][idx][2];
/* every 1st of 12 palettes */
rgb256Arrays[j+1][idx][0] = rgbArrays[y2][idx][0];
rgb256Arrays[j+1][idx][1] = rgbArrays[y2][idx][1];
rgb256Arrays[j+1][idx][2] = rgbArrays[y2][idx][2];
}
/* temporary ends */
/* palette for 13 lines */
for (x = y1; x < y2; x++) {
mypic.scb[x] = j;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[j][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[j][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[j][idx][2];
}
}
/* palettte for 12 lines */
y1 = y + 13;
y2 = y + 25;
j = (i*2) + 1;
for (x = y1; x < y2; x++) {
mypic.scb[x] = j;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[j][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[j][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[j][idx][2];
}
}
}
} /* shr256 ends */
else if (mix256 == 1) {
if (quietmode == 0) puts("mixing colors");
/* if we are also adding zone palettes to the mix then we build these here */
/* this just gives us some additional palettes to work with */
/* I have no clear thoughts on whether this will work better or worse */
for (i=0;i<16;i++) {
/* just initialize with the current palette values */
for (j=0;j<16;j++) {
rgb256Arrays[i][j][0] = rgbArray[j][0];
rgb256Arrays[i][j][1] = rgbArray[j][1];
rgb256Arrays[i][j][2] = rgbArray[j][2];
rgbUsed[i][j] = 0; /* not used */
rgbDistance[i][j] = 0.0; /* initialize */
}
}
/* only one pass is used - we don't use scbs for brooks */
/* and we also need to keep the brooks palette intact */
/* like the initial 16 - paletttes used by plain old SHR just divides the image into
sections and builds composite palettes that more closely match our initial palettes */
for (i = 0, y = 0; i < 8; i++, y+=25) {
y1 = y;
y2 = y + 13;
j = i * 2;
/* palette for 13 lines */
for (x = y1; x < y2; x++) {
for (idx = 0; idx < 16; idx++) {
r = rgbArrays[x][idx][0];
g = rgbArrays[x][idx][1];
b = rgbArrays[x][idx][2];
drawcolor = GetClosestColor(r,g,b);
if (rgbUsed[j][drawcolor] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[j][drawcolor] = 1;
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (globaldistance < rgbDistance[j][drawcolor]) {
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
}
}
}
/* palettte for 12 lines */
y1 = y + 13;
y2 = y + 25;
j = (i*2) + 1;
for (x = y1; x < y2; x++) {
for (idx = 0; idx < 16; idx++) {
r = rgbArrays[x][idx][0];
g = rgbArrays[x][idx][1];
b = rgbArrays[x][idx][2];
drawcolor = GetClosestColor(r,g,b);
if (rgbUsed[j][drawcolor] == 0) {
/* set initial value if this palette index has been used */
rgbUsed[j][drawcolor] = 1;
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
continue;
}
/* if the new color is closer to the currently selected palette color
than the previously stored color, then replace the previously stored
color with the new color */
if (globaldistance < rgbDistance[j][drawcolor]) {
rgbDistance[j][drawcolor] = globaldistance;
rgb256Arrays[j][drawcolor][0] = r;
rgb256Arrays[j][drawcolor][1] = g;
rgb256Arrays[j][drawcolor][2] = b;
}
}
}
}
} /* mix256 ends */
}
memset(&dhrbuf[0],0,32000); /* clear write buffer */
if (dither != 0) {
/* sizeof(sshort) * 640 */
memset(&redDither[0],0,1280);
memset(&greenDither[0],0,1280);
memset(&blueDither[0],0,1280);
memset(&redSeed[0],0,1280);
memset(&greenSeed[0],0,1280);
memset(&blueSeed[0],0,1280);
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
}
else {
usepalettedistance = 0;
}
/* seek to beginning of input file and process */
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
if (dither == 0) puts("non-dithered output");
else puts("dithered output");
/* bmp's are upside-down so conversion of scanlines is in
reverse order */
for(y=0,y1=height-1;y<height;y++,y1--)
{
fread((char *)&bmpscanline[0],1,packet,fp);
if (mono == 1) {
/* work from a greyscale if mono - LGR and DLGR have no mono */
/* SHR can have either mono or 16 levels of grey in dithered or non-dithered output */
for (x = 0,j=0;x<width;x++,j+=3) {
if (hsl == 1) {
/* achromatic greyscale */
/* de-saturate using the hsl color model */
rgb2hsl(bmpscanline[j+2],bmpscanline[j+1],bmpscanline[j],&hue,&saturation,&luminance);
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] =(uchar)(float)(luminance * 255);
}
else {
/* average RGB greyscale */
temp = (ushort)bmpscanline[j];
temp += bmpscanline[j+1];
temp += bmpscanline[j+2];
while ((temp % 3)!=0) temp++;
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] = (uchar) (temp/3);
}
}
}
else if (shrpalettes > 1) {
/* if doing brooks or 256 colors set the palette for this line */
InitDoubleLineArrays(y1);
}
if (dither == 0) {
/* if not dithering use direct pixel mapping */
for (x=0,j=0;x<width;x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
idx = GetClosestColor(r,g,b);
setlopixel((uchar)idx,x,y1);
}
}
else
{
/* add the current line to the r,g,b line buffers used for dithering */
for (j=0, x = 0; x < width; x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
/* values are already seeded from previous 2 - line(s) */
/* the idea here is to add a full value to whatever bleed values have been added */
AdjustShortPixel(1,(sshort *)&redDither[x],(sshort)r);
AdjustShortPixel(1,(sshort *)&greenDither[x],(sshort)g);
AdjustShortPixel(1,(sshort *)&blueDither[x],(sshort)b);
}
if (usepalettedistance == 1 && shrpalettes > 1) {
if (shrpalettes == 200) {
if (mix256 == 1) BrooksPicDither(y1,width);
else BrooksDither(y1,width);
}
else {
PicDither(y1,width);
}
}
else {
/* dithering - "pixels" is just a "placeholder" */
BuckelsDither(y1,width,1);
}
/* seed next line - promote nearest forward array to
current line */
memcpy(&redDither[0],&redSeed[0],1280);
memcpy(&greenDither[0],&greenSeed[0],1280);
memcpy(&blueDither[0],&blueSeed[0],1280);
/* seed first seed - promote furthest forward array
to nearest forward array */
memcpy(&redSeed[0],&redSeed2[0],1280);
memcpy(&greenSeed[0],&greenSeed2[0],1280);
memcpy(&blueSeed[0],&blueSeed2[0],1280);
/* clear last seed - furthest forward array */
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
}
}
fclose(fp);
if (reformat == 1) {
if (bmp3 == 0) {
remove("Reformat.bmp");
}
else {
sprintf(outfile,"%s.bm3", newname);
remove(outfile);
rename("Reformat.bmp",outfile);
}
}
if (shr == 0) {
/* create output files */
/* stay with old full-screen naming convention for these */
if (doublelores == 1) {
if (outputwidth == 80 && outputheight == 20) sprintf(outfile,"%s.DTO", newname);
else if (outputwidth == 80 && outputheight == 24) sprintf(outfile,"%s.DLO", newname);
else sprintf(outfile,"%s.DLR", newname);
}
else {
if (outputwidth == 40 && outputheight == 20) sprintf(outfile,"%s.STO", newname);
else if (outputwidth == 40 && outputheight == 24) sprintf(outfile,"%s.SLO", newname);
else sprintf(outfile,"%s.SLR", newname);
}
ucase((char *)&outfile[0]);
if (longnames == 0) tags = 0;
if (tags == 1) {
strcat(outfile,"#060400");
dosheader = 0;
}
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return INVALID;
}
if (dosheader == 1) {
fl = (ushort) outputwidth;
fl *= outputheight;
if (outputwidth < 256) fl += 2;
else fl += 7;
WriteDosHeader(fp,fl,1024);
}
/* for most files a 2 byte header is all that is needed */
/* these are exactly the same format as SLO,STO and DLO,DTO */
if (outputwidth < 256) {
fputc((uchar)outputwidth,fp); /* width in bytes */
fputc((uchar)outputheight,fp); /* height in scanlines */
}
else {
/* for files wider than 255 bytes */
/* 7 byte header */
/* 3 - bytes - some kind of identifier */
if (doublelores == 1) fputc('D',fp);
else fputc('S',fp);
fputc('L',fp);
fputc('R',fp);
WriteDosHeader(fp,(ushort)outputwidth,(ushort)outputheight);
}
/* raster oriented top-down */
for (y = 0; y < outputheight;y++) {
offset = y * 320;
fwrite(&dhrbuf[offset],1,outpacket,fp);
if (doublelores == 1) fwrite(&dhrbuf[offset+160],1,outpacket,fp);
}
fclose(fp);
printf("%s created.\n", outfile);
}
else if (shr == 320) {
if (usepalettedistance == 1 && shrpalettes > 1) {
/* if we shuffled the palettes based on the best total distance then we need
to put our new palettes in place before writing our output file(s) */
for (y= 0;y<200;y++) {
memcpy(&rgbArrays[y][0][0],&savepalettes[y][0][0],48);
}
memcpy(&mypic.scb[0],&savescb[0],200);
}
/* SHR output file naming convention for the occasion */
/* using different extensions to tell them apart */
if (mono == 1) {
if (shrgrey == 1) sprintf(outfile,"%s.SHG", newname);
else sprintf(outfile,"%s.SHM", newname);
}
else {
if (brooks == 0) sprintf(outfile,"%s.SHR", newname);
else {
if (shr256 == 1) sprintf(outfile,"%s.SH2", newname);
else sprintf(outfile,"%s.SH3", newname);
}
}
ucase((char *)&outfile[0]);
if (longnames == 0) tags = 0;
/* Ciderpress File Attribute Preservation Tags */
if (tags == 1){
if (brooks == 0) strcat(outfile,"#C10000");
else {
if (shr256 == 1) strcat(outfile,"#C10000");
else strcat(outfile,"#C10002");
}
}
if (SHR320_Output(outfile) != SUCCESS) {
printf("%s cannot be created.\n", outfile);
return INVALID;
}
printf("%s created.\n", outfile);
}
/* vbmp output is not available for LGR, DLGR, or SHR output because
vbmp works only with DHGR color and monochrome and HGR monochrome.
but I just use the same "option V" for preview output or M2S output here */
if (vbmp == 1) {
/* Magick2SHR palette output and Magick2SHR naming convention for round-trip editing */
/* this applies only to 16 palette and brooks output */
/* single palette files are output as 16-color BMPs and can be edited and reprocessed without using M2S */
if (m2s == 1 && shrpalettes < 2) m2s = 0;
if (m2s == 0) {
/* for MS-DOS output we use short (and different) names */
if (longnames == 0)sprintf(outfile,"%s.pmp", newname);
else sprintf(outfile,"%s_Preview.bmp", newname);
}
else {
if (longnames == 0)sprintf(outfile,"%s.dib", newname);
else sprintf(outfile,"%s_palette.bmp", newname);
/* open M2S palette file */
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return INVALID;
}
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)16;
mybmp.bmi.biHeight = (ulong)shrpalettes;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
outpacket = 48;
mybmp.bmi.biSizeImage = (ulong)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the M2S palette BMP */
fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
memset(&bmpscanline[0],0,outpacket);
/* write a BMP scanline for each SHR palette entry */
for(y=0,y1=shrpalettes-1;y<shrpalettes;y++,y1--) {
/* build a scanline of each palette entry */
for (x = 0, j=0; x < 16; x++) {
if (shrpalettes == 200) {
/* brooks palettes */
bmpscanline[j] = rgbArrays[y1][x][2]; j++;
bmpscanline[j] = rgbArrays[y1][x][1]; j++;
bmpscanline[j] = rgbArrays[y1][x][0]; j++;
}
else {
/* pic palettes */
bmpscanline[j] = rgb256Arrays[y1][x][2]; j++;
bmpscanline[j] = rgb256Arrays[y1][x][1]; j++;
bmpscanline[j] = rgb256Arrays[y1][x][0]; j++;
}
}
/* write a scanline of each palette entry */
fwrite((char *)&bmpscanline[0],1,outpacket,fp);
}
fclose(fp);
printf("%s created.\n", outfile);
/* now that the M2S palette file is written, the M2S proc file is exactly the same as the SHR preview file
except that the naming convention follows M2S naming unless in MS-DOS */
/* for MS-DOS output we use short names - MS-DOS users will need to rename the pmp file to a bmp file */
if (longnames == 0)sprintf(outfile,"%s.pmp", newname);
else sprintf(outfile,"%s_proc.bmp", newname);
}
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return INVALID;
}
/* monochrome preview is promoted to 16 color BMP format */
if (shrpalettes == 1) {
/* 16 color BMP preview */
outpacket = width/2;
}
else {
/* 256 color preview is promoted to */
/* 24-bit BMP format */
outpacket = width * 3;
}
if (width%2 != 0) outpacket++;
while (outpacket%4 != 0)outpacket++;
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)width;
mybmp.bmi.biHeight = (ulong)height;
mybmp.bmi.biPlanes = 1;
if (shrpalettes == 1) mybmp.bmi.biBitCount = 4;
else mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
mybmp.bmi.biSizeImage = (ulong)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
if (shrpalettes == 1) {
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER) + sizeof(RGBQUAD) * 16;
}
else {
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
}
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the output BMP */
fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
if (shrpalettes == 1) {
/* 4 bit bmp */
for (j=0;j<16;j++) {
sbmp[j].rgbRed = rgbArray[j][0];
sbmp[j].rgbGreen = rgbArray[j][1];
sbmp[j].rgbBlue = rgbArray[j][2];
sbmp[j].rgbReserved = 0;
}
/* write the palette for the output bmp */
fwrite((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*16,1,fp);
}
memset(&bmpscanline[0],0,outpacket);
for(y=0,y1=height-1;y<height;y++,y1--) {
/* build a packed scanline */
/* this is the same as for Apple IIgs SHR mode
providing of course we use the same palette */
for (x = 0, j=0; x < width; x++) {
idx = getlopixel(x,y1);
/* range check */
if (idx > 15)idx = 0; /* default black */
if (shrpalettes != 1) {
/* brooks palettes are scanline oriented */
/* the 256 color palette has been copied to the brooks palette */
bmpscanline[j] = rgbArrays[y1][idx][2]; j++;
bmpscanline[j] = rgbArrays[y1][idx][1]; j++;
bmpscanline[j] = rgbArrays[y1][idx][0]; j++;
}
else {
if (x%2 == 0) {
r = (uchar)idx << 4;
bmpscanline[j] = r;
}
else {
bmpscanline[j] = r | (uchar) idx; j++;
}
}
}
fwrite((char *)&bmpscanline[0],1,outpacket,fp);
}
fclose(fp);
printf("%s created.\n", outfile);
}
return SUCCESS;
}
/* PIM routines start here */
/* helper function for GetPCXPalettes */
sshort GetPIMPalette(int palnum, int numpalettes)
{
FILE *fp;
sshort status = INVALID, idx, i;
ulong flen;
for (;;) {
/* open file - global filename is created in calling function */
fp = fopen(pcxfile,"rb");
if (fp == NULL) break;
/* get the length of the file */
fseek(fp, 0L, 2);
flen = ftell(fp) - 769;
/* avoid empty files - file must have a header and a palette */
if (flen < (ulong)128) {
fclose(fp);
break;
}
/* read the PCX header for compatibility */
rewind(fp);
fread(pcxheader,66,1,fp);
/* PCX version 5 with 8-bits per pixel, run-length encoded with one color plane */
/* PCX version 5 with 1-bit per pixel, run-length encoded with one color plane */
if (pcxheader[0] != (char)10 || pcxheader[1] != (char)5) {
/* if (pcxheader[0] != (char)10 || pcxheader[1] != (char)5 || pcxheader[2] != (char)1 || pcxheader[65] != (char)1) { */
fclose(fp);
puts("pcx file header problem");
break;
}
/* seek to end of file and read the first sixteen palette entries */
fseek(fp,flen,SEEK_SET);
/* first check the palette header (it must be a formfeed character) */
fread(pcxheader, 1, 1, fp);
if (pcxheader[0] != (char) 12) {
puts("pcx palette header problem");
fclose(fp);
break;
}
/* if PIM palette debugging is turned-on and 24-bit colors create duplicate 12-bit colors
then print the details to the console */
if (shrdupedebug == 1) clearcolorsused(); /* clear dupe count */
/* read the palette */
idx = (numpalettes - 1) - palnum;
if (numpalettes == 200) {
fread(&rgbArrays[idx][0][0],48,1,fp);
if (shrdupedebug == 1) {
for (i = 0; i < 16; i++) shrcolorsused(rgbArrays[idx][i][0],rgbArrays[idx][i][1],rgbArrays[idx][i][2]);
}
}
else {
fread(&rgb256Arrays[idx][0][0],48,1,fp);
if (shrdupedebug == 1) {
for (i = 0; i < 16; i++) shrcolorsused(rgb256Arrays[idx][i][0],rgb256Arrays[idx][i][1],rgb256Arrays[idx][i][2]);
}
}
fclose(fp);
status = SUCCESS;
if (shrdupedebug == 1 && shrdupes == 1) {
printf("24-bit palette %s has %d duplicate 12-bit color(s).\n",pcxfile,shrdupecount);
}
break;
}
return status;
}
/* iterates through SHR segments in PCX format produced by ImageMagick Convert */
/* each segment has a 16 color palette that is used for SHR conversion. The PCX Image data is ignored */
/* this is just a simple way to use the ImageMagick Convert utility to provide a palette in a format that is easy for
me to read and use in this converter */
/* seedfile is the full pathname of one of the pcx images */
sshort GetPIMPalettes(char *seedfile)
{
FILE *fp;
sshort status = INVALID;
char seedbase[256], ext[10];
int i, j;
imnumpalettes = 0;
strcpy(seedbase,seedfile);
j=999;
for (i=0;seedbase[i]!=(char)0;i++) {
/* support both forward and back slash for compatibility */
if (seedbase[i] == (char)92 || seedbase[i] == (char)47) j = i+1;
}
if (j==999) {
printf("%s is an invalid IM seed file name!\n",seedfile);
return status;
}
seedbase[j] = 0;
/* test for brooks mode first */
strcpy(pcxfile,seedbase);
strcat(pcxfile,"199.pcx");
fp = fopen(pcxfile,"rb");
if (NULL == fp) {
/* if brooks test failed, test for multipalette */
strcpy(pcxfile,seedbase);
strcat(pcxfile,"15.pcx");
fp = fopen(pcxfile,"rb");
if (NULL == fp) {
/* if multipalette test failed check for 8-palette */
strcpy(pcxfile,seedbase);
strcat(pcxfile,"7.pcx");
fp = fopen(pcxfile,"rb");
if (NULL == fp) {
strcpy(pcxfile,seedbase);
strcat(pcxfile,"0.pcx");
fp = fopen(pcxfile,"rb");
if (NULL == fp) {
printf("%s is an invalid IM seed file!\n",seedfile);
return status;
}
imnumpalettes = 1;
}
else {
imnumpalettes = 8;
}
/* black-out the palettes if less than 16 are active */
memset(&rgb256Arrays[8][0][0],0,768);
}
else {
imnumpalettes = 16;
}
}
else {
imnumpalettes = 200;
}
fclose(fp);
if (quietmode == 0) shrdupedebug = 1;
/* all the pcx files must be present and accounted for */
for (i = 0; i < imnumpalettes; i++) {
strcpy(pcxfile,seedbase);
sprintf(ext,"%d.pcx",i);
strcat(pcxfile,ext);
status = GetPIMPalette(i,imnumpalettes);
if (status == INVALID) {
imnumpalettes = 0;
printf("Unable to open IM palette %s!\n",pcxfile);
return status;
}
}
printf("%d IM Palettes successfully loaded!\n",imnumpalettes);
return SUCCESS;
}
/* convert 256 color bmps to 24 bit bmps */
/* convert 24 bit bmps */
FILE *ReformatPIMBMP(FILE *fp)
{
FILE *fp2;
ushort packet,outpacket,y,x,x1;
sshort count = 0;
/* seek past extraneous info in header if any */
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
if (bmi.biBitCount == 8) {
packet = bmi.biWidth;
}
else {
packet = bmi.biWidth * 3;
}
while ((packet % 4)!=0)packet++;
if((fp2=fopen("Reformat.bmp","wb"))==NULL) {
printf("Error Opening %s for writing!\n","Reformat.bmp");
fclose(fp);
return fp;
}
outpacket = WriteDIBHeader(fp2,(ushort)bmi.biWidth,(ushort)bmi.biHeight);
if (outpacket < 1) {
fclose(fp2);
remove("Reformat.bmp");
printf("Error writing header to %s!\n","Reformat.bmp");
return fp;
}
for (y=0;y<bmi.biHeight;y++) {
fread((char *)&bmpscanline[0],1,packet,fp);
if (bmi.biBitCount == 8) {
ReformatVGALine();
}
if (fourbit == 1) {
for (x=0;x<outpacket;x++) bmpscanline[x] = gsColor(bmpscanline[x]);
}
fwrite((char *)&bmpscanline[0],1,outpacket,fp2);
}
fclose(fp2);
fclose(fp);
if((fp=fopen("Reformat.bmp","rb"))==NULL) {
printf("Error Opening %s for reading!\n","Reformat.bmp");
return fp;
}
/* read the header stuff into the appropriate structures */
fread((char *)&BitMapFileHeader.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
return fp;
}
/* uses ImageMagick to build the palettes */
/* todo - for raw output add functionality to use the image data from the palette pcx files */
/* also clean-up unused vars at some point */
int ConvertPIM(unsigned char *basename, unsigned char *newname)
{
FILE *fp;
int packet = INVALID, y,y1,y2,x,i,j,width,height,reformat = bmp3, bmpversion =0,lidx,didx,count;
int outpacket, outputwidth, outputheight, offset;
char bmpfile[256], outfile[256];
uchar r,g,b,lr,lg,lb,red,green,blue,drawcolor,idx,toneindex;
ushort temp, fl, darkest,lightest,found,unused;
float hue,saturation,luminance;
sshort jdx;
sprintf(bmpfile,"%s.bmp",basename);
if((fp=fopen(bmpfile,"rb"))==NULL) {
puts(szTextTitle);
printf("%s cannot be opened for input!\nIt probably does not exist or the filename was mis-spelled...\nExiting!\n");
return INVALID;
}
/* read the header stuff into the appropriate structures,
it's likely a bmp file */
memset(&BitMapFileHeader.bfType,0,sizeof(BITMAPFILEHEADER));
memset(&bmi.biSize,0,sizeof(BITMAPINFOHEADER));
fread((char *)&BitMapFileHeader.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV2)|| bmi.biSize == sizeof(BITMAPINFOHEADERV3)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,(unsigned)bmi.biSize,1,fp);
bmpversion = 3;
reformat = 1;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
reformat = 1;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
reformat = 1;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
}
else {
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion == 0) {
fclose(fp);
puts(szTextTitle);
puts("BMP version not recognized!");
return INVALID;
}
if (bmi.biCompression==BI_RGB &&
BitMapFileHeader.bfType[0] == 'B' && BitMapFileHeader.bfType[1] == 'M' &&
bmi.biPlanes==1 && (bmi.biBitCount == 24 || bmi.biBitCount == 8)) {
if (bmi.biWidth != 320 || bmi.biHeight != 200) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported size for SHR file output.\nExiting!\n", bmpfile);
return INVALID;
}
outputheight = height = (int) bmi.biHeight;
outputwidth = width = (int) bmi.biWidth;
if (outputheight%2 != 0) outputheight++;
outputheight = outputheight/2;
outpacket = outputwidth;
packet = width * 3;
while (packet%4 != 0)packet++; /* BMP scanlines align on DWORD boundaries */
}
if (packet == INVALID) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported format for SHR or BROOKS file output.\nExiting!\n", bmpfile);
if (bmi.biCompression==BI_RGB) puts("compression ok");
return INVALID;
}
if (bmi.biBitCount == 8) {
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*256,1,fp);
reformat = 1;
}
if (reformat == 1) {
fp = ReformatPIMBMP(fp);
if (NULL == fp) return SUCCESS;
}
if (imnumpalettes != 200) shrpalettes = 16;
else shrpalettes = 200;
shr = 320;
/* errorsum = 1; error summing is not forced but maybe it should be */
/* initialize nearest color arrays */
/* todo - probably not needed because setting nearest color arrays on a line by line basis */
InitDoubleArrays();
if (imnumpalettes == 16) {
/* populate 200 palettes from 16-palettes */
/* and set initial scbs */
/* this allows most of the same line oriented routines to be re-used for either
16-palette or 200-palette processing */
shr256 = 1;
for (i = 0, y = 0; i < 8; i++, y+=25) {
y1 = y;
y2 = y + 13;
j = i * 2;
/* palette for 13 lines */
for (x = y1; x < y2; x++) {
mypic.scb[x] = j;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[j][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[j][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[j][idx][2];
}
}
/* palettte for 12 lines */
y1 = y + 13;
y2 = y + 25;
j = (i*2) + 1;
for (x = y1; x < y2; x++) {
mypic.scb[x] = j;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[j][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[j][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[j][idx][2];
}
}
}
}
else if (imnumpalettes == 8) {
/* populate 200 palettes from 8-palettes */
/* and set initial scbs */
/* this allows most of the same line oriented routines to be re-used for either
8-palette, 16-palette or 200-palette processing */
shr256 = 1;
for (i = 0, y = 0; i < 8; i++, y+=25) {
y1 = y;
y2 = y + 13;
/* palette for 13 lines */
for (x = y1; x < y2; x++) {
mypic.scb[x] = i;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[i][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[i][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[i][idx][2];
}
}
/* same palettte for 12 lines */
y1 = y + 13;
y2 = y + 25;
for (x = y1; x < y2; x++) {
mypic.scb[x] = i;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[i][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[i][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[i][idx][2];
}
}
}
}
else if (imnumpalettes == 1) {
for (x=0;x<200;x++) {
shr256 = 1;
mypic.scb[x] = 0;
for (idx = 0; idx < 16; idx++) {
rgbArrays[x][idx][0] = rgb256Arrays[0][idx][0];
rgbArrays[x][idx][1] = rgb256Arrays[0][idx][1];
rgbArrays[x][idx][2] = rgb256Arrays[0][idx][2];
}
}
}
else {
shr256 = 0;
}
/* shr256 ends */
memset(&dhrbuf[0],0,32000); /* clear write buffer */
if (dither != 0) {
usepalettedistance = 1;
/* sizeof(sshort) * 640 */
memset(&redDither[0],0,1280);
memset(&greenDither[0],0,1280);
memset(&blueDither[0],0,1280);
memset(&redSeed[0],0,1280);
memset(&greenSeed[0],0,1280);
memset(&blueSeed[0],0,1280);
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
}
else {
usepalettedistance = 0;
}
/* seek to beginning of input file and process */
fseek(fp,BitMapFileHeader.bfOffBits,SEEK_SET);
if (dither == 0) puts("non-dithered output");
else puts("dithered output");
/* bmp's are upside-down so conversion of scanlines is in
reverse order */
for(y=0,y1=height-1;y<height;y++,y1--)
{
fread((char *)&bmpscanline[0],1,packet,fp);
/* set the palette for this line */
/* todo - probably not needed for dithered output */
InitDoubleLineArrays(y1);
if (dither == 0) {
/* if not dithering use direct pixel mapping */
for (x=0,j=0;x<width;x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
idx = GetClosestColor(r,g,b);
setlopixel((uchar)idx,x,y1);
}
}
else
{
/* add the current line to the r,g,b line buffers used for dithering */
for (j=0, x = 0; x < width; x++) {
b = bmpscanline[j]; j++;
g = bmpscanline[j]; j++;
r = bmpscanline[j]; j++;
/* values are already seeded from previous 2 - line(s) */
/* the idea here is to add a full value to whatever bleed values have been added */
AdjustShortPixel(1,(sshort *)&redDither[x],(sshort)r);
AdjustShortPixel(1,(sshort *)&greenDither[x],(sshort)g);
AdjustShortPixel(1,(sshort *)&blueDither[x],(sshort)b);
}
if (shrpalettes == 200) BrooksDither(y1,width);
else PicDither(y1,width);
/* seed next line - promote nearest forward array to
current line */
memcpy(&redDither[0],&redSeed[0],1280);
memcpy(&greenDither[0],&greenSeed[0],1280);
memcpy(&blueDither[0],&blueSeed[0],1280);
/* seed first seed - promote furthest forward array
to nearest forward array */
memcpy(&redSeed[0],&redSeed2[0],1280);
memcpy(&greenSeed[0],&greenSeed2[0],1280);
memcpy(&blueSeed[0],&blueSeed2[0],1280);
/* clear last seed - furthest forward array */
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
}
}
fclose(fp);
if (reformat == 1) {
if (bmp3 == 0) {
remove("Reformat.bmp");
}
else {
sprintf(outfile,"%s.bm3", newname);
remove(outfile);
rename("Reformat.bmp",outfile);
}
}
if (usepalettedistance == 1) {
/* if we shuffled the palettes based on the best total distance then we need
to put our new palettes in place before writing our output file(s) */
for (y= 0;y<200;y++) {
memcpy(&rgbArrays[y][0][0],&savepalettes[y][0][0],48);
}
memcpy(&mypic.scb[0],&savescb[0],200);
}
if (shr256 == 1) {
/* for SHR motion video prototyping */
/* uses only 8 palettes to facilitate slide transition */
if (imnumpalettes == 1) sprintf(outfile,"%s.SHR", newname);
else if (imnumpalettes == 8) sprintf(outfile,"%s.SMV", newname);
else sprintf(outfile,"%s.SH2", newname);
}
else {
sprintf(outfile,"%s.SH3", newname);
}
ucase((char *)&outfile[0]);
if (longnames == 0) tags = 0;
/* Ciderpress File Attribute Preservation Tags */
if (tags == 1){
if (shr256 == 1) strcat(outfile,"#C10000");
else strcat(outfile,"#C10002");
}
if (SHR320_Output(outfile) != SUCCESS) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return INVALID;
}
printf("%s created.\n", outfile);
/* "option V" for preview output or M2S output here */
if (vbmp == 1) {
if (m2s == 0) {
/* for MS-DOS output we use short (and different) names */
if (longnames == 0)sprintf(outfile,"%s.pmp", newname);
else sprintf(outfile,"%s_Preview.bmp", newname);
}
else {
/* Magick2SHR palette output and Magick2SHR naming convention for round-trip editing */
if (longnames == 0)sprintf(outfile,"%s.dib", newname);
else sprintf(outfile,"%s_palette.bmp", newname);
/* open M2S palette file */
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return INVALID;
}
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)16;
mybmp.bmi.biHeight = (ulong)shrpalettes;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
outpacket = 48;
mybmp.bmi.biSizeImage = (ulong)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the M2S palette BMP */
fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
memset(&bmpscanline[0],0,outpacket);
/* write a BMP scanline for each SHR palette entry */
for(y=0,y1=shrpalettes-1;y<shrpalettes;y++,y1--) {
/* build a scanline of each palette entry */
for (x = 0, j=0; x < 16; x++) {
if (shrpalettes == 200) {
/* brooks palettes */
bmpscanline[j] = rgbArrays[y1][x][2]; j++;
bmpscanline[j] = rgbArrays[y1][x][1]; j++;
bmpscanline[j] = rgbArrays[y1][x][0]; j++;
}
else {
/* pic palettes */
bmpscanline[j] = rgb256Arrays[y1][x][2]; j++;
bmpscanline[j] = rgb256Arrays[y1][x][1]; j++;
bmpscanline[j] = rgb256Arrays[y1][x][0]; j++;
}
}
/* write a scanline of each palette entry */
fwrite((char *)&bmpscanline[0],1,outpacket,fp);
}
fclose(fp);
printf("%s created.\n", outfile);
/* now that the M2S palette file is written, the M2S proc file is exactly the same as the SHR preview file
except that the naming convention follows M2S naming unless in MS-DOS */
/* for MS-DOS output we use short names - MS-DOS users will need to rename the pmp file to a bmp file */
if (longnames == 0)sprintf(outfile,"%s.pmp", newname);
else sprintf(outfile,"%s_proc.bmp", newname);
}
fp = fopen(outfile,"wb");
if (NULL == fp) {
puts(szTextTitle);
printf("%s cannot be created.\n", outfile);
return INVALID;
}
/* 256 color preview is promoted to */
/* 24-bit BMP format */
outpacket = width * 3;
if (width%2 != 0) outpacket++;
while (outpacket%4 != 0)outpacket++;
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)width;
mybmp.bmi.biHeight = (ulong)height;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
mybmp.bmi.biSizeImage = (ulong)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the output BMP */
fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
memset(&bmpscanline[0],0,outpacket);
for(y=0,y1=height-1;y<height;y++,y1--) {
for (x = 0, j=0; x < width; x++) {
idx = getlopixel(x,y1);
/* range check */
if (idx > 15)idx = 0; /* default black */
/* brooks palettes are scanline oriented */
/* the 256 color palette has been copied to the brooks palette */
bmpscanline[j] = rgbArrays[y1][idx][2]; j++;
bmpscanline[j] = rgbArrays[y1][idx][1]; j++;
bmpscanline[j] = rgbArrays[y1][idx][0]; j++;
}
fwrite((char *)&bmpscanline[0],1,outpacket,fp);
}
fclose(fp);
printf("%s created.\n", outfile);
}
return SUCCESS;
}
/* uses ImageMagick to build the palettes */
/* clean-up unused vars at some point */
int Convertfourbit(unsigned char *basename, unsigned char *newname)
{
FILE *fp;
int packet = INVALID, bmpversion =0;
char bmpfile[256], outfile[256];
sprintf(bmpfile,"%s.bmp",basename);
sprintf(outfile,"%s.4B",newname);
if((fp=fopen(bmpfile,"rb"))==NULL) {
puts(szTextTitle);
printf("%s cannot be opened for input!\nIt probably does not exist or the filename was mis-spelled...\nExiting!\n");
return INVALID;
}
/* read the header stuff into the appropriate structures,
it's likely a bmp file */
memset(&BitMapFileHeader.bfType,0,sizeof(BITMAPFILEHEADER));
memset(&bmi.biSize,0,sizeof(BITMAPINFOHEADER));
fread((char *)&BitMapFileHeader.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV2)|| bmi.biSize == sizeof(BITMAPINFOHEADERV3)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,(unsigned)bmi.biSize,1,fp);
bmpversion = 3;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
}
else {
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion == 0) {
fclose(fp);
puts(szTextTitle);
puts("BMP version not recognized!");
return INVALID;
}
if (bmi.biCompression==BI_RGB &&
BitMapFileHeader.bfType[0] == 'B' && BitMapFileHeader.bfType[1] == 'M' &&
bmi.biPlanes==1 && (bmi.biBitCount == 24 || bmi.biBitCount == 8)) {
if (bmi.biWidth != 320 || bmi.biHeight != 200) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported size for 4-bit file output.\nExiting!\n", bmpfile);
return INVALID;
}
packet = SUCCESS;
}
if (packet == INVALID) {
fclose(fp);
puts(szTextTitle);
printf("%s is not in a supported format for 4-bit file output.\nExiting!\n", bmpfile);
return INVALID;
}
if (bmi.biBitCount == 8) {
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*256,1,fp);
}
fp = ReformatPIMBMP(fp);
if (NULL == fp) return INVALID;
fclose(fp);
remove(outfile);
rename("Reformat.bmp",outfile);
printf("%s created.\n", outfile);
return SUCCESS;
}
/* Command Line Options Helper Function */
int cmpstr(char *str, char *cmp)
{
int i;
if (strlen(cmp) != strlen(str)) return INVALID;
for (i=0;str[i] != 0;i++) {
if (toupper(cmp[i]) != toupper(str[i])) return INVALID;
}
return SUCCESS;
}
/* raw SHR structures */
/* FileType $C1 AuxType $0000 - mode320 and mode640 */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagINPIC
#else
typedef struct tagINPIC
#endif
{
uchar line[200][160]; /* 32000 bytes */
uchar scb[200];
uchar padding[56];
uchar pal[16][32];
} INPIC;
/* FileType $C1 AuxType $0002 - mode3200 */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagINBROOKS
#else
typedef struct tagINBROOKS
#endif
{
uchar line[200][160];
uchar pal[200][32]; /* $0RGB table buffer palette entries 0-16 reversed */
} INBROOKS;
INPIC *p16 = NULL;
INBROOKS *p200 = NULL;
uchar *shrbuf = NULL;
sshort getshrindex(int x, int y)
{
uchar *crt, color;
int offset;
offset = x / 2;
/* each SHR scanline is offset by 160 bytes */
offset += (y * 160);
crt = (unsigned char *)&shrbuf[offset];
if (x%2 == 0) {
/* even pixels */
color = crt[0] >> 4;
}
else {
/* odd pixels */
color = crt[0] &0xf;
}
return (sshort)color;
}
/* the following provides some equivalent functionality for SHR as
what A2B provides for producing BMP files from DHGR files.
SHR Input files must follow one of our naming conventions.
limited to mode320 and mode3200 PIC files.
*/
/* converts SHR images to imagedata and palette file pairs in BMP format */
/* provides a 320 x 200 x 24-bit BMP file for editing stray pixels etc.in converted output */
/* can also be used to convert SHR files produced by other applications to BMP format */
/* these have lost their original 24-bit precision and should not be re-transformed */
/* m2s should be used to convert edited files back to PIC or BROOKS files */
int shrtom2s(char *infile, char *basename)
{
FILE *fp;
char procfile[256], palfile[256];
ulong flen;
int brooks_format = 0;
uchar buf[32],r,g,b;
ushort *tmppal = (ushort *)&buf[0], *brookspal;
sshort x,y,y1,i,j,k;
/* use the file length to determine what kind of output we
will provide */
fp = fopen(infile,"rb");
if (fp == NULL) {
printf("Could not open SHR file %s\n",infile);
return 1;
}
/* get the length of the file */
fseek(fp, 0L, 2);
flen = ftell(fp);
if (flen == (ulong) sizeof(INPIC)) {
/* 16 palettes = 32768 bytes */
shrbuf = (uchar *) malloc(sizeof(INPIC));
}
else if (flen == (ulong) sizeof(INBROOKS)) {
/* 200 palettes = 38400 bytes */
brooks_format = 1;
shrbuf = (uchar *) malloc(sizeof(INBROOKS));
}
else {
fclose(fp);
printf("%s is not a supported 320 x 200 SHR file\n",infile);
return 1;
}
if (NULL == shrbuf) {
puts("Not enough memory.");
return 1;
}
rewind(fp);
/* read scanlines */
fread((uchar *)&shrbuf[0],sizeof(uchar),32000,fp);
/* read palettes */
if (brooks_format == 1) {
fread((uchar *)&shrbuf[32000],sizeof(uchar),6400,fp);
fclose(fp);
/* convert 12-bit brooks palettes to 24-bit BMP palettes */
p200 = (INBROOKS *)&shrbuf[0];
for (y=0;y<200;y++) {
brookspal = (ushort *)&p200[0].pal[y][0];
/* flip the brooks palette end for end */
for (i=0,k=15; i < 16; i++,k--) tmppal[i] = brookspal[k];
/* place decoded 12 bit colors into 24-bit color array */
/* the m2s BMP palette file for a BROOKS image contains all 200 palettes
(or less)
*/
for (i=0,k=0;i< 16;i++,k+=2) {
/* decode $0RGB */
g = b = buf[k];
r = buf[k+1];
r = (r &0xf);
g = (g >> 4);
b = (b &0xf);
/* do not truncate the low nibble, use 24-bits */
rgbArrays[y][i][0] = (uchar) (r << 4 | r);
rgbArrays[y][i][1] = (uchar) (g << 4 | g);
rgbArrays[y][i][2] = (uchar) (b << 4 | b);
}
}
}
else {
fread((uchar *)&shrbuf[32000],sizeof(uchar),768,fp);
fclose(fp);
/* convert 12-bit pic palettes into 24-bit BMP palettes */
p16 = (INPIC *)&shrbuf[0];
/* encode into 16 palettes first
this is needed for the m2s BMP palette file for a PIC */
/* the m2s palette file for a PIC file contains 16 palettes
(or less)
*/
for (j=0;j<16;j++) {
/* palette colors 0 - 16 */
for (i=0,k=0;i< 16;i++,k+=2) {
/* decode $0RGB */
g = b = p16[0].pal[j][k];
r = p16[0].pal[j][k+1];
r = (r &0xf);
g = (g >> 4);
b = (b &0xf);
/* place decoded 12 bit colors into 24-bit color array */
rgb256Arrays[j][i][0] = (uchar) (r << 4 | r);
rgb256Arrays[j][i][1] = (uchar) (g << 4 | g);
rgb256Arrays[j][i][2] = (uchar) (b << 4 | b);
}
}
/* copy the corresponding palette to the 200 line output array.
this will be used to transform image data from the PIC file
to 24-bit colors for the m2s BMP proc file */
for (y=0;y<200;y++) {
j = (sshort) p16[0].scb[y];
/* palette colors 0 - 16 */
for (i=0;i< 16;i++) {
rgbArrays[y][i][0] = rgb256Arrays[j][i][0];
rgbArrays[y][i][1] = rgb256Arrays[j][i][1];
rgbArrays[y][i][2] = rgb256Arrays[j][i][2];
}
}
}
/* make output file names from basename */
sprintf(procfile,"%s_proc.bmp",basename);
sprintf(palfile,"%s_palette.bmp",basename);
/* write image data in BMP format */
fp = fopen(procfile,"wb");
if (fp == NULL) {
printf("Could not open %s for writing.\n",procfile);
free(shrbuf);
return 1;
}
clearcolorsused();
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)320;
mybmp.bmi.biHeight = (ulong)200;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
mybmp.bmi.biSizeImage = (ulong)960;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the output BMP */
fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
memset(&bmpscanline[0],0,960);
for(y=0,y1=199;y<200;y++,y1--) {
for (x = 0, j=0; x < 320; x++) {
k = getshrindex(x,y1);
/* range check */
if (k > 15)k = 0; /* default black */
b = bmpscanline[j] = rgbArrays[y1][k][2]; j++;
g = bmpscanline[j] = rgbArrays[y1][k][1]; j++;
r = bmpscanline[j] = rgbArrays[y1][k][0]; j++;
shrcolorsused(r,g,b);
}
fwrite((char *)&bmpscanline[0],1,960,fp);
}
fclose(fp);
printf("%s created.\n",procfile);
/* write palette data in BMP format */
fp = fopen(palfile,"wb");
if (fp == NULL) {
printf("Could not open %s for writing.\n",palfile);
free(shrbuf);
return 1;
}
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (ulong)40;
mybmp.bmi.biWidth = (ulong)16;
if (brooks_format == 0)
mybmp.bmi.biHeight = (ulong)16;
else
mybmp.bmi.biHeight = (ulong)200;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (ulong) BI_RGB;
mybmp.bmi.biSizeImage = (ulong)48;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER);
mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits;
/* write the header for the output BMP */
fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp);
memset(&bmpscanline[0],0,48);
if (brooks_format == 0) {
for(y=0,y1=15;y<16;y++,y1--) {
for (x = 0, j=0; x < 16; x++) {
bmpscanline[j] = rgb256Arrays[y1][x][2]; j++;
bmpscanline[j] = rgb256Arrays[y1][x][1]; j++;
bmpscanline[j] = rgb256Arrays[y1][x][0]; j++;
}
fwrite((char *)&bmpscanline[0],1,48,fp);
}
}
else {
for(y=0,y1=199;y<200;y++,y1--) {
for (x = 0, j=0; x < 16; x++) {
bmpscanline[j] = rgbArrays[y1][x][2]; j++;
bmpscanline[j] = rgbArrays[y1][x][1]; j++;
bmpscanline[j] = rgbArrays[y1][x][0]; j++;
}
fwrite((char *)&bmpscanline[0],1,48,fp);
}
}
fclose(fp);
printf("%s created.\n",palfile);
free(shrbuf);
puts("Done!");
printf("%d unique SHR palette colors in image.\n",shrcolorcount);
return 0;
}
/*
#define GRAVITY_CENTER 0
#define GRAVITY_EAST 1
#define GRAVITY_NORTHEAST 2
#define GRAVITY_NORTH 3
#define GRAVITY_NORTHWEST 4
#define GRAVITY_SOUTHEAST 5
#define GRAVITY_SOUTH 6
#define GRAVITY_SOUTHWEST 7
#define GRAVITY_WEST 8
*/
#define NUM_GRAVITIES 9
/* gravity options for cropped output - center (typical) */
char *geometry[] = {
"Center",
"East",
"NorthEast",
"North",
"NorthWest",
"SouthEast",
"South",
"SouthWest",
"West",
NULL};
char *gravity = "center";
/* command line helper function to set ImageMagick geometry for gravity option */
void SetMagickAspectScript(char *wordptr)
{
int idx;
if (wordptr[0] != (char) 0) {
for (idx = 0;idx < NUM_GRAVITIES;idx++) {
if (cmpstr(wordptr,geometry[idx]) == SUCCESS) {
gravity = (char *) &geometry[idx][0];
return;
}
}
}
}
/* args are image file name, type of output 0 or 1 (.bat or .sh),
and whether output is cropped 0 or padded 1 */
int MakeMagickAspectScript(char *name,int script, int pad)
{
FILE *fp, *fp2=NULL;
char buf[128], c, d, idfile[256], outfile[256];
int i, j, status = 1;
float fwidth, fheight;
int width, height, cropwidth, cropheight, xtent;
/* use image file base name for id file name and batch file or script file name */
strcpy(idfile,name);
strcpy(outfile,name);
j = 999;
for (i=0;name[i]!=(char)0;i++){
if (name[i] == '.')j=i;
}
/* the image file name must have an extension */
if (j==999) return status;
idfile[j] = outfile[j]=(char)0;
/* the id file must already exist */
strcat(idfile,".id");
fp = fopen(idfile,"r");
if (fp == NULL) return status;
if (script == 0) strcat(outfile,".bat");
else strcat(outfile,".sh");
for (;;) {
if (NULL == fgets(buf, 128, fp)) {
fclose(fp);
break;
}
nocr(buf);
/* this parser is really simplistic... it uses the first widthxheight string preceded by a space...
it will probably usually work since we don't usually put widthxheight strings in filenames */
width = height = 0;
d = (char)0;
for (i=0;buf[i]!=(char)0;i++){
if (d == (char)32) {
width = atoi((char *)&buf[i]);
}
c = buf[i];
if (c=='x' && width > 0) {
if (d > (char)47 && d < (char)58) {
height = atoi((char *)&buf[i+1]);
if (height > 0) break;
}
}
d = c;
}
if (width == 0) break;
if (height == 0) break;
if (pad == 0) {
/* cropping (not padding) */
/* for widescreen images trim both sides, the height remains the same */
fwidth = (float)1.3334 * height;
cropwidth = (int)fwidth;
if (cropwidth > width) {
/* if the cropping width is not 1/3 greater than the height, this is a portrait image
and must be cropped in the vertical axis only */
cropwidth = width;
fheight = (float)0.75 * width;
cropheight = (int)fheight;
}
else {
cropheight = height;
}
}
else {
fheight = (float)0.75 * width;
cropheight = (int)fheight;
if (cropheight == height) {
/* if the height is equal to 75% of the width, the output is verbatim (no padding is needed) */
cropwidth = width;
pad = 0;
}
else if (height < cropheight) {
/* if the height is less than 75% of the width, the output is letterboxed with padding above and below */
xtent = cropwidth = width;
}
else {
/* if the height is greater than 75% of the width, the output is pillarboxed with padding on the sides */
cropheight = height;
fwidth = (float)1.3334 * height;
xtent = cropwidth = (int)fwidth;
}
}
fp2 = fopen(outfile,"w");
if (NULL == fp2) break;
if (script == 0) {
/* batch file output */
fprintf(fp2,"@echo off\n");
if (pad == 0) {
/* %MAGICK% %%f -gravity center -crop 1440x1080+0+0 -define format:BMP %%~nf.bmp */
fprintf(fp2,"%%1 %%2 -gravity %s -crop %dx%d+0+0 -define format:BMP %%3.bmp\n",gravity,cropwidth,cropheight);
}
else {
/* letterbox */
/* %MAGICK% %%f -gravity center -background black -extent 1920x1920 -crop 1920x1440+0+0 +repage -define format:BMP %%~nf.bmp */
/* pillarbox */
/* %MAGICK% %%f -gravity center -background black -extent 2730x2730 -crop 2730x2048+0+0 +repage -define format:BMP %%~nf.bmp */
fprintf(fp2,
"%%1 %%2 -gravity %s -background black -extent %dx%d -crop %dx%d+0+0 +repage -define format:BMP %%3.bmp\n",
gravity,xtent,xtent,cropwidth,cropheight);
}
}
else {
/* shell script output */
fprintf(fp2,"#!/bin/sh\n");
if (pad == 0) {
/* $MAGICK $src -gravity center -crop 1440x1080+0+0 -define format:BMP $tgt.bmp */
fprintf(fp2,"$1 $2 -gravity %s -crop %dx%d+0+0 -define format:BMP $3.bmp\n",gravity,cropwidth,cropheight);
}
else {
fprintf(fp2,
"$1 $2 -gravity %s -background black -extent %dx%d -crop %dx%d+0+0 +repage -define format:BMP $3.bmp\n",
gravity,xtent,xtent,cropwidth,cropheight);
}
}
status = 0;
}
fclose(fp);
if (NULL != fp2) fclose(fp2);
return status;
}
int main(int argc, char **argv)
{
int status = 0, idx, jdx, pset;
char fname[256],sname[256],outfile[256], c, d, e, f;
uchar *wordptr;
FILE *fp;
fname[0] = sname[0] = outfile[0] = ASCIIZ;
for (idx = 0; idx < 16; idx++) {
desaturate[idx] = 100.0;
}
setluma();
#ifdef MSDOS
longnames = 0;
system("cls");
#endif
if(argc == 1) {
puts(szTextTitle);
puts("Command line Usage: a2b filenames options");
puts(" \"a2b MyAppleWinDHiresMonoScreenCapture.bmp\"");
puts(" or \"a2b MyDHires.2FC\" or \"a2b MyDHires.A2FC\"");
puts(" or \"a2b MyDHires.dhr\" or \"a2b MyDHires.dhgr\"");
puts(" or \"a2b MyDHires.BIN\" or \"a2b MyDHires.AUX\"");
puts("If converting .BIN and .AUX file pairs, both must be present.");
puts("Default is automatic naming. Optional different output filename.");
puts(" \"a2b MyDHires.2FC OutfileBaseName\"");
puts("Option P - Palettes p0-p16");
puts("Output: 280 x 192 x 24 Bit Windows .BMP File - Default");
puts(" 140 x 192 x 24 Bit Windows .BMP File - Option 140");
puts(" 560 x 384 x Monochrome Windows .BMP File - Option 384");
puts(" 560 x 192 x Monochrome Windows .BMP File - Option 192");
puts("For additional options read the documentation and source code.");
puts("Additional output includes Apple II DHGR, LGR and DLGR, and SHR files.");
puts("Additional output also includes VBMP files (or Previews) and Image Fragments.");
puts("Additional output also includes error-diffusion dithering.");
printf("Enter Input FileName (Blank to Exit): ");
gets(fname);
if (fname[0] == ASCIIZ)
return 1;
printf("Enter Output FileBaseName (Blank for None) : ");
gets(outfile);
}
else {
strcpy(fname, argv[1]);
/* getopts */
if (argc > 2) {
for (idx = 2; idx < argc; idx++) {
/* switch character is optional */
wordptr = (uchar *)&argv[idx][0];
c = toupper(wordptr[0]);
if (c == '-') {
wordptr = (uchar *)&argv[idx][1];
c = toupper(wordptr[0]);
}
d = e = f = toupper(wordptr[1]);
if (d != (char)0) e = f = toupper(wordptr[2]);
if (e != (char)0) f = toupper(wordptr[3]);
/* a43b or a43s */
if (c == 'A' && d == '4' && e == '3' && (f == 'B' || f == 'S')) {
/* set ImageMagick geometry for gravity option */
/* by default, cropping is centered */
SetMagickAspectScript((char *)&wordptr[4]);
/* create cropping scripts for ImageMagick convert from ImageMagick identify output */
/* batch file */
if (f == 'B') {
return MakeMagickAspectScript(fname,0,0);
}
/* shell script */
if (f == 'S') {
return MakeMagickAspectScript(fname,1,0);
}
}
/* p43b or p43s */
if (c == 'P' && d == '4') {
/* create padding scripts for ImageMagick convert from ImageMagick identify output */
/* batch file */
if (cmpstr(wordptr,"p43b") == SUCCESS) {
return MakeMagickAspectScript(fname,0,1);
}
/* shell script */
if (cmpstr(wordptr,"p43s") == SUCCESS) {
return MakeMagickAspectScript(fname,1,1);
}
}
if (c== 'B' && d == 'M') {
/* produce a 24-bit Windows 3.1 compatible BMP file of the input file */
/* this is primarily for compatibility with SuperConvert for running SHR comparisons */
if (cmpstr(wordptr,"bmp3") == SUCCESS) {
bmp3 = 1;
continue;
}
}
if (c == '4' && d != (char)0) {
/* SHR output */
/* settings to support reducing palettes to 4-bit depth */
/* the threshold settings are currently fixed for this setting but some future version might
include changing these to mask out midtones or whatever - leave as-is for now */
if (cmpstr(wordptr,"4bit") == SUCCESS) {
/* creates a copy of the 320 x 200 input file using 12-bit legal GS colors */
/* this file is then processed into segmented palettes using ImageMagick */
/* it can also be processed into a 16-color external palette for Image Tone rendering */
fourbit = 1;
continue;
}
if (cmpstr(wordptr,"4play") == SUCCESS) {
/* this setting affects only the colors that are in the orginal image and only for the
purposes of building palettes... */
fourplay = 1;
continue;
}
if (cmpstr(wordptr,"4pal") == SUCCESS) {
/* use this setting just before we start the building of the palettes */
/* this does not apply to segmented external palettes */
fourpal = 1;
continue;
}
}
if (c == 'Q' && d == (char)0) {
/* suppress additional output unless quiet mode is disabled - additional output may be useful to some including me */
/* note also - windows and MS-DOS users should re-direct to a NUL device in batch (cmd) files
to avoid slowing-down processing with extraneous console output */
quietmode = 0;
continue;
}
if (c == 'S' && d != (char)0) {
/* "lossless" dithering */
if (cmpstr(wordptr,"sum") == SUCCESS) {
errorsum = 1;
continue;
}
}
if (c == 'M' && d != (char)0) {
/* Magick2SHR palette output in addition to SHR preview */
if (cmpstr(wordptr,"m2s") == SUCCESS){
vbmp = m2s = 1;
continue;
}
if (cmpstr(wordptr,"mix") == SUCCESS){
/* use additional matching palettes for Brooks output */
mix256 = 1;
continue;
}
}
/* there is no option in this utility to force reduced saturation of an image.
the saturation control in this utility applies exclusively to SHR output
by default for most SHR conversion options when a color in the conversion palette
cannot be substituted with a real color in the same "spectrum" a fully saturated
color from the conversion palette displays on the finished output.
sometimes this looks like hell so the user has the option of replacing the
fully saturated color with a de-saturated or grey pixel image-wide.
but doing this changes the image tone and washes-it out so it seldom
results in good-looking output */
if ((c == 'S' && (d > (char)47 && d < (char)58)) || (c == 'S' && d == (char)0)) {
if (d == (char) 0 || d == '0' && wordptr[2] == (char)0) {
/* greyscale final output if color not in image */
for (d = 0; d < 16; d++) {
if(desaturate[d] == 100.0)desaturate[d] = 0.0;
}
continue;
}
/* desaturate final output if color not in image */
jdx = atoi((char *)&wordptr[1]);
if (jdx > 0 && jdx < 201) {
for (d = 0; d < 16; d++) {
if(desaturate[d] == 100.0)desaturate[d] = (double)jdx;
}
continue;
}
}
if (c == 'G' && d == 'O') {
/* grey over-ride when making SHR file */
jdx = atoi((char *)&wordptr[2]);
if (jdx > 0 && jdx < 16) {
greyoveride[jdx] = 1;
desaturate[jdx] = 0.0;
continue;
}
if (cmpstr(wordptr,"go0") == SUCCESS) {
greyoveride[0] = 1;
desaturate[0] = 0.0;
continue;
}
}
if ((c == 'I' && d == (char)ASCIIZ) || cmpstr(wordptr,"imagetone") == SUCCESS){
useimagetone = 1;
continue;
}
if ((c == 'L' && d == (char)ASCIIZ) || cmpstr(wordptr,"lgr") == SUCCESS){
lores = 1;
continue;
}
/* set different Luma coefficients for color distance */
jdx = 0;
if (cmpstr(wordptr,"GIMP") == SUCCESS) jdx = 411; /* color managed sRGB */
else if (cmpstr(wordptr,"SRGB") == SUCCESS) jdx = 411;
else if (cmpstr(wordptr,"MAGICK") == SUCCESS) jdx = 709;/* most modern images */
else if (cmpstr(wordptr,"HDTV") == SUCCESS) jdx = 240;
if (jdx != 0) {
lumaREQ = jdx;
printf("Using Luma Rec. %d\n", lumaREQ);
setluma();
continue;
}
if (c == 'L') {
/* Luma */
jdx = atoi((char *)&wordptr[1]);
if (jdx == 601 || jdx == 709 || jdx == 240 || jdx == 911 || jdx == 411 || jdx == 2020) {
lumaREQ = jdx;
printf("Using Luma Rec. %d\n", lumaREQ);
setluma();
continue;
}
}
if (cmpstr(wordptr,"dl") == SUCCESS || cmpstr(wordptr,"dlgr") == SUCCESS){
lores = doublelores = 1;
continue;
}
/* DOS 3.3 header will be appended to Apple II Output */
if (cmpstr(wordptr,"dos") == SUCCESS) {
dosheader = 1;
continue;
}
if (cmpstr(wordptr,"shr") == SUCCESS || cmpstr(wordptr,"shr2") == SUCCESS) {
shr = 320;
if (cmpstr(wordptr,"shr2") == SUCCESS) {
shr2 = 1;
puts("shr2 is in effect");
}
if (shrpalette == 15){
palnumber = 15;
GetBuiltinPalette('P','1',palnumber);
}
continue;
}
if (cmpstr(wordptr,"drooks") == SUCCESS || cmpstr(wordptr,"drooks2") == SUCCESS ||
cmpstr(wordptr,"drooks3") == SUCCESS || cmpstr(wordptr,"drooks4") == SUCCESS ||
cmpstr(wordptr,"drooks5") == SUCCESS) {
wordptr[0] = 'b';
usepalettedistance = 1;
puts("Palette distance conversion is now on. This takes longer, be patient.");
}
if (cmpstr(wordptr,"brooks") == SUCCESS || cmpstr(wordptr,"brooks2") == SUCCESS ||
cmpstr(wordptr,"brooks3") == SUCCESS || cmpstr(wordptr,"brooks4") == SUCCESS ||
cmpstr(wordptr,"brooks5") == SUCCESS || cmpstr(wordptr,"ega") == SUCCESS){
/* mutually exclusive options */
brooks2 = brooks3 = brooks4 = brooks5 = useegacolors = 0;
if (cmpstr(wordptr,"brooks2") == SUCCESS) brooks2 = 1;
else if (cmpstr(wordptr,"brooks3") == SUCCESS) brooks3 = 1;
else if (cmpstr(wordptr,"brooks4") == SUCCESS) {
brooks4 = 1;
}
else if (cmpstr(wordptr,"brooks5") == SUCCESS) {
brooks5 = 1;
}
else if (cmpstr(wordptr,"ega") == SUCCESS) {
useoriginalcolors = useegacolors = 1;
}
shr = 320;
brooks = 1;
shrgrey = mono = 0;
if (shrpalette == 15){
palnumber = 15;
GetBuiltinPalette('P','1',palnumber);
}
continue;
}
if (cmpstr(wordptr,"dic") == SUCCESS || cmpstr(wordptr,"dic2") == SUCCESS ||
cmpstr(wordptr,"dic3") == SUCCESS || cmpstr(wordptr,"dic4") == SUCCESS ||
cmpstr(wordptr,"dic5") == SUCCESS){
wordptr[0] = 'p';
usepalettedistance = 1;
puts("Palette distance conversion is now on. This takes longer, be patient.");
}
if (cmpstr(wordptr,"pic") == SUCCESS || cmpstr(wordptr,"pic2") == SUCCESS ||
cmpstr(wordptr,"pic3") == SUCCESS || cmpstr(wordptr,"pic4") == SUCCESS ||
cmpstr(wordptr,"pic5") == SUCCESS){
/* mutually exclusive options */
brooks2 = brooks3 = brooks4 = brooks5 = useegacolors = 0;
if (cmpstr(wordptr,"pic2") == SUCCESS) brooks2 = 1;
else if (cmpstr(wordptr,"pic3") == SUCCESS) brooks3 = 1;
else if (cmpstr(wordptr,"pic4") == SUCCESS) {
brooks4 = 1;
}
else if (cmpstr(wordptr,"pic5") == SUCCESS) {
brooks5 = 1;
}
shr256 = 1;
shr = 320;
brooks = 1;
shrgrey = mono = 0;
if (shrpalette == 15){
palnumber = 15;
GetBuiltinPalette('P','1',palnumber);
}
continue;
}
if (cmpstr(wordptr,"shrgrey") == SUCCESS || cmpstr(wordptr,"shrgray") == SUCCESS) {
shr = 320;
shrgrey = mono = 1;
brooks = 0;
continue;
}
if (cmpstr(wordptr,"rgb") == SUCCESS) {
hsl = 0;
continue;
}
if (cmpstr(wordptr,"gs") == SUCCESS) {
usegscolors = 1;
continue;
}
if (cmpstr(wordptr,"gs2") == SUCCESS) {
usegspalette = 1;
continue;
}
if (c == 'T' && d == (char)ASCIIZ) {
tags = 1;
continue;
}
if (c == 'D') {
if (d == (char)ASCIIZ) {
dither = 1;
continue;
}
if (d == 'R' && wordptr[2] == (char) ASCIIZ) {
dither = randomdither = errorsum = 1;
continue;
}
if (d == 'F' && wordptr[2] == (char) ASCIIZ) {
dithertype = FLOYDSTEINBERG;
dither = errorsum = 1;
continue;
}
if (d == 'A') {
if (wordptr[2] == (char) ASCIIZ) {
dithertype == ATKINSON;
dither = errorsum = 1;
continue;
}
else if (wordptr[2] == '2' && wordptr[3] == (char)ASCIIZ) {
dithertype = ATKINSON2;
dither = errorsum = 1;
continue;
}
}
}
if (c == 'O' && d == (char)ASCIIZ) {
useoriginalcolors = usegscolors = 1;
outline = 1;
continue;
}
if (c == 'O' && d == '1') {
if (wordptr[2] == '6' || wordptr[2] == '4') {
useoriginalcolors = usegscolors = 1;
if (wordptr[2] == '6')usesixteencolors = 1;
if (wordptr[2] == '4')usefourteencolors = 1;
continue;
}
}
if (c == 'R') {
if (d == (char)ASCIIZ) jdx = 25;
else jdx = atoi((char *)&wordptr[1]);
if (jdx != 0) {
if (jdx > 0 && jdx < 101) {
bleed = (8 * (100 + jdx))/ 100;
printf("%d percent reduced color bleed\n",jdx);
}
continue;
}
}
/* applesoft-alternate output option for AppleWin Screen Capture Conversions */
/* also applies to A2FC pixel-art (pixel level dithering) output from 24-bit BMP's */
if ((c == 'A' && d == (char)ASCIIZ) || cmpstr(wordptr,"BIN") == SUCCESS) {
/* output AUX,BIN - default is A2FC */
applesoft = 1;
continue;
}
/* VBMP output for DHGR or preview output for LGR/DLGR and SHR */
if ((c == 'V' && d == (char)ASCIIZ) || cmpstr(wordptr,"VBMP") == SUCCESS) {
vbmp = 1;
continue;
}
/* for color output only */
/* create BMP image fragment from Apple II full-screen or (larger) image fragment */
if ((c == 'X' || c == 'Y' || c == 'W' || c == 'H') && (d > (char)47 && d < (char)58)) {
jdx = atoi((char *)&wordptr[1]);
/* range check */
/* as long as there is something in the BMP output file, my range checking is relaxed */
/* if width or height go out of bounds, the BMP output file will be padded with black-space */
if (jdx < 0) continue;
if (c == 'X') {
if (jdx > 139) continue;
fragx = (ushort) jdx;
}
else if (c == 'Y') {
if (jdx > 191) continue;
fragy = (ushort) jdx;
}
else if (c == 'W') {
if (jdx < 1 || jdx > 140) continue;
fragwidth = (ushort) jdx;
}
else if (c == 'H') {
if (jdx < 1 || jdx > 192) continue;
fragheight = (ushort) jdx;
}
frag = 1;
continue;
}
jdx = atoi((char *)&wordptr[0]);
if (jdx == 140) {
/* 140 x 192 x 24-bit color BMP output option - default is 280 x 192 24-bit color BMP */
doublepixel = 0;
continue;
}
if ((c == 'M' && d == (char)ASCIIZ)|| jdx == 384 || jdx == 192 || cmpstr(wordptr,"mono") == SUCCESS) {
/* 560 x 384 or 560 x 192 monochrome BMP output option - default is 280 x 192 24-bit color BMP */
mono = 1;
if (jdx == 192) {
doublepixel = 0;
}
continue;
}
if (c == 'P') {
/* experimental ImageMagick segmentation for SHR output */
/* literal "pim" followed by "seed" palette pathname - sets conversion mode if valid */
if (toupper(wordptr[1]) == 'I') {
if (toupper(wordptr[2]) == 'M') {
if (GetPIMPalettes((char *)&wordptr[3]) == SUCCESS) continue;
puts("Exiting...");
return (1);
}
}
/* other palette routines */
/* first check for a bmp or a pcxfile being used as a palette */
jdx = GetBmpOrPcxPalette((char *)&wordptr[1]);
if (jdx != SUCCESS) {
/* always check for a user definable palette file */
jdx = GetUserPalette((char *)&wordptr[1]);
}
if (jdx == SUCCESS) {
jdx = 6;
d = '6';
}
else {
jdx = atoi((char *)&wordptr[1]);
}
/* palette option - p0 to p16 */
jdx = GetBuiltinPalette(c,d,jdx);
if (jdx != -1) {
palnumber = jdx;
shrpalette = palnumber;
continue;
}
}
if (cmpstr(wordptr,"full") == SUCCESS){
doublegrey = 1;
continue;
}
/* this is somewhat problematic */
/* however some users may find it convenient to output to a different basename */
/* I primarily left this in place to match the older MS-DOS version of this utility
because a fair amount of documentation is already in place for that. */
strcpy(outfile, argv[idx]);
}
}
}
/* do not allow extensions in output file base names */
jdx = 999;
for (idx = 0; outfile[idx] != ASCIIZ; idx++) {
if (outfile[idx] == '.') jdx = idx;
}
if (jdx != 999) outfile[jdx] = ASCIIZ;
jdx = 999;
for (idx = 0; fname[idx] != ASCIIZ; idx++) {
if (fname[idx] == '.') jdx = idx;
}
/* automatic bmp file extension for LGR, DLGR, and SHR output */
/* we can't just assume that the input file is a bmp file for DHGR output
because the input file extension drives the output type
and we run the risk of over-writing the wrong file if the user forgets
to put an extension on the input file name */
if (jdx == 999 && (lores == 1 || shr == 320) && fname[0] != (char)ASCIIZ) {
strcat(fname,".bmp");
for (idx = 0; fname[idx] != ASCIIZ; idx++) {
if (fname[idx] == '.') jdx = idx;
}
}
if (jdx != 999)
{
strcpy(sname, fname);
sname[jdx] = ASCIIZ;
if (outfile[0] == ASCIIZ)strcpy(outfile,sname);
c = toupper(fname[jdx + 1]);
d = toupper(fname[jdx + 2]);
e = toupper(fname[jdx + 3]);
if (longnames != 0) {
/* full-screen formats */
f = toupper(fname[jdx + 4]);
if (c == 'A' && d == '2' && e == 'F' && f == 'C') a2fc = 1;
if (c == 'A' && d == '2' && e == 'F' && f == 'M') a2fc = mono = 1;
/* support for Sheldon Simms tohgr */
if (c == 'D' && d == 'H' && e == 'G' && f == 'R') a2fc = tohgr = 1;
}
/* support for our extensions only */
/* full-screen formats only */
if (c == 'B' && d == 'M' && e == 'P') bmp = 1;
/* process back-up if requested */
if (c == 'B' && d == 'M' && e == '2') bmp = bm2 = 1;
/* full-screen formats */
if (c == '2' && d == 'F' && e == 'C') a2fc = 1;
if (c == 'B' && d == 'I' && e == 'N') auxbin = 1;
if (c == 'A' && d == 'U' && e == 'X') auxbin = 1;
/* image fragments */
if (c == 'D' && d == 'H' && e == 'R') dhr = 1;
if (c== 'S' && d == 'H') {
/* native SHR to M2S format BMP conversion */
if (e == 'R' || e == 'G' || e == '2' || e == '3') shrinput = 1;
}
}
/* native SHR to M2S format BMP conversion
all other options will be ignored */
if (shrinput == 1) return shrtom2s(fname,outfile);
dhrbuf = (uchar *)malloc(32000);
if (dhrbuf == NULL) {
puts("No memory...");
return (1);
}
if (imnumpalettes == 0 && fourbit == 0) {
if (mono == 1) {
if (shrgrey == 1) puts("Palette : 16 level Grey Scale");
else puts("Palette : Black and White Monochrome");
}
else {
printf("Palette : %d - %s\n",palnumber,palname[palnumber]);
}
}
/* color bleed is fixed for optional dither types for now */
/* not all these are implemented for now */
switch(dithertype) {
case FLOYDSTEINBERG: bleed = 16; break;
case JARVIS: bleed = 48; break;
case STUCKI: bleed = 42; break;
case ATKINSON: bleed = 8; break;
case ATKINSON2: bleed = 6; break;
case BURKES:
case SIERRA: bleed = 32; break;
case SIERRATWO: bleed = 16; break;
case SIERRALITE: bleed = 4; break;
}
status = INVALID;
if (bmp == 1) {
if (fourbit != 0) {
status = Convertfourbit(sname,outfile);
free(dhrbuf);
if (status == SUCCESS) return SUCCESS;
return 1;
}
if (imnumpalettes != 0) {
status = ConvertPIM(sname,outfile);
free(dhrbuf);
if (status == SUCCESS) return SUCCESS;
return 1;
}
if (lores == 1 || shr == 320) {
status = ConvertLoResAndSHR(sname,outfile);
free(dhrbuf);
if (status == SUCCESS) return SUCCESS;
return 1;
}
status = ReadHybrid(sname,outfile);
if (status == SUCCESS) {
free(dhrbuf);
return SUCCESS;
}
status = ReadAppleWin(sname,outfile);
if (status == SUCCESS) {
if (applesoft == 1) status = read_2fc(outfile);
else status = read_binaux(outfile);
}
}
if (auxbin == 1) status = read_binaux(sname);
if (a2fc == 1) status = read_2fc(sname);
if (dhr == 1) status = read_dhr(sname);
if (status) {
puts(szTextTitle);
printf("%s is an Unsupported Format or cannot be opened.\n", fname);
free(dhrbuf);
return 1;
}
if (mono == 1) status = save_to_bmp(outfile, doublepixel);
else status = save_to_bmp24(outfile);
if (status == SUCCESS) {
printf("%s.BMP Saved!\n",outfile);
}
else {
printf("Error saving %s.BMP!\n",outfile);
status = 1;
}
free(dhrbuf);
#ifdef MSDOS
puts("Have a Nice Dos!");
#endif
return status;
}