This is MovieView.m in view mode; [Download] [Up]
#include <objc/NXBundle.h> #include <dpsclient/psops.h> #include "wraps.h" #import "MovieView.h" /* * Movie 2.51 - 5/7/92 pjf * * Differences between 2.5 and 2.51: * - the save: method actually has a prayer of working when the * user saves on top of an existing movie and the old copy can't be renamed. * * Differences between 2.0 and 2.5: * - buttons to control cache depth * * - turn off multiframe .tiffs by default (define BC_VERSION_1 if you * have a .tiff movie and are too lazy to use tiffutil to turn * it into an .anim directory) * * - now able to save movie (currently-selected depth) * */ #define maxFrames 1024 @implementation MovieView void error(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); kill(getpid(),6); } // - initFrame:(const NXRect *) frameRect { const char *x; [(self = [super initFrame:frameRect]) allocateGState]; state = STOPPED; mode = LOOP; maxSize.width = maxSize.height = -1.0; movieFrame = NULL; frameCount = 0; anim = nil; pingDuringDisplay=NO; x=NXGetDefaultValue("Movie","DefaultDepth"); if (!x) dmode=D_DEF; /* use default depth */ else switch(atoi(x)) { default: case 0: dmode=D_DEF; break; case 2: dmode=D_2; break; case 8: dmode=D_8; break; case 12: dmode=D_12; break; case 24: dmode=D_24; break; }; updateControls = NO; showFrameNumber = NO; noOriginals = NO; fromStream = NO; return self; } - drawSelf:(const NXRect *) rects :(int)count { NXImage *image; NXPoint origin = {0.0,0.0}; if (!movieFrame) return nil; image = movieFrame[currentFrameIndex].image; if (!image) return self; [image composite:NX_COPY toPoint:&origin]; if (!fromStream && (state==LOADING || showFrameNumber)) { NXRect r; [self getBounds:&r]; if (state==LOADING) PSWtext(r.size.width, r.size.height, "Loading ...", currentFrameIndex+1); else PSWframe(r.size.width, r.size.height, currentFrameIndex+1); } if (pingDuringDisplay) NXPing(); if ((frameCount >= (int)frameRate)) { if (state != STOPPED && state != LOADING) { double t=[anim getDoubleRealTime]+[anim getSyncTime]; double afps=frameCount/(t-oldt); [actualFpsText setDoubleValue:afps]; oldt=t; } frameCount=0; } if (updateControls) { [fNumSlider setIntValue:currentFrameIndex]; [fNumText setIntValue:currentFrameIndex]; }; return self; } // - (BOOL)open:sender { #ifdef BC_VERSION_1 const char *const types[] = { "tiff", "anim", "mpg", "mpeg", (const char *) NULL }; #else const char *const types[] = { "anim", "mpg", "mpeg", (const char *) NULL }; #endif id pan = [OpenPanel new]; const char *const *filenames; char filename[FILENAME_MAX]; if (![pan runModalForTypes:types]) return NO; if ((filenames = [pan filenames]) == NULL) return NO; sprintf(filename,"%s/%s", [pan directory], filenames[0]); strcpy(moviePath,filename); return [self openFile:filename]; } - (BOOL)play:sender { fromStream = YES; noOriginals = YES; return [self open:sender]; } - makeWindow; { Window *w=[self window]; [w sizeWindow:maxSize.width:maxSize.height]; /* will recache images */ [w setMiniwindowIcon:"movieDoc.tiff"]; [w makeKeyAndOrderFront:self]; [w display]; return self; } - makePanel:(char *)filename; { char ptitle[FILENAME_MAX]; char *ptr=rindex(filename,'/')+1; if (ptr == (char *)1) ptr=filename; sprintf(ptitle,"Controls for %s",ptr); [panel setTitle:ptitle]; [panel setNextResponder:[self window]]; [[fNumSlider setMaxValue:numFrames-1] setEnabled:YES]; [nFramesText setIntValue:numFrames-1]; [depthButtons selectCellAt:(int)dmode:0]; [panel orderFront:self]; return self; } - setFps:(float)fps; { frameRate = fps; [fpsSlider setFloatValue:fps]; [fpsText setFloatValue:fps]; return self; } - setNoOriginals; { int i; Window *w=[self window]; if (noOriginals) return self; if (movieFrame) for (i=0; i<numFrames; i++) if (movieFrame[i].original) { [movieFrame[i].original free]; movieFrame[i].original = nil; } noOriginals = TRUE; [w setMinSize:&maxSize]; [w setMaxSize:&maxSize]; return self; } // openFile: returns YES if the frames were successfully read, NO if not. - (BOOL)openFile:(char *)filename { char *ptr=rindex(filename,'.'); if (!ptr) { NXRunAlertPanel(NULL,"Impossible filename %s", NULL, NULL, NULL, filename); return NO; }; state = LOADING; [[self window] setTitleAsFilename:filename]; /* get the bitmaps */ if (!strcmp(ptr,".anim")) { /* the file is an Icon-style .anim directory */ char buf[FILENAME_MAX]; char *ptr2; *ptr='\0'; /* clobber extension */ ptr2=1+rindex(filename,'/'); /* danger danger */ if (ptr2 == (char *)1) ptr2=filename; /* if not /full/path/name */ sprintf(buf,"%s.anim/%s",filename,ptr2); [self openAnimDirectory:buf]; } else if (!strcmp(ptr,".mpg") || !strcmp(ptr,".mpeg")) { /* an MPEG file */ [self openMPEGfile:filename]; /* 30-Dec-94 -- added .mpeg case */ } /* Mike Carlton carlton@isi.edu */ #ifdef BC_VERSION_1 else if (!strcmp(ptr,".tiff")) { /* a slew o' TIFFs in one file */ bitmaps = [NXBitmapImageRep newListFromFile:filename]; if (!bitmaps) { NXRunAlertPanel(NULL,"Couldn't get bitmaps from %s", NULL,NULL,NULL, filename); return NO; }; [self allocateFrames:bitmaps]; [bitmaps free]; /* does not free elements */ [self setFps:15.0]; } #endif else { /* this shouldn't happen */ NXRunAlertPanel(NULL,"Impossible filename %s",NULL,NULL,NULL,filename); return NO; }; state = STOPPED; if (fromStream) [window close]; else [self makePanel:filename]; return YES; } - (List *)listAnimDirectory:(char *)filenameRoot { List *bitmaps = [[List alloc] init]; int i=1; while (1) { char buf[FILENAME_MAX]; NXBitmapImageRep *newbitmap; sprintf(buf,"%s.%d.tiff",filenameRoot,i++); if ((access(buf,R_OK)) == -1) break; newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf]; if (!newbitmap) { NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL, buf); [[bitmaps freeObjects] free]; return nil; } else [bitmaps addObject:newbitmap]; }; return bitmaps; } - openAnimDirectory:(char *)filenameRoot; { int i; NXBitmapImageRep *newbitmap; char buf[FILENAME_MAX]; for (i=1; TRUE; i++) { sprintf(buf,"%s.%d.tiff", filenameRoot, i); if ((access(buf, R_OK)) == -1) break; newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf]; if (!newbitmap) { NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL, buf); continue; } [self addBitmap:newbitmap]; } [self setFps:15.0]; return self; } - openMPEGfile:(char *)filename { int i, n, flen; char command[256]; FILE *fd; mpegInfo *pInfo; long data; BOOL swab; NXStream *pStream; NXBitmapImageRep *newbitmap; if (!(fd = fopen(filename, "r"))) error("Could not open %s", filename); fread(&data, 4, 1, fd); if (data!=0x000001b3 && data!=0xb3010000) error("%s does not contain an mpeg stream: %x", filename, data); if (data==0xb3010000) swab=YES; else swab=NO; if (!(pInfo = calloc(1, sizeof(mpegInfo)))) error("Could not allocate pInfo."); // Get horizontal and vertical size of image space // as two 12 bit words, respectively // then aspect ratio and picture rate // as two 4 bit words. fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data); pInfo->picture_rate = 0x0F & data; data >>= 4; pInfo->aspect_ratio = 0x0F & data; data >>= 4; // In Motorola format, least significant bits come last // v_size is actually the second value in the file // i.e. h:12,v:12,a:4,p:4 pInfo->v_size = 0x0FFF & data; pInfo->h_size = 0x0FFF & data >> 12; maxSize.width = ((pInfo->h_size + 15) / 16) * 16.0; maxSize.height = ((pInfo->v_size + 15) / 16) * 16.0; // Get bit rate, vbv buffer size, and constrained parameter flag fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data); // throw away (non) intra quant matrix flags data >>= 2; pInfo->const_param_flag = 1 & data; data >>= 1; pInfo->vbv_buffer_size = 0x03FF & data; data >>= 10 + 1; // 1 marker bit pInfo->bit_rate = 0x03FFFF & data; fclose(fd); pInfo->fps = pInfo->picture_rate; flen = 3 * (int)maxSize.width * (int)maxSize.height; // printf("Dimensions: %d %d, buffer size:%d\n", (int)maxSize.width, (int)maxSize.height, flen); sprintf(command, "exec %s/mpegDecode %s", [[NXBundle mainBundle] directory], filename); if (!(fd = popen(command, "r"))) error("Could not create MPEG process:\n %s", command); pStream = NXOpenFile(fileno(fd), O_RDONLY); // printf("Reading frames:\n"); for (i=0; TRUE; i++) { newbitmap = [[NXBitmapImageRep alloc] initData:NULL pixelsWide:(int)maxSize.width pixelsHigh:(int)maxSize.height bitsPerSample:8 samplesPerPixel:3 // (cSpace == RGB_COLOR) ? 3 : 1 hasAlpha:NO isPlanar:NO colorSpace:NX_RGBColorSpace bytesPerRow:0 bitsPerPixel:0]; if (4!=NXRead(pStream, &data, 4)) { // printf("Finished.\n"); [newbitmap free]; break; } // printf("Frame # %d.\n", data); n = NXRead(pStream, [newbitmap data], flen); if (n!=flen) error("Error reading image data (%d/%d bytes read).", n, flen); [self addBitmap:newbitmap]; } [self setFps:pInfo->fps]; return self; } - addBitmap:bm; { NXSize sz; NXRect r; int flen, flag=0; [bm getSize:&sz]; if (sz.width > maxSize.width) maxSize.width=sz.width, flag=1; if (sz.height > maxSize.height) maxSize.height=sz.height, flag=1; if (flag || !movieFrame) [[self window] sizeWindow:maxSize.width:maxSize.height]; flen = ([bm pixelsWide]*[bm pixelsHigh]*[bm bitsPerPixel])>>3; if (numFrames*flen>20000000) [self setNoOriginals]; if (!movieFrame) { numFrames=1; NX_MALLOC(movieFrame, movieFrameStruct, numFrames); } else { numFrames++; NX_REALLOC(movieFrame, movieFrameStruct, numFrames); } currentFrameIndex=numFrames-1; if (!noOriginals) movieFrame[currentFrameIndex].original=bm; else movieFrame[currentFrameIndex].original=nil; // printf("Frame # %d, Size: %8.3f %8.3f\n", currentFrameIndex, sz.width, sz.height); if (!fromStream || !currentFrameIndex) { movieFrame[currentFrameIndex].image=[[NXImage alloc] initSize:&sz]; [movieFrame[currentFrameIndex].image setUnique:YES]; /* make caches disjoint */ [movieFrame[currentFrameIndex].image setBackgroundColor:NX_COLORBLACK]; } else { movieFrame[currentFrameIndex].image=movieFrame[currentFrameIndex-1].image; } [self getBounds:&r]; if ([movieFrame[currentFrameIndex].image lockFocus]) { [bm drawIn:&r]; [movieFrame[currentFrameIndex].image unlockFocus]; } else error("Could not lock focus on image"); if (noOriginals) [bm free]; if (numFrames==1) [self makeWindow]; else [self display]; NXPing(); return self; } NXWindowDepth deps[] = { NX_DefaultDepth, NX_TwoBitGrayDepth, NX_EightBitGrayDepth, NX_TwelveBitRGBDepth, NX_TwentyFourBitRGBDepth /*,NX_PurinaCatChow__ChowChowChowDepth*/ }; // set up Frame data structures and find max frame size - allocateFrames:(List *)frames { int i; numFrames=[frames count]; NX_MALLOC(movieFrame,movieFrameStruct,numFrames); for(i=0;i<numFrames;i++) { NXImage *nxi; NXBitmapImageRep *bm=[frames objectAt:i]; NXSize sz; [bm getSize:&sz]; movieFrame[i].original=bm; nxi=movieFrame[i].image=[[NXImage alloc] initSize:&sz]; [nxi setUnique:YES]; /* make caches disjoint */ [nxi setBackgroundColor:NX_COLORBLACK]; /* keep track of largest frame */ if (sz.width > maxSize.width) maxSize.width=sz.width; if (sz.height > maxSize.height) maxSize.height=sz.height; }; return self; } /***************************************************************** *****************************************************************/ - superviewSizeChanged:(NXSize *)old { [anim stopEntry]; [super superviewSizeChanged:old]; if (noOriginals) { if (!fromStream) NXRunAlertPanel("Resize","Can't resize, no originals.",NULL,NULL,NULL); } else if (state!=LOADING) { [self recache]; [self renderFrames]; } if (movieFrame) [[self window] display]; NXPing(); [anim resetRealTime]; [anim startEntry]; return self; } - renderFrames { int cfi; NXRect r; [self getBounds:&r]; cfi = currentFrameIndex; state = LOADING; for(currentFrameIndex=0; currentFrameIndex<numFrames; currentFrameIndex++) { if ([movieFrame[currentFrameIndex].image lockFocus]) { [movieFrame[currentFrameIndex].original drawIn:&r]; [movieFrame[currentFrameIndex].image unlockFocus]; } else { fprintf(stderr,"Barf: couldn't lockFocus on image %d\n", (int)movieFrame[currentFrameIndex].image); abort(); } [self display]; NXPing(); } state = STOPPED; currentFrameIndex = cfi; return self; } - recache // assume depth & size both changed // // Appkit bug? Can one render down from 24 bit color to 2 bit gray? // { NXRect r; int i; [self getBounds:&r]; [self freeCaches]; for(i=0; i<numFrames; i++) { movieFrame[i].image=[[NXImage alloc] initSize:&r.size]; [movieFrame[i].image useCacheWithDepth:deps[(int)dmode]]; }; return self; } - save:sender { const char *type = "anim"; // will only save in .anim format. SavePanel *sp = [SavePanel new]; [sp setDelegate:self]; [sp setRequiredFileType:type]; if ([sp runModal]) { // OK was hit int i; char cwd[MAXPATHLEN]; /* if directory exists, rename it with a wiggle in back. */ if (access([sp filename],F_OK) == 0) { /* I could do this with a couple of calls to system(), but noooo, * I had to do it the had way. yeccch. */ char *buf=malloc(strlen([sp filename]+2)); sprintf(buf,"%s~",[sp filename]); if (!getwd(cwd)) { NXRunAlertPanel("FATAL","Couldn't get current directory.",NULL,NULL,NULL); abort(); }; if (rename([sp filename],buf) == -1) { // sledgehammer time. struct direct *de; DIR *dp; chdir([sp filename]); dp=opendir("."); while(de=readdir(dp)) unlink(de->d_name); closedir(dp); chdir(cwd); unlink([sp filename]); }; }; mkdir([sp filename],0755); chdir([sp filename]); for(i=0;i<numFrames;i++) { char buf3[MAXPATHLEN]; char buf2[MAXPATHLEN]; char *ptr; int fd; NXStream *s; strcpy(buf3,[sp filename]); ptr=rindex(buf3,'/')+1; *(rindex(ptr,'.'))='\0'; sprintf(buf2,"./%s.%d.tiff",ptr,i+1); fd=open(buf2,O_WRONLY|O_CREAT,0644); s=NXOpenFile(fd,NX_WRITEONLY); [movieFrame[i].image writeTIFF:s]; NXClose(s); close(fd); }; chdir(cwd); }; return self; } - (BOOL) panelValidateFilenames:sender { if (!strcmp([sender filename],moviePath)) { NXRunAlertPanel("Save","Cannot overwrite original movie",NULL,NULL,NULL); return NO; }; return YES; } - freeCaches { int i; for(i=0;i<numFrames;i++) [movieFrame[i].image free]; return self; } - freeOriginals { int i; for(i=0;i<numFrames;i++) { [movieFrame[i].original free]; movieFrame[i].original = nil; } return self; } - free { [self freeCaches]; [self freeOriginals]; [self freeGState]; [anim free]; anim=nil; return [super free]; } - copy:sender { char *buffer; NXStream *stream; int length, maxLength; Pasteboard *pasteboard = [Pasteboard new]; runState s=state; [anim stopEntry]; if (s!=STOPPED) [self stop:self]; [pasteboard declareTypes:&NXPostScriptPboardType num:1 owner:self]; stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); [self copyPSCodeInside:&bounds to:stream]; NXFlush(stream); NXGetMemoryBuffer(stream, &buffer, &length, &maxLength); [pasteboard writeType:NXPostScriptPboard data:buffer length:length]; NXCloseMemory(stream, NX_FREEBUFFER); switch(s) { case STOPPED: case LOADING: break; case FORWARD: case REVERSE: [anim startEntry]; break; }; return self; } - tick:sender { int next, end=(state == FORWARD) ? numFrames-1 : 0; switch(mode) { case ONCE: if (currentFrameIndex == end) { [self stop:self]; return self; } else currentFrameIndex += (int)state; break; case LOOP: next = currentFrameIndex + (int)state; if (((state == FORWARD)&&(next>end)) || ((state == REVERSE)&&(next<end))) { currentFrameIndex = (state < 0) ? numFrames-1 : 0; } else currentFrameIndex = next; break; case BOUNCE: next = currentFrameIndex + (int)state; if (((state == FORWARD)&&(next>end)) || ((state == REVERSE)&&(next<end))) { if (state == FORWARD) [self selectStateButton:REV]; if (state == REVERSE) [self selectStateButton:FWD]; state *= -1; currentFrameIndex += (int)state; } else currentFrameIndex = next; break; }; frameCount++; [self display]; return self; } /***************************************************************** *****************************************************************/ - fwd:sender { if (numFrames < 2) return self; if (state != STOPPED) [self stop:self]; state = FORWARD; [self move:sender]; return self; } - rev:sender { if (numFrames < 2) return self; if (state != STOPPED) [self stop:self]; state = REVERSE; [self move:sender]; return self; } - move:sender { double period = 1.0/frameRate; if (numFrames < 2) { /* duh */ [self selectStateButton:STOP]; return self; }; anim = [[Animator alloc] initChronon:period adaptation:0.05 /*?*/ target:self action:@selector(tick:) autoStart:YES eventMask:0]; if (state == FORWARD) [self selectStateButton:FWD]; if (state == REVERSE) [self selectStateButton:REV]; [fNumText setStringValue:""]; [fNumSlider setEnabled:NO]; oldt=[anim getSyncTime]; frameCount = 0; return self; } - stop:sender { if (numFrames < 2) return self; switch(state) { case FORWARD: case REVERSE: [anim free]; anim=nil; case STOPPED: case LOADING: break; } state = STOPPED; [self selectStateButton:STOP]; [fNumText setIntValue:currentFrameIndex]; [fNumSlider setEnabled:YES]; [fNumSlider setIntValue:currentFrameIndex]; return self; } - fwdStep:sender { [self step:(int) FORWARD]; return self; } - revStep:sender { [self step:(int) REVERSE]; return self; } - step:(int) direction { if (numFrames < 2) return self; if (state != STOPPED) [self stop:self]; if ((currentFrameIndex = (currentFrameIndex + direction) % numFrames) < 0) currentFrameIndex = numFrames + currentFrameIndex; [self selectStateButton:STOP]; [fNumText setIntValue:currentFrameIndex]; [fNumSlider setEnabled:YES]; [fNumSlider setIntValue:currentFrameIndex]; return [self display]; } - reSize:(NXSize *)s { if (noOriginals) NXRunAlertPanel("Resize","Resize disabled for lack of memory.",NULL,NULL,NULL); else [[self window] sizeWindow:s->width :s->height]; return self; } - expand2x:sender { NXRect r; [self getBounds:&r]; r.size.width *= 2.0; r.size.height *= 2.0; [self reSize:&r.size]; return self; } - reduce50pct:sender { NXRect r; [self getBounds:&r]; r.size.width *= 0.5; r.size.height *= 0.5; [self reSize:&r.size]; return self; } - restore:sender { [self reSize:&maxSize]; return self; } - modeButtonsChanged:sender { mode = (runMode)[sender selectedRow]; return self; } - fNumSliderChanged:sender { if (currentFrameIndex == [sender intValue]) return self; currentFrameIndex=[sender intValue]; [self stop:self]; [self display]; return self; } - fpsSliderChanged:sender { frameRate = [sender floatValue]; [fpsText setFloatValue:frameRate]; switch(state) { case FORWARD: case REVERSE: { double period = 1.0/frameRate; [anim free]; anim = [[Animator alloc] initChronon:period adaptation:0.05 target:self action:@selector(tick:) autoStart:YES eventMask:0]; break; }; case STOPPED: case LOADING: break; }; return self; } - pingButtonChanged:sender { switch([sender selectedRow]) { case 0: pingDuringDisplay=NO; break; case 1: pingDuringDisplay=YES; break; }; return self; } - selectStateButton:(runState)b { [stateButtons selectCellAt:0:((int)b)]; return self; } - depthButtonsChanged:sender { if (noOriginals) { NXRunAlertPanel("Depth","Can't change depth, no originals.",NULL,NULL,NULL); return self; } dmode=(depthMode)[sender selectedRow]; [anim stopEntry]; [self recache]; [self renderFrames]; [self display]; [anim resetRealTime]; [anim startEntry]; return self; } - updateCheckBoxChanged:sender { updateControls = !updateControls; return self; } - frameCheckBoxChanged:sender { showFrameNumber = !showFrameNumber; return self; } // Window's delegate methods - windowWillClose:sender { [panel close]; [self free]; return self; } -windowDidMiniaturize:sender { [panel orderOut:sender]; [anim stopEntry]; return self; } -windowDidDeminiaturize:sender { [panel orderFront:sender]; [anim resetRealTime]; [anim startEntry]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.