/***********************************************************************\ * 2View V1.11 * * A simple, fast ILBM viewer, for use under AmigaOS V2.x. * * Written and ©1991 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/50ths 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 * * blink with 2View.lnk * * * * Version history: * * 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 #include #include /*Prototypes*/ #include #include #include #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 Library *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|ACTIVATE,NULL,NULL,NULL,NULL,NULL, 0,0,640,400,CUSTOMSCREEN }; struct Screen *screen=NULL; struct Window *window=NULL; /*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.11 (11.9.91)"; /*Just so that the © message is part of the actual program*/ char *copyRightMsg="Copyright 1991 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}; BPTR StdErr=NULL; /*'Standard error' for AmigaDOS IO functions*/ 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; char *picFilename; char buf[512]; /* A place to dump mask information */ 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"; 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 Library *)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.11 (September 11, 1991)", "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 }; /*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; /*Structures required for IFF parsing*/ struct StoredProperty *bmhd,*cmap,*camg; struct ContextNode *bodyContext; /*IntuiMessage...*/ struct IntuiMessage *mesg; /*Indentify chunks that should be stored during parse*/ /*(in this case, CMAP, BMHD, and CAMG)*/ error=PropChunks(iff,ilbmprops,3); /*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 BMHD, CMAP, and CAMG chunks that were read from the file*/ bmhd = FindProp(iff,ID_ILBM,ID_BMHD); cmap = FindProp(iff,ID_ILBM,ID_CMAP); camg = FindProp(iff,ID_ILBM,ID_CAMG); /*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; } /*Interpret the BMHD chunk*/ getBMHD((struct BitMapHeader *)bmhd->sp_Data); /*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; } /*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); /*Put the BODY chunk information into the bitmap*/ ReadBodyIntoBitmap(&(screen->BitMap),bodyBuffer,bufSize); /*Activate the window, and flush any IDCMP message*/ ActivateWindow(window); while((mesg=(struct IntuiMessage *)GetMsg(window->UserPort))!=NULL) ReplyMsg((struct Message *)mesg); /*Bring the screen to the front*/ ScreenToFront(screen); /*If the user used the 'print' flag on the command line, print*/ /*the picture*/ if(printPics) 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; 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*/ while((button=checkButton())==none && rexxAbort==none) { Wait((1<UserPort->mp_SigBit) | arexxSigBit); dispRexxPort(); } if(button==menu || rexxAbort==menu) ExitFlag=TRUE; } else /*Otherwise, wait for the specified amount of time*/ { for(c=0;cViewPort),i,colorMap[i*3]>>4,colorMap[i*3+1]>>4, colorMap[i*3+2]>>4); return; } /*Transform the information in the BODY chunk into a displayable picture,*/ /*decompressing if necessary*/ void ReadBodyIntoBitmap(struct BitMap *bm,UBYTE *buffer,ULONG bufferSize) { register int d; register int repl; register UBYTE toRepl; BYTE *src,*dest; register int depth,rows; register int bytesInRow; register int reqDepth; src=buffer; bytesInRow=((newScreen.Width+15)/8) & (~1); /*If there's a mask plane, we have an extra bitplane to read in*/ reqDepth=(masking) ? newScreen.Depth+1 : newScreen.Depth; /*Loop once for each row*/ for(rows=0;rowsPlanes[depth][bytesInRow*rows]); if(Compression==1) /*If the file is compressed, decompress*/ { /*Loop for each byte in the destination row*/ for(d=0;d= availBytes) { ReadChunkBytes(iff,src,bufSize); curPos=0; } /*Note: n = the value in the current byte. [n]+1 = the*/ /*value in the next byte*/ /*If 128>n>=0, copy the next n+1 bytes literally*/ if(src[curPos]>=0 && src[curPos]<128) { repl=src[curPos++]+1; /*If there aren't n+1 bytes in the buffer, transfer what */ /*is there to the bitmap, then refill the buffer*/ if(curPos+repl > bufSize) { repl-=(bufSize-curPos); CopyMem(&src[curPos],&dest[d],bufSize-curPos); d+=(bufSize-curPos); ReadChunkBytes(iff,src,bufSize); curPos=0; } CopyMem(&src[curPos],&dest[d],repl); curPos+=repl; d+=repl; } else if(src[curPos] < 0) /*If n < 0, the next -n+1 bytes*/ { /*are to be set equal to [n]+1*/ repl=-src[curPos++]+1+d; if(curPos >= availBytes) { ReadChunkBytes(iff,src,bufSize); curPos=0; } toRepl=src[curPos++]; for(;d bufSize) { repl-=(bufSize-curPos); CopyMem(&src[curPos],&dest[d],bufSize-curPos); d+=(bufSize-curPos); ReadChunkBytes(iff,src,bufSize); curPos=0; } CopyMem(&src[curPos],&dest[d],repl); curPos+=repl; } } return; } /*Make a newScreen structure using the BMHD chunk*/ void getBMHD(struct BitMapHeader *bmhd) { if(bmhd->w > 400 && bmhd->w <=704) /*Define the screen as hires*/ newScreen.ViewModes|=HIRES; /*If wider than 400 pixels*/ if(bmhd->h > 300 && bmhd->h <=512) /*Define the screen as interlaced*/ newScreen.ViewModes|=LACE; /*if the height is > 300 pixels*/ newScreen.Width=bmhd->w; /*Store the rest of the values*/ newScreen.Height=bmhd->h; newScreen.LeftEdge=bmhd->x; newScreen.TopEdge=bmhd->y; newScreen.Depth=bmhd->nplanes; masking=(bmhd->Masking == 1); Compression=bmhd->Compression; /*Compression flag. Store for*/ /*later use*/ return; } /*Data structures for ReadArgs()*/ struct RDArgs ra= { {NULL,0,0}, NULL, buf, 512, "FILE/A/M,SECS=SECONDS/K/N,TICKS/K/N,LOOP/S,FROM/K,PRINT/S" }; /*Parse the argument list, using ReadArgs()*/ void ParseArgs(ULONG *args) { ReadArgs("FILE/A/M,SECS=SECONDS/K/N,TICKS/K/N,LOOP/S,FROM/K,PRINT/S", args,&ra); return; } /*Check to see which mouse buttons have been pressed*/ ButtonTypes checkButton(void) { struct IntuiMessage *mesg; ButtonTypes Button=none; static justActivated=FALSE; /*This function disregards a select (left) mouse button click*/ /*if the window's just been activated. This is so that a user*/ /*can click on another window, then make this one active again,*/ /*without advancing to the next picture*/ /*While there are messages to be read...*/ while((mesg=(struct IntuiMessage *)GetMsg(prevWindow->UserPort))!=NULL) { /*Interpret them*/ switch(mesg->Class) { case ACTIVEWINDOW: /*Set the appropriate flag if the window*/ justActivated=TRUE; /*was just activated*/ break; case VANILLAKEY: switch(mesg->Code) { case 16: /*CTRL-P - Print (if this picture hasn't been*/ if(print) /*printed; this is designed in case the user*/ { /*holds down CTRL-P: we don't want 10-20 */ /*print requests to get queued up */ dumpRastPort(&(prevScreen->RastPort), &(prevScreen->ViewPort)); print=FALSE; } break; case 4: /*CTRL-D - Abort everything*/ Button=menu; break; case 3: Button=select; /*CTRL-C - Advance to next picture*/ break; } break; case MOUSEBUTTONS: /*Interpret a button click*/ if(mesg->Code==SELECTDOWN) /*If the left button was pushed,*/ if(justActivated) /*and not so as to activate the*/ { /*window, advance to the next*/ justActivated=FALSE; /*screen*/ break; } else Button=select; else if(mesg->Code == MENUDOWN) /*If the right button was*/ Button=menu; /*pushed, we'll want to*/ break; /*abort*/ } ReplyMsg((struct Message *)mesg); /*Reply to the message*/ } return(Button); /*Return the results*/ } /*This prints an error to the console, if we were run from the CLI*/ /*This is done instead of using Output() so as to get around any redirection*/ /*that may be in place (just like the standard C stderr)*/ /*If we can't open a StdErr or 2View was run from Workbench, a requester */ /*is put up*/ void printError(char *error1,char *error2) { if(StdErr==NULL) StdErr=Open("CONSOLE:",MODE_OLDFILE); /* If we can't open StdErr, or Output()==NULL (meaning we're running */ /* Workbench), put up a requester */ if(StdErr==NULL || Output()==NULL) { if(error2==NULL || error2[0]==NULL) EasyRequest(NULL,&erError1Line,NULL,error1,"Exiting..."); else EasyRequest(NULL,&erError2Line,NULL,error1,error2,"Exiting..."); } else { FPuts(StdErr,error1); FPuts(StdErr,error2); FPuts(StdErr,"\nExiting\n"); } return; } /*Free allocated resources in anticipation of quitting*/ void cleanup() { /*Close the ARexx port*/ dnRexxPort(); /*Close the standard-error file if opened*/ if(StdErr!=NULL) Close(StdErr); /*Close a previous screen and window, if open*/ if(prevWindow!=NULL) CloseWindow(prevWindow); if(prevScreen!=NULL) CloseScreen(prevScreen); /*Close a current screen and window, if open*/ if(window!=NULL) CloseWindow(window); if(screen!=NULL) CloseScreen(screen); if(iff!=NULL) FreeIFF(iff); if(pL!=NULL) Close(pL); if(IFFParseBase!=NULL) CloseLibrary(IFFParseBase); if(IntuitionBase!=NULL) CloseLibrary(IntuitionBase); if(GfxBase!=NULL) CloseLibrary(GfxBase); } /*Print the specified RastPort (whose ViewPort is pointed to by vp*/ BOOL dumpRastPort(struct RastPort *rp,struct ViewPort *vp) { struct IODRPReq *printerMsg; struct MsgPort *printerPort; static BOOL ableToPrint=TRUE; if(ableToPrint) { ableToPrint=FALSE; printerPort=CreatePort("2View.print.port",0); if(printerPort!=NULL) { printerMsg=(struct IORequest *)CreateExtIO(printerPort, (long)sizeof(struct IODRPReq)); if(printerMsg != NULL) { /*Open the printer device*/ if(OpenDevice("printer.device",0,printerMsg,0)==0) { /*Set up the IODRPReq structure*/ printerMsg->io_Command=PRD_DUMPRPORT; printerMsg->io_RastPort=rp; printerMsg->io_ColorMap=vp->ColorMap; printerMsg->io_Modes=vp->Modes; printerMsg->io_SrcX=0; printerMsg->io_SrcY=0; printerMsg->io_SrcWidth=vp->DWidth; printerMsg->io_SrcHeight=vp->DHeight; printerMsg->io_Special=SPECIAL_ASPECT|SPECIAL_FULLROWS| SPECIAL_FULLCOLS; /*Do it*/ if(DoIO(printerMsg)==0) ableToPrint=TRUE; CloseDevice(printerMsg); } DeleteExtIO(printerMsg); } DeletePort(printerPort); } return(ableToPrint); } } /*End of 2View.c*/