/* img_mip.c - hl1 and q1 image mips Copyright (C) 2007 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "imagelib.h" #include "mathlib.h" #include "wadfile.h" #include "studio.h" #include "sprite.h" #include "qfont.h" /* ============ Image_LoadPAL ============ */ qboolean Image_LoadPAL( const char *name, const byte *buffer, size_t filesize ) { int rendermode = LUMP_NORMAL; if( filesize != 768 ) { MsgDev( D_ERROR, "Image_LoadPAL: (%s) have invalid size (%d should be %d)\n", name, filesize, 768 ); return false; } if( name[0] == '#' ) { // using palette name as rendermode if( Q_stristr( name, "normal" )) rendermode = LUMP_NORMAL; else if( Q_stristr( name, "masked" )) rendermode = LUMP_MASKED; else if( Q_stristr( name, "gradient" )) rendermode = LUMP_GRADIENT; else if( Q_stristr( name, "valve" )) { rendermode = LUMP_HALFLIFE; buffer = NULL; // force to get HL palette } else if( Q_stristr( name, "id" )) { rendermode = LUMP_QUAKE1; buffer = NULL; // force to get Q1 palette } } // NOTE: image.d_currentpal not cleared with Image_Reset() // and stay valid any time before new call of Image_SetPalette Image_GetPaletteLMP( buffer, rendermode ); Image_CopyPalette32bit(); image.rgba = NULL; // only palette, not real image image.size = 1024; // expanded palette image.width = image.height = 0; image.depth = 1; return true; } /* ============ Image_LoadFNT ============ */ qboolean Image_LoadFNT( const char *name, const byte *buffer, size_t filesize ) { qfont_t font; const byte *pal, *fin; size_t size; int numcolors; if( image.hint == IL_HINT_Q1 ) return false; // Quake1 doesn't have qfonts if( filesize < sizeof( font )) return false; memcpy( &font, buffer, sizeof( font )); // last sixty four bytes - what the hell ???? size = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64; if( size != filesize ) { // oldstyle font: "conchars" or "creditsfont" image.width = 256; // hardcoded image.height = font.height; } else { // Half-Life 1.1.0.0 font style (qfont_t) image.width = font.width * QCHAR_WIDTH; image.height = font.height; } if( !Image_LumpValidSize( name )) return false; fin = buffer + sizeof( font ) - 4; pal = fin + (image.width * image.height); numcolors = *(short *)pal, pal += sizeof( short ); if( numcolors == 768 || numcolors == 256 ) { // g-cont. make sure that is didn't hit anything Image_GetPaletteLMP( pal, LUMP_MASKED ); image.flags |= IMAGE_HAS_ALPHA; // fonts always have transparency } else { if( image.hint == IL_HINT_NO ) MsgDev( D_ERROR, "Image_LoadFNT: (%s) have invalid palette size %d\n", name, numcolors ); return false; } image.type = PF_INDEXED_32; // 32-bit palette image.depth = 1; return Image_AddIndexedImageToPack( fin, image.width, image.height ); } /* ============ Image_LoadMDL ============ */ qboolean Image_LoadMDL( const char *name, const byte *buffer, size_t filesize ) { byte *fin; size_t pixels; mstudiotexture_t *pin; int flags; pin = (mstudiotexture_t *)buffer; flags = pin->flags; image.width = pin->width; image.height = pin->height; pixels = image.width * image.height; fin = (byte *)pin->index; // setup buffer if( !Image_ValidSize( name )) return false; if( image.hint == IL_HINT_HL ) { if( filesize < ( sizeof( *pin ) + pixels + 768 )) return false; if( FBitSet( flags, STUDIO_NF_MASKED )) { byte *pal = fin + pixels; Image_GetPaletteLMP( pal, LUMP_MASKED ); image.flags |= IMAGE_HAS_ALPHA|IMAGE_ONEBIT_ALPHA; } else Image_GetPaletteLMP( fin + pixels, LUMP_NORMAL ); } else { if( image.hint == IL_HINT_NO ) MsgDev( D_ERROR, "Image_LoadMDL: lump (%s) is corrupted\n", name ); return false; // unknown or unsupported mode rejected } image.type = PF_INDEXED_32; // 32-bit palete image.depth = 1; return Image_AddIndexedImageToPack( fin, image.width, image.height ); } /* ============ Image_LoadSPR ============ */ qboolean Image_LoadSPR( const char *name, const byte *buffer, size_t filesize ) { dspriteframe_t *pin; // identical for q1\hl sprites qboolean truecolor = false; if( image.hint == IL_HINT_HL ) { if( !image.d_currentpal ) { MsgDev( D_ERROR, "Image_LoadSPR: (%s) palette not installed\n", name ); return false; } } else if( image.hint == IL_HINT_Q1 ) { Image_GetPaletteQ1(); } else { // unknown mode rejected return false; } pin = (dspriteframe_t *)buffer; image.width = pin->width; image.height = pin->height; if( filesize < image.width * image.height ) { MsgDev( D_ERROR, "Image_LoadSPR: file (%s) have invalid size\n", name ); return false; } if( filesize == ( image.width * image.height * 4 )) truecolor = true; // sorry, can't validate palette rendermode if( !Image_LumpValidSize( name )) return false; image.type = (truecolor) ? PF_RGBA_32 : PF_INDEXED_32; // 32-bit palete image.depth = 1; // detect alpha-channel by palette type switch( image.d_rendermode ) { case LUMP_MASKED: SetBits( image.flags, IMAGE_ONEBIT_ALPHA ); case LUMP_GRADIENT: case LUMP_QUAKE1: SetBits( image.flags, IMAGE_HAS_ALPHA ); break; } if( truecolor ) { // spr32 support image.size = image.width * image.height * 4; image.rgba = Mem_Alloc( host.imagepool, image.size ); memcpy( image.rgba, (byte *)(pin + 1), image.size ); SetBits( image.flags, IMAGE_HAS_COLOR ); // Color. True Color! return true; } return Image_AddIndexedImageToPack( (byte *)(pin + 1), image.width, image.height ); } /* ============ Image_LoadLMP ============ */ qboolean Image_LoadLMP( const char *name, const byte *buffer, size_t filesize ) { lmp_t lmp; byte *fin, *pal; int rendermode; int i, pixels; if( filesize < sizeof( lmp )) { MsgDev( D_ERROR, "Image_LoadLMP: file (%s) have invalid size\n", name ); return false; } // valve software trick (particle palette) if( Q_stristr( name, "palette.lmp" )) return Image_LoadPAL( name, buffer, filesize ); // id software trick (image without header) if( image.hint != IL_HINT_HL && Q_stristr( name, "conchars" )) { image.width = image.height = 128; rendermode = LUMP_QUAKE1; filesize += sizeof( lmp ); fin = (byte *)buffer; // need to remap transparent color from first to last entry for( i = 0; i < 16384; i++ ) if( !fin[i] ) fin[i] = 0xFF; } else { fin = (byte *)buffer; memcpy( &lmp, fin, sizeof( lmp )); image.width = lmp.width; image.height = lmp.height; rendermode = LUMP_NORMAL; fin += sizeof( lmp ); } pixels = image.width * image.height; if( filesize < sizeof( lmp ) + pixels ) { MsgDev( D_ERROR, "Image_LoadLMP: file (%s) have invalid size %d\n", name, filesize ); return false; } if( !Image_ValidSize( name )) return false; if( image.hint != IL_HINT_Q1 && filesize > (int)sizeof(lmp) + pixels ) { int numcolors; if( fin[0] == 255 ) { image.flags |= IMAGE_HAS_ALPHA; rendermode = LUMP_MASKED; } pal = fin + pixels; numcolors = *(short *)pal; if( numcolors != 256 ) pal = NULL; // corrupted lump ? else pal += sizeof( short ); } else if( image.hint != IL_HINT_HL ) { image.flags |= IMAGE_HAS_ALPHA; rendermode = LUMP_QUAKE1; pal = NULL; } else { // unknown mode rejected return false; } Image_GetPaletteLMP( pal, rendermode ); image.type = PF_INDEXED_32; // 32-bit palete image.depth = 1; return Image_AddIndexedImageToPack( fin, image.width, image.height ); } /* ============= Image_LoadMIP ============= */ qboolean Image_LoadMIP( const char *name, const byte *buffer, size_t filesize ) { mip_t mip; qboolean hl_texture; byte *fin, *pal; int ofs[4], rendermode; int i, pixels, numcolors; int reflectivity[3] = { 0, 0, 0 }; if( filesize < sizeof( mip )) { MsgDev( D_ERROR, "Image_LoadMIP: file (%s) have invalid size\n", name ); return false; } memcpy( &mip, buffer, sizeof( mip )); image.width = mip.width; image.height = mip.height; if( !Image_ValidSize( name )) return false; memcpy( ofs, mip.offsets, sizeof( ofs )); pixels = image.width * image.height; if( image.hint != IL_HINT_Q1 && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6) + sizeof(short) + 768) { // half-life 1.0.0.1 mip version with palette fin = (byte *)buffer + mip.offsets[0]; pal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6); numcolors = *(short *)pal; if( numcolors != 256 ) pal = NULL; // corrupted mip ? else pal += sizeof( short ); // skip colorsize hl_texture = true; // setup rendermode if( Q_strrchr( name, '{' )) { // NOTE: decals with 'blue base' can be interpret as colored decals if( !Image_CheckFlag( IL_LOAD_DECAL ) || ( pal[765] == 0 && pal[766] == 0 && pal[767] == 255 )) { rendermode = LUMP_MASKED; image.flags |= IMAGE_ONEBIT_ALPHA; } else { // classic gradient decals image.flags |= IMAGE_COLORINDEX; rendermode = LUMP_GRADIENT; } SetBits( image.flags, IMAGE_HAS_ALPHA ); } else { int pal_type; // NOTE: we can have luma-pixels if quake1 texture // converted into the hl texture but palette leave unchanged // this is a good reason for using fullbright pixels pal_type = Image_ComparePalette( pal ); // check for luma pixels (but ignore liquid textures, this a Xash3D limitation) if( mip.name[0] != '!' && pal_type == PAL_QUAKE1 ) { for( i = 0; i < image.width * image.height; i++ ) { if( fin[i] > 224 ) { image.flags |= IMAGE_HAS_LUMA; break; } } } if( pal_type == PAL_QUAKE1 ) SetBits( image.flags, IMAGE_QUAKEPAL ); rendermode = LUMP_NORMAL; } Image_GetPaletteLMP( pal, rendermode ); image.d_currentpal[255] &= 0xFFFFFF; } else if( image.hint != IL_HINT_HL && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6)) { // quake1 1.01 mip version without palette fin = (byte *)buffer + mip.offsets[0]; pal = NULL; // clear palette rendermode = LUMP_NORMAL; hl_texture = false; // check for luma and alpha pixels for( i = 0; i < image.width * image.height; i++ ) { if( fin[i] > 224 && fin[i] != 255 ) { // don't apply luma to water surfaces because // we use glpoly->next for store luma chain each frame // and can't modify glpoly_t because many-many HL mods // expected unmodified glpoly_t and can crashes on changed struct // water surfaces uses glpoly->next as pointer to subdivided surfaces (as q1) if( mip.name[0] != '*' && mip.name[0] != '!' ) image.flags |= IMAGE_HAS_LUMA; break; } } // Arcane Dimensions has the transparent textures if( Q_strrchr( name, '{' )) { for( i = 0; i < image.width * image.height; i++ ) { if( fin[i] == 255 ) { // don't set ONEBIT_ALPHA flag for some reasons image.flags |= IMAGE_HAS_ALPHA; break; } } } SetBits( image.flags, IMAGE_QUAKEPAL ); Image_GetPaletteQ1(); } else { if( image.hint == IL_HINT_NO ) MsgDev( D_ERROR, "Image_LoadMIP: lump (%s) is corrupted\n", name ); return false; // unknown or unsupported mode rejected } // check for quake-sky texture if( !Q_strncmp( mip.name, "sky", 3 ) && image.width == ( image.height * 2 )) { // g-cont: we need to run additional checks for palette type and colors ? image.flags |= IMAGE_QUAKESKY; } // check for half-life water texture if( hl_texture && ( mip.name[0] == '!' || !Q_strnicmp( mip.name, "water", 5 ))) { // grab the fog color image.fogParams[0] = pal[3*3+0]; image.fogParams[1] = pal[3*3+1]; image.fogParams[2] = pal[3*3+2]; // grab the fog density image.fogParams[3] = pal[4*3+0]; } else if( hl_texture && ( rendermode == LUMP_GRADIENT )) { // grab the decal color image.fogParams[0] = pal[255*3+0]; image.fogParams[1] = pal[255*3+1]; image.fogParams[2] = pal[255*3+2]; // calc the decal reflectivity image.fogParams[3] = VectorAvg( image.fogParams ); } else if( pal != NULL )// calc texture reflectivity { for( i = 0; i < 256; i++ ) { reflectivity[0] += pal[i*3+0]; reflectivity[1] += pal[i*3+1]; reflectivity[2] += pal[i*3+2]; } VectorDivide( reflectivity, 256, image.fogParams ); } image.type = PF_INDEXED_32; // 32-bit palete image.depth = 1; return Image_AddIndexedImageToPack( fin, image.width, image.height ); }