/*--lpfile.c---------------------------------------------------------- * * Large Page File. This is a format for dividing a file into 64 KB * "large pages" ("lp"s) that can be stored out-of-sequence in the file. * That is, an lp can be logically inserted into the file, without * having to move all the following data out of the way. * * Each lp holds one or more "records". A record is of any length from * 0 to almost 64 KB. The records in the file are sequentially numbered * starting with 0. The records in each lp are a sub-sequence; e.g., * "3 records starting with #6" would be #6,#7,#8. * * A DeluxePaint Animation "ANM" ("Anim") file is built on this mechanism, * so that as frames change in size, they can be maintained with a minimum * of extra file i/o, yet without loss of playback performance. * In addition, there is an optional special record which is the delta from * the last frame to the first frame, for smooth playback of looping anims. */ #include "main.h" #include "anim.h" #include #include #include /* chsize */ #define local static extern BOOL animDisk, outputLargePageFile, editting, cycling; extern WORD curFrame; extern UWORD animFrames, frameDelay, startFrame, endFrame, waitVerts, gapLimitAddr; extern Box screenBox, animBox, animMove; extern BITMAP prevFrame, hidbm; extern LONG curpal[MAX_COLORS]; extern void AnimVBlank(); #if U_COLORCYCLE extern Range cycles[MAXNCYCS]; #endif /* --------------- forward declarations --------------- */ UWORD DAllocBiggest(UWORD nBytes, UWORD * allocSizeP); void ReadLargePage(UWORD nLp); WORD WriteCurLp(void); void ReadCurLp(void); UWORD FindLargePageForRecord(register UWORD nRecord); void WriteAnimFrameAndReadNext(void); /* --------------- --------------- */ WORD lpFile = NULL; /* Keep open so can access again. */ char lpfSuffix[]= "ANM"; local Range cycles[MAXNCYCS]; /* Disk i/o sets after filling buffer, screen i/o clears after using. */ UWORD nBuffers = 2; UWORD useBuffers = 2; extern char animName[]; local UWORD saveBitmapSeg = NULL; #define prevFrameSeg BSEG(&prevFrame) #define hidbmSeg BSEG(&hidbm) /* -------------------------------------------------------------------------- * --------------- Description of "current record". --------------- */ typedef struct { UWORD curRecord; /* record being accessed w/i lp. NOTE: ABSOLUTE rec * #, not relative to lp baseRecord. */ UWORD recAddr; /* Address of first byte of record, w/i lp buffer. */ UWORD recBytes; /* # bytes in record. */ UWORD bodyAddr; /* Address of Body of current frame. */ UWORD bodyBytes; /* # bytes in Bitmap record's Body. */ UWORD extraBytes; /* # bytes in Bitmap record prior to Body. * ID+FLAGS & ExtraData, incl. count & pad. * Used to skip over ExtraData. */ UWORD extraDataCnt; /* # bytes of contents of ExtraData. */ } BmRecX; /* Access to a Bitmap record. */ local BmRecX curRecX; /* Description of "current record". */ #define CUR_REC curRecX.curRecord /* --- These should be "CUR_REC_...", but the names get unreasonably long. */ #define REC_ADDR curRecX.recAddr #define REC_BYTES curRecX.recBytes #define BODY_ADDR curRecX.bodyAddr #define BODY_BYTES curRecX.bodyBytes #define EXTRA_BYTES curRecX.extraBytes #define EXTRA_DATA_CNT curRecX.extraDataCnt #define DEFAULT_BITMAP_ID_FLAGS ((0<<8) + 'B') /* First byte is 'B'. On 8086 that's low byte. */ /* ----- Functions to manipulate "current record" w/i CUR_LP w/i lp buffer. */ /* TBD: we're not actually using these, but doing the logic where needed. */ /* Shift data in buffer so there is room for "n" extraBytes, and set * EXTRA_BYTES. NOTE: this is NOT "extraDataCount" -- it includes * ID+FLAGS & extraDataCount in its size. */ local void CurRec_SetExtraBytes(UWORD n); /* Shift data in buffer so there is room for "n" bodyBytes, and set * BODY_BYTES. */ local void CurRec_SetBodyBytes(UWORD n); /* Find & set CUR_REC. NOTE: "n" is absolute, not relative to baseRecord. * This will put new lp in lp buffer if necessary. */ local void CurRec_Set(UWORD n); UWORD NRecord2NFrame(WORD nRecord); /* -------------------------------------------------------------------------- * --------------- buffering large pages --------------- */ local UWORD preBufferSeg = NULL; local UWORD preFrameAddr0 = 0; local UWORD nBuf, nNextBuf = 0, nNextPreBuf = 0, nPreLp = 0, nPreRecord; local BOOL lastRecordInBuf, curLpIsDirty = FALSE; /* "lastRecordInBuf is not used during editing, so is not part of * curRecX. */ #define LAST_RECORD_IN_BUF (LP_BASE_REC + LP_N_RECS - 1) /* -------------------- LargePage -------------------- */ #define MAX_LARGE_PAGE 256 typedef struct { UWORD baseRecord; /* First record in lp. */ UWORD nRecords; /* # records in lp. */ /* bit 15 of "nRecords" == "has continuation from previous lp". * bit 14 of "nRecords" == "final record continues on next lp". * File format thus permits 16383 records/lp. * Only MAX_RECORDS_PER_LP is currently supported by the code, * to minimize table size in DGROUP in DP Animation. * TBD: we're not handling non-zero bits 14&15. */ UWORD nBytes; /* # bytes of contents, excluding header. * Gap of "64KB - nBytesUsed - headerSize" at end of lp. * headerSize == "8 + 2*nRecords". * Header is followed by record #baseRecord. */ } LP_DESCRIPTOR; local LP_DESCRIPTOR lpfTable[MAX_LARGE_PAGE]; #define LARGE_PAGE_SIZE 0x10000L #define MAX_RECORDS_PER_LP 256 /* TBD: why restrict, other than RAM usage? */ #define LP_TABLE_ELEMENT UWORD /* # bytes for each record of curLp. */ /* NOTE: lp's header must be accounted for separately.*/ local LP_TABLE_ELEMENT lpTable[MAX_RECORDS_PER_LP]; local UWORD lpTableOffset = 0; /* So can pretend lpTable starts at some * element other than 0, to fake out * WriteCurLp. */ typedef struct { LP_DESCRIPTOR x; /* Duplicate of lpfTable[CUR_LP]. NOT relied on, * just here to help scavenge damaged files. * "baseRecord" NOT kept up-to-date, because * LP.AddRecord or DeleteRecord changes * "baseRecord" of ALL following lps -- would be * very slow to add one record near the start of a * huge file! This field is accurate whenever the * lp is written, but is not relied on after that. * It still helps scavenging. Since "Optimize Anim" * re-writes every lp, this field is accurate if * Optimize after AddRecord/DeleteRecord. */ UWORD nByteContinuation; /* ==0. FUTURE: allows record from * previous lp to extend on to this lp, so * don't waste file space. */ } LPageHeaderFixed; /* These fields precede the array giving "REC_BYTES" per record. */ #define LP_HEADER_SIZE(nRecords) ( sizeof(LPageHeaderFixed) + \ (nRecords) * sizeof(LP_TABLE_ELEMENT) ) /* NOTE: if this changes, so must WriteLargePage0(). */ #define FIRST_RECORD_N 0 /* Records #d from 0. */ #define LAST_RECORD_N (totalNRecords-1) /* INCLUDES last-to-first delta if present! */ UWORD totalNRecords; /* Total # records in lpFile, including lastDelta. */ UWORD nLps; /* # large pages in lpFile. */ /* -------------------------------------------------------------------------- * --------------- Description of "current lp". --------------- * One lp at a time is present in a buffer of just under 64 KB. * The contents of this buffer are maintained in the same form as on disk -- * fixed header info, variable-length recordSizes table, data for records. */ extern UWORD lpSeg; /* Segment of lp buffer. */ typedef struct { UWORD nLp; /* lp's # w/i file */ UWORD recAddr0; /* First byte of first record w/i lp. (after * header). */ LP_DESCRIPTOR x; /* Describes lp's contents. */ } LpX; /* Access to an lp. */ local LpX curLpX; /* Description of "current lp". */ #define NO_LP ((UWORD)-1) #define CUR_LP curLpX.nLp /* --- These should be "CUR_LP_...", but the names get unreasonably long. */ #define LP_BASE_REC curLpX.x.baseRecord #define LP_N_RECS curLpX.x.nRecords #define LP_N_BYTES curLpX.x.nBytes #define LP_REC_ADDR0 curLpX.recAddr0 /* Location of first byte of frame data in lpBuffer. */ /* ASSUMES LP_REC_ADDR0 set for correct # records. */ #define LP_TOTAL_BYTES (LP_REC_ADDR0 + LP_N_BYTES) #define GAP_SIZE (MAX_LARGE_PAGE_SIZE - LP_TOTAL_BYTES) /* -------------------------------------------------------------------------- * -------------------- LargePageFile -------------------- * Lpf's Header size is 256 + 3 * 256 + 6 * maxLps. * When lpf created, maxLps must be chosen. If file needs more lps, * will have to move all data in file. By convention, set maxLps to * 256 initially, increase in multiples of 256 as needed. * FOR NOW: WON'T HANDLE FILE UNLESS SET TO 256. * 256 * 64 KB == 16 MB. */ local struct { ULONG id; /* ID for LargePageFile == MakeID('L','P','F',' '). */ UWORD maxLps; /* 256 FOR NOW. max # largePages allowed in this * file. */ UWORD nLps; /* # largePages currently in this file. 0..maxLps. * lps #d from 0. * NOTE: In RAM, we don't keep this field up to date. */ ULONG nRecords; /* # records currently in this file. 65535 is * current limit. Allowing one for last-to-first * delta, that restricts to 65534 frames of lo-res * animation. * Records #d from 0. NOTE: In RAM, we * don't keep this field up to date. */ UWORD maxRecsPerLp; /* 256 FOR NOW. # records permitted in an lp. * So reader knows table size to allocate. */ UWORD lpfTableOffset; /* 1280 FOR NOW. Absolute Seek position of * lpfTable. This allows content-specific * header to be variable-size in the * future. */ ULONG contentType; /* for ANIM == MakeID('A','N','I','M'). All * following info is specific to ANIM format. */ UWORD width; /* in pixels. */ UWORD height; /* in pixels. */ UBYTE variant; /* 0==ANIM. */ UBYTE version; /* 0== cycles stored for 18x/sec timer. * 1== cycles stored for 70x/sec timer. */ BOOL hasLastDelta; /* 1==Record(s) representing final frame are a * delta from last-to-first frame. nFrames == * (nRecords/recordsPerFrame)-hasLastDelta. */ BOOL lastDeltaValid; /* So needn't actually remove delta. * (When hasLastDelta==1) 0==there is a last * delta, but it hasn't been updated to * match the current first&last frames, so * it should be ignored. This is done so * don't have to physically remove the * delta from the file when the first frame * changes. */ UBYTE pixelType; /* ==0 for 256-color. */ UBYTE highestBBComp; /* 1 (RunSkipDump) FOR NOW. highest #d * "Record Bitmap.Body.compressionType". * So reader can determine whether it can * handle this Anim. */ UBYTE otherRecordsPerFrame; /* 0 FOR NOW. */ UBYTE bitmapRecordsPerFrame;/* ==1 for 320x200,256-color. */ UBYTE recordTypes[32]; /* Not yet implemented. */ ULONG nFrames; /* In case future version adds other records at end * of file, we still know how many frames. NOTE: * DOES include last-to-first delta when present. * NOTE: In RAM, we don't keep * this field up to date. */ UWORD framesPerSecond; /* # frames to play per second. */ UWORD pad2[29]; #if 0 /* read/write this directly, no need to waste DGROUP */ Range cycles[MAXNCYCS]; #endif } lpfHdr; /* total is 128 words == 256 bytes. */ #if CCYCLE70 #define LPF_VERSION 1 /* cycles stored for 70x/sec timer. */ #else #define LPF_VERSION 0 /* cycles stored for 18x/sec timer. */ #endif #if 0 UWORD checkFrames[32]; /* frame #s to checkpoint. each ==0 when not used. * # non-zero * recsPerFrame == # records used for * this purpose. Must subtract from nRecords to * determine how many frames are really in file. * TBD: Allocate fixed record #s for these? TBD: * force to be one record per lp? NOTE: since frame * #s, not record #s, seems okay to restrict to * 16-bit. If lo-res file gets > 64 K frames, * scale #s so can always span # frames. Ex: if * 64K..127K frames, these #s are "*2". Recommend: * # checkpoints proportional to # lps. Keep 1/8th * anim-size in checkpoints. Every time file * doubles in size, double # checks. */ #endif #define LARGE_PAGE_FILE_ID MakeID('L','P','F',' ') #define ANIM_CONTENTS_ID MakeID('A','N','I','M') #define LPF_HEADER_HEAD_SIZE_IN_FILE 256 /* First few fields at start of a Large Page File. */ #define SIZEOF_LPF_TABLE_IN_FILE (sizeof(LP_DESCRIPTOR) * MAX_LARGE_PAGE) /* FUTURE: will be permitted to vary! */ #define LPF_HEADER_SIZE_IN_FILE ( LPF_HEADER_HEAD_SIZE_IN_FILE + \ PALETTE_SIZE + SIZEOF_LPF_TABLE_IN_FILE ) /* Everything in Large Page File before the first lp. */ #define LPF_TABLE_OFFSET (LPF_HEADER_HEAD_SIZE_IN_FILE + PALETTE_SIZE) #define BBC_RUN_SKIP_DUMP 1 /* ----------------------------------------------------------------------- */ /* TBD: If you want better error handling, implement these. */ #define DiskFullChangesLostExit() exit(1) #define CantCreateAnimFileExit() exit(1) #define InvalidAnimFileExit() exit(1) #define TooManyPagesExit() exit(1) #define BadBitmapRecordExit() exit(1) #define UnrecognizedDeltaTypeExit() exit(1) #define LPageTooLargeMsg() #define LpfTooLargeMsg() #define TroubleWritingFileMsg() #define LpfNotHandledByVersion() #define TroubleReadingFileMsg() #define NotAnAnimFileMsg() /* ----------------------------------------------------------------------- */ /* --------------- GetFramesPerSecond --------------- */ UWORD GetFramesPerSecond() { return lpfHdr.framesPerSecond; } /* --------------- DeclareFramesPerSecond --------------- * Make sure all parties know what the (new) framesPerSecond play-rate is. */ DeclareFramesPerSecond(UWORD newFPS) { if (newFPS == 0) newFPS = FASTEST_RATE; lpfHdr.framesPerSecond = newFPS; } /* --------------- AppendSuffix --------------- */ char sDot[] = "."; void AppendSuffix(char *name, char *baseName, char *suffix) { strcpy(name, baseName); strcat(name, sDot); strcat(name, suffix); } /* --------------- OpenFile --------------- */ WORD OpenFile(char *baseName, char *suffix) { WORD file; char name[80]; AppendSuffix(name, baseName, suffix); file = opendos(name, O_RDWR); if (file == -1) file = NULL; /* No file open. */ return file; } /* --------------- DeleteFile --------------- */ void DeleteFile(char *baseName, char *suffix) { char name[80]; AppendSuffix(name, baseName, suffix); deletedos(name); } /* --------------- AnimFramesX --------------- * Set "animFrames" to new value. * This is a low-level routine. */ void AnimFramesX(WORD nFrames) { animFrames = nFrames; } /* --------------- OpenLpFile --------------- */ BOOL OpenLpFile() { register UWORD i; lpFile = OpenFile(animName, lpfSuffix); if (!lpFile) return FAIL; /* --- Read lpfHdr. */ CHECK_DOS(lseekdos(lpFile, (LONG) 0, SEEK_SET)); CHECK_DOS(readfull(lpFile, (char *)&lpfHdr, sizeof(lpfHdr))); /* --- read cycle info */ CHECK_DOS(readfull(lpFile, (char *)cycles, sizeof(cycles))); /* --- Read palette, after skipping future-expansion of lpfHdr. * ASSUME PALETTE_SIZE == 3*256. */ CHECK_DOS(lseekdos(lpFile, (LONG) LPF_HEADER_HEAD_SIZE_IN_FILE, SEEK_SET)); CHECK_DOS(readfull(lpFile, (char *)curpal, PALETTE_SIZE)); /* --- Read lpfTable, immediately after palette. */ CHECK_DOS(readfull(lpFile, (char *)lpfTable, SIZEOF_LPF_TABLE_IN_FILE)); /* --- Check that it is a DPaint-Animate Anim. */ if (lpfHdr.id != LARGE_PAGE_FILE_ID || lpfHdr.contentType != ANIM_CONTENTS_ID) { NotAnAnimFileMsg(); goto badFile; } if (lpfHdr.maxLps != MAX_LARGE_PAGE || lpfHdr.nRecords > (ULONG) MAX_RECORDS) { LpfTooLargeMsg(); goto badFile; } /* Note: maxLps may be too SMALL as well. */ if (lpfHdr.maxRecsPerLp > MAX_RECORDS_PER_LP) { LPageTooLargeMsg(); goto badFile; } /* --- Check that it is an Anim type we can handle. */ if (lpfHdr.lpfTableOffset != LPF_TABLE_OFFSET || lpfHdr.width != 320 || lpfHdr.height != 200 || lpfHdr.highestBBComp > BBC_RUN_SKIP_DUMP || lpfHdr.bitmapRecordsPerFrame != 1 || lpfHdr.otherRecordsPerFrame || lpfHdr.pixelType) { LpfNotHandledByVersion(); goto badFile; } for (i = 0; i < lpfHdr.nLps; i++) { if (lpfTable[i].nRecords > MAX_RECORDS_PER_LP || lpfTable[i].nBytes > MAX_LARGE_PAGE_SIZE) { LPageTooLargeMsg(); goto badFile; } /* FUTURE: hi 2 bits of nRecords indicate continuation. */ } nLps = lpfHdr.nLps; /* TBD: why not use fields in place? */ totalNRecords = (UWORD) lpfHdr.nRecords; AnimFramesX(lpfHdr.nFrames - lpfHdr.hasLastDelta); DeclareFramesPerSecond(lpfHdr.framesPerSecond); CUR_LP = NO_LP; /* None loaded. */ /* ReadLpfFrame(FIRST_FRAME_N); --- So have valid, for InsureBuffer...*/ return SUCCESS; doserr: TroubleReadingFileMsg(); badFile: closedos(lpFile); lpFile = NULL; InvalidAnimFileExit(); return FAIL; } /* --------------- SeekLargePage --------------- * Seek lp file to the specified large page. * Return FALSE on error. */ #define LP_FILE_OFFSET(nLp) \ (LONG)((LONG)(nLp) * LARGE_PAGE_SIZE + LPF_HEADER_SIZE_IN_FILE) local LONG SeekLargePage(UWORD nLp) { if (nLp > MAX_LARGE_PAGE) BugExit("LP1"); /* "Invalid lp #"); */ /* Seek to lp. File has header, then lps #d from 0. */ return (lseekdos(lpFile, LP_FILE_OFFSET(nLp), SEEK_SET)); } /* --------------- ReadLargePage00 --------------- * Also reads HEADER into lpBuf, for read-ahead. * When editing, don't want this. * IMPLICIT PARAMETERS: preFrameAddr0, lpfTable. */ local ReadLargePage00(UWORD lpFile, WORD nLp, UWORD seg) { UWORD nBytes; preFrameAddr0 = LP_HEADER_SIZE(lpfTable[nLp].nRecords); nBytes = preFrameAddr0 + lpfTable[nLp].nBytes; if (readdos(lpFile, seg, 0, nBytes) != nBytes) BugExit("LP2"); /* "ReadLargePage00: nBytes"); */ } /* --------------- MaybeWriteCurLp --------------- */ local void MaybeWriteCurLp(void) { if (curLpIsDirty) { WriteCurLp(); } } /* --------------- ReadLargePage --------------- */ void ReadLargePage(UWORD nLp) { MaybeWriteCurLp(); SeekLargePage(nLp); ReadLargePage00(lpFile, nLp, lpSeg); curLpX.x = lpfTable[nLp]; /* LP_BASE_REC, LP_N_RECS, LP_N_BYTES */ LP_REC_ADDR0 = LP_HEADER_SIZE(LP_N_RECS); /* far_movmem( lpSeg, 0, dataseg(), (UWORD)&LP_N_BYTES, 2 ); */ /* "baseRecord" field ignored. */ /* far_movmem( lpSeg, 4, dataseg(), (UWORD)&LP_N_RECS, 2 ); */ /* "nByteContinuation" field not used. */ far_movmem(lpSeg, sizeof(LPageHeaderFixed), dataseg(), (UWORD) lpTable, LP_N_RECS * sizeof(LP_TABLE_ELEMENT)); CUR_LP = nLp; } /* --------------- NFrame2NRecord --------------- * Given a frame #, compute the record # in the lpFile. * ASSUME has a last-delta, which is the last record. */ local UWORD NFrame2NRecord(WORD nFrame) { if (nFrame == LAST_DELTA_N) return FIRST_RECORD_N; return (nFrame - FIRST_FRAME_N + FIRST_RECORD_N); } /* --------------- NRecord2NFrame --------------- * Given an lpfile record #, compute the frame #. * ASSUME records are #d from 0. */ UWORD NRecord2NFrame(WORD nRecord) { return nRecord + FIRST_FRAME_N; } /* --------------- FindLargePageForRecord --------------- * Step through lp descriptions, looking for one that contains * requested record. * RETURN lp #. * NOTE: it is possible for lpfTable[nLp].baseRecord to be non-zero * even on an empty page. Fortunately, the "baseRecord <= nRecord < ..." * tests will always reject pages with nRecords==0. */ local UWORD FindLargePageForRecord(register UWORD nRecord) { register UWORD nLp, baseRecord; for (nLp = 0; nLp < nLps; nLp++) { baseRecord = lpfTable[nLp].baseRecord; if (baseRecord <= nRecord && nRecord < baseRecord + lpfTable[nLp].nRecords) { #if DEBUG_BUFS if (!editting) printf("Find lp:%d for rec:%d\n", nLp, nRecord); /* TESTING */ #endif return nLp; } } BugExit("LP6"); /* "Didn't find record in lpfTable"); */ } /* --------------- ReadLargePageForRecord --------------- * Find lp containing record, then read it. */ local void ReadLargePageForRecord(register UWORD nRecord) { ReadLargePage(FindLargePageForRecord(nRecord)); #if 0 if (FALSE) { PrepExit(); printf("First ReadLargePageForRecord (%d) on playback.", nRecord); PrintLPFState(); exit(1); } #endif /* 0 */ } /* --------------- ReadLpfFrame --------------- * Transfer to temp buffer the specified delta-frame. */ ReadLpfFrame(WORD nFrame) { register UWORD nRecord; nRecord = NFrame2NRecord(nFrame); if ((CUR_LP >= nLps) || /* No lp yet. */ (nRecord < LP_BASE_REC) || /* record not in CUR_LP. */ (nRecord >= LP_BASE_REC + LP_N_RECS)) { if (nRecord >= totalNRecords) BugExit("LP7"); /* "Impossible lp record."); */ ReadLargePageForRecord(nRecord); } /* --- Step through records in CUR_LP. */ REC_ADDR = LP_REC_ADDR0; CUR_REC = LP_BASE_REC; while (nRecord > CUR_REC) { REC_ADDR += lpTable[CUR_REC - LP_BASE_REC]; CUR_REC++; } REC_BYTES = lpTable[CUR_REC - LP_BASE_REC]; if (REC_BYTES) { UWORD far *ptr; UBYTE bitmapId, bitmapFlags; ptr = WORD_FAR_PTR(lpSeg, REC_ADDR); bitmapId = ((UBYTE far *) ptr)[0]; if (bitmapId != 'B') #if 0 /* TESTING */ { PrepExit(); printf("BadB frame:%d id:%x exN:%d recN:%d bodyN:%d, addrs:%x %x, rec data:%x %x %x\n", nFrame, bitmapId, EXTRA_BYTES, REC_BYTES, BODY_BYTES, REC_ADDR, BODY_ADDR, ptr[0], ptr[1], ptr[2]); exit(1); } #else BadBitmapRecordExit(); #endif bitmapFlags = ((UBYTE far *) ptr)[1]; EXTRA_DATA_CNT = ptr[1]; EXTRA_BYTES = (bitmapFlags ? 4 + EVEN_UP(EXTRA_DATA_CNT) : 2); } else { EXTRA_BYTES = 0; /* No recData, therefore no extra data. */ } BODY_ADDR = REC_ADDR + EXTRA_BYTES; BODY_BYTES = REC_BYTES - EXTRA_BYTES; lastRecordInBuf = (CUR_REC == LAST_RECORD_IN_BUF); } /* --------------- PlayDeltaFrame0 --------------- * Play the current delta info onto the specified frame bitmap. * IMPLICIT INPUT: curRecX, lpSeg. * NOTE: The 0 BODY_BYTES case isn't handled at this level. */ #define PRSD_ASM 1 /* Play RunSkipDump in assembly. */ local void PlayDeltaFrame0(UWORD bmSeg) { #if 1 UWORD bodyType, nBytes; #else register UWORD deltaAddr; UWORD picSeg; LONG frameType; #endif if (!BODY_BYTES) return; /* empty body == no change */ if (BODY_BYTES < 2) BugExit("LP8"); /* "Short body of bitmap record"); * TBD: return error code */ /* --- runSkipDump or uncompressed */ #define BODY_HEADER_LEN sizeof(UWORD) /* UWORD holding body type precedes body data. */ bodyType = *WORD_FAR_PTR(lpSeg, BODY_ADDR); nBytes = BODY_BYTES - 2; if (bodyType == BMBODY_UNCOMPRESSED) { /* --- Direct dump of screen. */ if (nBytes != N_BYTES_IN_BITMAP) BugExit("LP9"); /* "Uncompressed body has wrong # bytes"); */ far_movmem(lpSeg, BODY_ADDR + BODY_HEADER_LEN, bmSeg, 0, nBytes); } else if (bodyType == BMBODY_RUNSKIPDUMP) { /* --- RunSkipDump * encoding of screen. */ #if PRSD_ASM /* RunSkipDump in assembly. */ UWORD dstAddr; UBYTE far *srcP; #else /* RunSkipDump in C. */ register signed char cnt; UBYTE pixel; UWORD wordCnt; UBYTE far *srcP, far * dstP; #endif if (!nBytes) return; /* empty delta == no change */ #define BMBODY_STOP_CODE_LEN 3 #define BBSTOP0 LONG_OP #define BBSTOP1 0 #define BBSTOP2 0 srcP = BYTE_FAR_PTR(lpSeg, BODY_ADDR + BODY_BYTES - BMBODY_STOP_CODE_LEN); if (nBytes < BMBODY_STOP_CODE_LEN || srcP[0] != BBSTOP0 || srcP[1] != BBSTOP1 || srcP[2] != BBSTOP2) BugExit("LPA"); /* "RunSkipDump body missing stop code"); */ #if PRSD_ASM /* RunSkipDump in assembly. */ dstAddr = PlayRunSkipDump(nBytes, lpSeg, BODY_ADDR + BODY_HEADER_LEN, bmSeg, 0); if (dstAddr > N_BYTES_IN_BITMAP) #else /* RunSkipDump in C. */ /* --- Here is an algorithm to playback a 256-color Body, * onto a same-size screen with rows in sequence, * so don't have to test each sequence to see * if it is on a new row. * NOTE: Once it is working, you may remove all the references to * "nBytes", as the stop code is sufficient to end processing. */ srcP = BYTE_FAR_PTR(lpSeg, BODY_ADDR + BODY_HEADER_LEN); dstP = BYTE_FAR_PTR(bmSeg, 0); /* srcP points at first sequence in Body, * dstP points at pixel #0 on screen. */ nextOp: if (nBytes < 3) BugExit("LPB"); /* "RunSkipDump body elided stop code"); */ cnt = (signed char) *srcP++; if (cnt > 0) goto dump; if (cnt == 0) goto run; cnt -= 0x80; if (cnt == 0) goto longOp; /* shortSkip */ nBytes -= 1; /* length of shortSkip code */ dstP += cnt; /* adding 7-bit count to 32-bit pointer */ if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) goto stop; goto nextOp; dump: nBytes -= 1 + cnt; /* length of dump code & data */ /* ASM: ds:si=srcP, es:di=dstP, cx=cnt, then do "rep movsb". */ /* Equivalent: "do { *dstP++ = *srcP++; } while (--cnt);" */ far_movmem_ptrs(srcP, dstP, cnt); dstP += cnt; if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) goto stop; srcP += cnt; goto nextOp; run: wordCnt = (UBYTE) * srcP++; /* 8-bit unsigned count */ pixel = *srcP++; nBytes -= 3; /* length of run code & data */ /* ASM: es:di=dstP, al=pixel, cx=wordCnt, then do "rep stosb". */ /* Equivalent: "do { *dstP++ = pixel; } while (--wordCnt);" */ far_setmem_ptr(dstP, wordCnt, pixel); dstP += wordCnt; if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) goto stop; goto nextOp; longOp: wordCnt = *((UWORD far *)srcP)++; if ((WORD)wordCnt <= 0) goto notLongSkip; /* Do SIGNED test. */ /* longSkip. */ nBytes -= 3; dstP += wordCnt; if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) goto stop; goto nextOp; notLongSkip: if (wordCnt == 0) goto stop; wordCnt -= 0x8000; /* Remove sign bit. */ if (wordCnt >= 0x4000) goto longRun; /* longDump. */ nBytes -= 3 + wordCnt; /* ASM: ds:si=srcP, es:di=dstP, cx=wordCnt, then do "rep movsb". */ /* Equivalent: "do { *dstP++ = *srcP++; } while (--wordCnt);" */ far_movmem_ptrs(srcP, dstP, wordCnt); dstP += wordCnt; if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) goto stop; srcP += wordCnt; goto nextOp; longRun: wordCnt -= 0x4000; /* Clear "longRun" bit. */ pixel = *srcP++; nBytes -= 4; /* ASM: es:di=dstP, al=pixel, cx=wordCnt, then do "rep stosb". */ /* Equivalent: "do { *dstP++ = pixel; } while (--wordCnt);" */ far_setmem_ptr(dstP, wordCnt, pixel); dstP += wordCnt; if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) goto stop; goto nextOp; stop: /* all done */ if (FP_OFF(dstP) > N_BYTES_IN_BITMAP) #endif /* RunSkipDump in C. */ BugExit("LPC"); /* "Wrong amount of screen data generated from RunSkipDump * body"); */ } else { /* TBD: Should be Info Msg, not Exit. */ UnrecognizedDeltaTypeExit(); } } /* --------------- PlayDeltaFrame --------------- * Play the current delta info onto the specified frame bitmap. */ PlayDeltaFrame(BITMAP * bm) { if (BODY_BYTES == 0) return; /* "0 bytes" means "no change". */ PlayDeltaFrame0(BSEG(bm)); } /* ----------------------------------------------------------------------- */ BOOL permitScreenSwap = TRUE; /* --------------- CreateFile --------------- */ WORD CreateFile(char *baseName, char *suffix) { WORD file; char name[80]; AppendSuffix(name, baseName, suffix); file = creatdos(name, O_RDWR); if (file == -1) file = NULL; /* No file open. */ return file; } /* --------------- CloseFile --------------- * Once i/o is buffered, this will flush & release buffer. */ void CloseFile(WORD file) { if (file && file != -1) closedos(file); /* NOTE: we use "NULL" to indicate non-open file. */ } /* --------------- WriteN --------------- * Write near data, and return FALSE on error. */ BOOL WriteN(UWORD file, BYTE *data, UWORD cnt) { return (cnt == writedos(file, dataseg(), data, cnt)); } /* --------------- WriteFar --------------- * Write far data, and return FALSE on error. */ BOOL WriteFar(UWORD file, WORD far * data, UWORD cnt) { return (cnt == writedos(file, FP_SEG(data), FP_OFF(data), cnt)); } /* --------------- ReadN --------------- * Read near data, and return FALSE on error. */ BOOL ReadN(UWORD file, BYTE * data, UWORD cnt) { return (cnt == read(file, data, cnt)); } /* --------------- ReadFar --------------- * Read far data, and return FALSE on error. */ BOOL ReadFar(UWORD file, WORD far * data, UWORD cnt) { return (cnt == readdos(file, FP_SEG(data), FP_OFF(data), cnt)); } /* --------------- CreateLpFile0 --------------- * Create file with one frame. * If "blank", the first frame is blank, otherwise it is the * contents of "hidbm". */ BOOL animOptimized; /* TRUE when anim is packed for optimal playback. */ BOOL cleanGap = FALSE; /* TRUE to put nulls in gap when write lp. */ void CreateLpFile0(BOOL blank) { lpFile = CreateFile(animName, lpfSuffix); if (!lpFile) CantCreateAnimFileExit(); setmem(&lpfHdr, sizeof(lpfHdr), 0); /* BEFORE set any fields. */ #if 0 /* ...done by setmem... */ lpfHdr.variant = lpfHdr.hasLastDelta = lpfHdr.lastDeltaValid = lpfHdr.pixelType = lpfHdr.otherRecordsPerFrame = 0; #endif lpfHdr.version = LPF_VERSION; /* TBD: Whenever create new anim, starts at a specified rate. * Should this instead be done when program boots, then use whatever * gets loaded? * Hard to say, since can either be set by user, * or by loading an anim (in which case user may not realize it * changed). */ /* TBD: Boot, or NewAnim, now creates at 10 fps. Good choice? */ DeclareFramesPerSecond(10 /* FASTEST_RATE */ ); lpfHdr.id = LARGE_PAGE_FILE_ID; lpfHdr.maxLps = MAX_LARGE_PAGE; lpfHdr.maxRecsPerLp = MAX_RECORDS_PER_LP; /* lpfHdr.nLps & .nRecords are over-ridden by independent variables. */ lpfHdr.lpfTableOffset = LPF_TABLE_OFFSET; lpfHdr.contentType = ANIM_CONTENTS_ID; lpfHdr.width = 320; lpfHdr.height = 200; lpfHdr.bitmapRecordsPerFrame = 1; lpfHdr.highestBBComp = BBC_RUN_SKIP_DUMP; AnimFramesX(totalNRecords = 1); /* # records in whole lpFile */ lpfHdr.nFrames = animFrames + lpfHdr.hasLastDelta; nLps = 1; CUR_LP = 0; CUR_REC = LP_BASE_REC = 0; LP_N_RECS = 1; /* # records in current large page */ LP_N_BYTES = 0; lpTable[0] = 0; /* A zero-byte page description. */ /* (Since it is first frame, zero-bytes means all-white). */ LP_REC_ADDR0 = 0; /* No header info before frame data in * buffer seg. */ #if 1 /* --- Hack: Force zero-size first frame to be re-computed as * a series of all-white runs, since the player no longer sets bitmap * to white before first frame. * THEN, say the anim is not dirty, so won't save it unless some * change is made. This avoids asking user to save the empty * untitled anim, when he boots DP-A, then does LoadAnim. * !!! Is ugly to be calling these routines from inside here. !!! */ ReadLpfFrame(FIRST_FRAME_N); if (blank) { SetBitMap(&hidbm, white_color); /* Clear what will be frame 0. */ FirstAnimFrame0(); WriteAnimFrameAndReadNext(); } else { /* Use "hidbm" as first frame's contents. */ FirstAnimFrame00(); WriteAnimFrameAndReadNext(); } animOptimized = TRUE; #endif WriteCurLp(); /* Make sure disk is up-to-date. */ ReadCurLp(); /* Overkill. Makes sure buffer & tables * consistent. */ ReadLpfFrame(FIRST_FRAME_N); /* Adjust BODY_ADDR, etc., for correct header size. */ } /* --------------- CreateLpFile --------------- * Create file, with first frame blank. */ void CreateLpFile() { CreateLpFile0(TRUE); } /* --------------- WriteHdrAndCloseLpFile --------------- * IMPLICIT PARAMETERS: lpFile, lpfHdr, nLps, lpfTable, totalNRecords. */ WriteHdrAndCloseLpFile() { register UWORD i; BOOL tryAgain = TRUE; again: if (!lpFile) return; MaybeWriteCurLp(); /* lpfHdr.version = LPF_VERSION; --redundant */ lpfHdr.nLps = nLps; lpfHdr.nRecords = totalNRecords; lpfHdr.nFrames = animFrames + lpfHdr.hasLastDelta; /* --- Clear unused elements of lpfTable. */ setmem(&lpfTable[nLps], sizeof(LP_DESCRIPTOR) * (MAX_LARGE_PAGE - nLps), 0); /* --- Write lpfHdr. */ CHECK_DOS(lseekdos(lpFile, (LONG) 0, SEEK_SET)); CHECK_DOS(writefull(lpFile, (char *) &lpfHdr, sizeof(lpfHdr))); /* --- Write cycle info as part of header */ /* TBD: byte flipping for 68000 */ CHECK_DOS(writefull(lpFile, (char *) cycles, sizeof(cycles))); /* --- Write palette, immediately after future-expansion of lpfHdr. * ASSUME PALETTE_SIZE == 3*256. * NOTE: Palette byte-order not changed between 8086 & 68000. */ CHECK_DOS(writefull(lpFile, (char *)curpal, PALETTE_SIZE)); /* --- Write lpfTable, immediately after palette. */ CHECK_DOS(writefull(lpFile, (char *)lpfTable, SIZEOF_LPF_TABLE_IN_FILE)); goto ok; doserr: if (tryAgain) { /* Attempt to overcome spurious disk error. */ tryAgain = FALSE; goto again; } TroubleWritingFileMsg(); ok: closedos(lpFile); lpFile = NULL; if ((animFrames + lpfHdr.hasLastDelta) != totalNRecords) BugExit("LPH"); /* "inconsistent #frames vs #records in * file"); */ /* NOTE: when hasLastDelta is TRUE, * nRecords includes that delta. * In that case, number of frames in anim is * "nRecords-1". * ASSUME TRUE == 1. * WARNING: Calling "BugExit" would be * recursive, if done before * set lpFile = NULL. */ } /* --------------- WriteLargePage0 --------------- * IMPLICIT PARAMETERS: "lpfTable" describes output lpFile. * TBD: should be descriptor containing both lpFile & lpfTable. * ASSUME lpFile is open for writing. * Q: Can this change to do in one disk i/o operation? * That requires lpTable to be moved to head of lpSeg, yet lpTable is * variable-size. So will have to MOVE the 64KB of data. * ANS: CANNOT have "lpTable" part of lpSeg (requiring this routine to * shift data in lpSeg), as this routine is used to * WriteNewLp, while lpSeg still holding other useful data! */ #define LP_SIZE_IN_FILE(nLp) \ (lpfTable[nLp].nBytes + LP_HEADER_SIZE(lpfTable[nLp].nRecords)) /* Size of an lp in file -- contents PLUS header. */ WORD WriteLargePage0( UWORD lpFile, UWORD seg, LP_TABLE_ELEMENT lpTable[], register LpX * lpX) { LPageHeaderFixed lphf; UWORD gapSize, writeSize; UBYTE zeroes[512]; /* 512 bytes on stack. TBD: can be larger? */ UWORD zeroSeg, zeroAddr; BOOL tryAgain = TRUE; again: lpfTable[lpX->nLp] = lpX->x; CHECK_DOS(SeekLargePage(lpX->nLp)); /* --- Write header for lp. * Note: if header changes, so must LP_HEADER_SIZE. */ lphf.x = lpX->x; lphf.nByteContinuation = 0; CHECK_DOS(writefull(lpFile, (char *) &lphf, sizeof(lphf))); if (lpX->x.nRecords) CHECK_DOS(writefull(lpFile, (char *) lpTable, lpX->x.nRecords * sizeof(LP_TABLE_ELEMENT))); /* --- Table describing the records. */ if (lpX->x.nBytes) CHECK_DOS(writedosfull(lpFile, seg, lpX->recAddr0, lpX->x.nBytes)); if (cleanGap) { /* Fill gap with nulls. */ zeroSeg = DAllocBiggest(32768, &writeSize); if (writeSize <= sizeof(zeroes)) zeroSeg = SFree(zeroSeg); if (zeroSeg) zeroAddr = 0; /* Allocated a large far buffer */ else { zeroSeg = dataseg();/* Use stack buffer */ zeroAddr = (UWORD) zeroes; writeSize = sizeof(zeroes); } far_setmem(zeroSeg, zeroAddr, writeSize, 0); gapSize = (65535 - LP_SIZE_IN_FILE(lpX->nLp)) + 1; while (gapSize) { if (writeSize > gapSize) writeSize = gapSize; gapSize -= writeSize; CHECK_DOS(writedosfull( lpFile, zeroSeg, zeroAddr, writeSize)); } if (zeroSeg != dataseg()) zeroSeg = SFree(zeroSeg); } return (SUCCESS); doserr: if (tryAgain) { /* Attempt to overcome spurious disk error. */ tryAgain = FALSE; goto again; } TroubleWritingFileMsg(); return (FAIL); } /* --------------- WriteCurLp --------------- * Write current large page back into buffer. */ local WORD WriteCurLp(void) { WORD result; result = WriteLargePage0(lpFile, lpSeg, &lpTable[lpTableOffset], &curLpX); curLpIsDirty = FALSE; return result; } /* --------------- ReadCurLp --------------- * Read current large page back into buffer. * TBD: If no lp specified yet, does nothing. Should it load first frame * instead? */ local void ReadCurLp(void) { if (CUR_LP < nLps) ReadLargePage(CUR_LP); } /* --------------- RecordFits --------------- */ BOOL RecordFits(UWORD srcBytes) { if (LP_N_RECS == MAX_RECORDS_PER_LP) return FALSE; /* --- If added this record, check whether header + body still * fit. */ if (LP_HEADER_SIZE(LP_N_RECS + 1) + (ULONG)LP_N_BYTES + (ULONG)srcBytes > (ULONG)MAX_LARGE_PAGE_SIZE) return FALSE; return TRUE; } /* --------------- LpfFrameLimitAddr --------------- * For current lpf frame, RETURN limitAddr. * == current size of frame. */ UWORD LpfFrameLimitAddr() { return BODY_ADDR + BODY_BYTES; } /* ------------------------------------------------------------ */ /* --------------- Other routines --------------- */ /* --------------- IsLpfId --------------- * RETURN TRUE if id matches Large-Page-File's id. */ BOOL IsLpfId(ULONG id) { return (id == LARGE_PAGE_FILE_ID); } /* ------------------------------------------------------------------------ * Delta management. * ------------------------------------------------------------------------ */ /* --------------- delta buffer --------------- * Buffer to hold one delta (difference between two bitmaps). * (seg:addr) is start of buffer, (seg:limit) is just past end of buffer, * "nBytes" is length of delta. */ typedef struct { UWORD seg, addr, limit, nBytes; } DeltaX; DeltaX deltaX = {0}; extern UWORD allocSeg; /* --------------- ComputeDeltaX --------------- * Given prevFrame & current frame in hidbm, compute delta in save_bitmap. * IMPLICIT INPUT: deltaX, hidbmSeg, prevFrameSeg. * RETURN TRUE if succeeded in creating delta, FALSE if delta didn't fit. * SIDE_EFFECT: set deltaX.nBytes, store that many bytes at deltaX.(seg:addr). * Note: if algorithm is changed, must be careful that can't exceed * MAX_LARGE_PAGE_SIZE. */ #define MRSD_ASM 1 /* Make RunSkipDump in assembly. */ #define CLIP_X(stopLabel) { \ if (x == w) { \ x = 0; \ if (inFinalRow) { \ mustStop = TRUE; \ goto stopLabel; } \ if (src2 == finalRow2) {\ w = finalRowWidth; \ inFinalRow = TRUE; }\ } \ } #define CROSSED_W(n) (xWas + (n) > width) /* Test avoids using SHORT ops when line-wrap. * This is specified, so can write algorithm to play onto wider screen, * without having to test shortDump & shortRun for line-wrap on any pixel. * "width" rather than "w", since is from previous line, not current line. * This only matters on last line. */ #define PUT_DUMP(body, cnt) if (cnt) { \ if ((cnt) <= MAX_SHORT_DUMP && !CROSSED_W(cnt)) \ *dst++ = OP_DUMP | (cnt); \ else{ \ *dst++ = LONG_OP; \ *((UWORD far *)dst)++ = \ (LONG_DUMP<<8) | (cnt); } \ if (dst - dst0 > finalDst-(cnt)) \ goto incompressible; \ far_movmem_ptrs( body, dst, cnt ); \ dst += cnt; } /* NOTE the check for zero cnt. * NOTE that cnt must already have been limited to MAX_DUMP. */ #define PUT_RUN(wordCnt,runPix) if (wordCnt) { \ if ((wordCnt) <= MAX_SHORT_RUN && !CROSSED_W(wordCnt)) { \ *dst++ = OP_RUN; \ *dst++ = wordCnt; } \ else{ \ *dst++ = LONG_OP; \ *((UWORD far *)dst)++ = \ (LONG_RUN<<8) | (wordCnt); } \ *dst++ = runPix; \ } local BOOL ComputeDeltaX(BOOL skipPermitted) { /* Inputs: */ UBYTE far *src1, far * src2, far * final2, far * dst; UWORD width, finalDst; /* src1 points to previous frame, src2 to screen, limit2 to final * screen * byte, dst points to "finalDst+1" byte buffer for Body, * width is # pixels in a row. For 64 KB buffer, finalDst==0xFFFF. */ /* Local variables: */ UBYTE far *finalRow2, far * dst0; UBYTE pixel, runPix, skipInDump, runInDump; UWORD x, w, finalRowWidth, wordCnt, xWas; BOOL inFinalRow, mustStop; #if 0 UWORD deltaSize; #endif if (!deltaX.limit) BugExit("LPJ"); /* "ComputeDelta -- deltaX.limit"); */ src1 = BYTE_FAR_PTR(prevFrameSeg, 0); src2 = BYTE_FAR_PTR(hidbmSeg, 0); final2 = BYTE_FAR_PTR(hidbmSeg, NBYTES_IN_BITMAP(&hidbm) - 1); dst = BYTE_FAR_PTR(deltaX.seg, deltaX.addr); width = hidbm.box.w; finalDst = deltaX.limit; w = width; finalRowWidth = (UWORD)(final2 - src2 + 1) % width; if (!finalRowWidth) finalRowWidth = width; /* # pixels in final row. */ finalRow2 = final2 - (finalRowWidth - 1); /* Ptr to start of final * row. */ inFinalRow = (finalRow2 == src2); /* FALSE unless src2 is a single row. */ if (inFinalRow) w = finalRowWidth; /* In case single-row src is shorter than * width. */ #if MRSD_ASM /* RunSkipDump in assembly. */ return MakeRunSkipDump(prevFrameSeg, hidbmSeg, NBYTES_IN_BITMAP(&hidbm) - 1, deltaX.seg, deltaX.addr, deltaX.limit, &deltaX.nBytes, w, FP_OFF(finalRow2), finalRowWidth, inFinalRow, skipPermitted); #else /* ---- RunSkipDump in C. --- most likely out of date ------ */ dst0 = dst; /* Remember, so can calculate Body size. */ *(UWORD far *) dst = BMBODY_RUNSKIPDUMP; /* 2-byte header with type. */ dst += 2; x = 0; /* Keep track of pixel-position, so break * at row end. */ mustStop = FALSE; notInASequence: wordCnt = 0; /*--- "pixel" must be set when jump to beDump with "wordCnt != 0".*/ beDump: xWas = x + width - wordCnt; /* Where in line dump started. */ if (xWas >= width) xWas -= width; /* "width" logic in case x < wordCnt, because already * line-wrapped. */ if (FP_OFF(dst) > finalDst - 3) goto incompressible; /* "3" is longDump overhead. Dump DATA is checked inside * PUT_DUMP. */ skipInDump = 0; runInDump = (wordCnt ? 1 : 0); /* Any pixel is run==1. */ inDump: CLIP_X(stopDump); /* If mustStop, goto to "stopDump" to get * final dump. */ runPix = pixel; pixel = *src2++; x++; wordCnt++; /* Get&count byte. */ if (pixel == *src1++) skipInDump++; /* # skippable bytes. */ else skipInDump = 0; if (pixel == runPix) runInDump++; /* # runnable bytes. */ else runInDump = 1; /* Any pixel is run==1. */ if (skipInDump == MIN_SKIP && skipPermitted) { /* Minimum worthwhile * skip. */ PUT_DUMP(src2 - wordCnt, wordCnt - MIN_SKIP); wordCnt = MIN_SKIP; goto beSkip; /* "Skip 2" so far. */ } if (runInDump == MIN_RUN) { /* Minimum worthwhile run. */ PUT_DUMP(src2 - wordCnt, wordCnt - MIN_RUN); wordCnt = MIN_RUN; goto beRun; /* "Run 4" so far. */ } /* --- Byte still in Dump. */ if (wordCnt == MAX_LONG_DUMP) { stopDump: PUT_DUMP(src2 - wordCnt, wordCnt); if (mustStop) goto stop; goto notInASequence; } goto inDump; beRun: xWas = x + width - wordCnt; /* Where in line run started. */ if (xWas >= width) xWas -= width; if (FP_OFF(dst) > finalDst - 4) goto incompressible; /* "4" is length of longRun. */ skipInDump = 0; /* ACTUALLY skip is in run, not in dump... */ runPix = pixel; /* Only need do once per run. */ inRun: CLIP_X(stopRun); /* If mustStop, goto to "stopRun" to get * final run. */ pixel = *src2++; x++; wordCnt++; /* Get byte & count it. */ if (pixel == *src1++) skipInDump++; /* # skippable bytes. */ else skipInDump = 0; /* Note: check for byte runnable comes later. */ if (skipInDump == MIN_RUN_SKIP && skipPermitted) { wordCnt -= MIN_RUN_SKIP; PUT_RUN(wordCnt, runPix); wordCnt = MIN_RUN_SKIP; goto beSkip; /* "Skip 4" so far. */ } if (pixel != runPix) { /* Byte not runnable -- requires Dump. */ wordCnt--; /* Retract pixel from Run. */ PUT_RUN(wordCnt, runPix); wordCnt = 1; goto beDump; /* "Dump 1" so far. */ } /* --- Byte runnable. */ if (wordCnt == MAX_LONG_RUN) { stopRun: PUT_RUN(wordCnt, runPix); if (mustStop) goto stop; goto notInASequence; } goto inRun; beSkip: xWas = x + width - wordCnt; /* Where in line skip started. */ if (xWas >= width) xWas -= width; if (FP_OFF(dst) > finalDst - 3) goto incompressible; /* "3" is length of LongSkip. */ inSkip: runPix = pixel; pixel = *src2++; x++; wordCnt++; /* Get byte & count it. */ if (pixel != *src1++) { /* Byte not skippable. */ wordCnt--; /* Retract pixel from Skip. */ if (wordCnt <= MAX_SHORT_SKIP) { *dst++ = OP_SKIP | wordCnt; } else if (wordCnt <= 2 * MAX_SHORT_SKIP) { *dst++ = OP_SKIP | MAX_SHORT_SKIP; wordCnt -= MAX_SHORT_SKIP; *dst++ = OP_SKIP | wordCnt; } else { UWORD wordCnt0 = wordCnt; while (wordCnt0) { wordCnt = MIN(wordCnt0, MAX_LONG_SKIP); wordCnt0 -= wordCnt; *dst++ = LONG_OP; *dst++ = (UBYTE)wordCnt; /* Low byte of UWORD. */ *dst++ = wordCnt >> 8; /* High byte of UWORD. */ } } wordCnt = 1; goto beDump; /* "Dump 1" so far. */ } /* --- Byte skippable. */ /* --- Final Skip is NOT output prior to Stop. */ CLIP_X(stop); goto inSkip; /* Skip may be any length up to 64KB-1. */ stop: if (FP_OFF(dst) > finalDst - 3) goto incompressible; /* "3" is length of Stop. */ *dst++ = LONG_OP; /* Stop represented as "LongOp #0". */ /* 68000 note: Don't use word op to put cnt, as may not be * word-aligned. */ *dst++ = 0; /* High byte of cnt==0. */ *dst++ = 0; /* Low byte of cnt==0. */ deltaX.nBytes = dst - dst0; /* "body size": # bytes in Body, excluding any header. * NOTE: incompressible tests guarantee that deltaX.nBytes won't quite * reach 64 KB, which would overflow UWORD. */ return TRUE; /* deltaX.nBytes is set as a side-effect. */ /* NOTE: if deltaX.nBytes == 3, all there is is a Stop. * Client may wish to set deltaX.nBytes == 0 & have no data in this * case. */ incompressible: return FALSE; #endif /* RunSkipDump in C. */ } /* --------------- AbandonChangesMaybeExit --------------- */ local void AbandonChangesMaybeExit(BOOL closeAndExit) { CUR_LP = (UWORD)-1; /* None loaded -- abandon lpBuffer. */ curLpIsDirty = FALSE; /* --- Drastic solution -- don't try to continue. */ if (closeAndExit) DiskFullChangesLostExit(); /* closes lp. */ } /* --------------- FindFreeLp --------------- */ local UWORD FindFreeLp(void) { register UWORD nLp; for (nLp = 0; nLp < nLps; nLp++) { if (lpfTable[nLp].nRecords == 0) return nLp; /* Found empty lp in middle of file. */ } if (nLps == MAX_LARGE_PAGE) { BugExit("LPK"); /* "Too many pages in anim"); */ } return nLps++; /* Page # of next lp to use (lps #d from 0). */ } /* --------------- LpTableToLpBufferHeader --------------- * Assuming space made available, put correct contents in header, * based on lpTable & variables. * ASSUMES LP_N_RECS & LP_N_BYTES has already been changed to reflect # recs * & new contents in lp. * Copy these changes to lpfTable, so FindLargePageForRecord will see truth. */ local void LpTableToLpBufferHeader(void) { far_movmem(dataseg(), (UWORD)&curLpX.x, lpSeg, 0, sizeof(LP_DESCRIPTOR)); *WORD_FAR_PTR(lpSeg, sizeof(LPageHeaderFixed) - 2) = 0; /* no cont'. */ /* ASSUMES nByteContinuation is last field. */ far_movmem(dataseg(), (UWORD)lpTable, lpSeg, sizeof(LPageHeaderFixed), sizeof(LP_TABLE_ELEMENT) * LP_N_RECS); lpfTable[CUR_LP] = curLpX.x; /* IMPORTANT! */ } /* --------------- UpdateLpBufferHeader --------------- * Shift the data in the lp buffer, and re-write the header, so that it * matches lpTable. */ local void UpdateLpBufferHeader(void) { UWORD newRecAddr0 = LP_HEADER_SIZE(LP_N_RECS); far_movmem_same_seg(lpSeg, LP_REC_ADDR0, newRecAddr0, LP_N_BYTES); /* --- "current record" info has changed location. */ REC_ADDR = REC_ADDR - LP_REC_ADDR0 + newRecAddr0; BODY_ADDR = BODY_ADDR - LP_REC_ADDR0 + newRecAddr0; LP_REC_ADDR0 = newRecAddr0; LpTableToLpBufferHeader(); curLpIsDirty = TRUE; } /* --------------- WriteNewLp --------------- * Extract a portion of lp buffer as a new lp. * Return FAIL if trouble writing (presumably, disk is full). */ #define SV_BASE_REC svLpX.x.baseRecord local WORD WriteNewLp(UWORD startRecord, UWORD endRecord) { register UWORD nRecord; WORD result; LpX svLpX; svLpX = curLpX; CUR_LP = FindFreeLp(); lpTableOffset = startRecord - SV_BASE_REC; /* Fake out WriteCurLp. */ /* NOTE: need unmodified startRecord & endRecord for this. */ LP_BASE_REC = startRecord; LP_N_RECS = endRecord - startRecord + 1; startRecord -= SV_BASE_REC; endRecord -= SV_BASE_REC; /* NOTE: use startRecord/endRecord RELATIVE to SV_BASE_REC. */ /* --- Get from real LP_REC_ADDR0 to fake LP_REC_ADDR0 (at * startRecord). */ for (nRecord = 0; nRecord < startRecord; nRecord++) { LP_REC_ADDR0 += lpTable[nRecord]; } /* --- Sum of bytes for desired records. */ LP_N_BYTES = 0; /* So far */ for (nRecord = startRecord; nRecord <= endRecord; nRecord++) { LP_N_BYTES += lpTable[nRecord]; } result = WriteCurLp(); /* New lp. */ lpTableOffset = 0; /* Done faking out WriteCurLp. */ curLpX = svLpX; /* Restore access to "current lp". */ /* Including LP_REC_ADDR0! */ return result; } /* --------------- CurRec_NewBody --------------- * Place a new body into CurRec. If necessary, records before and/or after * current are moved into new lps. * IMPLICIT INPUT: curRecX, deltaX, lpSeg, curLpX. */ local void CurRec_NewBody(void) { WORD result = SUCCESS; /* 1. Determine how much room for new body. */ #define AVAIL_BODY_SIZE (GAP_SIZE + BODY_BYTES) UWORD availBodySize = AVAIL_BODY_SIZE; BOOL newExtraBytes = (EXTRA_BYTES == 0); /* If there was no record at all, create an ID+FLAGS UWORD. */ if (newExtraBytes) availBodySize -= 2; /* For ID+FLAGS. */ /* 2A. If won't fit, move other records to new lps. */ if (deltaX.nBytes > availBodySize) { /* --- Dumbest possible approach: split into 3 buffers. */ UWORD newRecAddr0 = LP_HEADER_SIZE(1); /* --- Following records. */ if (CUR_REC != LAST_RECORD_IN_BUF && result == SUCCESS) { result = WriteNewLp(CUR_REC + 1, LAST_RECORD_IN_BUF); /* if (result != SUCCESS) goto fail; TBD... */ /* Cur lp now has just base-rec thru cur-rec. */ LP_N_RECS = (CUR_REC + 1) - LP_BASE_REC; LP_N_BYTES = (REC_ADDR + REC_BYTES) - LP_REC_ADDR0; UpdateLpBufferHeader(); availBodySize = AVAIL_BODY_SIZE; if (newExtraBytes) availBodySize -= 2; /* For ID+FLAGS. */ if (deltaX.nBytes <= availBodySize) goto nowFits; } if (CUR_REC > LP_BASE_REC && result == SUCCESS) result = WriteNewLp(LP_BASE_REC, CUR_REC - 1); /* Previous records */ if (result != SUCCESS) { /* TBD: Don't use new lps. */ BugExit("NewLp"); } /* --- Prepare lp with just CUR_REC. */ LP_BASE_REC = CUR_REC; LP_N_RECS = 1; if (newExtraBytes) EXTRA_BYTES = 2; else far_movmem(lpSeg, REC_ADDR, lpSeg, newRecAddr0, EXTRA_BYTES); BODY_BYTES = deltaX.nBytes; LP_REC_ADDR0 = REC_ADDR = newRecAddr0; BODY_ADDR = REC_ADDR + EXTRA_BYTES; REC_BYTES = EXTRA_BYTES + BODY_BYTES; LP_N_BYTES = lpTable[0] = REC_BYTES; LpTableToLpBufferHeader(); /* Buffer now has single frame in * it! */ /* NOTE: should NOT do i/o, * just preparing variables. */ } else { /* 2B. Make room for new body, & set * variables. */ UWORD curRecLimit; UWORD sizeChange; nowFits: curRecLimit = REC_ADDR + REC_BYTES; sizeChange = deltaX.nBytes - BODY_BYTES; /* NOTE: May be NEGATIVE number. Relying on 16-bit wrapping! */ #define BYTES_AFTER_BODY (LP_TOTAL_BYTES - curRecLimit) if (newExtraBytes) { EXTRA_BYTES = 2; sizeChange += EXTRA_BYTES; BODY_ADDR = REC_ADDR + EXTRA_BYTES; /* No longer access old body. */ } #define NEW_REC_LIM (curRecLimit + sizeChange) /* ASSUMES body is last field in record. */ /* ASSUMES lp starts at addr 0 w/i lpSeg. */ far_movmem_same_seg(lpSeg, curRecLimit, NEW_REC_LIM, BYTES_AFTER_BODY); BODY_BYTES = deltaX.nBytes; /* When newExtra, not same as * sizeChange */ REC_BYTES += sizeChange; LP_N_BYTES += sizeChange; lpTable[CUR_REC - LP_BASE_REC] = REC_BYTES; } /* 3. Get new body into lp buffer. */ if (newExtraBytes) *WORD_FAR_PTR(lpSeg, REC_ADDR) = DEFAULT_BITMAP_ID_FLAGS; far_movmem(deltaX.seg, deltaX.addr, lpSeg, BODY_ADDR, BODY_BYTES); curLpIsDirty = TRUE; } /* --------------- DAllocBiggest --------------- * Allocate the requested block. If can't, find and allocate * the biggest possible. * TBD: Is there a DOS or BIOS call to find biggest available block? * RETURNS seg # of allocation, NULL if failed. * SIDE_EFFECT: sets "*allocSizeP" to # bytes actually allocated. */ local UWORD DAllocBiggest(UWORD nBytes, UWORD * allocSizeP) { UWORD seg, allocParas; /* --- First, try to allocate full request, kicking out other stuff * if necessary. */ seg = DAlloc(nBytes); if (seg) { *allocSizeP = nBytes; return seg; } /* --- Couldn't get all, get what we can. */ #define N_PARAGRAPHS(nBytes) ( (nBytes >> 4) + (nBytes & 0x0f ? 1 : 0) ) /* NOTE: Don't do as "(nBytes+15) >> 4", since may overflow UWORD. */ seg = allocsome(N_PARAGRAPHS(nBytes), &allocParas); *allocSizeP = allocParas << 4; if (!seg) /* Only happens if memory completely full. */ *allocSizeP = 0; return seg; } /* --------------- ComputeDelta --------------- * Compute delta into a large temporary buffer. * IMPLICIT INPUT: curRecX, lpSeg. * SIDE_EFFECT: sets deltaX. */ local void ComputeDelta(void) { BOOL skipPermitted = (curFrame != FIRST_FRAME_N); /* Disallow skip code on first frame, so don't need to blank * frame before drawing delta -- will overwrite every pixel * via Dumps and Runs. */ deltaX.seg = allocSeg; deltaX.addr = 0; deltaX.limit = MAX_LARGE_PAGE_SIZE; bigEnough: if (!ComputeDeltaX(skipPermitted)) { /* Handle incompressible frame. */ UWORD rawSize = N_BYTES_IN_BITMAP; deltaX.nBytes = rawSize + sizeof(UWORD); /* UWORD for header, then raw dump of screen data. */ if (deltaX.limit - deltaX.addr < deltaX.nBytes) BugExit("LPM"); /* "Buffer too small for incompressible frame"); */ *WORD_FAR_PTR(deltaX.seg, deltaX.addr) = BMBODY_UNCOMPRESSED; /* header contains type "uncompressed". */ far_movmem(hidbmSeg, 0, deltaX.seg, deltaX.addr + 2, rawSize); } } /* --------------- WriteFinalAnimFrame --------------- * Compute and transfer to file the current delta-frame, * which must be the last frame in the file. * (If not last frame, must use WriteAnimFrameAndReadNext). */ void WriteFinalAnimFrame(void) { ComputeDelta(); CurRec_NewBody(); /* Implicit: deltaX. */ } /* --------------- WriteAnimFrameAndReadNext --------------- * Transfer to file the specified delta-frame. * Also compute & transfer the next delta-frame, as it is affected by the * changes in the current anim-frame. * TBD: Some caller's may wish to MaybeWriteCurLp, so that lp buffer is * free for immediate use by menus/pop-ups. */ void WriteAnimFrameAndReadNext(void) { ComputeDelta(); /* ---2. Exchange prevFrame with new-frame in hidbm. * Because new-frame is next-frame's prevFrame. */ far_swapmem(BSEG(&hidbm), 0, prevFrameSeg, 0, N_BYTES_IN_BITMAP); /* ---3. Apply old-delta to hidbm. (Convert prev- to old- frame.) */ PlayDeltaFrame(&hidbm); /* Implicit: curRecX, lpSeg. */ CurRec_NewBody(); /* Implicit: deltaX. */ /* ---4. If at anim end, simply set up first frame. */ /* FUTURE: may compute delta between last & first frame. * Could store in special frame before the normal first frame. */ curFrame++; if (curFrame > LAST_FRAME_N) { FirstAnimFrame0(); goto done; } /* ---5. Read & apply next delta. */ ReadLpfFrame(curFrame); PlayDeltaFrame(&hidbm); /* Convert old-frame to next-frame. */ /* ---6. Recompute next-frame's delta (since prevFrame is new.) */ ComputeDelta(); CurRec_NewBody(); done: ; } /* --------------- AddFrame --------------- * ASSUMES current frame is last frame in lpfile. * Add an identical frame after it, and make it current. * NOTE: Be careful to leave valid cur rec & cur lp variables. */ void AddFrame() { UWORD afterRecord = CUR_REC; /* Hold, in case lp changes affect CUR_REC.*/ WORD afterFrame = curFrame; if ( MAX_RECORDS_PER_LP - LP_N_RECS < 1 || /* lpTable overflow*/ MAX_LARGE_PAGE_SIZE - LP_N_BYTES < LP_HEADER_SIZE(LP_N_RECS+1) /* lp overflow */ ) { /* --- Create new lp, with only an empty frame. */ MaybeWriteCurLp(); LP_BASE_REC = afterRecord + 1; LP_N_BYTES = 0; setmem( &lpTable[0], sizeof(LP_TABLE_ELEMENT), 0 ); /* Zero the new frame. */ /* --- SWITCH ACCESS from current lp to new lp. */ CUR_LP = FindFreeLp(); LP_N_RECS = 1; WriteCurLp(); } else { /* Add frame to current lp. * Zero the new frame. record # relative to CUR_LP's base rec. */ lpTable[afterRecord+1 - LP_BASE_REC] = 0; LP_N_RECS += 1; } totalNRecords += 1; AnimFramesX(animFrames + 1); UpdateLpBufferHeader(); ReadLpfFrame(curFrame = afterFrame+1); /* Make this frame current. */ } /* --------------- ConvertFinalFrameToLastDelta --------------- */ ConvertFinalFrameToLastDelta() { animFrames--; lpfHdr.hasLastDelta = lpfHdr.lastDeltaValid = TRUE; curFrame--; }