DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Image_process.cpp (16277B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 #pragma hdrstop
     29 #include "../idlib/precompiled.h"
     30 
     31 
     32 #include "tr_local.h"
     33 
     34 /*
     35 ================
     36 R_ResampleTexture
     37 
     38 Used to resample images in a more general than quartering fashion.
     39 
     40 This will only have filter coverage if the resampled size
     41 is greater than half the original size.
     42 
     43 If a larger shrinking is needed, use the mipmap function 
     44 after resampling to the next lower power of two.
     45 ================
     46 */
     47 #define	MAX_DIMENSION	4096
     48 byte *R_ResampleTexture( const byte *in, int inwidth, int inheight,  
     49 							int outwidth, int outheight ) {
     50 	int		i, j;
     51 	const byte	*inrow, *inrow2;
     52 	unsigned int	frac, fracstep;
     53 	unsigned int	p1[MAX_DIMENSION], p2[MAX_DIMENSION];
     54 	const byte		*pix1, *pix2, *pix3, *pix4;
     55 	byte		*out, *out_p;
     56 
     57 	if ( outwidth > MAX_DIMENSION ) {
     58 		outwidth = MAX_DIMENSION;
     59 	}
     60 	if ( outheight > MAX_DIMENSION ) {
     61 		outheight = MAX_DIMENSION;
     62 	}
     63 
     64 	out = (byte *)R_StaticAlloc( outwidth * outheight * 4, TAG_IMAGE );
     65 	out_p = out;
     66 
     67 	fracstep = inwidth*0x10000/outwidth;
     68 
     69 	frac = fracstep>>2;
     70 	for ( i=0 ; i<outwidth ; i++ ) {
     71 		p1[i] = 4*(frac>>16);
     72 		frac += fracstep;
     73 	}
     74 	frac = 3*(fracstep>>2);
     75 	for ( i=0 ; i<outwidth ; i++ ) {
     76 		p2[i] = 4*(frac>>16);
     77 		frac += fracstep;
     78 	}
     79 
     80 	for (i=0 ; i<outheight ; i++, out_p += outwidth*4 ) {
     81 		inrow = in + 4 * inwidth * (int)( ( i + 0.25f ) * inheight / outheight );
     82 		inrow2 = in + 4 * inwidth * (int)( ( i + 0.75f ) * inheight / outheight );
     83 		frac = fracstep >> 1;
     84 		for (j=0 ; j<outwidth ; j++) {
     85 			pix1 = inrow + p1[j];
     86 			pix2 = inrow + p2[j];
     87 			pix3 = inrow2 + p1[j];
     88 			pix4 = inrow2 + p2[j];
     89 			out_p[j*4+0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
     90 			out_p[j*4+1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
     91 			out_p[j*4+2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
     92 			out_p[j*4+3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
     93 		}
     94 	}
     95 
     96 	return out;
     97 }
     98 
     99 /*
    100 ================
    101 R_Dropsample
    102 
    103 Used to resample images in a more general than quartering fashion.
    104 Normal maps and such should not be bilerped.
    105 ================
    106 */
    107 byte *R_Dropsample( const byte *in, int inwidth, int inheight,  
    108 							int outwidth, int outheight ) {
    109 	int		i, j, k;
    110 	const byte	*inrow;
    111 	const byte	*pix1;
    112 	byte		*out, *out_p;
    113 
    114 	out = (byte *)R_StaticAlloc( outwidth * outheight * 4, TAG_IMAGE );
    115 	out_p = out;
    116 
    117 	for (i=0 ; i<outheight ; i++, out_p += outwidth*4 ) {
    118 		inrow = in + 4*inwidth*(int)((i+0.25)*inheight/outheight);
    119 		for (j=0 ; j<outwidth ; j++) {
    120 			k = j * inwidth / outwidth;
    121 			pix1 = inrow + k * 4;
    122 			out_p[j*4+0] = pix1[0];
    123 			out_p[j*4+1] = pix1[1];
    124 			out_p[j*4+2] = pix1[2];
    125 			out_p[j*4+3] = pix1[3];
    126 		}
    127 	}
    128 
    129 	return out;
    130 }
    131 
    132 /*
    133 ================
    134 R_SetAlphaNormalDivergence
    135 
    136 If any of the angles inside the cone would directly reflect to the light, there will be
    137 a specular highlight.  The intensity of the highlight is inversely proportional to the
    138 area of the spread.
    139 
    140 Light source area is important for the base size.
    141 
    142 area subtended in light is the divergence times the distance
    143 
    144 Shininess value is subtracted from the divergence
    145 
    146 Sets the alpha channel to the greatest divergence dot product of the surrounding texels.
    147 1.0 = flat, 0.0 = turns a 90 degree angle
    148 Lower values give less shiny specular
    149 With mip maps, the lowest samnpled value will be retained
    150 
    151 Should we rewrite the normal as the centered average?
    152 ================
    153 */
    154 void	R_SetAlphaNormalDivergence( byte *in, int width, int height ) {
    155 	for ( int y = 0 ; y < height ; y++ ) {
    156 		for ( int x = 0 ; x < width ; x++ ) {
    157 			// the divergence is the smallest dot product of any of the eight surrounding texels
    158 			byte	*pic_p = in + ( y * width + x ) * 4;
    159 			idVec3	center;
    160 			center[0] = ( pic_p[0] - 128 ) / 127;
    161 			center[1] = ( pic_p[1] - 128 ) / 127;
    162 			center[2] = ( pic_p[2] - 128 ) / 127;
    163 			center.Normalize();
    164 
    165 			float	maxDiverge = 1.0;
    166 
    167 			// FIXME: this assumes wrap mode, but should handle clamp modes and border colors
    168 			for ( int yy = -1 ; yy <= 1 ; yy++ ) {
    169 				for ( int xx = -1 ; xx <= 1 ; xx++ ) {
    170 					if ( yy == 0 && xx == 0 ) {
    171 						continue;
    172 					}
    173 					byte	*corner_p = in + ( ((y+yy)&(height-1)) * width + ((x+xx)&width-1) ) * 4;
    174 					idVec3	corner;
    175 					corner[0] = ( corner_p[0] - 128 ) / 127;
    176 					corner[1] = ( corner_p[1] - 128 ) / 127;
    177 					corner[2] = ( corner_p[2] - 128 ) / 127;
    178 					corner.Normalize();
    179 
    180 					float	diverge = corner * center;
    181 					if ( diverge < maxDiverge ) {
    182 						maxDiverge = diverge;
    183 					}
    184 				}
    185 			}
    186 
    187 			// we can get a diverge < 0 in some extreme cases
    188 			if ( maxDiverge < 0 ) {
    189 				maxDiverge = 0;
    190 			}
    191 			pic_p[3] = maxDiverge * 255;
    192 		}
    193 	}
    194 }
    195 
    196 /*
    197 ================
    198 R_MipMapWithAlphaSpecularity
    199 
    200 Returns a new copy of the texture, quartered in size and filtered.
    201 The alpha channel is taken to be the minimum of the dots of all surrounding normals.
    202 ================
    203 */
    204 #define MIP_MIN(a,b) (a<b?a:b)
    205 
    206 byte *R_MipMapWithAlphaSpecularity( const byte *in, int width, int height ) {
    207 	int		i, j, c, x, y, sx, sy;
    208 	const byte	*in_p;
    209 	byte	*out, *out_p;
    210 	int		row;
    211 	int		newWidth, newHeight;
    212 	float	*fbuf, *fbuf_p;
    213 
    214 	if ( width < 1 || height < 1 || ( width + height == 2 ) ) {
    215 		common->FatalError( "R_MipMapWithAlphaMin called with size %i,%i", width, height );
    216 	}
    217 
    218 	// convert the incoming texture to centered floating point
    219 	c = width * height;
    220 	fbuf = (float *)_alloca( c * 4 * sizeof( *fbuf ) );
    221 	in_p = in;
    222 	fbuf_p = fbuf;
    223 	for ( i = 0 ; i < c ; i++, in_p+=4, fbuf_p += 4 ) {
    224 		fbuf_p[0] = ( in_p[0] / 255.0 ) * 2.0 - 1.0;	// convert to a normal
    225 		fbuf_p[1] = ( in_p[1] / 255.0 ) * 2.0 - 1.0;
    226 		fbuf_p[2] = ( in_p[2] / 255.0 ) * 2.0 - 1.0;
    227 		fbuf_p[3] = ( in_p[3] / 255.0 );				// filtered divegence / specularity
    228 	}
    229 
    230 	row = width * 4;
    231 
    232 	newWidth = width >> 1;
    233 	newHeight = height >> 1;
    234 	if ( !newWidth ) {
    235 		newWidth = 1;
    236 	}
    237 	if ( !newHeight ) {
    238 		newHeight = 1;
    239 	}
    240 	out = (byte *)R_StaticAlloc( newWidth * newHeight * 4, TAG_IMAGE );
    241 	out_p = out;
    242 
    243 	in_p = in;
    244 
    245 	for ( i=0 ; i<newHeight ; i++ ) {
    246 		for ( j=0 ; j<newWidth ; j++, out_p+=4 ) {
    247 			idVec3	total;
    248 			float	totalSpec;
    249 
    250 			total.Zero();
    251 			totalSpec = 0;
    252 			// find the average normal
    253 			for ( x = -1 ; x <= 1 ; x++ ) {
    254 				sx = ( j * 2 + x ) & (width-1);
    255 				for ( y = -1 ; y <= 1 ; y++ ) {
    256 					sy = ( i * 2 + y ) & (height-1);
    257 					fbuf_p = fbuf + ( sy * width + sx ) * 4;
    258 
    259 					total[0] += fbuf_p[0];
    260 					total[1] += fbuf_p[1];
    261 					total[2] += fbuf_p[2];
    262 
    263 					totalSpec += fbuf_p[3];
    264 				}
    265 			}
    266 			total.Normalize();
    267 			totalSpec /= 9.0;
    268 
    269 			// find the maximum divergence
    270 			for ( x = -1 ; x <= 1 ; x++ ) {
    271 				for ( y = -1 ; y <= 1 ; y++ ) {
    272 				}
    273 			}
    274 
    275 			// store the average normal and divergence
    276 		}
    277 	}
    278 
    279 	return out;
    280 }
    281 
    282 float mip_gammaTable[256] = {
    283 	0.000000f, 0.000005f, 0.000023f, 0.000057f, 0.000107f, 0.000175f, 0.000262f, 0.000367f, 0.000493f, 0.000638f, 0.000805f, 0.000992f, 0.001202f, 0.001433f, 0.001687f, 0.001963f,
    284 	0.002263f, 0.002586f, 0.002932f, 0.003303f, 0.003697f, 0.004116f, 0.004560f, 0.005028f, 0.005522f, 0.006041f, 0.006585f, 0.007155f, 0.007751f, 0.008373f, 0.009021f, 0.009696f,
    285 	0.010398f, 0.011126f, 0.011881f, 0.012664f, 0.013473f, 0.014311f, 0.015175f, 0.016068f, 0.016988f, 0.017936f, 0.018913f, 0.019918f, 0.020951f, 0.022013f, 0.023104f, 0.024223f,
    286 	0.025371f, 0.026549f, 0.027755f, 0.028991f, 0.030257f, 0.031551f, 0.032876f, 0.034230f, 0.035614f, 0.037029f, 0.038473f, 0.039947f, 0.041452f, 0.042987f, 0.044553f, 0.046149f,
    287 	0.047776f, 0.049433f, 0.051122f, 0.052842f, 0.054592f, 0.056374f, 0.058187f, 0.060032f, 0.061907f, 0.063815f, 0.065754f, 0.067725f, 0.069727f, 0.071761f, 0.073828f, 0.075926f,
    288 	0.078057f, 0.080219f, 0.082414f, 0.084642f, 0.086901f, 0.089194f, 0.091518f, 0.093876f, 0.096266f, 0.098689f, 0.101145f, 0.103634f, 0.106156f, 0.108711f, 0.111299f, 0.113921f,
    289 	0.116576f, 0.119264f, 0.121986f, 0.124741f, 0.127530f, 0.130352f, 0.133209f, 0.136099f, 0.139022f, 0.141980f, 0.144972f, 0.147998f, 0.151058f, 0.154152f, 0.157281f, 0.160444f,
    290 	0.163641f, 0.166872f, 0.170138f, 0.173439f, 0.176774f, 0.180144f, 0.183549f, 0.186989f, 0.190463f, 0.193972f, 0.197516f, 0.201096f, 0.204710f, 0.208360f, 0.212044f, 0.215764f,
    291 	0.219520f, 0.223310f, 0.227137f, 0.230998f, 0.234895f, 0.238828f, 0.242796f, 0.246800f, 0.250840f, 0.254916f, 0.259027f, 0.263175f, 0.267358f, 0.271577f, 0.275833f, 0.280124f,
    292 	0.284452f, 0.288816f, 0.293216f, 0.297653f, 0.302126f, 0.306635f, 0.311181f, 0.315763f, 0.320382f, 0.325037f, 0.329729f, 0.334458f, 0.339223f, 0.344026f, 0.348865f, 0.353741f,
    293 	0.358654f, 0.363604f, 0.368591f, 0.373615f, 0.378676f, 0.383775f, 0.388910f, 0.394083f, 0.399293f, 0.404541f, 0.409826f, 0.415148f, 0.420508f, 0.425905f, 0.431340f, 0.436813f,
    294 	0.442323f, 0.447871f, 0.453456f, 0.459080f, 0.464741f, 0.470440f, 0.476177f, 0.481952f, 0.487765f, 0.493616f, 0.499505f, 0.505432f, 0.511398f, 0.517401f, 0.523443f, 0.529523f,
    295 	0.535642f, 0.541798f, 0.547994f, 0.554227f, 0.560499f, 0.566810f, 0.573159f, 0.579547f, 0.585973f, 0.592438f, 0.598942f, 0.605484f, 0.612066f, 0.618686f, 0.625345f, 0.632043f,
    296 	0.638779f, 0.645555f, 0.652370f, 0.659224f, 0.666117f, 0.673049f, 0.680020f, 0.687031f, 0.694081f, 0.701169f, 0.708298f, 0.715465f, 0.722672f, 0.729919f, 0.737205f, 0.744530f,
    297 	0.751895f, 0.759300f, 0.766744f, 0.774227f, 0.781751f, 0.789314f, 0.796917f, 0.804559f, 0.812241f, 0.819964f, 0.827726f, 0.835528f, 0.843370f, 0.851252f, 0.859174f, 0.867136f,
    298 	0.875138f, 0.883180f, 0.891262f, 0.899384f, 0.907547f, 0.915750f, 0.923993f, 0.932277f, 0.940601f, 0.948965f, 0.957370f, 0.965815f, 0.974300f, 0.982826f, 0.991393f, 1.000000f
    299 };
    300 
    301 /*
    302 ================
    303 R_MipMapGamma
    304 
    305 Returns a new copy of the texture, quartered in size with gamma correction.
    306 ================
    307 */
    308 byte * R_MipMapWithGamma( const byte *in, int width, int height ) {
    309 	int		i, j;
    310 	const byte	*in_p;
    311 	byte	*out, *out_p;
    312 	int		row;
    313 	int		newWidth, newHeight;
    314 
    315 	if ( width < 1 || height < 1 || ( width + height == 2 ) ) {
    316 		return NULL;
    317 	}
    318 
    319 	row = width * 4;
    320 
    321 	newWidth = width >> 1;
    322 	newHeight = height >> 1;
    323 	if ( !newWidth ) {
    324 		newWidth = 1;
    325 	}
    326 	if ( !newHeight ) {
    327 		newHeight = 1;
    328 	}
    329 	out = (byte *)R_StaticAlloc( newWidth * newHeight * 4, TAG_IMAGE );
    330 	out_p = out;
    331 
    332 	in_p = in;
    333 
    334 	width >>= 1;
    335 	height >>= 1;
    336 
    337 	if ( width == 0 || height == 0 ) {
    338 		width += height;	// get largest
    339 		for (i=0 ; i<width ; i++, out_p+=4, in_p+=8 ) {
    340 			out_p[0] = idMath::Ftob( 255.0f * idMath::Pow( 0.5f * ( mip_gammaTable[in_p[0]] + mip_gammaTable[in_p[4]] ), 1.0f / 2.2f ) );
    341 			out_p[1] = idMath::Ftob( 255.0f * idMath::Pow( 0.5f * ( mip_gammaTable[in_p[1]] + mip_gammaTable[in_p[5]] ), 1.0f / 2.2f ) );
    342 			out_p[2] = idMath::Ftob( 255.0f * idMath::Pow( 0.5f * ( mip_gammaTable[in_p[2]] + mip_gammaTable[in_p[6]] ), 1.0f / 2.2f ) );
    343 			out_p[3] = idMath::Ftob( 255.0f * idMath::Pow( 0.5f * ( mip_gammaTable[in_p[3]] + mip_gammaTable[in_p[7]] ), 1.0f / 2.2f ) );
    344 		}
    345 		return out;
    346 	}
    347 	for (i=0 ; i<height ; i++, in_p+=row) {
    348 		for (j=0 ; j<width ; j++, out_p+=4, in_p+=8) {
    349 			out_p[0] = idMath::Ftob( 255.0f * idMath::Pow( 0.25f * ( mip_gammaTable[in_p[0]] + mip_gammaTable[in_p[4]] + mip_gammaTable[in_p[row+0]] + mip_gammaTable[in_p[row+4]] ), 1.0f / 2.2f ) );
    350 			out_p[1] = idMath::Ftob( 255.0f * idMath::Pow( 0.25f * ( mip_gammaTable[in_p[1]] + mip_gammaTable[in_p[5]] + mip_gammaTable[in_p[row+1]] + mip_gammaTable[in_p[row+5]] ), 1.0f / 2.2f ) );
    351 			out_p[2] = idMath::Ftob( 255.0f * idMath::Pow( 0.25f * ( mip_gammaTable[in_p[2]] + mip_gammaTable[in_p[6]] + mip_gammaTable[in_p[row+2]] + mip_gammaTable[in_p[row+6]] ), 1.0f / 2.2f ) );
    352 			out_p[3] = idMath::Ftob( 255.0f * idMath::Pow( 0.25f * ( mip_gammaTable[in_p[3]] + mip_gammaTable[in_p[7]] + mip_gammaTable[in_p[row+3]] + mip_gammaTable[in_p[row+7]] ), 1.0f / 2.2f ) );
    353 		}
    354 	}
    355 
    356 	return out;
    357 }
    358 
    359 /*
    360 ================
    361 R_MipMap
    362 
    363 Returns a new copy of the texture, quartered in size and filtered.
    364 ================
    365 */
    366 byte * R_MipMap( const byte *in, int width, int height ) {
    367 	int		i, j;
    368 	const byte	*in_p;
    369 	byte	*out, *out_p;
    370 	int		row;
    371 	int		newWidth, newHeight;
    372 
    373 	if ( width < 1 || height < 1 || ( width + height == 2 ) ) {
    374 		return NULL;
    375 	}
    376 
    377 	row = width * 4;
    378 
    379 	newWidth = width >> 1;
    380 	newHeight = height >> 1;
    381 	if ( !newWidth ) {
    382 		newWidth = 1;
    383 	}
    384 	if ( !newHeight ) {
    385 		newHeight = 1;
    386 	}
    387 	out = (byte *)R_StaticAlloc( newWidth * newHeight * 4, TAG_IMAGE );
    388 	out_p = out;
    389 
    390 	in_p = in;
    391 
    392 	width >>= 1;
    393 	height >>= 1;
    394 
    395 	if ( width == 0 || height == 0 ) {
    396 		width += height;	// get largest
    397 		for (i=0 ; i<width ; i++, out_p+=4, in_p+=8 ) {
    398 			out_p[0] = ( in_p[0] + in_p[4] )>>1;
    399 			out_p[1] = ( in_p[1] + in_p[5] )>>1;
    400 			out_p[2] = ( in_p[2] + in_p[6] )>>1;
    401 			out_p[3] = ( in_p[3] + in_p[7] )>>1;
    402 		}
    403 		return out;
    404 	}
    405 
    406 	for (i=0 ; i<height ; i++, in_p+=row) {
    407 		for (j=0 ; j<width ; j++, out_p+=4, in_p+=8) {
    408 			out_p[0] = (in_p[0] + in_p[4] + in_p[row+0] + in_p[row+4])>>2;
    409 			out_p[1] = (in_p[1] + in_p[5] + in_p[row+1] + in_p[row+5])>>2;
    410 			out_p[2] = (in_p[2] + in_p[6] + in_p[row+2] + in_p[row+6])>>2;
    411 			out_p[3] = (in_p[3] + in_p[7] + in_p[row+3] + in_p[row+7])>>2;
    412 		}
    413 	}
    414 
    415 	return out;
    416 }
    417 
    418 /*
    419 ==================
    420 R_BlendOverTexture
    421 
    422 Apply a color blend over a set of pixels
    423 ==================
    424 */
    425 void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[4] ) {
    426 	int		i;
    427 	int		inverseAlpha;
    428 	int		premult[3];
    429 
    430 	inverseAlpha = 255 - blend[3];
    431 	premult[0] = blend[0] * blend[3];
    432 	premult[1] = blend[1] * blend[3];
    433 	premult[2] = blend[2] * blend[3];
    434 
    435 	for ( i = 0 ; i < pixelCount ; i++, data+=4 ) {
    436 		data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9;
    437 		data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9;
    438 		data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9;
    439 	}
    440 }
    441 
    442 
    443 /*
    444 ==================
    445 R_HorizontalFlip
    446 
    447 Flip the image in place
    448 ==================
    449 */
    450 void R_HorizontalFlip( byte *data, int width, int height ) {
    451 	int		i, j;
    452 	int		temp;
    453 
    454 	for ( i = 0 ; i < height ; i++ ) {
    455 		for ( j = 0 ; j < width / 2 ; j++ ) {
    456 			temp = *( (int *)data + i * width + j );
    457 			*( (int *)data + i * width + j ) = *( (int *)data + i * width + width - 1 - j );
    458 			*( (int *)data + i * width + width - 1 - j ) = temp;
    459 		}
    460 	}
    461 }
    462 
    463 void R_VerticalFlip( byte *data, int width, int height ) {
    464 	int		i, j;
    465 	int		temp;
    466 
    467 	for ( i = 0 ; i < width ; i++ ) {
    468 		for ( j = 0 ; j < height / 2 ; j++ ) {
    469 			temp = *( (int *)data + j * width + i );
    470 			*( (int *)data + j * width + i ) = *( (int *)data + ( height - 1 - j ) * width + i );
    471 			*( (int *)data + ( height - 1 - j ) * width + i ) = temp;
    472 		}
    473 	}
    474 }
    475 
    476 void R_RotatePic( byte *data, int width ) {
    477 	int		i, j;
    478 	int		*temp;
    479 
    480 	temp = (int *)R_StaticAlloc( width * width * 4, TAG_IMAGE );
    481 
    482 	for ( i = 0 ; i < width ; i++ ) {
    483 		for ( j = 0 ; j < width ; j++ ) {
    484 			*( temp + i * width + j ) = *( (int *)data + j * width + i );
    485 		}
    486 	}
    487 
    488 	memcpy( data, temp, width * width * 4 );
    489 
    490 	R_StaticFree( temp );
    491 }
    492