/***********************************************************************\ * 2View V1.50 * * A simple, fast ILBM viewer, for use under AmigaOS V2.x. * * Written and ©1991-1992 by Dave Schreiber. All Rights Reserved. * * * * Usage: * * 2View FILE/A/M,FROM/K,SECS=SECONDS/K/N,TICKS/K/N,LOOP/S,PRINT * * * * Where the following arguments are defined as follows: * * FILE - The name of one (or more) IFF ILBM files * * FROM - A file containing a list of filenames. Used instead of FILE * * SECS - Number of seconds to display a file * * TICKS - Number of ticks (1/60ths of a second) * * LOOP - When finished showing the last pictures, start over * * PRINT - Print each picture as it is shown * * * * To compile (with SAS/C V5.10a): * * lc -v 2View ARexx * * lc -v -b0 Misc * * asm 2ViewAsm.a * * blink with 2View.lnk * * * * Version history: * * 1.50 - Rewrote the subroutine that reads the ILBM from disk in * * assembly language, for speed. Added support for SHAM and * * Macro Paint images, and for color cycling (both * * traditional (CRNG, i.e. continutout cycle ranges) and * * DPaint-IV style cycling (DRNG, i.e. non-continuous cycle * * ranges). A 'tick' (as used with the "TICK" keyword, above)* * has been redefined as 1/60th of a second. Finally, the * * source code in 2View.c has been split into two files * * (2View.c and Misc.c). * * Released 3/24/92 * * * * 1.11 - Improved error reporting (with this version, if the user * * run 2View from Workbench and there's an error, a requester * * is put up. Previously, the user was not notified at all * * of the error). * * Released 9/11/91 * * * * 1.10 - Added support for Workbench, ARexx, scrollable bitmaps, * * and printing. Also, the user can now use CTRL-C to advance* * to the next frame, and CTRL-D to abort a playlist. * * Released 9/3/91 * * * * 1.00 - Original version. Released 7/24/91 * * * \************************************************************************/ unsigned long availBytes,curPos,bufSize; /*Include files*/ #include #include #include #include #include #include #include #include /*Prototypes*/ #include #include #include /*Pragmas*/ #include #include #include #include #include /*Other include files*/ #include "iff.h" #include "2View.h" #include "arexx.h" /*Libraries we'll need*/ struct Library *IFFParseBase=NULL; struct Library *IntuitionBase=NULL; struct GfxBase *GfxBase=NULL; /*Provided by the compiler*/ extern struct Library *SysBase; extern struct Library *DOSBase; /*Generic screen and window definitions. They will be used to define*/ /*the screen and window that the various pictures will be shown on*/ struct NewScreen newScreen= { 0,0,0,0,0,1,0,NULL,CUSTOMSCREEN|SCREENBEHIND|AUTOSCROLL,NULL,NULL,NULL, NULL }; struct NewWindow newWindow = { 0,0,0,0,0,1,MENUDOWN|SELECTDOWN|ACTIVEWINDOW|VANILLAKEY, RMBTRAP|BORDERLESS|NOCAREREFRESH,NULL,NULL,NULL,NULL,NULL, 0,0,640,400,CUSTOMSCREEN }; struct Screen *screen=NULL; struct Window *window=NULL; UWORD *storage; int counter; /*A true here indicates the current ILBM file is compressed*/ BYTE Compression; /*An error message that used in various places*/ char *errorMsg="An error occured while reading"; /*The version string. Do a 'version 2View' to display it*/ char *version="$VER: QView V1.50 (24.3.92)"; /*Just so that the © message is part of the actual program*/ char *copyRightMsg="Copyright 1991-1992 by Dave Schreiber. All Rights Reserved."; BYTE ExitFlag=FALSE; /*'Exit now' flag*/ UWORD ticks=0; /*Delay requested by user.*/ /*The previous screen and window*/ struct Window *prevWindow=NULL; struct Screen *prevScreen=NULL; /*Data for a blank pointer*/ UWORD chip fakePointerData[]={0,0,0,0,0}; struct IFFHandle *iff=NULL; /*IFF handle*/ BPTR pL=NULL; /*Playlist file pointer*/ BOOL masking,print,toFront,printPics; extern struct WBStartup *WBenchMsg; char *playListFilename=NULL; /*Variables that have to be global so that ARexx.c can access them*/ ButtonTypes rexxAbort=none; extern void dispRexxPort(void); extern void dnRexxPort(); long arexxSigBit; UWORD ticksRemaining=0; BOOL loop=FALSE; BYTE specialModes; char *picFilename; char trashBuf[512]; /* A place to dump mask information */ char *buf=trashBuf; void _main(); struct TagItem TagList[]= { /* This defines what part of the displayed picture is shown. It's */ /* necessary to have a line like this in here in order to get */ /* 2.0 autoscrolling to work. */ {SA_Overscan,OSCAN_VIDEO}, {TAG_DONE,NULL} }; char *about1="2View"; char *about2="Please"; extern struct EasyStruct erError1Line; BOOL cycle=FALSE; UBYTE numColors; UWORD destMap[32]; UBYTE numCycleColors; UBYTE rate; /* The assembly-language reader routine */ extern int ReadIntoBitMapAsm(PLANEPTR,APTR,struct IFFHandle *, ULONG,ULONG,UWORD,UBYTE,UBYTE,UBYTE); char **filenames; UWORD numFilenames=0,numSlots; void _main() { UWORD c; LONG args[7]; char **filenames; char curFilename[140]; BYTE playList; /*True if a playlist is being used, false otherwise*/ /*Initialize the argument buffers to NULL*/ for(c=0;c<7;c++) args[c]=NULL; /*Open the libraries*/ IFFParseBase=(struct Library *)OpenLibrary("iffparse.library",0L); if(IFFParseBase==NULL) { cleanup(); exit(50); } IntuitionBase=(struct Library *)OpenLibrary("intuition.library",0L); if(IntuitionBase==NULL) { cleanup(); exit(75); } GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L); if(GfxBase==NULL) { cleanup(); exit(85); } /*Get the arguments*/ if(WBenchMsg==NULL) { ParseArgs(args); /*If a playlist filename was provided, store it for later use*/ if(args[4]!=NULL) { playListFilename=(char *)args[4]; playList=TRUE; } else /*Otherwise, read the filenames from the command line*/ playList=FALSE; /*If a time was provided (in ticks), use it*/ if(args[1]!=NULL) ticks=*(ULONG *)args[1]*50; /*If a time was provided (in seconds), use it (overrides ticks)*/ if(args[2]!=NULL && *(ULONG *)args[2]!=0) ticks=*(ULONG *)args[2]; /*If neither a picture filename, nor a playlist filename, was*/ /*specified, print an error and exit.*/ if(args[0]==NULL && !playList) { printError("Please enter one or more filenames",""); cleanup(); exit(45); } /*Determine if we should print the pictures we display or not*/ printPics=(args[5]!=NULL); /*Get the pointer to the list of filename*/ filenames=(char **)args[0]; /*Will we loop back to the beginning once we finish displaying all*/ /*the pictures?*/ loop=(args[3]!=NULL); } else if(WBenchMsg->sm_NumArgs==1) { EasyRequest(NULL,&erError1Line,NULL, "2View V1.50 (March 24, 1992)", "Written by Dave Schreiber"); cleanup(); exit(0); } /* Initialize the ARexx port */ arexxSigBit=initRexxPort(); if(arexxSigBit==0) { cleanup(); exit(47); } /*Allocate the IFF structure*/ iff=AllocIFF(); /*If the allocation failed, abort*/ if(iff==NULL) { printError("Couldn't allocate necessary resources",""); cleanup(); exit(100); } /*Run until we run out of filenames, or the user aborts*/ while(!ExitFlag) { picFilename=curFilename; /*Get a pointer to the filename buffer*/ /*Check to see if we're running from Workbench. If so, and the*/ /*user provided names of pictures to display (by clicking on their*/ /*icons), display those pictures*/ if(WBenchMsg!=NULL && WBenchMsg->sm_NumArgs>1) { CurrentDir(WBenchMsg->sm_ArgList[1].wa_Lock); picFilename=WBenchMsg->sm_ArgList[1].wa_Name; } else if(playList) /*If a playlist is being used*/ { pL=Open(playListFilename,MODE_OLDFILE); /*Open the playlist*/ if(pL==NULL) /*If we couldn't open the playlist, abort*/ { printError("Can't open playlist",""); cleanup(); exit(199); } do /*Loop until we run out of playlist, or get a valid name*/ { if(FGets(pL,picFilename,140)==NULL) /*If end-of-file*/ picFilename=NULL; /*Set as NULL as a flag*/ } while(picFilename!=NULL && picFilename[0]==0x0A); if(picFilename!=NULL) /*If not NULL, it's a valid filename*/ picFilename[strlen(picFilename)-1]=NULL; /*Remove the linefeed*/ } else /*Otherwise, if a playlist isn't being used, get the current*/ picFilename=filenames[0]; /*filename*/ /*Loop while the user hasn't requested an abort, and while*/ /*there are still files to display*/ for(c=0;!ExitFlag && picFilename!=NULL;c++) { if((iff->iff_Stream=Open(picFilename,MODE_OLDFILE))==NULL) { /*If the ILBM file can't be opened...*/ /*Print an error...*/ printError("Can't open: ", picFilename); cleanup(); exit(200); } InitIFFasDOS(iff); /*The IFF file will be read from disk*/ OpenIFF(iff,IFFF_READ); /*Make iffparse.library aware of the*/ /*ILBM file*/ /*Read in the file and display*/ ReadAndDisplay(picFilename,iff); CloseIFF(iff); /*Release iffparse's hold on the file*/ Close(iff->iff_Stream); /*Close the file*/ /*Get the next filename, either from Workbench,*/ if(WBenchMsg!=NULL) { if(WBenchMsg->sm_NumArgs > c+2) { CurrentDir(WBenchMsg->sm_ArgList[c+2].wa_Lock); picFilename=WBenchMsg->sm_ArgList[c+2].wa_Name; } else picFilename=NULL; } else if(playList) /*The playlist*/ { do { if(FGets(pL,picFilename,140)==NULL) picFilename=NULL; } while(picFilename!=NULL && picFilename[0]==0x0A); if(picFilename!=NULL) picFilename[strlen(picFilename)-1]=NULL; } else /*or the command line*/ picFilename=filenames[c+1]; } /*We're finished with this run of pictures*/ if(playList) /*Close playlist, if open*/ Close(pL); pL=NULL; if(!loop && !printPics) /*If the loop flag wasn't given, exit*/ ExitFlag=TRUE; } /*Time to exit, so close stuff*/ cleanup(); exit(0); /*And exit*/ } LONG ilbmprops[] = { ID_ILBM,ID_CMAP,ID_ILBM,ID_BMHD,ID_ILBM,ID_CAMG, ID_ILBM,ID_CRNG,ID_ILBM,ID_DRNG,ID_ILBM,ID_SHAM, ID_ILBM,ID_CTBL }; /*Read in an ILBM file and display it*/ void ReadAndDisplay(char *filename,struct IFFHandle *iff) { int error; UBYTE *bodyBuffer; /*Pointer to buffer holding 'BODY' chunk info*/ ULONG ViewModes; /*Holds the viewmodes flags*/ UWORD c; ButtonTypes button; UBYTE cycleTable[32]; UBYTE countDown; /*Structures required for IFF parsing*/ struct StoredProperty *bmhd,*cmap,*camg,*crng,*drng,*sham,*ctbl; struct ContextNode *bodyContext; /*IntuiMessage...*/ struct IntuiMessage *mesg; /*Indentify chunks that should be stored during parse*/ /*(in this case, CMAP, BMHD, CRNG, DRNG, CAMG, and SHAM)*/ error=PropChunks(iff,ilbmprops,7); /*If there was an error, print a message and return*/ if(error!=0) { printError(errorMsg,filename); ExitFlag=TRUE; return; } /*Tell iffparse to stop at a 'BODY' chunk*/ error=StopChunk(iff,ID_ILBM,ID_BODY); /*Error handling yet again*/ if(error!=0 && error!=-1) { printError(errorMsg,filename); ExitFlag=TRUE; return; } /*Do the actual parsing*/ error=ParseIFF(iff,IFFPARSE_SCAN); /*Check for errors yet again*/ if(error!=0 && error !=-1) { printError(errorMsg,filename); ExitFlag=TRUE; return; } /*Get the chunks that were found in the file*/ bmhd = FindProp(iff,ID_ILBM,ID_BMHD); /*Bitmap information*/ cmap = FindProp(iff,ID_ILBM,ID_CMAP); /*Color map*/ camg = FindProp(iff,ID_ILBM,ID_CAMG); /*Amiga viewmode information*/ crng = FindProp(iff,ID_ILBM,ID_CRNG); /*Color-cycling ranges*/ drng = FindProp(iff,ID_ILBM,ID_DRNG); /*New (DPaint IV) color-cycling*/ sham = FindProp(iff,ID_ILBM,ID_SHAM); /*SHAM color tables*/ ctbl = FindProp(iff,ID_ILBM,ID_CTBL); /*Macro Paint color table info*/ /*Get the descriptor for the BODY chunk*/ bodyContext=CurrentChunk(iff); /*If there wasn't a BMHD, CMAP, or BODY chunk, abort*/ if(!bmhd | !cmap | !bodyContext) { printError(filename,"is corrupted or is not in Amiga ILBM format"); ExitFlag=TRUE; return; } /*Prepare to determine screen modes*/ newScreen.ViewModes=NULL; /*If there was a CAMG chunk, use it to get the viewmodes*/ if(camg!=NULL) { ViewModes=( ((CAMG *)(camg->sp_Data))->viewmodes ); if(ViewModes & HAM) newScreen.ViewModes|=HAM; if(ViewModes & EXTRA_HALFBRITE) newScreen.ViewModes|=EXTRA_HALFBRITE; if(ViewModes & LACE) newScreen.ViewModes|=LACE; if(ViewModes & HIRES) newScreen.ViewModes|=HIRES; } if(crng==NULL) if(drng==NULL) /*No color cycling*/ numCycleColors=0; else /* DPaint-IV--style color cycling*/ numCycleColors=interpretDRNG(cycleTable,(DRNG *)(drng->sp_Data),&rate); else /*DPaint I-III--style color cycling*/ numCycleColors=interpretCRNG( cycleTable,(CRNG *)(crng->sp_Data),&rate); if(numCycleColors != 0) cycle=TRUE; /*Start cycling if there are colors to cycle*/ else cycle=FALSE; /*Interpret the BMHD chunk*/ getBMHD((struct BitMapHeader *)bmhd->sp_Data); /*Don't open an interlace screen if the image is in SHAM mode*/ /*(the Amiga OS doesn't properly handle user copper lists on */ /*interlaced screens for some reason)*/ if(sham!=NULL) newScreen.ViewModes&=~LACE; /*Open a screen, defined by the BMHD and CAMG chunks*/ screen=OpenScreenTagList(&newScreen,TagList); /*If the screen couldn't be opened, abort*/ if(screen==NULL) { printError("Cannot open screen!",""); ExitFlag=TRUE; return; } /*This more properly centers the screen, for some reason */ MoveScreen(screen,1,1); MoveScreen(screen,-1,-1); /*Set the window dimensions from the screen dimensions*/ newWindow.Screen=screen; newWindow.Width=newScreen.Width; newWindow.Height=newScreen.Height; /*Open the window*/ window=OpenWindow(&newWindow); /*Abort if the window couldn't be opened*/ if(window==NULL) { printError("Cannot open window!",""); ExitFlag=TRUE; return; } /*Allocate enough memory to hold the BODY data*/ /*We want to find out what the biggest block of memory is. If*/ /*we have enough memory to hold the entire body chunk, we'll load*/ /*the whole thing into memory and decompress from there. If not,*/ /*we'll load as much as we can and refill the buffer when it*/ /*empties*/ /*We do a Forbid() to keep anyone from allocating memory between*/ /*the AvailMem() and AllocMem() calls. This way, we're guaranteed*/ /*that a memory block of the size given by Availmem() can be */ /*allocated. For this reason, we don't need to check the result*/ /*of the AllocMem(); we're guaranteed that it worked*/ Forbid(); bufSize=AvailMem(MEMF_LARGEST); bufSize=MIN(bufSize,bodyContext->cn_Size+1); if(bufSize==0) /*It'll never happen, but just in case...*/ { Permit(); ExitFlag=TRUE; return; } bodyBuffer=AllocMem(bufSize,0L); Permit(); availBytes = bufSize; curPos = bufSize; /*Blank out the pointer*/ SetPointer(window,fakePointerData,1,16,0,0); /*Set the screen colors to those provided in the CMAP chunk*/ setScreenColors(screen,cmap->sp_Data,newScreen.Depth,destMap,&numColors); /*Uncompress an ILBM and copy it into the bitmap*/ if(ReadIntoBitmapAsm(&(screen->BitMap.Planes),bodyBuffer,iff,bufSize, (ULONG)screen->BitMap.BytesPerRow, screen->BitMap.Rows, screen->BitMap.Depth,(Compression==1) ? 1 : 0, (masking) ? 1 : 0)) { printError(errorMsg,""); cleanup(); exit(2000); } /*Activate the window, and flush any IDCMP message*/ ActivateWindow(window); while((mesg=(struct IntuiMessage *)GetMsg(window->UserPort))!=NULL) ReplyMsg((struct Message *)mesg); /*If this is a SHAM image, setup the copper list appropriately*/ if(sham!=NULL) { specialModes=SHAM; setupSHAM(screen,(UWORD *)(sham->sp_Data)); } else /*If this is a MacroPaint image, setup the copper list*/ if(ctbl!=NULL) { specialModes=MACROPAINT; setupDynHires(screen,(UWORD *)(ctbl->sp_Data)); } else /* Otherwise, this is a normal ILBM*/ specialModes=NORMAL; /*Bring the screen to the front*/ ScreenToFront(screen); /*If the user used the 'print' flag on the command line, print*/ /*the picture (but not if this is a SHAM or MacroPaint image)*/ if(printPics && specialModes == NORMAL) dumpRastPort(&(screen->RastPort),&(screen->ViewPort)); print=TRUE; /*Close the previous window and screen*/ if(prevWindow!=NULL) CloseWindow(prevWindow); if(prevScreen!=NULL) CloseScreen(prevScreen); /*Free the buffer that holds the BODY chunk information*/ FreeMem(bodyBuffer,bufSize); /*Store the current window & screen structures, so they can be*/ /*closed later*/ prevWindow=window; prevScreen=screen; screen=NULL; window=NULL; rexxAbort=none; countDown=rate; if(ticks==0) /*If ticks==0, this means that no delay was specified*/ { /*by the user. So just wait for him to click a button*/ int prevTopEdge=prevScreen->TopEdge; while((button=checkButton())==none && rexxAbort==none) { /*Wait for 1/60th of a second*/ WaitTOF(); /*Refresh the SHAM copper list if required*/ if(prevTopEdge!=prevScreen->TopEdge && sham!=NULL) { prevTopEdge=prevScreen->TopEdge; setupSHAM(prevScreen,(UWORD *)(sham->sp_Data)); } /*Refresh the MacroPaint copper list if required*? if(prevTopEdge!=prevScreen->TopEdge && ctbl!=NULL) { prevTopEdge=prevScreen->TopEdge; setupDynHires(prevScreen,(UWORD *)(ctbl->sp_Data)); } /*If its time to cycle the colors, then cycle them*/ if(cycle && numCycleColors!=0 && --countDown==0) { cycleColors(cycleTable,destMap,numCycleColors,numColors); countDown=rate; } dispRexxPort(); } /*Check to see if the user wants to abort*/ if(button==menu || rexxAbort==menu) ExitFlag=TRUE; } else /*Otherwise, wait for the specified amount of time*/ { for(c=0;c