diff --git a/docs/rh-log.txt b/docs/rh-log.txt index cad1fc30c..39e85c915 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,3 +1,18 @@ +August 14, 2006 +- Added JPEG texture support, courtesy of Ken's Picture Library. I will + probably switch to the IJG library once I pare it down. (Ken's code is 18K + of C source but does not support progressive JPEG. The IJG library is over + a megabyte of source and supports pretty much everything you would ever + need ever.) +- Fixed endianness issue in FTextureManager::CreateTexture(). +- Added support for interlaced PNGs. Now ZDoom is a mostly complete PNG + reader. The only thing missing is 48-bit RGB and 16-bit grayscale support, + which are just wastes of bits here, but also less likely to appear than + an interlaced PNG. (However, if you are using interlaced PNGs for textures, + then you are needlessly wasting space, since the image won't display + progressively.) +- Fixed: Writing named screenshots didn't work. + August 12, 2006 - Added support for truecolor PNG textures. They still get resampled to the global palette, but at least they are visible now. diff --git a/src/g_game.cpp b/src/g_game.cpp index 653cef3fe..b51046803 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -187,7 +187,7 @@ FString savegamefile; char savedescription[SAVESTRINGSIZE]; // [RH] Name of screenshot file to generate (usually NULL) -char *shotfile; +FString shotfile; AActor* bodyque[BODYQUESIZE]; int bodyqueslot; @@ -890,11 +890,7 @@ void G_Ticker () break; case ga_screenshot: M_ScreenShot (shotfile); - if (shotfile) - { - free (shotfile); - shotfile = NULL; - } + shotfile = ""; gameaction = ga_nothing; break; case ga_fullconsole: diff --git a/src/kplib.cpp b/src/kplib.cpp new file mode 100644 index 000000000..923f93409 --- /dev/null +++ b/src/kplib.cpp @@ -0,0 +1,571 @@ +/************************************************************************************************** +KPLIB.C: Ken's Picture LIBrary written by Ken Silverman +Copyright (c) 1998-2005 Ken Silverman +Ken Silverman's official web site: http://www.advsys.net/ken + +This source file includes routines for decompression of the following picture formats: + JPG,PNG,GIF,PCX,TGA,BMP,CEL +It also includes code to handle ZIP decompression. + +Brief history: +1998?: Wrote KPEG, a JPEG viewer for DOS +2000: Wrote KPNG, a PNG viewer for DOS +2001: Combined KPEG&KPNG, ported to Visual C, and made it into a nice library called KPLIB.C +2002: Added support for: TGA,GIF,CEL,ZIP +2003: Added support for: BMP +05/18/2004: Added support for 8/24 bit PCX + +I offer this code to the community for the benefit of Jonathon Fowler's Duke3D port. + +-Ken S. +**************************************************************************************************/ +// [RH] Removed everything but JPEG support. + +#include +#include +#include +#include +#include +#include + +#include "m_swap.h" +#include "m_fixed.h" + +#if !defined(_WIN32) && !defined(__DOS__) +#include +static __inline unsigned long _lrotl (unsigned long i, int sh) + { return((i>>(-sh))|(i< +#endif + +#if !defined(max) +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#if !defined(min) +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#if defined(__GNUC__) +#define _inline inline +#endif + + //use GCC-specific extension to force symbol name to be something in particular to override underscoring. + +static int bytesperline, xres, yres, globxoffs, globyoffs; +static unsigned char *frameplace; + +static const int pow2mask[32] = +{ + 0x00000000,0x00000001,0x00000003,0x00000007, + 0x0000000f,0x0000001f,0x0000003f,0x0000007f, + 0x000000ff,0x000001ff,0x000003ff,0x000007ff, + 0x00000fff,0x00001fff,0x00003fff,0x00007fff, + 0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff, + 0x000fffff,0x001fffff,0x003fffff,0x007fffff, + 0x00ffffff,0x01ffffff,0x03ffffff,0x07ffffff, + 0x0fffffff,0x1fffffff,0x3fffffff,0x7fffffff, +}; + +//Initialized tables (can't be in union) +//jpg: png: +// crmul 16384 abstab10 4096 +// cbmul 16384 hxbit 472 +// dct 4608 pow2mask 128* +// colclip 4096 +// colclipup8 4096 +// colclipup16 4096 +// unzig 256 +// pow2mask 128* +// dcflagor 64 + +int palcol[256], paleng; +unsigned char coltype, bitdepth; + +//============================ KPEGILIB begins =============================== + + //11/01/2000: This code was originally from KPEG.C + // All non 32-bit color drawing was removed + // "Motion" JPG code was removed + // A lot of parameters were added to kpeg() for library usage +static int clipxdim, clipydim; + +static int hufquickcnt[8]; +static int hufmaxatbit[8][20], hufvalatbit[8][20], hufcnt[8]; +static unsigned char hufnumatbit[8][20], huftable[8][256]; +static unsigned char gnumcomponents, dcflagor[64]; +static int gcompid[4], gcomphsamp[4], gcompvsamp[4], gcompquantab[4]; +static int lcompid[4], lcompdc[4], lcompac[4]; +static int lcomphsamp[4], lcompvsamp[4], lcomphvsamp[4], lcompquantab[4]; +static int lcomphsampmask[4], lcomphsampshift[4], lcompvsampshift[4]; +static int quantab[4][64], dct[19][64], lastdc[4], unzig[64]; +static const unsigned char pow2char[8] = {1,2,4,8,16,32,64,128}; + +#define SQRT2 23726566 //(sqrt(2))<<24 +#define C182 31000253 //(cos(PI/8)*2)<<24 +#define C18S22 43840978 //(cos(PI/8)*sqrt(2)*2)<<24 +#define C38S22 18159528 //(cos(PI*3/8)*sqrt(2)*2)<<24 +static const int cosqr16[8] = //cosqr16[i] = ((cos(PI*i/16)*sqrt(2))<<24); + {23726566,23270667,21920489,19727919,16777216,13181774,9079764,4628823}; + +// [RH] Moved the bigger tables into a dynamically allocated struct so that +// they don't waste space if a JPEG is never loaded. +struct kpegtables +{ + int hufquickval[8][1024], hufquickbits[8][1024]; + int colclip[1024], colclipup8[1024], colclipup16[1024]; + int crmul[4096], cbmul[4096]; +}; +static kpegtables *kpeg; + +static void initkpeg () +{ + int i, x, y; + + kpeg = new kpegtables; + + x = 0; //Back & forth diagonal pattern (aligning bytes for best compression) + for(i=0;i<16;i+=2) + { + for(y=7;y>=0;y--) + if ((unsigned)(i-y) < (unsigned)8) unzig[x++] = (y<<3)+i-y; + for(y=0;y<8;y++) + if ((unsigned)(i+1-y) < (unsigned)8) unzig[x++] = (y<<3)+i+1-y; + } + for(i=63;i>=0;i--) dcflagor[i] = (unsigned char)(1<<(unzig[i]>>3)); + + for(i=0;i<128;i++) kpeg->colclip[i] = i+128; + for(i=128;i<512;i++) kpeg->colclip[i] = 255; + for(i=512;i<896;i++) kpeg->colclip[i] = 0; + for(i=896;i<1024;i++) kpeg->colclip[i] = i-896; + for(i=0;i<1024;i++) + { + kpeg->colclipup8[i] = (kpeg->colclip[i]<<8); + kpeg->colclipup16[i] = (kpeg->colclip[i]<<16)+0xff000000; //Hack: set alphas to 255 + } + + for(i=0;i<2048;i++) + { + kpeg->crmul[(i<<1)+0] = (i-1024)*1470104; //1.402*1048576 + kpeg->crmul[(i<<1)+1] = (i-1024)*-748830; //-0.71414*1048576 + kpeg->cbmul[(i<<1)+0] = (i-1024)*-360857; //-0.34414*1048576 + kpeg->cbmul[(i<<1)+1] = (i-1024)*1858077; //1.772*1048576 + } + + memset((void *)&dct[16][0],0,64*2*sizeof(dct[0][0])); + memset((void *)&dct[18][0],0xa0,64*sizeof(dct[0][0])); +} + +static void huffgetval (int index, int curbits, int num, int *daval, int *dabits) +{ + int b, v, pow2, *hmax; + + hmax = &hufmaxatbit[index][0]; + pow2 = pow2mask[curbits-1]+1; + if (num&pow2) v = 1; else v = 0; + for(b=1;b<=16;b++) + { + if (v < hmax[b]) + { + *dabits = b; + *daval = huftable[index][hufvalatbit[index][b]+v]; + return; + } + pow2 >>= 1; v <<= 1; + if (num&pow2) v++; + } + *dabits = 16; + *daval = 0; +} + +int kpegrend (const char *kfilebuf, int kfilength, + unsigned char *daframeplace, int dabytesperline, int daxres, int dayres, + int daglobxoffs, int daglobyoffs) +{ + int i, j, v, leng, xdim = 0, ydim = 0, index, prec, restartinterval; + int restartcnt, num, curbits, x, y, z, dctcnt, c, cc, daval, dabits; + int xx, yy, zz, xxx, yyy, r, g, b, t0, t1, t2, t3, t4, t5, t6, t7; + int yv, cr = 0, cb = 0, *dc, *dc2, xxxend, yyyend; + int *hqval, *hqbits, hqcnt, *quanptr; + unsigned char ch, marker, numbits, lnumcomponents, dcflag, *p; + const unsigned char *kfileptr; + + if (kpeg == NULL) { initkpeg(); } + + kfileptr = (unsigned char *)kfilebuf; + + if (*(unsigned short *)kfileptr == 0xd8ff) kfileptr += 2; + else return(-1); //"%s is not a JPEG file\n",filename + + restartinterval = 0; + for(i=0;i<4;i++) lastdc[i] = 0; + for(i=0;i<8;i++) hufcnt[i] = 0; + + coltype = 0; bitdepth = 8; //For PNGOUT + do + { + ch = *kfileptr++; if (ch != 255) continue; + marker = *kfileptr++; + leng = ((int)kfileptr[0]<<8)+(int)kfileptr[1]-2; + kfileptr += 2; + switch(marker) + { + case 0xc0: case 0xc1: case 0xc2: + //processit! + numbits = *kfileptr++; + + ydim = ((int)kfileptr[0]<<8)+(int)kfileptr[1]; + xdim = ((int)kfileptr[2]<<8)+(int)kfileptr[3]; + //printf("%s: %ld / %ld = %ld\n",filename,xdim*ydim*3,kfilength,(xdim*ydim*3)/kfilength); + + frameplace = daframeplace; + bytesperline = dabytesperline; + xres = daxres; + yres = dayres; + globxoffs = daglobxoffs; + globyoffs = daglobyoffs; + + gnumcomponents = kfileptr[4]; + kfileptr += 5; + for(z=0;z>4); + gcompvsamp[z] = (kfileptr[1]&15); + gcompquantab[z] = kfileptr[2]; + kfileptr += 3; + } + break; + case 0xc4: //Huffman table + do + { + ch = *kfileptr++; leng--; + if (ch >= 16) { index = ch-12; } + else { index = ch; } + memcpy((void *)&hufnumatbit[index][1],(void *)kfileptr,16); kfileptr += 16; + leng -= 16; + + v = 0; hufcnt[index] = 0; + hufquickcnt[index] = 0; c = 0; + for(b=1;b<=16;b++) + { + hufmaxatbit[index][b] = v+hufnumatbit[index][b]; + hufvalatbit[index][b] = hufcnt[index]-v; + memcpy((void *)&huftable[index][hufcnt[index]],(void *)kfileptr,(int)hufnumatbit[index][b]); + if (b <= 10) + for(c=0;c0;j--) + { + kpeg->hufquickval[index][hufquickcnt[index]] = huftable[index][hufcnt[index]+c]; + kpeg->hufquickbits[index][hufquickcnt[index]] = b; + hufquickcnt[index]++; + } + kfileptr += hufnumatbit[index][b]; + leng -= hufnumatbit[index][b]; + hufcnt[index] += hufnumatbit[index][b]; + v = ((v+hufnumatbit[index][b])<<1); + } + + } while (leng > 0); + break; + case 0xdb: + do + { + ch = *kfileptr++; leng--; + index = (ch&15); + prec = (ch>>4); + for(z=0;z<64;z++) + { + v = (int)(*kfileptr++); + if (prec) v = (v<<8)+((int)(*kfileptr++)); + v <<= 19; + if (unzig[z]&7) v = MulScale24(v,cosqr16[unzig[z]&7]); + if (unzig[z]>>3) v = MulScale24(v,cosqr16[unzig[z]>>3]); + if (index) v >>= 6; + quantab[index][unzig[z]] = v; + } + leng -= 64; + if (prec) leng -= 64; + } while (leng > 0); + break; + case 0xdd: + restartinterval = (((int)kfileptr[0])<<8)+((int)kfileptr[1]); + kfileptr += leng; + break; + case 0xda: case 0xd9: + if ((xdim <= 0) || (ydim <= 0)) return(-1); + + lnumcomponents = *kfileptr++; + if (lnumcomponents > 1) coltype = 2; + for(z=0;z>4); + lcompac[z] = (kfileptr[1]&15); + kfileptr += 2; + for(zz=0;zz>4); + //Al = (kfileptr[2]&15); + kfileptr += 3; + + if ((hufcnt[0] == 0) || (hufcnt[4] == 0)) return(-1); + + clipxdim = min(xdim+globxoffs,xres); + clipydim = min(ydim+globyoffs,yres); + + xx = max(globxoffs,0); xxx = min(globxoffs+xdim,xres); + yy = max(globyoffs,0); yyy = min(globyoffs+ydim,yres); + if ((xx >= xres) || (yy >= yres) || (xxx <= 0) || (yyy <= 0)) return(0); + + restartcnt = restartinterval; marker = 0xd0; + num = 0; curbits = 0; x = 0; y = 0; + while (1) + { + if (kfileptr-(unsigned char *)kfilebuf >= kfilength) + lnumcomponents = 0; //rest of file is missing! + + dctcnt = 0; + for(c=0;chufquickval[lcompac[c]+4][0]; + hqbits = &kpeg->hufquickbits[lcompac[c]+4][0]; + hqcnt = hufquickcnt[lcompac[c]+4]; + quanptr = &quantab[lcompquantab[c]][0]; + for(cc=lcomphvsamp[c];cc>0;cc--) + { + dc = &dct[dctcnt][0]; + + //Get DC + while (curbits < 16) //Getbits + { + ch = *kfileptr++; if (ch == 255) kfileptr++; + num = (num<<8)+((int)ch); curbits += 8; + } + + i = ((num>>(curbits-10))&1023); + if (i < hufquickcnt[lcompdc[c]]) + { dabits = kpeg->hufquickbits[lcompdc[c]][i]; daval = kpeg->hufquickval[lcompdc[c]][i]; } + else + huffgetval(lcompdc[c],curbits,num,&daval,&dabits); + + curbits -= dabits; + if (daval) + { + while (curbits < 16) //Getbits + { + ch = *kfileptr++; if (ch == 255) kfileptr++; + num = (num<<8)+((int)ch); curbits += 8; + } + + v = ((unsigned)num >> (curbits-daval)) & pow2mask[daval]; + if (v <= pow2mask[daval-1]) v -= pow2mask[daval]; + lastdc[c] += v; + curbits -= daval; + } + dc[0] = lastdc[c]*quanptr[0]; + + //Get AC + memset((void *)&dc[1],0,63*4); + dcflag = 1; + for(z=1;z<64;z++) + { + while (curbits < 16) //Getbits + { + ch = *kfileptr++; if (ch == 255) kfileptr++; + num = (num<<8)+((int)ch); curbits += 8; + } + + i = ((num>>(curbits-10))&1023); + if (i < hqcnt) + { daval = hqval[i]; curbits -= hqbits[i]; } + else + { + huffgetval(lcompac[c]+4,curbits,num,&daval,&dabits); + curbits -= dabits; + } + + if (!daval) break; + z += (daval>>4); if (z >= 64) break; + daval &= 15; + + while (curbits < 16) //Getbits + { + ch = *kfileptr++; if (ch == 255) kfileptr++; + num = (num<<8)+((int)ch); curbits += 8; + } + + v = ((unsigned)num >> (curbits-daval)) & pow2mask[daval]; + if (v <= pow2mask[daval-1]) v -= pow2mask[daval]; + dcflag |= dcflagor[z]; + dc[unzig[z]] = v*quanptr[unzig[z]]; + curbits -= daval; + } + + for(z=0;z<8;z++,dc+=8) + { + if (!(dcflag&pow2char[z])) continue; + t3 = dc[2] + dc[6]; + t2 = (MulScale32(dc[2]-dc[6],SQRT2<<6)<<2) - t3; + t4 = dc[0] + dc[4]; t5 = dc[0] - dc[4]; + t0 = t4+t3; t3 = t4-t3; t1 = t5+t2; t2 = t5-t2; + t4 = (MulScale32(dc[5]-dc[3]+dc[1]-dc[7],C182<<6)<<2); + t7 = dc[1] + dc[7] + dc[5] + dc[3]; + t6 = (MulScale32(dc[3]-dc[5],C18S22<<5)<<3) + t4 - t7; + t5 = (MulScale32(dc[1]+dc[7]-dc[5]-dc[3],SQRT2<<6)<<2) - t6; + t4 = (MulScale32(dc[1]-dc[7],C38S22<<6)<<2) - t4 + t5; + dc[0] = t0+t7; dc[7] = t0-t7; dc[1] = t1+t6; dc[6] = t1-t6; + dc[2] = t2+t5; dc[5] = t2-t5; dc[4] = t3+t4; dc[3] = t3-t4; + } + dc = &dct[dctcnt][0]; + for(z=7;z>=0;z--,dc++) + { + t3 = dc[2<<3] + dc[6<<3]; + t2 = (MulScale32(dc[2<<3]-dc[6<<3],SQRT2<<6)<<2) - t3; + t4 = dc[0<<3] + dc[4<<3]; t5 = dc[0<<3] - dc[4<<3]; + t0 = t4+t3; t3 = t4-t3; t1 = t5+t2; t2 = t5-t2; + t4 = (MulScale32(dc[5<<3]-dc[3<<3]+dc[1<<3]-dc[7<<3],C182<<6)<<2); + t7 = dc[1<<3] + dc[7<<3] + dc[5<<3] + dc[3<<3]; + t6 = (MulScale32(dc[3<<3]-dc[5<<3],C18S22<<5)<<3) + t4 - t7; + t5 = (MulScale32(dc[1<<3]+dc[7<<3]-dc[5<<3]-dc[3<<3],SQRT2<<6)<<2) - t6; + t4 = (MulScale32(dc[1<<3]-dc[7<<3],C38S22<<6)<<2) - t4 + t5; + dc[0<<3] = t0+t7; dc[7<<3] = t0-t7; dc[1<<3] = t1+t6; dc[6<<3] = t1-t6; + dc[2<<3] = t2+t5; dc[5<<3] = t2-t5; dc[4<<3] = t3+t4; dc[3<<3] = t3-t4; + } + + dctcnt++; + } + } + + dctcnt = 0; dc = &dct[18][0]; dc2 = &dct[16][0]; + r = g = b = 0; cc = 0; + for(yy=0;yy<(lcompvsamp[0]<<3);yy+=8) + for(xx=0;xx<(lcomphsamp[0]<<3);xx+=8,dctcnt++) + { + yyy = y+yy+globyoffs; if ((unsigned)yyy >= (unsigned)clipydim) continue; + xxx = x+xx+globxoffs; if ((unsigned)xxx >= (unsigned)clipxdim) continue; + p = yyy*bytesperline + xxx*4 + frameplace; + if (lnumcomponents > 0) dc = &dct[dctcnt][0]; + if (lnumcomponents > 1) dc2 = &dct[lcomphvsamp[0]][((yy>>lcompvsampshift[0])<<3)+(xx>>lcomphsampshift[0])]; + xxxend = min(clipxdim-(x+xx+globxoffs),8); + yyyend = min(clipydim-(y+yy+globyoffs),8); + if ((lcomphsamp[0] == 1) && (xxxend == 8)) + { + for(yyy=0;yyy>13)&~1; + cb = (dc2[xxx]>>13)&~1; + ((int *)p)[xxx] = kpeg->colclipup16[(unsigned)(yv+kpeg->crmul[cr+2048])>>22]+ + kpeg->colclipup8[(unsigned)(yv+kpeg->crmul[cr+2049]+kpeg->cbmul[cb+2048])>>22]+ + kpeg->colclip[(unsigned)(yv+kpeg->cbmul[cb+2049])>>22]; + } + p += bytesperline; + dc += 8; + if (!((yyy+1)&(lcompvsamp[0]-1))) dc2 += 8; + } + } + else if ((lcomphsamp[0] == 2) && (xxxend == 8)) + { + for(yyy=0;yyy>1)+64]>>13)&~1; + cb = (dc2[(xxx>>1)]>>13)&~1; + i = kpeg->crmul[cr+2049]+kpeg->cbmul[cb+2048]; + cr = kpeg->crmul[cr+2048]; + cb = kpeg->cbmul[cb+2049]; + ((int *)p)[xxx] = kpeg->colclipup16[(unsigned)(yv+cr)>>22]+ + kpeg->colclipup8[(unsigned)(yv+i)>>22]+ + kpeg->colclip[(unsigned)(yv+cb)>>22]; + yv = dc[xxx+1]; + ((int *)p)[xxx+1] = kpeg->colclipup16[(unsigned)(yv+cr)>>22]+ + kpeg->colclipup8[(unsigned)(yv+i)>>22]+ + kpeg->colclip[(unsigned)(yv+cb)>>22]; + } + p += bytesperline; + dc += 8; + if (!((yyy+1)&(lcompvsamp[0]-1))) dc2 += 8; + } + } + else + { + for(yyy=0;yyy>13)&~1; + cb = (dc2[i]>>13)&~1; + i++; + } + ((int *)p)[xxx] = kpeg->colclipup16[(unsigned)(yv+kpeg->crmul[cr+2048])>>22]+ + kpeg->colclipup8[(unsigned)(yv+kpeg->crmul[cr+2049]+kpeg->cbmul[cb+2048])>>22]+ + kpeg->colclip[(unsigned)(yv+kpeg->cbmul[cb+2049])>>22]; + } + p += bytesperline; + dc += 8; + if (!((yyy+1)&(lcompvsamp[0]-1))) dc2 += 8; + } + } + } + + if (lnumcomponents) //do only when not EOF... + { + restartcnt--; + if (!restartcnt) + { + kfileptr += 1-(curbits>>3); curbits = 0; + if ((kfileptr[-2] != 255) || (kfileptr[-1] != marker)) kfileptr--; + marker++; if (marker >= 0xd8) marker = 0xd0; + restartcnt = restartinterval; + for(i=0;i<4;i++) lastdc[i] = 0; + } + } + + x += (lcomphsamp[0]<<3); + if (x >= xdim) { x = 0; y += (lcompvsamp[0]<<3); if (y >= ydim) return(0); } + } + default: + kfileptr += leng; + break; + } + } while (kfileptr-(unsigned char *)kfilebuf < kfilength); + return(0); +} + +//============================== KPEGILIB ends ============================== diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 2a6e6c9e3..54757d32a 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -537,14 +537,14 @@ static BOOL FindFreeName (FString &fullname, const char *extension) return false; } -void M_ScreenShot (char *filename) +void M_ScreenShot (const char *filename) { FILE *file; FString autoname; bool writepcx = (stricmp (screenshot_type, "pcx") == 0); // PNG is the default // find a file name to save it to - if (filename == NULL) + if (filename == NULL || filename[0] == '\0') { #ifndef unix if (Args.CheckParm ("-cdrom")) diff --git a/src/m_misc.h b/src/m_misc.h index 64b02edd0..15dcc12ea 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -36,7 +36,7 @@ void M_FindResponseFile (void); // [RH] M_ScreenShot now accepts a filename parameter. // Pass a NULL to get the original behavior. -void M_ScreenShot (char *filename); +void M_ScreenShot (const char *filename); void M_LoadDefaults (); diff --git a/src/m_png.cpp b/src/m_png.cpp index 38e56ee5e..86baca2d5 100644 --- a/src/m_png.cpp +++ b/src/m_png.cpp @@ -100,7 +100,7 @@ static inline void StuffPalette (const PalEntry *from, BYTE *to); static bool StuffBitmap (const DCanvas *canvas, FILE *file); static bool WriteIDAT (FILE *file, const BYTE *data, int len); static void UnfilterRow (int width, BYTE *dest, BYTE *stream, BYTE *prev, int bpp); -static void UnpackPixels (int width, int bytesPerRow, int bitdepth, BYTE *row); +static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -525,14 +525,21 @@ DCanvas *M_CreateCanvasFromPNG (PNGHandle *png) bool M_ReadIDAT (FileReader *file, BYTE *buffer, int width, int height, int pitch, BYTE bitdepth, BYTE colortype, BYTE interlace, unsigned int chunklen) { - Byte *inputLine, *prev, *curr; + // Uninterlaced images are treated as a conceptual eighth pass by these tables. + static const BYTE passwidthshift[8] = { 3, 3, 2, 2, 1, 1, 0, 0 }; + static const BYTE passheightshift[8] = { 3, 3, 3, 2, 2, 1, 1, 0 }; + static const BYTE passrowoffset[8] = { 0, 0, 4, 0, 2, 0, 1, 0 }; + static const BYTE passcoloffset[8] = { 0, 4, 0, 2, 0, 1, 0, 0 }; + + Byte *inputLine, *prev, *curr, *adam7buff[3], *bufferend; Byte chunkbuffer[4096]; z_stream stream; int err; - int y; + int i, pass, passbuff, passpitch, passwidth; bool lastIDAT; - int bytesPerRowIn; + int bytesPerRowIn, bytesPerRowOut; int bytesPerPixel; + bool initpass; switch (colortype) { @@ -541,23 +548,18 @@ bool M_ReadIDAT (FileReader *file, BYTE *buffer, int width, int height, int pitc case 6: bytesPerPixel = 4; break; // RGBA default: bytesPerPixel = 1; break; } - inputLine = (Byte *)alloca (1 + width*bytesPerPixel*2); - prev = inputLine + 1 + width*bytesPerPixel; - memset (prev, 0, width*bytesPerPixel); - if (bytesPerPixel == 1 && bitdepth != 8) - { // This is an invalid combination for PNG files - return false; - } - - switch (bitdepth) + bytesPerRowOut = width * bytesPerPixel; + i = 4 + bytesPerRowOut * 2; + if (interlace) { - case 8: bytesPerRowIn = width * bytesPerPixel; break; - case 4: bytesPerRowIn = (width+1)/2; break; - case 2: bytesPerRowIn = (width+3)/4; break; - case 1: bytesPerRowIn = (width+7)/8; break; - default: return false; + i += bytesPerRowOut * 2; } + inputLine = (Byte *)alloca (i); + adam7buff[0] = inputLine + 4 + bytesPerRowOut; + adam7buff[1] = adam7buff[0] + bytesPerRowOut; + adam7buff[2] = adam7buff[1] + bytesPerRowOut; + bufferend = buffer + pitch * height; stream.next_in = Z_NULL; stream.avail_in = 0; @@ -568,67 +570,166 @@ bool M_ReadIDAT (FileReader *file, BYTE *buffer, int width, int height, int pitc { return false; } - y = 0; - curr = buffer; - stream.next_out = inputLine; - stream.avail_out = bytesPerRowIn+1; lastIDAT = false; + initpass = true; + pass = interlace ? 0 : 7; - do + while (err != Z_STREAM_END && pass < 8 - interlace) { - while (err != Z_STREAM_END) + if (initpass) { - if (stream.avail_in == 0 && chunklen > 0) - { - stream.next_in = chunkbuffer; - stream.avail_in = (uInt)file->Read (chunkbuffer, MIN(chunklen,sizeof(chunkbuffer))); - chunklen -= stream.avail_in; - } + int rowoffset, coloffset; - err = inflate (&stream, Z_SYNC_FLUSH); - if (err != Z_OK && err != Z_STREAM_END) - { // something unexpected happened - inflateEnd (&stream); - return false; - } - - if (stream.avail_out == 0) + initpass = false; + pass--; + do { + pass++; + rowoffset = passrowoffset[pass]; + coloffset = passcoloffset[pass]; + } + while ((rowoffset >= height || coloffset >= width) && pass < 7); + if (pass == 7 && interlace) + { + break; + } + passwidth = (width + (1 << passwidthshift[pass]) - 1 - coloffset) >> passwidthshift[pass]; + prev = adam7buff[0]; + passbuff = 1; + memset (prev, 0, passwidth * bytesPerPixel); + switch (bitdepth) + { + case 8: bytesPerRowIn = passwidth * bytesPerPixel; break; + case 4: bytesPerRowIn = (passwidth+1)/2; break; + case 2: bytesPerRowIn = (passwidth+3)/4; break; + case 1: bytesPerRowIn = (passwidth+7)/8; break; + default: return false; + } + curr = buffer + rowoffset*pitch + coloffset*bytesPerPixel; + passpitch = pitch << passheightshift[pass]; + stream.next_out = inputLine; + stream.avail_out = bytesPerRowIn + 1; + } + if (stream.avail_in == 0 && chunklen > 0) + { + stream.next_in = chunkbuffer; + stream.avail_in = (uInt)file->Read (chunkbuffer, MIN(chunklen,sizeof(chunkbuffer))); + chunklen -= stream.avail_in; + } + + err = inflate (&stream, Z_SYNC_FLUSH); + if (err != Z_OK && err != Z_STREAM_END) + { // something unexpected happened + inflateEnd (&stream); + return false; + } + + if (stream.avail_out == 0) + { + if (pass >= 6) + { + // Store pixels directly into the output buffer UnfilterRow (bytesPerRowIn, curr, inputLine, prev, bytesPerPixel); prev = curr; - curr += pitch; - y++; - stream.next_out = inputLine; - stream.avail_out = bytesPerRowIn+1; } - - if (chunklen == 0 && !lastIDAT) + else { - DWORD x[3]; + const BYTE *in; + BYTE *out; + int colstep, x; - if (file->Read (x, 12) != 12) + // Store pixels into a temporary buffer + UnfilterRow (bytesPerRowIn, adam7buff[passbuff], inputLine, prev, bytesPerPixel); + prev = adam7buff[passbuff]; + passbuff ^= 1; + in = prev; + if (bitdepth < 8) { - lastIDAT = true; + UnpackPixels (passwidth, bytesPerRowIn, bitdepth, in, adam7buff[2]); + in = adam7buff[2]; } - else if (x[2] != MAKE_ID('I','D','A','T')) + // Distribute pixels into the output buffer + out = curr; + colstep = bytesPerPixel << passwidthshift[pass]; + switch (bytesPerPixel) { - lastIDAT = true; - } - else - { - chunklen = BigLong((unsigned int)x[1]); + case 1: + for (x = passwidth; x > 0; --x) + { + *out = *in; + out += colstep; + in += 1; + } + break; + + case 2: + for (x = passwidth; x > 0; --x) + { + *(WORD *)out = *(WORD *)in; + out += colstep; + in += 2; + } + break; + + case 3: + for (x = passwidth; x > 0; --x) + { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out += colstep; + in += 3; + } + break; + + case 4: + for (x = passwidth; x > 0; --x) + { + *(DWORD *)out = *(DWORD *)in; + out += colstep; + in += 4; + } + break; } } + if ((curr += passpitch) >= bufferend) + { + ++pass; + initpass = true; + } + stream.next_out = inputLine; + stream.avail_out = bytesPerRowIn + 1; + } + + if (chunklen == 0 && !lastIDAT) + { + DWORD x[3]; + + if (file->Read (x, 12) != 12) + { + lastIDAT = true; + } + else if (x[2] != MAKE_ID('I','D','A','T')) + { + lastIDAT = true; + } + else + { + chunklen = BigLong((unsigned int)x[1]); + } } - } while (err == Z_OK && y < height); + } inflateEnd (&stream); if (bitdepth < 8) { - for (curr = buffer; curr <= prev; curr += pitch) + // Noninterlaced images must be unpacked completely. + // Interlaced images only need their final pass unpacked. + passpitch = pitch << interlace; + for (curr = buffer + pitch * interlace; curr <= prev; curr += passpitch) { - UnpackPixels (width, bytesPerRowIn, bitdepth, curr); + UnpackPixels (width, bytesPerRowIn, bitdepth, curr, curr); } } return true; @@ -897,32 +998,33 @@ void UnfilterRow (int width, BYTE *dest, BYTE *row, BYTE *prev, int bpp) // // UnpackPixels // -// Unpacks a row of pixels whose depth is less than 8 into so that each -// pixel occupies a single byte. The packed pixels must be at the start -// of the row, and the row must be "width" bytes long. "bytesPerRow" is -// the number of bytes for the packed row. +// Unpacks a row of pixels whose depth is less than 8 so that each pixel +// occupies a single byte. The outrow must be "width" bytes long. +// "bytesPerRow" is the number of bytes for the packed row. The in and out +// rows may overlap, but only if rowin == rowout. // //========================================================================== -static void UnpackPixels (int width, int bytesPerRow, int bitdepth, BYTE *row) +static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout) { - BYTE *out, *in; + const BYTE *in; + BYTE *out; BYTE pack; int lastbyte; - out = row + width; - in = row + bytesPerRow; + out = rowout + width; + in = rowin + bytesPerRow; switch (bitdepth) { case 1: - lastbyte=width&7; - if (lastbyte!=0) + lastbyte = width & 7; + if (lastbyte != 0) { in--; pack = *in; - out-=lastbyte; + out -= lastbyte; out[0] = (pack >> 7) & 1; if (lastbyte >= 2) out[1] = (pack >> 6) & 1; if (lastbyte >= 3) out[2] = (pack >> 5) & 1; @@ -932,7 +1034,7 @@ static void UnpackPixels (int width, int bytesPerRow, int bitdepth, BYTE *row) if (lastbyte == 7) out[6] = (pack >> 1) & 1; } - while (in-- > row) + while (in-- > rowin) { pack = *in; out -= 8; @@ -949,18 +1051,18 @@ static void UnpackPixels (int width, int bytesPerRow, int bitdepth, BYTE *row) case 2: - lastbyte=width&3; - if (lastbyte!=0) + lastbyte = width & 3; + if (lastbyte != 0) { in--; pack = *in; - out-=lastbyte; + out -= lastbyte; out[0] = pack >> 6; if (lastbyte >= 2) out[1] = (pack >> 4) & 3; if (lastbyte == 3) out[2] = (pack >> 2) & 3; } - while (in-- > row) + while (in-- > rowin) { pack = *in; out -= 4; @@ -972,16 +1074,16 @@ static void UnpackPixels (int width, int bytesPerRow, int bitdepth, BYTE *row) break; case 4: - lastbyte=width&1; - if (lastbyte!=0) + lastbyte = width & 1; + if (lastbyte != 0) { in--; pack = *in; - out-=lastbyte; + out -= lastbyte; out[0] = pack >> 4; } - while (in-- > row) + while (in-- > rowin) { pack = *in; out -= 2; diff --git a/src/r_data.cpp b/src/r_data.cpp index 3fc4628c6..c5f8b5959 100644 --- a/src/r_data.cpp +++ b/src/r_data.cpp @@ -238,6 +238,7 @@ int FTextureManager::CreateTexture (int lumpnum, int usetype) { DWORD dw; WORD w[2]; + BYTE b[4]; } first4bytes; // Must check the length of the lump. Zero length flat markers (F1_START etc.) will come through here. @@ -250,7 +251,7 @@ int FTextureManager::CreateTexture (int lumpnum, int usetype) { FWadLump data = Wads.OpenLumpNum (lumpnum); - data >> first4bytes.dw; + data.Read (first4bytes.b, 4); if (first4bytes.dw == MAKE_ID('I','M','G','Z')) { type = t_imgz; @@ -263,11 +264,11 @@ int FTextureManager::CreateTexture (int lumpnum, int usetype) // This is most likely a PNG, but make sure. (Note that if the // first 4 bytes match, but later bytes don't, we assume it's // a corrupt PNG.) - data >> first4bytes.dw; + data.Read (first4bytes.b, 4); if (first4bytes.dw != MAKE_ID(13,10,26,10)) return -1; - data >> first4bytes.dw; + data.Read (first4bytes.b, 4); if (first4bytes.dw != MAKE_ID(0,0,0,13)) return -1; - data >> first4bytes.dw; + data.Read (first4bytes.b, 4); if (first4bytes.dw != MAKE_ID('I','H','D','R')) return -1; // The PNG looks valid so far. Check the IHDR to make sure it's a @@ -275,7 +276,7 @@ int FTextureManager::CreateTexture (int lumpnum, int usetype) data >> width >> height >> bitdepth >> colortype >> compression >> filter >> interlace; - if (compression != 0 || filter != 0 || interlace != 0) + if (compression != 0 || filter != 0 || interlace > 1) { return -1; } @@ -290,10 +291,11 @@ int FTextureManager::CreateTexture (int lumpnum, int usetype) // Just for completeness, make sure the PNG has something more than an // IHDR. - data >> first4bytes.dw >> first4bytes.dw; + data.Seek (4, SEEK_CUR); + data.Read (first4bytes.b, 4); if (first4bytes.dw == 0) { - data >> first4bytes.dw; + data.Read (first4bytes.b, 4); if (first4bytes.dw == MAKE_ID('I','E','N','D')) { return -1; @@ -304,6 +306,39 @@ int FTextureManager::CreateTexture (int lumpnum, int usetype) out = new FPNGTexture (lumpnum, BigLong((int)width), BigLong((int)height), bitdepth, colortype, interlace); } + else if (first4bytes.b[0] == 0xFF && first4bytes.b[1] == 0xD8 && first4bytes.b[2] == 0xFF) + { + // JPEG, I presume + + // Find the SOFn marker to extract the image dimensions, + // where n is 0, 1, or 2 (other types are unsupported). + while ((unsigned)first4bytes.b[3] - 0xC0 >= 3) + { + if (data.Read (first4bytes.w, 2) != 2) + { + return -1; + } + data.Seek (BigShort(first4bytes.w[0]) - 2, SEEK_CUR); + if (data.Read (first4bytes.b + 2, 2) != 2 || first4bytes.b[2] != 0xFF) + { + return -1; + } + } + if (data.Read (first4bytes.b, 3) != 3) + { + return -1; + } + if (BigShort (first4bytes.w[0]) <5) + { + return -1; + } + if (data.Read (first4bytes.b, 4) != 4) + { + return -1; + } + type = t_png; + out = new FJPEGTexture (lumpnum, BigShort(first4bytes.w[1]), BigShort(first4bytes.w[0])); + } else if (usetype == FTexture::TEX_Flat) { // allow PNGs as flats but not Doom patches. @@ -2091,6 +2126,111 @@ void FPNGTexture::MakeTexture () } } +int kpegrend (const char *kfilebuf, int kfilength, + unsigned char *daframeplace, int dabytesperline, int daxres, int dayres, + int daglobxoffs, int daglobyoffs); + +FJPEGTexture::FJPEGTexture (int lumpnum, int width, int height) +: SourceLump(lumpnum), Pixels(0) +{ + Wads.GetLumpName (Name, lumpnum); + Name[8] = 0; + + UseType = TEX_MiscPatch; + LeftOffset = 0; + TopOffset = 0; + bMasked = false; + + Width = width; + Height = height; + CalcBitSize (); + + DummySpans[0].TopOffset = 0; + DummySpans[0].Length = Height; + DummySpans[1].TopOffset = 0; + DummySpans[1].Length = 0; +} + +FJPEGTexture::~FJPEGTexture () +{ + Unload (); +} + +void FJPEGTexture::Unload () +{ + if (Pixels != NULL) + { + delete[] Pixels; + Pixels = NULL; + } +} + +const BYTE *FJPEGTexture::GetColumn (unsigned int column, const Span **spans_out) +{ + if (Pixels == NULL) + { + MakeTexture (); + } + if ((unsigned)column >= (unsigned)Width) + { + if (WidthMask + 1 == Width) + { + column &= WidthMask; + } + else + { + column %= Width; + } + } + if (spans_out != NULL) + { + *spans_out = DummySpans; + } + return Pixels + column*Height; +} + +const BYTE *FJPEGTexture::GetPixels () +{ + if (Pixels == NULL) + { + MakeTexture (); + } + return Pixels; +} + +void FJPEGTexture::MakeTexture () +{ + FMemLump lump = Wads.ReadLump (SourceLump); + BYTE *rgb = new BYTE[Width * Height * 4]; + + Pixels = new BYTE[Width * Height]; + if (kpegrend ((char *)lump.GetMem(), Wads.LumpLength (SourceLump), rgb, Width * 4, Width, Height, 0, 0) < 0) + { // Failed to read the JPEG + memset (Pixels, 0xBA, Width * Height); + } + else + { + BYTE *in, *out; + int x, y, pitch, backstep; + + in = rgb; + out = Pixels; + + // Convert from source format to paletted, column-major. + pitch = Width * 4; + backstep = Height * pitch - 4; + for (x = Width; x > 0; --x) + { + for (y = Height; y > 0; --y) + { + *out++ = RGB32k[in[2]>>3][in[1]>>3][in[0]>>3]; + in += pitch; + } + in -= backstep; + } + } + delete[] rgb; +} FBuildTexture::FBuildTexture (int tilenum, const BYTE *pixels, int width, int height, int left, int top) : Pixels (pixels), Spans (NULL) diff --git a/src/r_data.h b/src/r_data.h index 721cd0fe5..865e40bf0 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -212,6 +212,25 @@ protected: }; +// A JPEG image +class FJPEGTexture : public FTexture +{ +public: + FJPEGTexture (int lumpnum, int width, int height); + ~FJPEGTexture (); + + const BYTE *GetColumn (unsigned int column, const Span **spans_out); + const BYTE *GetPixels (); + void Unload (); + +protected: + int SourceLump; + BYTE *Pixels; + Span DummySpans[2]; + + void MakeTexture (); +}; + // A texture that returns a wiggly version of another texture. class FWarpTexture : public FTexture { diff --git a/zdoom.vcproj b/zdoom.vcproj index 135097062..618ec9e81 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -1924,6 +1924,10 @@ /> + + @@ -5381,7 +5385,7 @@ @@ -5399,7 +5403,7 @@