/* * xvpcd.c - load routine for 'PhotoCD' format pictures * * LoadPCD(fname, pinfo, size) - loads a PhotoCD file * * This routine will popup a choice of which of the 5 available resolutions * the user wants to choose, then load it as a 24 bit image. * * Copyright 1993 David Clunie, Melbourne, Australia. * * The outline of this is shamelessly derived from xvpbm.c to read the * file, and xvtiffwr.c to handle the popup window and X stuff (X never * has been my forte !), and the PhotoCD format information (though not * the code) was found in Hadmut Danisch's (danisch@ira.uka.de) hpcdtoppm * program in which he has reverse engineered the format by studying * hex dumps of PhotoCDs ! After all who can afford the Kodak developer's * kit, which none of us have seen yet ? Am I even allowed to mention these * words (Kodak, PhotoCD) ? I presume they are registered trade marks. * * PS. I have no idea how Halmut worked out the YCC <-> RGB conversion * factors, but I have calculated them from his tables and the results * look good enough to me. * * Added size parameter to allow the schnautzer to create thumnails * without requesting the size every time. */ #define trace (void) #define HAVE_PCD_DIALOG #include "xv.h" #include #ifdef HAVE_PCD /* comments on error handling: a truncated file is not considered a Major Error. The file is loaded, the rest of the pic is filled with 0's. not being able to malloc is a Fatal Error. The program is aborted. */ static void magnify PARM((int,int,int,int,int,byte*)); static int pcdError PARM((char*,char*)); static int gethuffdata PARM((byte*,byte*,byte*,int,int)); #define wcurfactor 16 /* Call WaitCursor() every n rows */ static char *bname; static int size; /* Set by window routines */ static int leaveitup; /* Cleared by docmd() when OK or CANCEL pressed */ static int goforit; /* Set to 1 if OK or 0 if CANCEL */ static FILE *fp; /*******************************************/ int LoadPCD(fname, pinfo,theSize) char *fname; PICINFO *pinfo; int theSize; /* The size should be -1 for the popup to ask otherwise fast is assumed */ /*******************************************/ { /* returns '1' on success */ int rv; long offset; int mag; byte *pic24, *luma, *chroma1, *chroma2, *ptr, *lptr, *c1ptr, *c2ptr; int w, h; int row, col; int huffplanes; bname = BaseName(fname); pinfo->pic = (byte *) NULL; pinfo->comment = (char *) NULL; /* open the file */ fp=fopen(fname,"r"); if (!fp) return (pcdError(bname, "can't open file")); /* base/16 - plain data starts at sector 1+2+1=4 (numbered from 0, ie. the 5th sector) - luma 192*128 = 24576 bytes (12 sectors) + chroma1 96*64 = 6144 bytes (3 sectors) + chroma2 96*64 = 6144 bytes (3 sectors) = total 18 sectors - NB. "Plain" data is interleaved - 2 luma rows 192 wide, then 1 of each of the chroma rows 96 wide ! base/4 - plain data starts at sector 1+2+1+18+1=23 - luma 384*256 = 98304 bytes (48 sectors) + chroma1 192*128 = 24576 bytes (12 sectors) + chroma2 192*128 = 24576 bytes (12 sectors) = total 72 sectors - NB. "Plain" data is interleaved - 2 luma rows 384 wide, then 1 of each of the chroma rows 192 wide ! base - plain data starts at sector 1+2+1+18+1+72+1=96 - luma 768*512 = 393216 bytes (192 sectors) + chroma1 384*256 = 98304 bytes (48 sectors) + chroma2 384*256 = 98304 bytes (48 sectors) = total 288 sectors - NB. "Plain" data is interleaved - 2 luma rows 768 wide, then 1 of each of the chroma rows 384 wide ! 4base - plain data for base is read - luma data interpolated *2 - chroma data interpolated *4 - cd_offset is 1+2+1+18+1+72+1+288=384 - at cd_offset+4 (388) is huffman table - at cd_offset+5 (389) is 4base luma plane (the sector at cd_offset+3 seems to contain 256 words each of which is an offset presumably to the sector containing certain rows ? rows/4 given 1024 possible rows. The rest of this sector is filled with zeroes) 16base - plain data for base is read - luma data interpolated *2 - chroma data interpolated *4 - cd_offset is 1+2+1+18+1+72+1+288=384 - at cd_offset+4 (388) is huffman table for 4 base - at cd_offset+5 (389) is 4base luma plane - luma plane interpolated *2 - cd_offset is set to current position (should be start of sector) - at cd_offset+12 is huffman table for 16 base - at cd_offset+14 is 16 base luma & 2 chroma planes which are read (note that the luma plane comes first, with a sync pattern announcing each row from 0 to 2047, then the two chroma planes are interleaved by row, the row # being even from 0 to 2046, with each row containing 1536 values, the chroma1 row coming first, finally followed by a sync pattern with a row of 2048 announcing the end (its plane seems to be set to 3, ie. chroma2) - chroma planes interpolated *2 (the sector at cd_offset+10 & 11 seem to contain 1024 pairs of words the first for luma and the second for chroma, each of which is an offset presumably to the sector containing certain rows ? rows/2 given 2048 possible rows) Not yet implemented: In order to do overskip for base and 4base, one has to reach the chroma data for 16 base: - for 4base, after reading the 4base luma plane (and presumably skipping the chroma planes) one sets cd_offset to the start of the "current" sector - for base, one has to skip the 4base data first: - cd_offset is set to 384 - at (cd_offset+3 sectors)[510] is a 16 bit word high byte 1st containing an offset to the beginning of the 16base stuff though there is then a loop until >30 0xff's start a sector ! - being now positioned after the end of the 4base stuff, - at (cd_offset+10 sectors)[2] is a 16 bit word high byte 1st containing an offset to the chroma planes. - at cd_offset+12 is the set of huffman tables - for base, the 16base chroma planes are then halved */ #ifdef HAVE_PCD_DIALOG PCDSetParamOptions(bname); if (theSize == -1) { PCDDialog(1); /* Open PCD Dialog box */ SetCursors(-1); /* Somebody has already set it to wait :( */ leaveitup=1; goforit=0; /* block until the popup window gets closed */ while (leaveitup) { int i; XEvent event; XNextEvent(theDisp, &event); HandleEvent(&event, &i); } /* At this point goforit and size will have been set */ if (!goforit) { /* nothing allocated so nothing needs freeing */ return 0; } WaitCursor(); } else { size = theSize; goforit = 1; } #else /* HAVE_PCD_DIALOG */ { static char *sizeoptions[3] = { "0192*128", "1384*256","2768*512" }; size=PopUp("Which of the stored resolutions would you like ?",sizeoptions,3); } #endif /* HAVE_PCD_DIALOG */ switch (size) { case 0: pinfo->w=192; pinfo->h=128; offset=4*0x800; mag=1; huffplanes=0; sprintf(pinfo->fullInfo, "PhotoCD, base/16 resolution"); break; case 1: pinfo->w=384; pinfo->h=256; offset=23*0x800; mag=1; huffplanes=0; sprintf(pinfo->fullInfo, "PhotoCD, base/4 resolution"); break; case 2: default: pinfo->w=768; pinfo->h=512; offset=96*0x800; mag=1; huffplanes=0; sprintf(pinfo->fullInfo, "PhotoCD, base resolution"); break; case 3: pinfo->w=1536; pinfo->h=1024; offset=96*0x800; mag=2; huffplanes=1; sprintf(pinfo->fullInfo, "PhotoCD, 4base resolution"); break; case 4: pinfo->w=3072; pinfo->h=2048; offset=96*0x800; mag=4; huffplanes=2; sprintf(pinfo->fullInfo, "PhotoCD, 16base resolution"); break; } /* allocate 24-bit image */ pinfo->pic = (byte *) calloc(pinfo->w*pinfo->h*3,1); if (!pinfo->pic) FatalError("couldn't malloc '24 bit rgb plane'"); pinfo->type = PIC24; sprintf(pinfo->shrtInfo, "%dx%d PhotoCD.", pinfo->w, pinfo->h); pinfo->colType = F_FULLCOLOR; pinfo->frmType = -1; if (fseek(fp,offset,0) == -1) return pcdError(bname,"Can't find start of data."); w = pinfo->w; h = pinfo->h; pic24 = pinfo->pic; luma=(byte *) calloc(w*h,1); if (!luma) FatalError("couldn't malloc 'luma plane'"); chroma1=(byte *) calloc(w*h/4,1); if (!chroma1) FatalError("couldn't malloc 'chroma1 plane'"); chroma2=(byte *) calloc(w*h/4,1); if (!chroma2) FatalError("couldn't malloc 'chroma2 plane'"); /* Read 2 luma rows length w, then one of each chroma rows w/2 */ /* If a mag factor is active, the small image is read into the */ /* top right hand corner of the larger allocated image */ for (row=0,lptr=luma,c1ptr=chroma1,c2ptr=chroma2; row 255) r=255; if (r < 0 ) r=0; if (g > 255) g=255; if (g < 0 ) g=0; if (b > 255) b=255; if (b < 0 ) b=0; *ptr++=r; *ptr++=g; *ptr++=b; if (col%2) { ++c1ptr; ++c2ptr; } } if (row%2 == 0) { c1ptr=rowc1ptr; c2ptr=rowc2ptr; } if (row%wcurfactor == 0) WaitCursor(); } free(luma); free(chroma1); free(chroma2); rv = 1; fclose(fp); if (!rv) { if (pinfo->pic) free(pinfo->comment); if (pinfo->comment) free(pinfo->comment); pinfo->pic = (byte *) NULL; pinfo->comment = (char *) NULL; } return rv; } /*******************************************/ /* derived from Hadmut Danisch's interpolate() */ static void magnify(mag,h,w,mh,mw,p) int mag; /* power of 2 by which to magnify in place */ int h,w; /* the "start" unmag'd dimensions of the data in the array */ int mh,mw; /* the real (maximum) dimensions of the array */ unsigned char *p; /* pointer to the data */ { int x,y,yi; unsigned char *optr,*nptr,*uptr; /* MUST be unsigned, else averaging fails */ while (mag > 1) { /* create every 2nd new row from 0 */ /* even pixels being equal to the old, odd ones averaged with successor */ /* special case being the last column which is just set equal to the */ /* second last) ... */ for(y=0;y>1; /* odd averaged */ } } /* Fill in odd rows, as average of prior & succeeding rows, with */ /* even pixels average of one column, odd pixels average of two */ for(y=0;y>1; /* even pixels */ nptr[1]=(((int)optr[0])+ ((int)optr[2])+ ((int)uptr[0])+ ((int)uptr[2])+2)>>2; /* odd pixels */ nptr+=2; optr+=2; uptr+=2; } *(nptr++)=(((int)*(optr++))+ ((int)*(uptr++))+1)>>1; /* 2nd last pixel */ *(nptr++)=(((int)*(optr++))+ ((int)*(uptr++))+1)>>1; /* last pixel */ } xvbcopy((char *) (p + (2*h-2)*mw), /* 2nd last row */ (char *) (p + (2*h-1)*mw), /* the last row */ (size_t) (2*w)); /* length of a new row */ h*=2; w*=2; mag>>=1; /* Obviously mag must be a power of 2 ! */ } } /*******************************************/ static int pcdError(fname, st) char *fname, *st; { SetISTR(ISTR_WARNING, "%s: %s", fname, st); return 0; } /**** Stuff for PCDDialog box ****/ #define TWIDE 380 #define THIGH 160 #define T_NBUTTS 2 #define T_BOK 0 #define T_BCANC 1 #define BUTTH 24 static void drawTD PARM((int,int,int,int)); static void clickTD PARM((int,int)); static void doCmd PARM((int)); static void PCDSetParams PARM((void)); /* local variables */ static BUTT tbut[T_NBUTTS]; static RBUTT *resnRB; /***************************************************/ void CreatePCDW() { int y; pcdW = CreateWindow("xv pcd", "XVpcd", NULL, TWIDE, THIGH, infofg, infobg, 0); if (!pcdW) FatalError("can't create pcd window!"); XSelectInput(theDisp, pcdW, ExposureMask | ButtonPressMask | KeyPressMask); BTCreate(&tbut[T_BOK], pcdW, TWIDE-140-1, THIGH-10-BUTTH-1, 60, BUTTH, "Ok", infofg, infobg, hicol, locol); BTCreate(&tbut[T_BCANC], pcdW, TWIDE-70-1, THIGH-10-BUTTH-1, 60, BUTTH, "Cancel", infofg, infobg, hicol, locol); y = 55; resnRB = RBCreate(NULL, pcdW, 36, y, "192*128 Base/16", infofg, infobg,hicol,locol); RBCreate(resnRB, pcdW, 36, y+18, "384*256 Base/4", infofg, infobg,hicol,locol); RBCreate(resnRB, pcdW, 36, y+36, "768*512 Base", infofg, infobg, hicol, locol); RBCreate(resnRB, pcdW, TWIDE/2, y, "1536*1024 4Base", infofg, infobg, hicol, locol); RBCreate(resnRB, pcdW, TWIDE/2, y+18, "3072*2048 16Base", infofg, infobg, hicol, locol); #ifdef CRAP RBCreate(resnRB, pcdW, TWIDE/2, y+36, "Other", infofg, infobg, hicol, locol); #endif XMapSubwindows(theDisp, pcdW); } /***************************************************/ void PCDDialog(vis) int vis; { if (vis) { CenterMapWindow(pcdW, tbut[T_BOK].x + tbut[T_BOK].w/2, tbut[T_BOK].y + tbut[T_BOK].h/2, TWIDE, THIGH); } else XUnmapWindow(theDisp, pcdW); pcdUp = vis; } /***************************************************/ int PCDCheckEvent(xev) XEvent *xev; { /* check event to see if it's for one of our subwindows. If it is, deal accordingly, and return '1'. Otherwise, return '0' */ int rv; rv = 1; if (!pcdUp) return 0; if (xev->type == Expose) { int x,y,w,h; XExposeEvent *e = (XExposeEvent *) xev; x = e->x; y = e->y; w = e->width; h = e->height; if (e->window == pcdW) drawTD(x, y, w, h); else rv = 0; } else if (xev->type == ButtonPress) { XButtonEvent *e = (XButtonEvent *) xev; int x,y; x = e->x; y = e->y; if (e->button == Button1) { if (e->window == pcdW) clickTD(x,y); else rv = 0; } /* button1 */ else rv = 0; } /* button press */ else if (xev->type == KeyPress) { XKeyEvent *e = (XKeyEvent *) xev; char buf[128]; KeySym ks; XComposeStatus status; int stlen; stlen = XLookupString(e,buf,128,&ks,&status); buf[stlen] = '\0'; if (e->window == pcdW) { if (stlen) { if (buf[0] == '\r' || buf[0] == '\n') { /* enter */ FakeButtonPress(&tbut[T_BOK]); } else if (buf[0] == '\033') { /* ESC */ FakeButtonPress(&tbut[T_BCANC]); } } } else rv = 0; } else rv = 0; if (rv==0 && (xev->type == ButtonPress || xev->type == KeyPress)) { XBell(theDisp, 50); rv = 1; /* eat it */ } return rv; } /***************************************************/ void PCDSetParamOptions(fname) char *fname; { int cur; cur = RBWhich(resnRB); RBSetActive(resnRB,0,1); RBSetActive(resnRB,1,1); RBSetActive(resnRB,2,1); RBSetActive(resnRB,3,1); RBSetActive(resnRB,4,1); RBSetActive(resnRB,5,0); } /***************************************************/ static void drawTD(x,y,w,h) int x,y,w,h; { char *title = "Load PhotoCD file..."; int i; XRectangle xr; xr.x = x; xr.y = y; xr.width = w; xr.height = h; XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted); XSetForeground(theDisp, theGC, infofg); XSetBackground(theDisp, theGC, infobg); for (i=0; ix-16, resnRB->y-10-DESCENT, "Resolution"); RBRedraw(resnRB, -1); DrawString(pcdW, 20, 19, title); XSetClipMask(theDisp, theGC, None); } /***************************************************/ static void clickTD(x,y) int x,y; { int i; BUTT *bp; /* check BUTTs */ /* check the RBUTTS first, since they don't DO anything */ if ( (i=RBClick(resnRB, x,y)) >= 0) { (void) RBTrack(resnRB, i); return; } for (i=0; ix, bp->y, bp->w, bp->h)) break; } if (i= 0) { hufftop+=2; if (hufftop-hufftab >2*num) { fprintf(stderr,"Table overflow\n"); exit(1); } *huffptr=-(hufftop-huffptr); } huffptr-=*huffptr; } } } } } *alength=num; return hufftab; } /* WORDTYPE & char buffer must be unsigned else */ /* fills with sign bit not 0 on right shifts */ typedef unsigned int WORDTYPE; typedef int SWORDTYPE; #define WORDSIZE sizeof(WORDTYPE) #define NBYTESINBUF 0x800 static unsigned char buffer[NBYTESINBUF]; static int bitsleft=0; static int bytesleft=0; static unsigned char *bufptr; static WORDTYPE word; /* assume WORDTYPE is 32 bit word */ #define issync() ((word & 0xffffff00) == 0xfffffe00) #define skiptosync() { while (!issync()) (void)getbit(); } static void dumpbuffer() { int i,left; unsigned char *ptr=buffer; fprintf(stderr,"dumpbuffer: bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); for (left=NBYTESINBUF; left>0; left-=16) { fprintf(stderr,"%05d ",left); for (i=0; i<8; i++) { fprintf(stderr,"%02x",*ptr++); fprintf(stderr,"%02x ",*ptr++); } fprintf(stderr,"\n"); } } static void loadbuffer() { trace(stderr,"loadbuffer: start at sector %ld\n",(long)ftell(fp)/0x800 ); if ((bytesleft=fread(buffer,1,NBYTESINBUF,fp)) == 0) { fprintf(stderr,"Truncation error\n"); exit(1); } bufptr=buffer; trace(stderr,"loadbuffer: Loaded buffer with %d bytes\n",bytesleft); /* dumpbuffer(); */ } static void loadbyte() { trace(stderr,"loadbyte: start bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); if (bytesleft <= 0) loadbuffer(); --bytesleft; word|=(WORDTYPE)(*bufptr++)<<(sizeof(WORDTYPE)*8-8-bitsleft); bitsleft+=8; trace(stderr,"loadbyte: done bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); } static int getbit() { int bit; trace(stderr,"getbit: start bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); while (bitsleft <= 0) loadbyte(); --bitsleft; bit=(SWORDTYPE)(word)<0; /* assumes word is signed */ /* bit=word>>(sizeof(WORDTYPE)*8-1); */ word<<=1; trace(stderr,"getbit: done bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); trace(stderr,"getbit: done bit=%d\n",bit); return bit; } static WORDTYPE getnn(nn) int nn; { WORDTYPE value; trace(stderr,"getnn: start nn=%d\n",nn); trace(stderr,"getnn: start bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); while (bitsleft <= nn) loadbyte(); bitsleft-=nn; value=word>>(sizeof(WORDTYPE)*8-nn); word<<=nn; trace(stderr,"getnn: done bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); trace(stderr,"getnn: done value=0x%08lx\n",(unsigned long)value); return value; } static WORDTYPE isnn(nn) int nn; { WORDTYPE value; trace(stderr,"isnn: start nn=%d\n",nn); trace(stderr,"isnn: start bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); while (bitsleft <= nn) loadbyte(); value=word>>(sizeof(WORDTYPE)*8-nn); trace(stderr,"isnn: done bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); trace(stderr,"isnn: done value=0x%08lx\n",(unsigned long)value); return value; } static void skipnn(nn) int nn; { trace(stderr,"skipnn: start nn=%d\n",nn); trace(stderr,"skipnn: start bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); while (bitsleft <= nn) loadbyte(); bitsleft-=nn; word<<=nn; trace(stderr,"skipnn: done bytesleft=%d bitsleft= %d word=0x%08lx\n", bytesleft,bitsleft,(unsigned long)word); } #define get1() (getbit()) #define get2() (getnn(2)) #define get8() (getnn(8)) #define get13() (getnn(13)) #define get16() (getnn(16)) #define get24() (getnn(24)) #define is8() (isnn(8)) #define is16() (isnn(16)) #define is24() (isnn(24)) #define skip1() (skipnn(1)) #define skip8() (skipnn(8)) #define skip16() (skipnn(16)) #define skip24() (skipnn(24)) static int gethuffdata(luma,chroma1,chroma2,realrowwidth,maxrownumber) byte *luma, *chroma1, *chroma2; int realrowwidth; int maxrownumber; { int row,plane,charcount; int i; trace(stderr,"gethuffdata: start\n"); /* should really only look for luma plane for 4base, but the */ /* there are zeroes in the rest of the sector that give both */ /* chroma tables 0 length */ for (i=0; i<3; i++) hufftable[i]=gethufftable(&hufflength[i]); while (is24() != 0xfffffe) { (void)get24(); trace(stderr,"Skipping for sync\n"); } for (;;) { ihufftab *huffstart; schar *pixelptr; if (is24() == 0xfffffe) { skip24(); trace(stderr,"Charcount=%d\n",charcount); charcount=0; plane=get2(); row=get13(); skip1(); trace(stderr,"Plane %d Row %d\n",plane,row); if (row>=maxrownumber) { trace(stderr,"Stopping at row %d\n",row); break; } switch (plane) { case 0: huffstart=hufftable[0]; pixelptr=(schar *) (luma+row*realrowwidth); trace(stderr,"Setting luma plane\n"); break; case 2: huffstart=hufftable[1]; pixelptr=(schar *) (chroma1+row/2*realrowwidth/2); trace(stderr,"Setting chroma1 plane\n"); break; case 3: huffstart=hufftable[2]; pixelptr=(schar *) (chroma2+row/2*realrowwidth/2); trace(stderr,"Setting chroma2 plane\n"); break; default: fprintf(stderr,"Bad plane %d\n",plane); exit(1); } WaitCursor(); } else { ihufftab *huffptr=huffstart; for (;;) { int bit; bit=get1(); /* never fails :) */ huffptr+=bit; /* select entry 0 or entry 1 */ if (*huffptr < 0) { /* flag to choose next entry */ huffptr-=*huffptr; } else { /* found the value for the code */ schar value = *huffptr; (*(pixelptr+charcount))+=value; /* in lower 8 bits */ /* probably don't need to mask with 0xff */ ++charcount; trace(stderr,"[%d]=%d\n",charcount,(int)value); break; } } } } trace(stderr,"Out ... \n"); for (i=0; i<3; i++) if (hufftable[i]) free(hufftable[i]); return 1; } #endif /* HAVE_PCD */