dos33fsprogs/bmp2dhr/b2d.c
Vince Weaver a6212fcfbf b2d: for regular GR the results were only half-screen wide?
think this fix is more or less OK but might break something else
2020-07-23 10:57:32 -04:00

6368 lines
186 KiB
C
Raw Blame History

/* ---------------------------------------------------------------------
Bmp2DHR (C) Copyright Bill Buckels 2014.
All Rights Reserved.
Module Name - Description
-------------------------
b2d.c - main program
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
and related files from your computer now.
Written by: Bill Buckels
Email: bbuckels@mts.net
Version 1.0
Developed between Aug 2014 and December 2014 with "standard parts".
Bmp2DHR reads a monochrome, 16 color, 256 color, or 24 bit BMP and writes Apple
II color or monochrome HGR or DHGR files.
Functional Summary of Bmp2DHR Version 1.0
-----------------------------------------
Input File Size
---------------
Full Screen Color Output - DHGR (default) and HGR (option "hgr")
Size, Nominal Resolution Etc
140 x 192 - verbatim
280 x 192 - lossy or merged
320 x 200 - 280 x 192
560 x 384 - lossy or merged
640 x 400 - 560 x 384
640 x 480 - 560 x 384
Full Screen HGR Monochrome Output - Option "mono"
280 x 192
Full Screen DHGR Monochrome Output - Option "mono"
560 x 192 Monochrome Only - verbatim conversion
560 x 384 Color Only - dithered output
Full Screen and Mixed Screen LGR (option "L") and DLGR (option "DL") Output
40 x 48 and 40 x 40 - LGR verbatim
80 x 48 and 80 x 40 - DLGR verbatim and LGR 2:1 merged scaling
160 x 96 and 160 x 80 - DLGR 2:2 and LGR 4:2 merged scaling
88 x 52 and 176 x 104 - Windowboxed Mini-Pix BMPs - Nominal Output Sizes same as above
320 x 192, 320 x 160, and 320 x 200 - DLGR 4:4 and LGR 8:4 merged scaling
560 x 384 and 560 x 320 - DLGR 7:8 and LGR 14:8 merged scaling
640 x 480 and 640 x 400 - DLGR 8:10 and LGR 16:10 merged scaling
Image Fragment DHGR Color "Sprite" Output - Option "F"
1 x 1 to 140 x 192 - verbatim - results in double-wide output appearance
1 x 1 to 280 x 192 - scaled - results in proportional output appearance
When scale is not set the maximum BMP input resolution for "Sprites" is 140 x
192, and when scale is set the maximum is 280 x 192.
Overlay File Size - 256 color BMP - verbatim sizes
HGR and DHGR Color Output - 140 x 192
HGR Mono Output - 280 x 192
DHGR Mono Output - 560 x 192
Additional Input Files - Text Format
------------------------------------
- Palette Files (various formats)
- User Definable Dither Files (see documentation and read source below)
- Overlay Titling Text (uses built-in TomThumb font)
Note: The Overlay Option for either a BMP or Text overlay does not apply to
image fragment output.
Output Summary
--------------
Bmp2DHR provides 4 primary types of Apple II output:
Default Color and Option "mono" - Full-Screen DHGR files - A2FC and A2FM single-file format.
Option "F"- Image Fragments in color DHGR Format - DHR scanline oriented single file format.
Option "hgr" - Full-Screen HGR files - single file BIN format.
Option "L" and "DL" - Full Screen and Mixed Screen Lo-Res and Double Lo-Res scanline oriented single file
DHGR Optional Alternate Output - Option "A" - AUX,BIN File Pairs instead of A2FC or A2FM files.
LGR and DLGR Optional Alternate Output - Option "A" - SL2 or DL1,DL2 File Pairs instead of SLO or DLO single files
AUX,BIN File Pairs, SL2 files and DL1,DL2 File Pairs are for AppleSoft BASIC programs.
Additional Optional Output includes:
Option "v" - Preview Output in BMP format.
Option "debug" - "debug" output of work files if any in BMP format.
Option "vbmp" - VBMP compatible BMP output (not available as LGR and DLGR output).
Additional Notes
----------------
For primary input Bmp2DHR accepts BMP files in Version 3 format only in a
specific range of input sizes and formats. The size and format of the input
file depends on the type of desired primary output and the rendering options
that have been selected.
Rendering options fall into several categories and where considered practically
possible and where it makes sense given the constraints and scope of Bmp2DHR,
all rendering options are available for all output.
Specific rendering options for specific output are also available.
Constraints are also also enforced by both BMP sizes and formats, and whether
output is color or monochrome, and also if external rendering is being used
like dithering in editors like The GIMP.
In the case of externally rendered input files, Bmp2DHR is only used as a
direct pixel converter to "pass-through" the BMP input file "verbatim". In this
case, the color palette needs to exactly match Bmp2DHR's color palette, and the
resolution needs to exactly match the Apple II output resolution.
560 x 384 and 560 x 192 BMPs are used as input files for DHGR monochrome output, and
280 x 192 BMPs are used as input files for HGR monochrome output.
Monochrome BMP files of 280 x 192 or 560 x 192 are required for verbatim
"pass-through" and output to Apple II HGR or DHGR files respectively. Palette
matched 140 x 192 color BMP files are required to pass-through properly to
color HGR or DHGR output. Color palettes that are used in external editors to
prepare pass-through input must either be imported into Bmp2DHR, or Bmp2DHR's
palettes must be imported into external editors.
Preview Output from Bmp2DHR can also generally be re-edited (carefully) and
reprocessed using direct pixel "passthrough" which is essentially the same
process as using an external editor to render and dither.
Secondary Input Files
---------------------
Bmp2DHR also accepts several secondary input files.
- Text Files for titling using a built-in font (HGR and DHGR full-screen conversion only)
- Palette Files in several text-based formats
- External Error Diffusion user-defined Dither Patterns in text format
- 256 color BMP files for overlaying the input image with verbatim text and simple pixel graphics
(HGR and DHGR full-screen conversion only)
For DHGR sprite output external Palette files and Dither Pattern files can be
used, but titling and overlaying is targeted at full-screen output.
Apple II Output Format Specification Summary
--------------------------------------------
DHGR output (default)
For DHGR default output, the A2FM and A2FC file extensions are just a naming
convention so the user can tell the difference between a monochrome and color
Apple II file; they are both binary DHGR files with a raw Auxiliary DHGR Memory
"DUMP" of 8192 bytes, followed by a raw Main DHGR Memory "DUMP" of 8192 bytes,
totalling 16384 bytes. These are stored in ProDOS as Binary FileType $06 with
an Auxiliary Type of either $2000 or $4000, which is the load address of the
DHGR screen.
Alternate Default Output (option "A")
Alternate output of a split version of the A2FC format is optionally available
using option "A". Sometimes called AUX,BIN file pairs, these are easier to load
in an AppleSoft BASIC program. They are also stored in ProDOS as Binary
FileType $06 with an Auxiliary Type of either $2000 or $4000 and are 8192 bytes
each.
For LGR and DLGR conversion the equivalent Alternate Output is also in "BSAVED" file
format. LGR and DLGR Apple II files are stored in ProDOS as Binary FileType $06 with
an Auxiliary Type of $0400.
HGR output (option "hgr")
For HGR output, the "BIN" file extension is used. These are indistinguishable
from DHGR BIN files in an AUX,BIN file pair which are also the same ProDOS file
type $06 and length of 8192 bytes, so a loader must be aware of the specific
files to load these properly.
Image Fragment ("Sprite") output (option "F")
Sprite (image fragment) format Output is an option. Sprite Output and normal
HGR and DHGR full-screen output are mutually exclusive (to some degree). If you
provide Bmp2DHR with a BMP image fragment but you don't specify Option "F" a
full-screen Apple II A2FC File will be produced with the Sprite in the top left
corner. This is so you can conveniently look at the sprite on an Apple II
display. The latest version of my cc65 dhishow slideshow also loads Sprites so
you can use that for the same purpose too.
About Sprites
The Sprites produced by this utility are in XPACK's DHR format, but XPACK only
produces Full Screen DHGR images so this is something new.
The DHR (Double Hi-Res Raster) Image Format
The image fragments produced by Bmp2DHR have an extension of DHR. Like A2FC and
AUX,BIN file pairs, they are stored on an Apple II Disk as ProDOS FileType $06
with an Auxiliary Type of $2000 or $4000 by default. On a DOS 3.3 disk they are
stored with header information required by DOS 3.3.
The Header of a DHR is in two parts;
3 bytes of ID data with the letters 'D', 'H', 'R' in upper-case
1 byte - width in bytes (multiples of 4 bytes - 7 pixels)
1 byte - height in rasters
The DHR is a raster based image with scanlines of raw DHGR data alternating
between auxiliary and main memory. Therefore a simple BASIC program cannot
easily load these since the DHGR screen is interleaved the same way that the
HGR screen is interleaved and not linear. Bank switching between auxiliary and
main memory banks 0 (main board) and 1 (language card) is also not easy in a
BASIC program.
For a full-screen DHR, there are 192 pairs of rasters, each of 40 bytes of
auxiliary memory data followed by 40 bytes of main memory data. This keeps bank
switching to a minimum and allows for linear reading from disk or buffer.
The full screen DHR loads raster by raster and displays as quickly as a
buffered read can display on the Apple II. At 15365 bytes per screen this
format provides a modest disk space saving over the 16384 bytes of the A2FC or
AUX,BIN equivalent.
A caveat for any file in DHR raster format is the 4 byte / 7 pixel pattern of
the DHGR display. The width descriptor in the header is given in byte width
rather than pixel width. Image fragments in DHGR must necessarily be aligned on
4 byte boundaries to display properly. This utility pads DHR formats as
required in an optional background color if desired.
By comparison, HGR image fragments (not produced by Bmp2DHR) are aligned on 2
byte boundaries for proper display but they are still somewhat recognizable if
not aligned properly.If DHGR image fragments are not aligned on 4 byte
boundaries they are a mess.
If a programmer wanted to load these according to a specific position on the
DHGR it would be possible to give the starting scanline and starting byte to
the desired position on the screen, and store that as the Auxiliary Type
instead:
1. The program would read the header and perform a file integrity check to
ensure that the file size was as expected.
2. Part of the verification would also be to determine if the Auxiliary Type
fell within the DHGR visible screen boundaries and if the file itself would
fit.
3. Having satisfied this requirement the image fragment could be positioned at
that point by the program.
Doing so would save disk-space and load time when constructing a pre-planned
screen in a DHGR program, since full-screens are generally larger by comparison
to creating full-screens from fragments.
Additional Remarks
------------------
This program has many more options. The source code comments and the
documentation can be reviewed for additional information.
------------------------------------------------------------------------ */
/* ***************************************************************** */
/* ========================== includes ============================= */
/* ***************************************************************** */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <math.h>
#include <ctype.h>
#include "b2d.h"
/* ***************************************************************** */
/* ======================= string data ============================= */
/* ***************************************************************** */
char *title = "Bmp2DHR Version 1.1 (c) Copyright Bill Buckels 2015.\nAll Rights Reserved.";
char *usage[] = {
"Usage: \"b2d input.bmp options\"",
"Input format: mono, 16 color, 256 color, or 24-bit Version 3 uncompressed BMP",
"Default DHGR Colored Output: Full Screen Apple II A2FC file",
"Optional Usage: \"b2d input.bmp hgr options\"",
" For HGR Colored Output: Full Screen Apple II BIN file",
"Optional Usage: \"b2d input.bmp mono options\"",
" For Mono Output: Full Screen Apple II DHGR A2FM or HGR BIN file",
"Free Scaled Input Sizes: Full Screen (default) or DHGR Sprite (option F) output",
" Full Scale: from 1 x 1 to 140 x 192 (default) - HGR and DHGR",
" Half Scale: from 1 x 1 to 280 x 192 (scaling option S2) - HGR and DHGR",
"HGR and DHGR Fixed Scaled Input Sizes: Full Screen Output (default)",
" 140 x 192 - Full Scale (for LGR use 40 x 48, for DLGR use 80 x 48)",
" 280 x 192 - Double Width Scale (for LGR and DLGR use 160 x 96)",
" 320 x 200 - Classic Size (also used for LGR and DLGR windowboxed output)",
" 560 x 384 - Quadruple Width, Double Height Scale (also for LGR and DLGR)",
" 640 x 400 - Classic Size (also used for LGR and DLGR mixed screen output)",
" 640 x 480 - Classic Size (also used for LGR and DLGR full screen output)",
"Full Screen Dithered Output (optional): Option D (D1 to D9)",
"Optional Usage: \"b2d input.bmp L (or DL) options\"",
" For Color LGR or DLGR Full Screen or Mixed Screen (option \"TOP\") Output",
"See documentation for more information including additional input size info",
NULL};
char *dithertext[] = {
"Floyd-Steinberg",
"Jarvis",
"Stucki",
"Atkinson",
"Burkes",
"Sierra",
"Sierra Two",
"Sierra Lite",
"Buckels",
"Custom"};
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"};
/* ***************************************************************** */
/* ========================== code ================================= */
/* ***************************************************************** */
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;
}
/* returns 255 if color number or color name are invalid */
uint8_t PaintByNumbers(char *str)
{
int idx;
uint8_t c = toupper(str[0]);
if (str[1] == (char) 0) {
/* alpha mnemonic */
if (c > 64 && c < 81) return (uint8_t) (c - 65);
}
/* non numeric argument so check for color names */
/* add additional color names here if required */
if (c > 57) {
c = 255;
if (cmpstr("black", str) == SUCCESS)c = 0;
else if (cmpstr("red", str) == SUCCESS)c = 1;
else if (cmpstr("dblue", str) == SUCCESS)c = 2;
else if (cmpstr("purple", str) == SUCCESS)c = 3;
else if (cmpstr("dgreen", str) == SUCCESS)c = 4;
else if (cmpstr("dgray", str) == SUCCESS)c = 5;
else if (cmpstr("dgrey", str) == SUCCESS)c = 5;
else if (cmpstr("mblue", str) == SUCCESS)c = 6;
else if (cmpstr("lblue", str) == SUCCESS)c = 7;
else if (cmpstr("brown", str) == SUCCESS)c = 8;
else if (cmpstr("orange", str) == SUCCESS)c = 9;
else if (cmpstr("lgray", str) == SUCCESS)c = 10;
else if (cmpstr("lgrey", str) == SUCCESS)c = 10;
else if (cmpstr("pink", str) == SUCCESS)c = 11;
else if (cmpstr("lgreen", str) == SUCCESS)c = 12;
else if (cmpstr("yellow", str) == SUCCESS)c = 13;
else if (cmpstr("aqua", str) == SUCCESS)c = 14;
else if (cmpstr("white", str) == SUCCESS)c = 15;
}
else {
if (c == '0' && str[1] == (char)0) {
c = 0;
}
else {
c = 255;
idx = atoi(str);
if (idx > -1 && idx < 16) c = (uint8_t) idx;
}
}
return c;
}
uint16_t Motorola16(uint16_t val)
{
uint8_t buf[2];
uint16_t *ptr;
/* msb in smallest address */
buf[0] = (uint8_t) (val % 256); val = val/256;
buf[1] = (uint8_t) (val % 256);
ptr = (uint16_t *)&buf[0];
val = ptr[0];
return val;
}
void WriteDosHeader(FILE *fp, uint16_t fl, uint16_t fa)
{
/* if CiderPress tags are turned-on I assume that the header is not required
since presumably the tags will be used to place the file properly and ciderpress
will create the DOS 3.3 header based on the file attribute preservation tag.
*/
if (dosheader == 1 && tags == 0) {
fa = Motorola16(fa);/* file bload address - not including this header */
fl = Motorola16(fl);/* file length - not including this header */
fwrite((char *)&fa,sizeof(uint16_t),1,fp);
fwrite((char *)&fl,sizeof(uint16_t),1,fp);
}
}
/*
Photoshop Luminosity Average
Formula for the Luminosity Average:
AvgLuma = 0.299<EFBFBD>AvgRed + 0.587<EFBFBD>AvgGreen + 0.114<EFBFBD>AvgBlue
*/
/* set luma to different values for closest color */
int lumaREQ = 601, lumaRED = 299, lumaGREEN = 587, lumaBLUE = 114;
double dlumaRED, dlumaGREEN, dlumaBLUE;
void setluma()
{
switch(lumaREQ)
{
case 240: /* SMPTE 240M transitional coefficients */
lumaRED = 212; lumaGREEN = 701; lumaBLUE = 87;
dlumaRED = 0.212; dlumaGREEN = 0.701; dlumaBLUE = 0.087;
break;
case 911: /* Sheldon Simms - tohgr */
lumaRED = 77; lumaGREEN = 151; lumaBLUE = 28;
dlumaRED = 0.077;dlumaGREEN = 0.151; dlumaBLUE = 0.028;
break;
case 411: /* The GIMP color managed */
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 */
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 */
default: lumaRED = 299; lumaGREEN = 587; lumaBLUE = 114;
dlumaRED = 0.299; dlumaGREEN = 0.587; dlumaBLUE = 0.114;
break;
}
}
/* intialize the values for the current palette */
void InitDoubleArrays()
{
int i;
double dr, dg, db, dthreshold;
unsigned r, g, b;
/* 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);
}
/* array for matching closest color in palette
threshold reduced by 25% */
if (threshold == 0) {
dthreshold = 0.75;
}
else {
dthreshold = (double) threshold;
if (xmatrix != 2) dthreshold *= 0.5;
dthreshold = (double) (100.0 - dthreshold) / 100;
}
for (i=0;i<16;i++) {
dr = (double) rgbArray[i][0];
dg = (double) rgbArray[i][1];
db = (double) rgbArray[i][2];
dr *= dthreshold;
dg *= dthreshold;
db *= dthreshold;
rgbDoubleBrighten[i][0] = dr;
rgbDoubleBrighten[i][1] = dg;
rgbDoubleBrighten[i][2] = db;
rgbLumaBrighten[i] = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
}
if (threshold == 0) {
dthreshold = 1.25;
}
else {
dthreshold = (double) threshold;
if (xmatrix != 2) dthreshold *= 0.5;
dthreshold = (double) (100.0 + dthreshold) / 100;
}
for (i=0;i<16;i++) {
dr = (double) rgbArray[i][0];
dg = (double) rgbArray[i][1];
db = (double) rgbArray[i][2];
dr *= dthreshold;
if (dr > 255.0) dr = 255.0;
dg *= dthreshold;
if (dg > 255.0) dg = 255.0;
db *= dthreshold;
if (db > 255.0) db = 255.0;
rgbDoubleDarken[i][0] = dr;
rgbDoubleDarken[i][1] = dg;
rgbDoubleDarken[i][2] = db;
rgbLumaDarken[i] = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
}
}
/* select current palette */
void GetBuiltinPalette(int16_t palidx, int16_t previewidx, int16_t pseudo)
{
int16_t i,j;
uint8_t r,g,b;
/* set conversion colors */
switch(palidx) {
case 16:/* optional NTSC palette from tohgr - used for HGR conversion */
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:
/* the infamous pseudo palette */
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;
case 14: /* Robert Munafo - http://mrob.com/pub/xapple2/colors.html */
/* NTSC Palette used by Cybernesto in VBMP GIMP tutorial */
for (i=0;i<16;i++) {
rgbArray[i][0] = Cybernesto[i][0];
rgbArray[i][1] = Cybernesto[i][1];
rgbArray[i][2] = Cybernesto[i][2];
}
break;
case 13: /* Jace emulator NTSC palette */
for (i=0;i<16;i++) {
rgbArray[i][0] = Jace[i][0];
rgbArray[i][1] = Jace[i][1];
rgbArray[i][2] = Jace[i][2];
}
break;
case 12: /* Super Convert HGR and DHGR conversion colors */
/* same as kegs32 colors */
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 IBM-PC legacy palettes from BMPA2FC */
/* used for color substitution - not Apple II colors */
case 11: for (i=0;i<16;i++) {
/* default colors from some old ZSoft 16 color PCX */
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++) {
/* colors from VGA bios */
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++) {
/* colors from Windows Paint XP - 16 color BMP */
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++) {
/* colors from MSPaint Windows 3.1 - 16 color BMP */
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++) {
/* "canvas" colors from BmpA2FC */
rgbArray[i][0] = rgbCanvasArray[i][0];
rgbArray[i][1] = rgbCanvasArray[i][1];
rgbArray[i][2] = rgbCanvasArray[i][2];
}
break;
case 6: /* user definable imported palette file */
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;
case 4: /* wikipedia Apple II NTSC colors */
for (i=0;i<16;i++) {
rgbArray[i][0] = wikipedia[i][0];
rgbArray[i][1] = wikipedia[i][1];
rgbArray[i][2] = wikipedia[i][2];
}
break;
case 3: /* Current AppleWin Version's sort-of NTSC colors */
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: /* Previous AppleWin Version's sort-of NTSC colors */
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: /* CiderPress RGB File Viewer colors */
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: /* kegs32 RGB colors - same as Super Convert */
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;
case 5: /* NTSC palette from tohgr - used for default HGR and DHGR conversion */
default:
for (i=0;i<16;i++) {
rgbArray[i][0] = grpal[i][0];
rgbArray[i][1] = grpal[i][1];
rgbArray[i][2] = grpal[i][2];
}
palidx = 5;
break;
}
/* set preview colors */
switch(previewidx) {
case 16:/* HGR conversion - optional palette from tohgr */
for (i=0;i<16;i++) {
rgbPreview[i][0] = hgrpal[i][0];
rgbPreview[i][1] = hgrpal[i][1];
rgbPreview[i][2] = hgrpal[i][2];
}
break;
case 15: /* the infamous pseudo palette */
for (i=0;i<16;i++) {
rgbPreview[i][0] = PseudoPalette[i][0];
rgbPreview[i][1] = PseudoPalette[i][1];
rgbPreview[i][2] = PseudoPalette[i][2];
}
break;
case 14: /* Robert Munafo - http://mrob.com/pub/xapple2/colors.html */
/* NTSC Palette used by Cybernesto in VBMP GIMP tutorial */
for (i=0;i<16;i++) {
rgbPreview[i][0] = Cybernesto[i][0];
rgbPreview[i][1] = Cybernesto[i][1];
rgbPreview[i][2] = Cybernesto[i][2];
}
break;
case 13: /* Jace emulator NTSC palette */
for (i=0;i<16;i++) {
rgbPreview[i][0] = Jace[i][0];
rgbPreview[i][1] = Jace[i][1];
rgbPreview[i][2] = Jace[i][2];
}
break;
case 12: /* Super Convert HGR and DHGR conversion colors */
/* same as kegs32 colors */
for (i=0;i<16;i++) {
rgbPreview[i][0] = SuperConvert[i][0];
rgbPreview[i][1] = SuperConvert[i][1];
rgbPreview[i][2] = SuperConvert[i][2];
}
break;
/* 5 IBM-PC VGA legacy palettes from BMPA2FC */
/* used for color substitution - not Apple II colors */
case 11: for (i=0;i<16;i++) {
/* default colors from some old ZSoft 16 color PCX */
rgbPreview[i][0] = rgbPcxArray[i][0];
rgbPreview[i][1] = rgbPcxArray[i][1];
rgbPreview[i][2] = rgbPcxArray[i][2];
}
break;
case 10: for (i=0;i<16;i++) {
/* colors from VGA bios */
rgbPreview[i][0] = rgbVgaArray[i][0];
rgbPreview[i][1] = rgbVgaArray[i][1];
rgbPreview[i][2] = rgbVgaArray[i][2];
}
break;
case 9: for (i=0;i<16;i++) {
/* colors from Windows Paint XP - 16 color BMP */
rgbPreview[i][0] = rgbXmpArray[i][0];
rgbPreview[i][1] = rgbXmpArray[i][1];
rgbPreview[i][2] = rgbXmpArray[i][2];
}
break;
case 8: for (i=0;i<16;i++) {
/* colors from MSPaint Windows 3.1 - 16 color BMP */
rgbPreview[i][0] = rgbBmpArray[i][0];
rgbPreview[i][1] = rgbBmpArray[i][1];
rgbPreview[i][2] = rgbBmpArray[i][2];
}
break;
case 7: for (i=0;i<16;i++) {
/* "canvas" colors from BmpA2FC */
rgbPreview[i][0] = rgbCanvasArray[i][0];
rgbPreview[i][1] = rgbCanvasArray[i][1];
rgbPreview[i][2] = rgbCanvasArray[i][2];
}
break;
case 6: /* user definable imported palette file */
for (i=0;i<16;i++) {
rgbPreview[i][0] = rgbUser[i][0];
rgbPreview[i][1] = rgbUser[i][1];
rgbPreview[i][2] = rgbUser[i][2];
}
break;
case 4: /* wikipedia Apple II NTSC colors */
for (i=0;i<16;i++) {
rgbPreview[i][0] = wikipedia[i][0];
rgbPreview[i][1] = wikipedia[i][1];
rgbPreview[i][2] = wikipedia[i][2];
}
break;
case 3: /* Current AppleWin Version's sort-of NTSC colors */
for (i=0;i<16;i++) {
rgbPreview[i][0] = awinnewcolors[i][0];
rgbPreview[i][1] = awinnewcolors[i][1];
rgbPreview[i][2] = awinnewcolors[i][2];
}
break;
case 2: /* Previous AppleWin Version's sort-of NTSC colors */
for (i=0;i<16;i++) {
rgbPreview[i][0] = awinoldcolors[i][0];
rgbPreview[i][1] = awinoldcolors[i][1];
rgbPreview[i][2] = awinoldcolors[i][2];
}
break;
case 1: /* CiderPress RGB File Viewer colors */
for (i=0;i<16;i++) {
rgbPreview[i][0] = ciderpresscolors[i][0];
rgbPreview[i][1] = ciderpresscolors[i][1];
rgbPreview[i][2] = ciderpresscolors[i][2];
}
break;
case 0: /* kegs32 RGB colors - same as Super Convert */
for (i=0;i<16;i++) {
rgbPreview[i][0] = kegs32colors[i][0];
rgbPreview[i][1] = kegs32colors[i][1];
rgbPreview[i][2] = kegs32colors[i][2];
}
break;
case 5: /* NTSC palette from tohgr - used for default HGR and DHGR conversion */
default:
for (i=0;i<16;i++) {
rgbPreview[i][0] = grpal[i][0];
rgbPreview[i][1] = grpal[i][1];
rgbPreview[i][2] = grpal[i][2];
}
previewidx = 5;
break;
}
/* set-up the HGR conversion palette based-on the colors that were removed from palette 5 */
/* 3 options are available - 6 colors, 4 color Orange-Blue, or 4 color Green-Violet */
if (hgroutput == 1) {
for (i=0;i<16;i++) {
if (grpal[i][0] == 0 && grpal[i][1] == 0 && grpal[i][2] == 0) {
rgbPreview[i][0] = rgbArray[i][0] = 0;
rgbPreview[i][1] = rgbArray[i][1] = 0;
rgbPreview[i][2] = rgbArray[i][2] = 0;
}
}
}
for (i=0;i<16;i++) {
/* verbatim match - 4-bits deep not 8 */
rgbAppleArray[i][0] = rgbArray[i][0] >> 4;
rgbAppleArray[i][1] = rgbArray[i][1] >> 4;
rgbAppleArray[i][2] = rgbArray[i][2] >> 4;
/* match VBMP color palette to the current conversion palette */
rgbVBMP[i][0] = rgbArray[i][0];
rgbVBMP[i][1] = rgbArray[i][1];
rgbVBMP[i][2] = rgbArray[i][2];
}
/* no need to clip mono - the mono palette has only 2 colors */
if (paletteclip == 1 && mono == 0) {
/* command options "CV" or "CP" (clip view or clip palette) */
/* not implemented for preview or for verbatim match */
/* note that verbatim match is only 4-bits deep so already clips */
/* clipping filter for dirty blacks and whites */
/* borrowed from Sheldon Simms */
/* but this may have other adverse effects so it is optional */
rgbArray[0][RED] = 1;
rgbArray[0][GREEN] = 4;
rgbArray[0][BLUE] = 8;
rgbArray[15][RED] = 248;
rgbArray[15][GREEN] = 250;
rgbArray[15][BLUE] = 244;
}
if(pseudo != 1) {
if (quietmode == 1) {
if (mono == 1) puts("Black and White Monochrome Palette");
else printf("Palette %d: %s Colors\nPreview Palette %d: %s Colors\n",palidx,palname[palidx],previewidx,palname[previewidx]);
}
}
}
/* build pseudo-palettes by using the average rgb values of two or more palettes into one */
/* called from main() before setting the palettes
and after an external user definable palette has been set (if any) */
void BuildPseudoPalette(int16_t palidx)
{
int16_t i,j,k,idx;
uint16_t gun;
/* a pseudopalette cannot be used if palette 15 is selected as a conversion palette */
if ((palidx < 0 || palidx > 16) || palidx == 15) return;
/* get the initial values */
/* call the palette routine before it is actually used to select the
conversion and preview palette to avoid doing so much duplicate code */
GetBuiltinPalette(palidx,palidx,1);
for (i=0;i<16;i++) {
for (j=0;j<3;j++) {
pseudowork[i][j] = (uint16_t)rgbArray[i][j];
}
}
/* merge the values from the work buffers into the pseudo-palette */
/* accumulate the additional values */
for (k = 0; k < pseudocount;k++) {
idx = pseudolist[k];
GetBuiltinPalette(idx,idx,1);
for (i=0;i<16;i++) {
for (j=0;j<3;j++) {
pseudowork[i][j] += rgbArray[i][j];
}
}
}
pseudocount++;
for (i=0;i<16;i++) {
for (j=0;j<3;j++) {
/* basic linear color distance */
/* use the average rgb values */
/* no attempt to avoid rounding */
gun = pseudowork[i][j]/pseudocount;
PseudoPalette[i][j] = (uint8_t)gun;
}
}
pseudocount--;
/* if quiet mode is set print the final values */
if (outputtype != SPRITE_OUTPUT) {
if (quietmode == 0){
/* rgb values can be redirected to a text file and used as an external palette
for subsequnet conversions and/or whatever else this is useful for */
for (i=0;i<16;i++)
printf("%d,%d,%d\n",PseudoPalette[i][0],PseudoPalette[i][1],PseudoPalette[i][2]);
}
}
/* for normal output print the palette list */
if (quietmode == 1) {
printf("Pseudo Palette: %d (%s)",palidx,palname[palidx]);
for (k = 0; k < pseudocount;k++) {
idx = pseudolist[k];
printf(" + %d (%s)",idx,palname[idx]);
}
printf("\n");
}
}
/* use CCIR 601 luminosity to get closest color in current palette */
/* based on palette that has been selected for conversion */
uint8_t GetMedColor(uint8_t r, uint8_t g, uint8_t b, double *paldistance)
{
uint8_t drawcolor;
double dr, dg, db, diffR, diffG, diffB, luma, lumadiff, distance, prevdistance;
int i;
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;
paldistance[0] = prevdistance;
/* get color distance to rest of palette colors */
for (i=1;i<16;i++) {
/* error test for doing dithered HGR */
/* test with a 4 color palette */
if (dither7 != (uint8_t) 0) {
/* dither7 is set in FloydSteinberg() function */
if (dither7 == 'O') {
/* 'O' - orange-blue palette */
if (i != LOMEDBLUE && i!= LOORANGE && i!= LOWHITE) continue;
}
else {
/* 'G' - green-violet palette */
if (i != LOPURPLE && i!= LOLTGREEN && i!= LOWHITE) 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;
paldistance[0] = prevdistance;
drawcolor = (uint8_t)i;
}
}
return drawcolor;
}
/* use CCIR 601 luminosity to get closest color in current palette */
/* match values have been decreased by user-defined threshold */
/* brightens darker colors by promoting them */
uint8_t GetHighColor(uint8_t r, uint8_t g, uint8_t b, double *paldistance)
{
uint8_t drawcolor;
double dr, dg, db, diffR, diffG, diffB, luma, lumadiff, distance, prevdistance;
int i;
dr = (double)r;
dg = (double)g;
db = (double)b;
luma = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
lumadiff = rgbLumaBrighten[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 = (rgbDoubleBrighten[0][0]-dr)/255.0;
diffG = (rgbDoubleBrighten[0][1]-dg)/255.0;
diffB = (rgbDoubleBrighten[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;
paldistance[0] = prevdistance;
/* get color distance to rest of palette colors */
for (i=1;i<16;i++) {
/* error test for doing dithered HGR */
/* test with a 4 color palette */
if (dither7 != (uint8_t) 0) {
/* dither7 is set in FloydSteinberg() function */
if (dither7 == 'O') {
/* 'O' - orange-blue palette */
if (i != LOMEDBLUE && i!= LOORANGE && i!= LOWHITE) continue;
}
else {
/* 'G' - green-violet palette */
if (i != LOPURPLE && i!= LOLTGREEN && i!= LOWHITE) continue;
}
}
/* get color distance of to this index */
lumadiff = rgbLumaBrighten[i]-luma;
diffR = (rgbDoubleBrighten[i][0]-dr)/255.0;
diffG = (rgbDoubleBrighten[i][1]-dg)/255.0;
diffB = (rgbDoubleBrighten[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;
paldistance[0] = prevdistance;
drawcolor = (uint8_t)i;
}
}
return drawcolor;
}
/* use CCIR 601 luminosity to get closest color in current palette */
/* match values have been increased by user-defined threshold */
/* darkens lighter colors by demoting them */
uint8_t GetLowColor(uint8_t r, uint8_t g, uint8_t b, double *paldistance)
{
uint8_t drawcolor;
double dr, dg, db, diffR, diffG, diffB, luma, lumadiff, distance, prevdistance;
int i;
dr = (double)r;
dg = (double)g;
db = (double)b;
luma = (dr*lumaRED + dg*lumaGREEN + db*lumaBLUE) / (255.0*1000);
lumadiff = rgbLumaDarken[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 = (rgbDoubleDarken[0][0]-dr)/255.0;
diffG = (rgbDoubleDarken[0][1]-dg)/255.0;
diffB = (rgbDoubleDarken[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;
paldistance[0] = prevdistance;
/* get color distance to rest of palette colors */
for (i=1;i<16;i++) {
/* error test for doing dithered HGR */
/* test with a 4 color palette */
if (dither7 != (uint8_t) 0) {
/* dither7 is set in FloydSteinberg() function */
if (dither7 == 'O') {
/* 'O' - orange-blue palette */
if (i != LOMEDBLUE && i!= LOORANGE && i!= LOWHITE) continue;
}
else {
/* 'G' - green-violet palette */
if (i != LOPURPLE && i!= LOLTGREEN && i!= LOWHITE) continue;
}
}
/* get color distance of to this index */
lumadiff = rgbLumaDarken[i]-luma;
diffR = (rgbDoubleDarken[i][0]-dr)/255.0;
diffG = (rgbDoubleDarken[i][1]-dg)/255.0;
diffB = (rgbDoubleDarken[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;
paldistance[0] = prevdistance;
drawcolor = (uint8_t)i;
}
}
return drawcolor;
}
/* switchboard function to handle cross-hatched and non-cross-hatched output */
/* keeps the conditionals out of the main loop */
uint8_t GetDrawColor(uint8_t r, uint8_t g, uint8_t b, int x, int y)
{
/* additional vars for future */
double distance, lowdistance, highdistance;
uint8_t drawcolor, lowcolor, highcolor;
uint8_t red = (uint8_t)(r >> 4),
green = (uint8_t)(g >> 4),
blue = (uint8_t)(b >> 4);
int i;
/* quick check for verbatim match */
for (i = 0; i < 16; i++) {
/* error test for doing dithered HGR */
/* test with a 4 color palette */
if (i!= 0 && dither7 != (uint8_t) 0) {
/* dither7 is set in FloydSteinberg() function */
if (dither7 == 'O') {
/* 'O' - orange-blue palette */
if (i != LOMEDBLUE && i!= LOORANGE && i!= LOWHITE) continue;
}
else {
/* 'G' - green-violet palette */
if (i != LOPURPLE && i!= LOLTGREEN && i!= LOWHITE) continue;
}
}
if (rgbAppleArray[i][0] == red &&
rgbAppleArray[i][1] == green &&
rgbAppleArray[i][2] == blue) return (uint8_t)i;
}
/* non-cross-hatched output */
if (threshold == 0 && ymatrix == 0) return GetMedColor(r,g,b,&distance);
if (ymatrix != 0) {
switch(ymatrix) {
case 1: return GetLowColor(r,g,b,&lowdistance);
case 3: return GetHighColor(r,g,b,&highdistance);
case 2:
default:return GetMedColor(r,g,b,&distance);
}
}
/* patterned cross-hatching */
/* the thresholds are percentage based */
/* with a user definable threshold */
switch(xmatrix)
{
/* patterns 1, 2, 3 - 2 x 2 patterned cross-hatching */
case 1:
/* low, med
med, low
*/
if (y % 2 == 0) {
if (x%2 == 1) return GetMedColor(r,g,b,&distance);
return GetLowColor(r,g,b,&lowdistance);
}
if (x%2 == 0) return GetMedColor(r,g,b,&distance);
return GetLowColor(r,g,b,&lowdistance);
case 3:
/* high, med
med, high
*/
if (y % 2 == 0) {
if (x%2 == 1) return GetMedColor(r,g,b,&distance);
return GetHighColor(r,g,b,&highdistance);
}
if (x%2 == 0) return GetMedColor(r,g,b,&distance);
return GetHighColor(r,g,b,&highdistance);
case 2:
default:
/* high, low
low, high
*/
if (y % 2 == 0) {
if (x%2 == 1) return GetLowColor(r,g,b,&lowdistance);
return GetHighColor(r,g,b,&highdistance);
}
if (x%2 == 0) return GetLowColor(r,g,b,&lowdistance);
return GetHighColor(r,g,b,&highdistance);
}
#ifndef TURBOC
/* never gets to here */
return GetMedColor(r,g,b,&distance);
#endif
}
/* 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,uint8_t drawcolor)
{
int xoff, pattern;
uint8_t *ptraux, *ptrmain;
pattern = (x%7);
xoff = HB[y] + ((x/7) * 2);
ptraux = (uint8_t *) &dhrbuf[xoff-0x2000];
ptrmain = (uint8_t *) &dhrbuf[xoff];
switch(pattern)
{
/* left this here for reference
uint8_t 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, uint8_t drawcolor)
{
int xoff, pixel;
uint8_t *ptr;
if (x > 559) return;
xoff = HB[y] + (x/14);
pixel = (x%14);
if (pixel > 6) {
/* main memory */
pixel -= 7;
ptr = (uint8_t *) &dhrbuf[xoff];
}
else {
/* auxiliary memory */
ptr = (uint8_t *) &dhrbuf[xoff-0x2000];
}
if (drawcolor != 0) {
/* white */
ptr[0] |= dhwmono[pixel]; /* inclusive OR */
}
else {
/* black */
ptr[0] &= dhbmono[pixel]; /* bitwise AND */
}
}
/* monochrome HGR 280 x 192 */
void hrmonoplot(int x, int y, uint8_t drawcolor)
{
int xoff, pixel;
uint8_t *ptr;
if (x > 279) return;
xoff = HB[y] + (x/7);
pixel = (x%7);
/* main memory */
ptr = (uint8_t *) &dhrbuf[xoff-0x2000];
if (drawcolor != 0) {
/* white */
ptr[0] |= dhwmono[pixel]; /* inclusive OR */
}
else {
/* black */
ptr[0] &= dhbmono[pixel]; /* bitwise AND */
}
}
void dhrfill(int y,uint8_t drawcolor)
{
int xoff, x;
uint8_t *ptraux, *ptrmain;
xoff = HB[y];
ptraux = (uint8_t *) &dhrbuf[xoff-0x2000];
ptrmain = (uint8_t *) &dhrbuf[xoff];
for (x = 0,xoff=0; x < 20; x++) {
ptraux[xoff] = dhrbytes[drawcolor][0];
ptrmain[xoff] = dhrbytes[drawcolor][1]; xoff++;
ptraux[xoff] = dhrbytes[drawcolor][2];
ptrmain[xoff] = dhrbytes[drawcolor][3]; xoff++;
}
}
/* initialize the scanlines in the write buffer
to the background color
this doesn't matter for a full-screen image
*/
void dhrclear()
{
int y;
uint8_t drawcolor;
memset(dhrbuf,0,16384);
if (backgroundcolor == LOBLACK) return;
drawcolor = (uint8_t)backgroundcolor;
for (y=0;y<192;y++) dhrfill(y,drawcolor);
}
/* mono-spaced "tom thumb" 4 x 6 font */
/* using a byte map to gain a little speed at the expense of memory */
/* a bitmap could have been encoded into nibbles of 3 bytes per character
rather than the 18 bytes per character that I am using
but the trade-off in the speed in unmasking would have slowed this down */
void plotthumbDHGR(unsigned char ch, unsigned x, unsigned y,
unsigned char fg, unsigned char bg)
{
unsigned offset, x1, x2=x+3, y2=y+6, xmono;
unsigned char byte;
if (ch < 33 || ch > 127) ch = 0;
else ch -=32;
if (ch == 0 && bg > 15) return;
/* each of the 96 characters is encoded into 18 bytes */
offset = (18 * ch);
while (y < y2) {
xmono = x * 2;
for (x1 = x; x1 < x2; x1++,xmono+=2) {
if (x1 > 139) {
offset++;
continue;
}
byte = tomthumb[offset++];
if (byte == 0) {
if (bg > 15) continue;
if (hgroutput == 1 && mono == 1) {
hrmonoplot(xmono,y,bg);
hrmonoplot(xmono+1,y,bg);
}
else {
dhrplot(x1,y,bg);
}
}
else {
if (fg > 15) continue;
if (hgroutput == 1 && mono == 1) {
hrmonoplot(xmono,y,fg);
hrmonoplot(xmono+1,y,fg);
}
else {
dhrplot(x1,y,fg);
}
}
}
/* if background color is being used then a trailing pixel is required
between characters */
if (bg < 16 && x2 < 140) {
if (hgroutput == 1 && mono == 1) {
hrmonoplot(xmono,y,bg);
hrmonoplot(xmono+1,y,bg);
}
else {
dhrplot(x2,y,bg);
}
}
if (y++ > 191) break;
}
}
/* normally spaced 4 x 6 font */
/* using character plotting function plotthumb() (above) */
void thumbDHGR(char *str,unsigned x, unsigned y,
unsigned char fg,unsigned char bg, unsigned char justify)
{
int target;
unsigned char ch;
if (justify == 'M' || justify == 'm') {
target = strlen(str);
x-= ((target * 4) /2);
}
while ((ch = *str++) != 0) {
plotthumbDHGR(ch,x,y,fg,bg);
x+=4;
}
}
/* VBMP output routines */
/* VBMP requires a palette in the DHGR color order
My Apple II routines whether in cc65, Aztec C65, or in my converters
always use the LORES color order. LORES, Double LORES, and DHGR all use
the same colors so it seems rather silly to use a different index value
for DHGR in re-usable program code.
However, since VBMP doesn't really care about the color palette in a BMP
and just the order of the palette, we need to remap the palette and the
scanline palette indices to DHGR color order.
*/
uint16_t WriteVbmpHeader(FILE *fp)
{
uint16_t outpacket;
int c, i, j;
/* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */
outpacket = (uint16_t)72;
if (mono != 0 || hgroutput == 1) {
if (hgroutput == 1) {
outpacket = 36;
c = fwrite(mono280,1,sizeof(mono192),fp);
}
else {
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 = (uint32_t)40;
mybmp.bmi.biWidth = (uint32_t)140;
mybmp.bmi.biHeight = (uint32_t)192;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 4;
mybmp.bmi.biCompression = (uint32_t) BI_RGB;
mybmp.bmi.biSizeImage = (uint32_t)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (uint32_t) 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;
/* use the current conversion palette for the VBMP palette */
/* rather than the preview palette */
for (i=0;i<16;i++) {
j = RemapLoToHi[i];
sbmp[i].rgbRed = rgbVBMP[j][RED];
sbmp[i].rgbGreen = rgbVBMP[j][GREEN];
sbmp[i].rgbBlue = rgbVBMP[j][BLUE];
}
/* 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;
}
/* decodes scanlines from hgr or dhgr monochrome buffer */
void applemonobites(int y, int doubleres)
{
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];
buf280[xoff] = ((ch) &1); xoff++;
buf280[xoff] = ((ch >> 1) &1); xoff++;
buf280[xoff] = ((ch >> 2) &1); xoff++;
buf280[xoff] = ((ch >> 3) &1); xoff++;
buf280[xoff] = ((ch >> 4) &1); xoff++;
buf280[xoff] = ((ch >> 5) &1); xoff++;
buf280[xoff] = ((ch >> 6) &1); xoff++;
if (doubleres == 0) continue;
ch = ptrmain[idx];
buf280[xoff] = ((ch) &1); xoff++;
buf280[xoff] = ((ch >> 1) &1); xoff++;
buf280[xoff] = ((ch >> 2) &1); xoff++;
buf280[xoff] = ((ch >> 3) &1); xoff++;
buf280[xoff] = ((ch >> 4) &1); xoff++;
buf280[xoff] = ((ch >> 5) &1); xoff++;
buf280[xoff] = ((ch >> 6) &1); xoff++;
}
}
/* encodes monochrome bmp scanline */
void ibmmonobites()
{
int i,j,k;
unsigned char bits[8];
j=0;
for(i=0;i<35;i++)
{
for(k=0;k<8;k++)
{
bits[k] = buf280[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]);
}
}
/* writes VBMP compatible 140 x 192 x 16 color bmp or VBMP monochrome bmp in 2 sizes */
int WriteVBMPFile()
{
FILE *fp;
uint8_t ch;
int x,x1,y,y2,idx,j,packet=72;
if (hgroutput == 1) packet = 36;
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++) {
if (hgroutput == 1) {
applemonobites(y2,0);
ibmmonobites();
}
else {
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 = (uint8_t)j << 4;
}
else {
idx = dhrgetpixel(x,y2);
/* range check */
if (idx < 0 || idx > 15)idx = 0; /* default black */
j = RemapLoToHi[idx];
bmpscanline[x1] = ch | (uint8_t)j; x1++;
}
}
}
fwrite((char *)&bmpscanline[0],1,packet,fp);
y2 -= 1;
}
fclose(fp);
if (quietmode == 1)printf("%s created!\n",vbmpfile);
return SUCCESS;
}
/* plain old HGR transformation routines for DHGR 6-color HGR pseudo-output */
/* encodes apple II hgr scanline into buffer */
void hgrbits(int y)
{
int xoff,idx,jdx;
unsigned char *ptr, bits[7], x1, palettebit;
xoff = HB[y]-0x2000;
ptr = (unsigned char *) &hgrbuf[xoff];
xoff = 0;
for (idx = 0; idx < 40; idx++) {
for (jdx = 0; jdx < 7; jdx++) {
bits[jdx] = buf280[xoff]; xoff++;
}
palettebit = palettebits[idx];
x1 = (palettebit | bits[6]<<6|bits[5]<<5|bits[4]<<4|
bits[3]<<3|bits[2]<<2|bits[1]<<1|bits[0]);
ptr[idx] = x1;
}
}
void buildhgr()
{
int i, j;
/* create bit pattern from pixelized values */
/* i is even and j is odd */
for (i= 0, j = 1; i < 280; i+=2, j+=2) {
buf280[i] = 0; /* assume everything is black */
buf280[j] = 0;
/* add the white bits - this also accounts for the half shift of
the color pixels which applewin renders as white to represent
aliasing of the color anomalies */
if (doublewhite == 1) {
/* if double white is on, set the white pixels in pairs */
if (work280[i] == HWHITE || work280[j] == HWHITE) {
buf280[i] = buf280[j] = 1;
}
}
else {
/* otherwise set white pixels individually */
if (work280[i] == HWHITE) buf280[i] = 1;
if (work280[j] == HWHITE) buf280[j] = 1;
}
/* if double colors is on, set the color pixels in pairs */
if (doublecolors == 1) {
/* add the violet or blue bits - the 2-bit value will be 2 */
if (work280[i] == HBLUE || work280[i] == HVIOLET ||
work280[j] == HBLUE || work280[j] == HVIOLET ) {
buf280[i] = 1;
buf280[j] = 0;
}
/* add the green or orange bits - the 2-bit value will be 1 */
if (work280[i] == HORANGE || work280[i] == HGREEN ||
work280[j] == HORANGE || work280[j] == HGREEN ) {
buf280[i] = 0;
buf280[j] = 1;
}
}
else {
/* otherwise set the colors individually if double colors is off */
/* add the violet or blue bits - the 2-bit value will be 2 */
if (work280[i] == HBLUE || work280[i] == HVIOLET )buf280[i] = 1;
if (work280[j] == HBLUE || work280[j] == HVIOLET )buf280[j] = 0;
/* add the green or orange bits - the 2-bit value will be 1 */
if (work280[i] == HORANGE || work280[i] == HGREEN )buf280[i] = 0;
if (work280[j] == HORANGE || work280[j] == HGREEN )buf280[j] = 1;
}
if (doubleblack == 1) {
/* be careful here - this can foul the colors */
if (work280[i] == HBLACK || work280[j]==HBLACK) {
buf280[i] = 0;
buf280[j] = 0;
}
}
}
}
void hgrline(int y)
{
int x,i,j,k,l, green, orange;
unsigned char c, p;
/* read the 6-color DHGR buffer and translate to HGR */
/* since DHGR is 140 pixels in width and HGR is 280, double each pixel */
/* HGR is only 140 color pixels in width so effective color resolution is identical */
/* this is a really slack method of converting to HGR because it ignores the half-pixel shift for black and white
pixels so technically half the available detail is lost on images with large areas of black and white but
generally on complex images with lots of colors artifacting is minimized by doubling-up
some of my other converters like Bmp2RAG offer more options but they don't provided dithering.
*/
/* double colors */
for (x=0,i=0,j=1;x<140;x++,i+=2,j+=2) {
/* get the DHGR color */
k = dhrgetpixel(x,y);
/* remap to the HGR color indices */
work280[i] = work280[j] = dhgr2hgr[k];
}
/* single colors - shift image right by one nominal pixel */
/* otherwise this setting will have no effect */
if (doublecolors == 0) {
for (x = 279;x > 0;x--) {
work280[x] = work280[x-1];
}
}
buildhgr();
/* set the HGR palette based on groups of seven HGR pixels */
if (hgrpaltype == 0 || hgrpaltype == 0x80) {
/* single palette over-ride... 4 color output. all non-black and
non-white pixels will be converted to either Green-Violet or
Orange-Blue */
for (i = 0; i < 40; i++) palettebits[i] = hgrpaltype;
}
else {
/* seed palette hi-bit with some value */
if (hgrcolortype == 'G' || hgrcolortype == 'V') p = 0;
else p = 0x80;
/* go through the 280 pixel scanline and determine precedence of colors
for the palette bit settings based on the command option selected. */
for (i = 0, k=0; i < 40; i++ ) {
orange = 0;
green = 0;
for (j = 0; j < 7; j++) {
/* count in groups of 7 pixels (really 3.5 color pixels) */
if (work280[k] == HORANGE || work280[k] == HBLUE) orange++;
if (work280[k] == HGREEN || work280[k] == HVIOLET) green++;
k++;
}
if (hgrcolortype == 'O') {
/* big orange - one orange pixel sets the palette */
/* orange blue */
if (orange > 0) p = 0x80;
else {
if (green > 0) p = 0;
}
}
else if (hgrcolortype == 'G') {
/* big green - one green pixel sets the palette */
/* green violet */
if (green > 0) p = 0;
else {
if (orange > 0) p = 0x80;
}
}
else {
/* normal precedence - the dominant color group sets the palette */
if (green > orange) p = 0;
else if (orange > green) p = 0x80;
else {
/* but if both groups are equal then 3 - options for behaviour */
/* little green - equal green and orange sets the palette to green */
if (hgrcolortype == 'V' && green == orange) p = 0;
/* little orange - equal green and orange sets the palette to orange */
else if (hgrcolortype == 'B' && orange == green) p = 0x80;
else if (orange > 0) {
/* it was either do this or carry the previous palette bit setting forward */
if (hgrcolortype == 'G' || hgrcolortype == 'V') p = 0;
else p = 0x80;
}
}
}
palettebits[i] = p;
}
}
}
/* routines to save to Apple 2 Lores Format */
#define LORAGWIDTH 80
#define LORAGHEIGHT 48
#define LORAGSIZE 1920
#define LOTOPSIZE 1600
#define LOBINSIZE 1016
/* Lo-Res and Double Lo-Res Routines */
/* The Lo-Res Display has a resolution of 40 x 48 x 16 colors.
The Double Lo-Res display has a resolution of 80 x 48 x 16 colors.
In either case, these are the same 16 colors used by the DHGR display.
The nominal resolution for a Double Lo-Res Screen is 320 x 192
/* routines to save to Apple 2 Lores Format */
/* sets the pixels in the lores buffer (hgrbuf) */
void setlopixel(unsigned char color,int x, int y,int ragflag)
{
unsigned char *crt, c1, c2;
int y1, offset;
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;
}
if (ragflag)
offset = (y1 * 80) + x;
else
offset = (textbase[y1]-1024)+x;
crt = (unsigned char *)&hgrbuf[offset];
crt[0] &= c1;
crt[0] |= c2;
}
/* save LGR or DLGR output files in either raster-oriented or BSAVE format */
/* only full-screen (48 line) or mixed-screen (40 line) files are supported for raster-oriented files */
/* only full-sceen format is supported for BSAVE files */
/* image fragments are not supported */
int savelofragment()
{
FILE *fp;
unsigned char outfile[MAXF], temp, remap;
int x,y,x2,y2, offset;
uint16_t fl = 1016; /* default LGR or DLGR file size in bytes - BSAVE format */
/* raster files - single file output */
if (applesoft == 0) {
/* save single lo-res and double lo-res */
/* save raster images of 48 or 40 scanlines
(full graphics or mixed text and graphics) */
if (lores == 1) {
if (appletop == 1) {
fl = 802;
sprintf(outfile,"%s.STO",hgrwork);
}
else {
fl = 962;
sprintf(outfile,"%s.SLO",hgrwork);
}
}
else {
if (appletop == 1) {
fl = 1602;
sprintf(outfile,"%s.DTO",hgrwork);
}
else {
fl = 1922;
sprintf(outfile,"%s.DLO",hgrwork);
}
}
if (tags == 1) {
strcat(outfile,"#060400");
}
fp = fopen(outfile,"wb");
if (NULL == fp)return INVALID;
WriteDosHeader(fp,fl,1024);
/* On the double lo res display each byte in
high memory is interleaved with a byte in low memory
in the interests of efficiency I am saving and loading
the interleaf on a scanline by scanline basis.
*/
memset(hgrbuf,0,LORAGSIZE);
for (y = 0; y< 48; y++) {
if (appletop == 1 && y > 39)break;
y2 = y;
/* first 40 bytes goes to auxiliary memory (even pixels) */
for (x = 0; x < 40; x++) {
x2 = (x*2);
remap = dhrgetpixel(x2,y2);
temp = dloauxcolor[remap];
setlopixel(temp,x,y,1);
}
/* followed by the interleaf (odd pixels)
next 40 bytes goes to main memory */
for (x = 0; x < 40; x++) {
x2 = (x*2) + 1;
temp = dhrgetpixel(x2,y2);
setlopixel(temp,x+40,y,1);
}
}
if (lores == 1) {
fputc(40,fp); /* bytes */
if (appletop == 1) fputc(20,fp);
else fputc(24,fp);
for (y = 0; y < 24; y++) {
if (appletop == 1 && y > 19)break;
offset = (y * 80)+40;
fwrite((unsigned char *)&hgrbuf[offset],1,40,fp);
}
}
else {
fputc(80,fp); /* bytes */
if (appletop == 1) {
fputc(20,fp); /* bytes (rasters / 2) */
fwrite(hgrbuf,1,LOTOPSIZE,fp);
}
else {
fputc(24,fp); /* bytes (rasters / 2) */
fwrite(hgrbuf,1,LORAGSIZE,fp);
}
}
fclose(fp);
printf("%s Saved!",outfile);
}
else {
/* bsaved images */
/* 2 files for double lo-res */
/* one file for lo-res */
/* these cannot be loaded in a ProDOS BASIC program and should
probably not be loaded in a C program */
/* they are arguably unsafe to load even in DOS 3.3
since they clobber the text screen holes */
/* for double lo-res the bsaved images are split into two files
the first file is loaded into aux mem
*/
if (lores == 0) {
sprintf(outfile,"%s.DL1",hgrwork);
if (tags == 1) {
strcat(outfile,"#060400");
}
fp = fopen(outfile,"wb");
if (NULL == fp)return INVALID;
WriteDosHeader(fp,fl,1024);
memset(hgrbuf,0,LOBINSIZE);
for (y = 0; y< 48; y++) {
y2 = y;
for (x = 0; x < 40; x++) {
x2 = (x*2);
remap = dhrgetpixel(x2,y2);
temp = dloauxcolor[remap];
setlopixel(temp,x,y,0);
}
}
fwrite(hgrbuf,1,LOBINSIZE,fp);
fclose(fp);
printf("%s Saved!",outfile);
}
/*
for single lo res only 1 file is needed
for double lo res the second file is loaded into main mem
*/
if (lores == 1)
sprintf(outfile,"%s.SL2",hgrwork);
else
sprintf(outfile,"%s.DL2",hgrwork);
if (tags == 1) {
strcat(outfile,"#060400");
}
fp = fopen(outfile,"wb");
if (NULL == fp)return INVALID;
WriteDosHeader(fp,fl,1024);
memset(hgrbuf,0,LOBINSIZE);
for (y = 0; y< 48; y++) {
y2 = y;
for (x = 0; x < 40; x++) {
// VMW
if (lores==1) {
x2 = x;
}
else {
x2 = (x*2) + 1;
}
temp = dhrgetpixel(x2,y2);
setlopixel(temp,x,y,0);
}
}
fwrite(hgrbuf,1,LOBINSIZE,fp);
fclose(fp);
printf("%s Saved!",outfile);
}
return SUCCESS;
}
/* save both raw output file formats */
int savedhr()
{
FILE *fp;
int c,y;
if (outputtype != BIN_OUTPUT) return SUCCESS;
if (loresoutput == 1) {
savelofragment();
return SUCCESS;
}
/* titling from text files if found */
GetUserTextFile();
if (hgroutput == 1) {
/* just using the BIN file extension as always */
if (mono == 0) {
strcpy(mainfile,hgrcolor);
memset(hgrbuf,0,8192);
for (y = 0; y < 192; y++) {
hgrline(y); /* translate from DHGR and format the HGR line */
hgrbits(y); /* put the HGR line into the HGR file buffer */
}
}
else {
strcpy(mainfile,hgrmono);
}
fp = fopen(mainfile,"wb");
if (NULL == fp) {
if (quietmode == 1)printf("Error Opening %s for writing!\n",mainfile);
return INVALID;
}
WriteDosHeader(fp,8192,8192);
if (mono == 1) c = fwrite(dhrbuf,1,8192,fp);
else c = fwrite(&hgrbuf[0],1,8192,fp);
fclose(fp);
if (c != 8192) {
remove(mainfile);
if (quietmode == 1)printf("Error Writing %s!\n",mainfile);
return INVALID;
}
if (quietmode == 1) printf("%s created!\n",mainfile);
if (vbmp != 0) {
/* additional BMP file for Cybernesto's VBMP */
if (mono == 0) memcpy(&dhrbuf[0],&hgrbuf[0],8192);
WriteVBMPFile();
}
return SUCCESS;
}
if (applesoft == 0) {
fp = fopen(a2fcfile,"wb");
if (NULL == fp) {
if (quietmode == 1)printf("Error Opening %s for writing!\n",a2fcfile);
return INVALID;
}
WriteDosHeader(fp,16384,8192);
c = fwrite(dhrbuf,1,16384,fp);
fclose(fp);
if (c != 16384) {
remove(a2fcfile);
if (quietmode == 1)printf("Error Writing %s!\n",a2fcfile);
return INVALID;
}
if (quietmode == 1)printf("%s created!\n",a2fcfile);
if (vbmp != 0) {
/* additional BMP file for Cybernesto's VBMP */
WriteVBMPFile();
}
return SUCCESS;
}
/* the bsaved images are split into two files
the first file is loaded into aux mem */
fp = fopen(auxfile,"wb");
if (NULL == fp) {
if (quietmode == 1)printf("Error Opening %s for writing!\n",auxfile);
return INVALID;
}
WriteDosHeader(fp,8192,8192);
c = fwrite(dhrbuf,1,8192,fp);
fclose(fp);
if (c != 8192) {
remove(auxfile);
if (quietmode == 1)printf("Error Writing %s!\n",auxfile);
return INVALID;
}
/* the second file is loaded into main mem */
fp = fopen(mainfile,"wb");
if (NULL == fp) {
remove(auxfile);
if (quietmode == 1)printf("Error Opening %s for writing!\n",mainfile);
return INVALID;
}
WriteDosHeader(fp,8192,8192);
c = fwrite(&dhrbuf[8192],1,8192,fp);
fclose(fp);
if (c != 8192) {
/* remove both files */
remove(auxfile);
remove(mainfile);
if (quietmode == 1)printf("Error Writing %s!\n",mainfile);
return INVALID;
}
if (quietmode == 1) {
printf("%s created!\n",auxfile);
printf("%s created!\n",mainfile);
}
if (vbmp != 0) {
/* additional BMP file for Cybernesto's VBMP */
WriteVBMPFile();
}
return SUCCESS;
}
int saverag()
{
FILE *fp;
/* make an Rasterized Apple II Graphic (RAG) */
int c, x, y, xoff, width;
unsigned char *ptr;
if (scale == 1) spritewidth = bmpwidth;
else spritewidth = bmpwidth * 2;
if (spritewidth < 1) {
printf("Width is too small for %s!\n",spritefile);
return INVALID;
}
memset(hgrbuf,0,8192);
for (y = 0; y < 192; y++) {
hgrline(y); /* translate from DHGR and format the HGR line */
hgrbits(y); /* put the HGR line into the HGR file buffer */
}
width = spritewidth;
while (width%7 != 0)width++; /* multiples of 7 pixels */
/* if we have an orphan pixel hanging at the edge of an even byte
increase the width to the next 7 pixels */
if (width == spritewidth && (width % 14) != 0) width += 7;
width /= 7;
if (width > 40)width = 40; /* likely not necessary */
/* over-ride for default .RAG file extension */
/* use .BOT extension for full-screen */
/* use .TOP extension for mixed-screen */
if (width == 40 && (bmpheight == 160 || bmpheight == 192)) {
x = 999;
for (y=0;spritefile[y] != (char)0;y++) {
if (spritefile[y] == '.') x = y;
}
if (x != 999) {
spritefile[x+2] = 'O';
if (bmpheight == 160) {
spritefile[x+1] = 'T'; spritefile[x+3] = 'P';
}
else {
spritefile[x+1] = 'B'; spritefile[x+3] = 'T';
}
}
}
fp = fopen(spritefile,"wb");
if (NULL == fp) {
printf("Error Opening %s for writing!\n",spritefile);
return INVALID;
}
/* write 2 byte header */
fputc((uint8_t)width,fp); /* width in bytes */
fputc((uint8_t)bmpheight,fp); /* height in scanlines */
for (y = 0; y < bmpheight; y++) {
xoff = HB[y] - 0x2000;
ptr = (unsigned char *) &hgrbuf[xoff];
c = fwrite(ptr,1,width,fp);
if (c!=width) break;
}
fclose(fp);
if (c!=width) {
remove(spritefile);
printf("Error Writing %s!\n",spritefile);
return INVALID;
}
printf("%s created!\n",spritefile);
return SUCCESS;
}
/* save raster oriented DHGR image fragment
file format is 5 byte header
3 - upper case ID bytes 'D' 'H' 'R' for a sprite
3 - upper case ID bytes 'D' 'H' 'M' for a sprite-mask
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...
*/
int savesprite()
{
FILE *fp;
int i, c, width, packet, x, y, xoff, cnt;
uint16_t fl;
uint8_t *ptraux, *ptrmain, ch;
if (outputtype != SPRITE_OUTPUT) return SUCCESS;
if (hgroutput == 1) return saverag();
/* if scaling is turned-on the sprite matrix is 280 x 192 so for every 2-pixels
in the BMP only 1-pixel will be in the sprite. BMPs over 140 x 192 implictly
and automatically turn-on scaling whether sprite mode is selected (option "F")
or not.
*/
if (scale == 1) spritewidth = bmpwidth / 2;
else spritewidth = bmpwidth;
if (spritewidth < 1) {
if (quietmode == 1)printf("Width is too small for %s!\n",spritefile);
return INVALID;
}
while (spritewidth%7 != 0) spritewidth++;
width = (int)((spritewidth / 7) * 4); /* 4 bytes = 7 pixels */
packet = (int)width / 2;
/* prepare either an image fragment or a mask for the image fragment */
/* the idea for a mask is to provide a background mixing map for the image fragment */
if (spritemask != 1) {
fp = fopen(spritefile,"wb");
if (NULL == fp) {
if (quietmode == 1)printf("Error Opening %s for writing!\n",spritefile);
return INVALID;
}
}
else {
fp = fopen(fmask,"wb");
if (NULL == fp) {
if (quietmode == 1)printf("Error Opening %s for writing!\n",fmask);
return INVALID;
}
/* transform the buffer to a black and white mask for the sprite */
/* the background is black and the foreground is white */
/* this allows a rendered sprite to be prepared independently of the mask */
/* and to contain the background color in any rendering or dithering that goes-on */
for (y = 0; y < bmpheight; y ++) {
for (x = 0; x < spritewidth; x++) {
if (dhrgetpixel(x,y) == backgroundcolor) ch = 0;
else ch = 15;
dhrplot(x,y,ch);
}
}
/* now that we have transformed the image into a mask for mixing the sprite
with a background image we save it in the same format as the sprite
but as a DHM file rather than a DHR file */
/* append M for mask to the array basename */
if (quietmode == 0) strcat(fname,"M");
}
if (dosheader == 1) {
fl = (uint16_t) width;
fl *=bmpheight;
fl += 5;
WriteDosHeader(fp,fl,8192);
}
/* 5 byte header */
/* some kind of identifier */
fputc('D',fp);
fputc('H',fp);
if (spritemask != 1) fputc('R',fp);
else fputc('M',fp);
fputc((uint8_t)width,fp); /* width in bytes */
fputc((uint8_t)bmpheight,fp); /* height in scanlines */
/* write header values to stdout */
if (quietmode == 0) {
printf("#define %sWIDTH %d\n",fname,width);
printf("#define %sHEIGHT %d\n",fname,bmpheight);
printf("#define %sSIZE %d\n\n",fname,width * bmpheight);
/* if we are writing a mask, background color is irrelevant */
/* the whole idea behind background color is the same as a mask */
if (spritemask != 1) printf("uint8_t %sBackgroundColor = %d;\n\n",fname,backgroundcolor);
printf("/* Embedded DHGR Image Fragment created from %s */\n\n",bmpfile);
printf("uint8_t %sPixelData[] = {\n",fname);
}
for (y = 0, cnt = 0; y < bmpheight; y++) {
xoff = HB[y];
ptraux = (uint8_t *) &dhrbuf[xoff-0x2000];
ptrmain = (uint8_t *) &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;
if (quietmode == 0) {
for (i=0;i<width;i++) {
if (i <packet)ch = ptraux[i];
else ch = ptrmain[i-packet];
if (cnt == 0) {
printf("%3d",ch);
}
else {
printf(",");
if (cnt%16 == 0) printf("\n");
printf("%3d",ch);
}
cnt++;
}
}
}
if (quietmode == 0) printf("};\n\n");
fclose(fp);
if (c!=packet) {
if (spritemask != 1) {
remove(spritefile);
if (quietmode == 1)printf("Error Writing %s!\n",spritefile);
}
else {
remove(fmask);
if (quietmode == 1)printf("Error Writing %s!\n",fmask);
}
return INVALID;
}
if (quietmode == 1) {
if (spritemask != 1) printf("%s created!\n",spritefile);
else printf("%s created!\n",fmask);
}
return SUCCESS;
}
/* read and remap a mask line from an open mask file */
/* required by dithered and non-dithered routines when in use */
int16_t ReadMaskLine(uint16_t y)
{
uint32_t pos;
uint16_t x, packet;
uint8_t ch;
if (overlay == 0) return INVALID;
if (mono == 1) {
/* two sizes for mono overlays depending on output */
/* 560 x 192 DHGR overlay or 280 x 192 HGR overlay */
if (hgroutput == 1) packet = 280;
else packet = 560;
}
else packet = 140;
pos = (uint32_t) (191 - y);
pos *= packet;
pos += maskbmp.bfi.bfOffBits;
fseek(fpmask,pos,SEEK_SET);
fread((char *)&maskline[0],1,packet,fpmask);
for (x = 0; x < packet; x++) {
ch = maskline[x];
maskline[x] = remap[ch];
}
return SUCCESS;
}
/*
http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
The Floyd-Steinberg filter
This is where it all began, with Floyd and Steinberg's pioneering
research in 1975. The filter can be diagrammed thus:
* 7
3 5 1 (1/16)
In this (and all subsequent) filter diagrams, the "*" represents the pixel
currently being scanning, and the neighboring numbers (called weights)
represent the portion of the error distributed to the pixel in that
position. The expression in parentheses is the divisor used to break up the
error weights. In the Floyd-Steinberg filter, each pixel "communicates"
with 4 "neighbors." The pixel immediately to the right gets 7/16 of the
error value, the pixel directly below gets 5/16 of the error, and the
diagonally adjacent pixels get 3/16 and 1/16.
The weighting shown is for the traditional left-to-right scanning of the
image. If the line were scanned right-to-left (more about this later), this
pattern would be reversed. In either case, the weights calculated for the
subsequent line must be held by the program, usually in an array of some
sort, until that line is visited later.
Floyd and Steinberg carefully chose this filter so that it would produce a
checkerboard pattern in areas with intensity of 1/2 (or 128, in our sample
image). It is also fairly easy to execute in programming code, since the
division by 16 is accomplished by simple, fast bit-shifting instructions
(this is the case whenever the divisor is a power of 2).
*/
/*
Floyd-Steinberg dithering published by Robert Floyd and Louis Steinberg in
1976 was the first 2D error diffusion dithering formula.(Filter Lite is an
algorithm by Sierra that produces similar results.) Floyd-Steinberg dithering
only diffuses the error to neighbouring pixels. This results in very
fine-grained dithering.
In the same year a much more powerful algorithm was also published: Jarvis,
Judice, and Ninke. (Sierra dithering and Sierra 2 are based on Jarvis
dithering, and produce similar results. Atkinson dithering resembles Jarvis
dithering and Sierra dithering; speckling is improved but very light and dark
areas may appear blown out.) Jarvis dithering is coarser than Floyd-Steinberg,
but has fewer visual artifacts.
Five years after Jarvis dithering, Peter Stucki published an adjusted version,
to improve processing time. Its output tends to be clean and sharp.
Seven years after Stucki published his improvement to Jarvis, Judice, Ninke
dithering, Daniel Burkes developed a simplified form of Stucki dithering that
is somewhat less clean and sharp.
*/
/* setting clip to 0 increases the potential amount of retained error */
/* error is accumulated in a short integer and may be negative or positive */
uint8_t AdjustShortPixel(int clip,int16_t *buf,int16_t value)
{
if (globalclip == 1) clip = 1;
value = (int16_t)(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 (uint8_t) value;
}
/* helper function for ReadCustomDither */
int InitCustomLine(char *ptr, int lidx)
{
int cnt=0, i;
customdither[lidx][cnt] = (int16_t) atoi(ptr);
/* enforce 11 fields */
for (i=0;ptr[i]!=0;i++) {
if (ptr[i]== ',') {
cnt++;
if (cnt < 11) customdither[lidx][cnt] = (int16_t) atoi((char*)&ptr[i+1]);
}
}
if (cnt != 10) return -1;
}
/* read a custom dither pattern from a comma delimited text file
line 1 is the custom divisor
the next 3 lines are 11 fields in the following format:
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,0,0,0,0
unused fields must be padded with zeros
errata:
- no range checking
- current pixel (asterisk) at subscript 5 is not "protected"
*/
int ReadCustomDither(char *name)
{
FILE *fp;
char ch, buf[128];
int i,j;
/* clear 3-dimensional custom dither array */
memset(&customdither[0][0],0,sizeof(int16_t)*33);
fp = fopen(name,"r");
if (NULL == fp) return -1;
/* read divisor */
for (;;) {
if (NULL == fgets(buf, 128, fp)) {
fclose(fp);
return -1;
}
/* ignore comment lines and blank lines */
ch = buf[0];
/* leading numeric characters only */
if (ch < 48 || ch > 57) continue;
break;
}
customdivisor = (int16_t) atoi(buf);
if (customdivisor < 1) {
fclose(fp);
return -1;
}
/* read up to 3 lines of dither pattern */
for (i=0;;) {
if (NULL == fgets(buf, 128, fp)) {
fclose(fp);
return -1;
}
/* ignore comment lines and blank lines */
ch = buf[0];
/* leading numeric characters only */
if (ch < 48 || ch > 57) continue;
/* condition line - remove trailing comments */
for (j=0;buf[j]!=0;j++) {
ch = buf[j];
/* numeric characters are ok */
if (ch > 47 && ch < 58) continue;
/* commas and asterisks are ok */
if (ch == ',' || ch == '*') continue;
buf[j] = 0;
break;
}
/* parse fields - there must be 11 fields */
if(InitCustomLine((char *)&buf[0],i)==-1) {
fclose(fp);
return -1;
}
i++;
if (i == 3) break;
}
fclose(fp);
if (i == 0) return -1;
if (quietmode == 1) {
printf("Imported Dither from %s\n",name);
}
dither = CUSTOM;
return SUCCESS;
}
/* http://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering */
/* http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/ */
/* http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT */
int run0=0, run1=0, run2=0;
void FloydSteinberg(int y, int width)
{
double paldistance; /* not used in this function */
int16_t red, green, blue, red_error, green_error, blue_error;
int16_t pos, mult;
int dx,i, x,x1, total_difference, total_error, total_used;
int testrun, runs, temperror, z;
uint8_t drawcolor, r,g,b;
if (ditherstart == 0) {
/* for hgr color dithering cancel serpentine effect and go forward only
otherwise groups of 7 pixels for choosing between Orange and Green hgr
palettes becomes too complicated */
/* this solution may effect user definable dithering but it is up to the
user to make their own pattern work within the program's limitations
*/
if (hgrdither == 1) serpentine = 0;
if (quietmode == 1) {
if (mono == 1) puts("Monochrome Dithered Output:");
else puts("Color Dithered Output:");
if (colorbleed < 100)
printf("Dither = %d - %s, Color Bleed Increase: %d%%\n",dither,dithertext[dither-1],(colorbleed-100)*-1);
else if (colorbleed > 100)
printf("Dither = %d - %s, Color Bleed Reduction: %d%%\n",dither,dithertext[dither-1],(colorbleed-100));
else
printf("Dither = %d - %s\n",dither,dithertext[dither-1]);
if (serpentine == 1) puts("Serpentine effect is on!");
}
ditherstart = 1;
/* reduce or increase color bleed */
switch(dither) {
case FLOYDSTEINBERG: bleed = (16 * colorbleed)/100; break;
case JARVIS: bleed = (48 * colorbleed)/100; break;
case STUCKI: bleed = (42 * colorbleed)/100; break;
case ATKINSON: bleed = (8 * colorbleed)/100; break;
case BURKES:
case SIERRA: bleed = (32 * colorbleed)/100; break;
case SIERRATWO: bleed = (16 * colorbleed)/100; break;
case SIERRALITE: bleed = (4 * colorbleed)/100; break;
case CUSTOM: bleed = (customdivisor * colorbleed)/100; break;
default: bleed = (8 * colorbleed)/100; break; /* same as atkinson */
}
if (bleed < 1) bleed = 1;
}
/* When converting to HGR do palette matching here between Green-Violet and
Orange-Blue palettes in groups of 7 pixels */
/* from left to right */
/* if we are dithering HGR we need to decide if we are using the Orange-Blue palette or
the Green-Violet palette based on groups of 7 pixels */
if (hgrdither == 1) {
testrun = 0;
/* the idea here is to work on a copy while we make the first two passes
to determine the palette */
/* on the third (and final) pass, we dither using the 4 color choice
with the lowest cumulative error for each 7 pixel group */
/* this particular idea is based on how Sheldon Simms tohgr program
decides which palette to use but you probably wouldn't know that
by just looking at the code */
/* Clear the buffers */
memset(&OrangeBlueError[0],0,640);
memset(&GreenVioletError[0],0,640);
memset(&HgrPixelPalette[0],0,320);
/* save the original dither buffers */
/* work on a copy for the first two passes */
memcpy(&redSave[0],&redDither[0],640);
memcpy(&greenSave[0],&greenDither[0],640);
memcpy(&blueSave[0],&blueDither[0],640);
}
else {
testrun = 2;
}
/* if we are dithering color hgr, for the first two test passes, we don't
bother to diffuse the error beyond the current scanline */
/* it is not necessary to do so because all we are concerned with is the
color of the transformed pixels on the current line, but it is necessary to
dither the line completely in either palette in order to tansform the pixels
in the current line */
for (runs=testrun;runs<3;runs++) {
/* big hgr color rigamorole here */
if (hgrdither == 1) {
if (runs == 1 || runs == 2) {
/* restore dither buffer after both test runs for the final run */
memcpy(&redDither[0],&redSave[0],640);
memcpy(&greenDither[0],&greenSave[0],640);
memcpy(&blueSave[0],&blueDither[0],640);
}
/* for the first two runs, dither7 does not change */
if (runs == 0) {
dither7 = 'O';
}
else if (runs == 1) {
dither7 = 'G';
}
else {
/* after the first two runs */
/* determine hgr palette for each pixel based on the first two
runs here before beginning the 3rd and final run */
for (x = 0; x < width; x+=7) {
red_error = green_error = 0;
for (z = 0; z < 7; z++) {
red_error += OrangeBlueError[x+z];
green_error += GreenVioletError[x+z];
}
/* if the Green-Violet palette has the closest colors for
this group then use it. otherwise use the Orange-Blue
palette */
if (green_error < red_error) dither7 = 'G';
else dither7 = 'O';
/* set the hgr palette for 7 pixels */
for (z = 0; z < 7; z++) {
HgrPixelPalette[x+z] = dither7;
}
}
}
}
for (x=0;x<width;x++) {
red = redDither[x];
green = greenDither[x];
blue = blueDither[x];
r = (uint8_t)red;
g = (uint8_t)green;
b = (uint8_t)blue;
/* for the final pass, use the best hgr 4 color palette, Orange-Blue
or Green-Violet */
/* the palette to use for each pixel comes from an array that was
built based on the lowest 7 pixel cumulative error between the two
palettes that were tested on the first and second passes
respectively */
if (hgrdither == 1 && runs == 2) dither7 = HgrPixelPalette[x];
drawcolor = GetDrawColor(r,g,b,x,y);
r = rgbArray[drawcolor][RED];
g = rgbArray[drawcolor][GREEN];
b = rgbArray[drawcolor][BLUE];
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;
if (runs == 0 || runs == 1) {
/* for hgr color only accumulate total error per pixel for the first two passes */
/* use absolute error */
if (red_error < 0) temperror = red_error * -1;
else temperror = red_error;
if (green_error < 0) temperror += (green_error * -1);
else temperror += green_error;
if (blue_error < 0) temperror += (blue_error * -1);
else temperror += blue_error;
if (runs == 0) OrangeBlueError[x] = temperror;
else GreenVioletError[x] = temperror;
/* before we do the third pass, these arrays will be processed in 7 pixel chunks
and the lowest cumulative error in each chunk will determine if the
Orange-Blue or Green-Violet hgr palette will be used for the 7 pixels in the chunk */
}
for (i=0;i<3;i++) {
/* loop through all 3 RGB channels */
switch(i) {
case RED: colorptr = (int16_t *)&redDither[0];
seedptr = (int16_t *)&redSeed[0];
seed2ptr = (int16_t *)&redSeed2[0];
color_error = red_error;
break;
case GREEN: colorptr = (int16_t *)&greenDither[0];
seedptr = (int16_t *)&greenSeed[0];
seed2ptr = (int16_t *)&greenSeed2[0];
color_error = green_error;
break;
case BLUE: colorptr = (int16_t *)&blueDither[0];
seedptr = (int16_t *)&blueSeed[0];
seed2ptr = (int16_t *)&blueSeed2[0];
color_error = blue_error;
break;
}
/* diffuse the error based on the dither */
switch(dither) {
/* F 1*/
case FLOYDSTEINBERG:
/*
* 7
3 5 1 (1/16)
Serpentine
7 *
1 5 3
*/
/* 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;
}
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
/* for serpentine effect line 1 error is added behind */
if (x > 0) AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error * 7)/bleed)+total_difference);
/* seed next line forward */
/* for serpentine effect line 2 error is reversed */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error * 1)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error * 3)/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error * 7)/bleed)+total_difference);
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next line forward */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error * 3)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error * 1)/bleed));
}
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error * 5)/bleed));
break;
/* J 2 */
case JARVIS:
/*
* 7 5
3 5 7 5 3
1 3 5 3 1 (1/48)
*/
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error * 7)/bleed));
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)((color_error * 5)/bleed));
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next lines forward */
if (x>0){
AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error * 5)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x-1],(int16_t)((color_error * 3)/bleed));
}
if (x>1){
AdjustShortPixel(threshold,(int16_t *)&seedptr[x-2],(int16_t)((color_error * 3)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x-2],(int16_t)(color_error/bleed));
}
/* seed next line forward */
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error * 7)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error * 5)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+2],(int16_t)((color_error * 3)/bleed));
/* seed furthest line forward */
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x],(int16_t)((color_error * 5)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x+1],(int16_t)((color_error * 3)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x+2],(int16_t)(color_error/bleed));
break;
/* S 3 */
case STUCKI:
/*
* 8 4
2 4 8 4 2
1 2 4 2 1 (1/42)
*/
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if(x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error * 8)/bleed));
if(x>1)AdjustShortPixel(1,(int16_t *)&colorptr[x-2],(int16_t)((color_error * 4)/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error * 8)/bleed));
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)((color_error * 4)/bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next lines forward */
if (x>0){
AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error * 4)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x-1],(int16_t)((color_error * 2)/bleed));
}
if (x>1){
AdjustShortPixel(threshold,(int16_t *)&seedptr[x-2],(int16_t)((color_error * 2)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x-2],(int16_t)(color_error/bleed));
}
/* seed next line forward */
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error * 8)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error * 4)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+2],(int16_t)((color_error * 2)/bleed));
/* seed furthest line forward */
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x],(int16_t)((color_error * 4)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x+1],(int16_t)((color_error * 2)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x+2],(int16_t)(color_error/bleed));
break;
/* A 4 */
case ATKINSON:
/*
* 1 1
1 1 1
1 (1/8)
*/
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if (x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)(color_error/bleed));
if (x>1)AdjustShortPixel(1,(int16_t *)&colorptr[x-2],(int16_t)(color_error/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)(color_error/bleed));
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)(color_error/bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next line forward */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)(color_error/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)(color_error/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)(color_error/bleed));
/* seed furthest line forward */
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x],(int16_t)(color_error/bleed));
break;
/* B 5 */
case BURKES:
/*
* 8 4
2 4 8 4 2 (1/32)
*/
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if(x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error * 8) /bleed));
if(x>1)AdjustShortPixel(1,(int16_t *)&colorptr[x-2],(int16_t)((color_error * 4) /bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error * 8) /bleed));
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)((color_error * 4) /bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next line forward */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error * 4) / bleed));
if (x>1)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-2],(int16_t)((color_error * 2) / bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error * 8) /bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error * 4) /bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+2],(int16_t)((color_error * 2) /bleed));
break;
/* SI 6 */
case SIERRA:
/*
* 5 3
2 4 5 4 2
2 3 2 (1/32)
*/
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if(x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error * 5)/bleed));
if(x>1)AdjustShortPixel(1,(int16_t *)&colorptr[x-2],(int16_t)((color_error * 3)/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error * 5)/bleed));
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)((color_error * 3)/bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next lines forward */
if (x>0){
AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error * 4)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x-1],(int16_t)((color_error * 2)/bleed));
}
if (x>1){
AdjustShortPixel(threshold,(int16_t *)&seedptr[x-2],(int16_t)((color_error * 2)/bleed));
}
/* seed next line forward */
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error * 5)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error * 4)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+2],(int16_t)((color_error * 2)/bleed));
/* seed furthest line forward */
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x],(int16_t)((color_error * 3)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x+1],(int16_t)((color_error * 2)/bleed));
break;
/* S2 7 */
case SIERRATWO:
/*
* 4 3
1 2 3 2 1 (1/16)
*/
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if(x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error*4)/bleed));
if(x>1)AdjustShortPixel(1,(int16_t *)&colorptr[x-2],(int16_t)((color_error*3)/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error*4)/bleed));
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)((color_error*3)/bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next line forward */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)((color_error*2)/bleed));
if (x>1)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-2],(int16_t)(color_error/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error*3)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)((color_error*2)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+2],(int16_t)(color_error/bleed));
break;
/* SL 8 */
case SIERRALITE:
/*
* 2
1 1 (1/4)
*/
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if (x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error * 2) /bleed));
/* seed next line forward */
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)(color_error/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error * 2) /bleed));
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next line forward */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)(color_error/bleed));
}
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)(color_error/bleed));
break;
case CUSTOM:
/* 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,0,0,0,0 */
for (dx = 0,pos=x-5;dx < 11; dx++,pos++) {
/* finish this line */
if (pos < 0) continue;
mult = customdither[0][dx];
if (mult > 0) {
AdjustShortPixel(1,(int16_t *)&colorptr[pos],(int16_t)((color_error * mult) /bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) continue;
/* seed next line forward */
mult = customdither[1][dx];
if (mult > 0) {
AdjustShortPixel(threshold,(int16_t *)&seedptr[pos],(int16_t)((color_error * mult) /bleed));
}
/* seed furthest line forward */
mult = customdither[2][dx];
if (mult > 0) {
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[pos],(int16_t)((color_error * mult) /bleed));
}
}
break;
default: /* buckels dither - d9 */
/*
* 2 1
1 2 1
1 (1/8)
Serpentine
1 2 *
1 2 1
1
*/
/* 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;
}
/* for serpentine effect alternating scanlines run the error in reverse */
if (serpentine == 1 && y%2 == 1) {
/* finish this line */
if (x>0)AdjustShortPixel(1,(int16_t *)&colorptr[x-1],(int16_t)((color_error*2)/bleed)+total_difference);
if (x>1)AdjustShortPixel(1,(int16_t *)&colorptr[x-2],(int16_t)(color_error/bleed));
}
else {
/* finish this line */
AdjustShortPixel(1,(int16_t *)&colorptr[x+1],(int16_t)((color_error*2)/bleed)+total_difference);
AdjustShortPixel(1,(int16_t *)&colorptr[x+2],(int16_t)(color_error/bleed));
}
/* if making hgr passes 0 and 1 dither first line only */
if (runs < 2 || ditheroneline == 1) break;
/* seed next line forward */
if (x>0)AdjustShortPixel(threshold,(int16_t *)&seedptr[x-1],(int16_t)(color_error/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x],(int16_t)((color_error*2)/bleed));
AdjustShortPixel(threshold,(int16_t *)&seedptr[x+1],(int16_t)(color_error/bleed));
/* seed furthest line forward */
AdjustShortPixel(threshold,(int16_t *)&seed2ptr[x],(int16_t)(color_error/bleed));
}
}
}
}
/* turn-off hgr color dither */
dither7 = 0;
/* get the mask line from the mask file if we are overlaying this image */
/* the mask file is a 256 color BMP and is applied after rendering is complete and */
/* immediately before Preview files are written to disk and the DHGR buffer is plotted */
/* for monochrome masking the maskfile is either 280 x 192 or 560 x 192 */
/* for color masking the maskfile is always 140 x 192 */
if (overlay == 1) {
ReadMaskLine(y);
}
/* plot dithered scanline in DHGR buffer using selected conversion palette */
/* plot dithered scanline in Preview buffer using selected preview palette */
for (x=0,x1=0;x<width;x++) {
maskpixel = 0;
if (overlay == 1) {
overcolor = maskline[x];
if (mono == 1) {
/* for monochrome masking if an area is black or white
it overlays the image */
if (overcolor == 0 || overcolor == 15) maskpixel = 1;
}
else {
/* for color masking clearcolor is the transparent color for the mask */
/* if the overlay color is some other color then the pixel is overlaid
with the mask color */
if (overcolor != clearcolor) maskpixel = 1;
}
}
if (maskpixel == 1) {
drawcolor = (uint8_t)overcolor;
}
else {
r = (uint8_t)redDither[x];
g = (uint8_t)greenDither[x];
b = (uint8_t)blueDither[x];
drawcolor = GetMedColor(r,g,b,&paldistance);
}
if (mono == 1) {
if (width == 280) hrmonoplot(x,y,drawcolor);
else dhrmonoplot(x,y,drawcolor);
}
else dhrplot(x,y,drawcolor);
/* if color preview option, plot double-wide pixels in pairs of 24-bit RGB triples */
/* unless plotting double lo-res */
if (preview == 1) {
if (mono == 1 || (loresoutput == 1 && lores == 0)) {
previewline[x1] = rgbPreview[drawcolor][BLUE]; x1++;
previewline[x1] = rgbPreview[drawcolor][GREEN];x1++;
previewline[x1] = rgbPreview[drawcolor][RED]; x1++;
}
else {
/* we are plotting a double pixel in a 6 byte chunk - b,g,r,b,g,r */
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][BLUE]; x1++;
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][GREEN];x1++;
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][RED]; x1+=4;
}
}
}
}
uint16_t WriteDIBHeader(FILE *fp, uint16_t pixels, uint16_t rasters)
{
uint16_t outpacket;
int c;
memset((char *)&mybmp.bfi.bfType[0],0,sizeof(BMPHEADER));
/* create the info header */
mybmp.bmi.biSize = (uint32_t)sizeof(BITMAPINFOHEADER);
mybmp.bmi.biWidth = (uint32_t)pixels;
mybmp.bmi.biHeight = (uint32_t)rasters;
mybmp.bmi.biPlanes = 1;
mybmp.bmi.biBitCount = 24;
mybmp.bmi.biCompression = (uint32_t) BI_RGB;
/* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */
outpacket = (uint16_t)mybmp.bmi.biWidth * 3;
while (outpacket%4 != 0)outpacket++;
mybmp.bmi.biSizeImage = (uint32_t)outpacket;
mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight;
/* create the file header */
mybmp.bfi.bfType[0] = 'B';
mybmp.bfi.bfType[1] = 'M';
mybmp.bfi.bfOffBits = (uint32_t) 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;
}
void DiffuseError(uint16_t outpacket)
{
/*
http://en.wikipedia.org/wiki/Error_diffusion
x,y axis (two dimensional) color error diffusion
in the case of a BMP this disperses the color from the bottom left diagonally through the image
in the case of some other image format that stores rasters from the top instead of the bottom this would
disperse the color diagonally from the top left.
the following scaling ratio is applied:
1/2 of the gun value of the current pixel is summed with previous pixels as follows:
1/4 of the gun value of the previous pixel is added to 1/2 the value of the current pixel
1/8 of the gun value from the previous pixel on the previous line is added to the 1/2 the value of the current pixel
1/8 of the gun value from the same pixel on the previous line is added to 1/2 the value of the current pixel
*/
uint8_t r2, g2, b2, r4, g4, b4;
uint16_t r, g, b;
int16_t i;
/* create previous pixels for current pixels */
/* previous pixel on same line */
b2 = dibscanline1[0];
g2 = dibscanline1[1];
r2 = dibscanline1[2];
/* previous pixel from previous line */
b4 = dibscanline2[0];
g4 = dibscanline2[1];
r4 = dibscanline2[2];
for (i=0; i < outpacket; i+=3) {
/* RGB Triples */
b = (uint16_t) dibscanline1[i];
g = (uint16_t) dibscanline1[i+1];
r = (uint16_t) dibscanline1[i+2];
/* add pixels to create 7/8 ratio of required value */
/* 4 - current pixels
2 - previous pixels
1 - previous pixel below */
b *= 4; b += b2; b += b2; b += b4;
g *= 4; g += g2; g += g2; g += g4;
r *= 4; r += r2; r += r2; r += r4;
/* add 1 current pixel below - 1/8 ratio of required value */
/* carry forward b4,g4 and r4 to next pixel */
/* current pixel below becomes previous pixel below */
b4 = dibscanline2[i]; b += b4; while (b % 8 != 0) b++; b /=8;
g4 = dibscanline2[i+1]; g += g4; while (g % 8 != 0) g++; g /=8;
r4 = dibscanline2[i+2]; r += r4; while (r % 8 != 0) r++; r /=8;
/* assign new color to current pixel */
/* and carry forward b2,g2 and r2 to next pixel */
/* current pixel becomes previous pixel */
dibscanline1[i] = b2 = (uint8_t)b;
dibscanline1[i+1] = g2 = (uint8_t)g;
dibscanline1[i+2] = r2 = (uint8_t)r;
}
}
/* create an error-diffused copy of the input file
and use that instead */
FILE *ReadDIBFile(FILE *fp, uint16_t packet)
{
FILE *fpdib;
uint16_t y,outpacket;
if((fpdib=fopen(dibfile,"wb"))==NULL) {
printf("Error Opening %s for writing!\n",dibfile);
return fp;
}
outpacket = WriteDIBHeader(fpdib,bmpwidth,bmpheight);
if (outpacket != packet) {
fclose(fpdib);
remove(dibfile);
printf("Error writing header to %s!\n",dibfile);
return fp;
}
/* seek past extraneous info in header if any */
fseek(fp,bfi.bfOffBits,SEEK_SET);
for (y=0;y<bmpheight;y++) {
fread((char *)&bmpscanline[0],1,packet,fp);
memcpy(&dibscanline1[0],&bmpscanline[0],packet);
if (y==0) memcpy(&dibscanline2[0],&bmpscanline[0],packet);
DiffuseError(packet);
/* save a copy of the previous line */
if (diffuse == 2) {
/* if diffusion is by original value use pure line */
memcpy(&dibscanline2[0],&bmpscanline[0],packet);
}
else {
/* otherwise use diffused line */
memcpy(&dibscanline2[0],&dibscanline1[0],packet);
}
fwrite((char *)&dibscanline1[0],1,packet,fpdib);
}
fclose(fpdib);
fclose(fp);
if((fp=fopen(dibfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",dibfile);
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",bmpfile);
return fp;
}
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
return fp;
}
/* helper functions for horizontal resizing */
int ExpandBMPLine(uint8_t *src, uint8_t *dest, uint16_t srcwidth, uint16_t scale)
{
int i,j,k;
unsigned char r,g,b;
srcwidth *=3;
for (i=0,j=0,k=0;i<srcwidth;) {
b = src[i++];
g = src[i++];
r = src[i++];
for (j=0;j<scale;j++) {
dest[k] = b; k++;
dest[k] = g; k++;
dest[k] = r; k++;
}
}
return k;
}
int ShrinkBMPLine(uint8_t *src, uint8_t *dest, int srcwidth)
{
int i,j,k;
uint16_t r,g,b;
scale = srcwidth / 140;
srcwidth *=3;
for (i=0,j=0,k=0;k<srcwidth;) {
b = g = r = 0;
for (j=0;j<scale;j++) {
b += src[k++];
g += src[k++];
r += src[k++];
}
dest[i] = (b/scale);i++;
dest[i] = (g/scale);i++;
dest[i] = (r/scale);i++;
}
return i;
}
/* shrink 640 or 320 to 140 */
/* uses dhrbuf as a work buffer and output buffer */
void ShrinkPixels(FILE *fp)
{
int packet = (bmpwidth * 3);
while (packet%4 != 0)packet++;
fread((char *)&bmpscanline[0],1,packet,fp);
ExpandBMPLine((uint8_t *)&bmpscanline[0],(uint8_t *)&dhrbuf[0],bmpwidth,7);
ShrinkBMPLine((uint8_t *)&dhrbuf[0],(uint8_t *)&dhrbuf[0],(uint16_t)(bmpwidth * 7));
}
/* table-driven scaling from 25 to 24 lines */
void ShrinkLines25to24(FILE *fp, FILE *fp2)
{
uint16_t pixel,x,i;
uint16_t x1,x2;
ShrinkPixels(fp);
memcpy(&dibscanline1[0],&dhrbuf[0],420);
if (bmpheight == 400) {
ShrinkPixels(fp);
for (x=0;x<420;x++) {
pixel = (uint16_t) dhrbuf[x];
pixel += dibscanline1[x];
dibscanline1[x] = (uint8_t) (pixel/2);
}
}
for (i=0;i<24;i++) {
ShrinkPixels(fp);
memcpy(&dibscanline2[0],&dhrbuf[0],420);
if (bmpheight == 400) {
ShrinkPixels(fp);
for (x=0;x<420;x++) {
pixel = (uint16_t) dhrbuf[x];
pixel += dibscanline2[x];
dibscanline2[x] = (uint8_t) (pixel/2);
}
}
for (x=0;x<420;x++) {
x1 = (uint16_t)dibscanline1[x];
x2 = (uint16_t)dibscanline2[x];
pixel = (uint16_t) (x1 * mix25to24[i][0]) + (x2 * mix25to24[i][1]);
bmpscanline[x] = (uint8_t)(pixel/25);
}
fwrite((char *)&bmpscanline[0],1,420,fp2);
if (i<23)memcpy(&dibscanline1[0],&dibscanline2[0],420);
}
}
/* 640 x 480 scaled to 140 x 192 */
void ShrinkLines640x480(FILE *fp, FILE *fp2)
{
uint16_t pixel1,pixel2,x,i;
ShrinkPixels(fp);
memcpy(&dibscanline1[0],&dhrbuf[0],420);
ShrinkPixels(fp);
memcpy(&dibscanline2[0],&dhrbuf[0],420);
ShrinkPixels(fp);
memcpy(&dibscanline3[0],&dhrbuf[0],420);
ShrinkPixels(fp);
memcpy(&dibscanline4[0],&dhrbuf[0],420);
ShrinkPixels(fp);
for (x=0;x<420;x++) {
pixel1 = (uint16_t) dibscanline1[x];
pixel1 += dibscanline2[x];
pixel1 *= 2;
pixel1 += dibscanline3[x];
dibscanline1[x] = (uint8_t) (pixel1/5);
pixel2 = (uint16_t) dhrbuf[x];
pixel2 += dibscanline4[x];
pixel2 *= 2;
pixel2 += dibscanline3[x];
dibscanline2[x] = (uint8_t) (pixel2/5);
}
fwrite((char *)&dibscanline1[0],1,420,fp2);
fwrite((char *)&dibscanline2[0],1,420,fp2);
}
/* merges the RGB values of 2 lines into one */
void ShrinkLines560x384(FILE *fp, FILE *fp2)
{
uint16_t x, pixel, packet = (bmpwidth * 3);
while (packet%4 != 0)packet++;
fread((char *)&bmpscanline[0],1,packet,fp);
ShrinkBMPLine((uint8_t *)&bmpscanline[0],(uint8_t *)&dibscanline1[0],bmpwidth);
fread((char *)&bmpscanline[0],1,packet,fp);
ShrinkBMPLine((uint8_t *)&bmpscanline[0],(uint8_t *)&dibscanline2[0],bmpwidth);
for (x=0;x<420;x++) {
pixel = (uint16_t)dibscanline1[x];
pixel+= dibscanline2[x];
bmpscanline[x] = (uint8_t)(pixel/2);
}
fwrite((char *)&bmpscanline[0],1,420,fp2);
}
/* lo-res and double lo-res input files are in multiples of 80 pixels */
int ShrinkLoResLine(uint8_t *src, uint8_t *dest, int srcwidth)
{
int i,j,k;
uint16_t r,g,b;
scale = srcwidth / 80;
srcwidth *=3;
for (i=0,j=0,k=0;k<srcwidth;) {
b = g = r = 0;
for (j=0;j<scale;j++) {
b += src[k++];
g += src[k++];
r += src[k++];
}
dest[i] = (b/scale);i++;
dest[i] = (g/scale);i++;
dest[i] = (r/scale);i++;
}
return i;
}
void ShrinkLoResData(FILE *fp, FILE *fp2)
{
uint16_t x, x1, x2, y, lines, srcwidth, packet = (bmpwidth * 3), pixel;
while (packet%4 != 0)packet++;
switch(bmpwidth) {
case 40:
case 80:
case 88: lines = 1; srcwidth = 80; break;
case 160:
case 176: lines = 2; srcwidth = 160; break;
case 320: lines = 4; srcwidth = 320; break;
case 560: lines = 8; srcwidth = 560; break;
case 640: lines = 10;srcwidth = 640; break;
}
/* clear accumulators */
memset(&redDither[0],0,480);
memset(&greenDither[0],0,480);
memset(&blueDither[0],0,480);
/* scale up */
for (y = 0; y < lines; y++) {
if (bmpwidth == 40) {
fread((char *)&dibscanline1[0],1,packet,fp);
/* double the width */
for (x = 0, x1 = 0, x2 = 0; x < 40; x++) {
bmpscanline[x2] = bmpscanline[x2+3] = dibscanline1[x1]; x1++; x2++;
bmpscanline[x2] = bmpscanline[x2+3] = dibscanline1[x1]; x1++; x2++;
bmpscanline[x2] = bmpscanline[x2+3] = dibscanline1[x1]; x1++; x2+=4;
}
}
else {
fread((char *)&bmpscanline[0],1,packet,fp);
}
ShrinkLoResLine((uint8_t *)&bmpscanline[0],(uint8_t *)&dibscanline1[0],srcwidth);
for (x = 0, x1=0; x < 80; x++) {
blueDither[x] += dibscanline1[x1]; x1++;
greenDither[x] += dibscanline1[x1]; x1++;
redDither[x] += dibscanline1[x1]; x1++;
}
}
/* scale down */
for (x = 0, x1=0; x < 80; x++) {
pixel = blueDither[x] / lines;
bmpscanline[x1] = (uint8_t) pixel; x1++;
pixel = greenDither[x] / lines;
bmpscanline[x1] = (uint8_t) pixel; x1++;
pixel = redDither[x] / lines;
bmpscanline[x1] = (uint8_t) pixel; x1++;
}
fwrite((char *)&bmpscanline[0],1,240,fp2);
}
/* create a resized copy of the input file
and use that instead */
FILE *ResizeBMP(FILE *fp, int16_t resize)
{
FILE *fp2;
uint16_t x,y,packet,outpacket,chunks;
uint16_t i,j,r,g,b;
uint32_t offset=0L;
#ifdef TURBOC
if (resize == 0)return NULL;
#endif
if((fp2=fopen(scaledfile,"wb"))==NULL) {
printf("Error Opening %s for writing!\n",scaledfile);
return fp;
}
if (loresoutput == 1) {
/* Lo-Res and Double Lo-Res */
if (appletop == 0) outpacket = WriteDIBHeader(fp2,80,48);
else outpacket = WriteDIBHeader(fp2,80,40);
if (outpacket != 240) {
fclose(fp2);
remove(scaledfile);
printf("Error writing header to %s!\n",scaledfile);
return fp;
}
}
else {
/* HGR and DHGR */
if (justify == 1) outpacket = WriteDIBHeader(fp2,280,192);
else outpacket = WriteDIBHeader(fp2,140,192);
if (outpacket != 420 && outpacket != 840) {
fclose(fp2);
remove(scaledfile);
printf("Error writing header to %s!\n",scaledfile);
return fp;
}
}
packet = bmpwidth * 3;
while (packet%4 != 0)packet++;
if (justify == 1) {
if (loresoutput == 0) {
/* HGR and DHGR */
switch (bmpwidth) {
case 640:
if (jxoffset > -1) {
if (jxoffset > 80) jxoffset = 80;
offset+= jxoffset * 3;
}
else {
offset += 120;
}
if (bmpheight == 480) {
if (jyoffset > -1) {
if (jyoffset > 96)jyoffset = 0;
else jyoffset = 96 - jyoffset;
offset += (1920L * jyoffset);
}
else {
offset += (1920L * 48);
}
}
if (bmpheight == 400) {
if (jyoffset > -1) {
if (jyoffset > 16)jyoffset = 0;
else jyoffset = 16 - jyoffset;
offset += (1920L * jyoffset);
}
else {
offset += (1920L * 8);
}
}
break;
case 320:
if (jxoffset > -1) {
if (jxoffset > 40) jxoffset = 40;
offset+= jxoffset * 3;
}
else {
offset += 60;
}
if (jyoffset > -1) {
if (jyoffset > 8)jyoffset = 0;
else jyoffset = 8 - jyoffset;
offset += (960L * jyoffset);
}
else {
offset += (960L * 4);
}
break;
}
}
else {
/* LGR and DLGR */
offset += (jyoffset * packet);
offset += (jxoffset * 3);
}
}
/* seek past extraneous info in header if any */
fseek(fp,bfi.bfOffBits+offset,SEEK_SET);
if (justify == 1 && loresoutput == 0) {
for (y = 0;y< 192;y++) {
fread((char *)&dibscanline1[0],1,packet,fp);
if (bmpheight == 200) {
/* no merging at all on 320 x 200 */
fwrite((char *)&dibscanline1[0],1,outpacket,fp2);
continue;
}
fread((char *)&dibscanline2[0],1,packet,fp);
for (x = 0,i=0,j=0;x<280;x++) {
b = (uint16_t)dibscanline1[i]; b+= dibscanline2[i]; i++;
g = (uint16_t)dibscanline1[i]; g+= dibscanline2[i]; i++;
r = (uint16_t)dibscanline1[i]; r+= dibscanline2[i]; i++;
/* half merge (merge vertically) unless merge is turned-on */
if (merge == 0) {
i+=3;b*=2;g*=2;r*=2;
}
else {
b += dibscanline1[i]; b+= dibscanline2[i]; i++;
g += dibscanline1[i]; g+= dibscanline2[i]; i++;
r += dibscanline1[i]; r+= dibscanline2[i]; i++;
}
bmpscanline[j] = (uint8_t) (uint16_t)(b/4);j++;
bmpscanline[j] = (uint8_t) (uint16_t)(g/4);j++;
bmpscanline[j] = (uint8_t) (uint16_t)(r/4);j++;
}
fwrite((char *)&bmpscanline[0],1,outpacket,fp2);
}
}
else {
if (loresoutput == 1) {
/* LGR and DLGR input file */
if (appletop == 1) chunks = 40;
else chunks = 48;
for (y=0;y<chunks;y++) ShrinkLoResData(fp,fp2);
}
else {
/* HGR and DHGR input file */
switch(bmpheight)
{
case 200:
case 400: chunks = 8; break;
case 384: chunks = 192; break;
case 480: chunks = 96; break;
}
for (y=0;y<chunks;y++) {
switch(bmpheight) {
case 200:
case 400: ShrinkLines25to24(fp,fp2);break;
case 480: ShrinkLines640x480(fp,fp2);break;
case 384: ShrinkLines560x384(fp,fp2);break;
}
}
}
}
fclose(fp2);
fclose(fp);
if((fp=fopen(scaledfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",scaledfile);
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",bmpfile);
return fp;
}
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
return fp;
}
/* expand monochrome bmp lines to 24-bit bmp lines */
void ReformatMonoLine()
{
int i,j,k,packet;
uint8_t b = 0, w = 255;
if (bmpwidth == 280) packet = 35;
else packet = 70;
memcpy(&dibscanline1[0],&bmpscanline[0],packet);
if (reverse == 1) {
b = 255;
w = 0;
}
for(i=0,j=0;i<packet;i++)
{
for(k=0;k<8;k++)
{
if (dibscanline1[i]&msk[k]) {
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] = w;
}
else {
bmpscanline[j] = bmpscanline[j+1] = bmpscanline[j+2] = b;
}
j+=3;
}
}
}
/* expand 16 color and 256 color bmp lines to 24-bit bmp lines */
void ReformatVGALine()
{
int16_t i, j, packet;
uint8_t ch;
memset(dibscanline1,0,1920);
if (bmi.biBitCount == 8) {
memcpy(&dibscanline1[0],&bmpscanline[0],bmpwidth);
}
else {
packet = bmpwidth /2;
if (bmpwidth%2 != 0) packet++;
for (i=0,j=0;i<packet;i++) {
ch = bmpscanline[i] >> 4;
dibscanline1[j] = ch; j++;
ch = bmpscanline[i] & 0xf;
dibscanline1[j] = ch; j++;
}
}
memset(&bmpscanline[0],0,1920);
for (i=0,j=0;i<bmpwidth;i++) {
ch = dibscanline1[i];
bmpscanline[j] = sbmp[ch].rgbBlue; j++;
bmpscanline[j] = sbmp[ch].rgbGreen; j++;
bmpscanline[j] = sbmp[ch].rgbRed; j++;
}
}
/* convert 16 color and 256 color bmps to 24 bit bmps */
/* convert Monochrome bmps to 24 bit bmps */
FILE *ReformatBMP(FILE *fp)
{
FILE *fp2;
int16_t status = SUCCESS;
uint16_t packet, outpacket,y;
if (bmi.biBitCount == 1) {
/* Mono HGR = 280 and Mono DHGR = 560 */
if (bmpwidth != 280 && bmpwidth != 560) status = INVALID;
if (bmpheight != 192) status = INVALID;
}
else {
/* HGR and DHGR size check */
/* LGR and DLGR sizes were checked previously in Convert() */
if (loresoutput == 0) {
if (bmpwidth > 280) {
status = INVALID;
switch(bmpwidth) {
case 640: if (bmpheight == 400 || bmpheight == 480) status = SUCCESS; break;
case 320: if (bmpheight == 200) status = SUCCESS; break;
case 560: if (bmpheight == 384) status = SUCCESS; break;
}
}
else {
if (bmpheight > 192) status = INVALID;
}
}
}
if (status == INVALID) {
fclose(fp);
fp = NULL;
printf("%s is not a supported size!\n",bmpfile);
return fp;
}
if (bmi.biBitCount == 8)
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*256,1,fp);
else if (bmi.biBitCount == 4)
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*16,1,fp);
else if (bmi.biBitCount == 1)
fread((char *)&sbmp[0].rgbBlue, sizeof(RGBQUAD)*2,1,fp);
/* seek past extraneous info in header if any */
fseek(fp,bfi.bfOffBits,SEEK_SET);
/* align on 4 byte boundaries */
if (bmi.biBitCount == 1) {
if (bmpwidth == 280) packet = 36;
else packet = 72;
}
else if (bmi.biBitCount == 8) {
packet = bmpwidth;
}
else {
packet = bmpwidth / 2;
if (bmpwidth%2 != 0)packet++;
}
while ((packet % 4)!=0)packet++;
if((fp2=fopen(reformatfile,"wb"))==NULL) {
printf("Error Opening %s for writing!\n",reformatfile);
return fp;
}
if (bmi.biBitCount == 1) {
if (bmpwidth == 280) outpacket = WriteDIBHeader(fp2,bmpwidth,bmpheight);
else outpacket = WriteDIBHeader(fp2,bmpwidth,bmpheight*2);
}
else {
outpacket = WriteDIBHeader(fp2,bmpwidth,bmpheight);
}
if (outpacket < 1) {
fclose(fp2);
remove(reformatfile);
printf("Error writing header to %s!\n",reformatfile);
return fp;
}
for (y=0;y<bmpheight;y++) {
fread((char *)&bmpscanline[0],1,packet,fp);
if (bmi.biBitCount == 1) ReformatMonoLine();
else ReformatVGALine();
fwrite((char *)&bmpscanline[0],1,outpacket,fp2);
/* double lines for DHGR monochrome conversion */
/* single lines for HGR monochrome conversion */
if (bmi.biBitCount == 1 && bmpwidth == 560) fwrite((char *)&bmpscanline[0],1,outpacket,fp2);
}
fclose(fp2);
fclose(fp);
reformat = 1;
if((fp=fopen(reformatfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",reformatfile);
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",bmpfile);
return fp;
}
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
return fp;
}
/* overlay using a 256 color BMP file in verbatim output resolution */
/* HGR and DHGR color overlay files are 140 x 192 */
/* HGR and DHGR monochrome are 280 x 192 and 560 x 192 respectively */
int16_t OpenMaskFile()
{
int16_t status = INVALID;
uint16_t i, width=0, height=0;
double dummy;
int c;
if (overlay == 0) return status;
overlay = 0;
fpmask = fopen(maskfile,"rb");
if (NULL == fpmask) {
printf("Error opening maskfile %s\n",maskfile);
return status;
}
for (;;) {
c = fread((char *)&maskbmp.bfi.bfType[0],sizeof(BMPHEADER),1,fpmask);
if (c!= 1) {
/* printf("header read returned %d\n",c); */
break;
}
if (maskbmp.bmi.biCompression==BI_RGB &&
maskbmp.bfi.bfType[0] == 'B' && maskbmp.bfi.bfType[1] == 'M' &&
maskbmp.bmi.biPlanes==1 && maskbmp.bmi.biBitCount == 8) {
width = (uint16_t) maskbmp.bmi.biWidth;
height = (uint16_t) maskbmp.bmi.biHeight;
}
/* this ensures that only full-screen output is masked */
/* it doesn't make sense to mix image fragment routines into here */
if (mono == 1) {
if (hgroutput == 0) {
if (width != 560 || height != 192) {
/* printf("width = %d, height = %d\n",width,height); */
puts("Mask file width must be 560 x 192");
break;
}
}
else {
if (width != 280 || height != 192) {
/* printf("width = %d, height = %d\n",width,height); */
puts("Mask file width must be 280 x 192");
break;
}
}
}
else {
if (width != 140 || height != 192) {
/* printf("width = %d, height = %d\n",width,height); */
puts("Mask file width must be 140 x 192");
break;
}
}
fread((char *)&maskpalette[0].rgbBlue, sizeof(RGBQUAD)*256,1,fpmask);
for (i=0;i<256;i++) {
if (mono == 1) {
/* build a remap array for monochrome output */
if (maskpalette[i].rgbRed == 255 && maskpalette[i].rgbGreen == 255 &&
maskpalette[i].rgbBlue == 255) {
remap[i] = 15;
}
else if (maskpalette[i].rgbRed == 0 && maskpalette[i].rgbGreen == 0 &&
maskpalette[i].rgbBlue == 0) {
remap[i] = 0;
}
else {
/* anything else maps to color 1 for mono */
remap[i] = 1;
}
}
else {
/* build a remap array for color output */
remap[i] = GetMedColor(maskpalette[i].rgbRed,
maskpalette[i].rgbGreen,
maskpalette[i].rgbBlue,&dummy);
}
}
fseek(fpmask,bfi.bfOffBits,SEEK_SET);
status = SUCCESS;
overlay = 1;
break;
}
if (status == INVALID){
/* puts("Failed!"); */
fclose(fpmask);
fpmask = NULL;
if (quietmode == 1)printf("Error loading %s\n",maskfile);
}
else {
if (quietmode == 1)printf("Loaded mask %s\n",maskfile);
}
return status;
}
/* LGR and DLGR only */
int16_t ValidLoResSizeRange()
{
int16_t status = INVALID;
/* monochrome input files are not accepted for LGR or DLGR conversion */
if (bmi.biBitCount != 1) {
/* http://en.wikipedia.org/wiki/Windowbox_(film) - cropped (clipped) images */
/* restricted to a reasonable number of sizes that make sense to me */
/* additional scaling can be done outside of here */
/* for lores verbatim fullscreen output use 40 x 48 input files */
/* for double lores verbatim fullscreen output use 80 x 48 input files */
/* for mixed text and graphics mode output of the above is automatic with
input files of 40 lines, and by default clips the bottom 8 lines with
input files of 48 lines... and optionally the windowbox can be shifted
downwards by 0-8 lines when justify option JL is used.
larger sizes behave similarly. scaling of larger sizes uses pixel merge
to combine pixels and scanlines so detail loss will occur. sizes like
88 x 52, 176 x 104 and 320 x 200 also provide optional windowbox shifting for
fullscreen output as well as for mixed text and graphics output. these
over-scanned sizes are centered on the display by default. over-scanned sizes
use the same nominal resolution as pre-scaled sizes,
640 x 480 (fullscreen) and 640 x 400 (mixed text and graphics) is the only
"Classic Size" supported for LGR and DLGR
during processing, in order to lever the same routines used by HGR and DHGR,
half-scaling is provided for LGR as a "second level" of scaling. all output
is restricted to one of 4 sizes:
LGR - 40 x 40 and 40 x 48
DLGR - 80 x 40 and 80 x 48
LGR and DLGR image fragments are not supported for this version of Bmp2DHR
*/
switch(bmpwidth) {
case 40: lores = 1; /* verbatim 1:1 lgr only - nominal size 40 x 48 */
case 80:
/* verbatim 1:1 for dlgr - nominal size 80 x 48 */
/* 2:1 scaled for lgr */
if (bmpheight == 40 || bmpheight == 48) status = SUCCESS;
else break;
jxoffset = 0; /* windowbox in vertical axis only */
if (bmpheight == 40) {
/* mixed text and graphics */
/* 40 x 40 and 80 x 40 - windowbox not required */
appletop = 1;
justify = jyoffset = 0;
break;
}
if (appletop == 1) {
/* mixed text and graphics */
/* 40 x 48 and 80 x 48 - windowbox required */
/* top justified if not otherwise specified */
if (justify == 1 && (jyoffset > -1 || jyoffset < 9)) {
jyoffset = 8 - jyoffset;
break;
}
jyoffset = 8;
justify = 1;
}
else {
/* fullscreen */
/* 40 x 48 and 80 x 48 */
justify = jyoffset = 0;
}
break;
case 88: /* the next two input widths (88 and 176) are processed as over-scanned images */
/* these originate from clipped BMP format "MiniPix" conversions */
/* saved in Windows Paint as "old" and "new" printshop "pastes" from ClipShop in
small-copy (single-scaled) or regular copy (double-scaled) format. */
if (bmpheight != 52) break;
status = SUCCESS; /* verbatim for dlgr - nominal size 80 x 48 */
if (jxoffset < 0 || jxoffset > 8) {
jxoffset = 4; /* centre */
}
if (appletop == 1) { /* mixed text and graphics - dlgr windowbox 80 x 40 */
if (justify == 1 && (jyoffset > -1 && jyoffset < 13)) jyoffset = 12 - jyoffset;
else jyoffset = 10;
justify = 1;
break;
}
justify = 1;
if (jyoffset < 0 || jyoffset > 4) {
jyoffset = 2; /* centre */
}
break;
case 176: if (bmpheight != 104) break;
status = SUCCESS; /* dlgr double-scaled - nominal size 160 x 96 */
if (jxoffset < 0 || jxoffset > 16) {
jxoffset = 8; /* centre */
}
if (appletop == 1) { /* mixed text and graphics - dlgr windowbox 160 x 80 */
if (justify == 1 && (jyoffset > -1 && jyoffset < 25)) jyoffset = 24 - jyoffset;
else jyoffset = 20;
justify = 1;
break;
}
justify = 1;
if (jyoffset < 0 || jyoffset > 8) {
jyoffset = 4; /* centre */
}
break;
case 160: /* nominal size 160 x 96 */
/* 2:2 scaled for dlgr, 4:2 scaled for lgr */
if (bmpheight == 80 || bmpheight == 96) status = SUCCESS;
else break;
jxoffset = 0;
if (bmpheight == 80) {
/* 160 x 80 - mixed text and graphics */
appletop = 1;
justify = jyoffset = 0;
break;
}
if (appletop == 1) {
/* mixed text and graphics */
/* 160 x 96 - windowbox required */
/* top justified if not otherwise specified */
if (justify == 1 && (jyoffset > -1 || jyoffset < 17)) {
jyoffset = 16 - jyoffset;
break;
}
jyoffset = 16;
justify = 1;
}
else {
/* fullscreen */
/* 160 x 96 */
justify = jyoffset = 0;
}
break;
case 320: /* nominal size 320 x 192 */
/* 4:4 scaled for dlgr, 8:4 scaled for lgr */
if (bmpheight == 160 || bmpheight == 192 || bmpheight == 200) {
status = SUCCESS;
jxoffset = 0; /* vertical scaling of mixed text and graphics only */
if (bmpheight == 160) {
appletop = 1;
justify = jyoffset = 0;
break;
}
if (bmpheight == 192) {
if (appletop == 1) {
if (justify == 1 && (jyoffset > -1 && jyoffset < 33)) jyoffset = 32 - jyoffset;
else jyoffset = 32;
justify = 1;
break;
}
justify = jyoffset = 0;
break;
}
/* 320 x 200 */
/* centre in frame by default */
if (appletop == 1) {
if (justify == 1 && (jyoffset > -1 && jyoffset < 41)) jyoffset = 40 - jyoffset;
else jyoffset = 36;
justify = 1;
break;
}
if (justify == 1 && (jyoffset > -1 && jyoffset < 9)) jyoffset = 8 - jyoffset;
else jyoffset = 4;
justify = 1;
}
break;
case 560: /* "Classic" full-screen conversion of pre-scaled 560 x 384 input */
/* "Classic" mixed text and graphics screen conversion of pre-scaled
560 x 384 full-screen or 560 x 320 cropped input */
/* 7:8 scaled for dlgr, 14:8 scaled for lgr */
if (bmpheight == 320 || bmpheight == 384) status = SUCCESS;
else break;
jxoffset = 0;
if (bmpheight == 320) {
/* mixed text and graphics */
appletop = 1;
justify = jyoffset = 0;
break;
}
if (appletop == 1) {
/* mixed text and graphics */
if (justify == 1 && (jyoffset > -1 && jyoffset < 65)) jyoffset = 64 - jyoffset;
else jyoffset = 64;
justify = 1;
}
else {
/* fullscreen */
justify = jyoffset = 0;
}
break;
case 640: /* "Classic" full-screen conversion of 640 x 480 square pixeled input */
/* "Classic" mixed text and graphics screen conversion of square pixeled
640 x 480 full-screen or 640 x 400 cropped input */
/* 8:10 scaled for dlgr, 16:10 scaled for dlgr */
if (bmpheight == 480 || bmpheight == 400) status = SUCCESS;
else break;
jxoffset = 0;
if (bmpheight == 400) {
/* mixed text and graphics */
appletop = 1;
justify = jyoffset = 0;
break;
}
if (appletop == 1) {
/* mixed text and graphics */
if (justify == 1 && (jyoffset > -1 && jyoffset < 81)) jyoffset = 80 - jyoffset;
else jyoffset = 64;
justify = 1;
}
else {
/* fullscreen */
justify = jyoffset = 0;
}
break;
}
}
return status;
}
/* for color DHGR */
/* 1. reads a 24 bit BMP file in the range from 1 x 1 to 280 x 192 */
/* 2. writes a DHGR screen image or optionally a DHGR image fragment */
/* 3. also creates an optional preview file...
when preview is on... also leaves an optional error-diffused dib file
in place if error diffusion is also turned-on */
/* Etcetera */
int16_t Convert()
{
FILE *fp, *fpdib, *fpreview;
int16_t status = INVALID, resize = 0;
uint16_t x,x1,x2,y,yoff,i,packet, outpacket, width, dwidth, red, green, blue;
uint8_t r,g,b,drawcolor;
uint32_t pos, prepos;
/* if using a mask file, open it now */
/* leave it open throughout the conversion session */
/* it will be closed in main before exiting */
if (overlay == 1)OpenMaskFile();
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",bmpfile);
return status;
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
/* reformat to 24 bit */
if (bmi.biCompression==BI_RGB &&
bfi.bfType[0] == 'B' && bfi.bfType[1] == 'M' && bmi.biPlanes==1) {
bmpwidth = (uint16_t) bmi.biWidth;
bmpheight = (uint16_t) bmi.biHeight;
if (loresoutput == 1) {
/* LGR and DLGR */
status = ValidLoResSizeRange();
if (status == INVALID) {
fclose(fp);
printf("%s is in the wrong format!\n",bmpfile);
return status;
}
}
if (bmi.biBitCount == 8 || bmi.biBitCount == 4) {
fp = ReformatBMP(fp);
if (fp == NULL) return INVALID;
}
}
if (bmi.biCompression==BI_RGB &&
bfi.bfType[0] == 'B' && bfi.bfType[1] == 'M' &&
bmi.biPlanes==1 && bmi.biBitCount == 24) {
bmpwidth = (uint16_t) bmi.biWidth;
bmpheight = (uint16_t) bmi.biHeight;
if (loresoutput == 0) {
/* color HGR and DHGR */
/* resize some classic screen sizes */
if (bmpwidth == 320 && bmpheight == 200)
resize = 1;
else if (bmpwidth == 640 && bmpheight == 400)
resize = 2;
else if (bmpwidth == 640 && bmpheight == 480)
resize = 3;
else if (bmpwidth == 560 && bmpheight == 384)
resize = 4;
}
else {
/* color LGR and DLGR */
/* all lo-res and double lo-res input is resized to one of 2 -
sizes of input file:
80 x 48 or 80 x 40
the input file is then processed as a "small" double hi-res
image. since the color palette is exactly the same, the same
rendering options are available.
after processing the double hi-res output buffer is converted to
lo-res or double lo-res formatting and saved as Apple II native
LGR or DLGR output.
*/
resize = 5;
}
if (resize != 0) {
memset(&bmpscanline[0],0,1920);
memset(&dibscanline1[0],0,1920);
memset(&dibscanline2[0],0,1920);
memset(&dibscanline3[0],0,1920);
memset(&dibscanline4[0],0,1920);
fp = ResizeBMP(fp,resize);
if (fp == NULL) return INVALID;
bmpwidth = (uint16_t) bmi.biWidth;
bmpheight = (uint16_t) bmi.biHeight;
}
if (loresoutput == 0) {
/* HGR and DHGR output */
if (scale == 0) {
if (bmpwidth > 140) scale = 1;
}
if (scale == 1) {
width = bmpwidth;
dwidth = (bmpwidth+1)/2;
if (bmpwidth > 0 && bmpwidth < 281 &&
bmpheight > 0 && bmpheight < 193) status = SUCCESS;
}
else {
width = bmpwidth * 2;
dwidth = bmpwidth;
if (bmpwidth > 0 && bmpwidth < 141 &&
bmpheight > 0 && bmpheight < 193) status = SUCCESS;
}
}
else {
/* LGR and DLGR */
width = bmpwidth;
if (lores == 1) {
scale = 1;
dwidth = 40;
}
else {
scale = 0;
dwidth = 80;
}
}
}
if (status == INVALID) {
fclose(fp);
printf("%s is in the wrong format!\n",bmpfile);
return status;
}
packet = bmpwidth * 3;
/* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */
while ((packet % 4) != 0) packet++;
/* error diffusion option */
if (diffuse != 0) {
/* clear buffers */
memset(&bmpscanline[0],0,960);
memset(&dibscanline1[0],0,960);
memset(&dibscanline2[0],0,960);
fp = ReadDIBFile(fp, packet);
if (fp == NULL) return INVALID;
}
if (preview!=0) {
fpreview = fopen(previewfile,"wb+");
if (fpreview != NULL) {
outpacket = WriteDIBHeader(fpreview,width,bmpheight);
if (outpacket == 0) {
fclose(fpreview);
remove(previewfile);
printf("Error writing header to %s!\n",previewfile);
preview = 0;
}
else {
/* pad the preview file */
memset(&dibscanline1[0],0,960);
for (y=0;y<bmpheight;y++) fwrite((char *)&dibscanline1[0],1,outpacket,fpreview);
/* set the seek distance to scanline 0 in the preview file */
prepos = (uint32_t) (bmpheight - 1);
prepos *= outpacket;
prepos += mybmp.bfi.bfOffBits;
}
}
else {
printf("Error opening %s for writing!\n",previewfile);
preview = 0;
}
}
/* read BMP from top scanline to bottom scanline */
pos = (uint32_t) (bmpheight - 1);
pos *= packet;
pos += bfi.bfOffBits;
/* clear buffers */
dhrclear();
memset(&bmpscanline[0],0,960);
memset(&previewline[0],0,960);
if (dither != 0) {
/* sizeof(int16_t) * 320 */
memset(&redDither[0],0,640);
memset(&greenDither[0],0,640);
memset(&blueDither[0],0,640);
memset(&redSeed[0],0,640);
memset(&greenSeed[0],0,640);
memset(&blueSeed[0],0,640);
memset(&redSeed2[0],0,640);
memset(&greenSeed2[0],0,640);
memset(&blueSeed2[0],0,640);
}
for (y=0;y<bmpheight;y++,pos-=packet) {
fseek(fp,pos,SEEK_SET);
fread((char *)&bmpscanline[0],1,packet,fp);
if (overlay == 1)ReadMaskLine(y);
if (scale == 1) {
for (x = 0,i = 0, x1=0; x < bmpwidth; x++) {
/* get even pixel values */
b = bmpscanline[i]; i++;
g = bmpscanline[i]; i++;
r = bmpscanline[i]; i++;
x++;
/* get odd pixel values */
if (x < bmpwidth) {
if (merge == 0) {
blue = (uint16_t)b;
green = (uint16_t)g;
red = (uint16_t)r;
i+=3;
}
else {
blue = (uint16_t)bmpscanline[i]; i++;
green = (uint16_t)bmpscanline[i]; i++;
red = (uint16_t)bmpscanline[i]; i++;
}
}
else {
/* if no odd pixel double-plot the last pixel */
if (merge == 0) {
blue = (uint16_t)b;
green = (uint16_t)g;
red = (uint16_t)r;
}
else {
/* merge with background color
on some fragments the background color might already be padded-out
*/
blue = (uint16_t)rgbArray[backgroundcolor][2];
green = (uint16_t)rgbArray[backgroundcolor][1];
red = (uint16_t)rgbArray[backgroundcolor][0];
}
}
blue += b;
green += g;
red += r;
b = (uint8_t) (blue/2);
g = (uint8_t) (green/2);
r = (uint8_t) (red/2);
if (dither == 0) {
maskpixel = 0;
if (overlay == 1) {
overcolor = maskline[x/2];
/* clearcolor is the transparent color for the mask */
/* if the overlay color is some other color then the pixel is overlaid
with the mask color */
if (overcolor != clearcolor) maskpixel = 1;
}
if (maskpixel == 1) {
drawcolor = (uint8_t)overcolor;
}
else {
/* get nearest color index from currently selected conversion palette */
drawcolor = GetDrawColor(r,g,b,x/2,y);
}
/* plot to DHGR buffer */
dhrplot(x/2,y,drawcolor);
if (preview == 1) {
/* plot preview using currently selected preview palette */
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][BLUE]; x1++;
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][GREEN]; x1++;
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][RED]; x1+=4;
}
}
else {
/* Floyd-Steinberg Etc. dithering */
/* values are already seeded from previous line(s) */
x2 = x/2;
AdjustShortPixel(1,(int16_t *)&redDither[x2],(int16_t)r);
AdjustShortPixel(1,(int16_t *)&greenDither[x2],(int16_t)g);
AdjustShortPixel(1,(int16_t *)&blueDither[x2],(int16_t)b);
}
}
}
else {
/* merge has no meaning unless we are scaling */
for (x = 0,i = 0,x1=0; x < bmpwidth; x++) {
b = bmpscanline[i]; i++;
g = bmpscanline[i]; i++;
r = bmpscanline[i]; i++;
if (dither != 0) {
/* Floyd-Steinberg Etc. dithering */
/* values are already seeded from previous line(s) */
AdjustShortPixel(1,(int16_t *)&redDither[x],(int16_t)r);
AdjustShortPixel(1,(int16_t *)&greenDither[x],(int16_t)g);
AdjustShortPixel(1,(int16_t *)&blueDither[x],(int16_t)b);
}
else {
maskpixel = 0;
if (overlay == 1) {
overcolor = maskline[x];
/* clearcolor is the transparent color for the mask */
/* if the overlay color is some other color then the pixel is overlaid
with the mask color */
if (overcolor != clearcolor) maskpixel = 1;
}
if (maskpixel == 1) {
drawcolor = (uint8_t)overcolor;
}
else {
/* get nearest color index from currently selected conversion palette */
drawcolor = GetDrawColor(r,g,b,x,y);
}
/* plot to DHGR buffer */
dhrplot(x,y,drawcolor);
if (preview == 1) {
/* plot preview using currently selected preview palette */
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][BLUE]; x1++;
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][GREEN]; x1++;
previewline[x1] = previewline[x1+3] = rgbPreview[drawcolor][RED];
if (loresoutput == 1 && lores == 0) x1++;
else x1+=4;
}
}
}
}
if (dither != 0) {
/* Floyd-Steinberg dithering */
FloydSteinberg(y,dwidth);
/* seed next line - promote nearest forward array to
current line */
memcpy(&redDither[0],&redSeed[0],640);
memcpy(&greenDither[0],&greenSeed[0],640);
memcpy(&blueDither[0],&blueSeed[0],640);
/* seed first seed - promote furthest forward array
to nearest forward array */
memcpy(&redSeed[0],&redSeed2[0],640);
memcpy(&greenSeed[0],&greenSeed2[0],640);
memcpy(&blueSeed[0],&blueSeed2[0],640);
/* clear last seed - furthest forward array */
/* this is not used in all the error diffusion dithers */
/* - but dithers like atkinson use 2 foward arrays */
/* - in dithers that use only one forward array this does no harm */
/* somewhat brute force but simple code */
memset(&redSeed2[0],0,640);
memset(&greenSeed2[0],0,640);
memset(&blueSeed2[0],0,640);
}
if (preview != 0) {
/* write the preview line to the preview file */
fseek(fpreview,prepos,SEEK_SET);
fwrite((char *)&previewline[0],1,outpacket,fpreview);
prepos -= outpacket;
}
}
fclose(fp);
if (preview != 0) {
fclose(fpreview);
if (quietmode != 0) printf("Preview file %s created!\n",previewfile);
}
if (debug == 0) {
if (diffuse != 0) remove(dibfile);
if (resize != 0) remove(scaledfile);
if (reformat != 0) remove(reformatfile);
}
if (savedhr() != SUCCESS) return INVALID;
if (savesprite() != SUCCESS) return INVALID;
return SUCCESS;
}
int16_t ConvertMono()
{
FILE *fp, *fpreview;
int16_t status = INVALID;
uint16_t x,y,i,packet, outpacket, red, green, blue, verbatim;
uint32_t pos, prepos;
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("Error Opening %s for reading!\n",bmpfile);
return status;
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType[0],
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
bmpwidth = (uint16_t) bmi.biWidth;
bmpheight = (uint16_t) bmi.biHeight;
/* monochrome verbatim DHGR conversion */
if (bmpwidth == 560 && bmpheight == 192 && bmi.biBitCount == 1) {
verbatim = 1;
hgroutput = 0;
}
/* color to dithered DHGR monochrome conversion */
else if (bmpwidth == 560 && bmpheight == 384 && bmi.biBitCount != 1) {
verbatim = 2;
hgroutput = 0;
}
/* monochrome verbatim HGR conversion */
else if (bmpwidth == 280 && bmpheight == 192 && bmi.biBitCount == 1) {
verbatim = hgroutput = 1;
}
/* color to dithered HGR monochrome conversion */
else if (bmpwidth == 280 && bmpheight == 192 && bmi.biBitCount != 1) {
verbatim = hgroutput = 1;
}
else {
fclose(fp);
puts("Invalid size for Monochrome conversion!");
return status;
}
/* reformat to 24 bit */
if (bmi.biCompression==BI_RGB &&
bfi.bfType[0] == 'B' && bfi.bfType[1] == 'M' && bmi.biPlanes==1 &&
((bmi.biBitCount == 8) || (bmi.biBitCount == 4) || (bmi.biBitCount == 1))) {
fp = ReformatBMP(fp);
if (fp == NULL) return INVALID;
}
if (bmi.biCompression==BI_RGB &&
bfi.bfType[0] == 'B' && bfi.bfType[1] == 'M' &&
bmi.biPlanes==1 && bmi.biBitCount == 24) {
bmpwidth = (uint16_t) bmi.biWidth;
bmpheight = (uint16_t) bmi.biHeight;
status = SUCCESS;
}
if (status == INVALID) {
fclose(fp);
printf("%s is in the wrong format!\n",bmpfile);
return status;
}
/* if using a mask file, open it now */
/* leave it open throughout the conversion session */
/* it will be closed in main before exiting */
if (overlay == 1)OpenMaskFile();
packet = bmpwidth * 3;
/* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */
while ((packet % 4) != 0) packet++;
if (preview!=0) {
fpreview = fopen(previewfile,"wb+");
if (fpreview != NULL) {
outpacket = WriteDIBHeader(fpreview,bmpwidth,bmpheight);
if (outpacket == 0) {
fclose(fpreview);
remove(previewfile);
printf("Error writing header to %s!\n",previewfile);
preview = 0;
}
else {
/* pad the preview file */
memset(&dibscanline1[0],0,960);
for (y=0;y<bmpheight;y++) fwrite((char *)&dibscanline1[0],1,outpacket,fpreview);
/* set the seek distance to scanline 0 in the preview file */
prepos = (uint32_t) (bmpheight - 1);
prepos *= outpacket;
prepos += mybmp.bfi.bfOffBits;
}
}
else {
printf("Error opening %s for writing!\n",previewfile);
preview = 0;
}
}
/* read BMP from top scanline to bottom scanline */
pos = (uint32_t) (bmpheight - 1);
pos *= packet;
pos += bfi.bfOffBits;
/* clear buffers */
memset(dhrbuf,0,16384);
memset(&bmpscanline[0],0,1920);
memset(&previewline[0],0,1920);
/* sizeof(int16_t) * 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);
for (y=0;y<192;y++,pos-=packet) {
fseek(fp,pos,SEEK_SET);
fread((char *)&bmpscanline[0],1,packet,fp);
if (hgroutput != 1) {
pos-=packet;
fread((char *)&bmpscanline2[0],1,packet,fp);
}
for (x = 0,i = 0; x < bmpwidth; x++, i+=3) {
blue = (uint16_t)bmpscanline[i];
green = (uint16_t)bmpscanline[i+1];
red = (uint16_t)bmpscanline[i+2];
if (verbatim == 2) {
blue += bmpscanline2[i];
green += bmpscanline2[i+1];
red += bmpscanline2[i+2];
}
/* Floyd-Steinberg Etc. dithering */
/* values are already seeded from previous line(s) */
AdjustShortPixel(1,(int16_t *)&redDither[x],(int16_t)red/verbatim);
AdjustShortPixel(1,(int16_t *)&greenDither[x],(int16_t)green/verbatim);
AdjustShortPixel(1,(int16_t *)&blueDither[x],(int16_t)blue/verbatim);
}
/* Floyd-Steinberg dithering */
FloydSteinberg(y,bmpwidth);
/* 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 */
/* this is not used in all the error diffusion dithers */
/* - but dithers like atkinson use 2 forward arrays */
/* - in dithers that use only one forward array this does no harm */
/* somewhat brute force but simple code */
memset(&redSeed2[0],0,1280);
memset(&greenSeed2[0],0,1280);
memset(&blueSeed2[0],0,1280);
if (preview != 0) {
/* write the preview line to the preview file */
fseek(fpreview,prepos,SEEK_SET);
fwrite((char *)&previewline[0],1,outpacket,fpreview);
prepos -= outpacket;
if (hgroutput != 1) {
fseek(fpreview,prepos,SEEK_SET);
fwrite((char *)&previewline[0],1,outpacket,fpreview);
prepos -= outpacket;
}
}
}
fclose(fp);
if (preview != 0) {
fclose(fpreview);
if (quietmode != 0) printf("Preview file %s created!\n",previewfile);
}
if (debug == 0) {
if (reformat != 0) remove(reformatfile);
}
if (savedhr() != SUCCESS) return INVALID;
return SUCCESS;
}
void pusage(void)
{
int16_t i;
puts(title);
for (i=0;usage[i] != NULL;i++) puts(usage[i]);
}
/* ------------------------------------------------------------------------ */
/* palette reader and helper functions */
/* adapted from Clipshop */
/* ------------------------------------------------------------------------ */
/* 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;
}
/*
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] = (uint8_t)red << 2;
palptr[1] = (uint8_t)green << 2;
palptr[2] = (uint8_t)blue << 2;
}
else {
palptr[0] = (uint8_t)red;
palptr[1] = (uint8_t)green;
palptr[2] = (uint8_t)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
int16_t GetUserPalette(char *name)
{
FILE *fp;
char buf[128];
int cnt=16;
int16_t status = INVALID;
unsigned colordepth=8,userpaltype=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) userpaltype = GIMP;
else if (strcmp(PaintShop, buf)==0) userpaltype = JASC;
else if (strcmp(NeoPaint, buf)==0) {
colordepth = 6;
userpaltype = NEO;
}
else if (strcmp(AldusPal, buf) == 0 || strcmp(AldusClr, buf) == 0) {
userpaltype = ALDUS;
}
/* if not a known type then assume it's just a simple csv */
status = SUCCESS;
switch(userpaltype)
{
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 (userpaltype == JASC && strcmp(PaintShopVersion, buf)!=0)break;
if (userpaltype == 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,(uint8_t *)&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;
}
/* returns the Apple II Hires drawcolor 0-15 */
/* 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 */
int dhrgetpixel(int x,int y)
{
int xoff, pattern, idx;
unsigned char *ptraux, *ptrmain,c1, c2, d1, d2;
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;
}
int save_to_bmp24(void)
{
FILE *fp;
uint8_t tempr, tempg, tempb;
int i,x,y, y2 = 191,idx = 1;
uint16_t width = 280, height = 192, outpacket;
fp = fopen(previewfile,"wb");
if (NULL == fp) {
printf("Error opening %s for writing!\n",previewfile);
preview = 0;
return INVALID;
}
if (mono == 1 && hgroutput == 0) {
width = 560;
height = 384;
idx = 2;
}
/* write header for 24 bit bmp */
outpacket = WriteDIBHeader(fp,width,height);
if (outpacket == 0) {
fclose(fp);
remove(previewfile);
printf("Error writing header to %s!\n",previewfile);
preview = 0;
return INVALID;
}
if (mono == 0) {
/* write rgb triples and double each pixel to preserve the aspect ratio */
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 black */
tempr = rgbPreview[idx][0];
tempg = rgbPreview[idx][1];
tempb = rgbPreview[idx][2];
/* reverse order */
fputc(tempb, fp);
fputc(tempg, fp);
fputc(tempr, fp);
/* double-up */
fputc(tempb, fp);
fputc(tempg, fp);
fputc(tempr, fp);
}
y2 -= 1;
}
}
else {
for (y = 0;y< 192;y++,y2--) {
if (width == 560) applemonobites(y2,1);
else applemonobites(y2,0);
for (i=0;i < idx;i++) {
for (x = 0; x < width; x++) {
if (buf280[x] == 0) tempb = 0;
else tempb = 255;
/* any order - black and white */
fputc(tempb, fp);
fputc(tempb, fp);
fputc(tempb, fp);
}
}
}
}
fclose(fp);
return SUCCESS;
}
/* titling from a textfile */
/* only available for full-screen raw output */
/* no attempt at proportional spacing */
/* 4 x 6 font titling */
/* based on a 140 x 192 matrix */
/* 140 / 4 = 35 characters across */
/* 192 / 6 = 32 lines down */
int16_t GetUserTextFile()
{
FILE *fp;
char buf[128];
int x,y,i,cnt=0;
fp = fopen(usertextfile,"r");
if (NULL == fp) {
/* for batch operations */
fp = fopen("b2d.txt","r");
if (NULL == fp) return INVALID;
}
/* read up to 32 lines of text */
for (i=0,y=0;i<32;i++,y+=6) {
if (NULL == fgets(buf, 128, fp)) {
break;
}
nocr(buf);
if (buf[0] == 0) continue;
buf[35] = 0;
for (x=0;x<3;x++) {
/* creating a black outline */
thumbDHGR(buf,x,y,0,255,'L');
/* skip the middle pixels, and fill-in color */
if (x!=1)thumbDHGR(buf,x,y+1,0,255,'L');
thumbDHGR(buf,x,y+2,0,255,'L');
}
/* now do the middle pixels */
if (mono == 1) {
/* for monochrome the letters are always white */
thumbDHGR(buf,1,y+1,15,255,'L');
}
else {
/* for color the letters can be any color at all except for black */
if (backgroundcolor == 0)thumbDHGR(buf,1,y+1,15,255,'L');
else thumbDHGR(buf,1,y+1,(unsigned char)backgroundcolor,255,'L');
}
cnt++;
}
fclose(fp);
/* if we have created titled output, and we have already written a preview image
we need to over-write it with a titled version */
if (cnt!= 0 && preview != 0) save_to_bmp24();
/* at this point we just return */
return SUCCESS;
}
int main(int argc, char **argv)
{
int16_t idx,jdx,kdx,palidx=5,previewidx=5,hgrpalidx=5,pseudopal=0,
status,basename=0,plainname=0;
uint8_t c, ch, *wordptr, *ptr;
char hgroptions[20];
if (argc < 2) {
pusage();
return (1);
}
/* allocate our output buffers to support MS-DOS compilers
but does no harm for 32-bit compilers
*/
dhrbuf = hgrbuf = (uint8_t *)malloc(8192);
if (NULL != hgrbuf) {
dhrbuf = (uint8_t *)malloc(16384);
}
if (dhrbuf == NULL) {
puts("No memory...");
return (1);
}
/* initialize color space for color distance */
setluma();
/* automatic naming is used for a number of reasons */
/* I make no attempt to test for a legal ProDOS file name length - that's up to the user */
/* but short names should be used when possible for a number of reasons */
/* HGR output - options accumulator for HGR long filename - not available in MS-DOS */
/* this differentiates BIN files created for HGR from AUX,BIN file pairs used for alternate output of DHGR */
/* and from each other so they can be compared */
/* for HGR output the preview file is an approximation so far */
hgroptions[0] = 0;
usertextfile[0] = 0;
/* getopts */
if (argc > 2) {
for (idx = 2; idx < argc; idx++) {
/* switch character is optional */
wordptr = (uint8_t *)&argv[idx][0];
ch = toupper(wordptr[0]);
if (ch == '-') {
wordptr = (uint8_t *)&argv[idx][1];
ch = toupper(wordptr[0]);
}
if (cmpstr(wordptr,"debug") == SUCCESS) {
debug = 1;
continue;
}
/* set different Luma for color distance */
jdx = 0;
if (cmpstr(wordptr,"GIMP") == SUCCESS) jdx = 411;
else if (cmpstr(wordptr,"MAGICK") == SUCCESS) jdx = 709;
else if (cmpstr(wordptr,"HDMI") == SUCCESS) jdx = 240;
if (jdx != 0) {
lumaREQ = jdx;
printf("Using LumaREQ %d\n", lumaREQ);
setluma();
continue;
}
/* so-called "quick" commands */
if (cmpstr(wordptr,"photo") == SUCCESS) {
dither = FLOYDSTEINBERG;
continue;
}
if (cmpstr(wordptr,"art") == SUCCESS) {
threshold = 25;
xmatrix = 2;
continue;
}
if (cmpstr(wordptr,"both") == SUCCESS) {
dither = FLOYDSTEINBERG;
threshold = 15;
xmatrix = 2;
continue;
}
if (cmpstr(wordptr,"sprite") == SUCCESS) {
outputtype = SPRITE_OUTPUT;
continue;
}
if (cmpstr(wordptr,"BIN") == SUCCESS) {
applesoft = 1;
continue;
}
if (cmpstr(wordptr,"sum") == SUCCESS) {
errorsum = 1;
continue;
}
if (cmpstr(wordptr,"mono") == SUCCESS || cmpstr(wordptr,"reverse") == SUCCESS) {
mono = 1;
if (dither == 0) dither = FLOYDSTEINBERG;
if (cmpstr(wordptr,"reverse") == SUCCESS) reverse = 1;
continue;
}
/* DOS 3.3 header will be appended to Apple II Output */
if (cmpstr(wordptr,"dos") == SUCCESS) {
dosheader = 1;
continue;
}
/* TGR long commands */
/* to select alternate conversion palette 15 from tohgr, instead of HGR use TGR for HGR long commands */
if (ch == 'T') {
c = toupper(wordptr[1]);
if (c == 'G') {
c = toupper(wordptr[2]);
if (c == 'R') {
wordptr[0] = ch = 'H';
palidx = hgrpalidx = 16;
puts("HGR Option TGR: tohgr HGR color conversion palette");
}
}
}
switch(ch) {
case 'A': /* output AUX,BIN - default is A2FC */
applesoft = 1;
break;
case 'B':
c = wordptr[1];
if (c == (uint8_t)0) break;
if (cmpstr("basename", (char *)&wordptr[0]) == SUCCESS ||
cmpstr("base", (char *)&wordptr[0]) == SUCCESS) {
basename = 1;
break;
}
/* background color 1-15 (0 by default) */
c = PaintByNumbers((char *)&wordptr[1]);
if (c!= (uint8_t)255) backgroundcolor = c;
break;
case 'C': ch = toupper(wordptr[1]);
if (ch == 'P' || ch == 'V') {
paletteclip = 1;
break;
}
globalclip = 1;
break;
case 'D': if (cmpstr("DL", (char *)&wordptr[0]) == SUCCESS) {
/* DLGR output */
loresoutput = 1;
break;
}
dither = FLOYDSTEINBERG;
if (ReadCustomDither((char *)&wordptr[1]) == SUCCESS) {
break;
}
ch = toupper(wordptr[1]);
if (ch == 'X') {
wordptr++;
serpentine = 1;
}
jdx = atoi((char *)&wordptr[1]);
if (jdx > 0 && jdx < 10) dither = jdx;
else {
ch = toupper(wordptr[1]);
switch(ch) {
case 'F': dither = FLOYDSTEINBERG;break;
case 'J': dither = JARVIS;break;
case 'S': dither = STUCKI;
ch = toupper(wordptr[2]);
if (ch == 'I') dither = SIERRA;
else if (ch == '2') dither = SIERRATWO;
else if (ch == 'L') dither = SIERRALITE;
break;
case 'A': dither = ATKINSON;break;
case 'B': dither = BURKES;
ch = toupper(wordptr[2]);
if (ch == 'B') dither = 9;
break;
}
}
break;
case 'E':
/* error diffusion default = E2 */
diffuse = 2;
jdx = atoi((char *)&wordptr[1]);
/* E4 */
if (jdx == 4) diffuse = 4;
break;
case 'F': /* image fragment - off by default */
outputtype = SPRITE_OUTPUT;
ch = toupper(wordptr[1]);
if (ch == 'M') spritemask = 1;
break;
case 'J':
/* scaling of larger sizes is pixel by pixel
when justification is selected */
justify = 1;
ch = toupper(wordptr[1]); /* justify */
switch(ch) {
case 'L': jxoffset = atoi((char *)&wordptr[2]);
break;
case 'T': jyoffset = atoi((char *)&wordptr[2]);
break;
}
break;
case 'H': /* HGR output - command option 'H' */
/* some options follow */
hgroutput = 1;
if (hgroptions[0] == (char)0){
/* if we are using Sheldon's HGR palette we use the TC suffix */
/* if we are using Sheldon's DHGR palette modified for HGR we use the C suffix */
if (hgrpalidx == 16)strcat(hgroptions,"TC");
else strcat(hgroptions,"C");
clearcolor = 3; /* set overlay color to violet */
}
/* default long file name for HGR color */
/* HGR long commands - can be followed by separate HGR short commands to over-ride fixed settings */
/* by default individual pixels are set */
/* hgrclean = X, hgrclip = Y, hgrsum = Z */
for (;;) {
jdx = strlen((char *)&wordptr[0]);
if (jdx < 6 || jdx > 9) break;
if (jdx == 8 || jdx == 9) {
if (jdx == 8) ptr = (char *)&wordptr[3];
else ptr = (char *)&wordptr[4];
if (cmpstr("clean", (char *)&ptr[0]) == SUCCESS) {
printf("HGR Option X: %s\n",(char *)&ptr[0]);
globalclip = errorsum = 1;
ptr[0] = 0;
strcat(hgroptions,"X");
jdx = 0;
}
}
if (jdx < 6 || jdx > 8) break;
jdx = strlen((char *)&wordptr[0]);
if (jdx == 7 || jdx == 8) {
if (jdx == 7) ptr = (char *)&wordptr[3];
else ptr = (char *)&wordptr[4];
if (cmpstr("clip", (char *)&ptr[0]) == SUCCESS) {
printf("HGR Option Y: %s\n",(char *)&ptr[0]);
globalclip = 1;
ptr[0] = 0;
strcat(hgroptions,"Y");
jdx = 0;
}
}
if (jdx < 6 || jdx > 8) break;
jdx = strlen((char *)&wordptr[0]);
if (jdx == 6 || jdx == 7) {
if (jdx == 6) ptr = (char *)&wordptr[3];
else ptr = (char *)&wordptr[4];
if (cmpstr("sum", (char *)&ptr[0]) == SUCCESS) {
printf("HGR Option Z: %s\n",(char *)&ptr[0]);
errorsum = 1;
ptr[0] = 0;
strcat(hgroptions,"Z");
}
}
break;
}
jdx = strlen((char *)&wordptr[0]);
switch(jdx) {
/* long commands */
case 3:
if (cmpstr("hgr", (char *)&wordptr[0]) == SUCCESS) {
if (hgrcolortype == (char)0) hgrcolortype = 'B';
}
break;
case 4:
if (cmpstr("hgrs", (char *)&wordptr[0]) == SUCCESS) {
/* optionally single colored pixels are set */
if (hgrcolortype == (char)0) hgrcolortype = 'B';
doublecolors = 0;
puts("HGR Option S: single color pixels");
strcat(hgroptions,"S");
}
else if (cmpstr("hgrw", (char *)&wordptr[0]) == SUCCESS) {
/* optionally double colors are set with a double white overlay */
if (hgrcolortype == (char)0) hgrcolortype = 'B';
puts("HGR Option W: double color and white pixels");
doublecolors = 1;
doublewhite = 1;
strcat(hgroptions,"W");
}
else if (cmpstr("hgrb", (char *)&wordptr[0]) == SUCCESS) {
/* optionally double colors are set with a double black overlay */
if (hgrcolortype == (char)0) hgrcolortype = 'B';
puts("HGR Option B: double color and black pixels");
doublecolors = 1;
doubleblack = 1;
strcat(hgroptions,"B");
}
else if (cmpstr("hgro", (char *)&wordptr[0]) == SUCCESS) {
/* set HGR output for orange and blue only */
/* color type is not needed */
/* no pixel options - individual pixels only */
puts("HGR Option O: Orange and Blue Palette Only");
grpal[3][0] = grpal[3][1] = grpal[3][2] = 0;
grpal[12][0] = grpal[12][1] = grpal[12][2] = 0;
hgrpaltype = 0x80;
strcat(hgroptions,"O");
hgrdither = 0;
}
else if (cmpstr("hgrg", (char *)&wordptr[0]) == SUCCESS) {
/* set HGR output for green and violet only */
/* color type is not needed */
/* no pixel options - individual pixels only */
clearcolor = 6; /* set overlay color to blue */
puts("HGR Option G: Green and Violet Palette Only");
grpal[6][0] = grpal[6][1] = grpal[6][2] = 0;
grpal[9][0] = grpal[9][1] = grpal[9][2] = 0;
hgrpaltype = 0;
strcat(hgroptions,"G");
hgrdither = 0;
}
else if (cmpstr("hgr2",(char *)&wordptr[0]) == SUCCESS) {
puts("HGR alternate nearest color option");
strcat(hgroptions,"A");
hgrdither = 1;
}
break;
case 1:
case 2:
/* short commands */
ch = toupper(wordptr[1]);
if (ch == 'O' || ch == 'G' || ch == 'B' || ch == 'V' || ch == (char)0) {
/* this command sets color precedence for the palette bit
the default is 'O' orange
'HO' and 'HG' set strong precedence
'HB' and 'HV' set weak precedence
'H' by itself sets equal precedence
regardless of precedence both palettes will be used unless specifically disabled
by 'HGRO' or 'HGRG' which sets 4 color HGR output.
*/
if (ch == (char)0) {
puts("HGR Precedence Over-ride: Equal");
}
else if (ch == 'B' || ch == 'V') {
printf("HGR Precedence Over-ride: Weak %c\n",ch);
}
else {
printf("HGR Precedence Over-ride: Strong %c\n",ch);
}
wordptr[1] = hgrcolortype = ch;
wordptr[0] = toupper(wordptr[0]);
strcat(hgroptions,(char *)&wordptr[0]);
}
}
/* Low-resolution colors
0 (black),
3 (purple),
6 (medium blue),
9 (orange),
12 (light green) and
15 (white) are also available in high-resolution mode */
/*
/* disable the unused colors in the default palette */
/* these will be propagated to any alternate palettes that are selected */
grpal[1][0] = grpal[1][1] = grpal[1][2] = 0;
grpal[2][0] = grpal[2][1] = grpal[2][2] = 0;
grpal[4][0] = grpal[4][1] = grpal[4][2] = 0;
grpal[5][0] = grpal[5][1] = grpal[5][2] = 0;
grpal[7][0] = grpal[7][1] = grpal[7][2] = 0;
grpal[8][0] = grpal[8][1] = grpal[8][2] = 0;
grpal[10][0] = grpal[10][1] = grpal[10][2] = 0;
grpal[11][0] = grpal[11][1] = grpal[11][2] = 0;
grpal[13][0] = grpal[13][1] = grpal[13][2] = 0;
grpal[14][0] = grpal[14][1] = grpal[14][2] = 0;
break;
case 'L':
if (wordptr[1] == (char) 0 || cmpstr(wordptr,"lgr") == SUCCESS) {
/* LGR output */
lores = loresoutput = 1;
break;
}
/* Luma */
jdx = atoi((char *)&wordptr[1]);
if (jdx == 601 || jdx == 709 || jdx == 240 || jdx == 911 || jdx == 411) {
lumaREQ = jdx;
printf("Using LumaREQ %d\n", lumaREQ);
setluma();
break;
}
break;
case 'M': /* M2 - horizontal merge - S2 mode only */
/* by default every second pixel is skipped */
merge = 1;
break;
case 'O': /* use an 8 bit overlay file - must be 140 x 192 */
/* transparent color must be set or default is 128,128,128 - color 5 */
c = wordptr[1];
if (c == (uint8_t)0) break;
c = PaintByNumbers((char *)&wordptr[1]);
if (c!= (uint8_t)255) {
clearcolor = c; break;
}
overlay = 1;
strcpy(maskfile,(char *)&wordptr[1]);
/* maskfile must have an extension or .bmp is assumed */
/* this avoids typing extensions which I dislike doing */
/* so I provide expected extensions. any questions? */
kdx = 999;
for (jdx=0;maskfile[jdx]!=0;jdx++) {
if (maskfile[jdx] == '.')kdx = jdx;
}
if (kdx == 999)strcat(maskfile,".bmp");
break;
case 'V': /* create preview file */
preview = 1;
if (wordptr[1] == 0) break;
if (cmpstr(wordptr,"vbmp") == SUCCESS) {
vbmp = 1;
break;
}
case 'P':
if (cmpstr("plainname", (char *)&wordptr[0]) == SUCCESS ||
cmpstr("plain", (char *)&wordptr[0]) == SUCCESS) {
plainname = 1;
break;
}
/* palette settings */
c = toupper(wordptr[1]);
/* check for palette names */
if (c > 57) {
c = 255;
if (cmpstr("kegs32", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("kegs", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 0;
else if (cmpstr("cider", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("ciderpress", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 1;
else if (cmpstr("old", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 2;
else if (cmpstr("new", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("applewin", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 3;
else if (cmpstr("wikipedia", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("wiki", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 4;
/* Sheldon Simms tohgr and AppleWin NTSC */
else if (cmpstr("sheldon", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("todhr", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("ntsc", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 5;
/* Jason Harper's Super Convert DHGR Palette */
else if (cmpstr("rgb", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("super", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("gs", (char *)&wordptr[1]) == SUCCESS) c = previewidx = palidx = 12;
/* Jace DHGR Palette */
else if (cmpstr("jace", (char *)&wordptr[1]) == SUCCESS ||
cmpstr("blurry", (char *)&wordptr[1]) == SUCCESS) c = previewidx = palidx = 13;
/* Cybnernesto */
else if (cmpstr("cybernesto", (char *)&wordptr[1]) == SUCCESS)c = previewidx = palidx = 14;
else if (cmpstr("canvas", (char *)&wordptr[1]) == SUCCESS)c = 7;
else if (cmpstr("bmp", (char *)&wordptr[1]) == SUCCESS)c = 8;
else if (cmpstr("win16", (char *)&wordptr[1]) == SUCCESS)c = 8;
else if (cmpstr("xmp", (char *)&wordptr[1]) == SUCCESS)c = 9;
else if (cmpstr("win32", (char *)&wordptr[1]) == SUCCESS)c = 9;
else if (cmpstr("vga", (char *)&wordptr[1]) == SUCCESS)c = 10;
else if (cmpstr("pcx", (char *)&wordptr[1]) == SUCCESS)c = 11;
if (c!= 255) {
if (ch == 'P') palidx = c;
else previewidx = c;
break;
}
}
jdx = GetUserPalette((char *)&wordptr[1]);
if (jdx == SUCCESS) {
if (ch == 'P') palidx = 6;
else previewidx = 6;
}
else {
c = toupper(wordptr[1]);
if (c == 'P') {
/* pseudo palette */
if (wordptr[2] > (char)47 && wordptr[2] < (char)58) {
jdx = atoi((char *)&wordptr[2]);
if ((jdx < 0 || jdx > 16) || jdx == 15) break;
if (pseudocount < PSEUDOMAX) {
pseudolist[pseudocount] = jdx;
pseudocount++;
pseudopal = 1;
break;
}
}
}
if (c == 'K' || c == 'C' || c == 'O' || c == 'N' || c == 'W' || c == 'S'||
c == 'R' || c == 'G' || c == 'E' || c == 'J' || c == 'V') {
jdx=0; /* Kegs */
switch(c) {
case 'R': /* RGB */
case 'G': jdx = 12; break; /* Apple II "G" (IIgs) - RGB display */
case 'J': jdx = 13; break; /* Jace NTSC Palette */
case 'V': jdx = 14; break; /* VBMP NTSC Palette */
case 'E': /* Apple II "E" (IIe) - composite display */
case 'S': jdx++;/* Sheldon Simms NTSC Palette */
case 'W': jdx++;/* Wikipedia NTSC */
case 'N': jdx++;/* New AppleWin */
case 'O': jdx++;/* Old AppleWin */
case 'C': jdx++;/* CiderPress */
}
}
else {
if (c < 48 || c > 59) break;
jdx = atoi((char *)&wordptr[1]);
}
/* palettes 0-5 are the original palettes */
/* palette 6 is a user palette file */
/* palettes 7-11 are 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 defaults to a Pseudo-Palette of the average RGB values
of Palette 5 (tohgr NTSC) and Palette 12 (Super Convert RGB) */
/* palette 16 is tohgr's old NTSC colors which as of June 2014 are still used for HGR conversion */
if (jdx > -1 && jdx < 17) {
if (ch == 'P') palidx = jdx;
else previewidx = jdx;
}
}
break;
case 'Q': quietmode = 0;
break;
case 'R': /* reduced or increased color bleed
by percentage (for dithering only) */
jdx = atoi((char *)&wordptr[1]);
if ((jdx > 0 && jdx < 101) || (jdx < 0 && jdx > -101)) colorbleed = 100 + jdx;
break;
case 'S': /* by default scaling is set to S1 - full scale (verbatim) */
/* so choosing option S without the S1 numeric modifier sets scaling to half-scale */
jdx = atoi((char *)&wordptr[1]);
if (jdx == 1) scale = 0; /* S1 full scale (verbatim) */
else scale = 1; /* S2 - double scaled */
break;
case 'T': if (cmpstr("op", (char *)&wordptr[1]) == SUCCESS) {
/* LGR and DLGR mixed text and graphics */
loresoutput = appletop = 1;
break;
}
/* use ciderpress tags - off by default */
tags = 1;
break;
case 'X': /* pattern setting for general purpose 2 x 2 cross-hatching */
xmatrix = 2;
if (threshold == 0) threshold = 25;
/* optional pattern setting for general purpose 2 x 2 cross-hatching */
jdx = atoi((char *)&wordptr[1]);
if (jdx == 1 || jdx == 3) xmatrix = jdx;
break;
case 'Y': /* increase or decrease color - non-cross-hatched ouput */
/* this can eventually be replaced by a saturation adjustment or
a hue correction or something else */
ymatrix = 1;
jdx = atoi((char *)&wordptr[1]);
if (jdx == 2 || jdx == 3) ymatrix = jdx;
break;
case 'Z': /* threshold setting for general purpose 2 x 2 cross-hatching */
/* and for brightening and darkening of colors */
threshold = 25;
if (xmatrix == 0)xmatrix = 2;
jdx = atoi((char *)&wordptr[1]);
/* allow up to 50% adjustment on RGB values */
/* surely that's enough */
if (jdx > 0 && jdx < 51) threshold = jdx;
break;
}
}
}
/* mutually exclusive commands are handled here */
if (hgroutput == 1) {
if (loresoutput == 1) {
loresoutput = 0;
puts("HGR output and Lo-Res output are mutually exclusive.\nLo-Res output cancelled!");
}
/*
if (outputtype == SPRITE_OUTPUT) {
outputtype = BIN_OUTPUT;
puts("HGR output and Image Fragment output are mutually exclusive.\nImage Fragment output cancelled!");
}
*/
if (mono == 1) {
mono = 0;
puts("HGR output and Monochrome output are mutually exclusive.\nMonochrome output cancelled!");
}
}
else {
hgrdither = 0;
}
if (mono == 1) {
if (loresoutput == 1) {
loresoutput = 0;
puts("Monochrome output and Lo-Res output are mutually exclusive.\nLo-Res output cancelled!");
}
if (outputtype == SPRITE_OUTPUT) {
mono = 0;
puts("Image Fragment output and Monochrome output are mutually exclusive.\nMonochrome output cancelled!");
}
}
if (loresoutput == 1) {
overlay = 0;
if (outputtype == SPRITE_OUTPUT) {
outputtype = BIN_OUTPUT;
puts("Lo-Res output and Image Fragment output are mutually exclusive.\nImage Fragment output cancelled!");
}
}
/* embedding of image fragments or palette output only */
if (outputtype != SPRITE_OUTPUT) {
if (pseudopal == 0) quietmode = 1;
}
jdx = 999;
strcpy(fname, argv[1]);
for (idx = 0; fname[idx] != (uint8_t)0; idx++) {
if (fname[idx] == '.') {
jdx = idx;
}
}
if (jdx != 999) fname[jdx] = (uint8_t)0;
sprintf(bmpfile,"%s.bmp",fname);
sprintf(dibfile,"%s.dib",fname);
#ifdef MSDOS
tags = 0;
sprintf(previewfile,"%s.pmp",fname);
sprintf(scaledfile,"%s.smp",fname);
sprintf(reformatfile,"%s.rmp",fname);
sprintf(vbmpfile,"%s.vmp",fname);
#else
sprintf(previewfile,"%s_Preview.bmp",fname);
sprintf(scaledfile,"%s_Scaled.bmp",fname);
sprintf(reformatfile,"%s_Reformat.bmp",fname);
sprintf(vbmpfile,"%s_VBMP.bmp",fname);
#endif
/* user titling file */
sprintf(usertextfile,"%s.txt",fname);
/* upper case basename for Apple II Output */
for (idx = 0; fname[idx] != (uint8_t)0; idx++) {
ch = toupper(fname[idx]);
fname[idx] = ch;
}
strcpy(hgrwork,fname);
if (basename == 1) {
/* if they are using the same naming convention that I am */
/* optionally strip the resolution nomenclature from the input file's base name */
idx = strlen(hgrwork);
if (idx > 3) {
/* in order below: 384 - 560 x 384
280 - 280 x 192
640 - 640 x 480
400 - 640 x 400
320 - 320 x 200
140 - 140 x 192
560 - 560 x 192
LGR and DLGR only
176 - 176 x 104
160 - 160 x 80 and 160 x 96
88 - 88 x 52
80 - 80 x 40 and 80 x 48
48 - 80 x 48 and 40 x 48
40 - 80 x 40 and 40 x 40
*/
if (hgrwork[idx-3] == '3' && hgrwork[idx-2] == '8' && hgrwork[idx-1] == '4') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '2' && hgrwork[idx-2] == '8' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '6' && hgrwork[idx-2] == '4' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '4' && hgrwork[idx-2] == '0' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '3' && hgrwork[idx-2] == '2' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '1' && hgrwork[idx-2] == '4' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '5' && hgrwork[idx-2] == '6' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '1' && hgrwork[idx-2] == '7' && hgrwork[idx-1] == '6') hgrwork[idx - 3] = 0;
else if (hgrwork[idx-3] == '1' && hgrwork[idx-2] == '6' && hgrwork[idx-1] == '0') hgrwork[idx - 3] = 0;
if (hgrwork[idx - 3] != (char)0) {
/* LGR and DLGR only */
if (hgrwork[idx-2] == '8' && hgrwork[idx-1] == '8') hgrwork[idx - 2] = 0;
else if (hgrwork[idx-2] == '8' && hgrwork[idx-1] == '0') hgrwork[idx - 2] = 0;
else if (hgrwork[idx-2] == '4' && hgrwork[idx-1] == '8') hgrwork[idx - 2] = 0;
else if (hgrwork[idx-2] == '4' && hgrwork[idx-1] == '0') hgrwork[idx - 2] = 0;
}
}
}
/* CiderPress File Attribute Preservation Tags */
if (tags == 1) {
if (hgroutput == 0)
sprintf(spritefile,"%s.DHR#062000",hgrwork);
else
sprintf(spritefile,"%s.RAG#062000",hgrwork);
sprintf(fmask,"%s.DHM#062000",hgrwork);
sprintf(mainfile,"%s.BIN#062000",hgrwork);
sprintf(auxfile,"%s.AUX#062000",hgrwork);
sprintf(a2fcfile,"%s.A2FC#062000",hgrwork);
if (plainname == 0) {
sprintf(hgrcolor,"%s%s.BIN#062000",hgrwork,hgroptions);
sprintf(hgrmono,"%sM.BIN#062000",hgrwork);
if (mono == 1) {
sprintf(a2fcfile,"%s.A2FM#062000",hgrwork);
sprintf(mainfile,"%sM.BIN#062000",hgrwork);
sprintf(auxfile,"%sM.AUX#062000",hgrwork);
}
}
else {
sprintf(hgrcolor,"%s.BIN#062000",hgrwork);
sprintf(hgrmono,"%s.BIN#062000",hgrwork);
}
}
else {
/* tags are off by default */
/* unadorned file names */
if (hgroutput == 0)
sprintf(spritefile,"%s.DHR",hgrwork);
else
sprintf(spritefile,"%s.RAG",hgrwork);
sprintf(fmask,"%s.DHM",hgrwork);
sprintf(mainfile,"%s.BIN",hgrwork);
sprintf(auxfile,"%s.AUX",hgrwork);
#ifdef MSDOS
if (plainname == 0 && mono == 1)
sprintf(a2fcfile,"%s.2FM",hgrwork);
else
sprintf(a2fcfile,"%s.2FC",hgrwork);
strcpy(hgrcolor,hgrwork);
strcpy(hgrmono,hgrwork);
#else
sprintf(a2fcfile,"%s.A2FC",hgrwork);
if (plainname == 0) {
sprintf(hgrcolor,"%s%s.BIN",hgrwork,hgroptions);
sprintf(hgrmono,"%sM.BIN",hgrwork);
if (mono == 1) {
sprintf(a2fcfile,"%s.A2FM",hgrwork);
sprintf(mainfile,"%sM.BIN#062000",hgrwork);
sprintf(auxfile,"%sM.AUX#062000",hgrwork);
}
}
else {
sprintf(hgrcolor,"%s.BIN",hgrwork);
sprintf(hgrmono,"%s.BIN",hgrwork);
}
#endif
}
if (mono == 1) {
palidx = previewidx = 4;
/* create a black and white palette */
memset(&wikipedia[0][0],0,45);
}
else {
/* create pseudo-palette for conversion */
/* preview using pseudopalette is optional - v15 */
if (pseudopal != 0) {
BuildPseudoPalette(palidx);
palidx = 15;
}
}
GetBuiltinPalette(palidx,previewidx,0);
InitDoubleArrays();
if (mono == 1) status = ConvertMono();
else status = Convert();
/* close mask file if any before exiting */
if (NULL != fpmask) fclose(fpmask);
free(dhrbuf);
free(hgrbuf);
if (status == INVALID) return (1);
return SUCCESS;
}