apple2gs_shr_converter/src_m2s/m2s.c

1979 lines
61 KiB
C
Executable File

/* ---------------------------------------------------------------------
m2s MagickToShr (C) Copyright Jonas Grönhagen and Bill Buckels 2014.
All Rights Reserved.
Licence Agreement
-----------------
Use and Distribute at your own risk!
This code and the programs it produces are under development. Neither
Jonas Grönhagen or Bill Buckels have warranty obligations or liability
resulting from its use in any way whatsoever. If you don't agree,
remove this source code, the programs it produces, and related files
from your computer now.
Description
-----------
MagickToShr (C) Copyright Jonas Grönhagen and Bill Buckels 2014.
All Rights Reserved.
Usage is: m2s BaseName -Options
BaseName: _proc.bmp and _palette.bmp (file pairs)
"m2s Woz" opens "Woz_proc.bmp" and "Woz_palette.bmp"...
For MS-DOS, "M2S16 WOZ" opens "WOZ.BMP" and "WOZ.DIB!"
16 palettes for mode320 output and 200 for mode3200!
Options: -A = Alternate PNT file output (run length encoded).
Default: raw SHR (mode320) or SH3 (mode3200).
-T = Use CiderPress Attribute Preservation Tags.
Default: No Tags! (unadorned file extensions)
Does not apply to M2S16.EXE (MS-DOS binary).
Options may be combined: "-ta" or "-at"
Options are Case Insensitive - Switchar "-" is Optional.
Designed by: Jonas Grönhagen and Bill Buckels
Programmed by: Bill Buckels
Email: bbuckels@mts.net
Synopsis:
BMP Input
Apple II SHR Output
Revision : April, 2014 - 0.0 - None - Under Development
April 2015 - fixed palette count - watch for problems
April 2015 - added support for BMP 4 and BMP 5
initilly only BMP 3 was supported.
-----------------------------------------------------------------------
M2S16.EXE - MS-DOS
Built under Large Model 16 bit Microsoft C (MSC) Version 8.00c
Note: Run in an MS-DOS emulator like DOSBox if you can't run it raw.
M2S32.EXE - WIN32
Built under Visual Studio 2005
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00 for 80x86
M2S.EXE - WIN32
Built under MinGW 5.1.4 (gcc)
----------------------------------------------------------------------- */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
/* ***************************************************************** */
/* ========================== defines ============================== */
/* Note: define DEBUG to get additional info. */
/* ***************************************************************** */
/* status codes */
#define SUCCESS 0
#define VALID SUCCESS
#define FAILURE -1
#define INVALID FAILURE
/* error codes */
#define ERR_IN -1
#define ERR_OUT -2
#define ERR_FMT -3
#define ERR_SIZE -4
#define ERR_ABORT -5
/* the various 3 dimensional color arrays used in here expect this order for RGB values */
#define RED 0
#define GREEN 1
#define BLUE 2
/* constants for the biCompression field in a BMP header */
#define BI_RGB 0L
#define BI_RLE8 1L
#define BI_RLE4 2L
#define PIC_FMT 1
#define BROOKS_FMT 2
/* RLE constants - including APF and PackBytes constants */
/* maximum number of list nodes for packbytes encoder */
/* must be equal or greater than the raw line length of the input BMP */
#define RAW_MAX 160
/* maximum line length for encoded packbytes buffer */
/* must be double the raw line length of the input bmp */
#define PAK_MAX (RAW_MAX * 2)
/* ***************************************************************** */
/* ========================== typedefs ============================= */
/* ***************************************************************** */
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;
typedef short sshort;
/* Bitmap Header structures */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADER
#else
typedef struct tagBITMAPINFOHEADER
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
} BITMAPINFOHEADER;
/* April 2015 - added support for BMP 4 and BMP 5 headers */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADERV4
#else
typedef struct tagBITMAPINFOHEADERV4
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
ulong biRedMask;
ulong biGreenMask;
ulong biBlueMask;
ulong biAlphaMask;
ulong biCSType;
uchar biEndpoints[36]; /* CIEXYZTRIPLE */
ulong biGammaRed;
ulong biGammaGreen;
ulong biGammaBlue;
} BITMAPINFOHEADERV4;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPINFOHEADERV5
#else
typedef struct tagBITMAPINFOHEADERV5
#endif
{
ulong biSize;
ulong biWidth;
ulong biHeight;
ushort biPlanes;
ushort biBitCount;
ulong biCompression;
ulong biSizeImage;
ulong biXPelsPerMeter;
ulong biYPelsPerMeter;
ulong biClrUsed;
ulong biClrImportant;
ulong biRedMask;
ulong biGreenMask;
ulong biBlueMask;
ulong biAlphaMask;
ulong biCSType;
uchar biEndpoints[36]; /* CIEXYZTRIPLE */
ulong biGammaRed;
ulong biGammaGreen;
ulong biGammaBlue;
ulong biIntent;
ulong biProfileData;
ulong biProfileSize;
ulong biReserved;
} BITMAPINFOHEADERV5;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagBITMAPFILEHEADER
#else
typedef struct tagBITMAPFILEHEADER
#endif
{
uchar bfType[2];
ulong bfSize;
ushort bfReserved1;
ushort bfReserved2;
ulong bfOffBits;
} BITMAPFILEHEADER;
/* picfile trailer structure (sort of) - extended for brooks output
this is really just a buffer when it comes to brooks */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagPICFILE
#else
typedef struct tagPICFILE
#endif
{
uchar scb[200];
uchar padding[56];
uchar pal[200][32]; /* note the PIC file actually has only 16 palette entries */
} PICFILE;
/* APF structures */
/* FileType - $C0 AuxType $0002 */
/* integers are in big endian (Motorola) Format */
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagPNTPIC
#else
typedef struct tagPNTPIC
#endif
{
ulong Length; /* Block Length (also image length) */
uchar Kind[5]; /* 4 'M' 'A' 'I' 'N' */
ushort MasterMode; /* 0 for mode320 and 0x80 for mode640 */
ushort PixelsPerScanline; /* 320 or 640 */
ushort NumColorTables; /* 16 */
uchar ColorTable[16][32]; /* $0RGB table buffer */
ushort NumScanLines; /* vertical resolution */
ushort ScanLineDirectory[200][2];
} PNTPIC;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagPNTBROOKS
#else
typedef struct tagPNTBROOKS
#endif
{
ulong Length;
uchar Kind[5]; /* 4 'M' 'A' 'I' 'N' */
ushort MasterMode;
ushort PixelsPerScanline; /* 320 */
ushort NumColorTables; /* 1 */
uchar ColorTable[1][32]; /* $0RGB table buffer - All Black */
ushort NumScanLines; /* vertical resolution */
ushort ScanLineDirectory[200][2];/* ModeWord is used for sequential scbs */
} PNTBROOKS;
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagMULTIPAL
#else
typedef struct tagMULTIPAL
#endif
{
ulong Length;
uchar Kind[9]; /* 8 'M' 'U' 'L' 'T' 'I' 'P' 'A' 'L' */
ushort NumColorTables; /* 200 */
uchar ColorTableArray[200][32]; /* $0RGB table buffer */
} MULTIPAL;
/* end of Big Endian Motorola Format Structures */
/* PackBytes Line Encoder List Structure */
/* FileType - $C0 AuxType $0002 */
/* Double layers - both sequential
Layer 1 - Count, Value pairs for an shr scanline
Layer 2 - FIFO Stack of Singleton Values
This List Structure must always provide 1 node for each byte of a raw shr scanline.
*/
#ifdef MINGW
typedef struct __attribute__((__packed__)) tagRAWLIST
#else
typedef struct tagRAWLIST
#endif
{
uchar CNT;
ushort VAL;
uchar Singleton; /* this is a FIFO stack for processing singletons */
} RAWLIST;
/* ***************************************************************** */
/* ========================== globals ============================== */
/* ***************************************************************** */
/* static structures for processing */
BITMAPFILEHEADER bfi;
BITMAPINFOHEADER bmi;
/* April 2015 - support for BMP version 4 but not sure about other versions */
BITMAPINFOHEADERV5 bmiV5;
PICFILE shr;
uchar shrline[200][160];
/* colormap array */
uchar cmap[200][16][3];
/* line index for shr palette */
uchar svgaline[320];
/* rgb triples from bmpline */
uchar bmpline[960];
/* filenames */
char bmpfile[256], cmapfile[256], shrfile[256], brooksfile[256], pntfile[256];
/* default */
sshort output_format = PIC_FMT;
sshort numpalettes = 16;
/* ***************************************************************** */
/* ========================== RLE specific globals ================= */
/* ***************************************************************** */
sshort output_pnt = 0, suppress_pnt = 1, no_tags = 1, suppress_pic = 0;
ulong MainLength = 0L;
/* an SHR file is always 160 bytes x 200 scanlines */
/* but a PNT file can be any length x any width */
/* the packbytes implementation in this program is limited to 160 bytes */
/* it is therefore limited to packing screensize PNT files */
ushort RawCount = 0, SingleCount = 0;
RAWLIST *RawBuf;
MULTIPAL *MultiPal;
PNTPIC *picMain;
PNTBROOKS *brooksMain;
ushort PackedCount = 0;
/* output buffer for the Packed Line */
uchar PackedBuf[PAK_MAX];
/* ***************************************************************** */
/* ========================== RLE output routines ================== */
/* ***************************************************************** */
/* The Apple Preferred Format (APF) file is also called a PNT file.
It stores graphics images for display in SHR mode320, mode640 and
mode3200.
Images stored in APF can be wider and longer than the screen.
Since I am processing mode320 and mode3200 screens only, I am
limiting my processing and output routines to these two subsets of
the Apple Preferrred Format (APF).
For the purposes of general SHR file interchange, there are only 3
Native SHR FileTypes of interest (marked with arrows below):
$Cx Types: Graphics
$C0 PNT Apple IIgs Packed Super HiRes
$0000 - Paintworks Packed Super Hi-Res
$0001 - Packed Super HiRes
$0002 - Apple Preferred Format <--- Run Length Encoded
$0003 - Packed QuickDraw II PICT
$C1 PIC Apple IIgs Super HiRes
$0000 - Super Hi-Res Screen Image <--- Raw - Easiest
$0001 - QuickDraw PICT
$0002 - Super HiRes 3200 <--- Raw - Not Easy to load.
Output File Extension - SHR#C10000 - Pic
==================================
For the average user, the PIC File Aux Type 0000 is easiest and
quickest to load. It is a RAW BSAVED image of SHR memory, which
includes 3200 bytes of screen memory of 200 x 160 byte scanlines,
followed by scanline control bytes padded to 256 bytes, followed by
512 bytes of 16 palettes of 16 colors.
Because the standard SHR Screen Memory is only in two resolutions,
mode320 and mode640, this file format supports both resolutions,
but does not support images with higher resolutions than the SHR
screen
Even though this file is the lowest common denominator for SHR and
easiest to load, a specific load sequence including control over
main and auxiliary memory is still required, so intermediate
programming skills are required for this file to be of use by a
programmer.
Output File Extension - SH3#C10002 - Brooks
==================================
The PIC File Aux Type 0002 (Brooks Format) is quick to load but not
easy for a programmer to display. Displaying this type of file in a
program requires that the program code is constantly synching with
the screen update routines for each scanline to move palettes in
real time from a buffer of 200 palettes into the 16 palettes
supported by SHR. This leaves little time for anything else, and
less time on a slower Apple IIgs, and even less time on an Apple
IIe with a VOC but no accelerator.
While I see Todd Whitesel has written a Brooks Loader for the VOC
on a GS, I have never seen a VOC Brooks loader for the Apple IIe.
I don't know whether an 8 bit program like SHRVIEW would work for
Brooks on a IIe equipped with a VOC since I don't have a VOC.
Todd's file format for the VOC's standard interlace mode is also not
as robust as my own, since it only allows half the palettes.
I am speculating that he likely based his format on what was easiest
for him to create in whatever IIgs Paint program he used at the time.
I also have no evidence of any Brooks format files that follow his
HT and HB formats.
While my BMP2SHR utility supports the creation of Brooks Files for
the VOC's interlace mode, in practice these may be quite useless.
This utility does not support those, nor any provision for the
VOC's interlace mode400 at all.
Output File Extension - PNT#C00002 - APF
========================================
APF is the most complicated of the 3 SHR output formats supported
by this program, and also the most extensible since APF includes
Run-Length Encoded (RLE) versions of both PIC and BROOKS files, and
also offers support for sizes other than 320 x 200 and 320 x 400.
An APF file can be used to store Image Fragments (Sprites) or SHR
images larger than the screen. All the SHR scanlines stored in an
APF are in PackBytes RLE format, but the header information is not.
While APF's PackBytes RLE is not as efficient as DreamGraphx LZW
compression, APF files are widely supported. (DreamGraphx files are
not noted above, and are also not supported by this program.)
Some APF Considerations:
1. The APF consists of CHUNKS called BLOCKS. The MAIN Block in
an APF contains the entire mode320 and mode640 subset and
in the case of a mode3200, everything except for the 200
palettes is stored.
2. One additional consideration is the requirement to store
the position for the start of each scanline in the APF
file so that each scanline can be unpacked separately.
Doing so allows an APF viewer or Editor to Unpack scanlines one
a time to support scrolling through an SHR image larger than the
screen. Memory is scarce on the Apple II, but because the APF
stores the start of each scanline, the programmer can store the
Packed scanline in a small buffer using very little memory in
the process; by seeking through a file one line at a time to the
start of a packed line, then reading and unpacking (in whole or
in part) directly to the SHR screen.
Unsupported SHR File Formats
============================
Unsupported Compressed Formats
==============================
As noted above DreamGraphx file output is not supported. If this
program is extended to support converting to a format that uses
file compression, only the most efficient compressed native IIgs
SHR format will be supported.
For some notes on the work Brutal Deluxe has done to develop this
format see the following link:
http://www.brutaldeluxe.fr/products/crossdevtools/lz4/
Non-native formats abound, both compressed and uncompressed,
but these can commonly be converted to a simple 24-bit
uncompressed BMP file for conversion to the 3 Apple II formats
supported by this program.
Compuserve GIF files saw wide use at the time the IIgs had wide
use. GIF files were largely produced on the IBM-PC for display
in SVGA unrestricted 256 color mode. They were often highly
optimized for a single image palette, and not for 16 segmented
palettes like SHR, or 200 16 color line palette like BROOKS.
Therefore reorganizing the colors in a GIF to match the SHR palette
can produce poor results. Complex GIFs like those created from
photos can end-up dithered and the color balance can also be
optimized for the SVGA display, so methods like error diffusion to
smooth a GIF may not work so well. Despite the fact that a GIF is
not lossy, and a JPEG is, the color balance in a JPEG combined with
its lack of dithering can result in better palettizing and
dithering to an SHR file.
GIF and JPEG are neither supported input formats (only 24-bit BMPs
are input formats) nor are they supported for output: this program
converts to SHR formats, not to IBM-PC style formats. PNG is not
supported for input or output either.
Unsupported Uncompressed Formats - Raw or Packed
================================
4 of the file formats noted above are not supported by this
program, They include two variants of the QuickDraw PICT file
format.
QuickDraw II PICT files store pictures as a script; a series of
command instructions; opcodes which are used to record the
QuickDraw II commands that created the picture. Modeled after PICT2
on the Macintosh, they can be used to exchange pictures between the
IIgs and the Mac. They can be played-back by a IIGs GUI program
with the QuickDraw II Toolbox Routine DrawPicture:
http://apple2.boldt.ca/?page=til/tn.iigs.046
*BUT* they are not bitmapped graphics files.
Of the 2 remaining unsupported packed SHR files in the list above,
the least elegant is the $C0,$0001 Packed Super HiRes. It is just
an RLE PIC file, encoded from start to finish. It is not extensible
to BROOKS, and it is not extensible to sizes smaller or larger than
the SHR screen. Any programmer who can load these to the screen can
also load an APF. Any programmer who cannot write the code for
UnPackBytes will have no use for these but may still be able to
load the raw PIC format equivalent. So a Packed Super Hi Res file
offers no advantage over the APF.
The remaining file format of the unsupported formats from the list
above is the Paintworks Packed Super Hi-Res Picture File. This file
is targetted to users of Activision's PaintWorks Program, and
contains irrelevent header baggage in the form of QuickDraw II
Patterns in addition to Bitmapped Graphics Information.
It is also not as robust as the APF when it comes to sizes; only
screen width is supported, and even though images longer than the
screen are supported (which may suit editing SHR images of 400
lines targetted at the VOC's interlaced mode400), Paintworks
Packed files can support full-paged documents but do not specify
exactly how many scanlines are in a page.
So besides its reliance on QuickDraw II, this also relies on the
PaintWorks Application. And besides being primarily supported only
by their creator (Activision PaintWorks), these files use the
PackBytes RLE and not something better, so offer no advantage
over APF in image size.
The Apple II FileType notes can be referred to for additional
details not included here or elsewhere in this program's source
code comments or additional documentation.
*/
/*
* Apple PackBytes RLE format
*
* Format is:
* <mask | count>, <value>
*
* Mask is the 2 hi-bits | Count is the 6 low bits, value is the raw byte
* Encoding: Description
* 00xxxxxx: (0-63) 1 to 64 bytes follow, all different - mask = 0x00
* 01xxxxxx: (0-63) 1 to 64 repeats of next byte - mask = 0x40
* 11xxxxxx: (0-63) 1 to 64 repeats of next byte taken as 4 bytes - mask - 0xc0
The above 3 encodings are used in this implementation. The following is not:
* 10xxxxxx: (0-63) 1 to 64 repeats of next 4 bytes (quads) - mask = 0x80
Rationale
=========
My rationale of PackBytes, including for selecting only 3 of the 4
encodings is as follows:
Run-length encoding depends on repeats. If there are no repeats, or a
small number of short repeats, the packed line will be larger than
than the raw line.
Since the SHR display is a packed pixel format of 2 pixels per byte,
2 pixels must repeat. Packed pixels already result in smaller lines
in raw format, so the savings between raw and packed scanlines can be much
less they might be with 8 bit indices.
In any run length encoding scheme the likehood of pixels repeating
decreases with more colors. The likelihood of the SHR's packed pairs
of pixels repeating decreases even more with more colors per line .
Mask x00 - Singletons
=====================
If all 160 bytes in a screen width image are different, the line will
expand when encoded as follows:
Raw (64,64,32) = Encoded (65,65,33) = 163 Bytes.
But some encoded lines can be even larger, making it hardly worth the
effort to encode the line in the first place. To begin with
additional disk space may needed to store the encoded file. and
loading the file requires decoding, which not only takes longer to
load from disk because of the larger file size, but because the file
can't be read directly into screen memory, it must be buffered and
decoded. This might be worth the effort to save disk space if a file
encodes to a much smaller size, but not when it expands when encoded.
Since the SHR display is in linear auxiliary memory, and organized
from the top of the screen to the bottom of the screen, the only
advantage of using run length encoding on an SHR file that gets
larger and not smaller when encoded, is when the file is larger than
the display resolution and must be scrolled through to view. For
screen images a larger file is pointless especially since IIgs
programs that work with SHR images will likely work with raw PIC files
anyway.
If a small number of short repeats occur throughout the line,
encoding can expand the line size further.
For example if a line has 2 repeats of 2 bytes that divide it into 5
segments, it can look like this when encoded:
Repeated bytes = 4, Encoded Bytes = 4
Equal Segments - Packed Length = 159 + 4 = 163
Individual bytes = 160 - 4 = 156 / 3 = (52,52,52), Encoded Bytes = (53+53+53) = 159
Unequal Segments - Packed Length = 161 + 4 = 165
Individual bytes = 160 - 4 = 156 / 3 = (154,1,1), Encoded Bytes = (65+65+27)+(2+2) = 161
A second example for a line with 3 repeats of 2 bytes that divide it into 7 segments can
look like this when encoded:
Repeated bytes = 6, Encoded Bytes = 6
Equal Segments - Packed Length = 158 + 6 = 164
Individual bytes = 160 - 6 = 154 / 4 = (38,39,38,39), Encoded Bytes = (39+40+39+40) = 158
Unequal Segments - Packed Length = 160 + 6 = 166
Individual bytes = 160 - 6 = 154 / 4 = (151,1,1,1), Encoded Bytes = (65+65+24)+(2+2+2) = 160
A final example for a line with 4 repeats of 2 bytes that divide it into 9 segments can
look like this when encoded:
Repeated bytes = 8, Encoded Bytes = 8
Equal Segments - Packed Length = 158 + 8 = 166
Individual bytes = 160 - 8 = 152 / 5 = (30,30,32,30,30), Encoded Bytes = (31+31+33+31+31) = 158
Unequal Segments - Packed Length = 156 + 8 = 164
Individual bytes = 160 - 8 = 152 / 5 = (148,1,1,1,1), Encoded Bytes = (65+65+21)+(2+2+2+2) = 156
The 3 examples above are ordered examples, but in practice SHR images
follow no rhyme or reason, so the combinations resulting from encoding
will be as random and unpredicatble as the image itself.
The PackBytes implementation in this program attempts to overcome
unecessarily complicated encoded lines. If a line does not encode
below the 163 byte threshold of a 3 segmented line of individual
bytes, it will be encoded into a 163 byte 3 segmented line of 163
bytes. PackBytes provides for no smaller alternative.
When compared to other run length encoding of the same era, like
Z-Soft PCX, PackBytes is less-efficient when it comes to a line of
all different single bytes, unless single bytes of PCX's 0xc0 mask byte
are encountered. Otherwise such a line in a PCX file be the same as
the raw version of the line... but this 0xc0 constraint is the only
condition that will expand a PCX line.
Since PCX encoding offers a maximum encoded length of 63 and not 64
bytes as in PackBytes Mask 0x40, and also does not allow a whole one
color line to be encoded into a single byte pair (PCX has no
equivalent to PackBytes Mask 0xc0), sometimes PackBytes can outperform
PCX on simple images with repeats over 63 bytes in length. Other
schemes for RLE similar to PCX that changed the mask byte incurred
additional overhead to do so. It is difficult to generally compare
one RLE scheme to another; that needs to be done image by image.
File formats that came after RLE, like GIF, applied non-lossy
compression and achieved better results than RLE alone, but were more
complicated to display, despite the fact that they incurred less
storage and loading overhead. These were followed by equally
complicated but lossy formats like jpeg which are not well suited to
the limited 4 bit color depth of the SHR display despite far smaller
files.
Since PackBytes is the standard for the APF file and the SHR display,
and works reasonably quickly on 8 bit Apple //e's equipped with a
VOC, discussion of other RLE's and other compressed formats is moot,
and out of the scope of what I have done here.
Mask 0x40 and Mask 0xc0 - Repeats
=================================
Repeats were discussed above. There are 3 types of repeats in
PackBytes but only two of them are used in this PackBytes
implementation.
If a repeat is 64 bytes or less, Mask 0x40 is used to encode the
line. For repeat of over 64 bytes, Mask 0xc0 is used for the first
encoded byte pair. But how the subsequent encoded byte pairs are done
depends on how many repeats are done.
Mask 0xc0 works the same way as Mask 0x40, and both encodings are
pairs of bytes. But the count for Mask 0x40 is from 1-64, and the
count for Mask 0xc0 is from 1-64 in repeats of 4. Therefore to be
encoded entirely using Mask 0xc0, repeats that do not evenly divide
by 4 will have 1-3 stragglers at the end of a run. These stragglers
are encoded using Mask 0x40 at the end of the Mask 0xc0 encoded
pairs, adding an additional 2 bytes to the encoded line.
Mask 0xc0 lines are obviously the only way to go on a line with more
than 64 repeats. On a line of one color the savings between a raw
line and an encoded line are as follows:
Best Case Scenario - Mask 0xc0
10 Values
Repeated Bytes = 160 = (40 x 4), Encoded Bytes = 2
Repeated Bytes = 124,128,132,136,140,144,148,152,156 Encoded Bytes = 2
10 Values
Repeated Bytes = 120 = (30 x 4), Encoded Bytes = 2
Repeated Bytes = 84,88,92,96,100,104,108,112,116 Encoded Bytes = 2;
4 Values
Repeated Bytes = 80 = (20 x 4), Encoded Bytes = 2
Repeated Bytes = 68,72,76 Encoded Bytes = 2
Total Values = 24
Best Case Scenario - Mask 0x40
Repeated Bytes = 64 = (64 x 1), Encoded Bytes = 2
Repeated Bytes = 2-63 Encoded Bytes = 2
Total Values = 63
On an image with many colors on one line, this scenario is most
likely, if repeats exist at all.
Worst Case Scenario - Mask 0xc0 = 2 Encoded Bytes, Mask 0x40 = 2 Encoded Bytes
The Best Case Scenarios above need only 2 Bytes to Encode up to an
entire line of repeats. Repeated bytes in the following ranges need
three encoded bytes:
30 Values
Repeated Bytes = 121-123,125-127,129-131,133-135,137-139,141-143,145-147,149-151,153-155,157-159 = 3
30 Values
Repeated Bytes = 81-83,85-87,89-91,93-95,97-99,101-103,105-107,109-111,113-115,117-119 = 3
12 Values
Repeated Bytes = 65-67,69-71,73-75,77-79 = 3
Total Values = 72
Conclusions:
============
Repeats of 2-64 Are 100% likely to encode in 2 bytes.
Repeats of 65-160 Are 25% likely to encode in 2 bytes.
Repeats of 2-160 Are 60% likely to encode in 2 bytes
PCX Comparison
==============
34 Values 127-160 Encoded Bytes = 6
63 Values 64-126 Encoded Bytes = 4
62 Values 2-63 Encoded Bytes = 2
0xc0 Mask Byte - Must Always Be Encoded - Not Included Above
Comparitive Conclusions
=======================
Repeats of 2-64 Are 98% likely to encode in 2 bytes.
Repeats of 65-160 Are 0% likely to encode in 2 bytes
Repeats of 2-160 Are 39% likely to encode in 2 bytes
Full Range Compression Ratio - 160 values
=========================================
PackBytes (88*2) + (72*3) = 392
PCX (62*2) + (63*4) + (34*6) = 580
On lines with over 63 repeats PackBytes is far more efficient than PCX.
On lines of single colors PCX does better than Packbytes.
PCX Could learn from PackBytes, and allow 2 MaskBytes like 0xc0 and
0x40. Both formats could learn from Pictor which allows MaskBytes to
be set inline. One feature that PCX has that PackBytes does not is
the ability to encode in planar format. But encoding a single bit
plane offers debatable efficiency when it comes to encoding bit
mapped images. Both are designed in a device dependent manner so
suffer trade-offs for reasons of ease of display rather than encoding
efficiency.
Mask 0x80 - Quad Repeats of the same 4 Bytes - Unused
=====================================================
Mask 0x80 encoding expects a byte pattern of 4 bytes to repeat in an
image.
Using Mask 0xc0 encoding the Packed Repeat for 160 Bytes is (count,data) = (40,value) = 2 bytes;
Using Mask 0x80 the same repeat is (40,value,value,value,value) = 5 bytes.
No advantage there. No advantage for mode320 files at all, as far as
I can see.
I haven't been able to establish a logical case for the efficiency of
Mask 80 encoding for mode640 files either.
To expect a pattern of 01234567 to repeat in a mode320 image seems
rather remote.
In a dithered mode640 image, to expect a pixel pattern of
0123456789012345 to repeat also seems rather remote.
Overall Conclusion
==================
If lines expand to more than 103 bytes when packed, then pack the
line as a 103 byte run in 3 segments, and hope the next line saves
some space.
For repeats of multiples, use Mask 0xc0 where possible for runs of
over 64 bytes, and encode the stragglers as a Mask 0x40 repeat of 2
or 3 pixels, and single pixels if less than 2.
I will likely expand a test version that tries Mask 0x80 at some
point if I have some epiphany about size reduction advantages.It
could well be for all that I know, that some encoder somewhere uses
the 0x80 mask for something or other, but based on what I see,it
wasn't very well thought out. Or perhaps it is so well thought out
that I can't figure it out.
*/
/*
This PackBytes encoder is really a simple list processor.
Although the list processor for this function was written for the
occasion, it is a gutted version of the PCX encoder from the Z-Soft
technical reference for the PCX file format. It is just the standard
RLE algorithm without encoding. and can be easily adapted to most
RLE's including for debugging an RLE, because of its modular nature.
Inputs - Shr Scanline, Length
Outputs - PackBytes Scanline, Length
Pre Processing
This PackBytes Encoder uses a double buffering approach; It initially
reads a raw Super Hi-Res scanline into a sequential list of
Count,Value pairs, effectively splitting the line into a queue of 2 types of
nodes; Singletons and Repeats.
After the initial pass, the raw scanline is set aside, and the list is
encoded into an output buffer.
Passive State - Queue Priority 0
During this second pass, Singletons are
pushed onto a stack, until a Repeat node is encountered.
Active State - Singletons Priority 1, Repeats Priority 0
Then all the Singletons (if any) are popped off the stack and encoded,
before encoding the Repeat node.
Active State - Repeats Priority 1, Singletons Priority 0
Repeats are always immediate, but always yield to Singletons.
Singletons always line-up in the Queue, waiting for a repeat to
trigger an encoding event.
Meta State
This second process iterates through the list using this same
strategy, until all the nodes are encoded.
Quit State
If straggler Singletons are still on the stack when the end of the list
is reached, they are popped off the stack and encoded.
Post Processing
An encoded line with many colors and few repeats can be longer than
the raw line. Packbytes does poorly with when runs of Singletons are
split with short repeats, fragmenting the line.
Condition
To avoid expanding the line unnecesarily, this PackBytes encoder
calculates the optimum encoded line length without repeats. This is a
simple calculation which involves splitting the line up into 65 byte
segments, with 1 count byte for every 64 data bytes. If the encoded
line has exceeded the calculated optimal Singleton length, the raw
line is recoded as a line of Singletons without repeats.
Return Value
Length of Encoded Line
*/
int PackBytes(uchar *inbuff, sshort inlen)
{
uchar this,last,msk;
ushort runcount, repeats, singlerun, singlereps, maxpack, len;
sshort idx,jdx,i,j;
/* ********************************************* */
/* ========== Build the List for this line ===== */
/* ********************************************* */
/* Build a list of count,value pairs */
RawCount = 0;
last = inbuff[0];
len = 0;
runcount=1;
for(idx=1;idx<inlen;idx++){
this=inbuff[idx];
if(this==last){
runcount++;
}
else{
if(runcount > 0){
RawBuf[RawCount].CNT = runcount;
RawBuf[RawCount].VAL = last;
RawCount++;
len += runcount;
}
last=this;
runcount=1;
}
}
/* stragglers */
if(runcount > 0) {
RawBuf[RawCount].CNT = runcount;
RawBuf[RawCount].VAL = last;
RawCount++;
len+= runcount;
}
#ifdef DEBUG
if (len != inlen) printf("Wrong length %d\n",len);
#endif
/* ********************************************* */
/* ====== Encode the List for this line ======== */
/* ********************************************* */
/* Process a list of count,value pairs */
PackedCount = SingleCount = 0;
for (idx=0;idx<RawCount;) {
runcount = RawBuf[idx].CNT;
if (runcount == 0) {
/* list nodes should never have a zero count */
idx++;
continue;
}
if (runcount == 1) {
/* push singleton nodes onto the stack until we hit a repeat node */
RawBuf[SingleCount].Singleton = RawBuf[idx].VAL;
SingleCount++;
idx++;
continue;
}
/* if we have hit a repeat list node... */
/* before encoding the repeat, pop singleton nodes (if any) off the stack and encode 'em first */
singlerun = 0;
while (SingleCount > 0) {
if (SingleCount < 65) {
msk = (uchar)(SingleCount - 1);
PackedBuf[PackedCount] = msk;
PackedCount++;
for (i=0;i<SingleCount;i++) {
PackedBuf[PackedCount] = RawBuf[singlerun].Singleton;
singlerun++;
PackedCount++;
}
SingleCount = 0;
break;
}
PackedBuf[PackedCount] = (uchar)63;
PackedCount++;
for (i=0;i<64;i++) {
PackedBuf[PackedCount] = RawBuf[singlerun].Singleton;
singlerun++;
PackedCount++;
}
SingleCount -= 64;
}
#ifdef DEBUG
if (SingleCount != 0) printf("SingleCount error = %d\n", SingleCount);
#endif
/* Mask 0xc0 - use full quads to reduce repeats */
while (runcount > 256) {
PackedBuf[PackedCount] = 0xff; /* 63 | 0xc0 */
PackedCount++;
PackedBuf[PackedCount] = RawBuf[idx].VAL;
PackedCount++;
runcount-= 256; /* decrement runcount until 256 or below */
}
if (runcount < 1) {
idx++;
continue;
}
/* Mask 0x40 for repeats of 1 to 64 */
if (runcount < 65) {
msk = (uchar)(runcount - 1);
PackedBuf[PackedCount] = (uchar) (msk | 0x40);
PackedCount++;
PackedBuf[PackedCount] = RawBuf[idx].VAL;
PackedCount++;
idx++;
continue;
}
/* Mask 0xc0 - use quads for repeats of 65 to 256 */
repeats = runcount / 4;
msk = (uchar) (repeats - 1);
PackedBuf[PackedCount] = (uchar) (msk | 0xc0);
PackedCount++;
PackedBuf[PackedCount] = RawBuf[idx].VAL;
PackedCount++;
runcount -= (repeats * 4);
if (runcount < 1) {
idx++;
continue;
}
/* Mask 0x40 for stragglers - repeats of 1 to 3 */
msk = (uchar)(runcount - 1);
PackedBuf[PackedCount] = (uchar) (msk | 0x40);
PackedCount++;
PackedBuf[PackedCount] = RawBuf[idx].VAL;
PackedCount++;
/* on to the next list member */
idx++;
}
/* clear stragglers */
singlerun = 0;
while (SingleCount > 0) {
/* pop any remaining singleton nodes off the stack and finish-up */
if (SingleCount < 65) {
msk = (uchar)(SingleCount - 1);
PackedBuf[PackedCount] = msk;
PackedCount++;
for (i=0;i<SingleCount;i++) {
PackedBuf[PackedCount] = RawBuf[singlerun].Singleton;
singlerun++;
PackedCount++;
}
SingleCount = 0;
break;
}
PackedBuf[PackedCount] = (uchar)63;
PackedCount++;
for (i=0;i<64;i++) {
PackedBuf[PackedCount] = RawBuf[singlerun].Singleton;
singlerun++;
PackedCount++;
}
SingleCount -= 64;
}
#ifdef DEBUG
if (SingleCount != 0) printf("SingleCount error = %d\n", SingleCount);
goto SKIP;
#endif
/* a raw line of singletons incurs 1 count byte for every 64 data bytes */
/* a line of 160 singletons is expanded by 3 count bytes to 163 bytes */
/* if the packed line expands beyond this length then simply encode it as singletons */
/* the following calculates the most efficient encoding for a line of singletons */
maxpack = (ushort)(inlen/64);
if ((inlen % 64) != 0) maxpack++;
maxpack+=inlen;
/* if the length of a packed line goes beyond this, run individual bytes */
if (PackedCount > maxpack) {
PackedCount = RawCount = 0;
maxpack = (ushort) (inlen / 64);
for (idx = 0;idx < maxpack; idx++) {
/* runs of 64 individual bytes */
PackedBuf[PackedCount] = 63; PackedCount++;
for (jdx = 0; jdx < 64; jdx++, RawCount++, PackedCount++) {
PackedBuf[PackedCount] = inbuff[RawCount];
}
}
inlen -= (maxpack*64);
if (inlen > 0) {
/* stragglers if any */
PackedBuf[PackedCount] = (uchar)(inlen-1); PackedCount++;
for (jdx = 0; jdx < inlen; jdx++, RawCount++, PackedCount++) {
PackedBuf[PackedCount] = inbuff[RawCount];
}
}
}
SKIP:;
/* return length of packed buffer */
return PackedCount;
}
/* intel uses little endian */
/* motorola uses big endian */
/* for debugging */
ulong Intel32(ulong val)
{
uchar buf[4];
ulong *ptr;
buf[0] = (uchar) val &0xff; val >>=8;
buf[1] = (uchar) val &0xff; val >>=8;
buf[2] = (uchar) val &0xff; val >>=8;
buf[3] = (uchar) val &0xff;
ptr = (ulong *)&buf[0];
val = ptr[0];
return val;
}
ushort Intel16(ushort val)
{
uchar buf[12];
ushort *ptr;
/* msb in largest address */
buf[0] = (uchar) val &0xff; val >>=8;
buf[1] = (uchar) val &0xff;
ptr = (ushort *)&buf[0];
val = ptr[0];
return val;
}
/* for conversion */
ulong Motorola32(ulong val)
{
uchar buf[4];
ulong *ptr;
/*
Base Address+0 Byte3
Base Address+1 Byte2
Base Address+2 Byte1
Base Address+3 Byte0
*/
buf[0] = (uchar) (val % 256); val = val/256;
buf[1] = (uchar) (val % 256); val = val/256;
buf[2] = (uchar) (val % 256); val = val/256;
buf[3] = (uchar) (val % 256);
/* cast back to unsigned long data type */
ptr = (ulong *)&buf[0];
val = ptr[0];
return val;
}
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;
}
/* frees memory allocated by PntAlloc (below) */
void PntFree()
{
if (output_pnt == 0) return;
free(RawBuf); /* packbytes list */
switch(output_format) {
case BROOKS_FMT:
free(MultiPal);
free(brooksMain);
break;
case PIC_FMT:
default:
free(picMain);
break;
}
}
/* allocates memory and sets-up defaults for the type of
PNT file output that will be produced */
sshort PntAlloc()
{
sshort status = INVALID;
uchar buf[10];
output_pnt = 0;
if(suppress_pnt == 1) return INVALID;
if (NULL == (RawBuf = (RAWLIST *) malloc(sizeof(RAWLIST)*RAW_MAX))) {
puts("Not Enough Memory for PackBytes... PNT Output Disabled.");
return status;
}
switch(output_format) {
case BROOKS_FMT:
MultiPal = (MULTIPAL *) malloc(sizeof(MULTIPAL));
if (NULL == MultiPal)
{
free(RawBuf);
puts("Not Enough Memory for Multipalette... PNT Output Disabled.");
break;
}
brooksMain = (PNTBROOKS *) malloc(sizeof(PNTBROOKS));
if (NULL == brooksMain) {
puts("Not Enough Memory for Main... PNT Output Disabled.");
free(MultiPal);
free(RawBuf);
break;
}
/* set defaults and invariants */
memset(&MultiPal[0].Length,0,sizeof(MULTIPAL));
MultiPal[0].Length = Motorola32((ulong)sizeof(MULTIPAL));
strcpy(&buf[1],"MULTIPAL"); buf[0] = 8;
memcpy(&MultiPal[0].Kind[0],&buf[0],9);
MultiPal[0].NumColorTables = Motorola16((ushort)200);
/* The 200 Palettes will be assigned when the Brooks Palettes are built
so nothing further to do except write to disk when done processing */
memset(&brooksMain[0].Length,0,sizeof(PNTBROOKS));
strcpy(&buf[1],"MAIN"); buf[0] = 4;
memcpy(&brooksMain[0].Kind[0],&buf[0],5);
brooksMain[0].PixelsPerScanline = Motorola16((ushort)320);
/* as dumb as it may sound, the CiderPress file viewer needs one color table
to be included in the MAIN block even when a Brooks MULTIPAL is used. */
brooksMain[0].NumColorTables = Motorola16((ushort)1);
brooksMain[0].NumScanLines = Motorola16((ushort)200);
/* the scbs will be setup in the scan line directory
with the packed lengths when the scanlines are packed... */
output_pnt = 1;
status = SUCCESS;
break;
case PIC_FMT:
default:
picMain = (PNTPIC *) malloc(sizeof(PNTPIC));
if (NULL == picMain) {
puts("Not Enough Memory for Main... PNT Output Disabled.");
free(RawBuf);
break;
}
/* set defaults and invariants */
memset(&picMain[0].Length,0,sizeof(PNTPIC));
strcpy(&buf[1],"MAIN"); buf[0] = 4;
memcpy(&picMain[0].Kind[0],&buf[0],5);
picMain[0].PixelsPerScanline = Motorola16((ushort)320);
picMain[0].NumColorTables = Motorola16((ushort)16);
picMain[0].NumScanLines = Motorola16((ushort)200);
output_pnt = 1;
status = SUCCESS;
break;
}
return status;
}
sshort WritePnt(FILE *fp)
{
sshort status = SUCCESS;
ulong blocklen = 0L;
ushort len,y;
if (output_pnt == 0) return status;
/* write the header to disk as-is, then rewind, write it again
after updating it when we are done packing scanlines etc. */
if (output_format == BROOKS_FMT) {
fwrite((char*)&brooksMain[0].Length,sizeof(PNTBROOKS),1,fp);
blocklen = (ulong)sizeof(PNTBROOKS);
for (y=0;y<200;y++) {
len = PackBytes((char *)&shrline[y][0],160);
fwrite((char *)&PackedBuf[0],sizeof(uchar) * len,1,fp);
brooksMain[0].ScanLineDirectory[y][0] = Motorola16(len);
brooksMain[0].ScanLineDirectory[y][1] = Motorola16(y);
blocklen += len;
}
fwrite((char*)&MultiPal[0].Length,sizeof(MULTIPAL),1,fp);
rewind(fp);
brooksMain[0].Length = Motorola32(blocklen);
fwrite(&brooksMain[0].Length,sizeof(PNTBROOKS),1,fp);
#ifdef DEBUG
printf("APF File Length = %ld\n",(ulong)Intel32(brooksMain[0].Length) + sizeof(MULTIPAL));
printf("MasterMode = %d\n",Intel16(brooksMain[0].MasterMode));
printf("Pixels per Line = %d\n",Intel16(brooksMain[0].PixelsPerScanline));
printf("Color Tables = %d\n",Intel16(brooksMain[0].NumColorTables));
printf("Number of Lines = %d\n",Intel16(brooksMain[0].NumScanLines));
printf("MAIN Information Block Length = %ld\n", blocklen);
printf("MULTIPALETTE Information Block Length = %ld\n", (ulong) sizeof(MULTIPAL));
#endif
}
else {
/* copy palette from picfile */
memcpy(&picMain[0].ColorTable[0][0],&shr.pal[0][0],512);
fwrite((char*)&picMain[0].Length,sizeof(PNTPIC),1,fp);
blocklen = (ulong)sizeof(PNTPIC);
for (y=0;y<200;y++) {
len = PackBytes((char *)&shrline[y][0],160);
fwrite((char *)&PackedBuf[0],sizeof(uchar) * len,1,fp);
picMain[0].ScanLineDirectory[y][0] = Motorola16(len);
picMain[0].ScanLineDirectory[y][1] = Motorola16((ushort)shr.scb[y]);
blocklen += len;
}
rewind(fp);
picMain[0].Length = Motorola32(blocklen);
fwrite(&picMain[0].Length,sizeof(PNTPIC),1,fp);
#ifdef DEBUG
printf("APF File Length = %ld\n",Intel32(picMain[0].Length ));
printf("MasterMode = %d\n",Intel16(picMain[0].MasterMode));
printf("Pixels per Line = %d\n",Intel16(picMain[0].PixelsPerScanline));
printf("Color Tables = %d\n",Intel16(picMain[0].NumColorTables));
printf("Number of Lines = %d\n",Intel16(picMain[0].NumScanLines));
printf("MAIN Information Block Length = %ld\n", blocklen);
#endif
}
return status;
}
/* ***************************************************************** */
/* ========================== raw output routines ================== */
/* ***************************************************************** */
/* Brooks Palettes are stored sequentially. There are no scb's */
void BuildBrooksPaletteLine(short y, sshort midx)
{
uchar g, *pal;
sshort i,j;
/* Brooks Palette Lines are in reverse order
the color value for color 15 is stored first.*/
for (i = 0,j=30; i < 16;i++,j-=2) {
g = cmap[midx][i][GREEN] << 4;
shr.pal[y][j] = g | cmap[midx][i][BLUE];
shr.pal[y][j+1] = cmap[midx][i][RED];
}
if (output_pnt == 0) return;
/* Build the Palette for the APF when we build the Brooks Palette */
pal = (uchar *)&MultiPal[0].ColorTableArray[y][0];
/* According to Apple's Filetype Notes mode3200 palettes
are in the same order as any other palette: 0..16 */
for (i = 0,j=0; i < 16;i++,j+=2) {
g = cmap[midx][i][GREEN] << 4;
pal[j] = g | cmap[midx][i][BLUE];
pal[j+1] = cmap[midx][i][RED];
}
}
/* this builds the shr PIC file palette as well as
setting-up the colormap */
void SetColorIndex(sshort midx)
{
sshort i,j,k;
uchar r,g,b;
for (i=0,j=0; i< 16; i++) {
/* read BGR triples - 48 bytes */
b = (uchar) (bmpline[j] >> 4); j++;
g = (uchar) (bmpline[j] >> 4); j++;
r = (uchar) (bmpline[j] >> 4); j++;
/* encode RGB 4-bit color into cmap */
cmap[midx][i][RED] = r;
cmap[midx][i][GREEN] = g;
cmap[midx][i][BLUE] = b;
/* Brooks palettes are built later */
if (output_format == BROOKS_FMT) continue;
/* offset from char to short */
k = i*2;
/* encode $0RGB motorola unsigned short (LSB, MSB) for pic file into 2 bytes */
g = (uchar) (g << 4);
shr.pal[midx][k] = (uchar) (g | b);
shr.pal[midx][k+1] = r;
}
}
/* returns -1 if color not found in 16 color palette */
/* unfortunely for us a sequential search is required */
sshort GetColorIndex(uchar r, uchar g, uchar b, sshort midx)
{
sshort i;
for (i=0;i<16;i++) {
if (cmap[midx][i][RED] == r && cmap[midx][i][GREEN] == g && cmap[midx][i][BLUE] == b) return i;
}
return INVALID;
}
/* this sets the scb for the shrfile */
/* it also converts the bmp line from 8 bit to 4 bit */
/* and puts it into the shr file buffer */
sshort ConvertLine(sshort y)
{
sshort x, i, j, midx = -1;
uchar r,g,b;
/* convert RGB triples to 4 bit depth */
for (i = 0; i < 960; i++) {
bmpline[i] = (uchar) (bmpline[i] >> 4);
}
/* go through every palette until we hit one that works for the whole line */
/* try to build the line as we go */
for (i = 0; i < numpalettes; i++) {
/* convert to a line index in the range of 0-16 */
memset(svgaline,0,320);
for (x = 0, j=0; x < 320; x++) {
b = bmpline[j]; j++;
g = bmpline[j]; j++;
r = bmpline[j]; j++;
/* just bail immediately if the palette doesn't match
and try the next palette */
midx = GetColorIndex(r,g,b,i);
if (midx == INVALID) break;
svgaline[x] = (uchar)midx;
}
if (midx == INVALID) continue;
midx = i;
break;
}
/* we only use the scb array for the PIC structure but.
when we add support for PNT files we will also use
something similar... the output format logic will be
extended at that time.
*/
if (midx == -1) {
if (output_format == BROOKS_FMT) return INVALID;
shr.scb[y] = 0;
return INVALID;
}
if (output_format == BROOKS_FMT) BuildBrooksPaletteLine(y,midx);
else shr.scb[y] = midx;
/* create 4 bit line - pixel order nibbles */
for (i = 0,j=1,x = 0; x < 160; x++,i+=2,j+=2) {
shrline[y][x] = (uchar) ((svgaline[i] << 4) | svgaline[j]);
}
return SUCCESS;
}
sshort Convert()
{
FILE *fp, *fpshr, *fpapf;
sshort status = INVALID, i, y, bmpversion;
uchar ch;
if((fp=fopen(bmpfile,"rb"))==NULL) {
printf("Error Opening %s!\n",bmpfile);
return status;
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
/* https://msdn.microsoft.com/en-us/library/windows/desktop/dd183386%28v=vs.85%29.aspx */
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
}
else {
/* perhaps some unknown version that I don't support */
/* whether I support the other versions properly is also an interesting question */
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion == 0) {
fclose(fp);
printf("BMP version of %s not recognized!\n",bmpfile);
return status;
}
if (bmi.biCompression==BI_RGB &&
bfi.bfType[0] == 'B' && bfi.bfType[1] == 'M' &&
bmi.biPlanes==1 && bmi.biBitCount == 24) {
if (bmi.biWidth == 320 && bmi.biHeight == 200) status = SUCCESS;
}
if (status == INVALID) {
printf("%s is in the wrong format!\n",bmpfile);
fclose(fp);
return status;
}
/* seek past extraneous info in header if any */
fseek(fp,bfi.bfOffBits,SEEK_SET);
for (i = 0,y=199; i< 200; i++, y--) {
fread((char *)&bmpline[0],1,960,fp);
if (ConvertLine(y) == INVALID) {
printf("No palette for line %d\n",y);
status = INVALID;
}
}
fclose(fp);
/* insert RLE routines here */
/* the buffers are ready to be written by the time it gets to this point */
if((fpshr=fopen(shrfile,"wb"))==NULL) {
printf("Error Opening %s!\n",shrfile);
return INVALID;
}
if (output_pnt != 0) {
if(NULL == (fpapf=fopen(pntfile,"wb+"))) {
printf("Error Opening %s!\n",pntfile);
PntFree();
output_pnt = 0;
}
}
switch(output_format) {
case BROOKS_FMT:
fwrite((char *)&shrline[0][0],1,32000,fpshr);
fwrite((char *)&shr.pal[0][0],1,sizeof(uchar) * 6400,fpshr);
break;
case PIC_FMT:
default:
fwrite((char *)&shrline[0][0],1,32000,fpshr);
fwrite((char *)&shr.scb[0],sizeof(uchar) * 768,1,fpshr);
break;
}
fclose(fpshr);
if (suppress_pic == 1) remove(shrfile);
else printf("Created %s!\n",shrfile);
if (output_pnt != 0) {
if (WritePnt(fpapf) == SUCCESS) {
printf("Created %s!\n",pntfile);
}
fclose(fpapf);
}
return SUCCESS;
}
sshort ReadColorMap()
{
FILE *fp;
sshort status = INVALID,i,x,y,localpalettes, bmpversion;
/* open the colormap */
if((fp=fopen(cmapfile,"rb"))==NULL) {
printf("Error Opening %s!\n",cmapfile);
return status;
}
/* read the header stuff into the appropriate structures */
fread((char *)&bfi.bfType,
sizeof(BITMAPFILEHEADER),1,fp);
fread((char *)&bmi.biSize,
sizeof(BITMAPINFOHEADER),1,fp);
if (bmi.biSize != sizeof(BITMAPINFOHEADER)) {
if (bmi.biSize == sizeof(BITMAPINFOHEADERV4)) {
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV4),1,fp);
bmpversion = 4;
}
else if (bmi.biSize == sizeof(BITMAPINFOHEADERV5)) {
/* https://msdn.microsoft.com/en-us/library/windows/desktop/dd183386%28v=vs.85%29.aspx */
memset(&bmiV5.biSize,0,sizeof(BITMAPINFOHEADERV5));
fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET);
fread((char *)&bmiV5.biSize,sizeof(BITMAPINFOHEADERV5),1,fp);
bmpversion = 5;
/*
Profile data refers to either the profile file name (linked profile)
or the actual profile bits (embedded profile).
The file format places the profile data at the end of the file.
The profile data is placed just after the color table (if present).
*/
}
else {
/* perhaps some unknown version that I don't support */
/* whether I support the other versions properly is also an interesting question */
bmpversion = 0;
}
}
else {
bmpversion = 3;
}
if (bmpversion == 0) {
fclose(fp);
printf("BMP version of %s not recognized!\n",bmpfile);
return status;
}
if (bmi.biCompression==BI_RGB &&
bfi.bfType[0] == 'B' && bfi.bfType[1] == 'M' &&
bmi.biPlanes==1 && bmi.biBitCount == 24 && bmi.biWidth == 16) {
if (bmi.biHeight == 1) {
output_format = PIC_FMT;
numpalettes = 16;
localpalettes = 1;
status = SUCCESS;
}
if (bmi.biHeight == 16) {
output_format = PIC_FMT;
numpalettes = localpalettes = 16;
status = SUCCESS;
}
else if (bmi.biHeight == 200) {
output_format = BROOKS_FMT;
/* this will likely be adjusted down
when the palette is read */
numpalettes = 200;
strcpy(shrfile,brooksfile);
status = SUCCESS;
}
}
if (status == INVALID) {
printf("%s is in the wrong format!\n",cmapfile);
fclose(fp);
return status;
}
PntAlloc();
/* clear the memory for the shr file buffers */
memset((char *)&shrline[0][0],0,32000);
memset((char *)&shr.scb[0],0,sizeof(PICFILE));
/* seek past extraneous info in header if any */
fseek(fp,bfi.bfOffBits,SEEK_SET);
/* set palettes for the SHR file */
switch(output_format) {
case BROOKS_FMT:
numpalettes = i = 0;
for (y=0;y<200;y++) {
fread((char *)&bmpline[0],1,48,fp);
/* skip blank lines at the bottom of the image */
/* a black line with an all black palette will
end-up as a black line with an all black palette
anwyay... obviously */
for (x = 0; x < 48; x++) {
if (bmpline[x] != 0) {
SetColorIndex(i); i++;
numpalettes++;
break;
}
}
}
/* printf("Number of palettes is %d\n",numpalettes); */
break;
case PIC_FMT:
default:
for (i=0;i<16;i++) {
if (i < localpalettes) fread((char *)&bmpline[0],1,48,fp);
SetColorIndex(i);
}
}
fclose(fp);
return status;
}
int main(int argc, char **argv)
{
sshort idx, jdx=999, status = 0;
uchar fname[256], ch, ch2, *wordptr;
/* defaults */
suppress_pnt = 1;
suppress_pic = 0;
no_tags = 1;
/* getopts */
if (argc > 2) {
for (idx = 2; idx < argc; idx++) {
wordptr = (uchar *)&argv[idx][0];
ch2 = ch = toupper(wordptr[0]);
if (ch == '-') {
wordptr = (uchar *)&argv[idx][1];
ch2 = ch = toupper(wordptr[0]);
}
if (ch != 0) ch2 = toupper(wordptr[1]);
if (ch == 'A' || ch2 == 'A') {
suppress_pnt = 0; suppress_pic = 1;
}
if (ch == 'T' || ch2 == 'T') {
no_tags = 0;
}
}
if (suppress_pnt == 0 || no_tags == 0) argc = 2;
}
if (argc == 2) {
strcpy(fname, argv[1]);
/* check fullname _proc.bmp */
for (idx = 0; fname[idx] != (uchar)0; idx++) {
ch = fname[idx];
if (ch != '_') continue;
ch = toupper(fname[idx+1]);
if (ch != 'P') continue;
ch = toupper(fname[idx+2]);
if (ch != 'R') continue;
ch = toupper(fname[idx+3]);
if (ch != 'O') continue;
ch = toupper(fname[idx+4]);
if (ch != 'C') continue;
ch = toupper(fname[idx+5]);
if (ch != '.') continue;
ch = toupper(fname[idx+6]);
if (ch != 'B') continue;
ch = toupper(fname[idx+7]);
if (ch != 'M') continue;
ch = toupper(fname[idx+8]);
if (ch != 'P') continue;
fname[idx] = (uchar)0;
break;
}
if (jdx == 999) {
/* check basename */
for (idx = 0; fname[idx] != (uchar)0; idx++) {
ch = fname[idx];
if (ch != '.') continue;
ch = toupper(fname[idx+1]);
if (ch != 'B' && ch != 'D') continue;
ch = toupper(fname[idx+2]);
if (ch != 'M' && ch != 'I') continue;
ch = toupper(fname[idx+3]);
if (ch != 'P' && ch != 'B') continue;
jdx = idx;
}
}
}
#ifdef MSDOS
if (jdx != 999) fname[jdx] = (uchar)0;
#else
if (jdx !=999) argc = 1;
#endif
if(argc != 2) {
puts("MagickToShr (C) Copyright Jonas Grönhagen and Bill Buckels 2014.");
puts("All Rights Reserved.");
puts("Usage is: m2s BaseName -Options");
puts("BaseName: _proc.bmp and _palette.bmp (file pairs)");
puts(" \"m2s Woz\" opens \"Woz_proc.bmp\" and \"Woz_palette.bmp\"...");
puts(" For MS-DOS, \"M2S16 WOZ\" opens \"WOZ.BMP\" and \"WOZ.DIB!\"");
puts(" 16 palettes for mode320 output and 200 for mode3200!");
puts("Options: -A = Alternate PNT file output (run length encoded).");
puts(" Default: raw SHR (mode320) or SH3 (mode3200).");
puts(" -T = Use CiderPress Attribute Preservation Tags.");
puts(" Default: No Tags! (unadorned file extensions)");
puts(" Does not apply to M2S16.EXE (MS-DOS binary).");
puts(" Options may be combined: \"-ta\" or \"-at\"");
puts(" Options are Case Insensitive - Switchar \"-\" is Optional.");
return(1);
}
#ifdef MSDOS
/* MS-DOS uses short file names - 8.3 */
sprintf(bmpfile,"%s.BMP",fname);
sprintf(cmapfile,"%s.DIB",fname);
sprintf(shrfile,"%s.SHR",fname);
sprintf(brooksfile,"%s.SH3",fname);
sprintf(pntfile,"%s.PNT",fname);
#else
/* if using long file names add
ciderpress file attribute preservation tags
to output files */
sprintf(bmpfile,"%s_proc.bmp",fname);
sprintf(cmapfile,"%s_palette.bmp",fname);
if (no_tags == 1) {
sprintf(shrfile,"%s.SHR",fname);
sprintf(brooksfile,"%s.SH3",fname);
sprintf(pntfile,"%s.PNT",fname);
}
else {
sprintf(shrfile,"%s.SHR#C10000",fname);
sprintf(brooksfile,"%s.SH3#C10002",fname);
sprintf(pntfile,"%s.PNT#C00002",fname);
}
#endif
for (;;) {
if ((status = ReadColorMap()) == INVALID) break;
status = Convert();
break;
}
PntFree();
if (status == INVALID) return (1);
return SUCCESS;
}