/* --------------------------------------------------------------------- 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 #include #include #include #include #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 */ uchar PaintByNumbers(char *str) { int idx; uchar c = toupper(str[0]); if (str[1] == (char) 0) { /* alpha mnemonic */ if (c > 64 && c < 81) return (uchar) (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 = (uchar) idx; } } return c; } ushort Motorola16(ushort val) { uchar buf[2]; ushort *ptr; /* msb in smallest address */ buf[0] = (uchar) (val % 256); val = val/256; buf[1] = (uchar) (val % 256); ptr = (ushort *)&buf[0]; val = ptr[0]; return val; } void WriteDosHeader(FILE *fp, ushort fl, ushort 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(ushort),1,fp); fwrite((char *)&fl,sizeof(ushort),1,fp); } } /* Photoshop Luminosity Average Formula for the Luminosity Average: AvgLuma = 0.299·AvgRed + 0.587·AvgGreen + 0.114·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(sshort palidx, sshort previewidx, sshort pseudo) { sshort i,j; uchar 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(sshort palidx) { sshort i,j,k,idx; ushort 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] = (ushort)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] = (uchar)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 */ uchar GetMedColor(uchar r, uchar g, uchar b, double *paldistance) { uchar 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 != (uchar) 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 = (uchar)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 */ uchar GetHighColor(uchar r, uchar g, uchar b, double *paldistance) { uchar 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 != (uchar) 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 = (uchar)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 */ uchar GetLowColor(uchar r, uchar g, uchar b, double *paldistance) { uchar 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 != (uchar) 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 = (uchar)i; } } return drawcolor; } /* switchboard function to handle cross-hatched and non-cross-hatched output */ /* keeps the conditionals out of the main loop */ uchar GetDrawColor(uchar r, uchar g, uchar b, int x, int y) { /* additional vars for future */ double distance, lowdistance, highdistance; uchar drawcolor, lowcolor, highcolor; uchar red = (uchar)(r >> 4), green = (uchar)(g >> 4), blue = (uchar)(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 != (uchar) 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 (uchar)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,uchar drawcolor) { int xoff, pattern; uchar *ptraux, *ptrmain; pattern = (x%7); xoff = HB[y] + ((x/7) * 2); ptraux = (uchar *) &dhrbuf[xoff-0x2000]; ptrmain = (uchar *) &dhrbuf[xoff]; switch(pattern) { /* left this here for reference uchar dhrpattern[7][4] = { 0,0,0,0, 0,0,0,1, 1,1,1,1, 1,1,2,2, 2,2,2,2, 2,3,3,3, 3,3,3,3}; */ case 0: ptraux[0] &= 0x70; ptraux[0] |= dhrbytes[drawcolor][0] &0x0f; break; case 1: ptraux[0] &= 0x0f; ptraux[0] |= dhrbytes[drawcolor][0] & 0x70; ptrmain[0] &= 0x7e; ptrmain[0] |= dhrbytes[drawcolor][1] & 0x01; break; case 2: ptrmain[0] &= 0x61; ptrmain[0] |= dhrbytes[drawcolor][1] & 0x1e; break; case 3: ptrmain[0] &= 0x1f; ptrmain[0] |= dhrbytes[drawcolor][1] & 0x60; ptraux[1] &= 0x7c; ptraux[1] |= dhrbytes[drawcolor][2] & 0x03; break; case 4: ptraux[1] &= 0x43; ptraux[1] |= dhrbytes[drawcolor][2] & 0x3c; break; case 5: ptraux[1] &= 0x3f; ptraux[1] |= dhrbytes[drawcolor][2] & 0x40; ptrmain[1] &= 0x78; ptrmain[1] |= dhrbytes[drawcolor][3] & 0x07; break; case 6: ptrmain[1] &= 0x07; ptrmain[1] |= dhrbytes[drawcolor][3] & 0x78; break; } } /* monochrome DHGR - 560 x 192 */ unsigned char dhbmono[] = {0x7e,0x7d,0x7b,0x77,0x6f,0x5f,0x3f}; unsigned char dhwmono[] = {0x1,0x2,0x4,0x8,0x10,0x20,0x40}; void dhrmonoplot(int x, int y, uchar drawcolor) { int xoff, pixel; uchar *ptr; if (x > 559) return; xoff = HB[y] + (x/14); pixel = (x%14); if (pixel > 6) { /* main memory */ pixel -= 7; ptr = (uchar *) &dhrbuf[xoff]; } else { /* auxiliary memory */ ptr = (uchar *) &dhrbuf[xoff-0x2000]; } if (drawcolor != 0) { /* white */ ptr[0] |= dhwmono[pixel]; /* inclusive OR */ } else { /* black */ ptr[0] &= dhbmono[pixel]; /* bitwise AND */ } } /* monochrome HGR 280 x 192 */ void hrmonoplot(int x, int y, uchar drawcolor) { int xoff, pixel; uchar *ptr; if (x > 279) return; xoff = HB[y] + (x/7); pixel = (x%7); /* main memory */ ptr = (uchar *) &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,uchar drawcolor) { int xoff, x; uchar *ptraux, *ptrmain; xoff = HB[y]; ptraux = (uchar *) &dhrbuf[xoff-0x2000]; ptrmain = (uchar *) &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; uchar drawcolor; memset(dhrbuf,0,16384); if (backgroundcolor == LOBLACK) return; drawcolor = (uchar)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. */ ushort WriteVbmpHeader(FILE *fp) { ushort outpacket; int c, i, j; /* BMP scanlines are padded to a multiple of 4 bytes (DWORD) */ outpacket = (ushort)72; if (mono != 0 || 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 = (ulong)40; mybmp.bmi.biWidth = (ulong)140; mybmp.bmi.biHeight = (ulong)192; mybmp.bmi.biPlanes = 1; mybmp.bmi.biBitCount = 4; mybmp.bmi.biCompression = (ulong) BI_RGB; mybmp.bmi.biSizeImage = (ulong)outpacket; mybmp.bmi.biSizeImage *= mybmp.bmi.biHeight; /* create the file header */ mybmp.bfi.bfType[0] = 'B'; mybmp.bfi.bfType[1] = 'M'; mybmp.bfi.bfOffBits = (ulong) sizeof(BMPHEADER) + sizeof(RGBQUAD) * 16; mybmp.bfi.bfSize = mybmp.bmi.biSizeImage + mybmp.bfi.bfOffBits; /* write the header for the output BMP */ c = fwrite((char *)&mybmp.bfi.bfType[0],sizeof(BMPHEADER),1,fp); if (c!= 1)return 0; /* 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; uchar 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 = (uchar)j << 4; } else { idx = dhrgetpixel(x,y2); /* range check */ if (idx < 0 || idx > 15)idx = 0; /* default black */ j = RemapLoToHi[idx]; bmpscanline[x1] = ch | (uchar)j; x1++; } } } fwrite((char *)&bmpscanline[0],1,packet,fp); y2 -= 1; } fclose(fp); 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; ushort 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++) { 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((uchar)width,fp); /* width in bytes */ fputc((uchar)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; ushort fl; uchar *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 = (ushort) 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((uchar)width,fp); /* width in bytes */ fputc((uchar)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("uchar %sBackgroundColor = %d;\n\n",fname,backgroundcolor); printf("/* Embedded DHGR Image Fragment created from %s */\n\n",bmpfile); printf("uchar %sPixelData[] = {\n",fname); } for (y = 0, cnt = 0; y < bmpheight; y++) { xoff = HB[y]; ptraux = (uchar *) &dhrbuf[xoff-0x2000]; ptrmain = (uchar *) &dhrbuf[xoff]; /* aux raster */ c = fwrite((char *)&ptraux[0],1,packet,fp); if (c!= packet) break; /* main raster */ c = fwrite((char *)&ptrmain[0],1,packet,fp); if (c!= packet) break; if (quietmode == 0) { for (i=0;i 255) value = 255; } buf[0] = value; if (clip == 0) { if (value < 0) value = 0; else if (value > 255) value = 255; } return (uchar) value; } /* helper function for ReadCustomDither */ int InitCustomLine(char *ptr, int lidx) { int cnt=0, i; customdither[lidx][cnt] = (sshort) atoi(ptr); /* enforce 11 fields */ for (i=0;ptr[i]!=0;i++) { if (ptr[i]== ',') { cnt++; if (cnt < 11) customdither[lidx][cnt] = (sshort) 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(sshort)*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 = (sshort) 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 */ sshort red, green, blue, red_error, green_error, blue_error; sshort pos, mult; int dx,i, x,x1, total_difference, total_error, total_used; int testrun, runs, temperror, z; uchar 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 0) AdjustShortPixel(1,(sshort *)&colorptr[x-1],(sshort)((color_error * 7)/bleed)+total_difference); /* seed next line forward */ /* for serpentine effect line 2 error is reversed */ if (x>0)AdjustShortPixel(threshold,(sshort *)&seedptr[x-1],(sshort)((color_error * 1)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error * 3)/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)((color_error * 3)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error * 1)/bleed)); } AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((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,(sshort *)&colorptr[x+1],(sshort)((color_error * 7)/bleed)); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)((color_error * 5)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x-1],(sshort)((color_error * 3)/bleed)); } if (x>1){ AdjustShortPixel(threshold,(sshort *)&seedptr[x-2],(sshort)((color_error * 3)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x-2],(sshort)(color_error/bleed)); } /* seed next line forward */ AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((color_error * 7)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error * 5)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+2],(sshort)((color_error * 3)/bleed)); /* seed furthest line forward */ AdjustShortPixel(threshold,(sshort *)&seed2ptr[x],(sshort)((color_error * 5)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x+1],(sshort)((color_error * 3)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x+2],(sshort)(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,(sshort *)&colorptr[x-1],(sshort)((color_error * 8)/bleed)); if(x>1)AdjustShortPixel(1,(sshort *)&colorptr[x-2],(sshort)((color_error * 4)/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error * 8)/bleed)); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)((color_error * 4)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x-1],(sshort)((color_error * 2)/bleed)); } if (x>1){ AdjustShortPixel(threshold,(sshort *)&seedptr[x-2],(sshort)((color_error * 2)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x-2],(sshort)(color_error/bleed)); } /* seed next line forward */ AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((color_error * 8)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error * 4)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+2],(sshort)((color_error * 2)/bleed)); /* seed furthest line forward */ AdjustShortPixel(threshold,(sshort *)&seed2ptr[x],(sshort)((color_error * 4)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x+1],(sshort)((color_error * 2)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x+2],(sshort)(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,(sshort *)&colorptr[x-1],(sshort)(color_error/bleed)); if (x>1)AdjustShortPixel(1,(sshort *)&colorptr[x-2],(sshort)(color_error/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)(color_error/bleed)); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(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,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)(color_error/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed)); /* seed furthest line forward */ AdjustShortPixel(threshold,(sshort *)&seed2ptr[x],(sshort)(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,(sshort *)&colorptr[x-1],(sshort)((color_error * 8) /bleed)); if(x>1)AdjustShortPixel(1,(sshort *)&colorptr[x-2],(sshort)((color_error * 4) /bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error * 8) /bleed)); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)((color_error * 4) / bleed)); if (x>1)AdjustShortPixel(threshold,(sshort *)&seedptr[x-2],(sshort)((color_error * 2) / bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((color_error * 8) /bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error * 4) /bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+2],(sshort)((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,(sshort *)&colorptr[x-1],(sshort)((color_error * 5)/bleed)); if(x>1)AdjustShortPixel(1,(sshort *)&colorptr[x-2],(sshort)((color_error * 3)/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error * 5)/bleed)); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)((color_error * 4)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x-1],(sshort)((color_error * 2)/bleed)); } if (x>1){ AdjustShortPixel(threshold,(sshort *)&seedptr[x-2],(sshort)((color_error * 2)/bleed)); } /* seed next line forward */ AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((color_error * 5)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error * 4)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+2],(sshort)((color_error * 2)/bleed)); /* seed furthest line forward */ AdjustShortPixel(threshold,(sshort *)&seed2ptr[x],(sshort)((color_error * 3)/bleed)); AdjustShortPixel(threshold,(sshort *)&seed2ptr[x+1],(sshort)((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,(sshort *)&colorptr[x-1],(sshort)((color_error*4)/bleed)); if(x>1)AdjustShortPixel(1,(sshort *)&colorptr[x-2],(sshort)((color_error*3)/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error*4)/bleed)); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)((color_error*2)/bleed)); if (x>1)AdjustShortPixel(threshold,(sshort *)&seedptr[x-2],(sshort)(color_error/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((color_error*3)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)((color_error*2)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+2],(sshort)(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,(sshort *)&colorptr[x-1],(sshort)((color_error * 2) /bleed)); /* seed next line forward */ AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((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,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed)); } AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)(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,(sshort *)&colorptr[pos],(sshort)((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,(sshort *)&seedptr[pos],(sshort)((color_error * mult) /bleed)); } /* seed furthest line forward */ mult = customdither[2][dx]; if (mult > 0) { AdjustShortPixel(threshold,(sshort *)&seed2ptr[pos],(sshort)((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,(sshort *)&colorptr[x-1],(sshort)((color_error*2)/bleed)+total_difference); if (x>1)AdjustShortPixel(1,(sshort *)&colorptr[x-2],(sshort)(color_error/bleed)); } else { /* finish this line */ AdjustShortPixel(1,(sshort *)&colorptr[x+1],(sshort)((color_error*2)/bleed)+total_difference); AdjustShortPixel(1,(sshort *)&colorptr[x+2],(sshort)(color_error/bleed)); } /* 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,(sshort *)&seedptr[x-1],(sshort)(color_error/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x],(sshort)((color_error*2)/bleed)); AdjustShortPixel(threshold,(sshort *)&seedptr[x+1],(sshort)(color_error/bleed)); /* seed furthest line forward */ AdjustShortPixel(threshold,(sshort *)&seed2ptr[x],(sshort)(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 -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 = (ushort)dibscanline1[i]; b+= dibscanline2[i]; i++; g = (ushort)dibscanline1[i]; g+= dibscanline2[i]; i++; r = (ushort)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] = (uchar) (ushort)(b/4);j++; bmpscanline[j] = (uchar) (ushort)(g/4);j++; bmpscanline[j] = (uchar) (ushort)(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> 4; dibscanline1[j] = ch; j++; ch = bmpscanline[i] & 0xf; dibscanline1[j] = ch; j++; } } memset(&bmpscanline[0],0,1920); for (i=0,j=0;i 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 -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 */ sshort Convert() { FILE *fp, *fpdib, *fpreview; sshort status = INVALID, resize = 0; ushort x,x1,x2,y,yoff,i,packet, outpacket, width, dwidth, red, green, blue; uchar r,g,b,drawcolor; ulong 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 = (ushort) bmi.biWidth; bmpheight = (ushort) 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 = (ushort) bmi.biWidth; bmpheight = (ushort) 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 = (ushort) bmi.biWidth; bmpheight = (ushort) 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 '9') && buf[idx] != ' ') buf[idx] = ASCIIZ; ptr[jdx] = buf[idx]; jdx++; ptr[jdx] = ASCIIZ; } /* remove trailing whitespace... this occurrs during parsing of photostyler */ len = strlen(ptr); while (len > 0) { len--; if (ptr[len] != ' ') break; ptr[len] = ASCIIZ; } } /* split the RGB triple from a text line read-in from an ascii palette file. */ int ReadPaletteLine(unsigned char *ptr, unsigned char *palptr, unsigned int colordepth) { int red, green, blue, idx, spaces = 0; red = atoi(ptr); if (red < 0 || red > 255) return INVALID; /* there must be at least 3 fields */ for (idx = 0; ptr[idx] != 0; idx++) { if (ptr[idx] == ' ' && ptr[idx+1] >= '0' && ptr[idx+1] <= '9') { spaces++; switch(spaces) { case 1: green = atoi(&ptr[idx+1]); if (green < 0 || green > 255) return INVALID; break; case 2: blue = atoi(&ptr[idx+1]); if (blue < 0 || blue > 255) return INVALID; break; } } } if (spaces<2) return INVALID; if (colordepth == 6) { palptr[0] = (uchar)red << 2; palptr[1] = (uchar)green << 2; palptr[2] = (uchar)blue << 2; } else { palptr[0] = (uchar)red; palptr[1] = (uchar)green; palptr[2] = (uchar)blue; } return SUCCESS; } /* check version if Paintshop palette since JASC may change someday */ /* also check Aldus version although that product is old... */ /* The Top Half of NeoPaint Windows Palettes are the same as their */ /* DOS palettes so we use the 6 bit color values and handle both */ /* file types the same way... so no worry about neopaint versions. */ char *Gimp = "GIMP Palette"; /* followed by RGB values and comments */ /* NeoPaint and PaintShop Pro headers 3 lines followed by RGB values */ char *NeoPaint = "NeoPaint Palette File"; char *PaintShop = "JASC-PAL"; char *PaintShopVersion = "0100"; /* Aldus photostyler 3 lines followed by RGB values */ char *AldusPal = "CWPAL"; char *AldusClr = "CWCLR"; /* partial palettes */ char *AldusVersion = "100"; #define GENERIC 1 #define GIMP 2 #define JASC 3 #define NEO 4 #define ALDUS 5 sshort GetUserPalette(char *name) { FILE *fp; char buf[128]; int cnt=16; sshort status = INVALID; unsigned colordepth=8,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,(uchar *)&rgbUser[cnt][0],colordepth)) continue; cnt++; if (cnt > 15)break; } break; } fclose(fp); if (cnt < 15) { printf("%s contains only %d colors!",name,cnt); } if (status == INVALID) { printf("%s is not a valid palette file!",name); } return status; } /* 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; uchar tempr, tempg, tempb; int i,x,y, y2 = 191,idx = 1; ushort 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 */ sshort 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) { sshort idx,jdx,kdx,palidx=5,previewidx=5,hgrpalidx=5,pseudopal=0, status,basename=0,plainname=0; uchar 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 = (uchar *)malloc(8192); if (NULL != hgrbuf) { dhrbuf = (uchar *)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 = (uchar *)&argv[idx][0]; ch = toupper(wordptr[0]); if (ch == '-') { wordptr = (uchar *)&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 == (uchar)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!= (uchar)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 == (uchar)0) break; c = PaintByNumbers((char *)&wordptr[1]); if (c!= (uchar)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] != (uchar)0; idx++) { if (fname[idx] == '.') { jdx = idx; } } if (jdx != 999) fname[jdx] = (uchar)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] != (uchar)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; }