forked from FWGS/Paranoia2
371 lines
8.9 KiB
C++
371 lines
8.9 KiB
C++
/*
|
|
miptex.cpp - prepare and store miptex into the wad
|
|
Copyright (C) 2015 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 "conprint.h"
|
|
#include <windows.h>
|
|
#include <direct.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <io.h>
|
|
#include "cmdlib.h"
|
|
#include "stringlib.h"
|
|
#include "filesystem.h"
|
|
#include "imagelib.h"
|
|
#include "makewad.h"
|
|
#include "miptex.h"
|
|
|
|
byte lbmpalette[256*3];
|
|
float linearpalette[256][3];
|
|
float d_red, d_green, d_blue;
|
|
bool color_used[256];
|
|
float maxdistortion;
|
|
int colors_used;
|
|
byte pixdata[256];
|
|
|
|
/*
|
|
=============
|
|
MIP_AddColor
|
|
|
|
add unique color and restore original gamma
|
|
=============
|
|
*/
|
|
byte MIP_AddColor( float r, float g, float b )
|
|
{
|
|
// one color as reserved for transparent
|
|
for( int i = 0; i < 255; i++ )
|
|
{
|
|
if( !color_used[i] )
|
|
{
|
|
linearpalette[i][0] = r;
|
|
linearpalette[i][1] = g;
|
|
linearpalette[i][2] = b;
|
|
|
|
r = bound( 0.0f, r, 1.0f );
|
|
lbmpalette[i*3+0] = (byte)pow( r, INVGAMMA ) * 255;
|
|
g = bound( 0.0f, g, 1.0f );
|
|
lbmpalette[i*3+1] = (byte)pow( g, INVGAMMA ) * 255;
|
|
r = bound( 0.0f, r, 1.0f );
|
|
lbmpalette[i*3+2] = (byte)pow( b, INVGAMMA ) * 255;
|
|
|
|
color_used[i] = true;
|
|
colors_used++;
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
MIP_AveragePixels
|
|
|
|
average pixels for mip-mapping
|
|
=============
|
|
*/
|
|
byte MIP_AveragePixels( int count )
|
|
{
|
|
float bestdistortion, distortion;
|
|
float r, g, b, dr, dg, db;
|
|
int i, pix, bestcolor;
|
|
|
|
r = g = b = 0.0f;
|
|
|
|
for( i = 0; i < count; i++ )
|
|
{
|
|
pix = pixdata[i];
|
|
r += linearpalette[pix][0];
|
|
g += linearpalette[pix][1];
|
|
b += linearpalette[pix][2];
|
|
}
|
|
|
|
r /= count;
|
|
g /= count;
|
|
b /= count;
|
|
|
|
r += d_red;
|
|
g += d_green;
|
|
b += d_blue;
|
|
|
|
// find the best color
|
|
bestdistortion = 3.0;
|
|
bestcolor = -1;
|
|
|
|
for( i = 0; i < 255; i++ )
|
|
{
|
|
if( color_used[i] )
|
|
{
|
|
pix = i;
|
|
dr = r - linearpalette[i][0];
|
|
dg = g - linearpalette[i][1];
|
|
db = b - linearpalette[i][2];
|
|
|
|
distortion = (dr * dr) + (dg * dg) + (db * db);
|
|
|
|
if( distortion < bestdistortion )
|
|
{
|
|
if( !distortion )
|
|
{
|
|
d_red = d_green = d_blue = 0.0f; // no distortion yet
|
|
return pix; // perfect match
|
|
}
|
|
|
|
bestdistortion = distortion;
|
|
bestcolor = pix;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bestdistortion > 0.001f && colors_used < 255 )
|
|
{
|
|
bestcolor = MIP_AddColor( r, g, b );
|
|
d_red = d_green = d_blue = 0.0f;
|
|
bestdistortion = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// error diffusion
|
|
d_red = r - linearpalette[bestcolor][0];
|
|
d_green = g - linearpalette[bestcolor][1];
|
|
d_blue = b - linearpalette[bestcolor][2];
|
|
}
|
|
|
|
if( bestdistortion > maxdistortion )
|
|
maxdistortion = bestdistortion;
|
|
|
|
return bestcolor;
|
|
}
|
|
|
|
bool MIP_WriteMiptex( const char *lumpname, rgbdata_t *pix )
|
|
{
|
|
char tmpname[64];
|
|
mip_t *mip;
|
|
|
|
// check for all the possible problems
|
|
if( !pix || !FBitSet( pix->flags, IMAGE_QUANTIZED ))
|
|
return false;
|
|
|
|
if(( pix->width & 7 ) || ( pix->height & 7 ))
|
|
return false; // not aligned by 16
|
|
|
|
if( pix->width < IMAGE_MINWIDTH || pix->width > IMAGE_MAXWIDTH || pix->height < IMAGE_MINHEIGHT || pix->height > IMAGE_MAXHEIGHT )
|
|
return false; // to small or too large
|
|
|
|
// calculate gamma corrected linear palette
|
|
for( int i = 0; i < 256; i++ )
|
|
{
|
|
// setup palette
|
|
lbmpalette[i*3+0] = pix->palette[i*4+0];
|
|
lbmpalette[i*3+1] = pix->palette[i*4+1];
|
|
lbmpalette[i*3+2] = pix->palette[i*4+2];
|
|
|
|
for( int j = 0; j < 3; j++ )
|
|
{
|
|
float f = lbmpalette[i*3+j] / 255.0f;
|
|
linearpalette[i][j] = pow( f, GAMMA ); // assume textures are done at 2.2, we want to remap them at 1.0
|
|
}
|
|
}
|
|
|
|
char hint = W_HintFromSuf( lumpname );
|
|
bool all_colors = false;
|
|
|
|
size_t pixels = pix->width * pix->height;
|
|
size_t lumpsize = sizeof( mip_t ) + (( pixels * 85 )>>6) + sizeof( short ) + 768;
|
|
byte *lumpbuffer, *lump_p;
|
|
|
|
// all the lumps must be aligned by 4
|
|
// or Wally will stop working properly
|
|
lumpsize = (lumpsize + 3) & ~3;
|
|
maxdistortion = 0.0f;
|
|
|
|
if( FBitSet( pix->flags, IMAGE_HAS_1BIT_ALPHA ))
|
|
all_colors = true;
|
|
|
|
if( hint == IMG_NORMALMAP )
|
|
all_colors = true;
|
|
|
|
if( all_colors )
|
|
{
|
|
// assume palette full for some reasons
|
|
for( int i = 0; i < 256; i++ )
|
|
color_used[i] = true;
|
|
colors_used = 256;
|
|
}
|
|
else
|
|
{
|
|
// figure out what palette entries are actually used
|
|
for( int i = 0; i < 256; i++ )
|
|
color_used[i] = false;
|
|
colors_used = 0;
|
|
|
|
for( int x = 0; x < pix->width; x++ )
|
|
{
|
|
for( int y = 0; y < pix->height; y++ )
|
|
{
|
|
int color = pix->buffer[y * pix->width + x];
|
|
|
|
if( !color_used[color] )
|
|
{
|
|
color_used[color] = true;
|
|
colors_used++;
|
|
}
|
|
}
|
|
}
|
|
|
|
MsgDev( D_REPORT, "%s (colors %i)\n", lumpname, colors_used );
|
|
}
|
|
|
|
lumpbuffer = lump_p = (byte *)Mem_Alloc( lumpsize );
|
|
mip = (mip_t *)lumpbuffer;
|
|
|
|
// prepare lump name
|
|
Q_strncpy( tmpname, lumpname, sizeof( tmpname ));
|
|
if( hint != IMG_DIFFUSE ) tmpname[Q_strlen( tmpname ) - HINT_NAMELEN] = '\0'; // kill the suffix
|
|
|
|
// fill the header
|
|
Q_strncpy( mip->name, tmpname, WAD3_NAMELEN );
|
|
mip->width = pix->width;
|
|
mip->height = pix->height;
|
|
mip->offsets[0] = sizeof( mip_t );
|
|
lump_p += sizeof( mip_t );
|
|
|
|
// write miptex
|
|
memcpy( lump_p, pix->buffer, pixels );
|
|
lump_p += pixels;
|
|
|
|
// subsample for greater mip levels
|
|
for( int miplevel = 1; miplevel < 4; miplevel++ )
|
|
{
|
|
int mipstep = BIT( miplevel );
|
|
int pixTest = (int)((float)(mipstep * mipstep) * 0.4f ); // 40% of pixels
|
|
|
|
mip->offsets[miplevel] = lump_p - (byte *)mip;
|
|
d_red = d_green = d_blue = 0.0f; // no distortion yet
|
|
|
|
for( int y = 0; y < pix->height; y += mipstep )
|
|
{
|
|
for( int x = 0; x < pix->width; x += mipstep )
|
|
{
|
|
int count = 0;
|
|
|
|
for( int yy = 0; yy < mipstep; yy++ )
|
|
{
|
|
for( int xx = 0; xx < mipstep; xx++ )
|
|
{
|
|
byte testpixel = pix->buffer[(y + yy) * pix->width + x + xx];
|
|
|
|
// if 255 is not transparent, or this isn't a transparent pixel,
|
|
// add it in to the image filter
|
|
if( !FBitSet( pix->flags, IMAGE_HAS_1BIT_ALPHA ) || testpixel != 255 )
|
|
{
|
|
pixdata[count] = testpixel;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// solid pixels account for < 40% of this pixel, make it transparent
|
|
if( count > pixTest )
|
|
*lump_p++ = MIP_AveragePixels( count );
|
|
else *lump_p++ = 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write out palette
|
|
*(unsigned short *)lump_p = 256; // palette size
|
|
lump_p += sizeof( short );
|
|
|
|
memcpy( lump_p, lbmpalette, 768 );
|
|
lump_p += 768;
|
|
|
|
size_t disksize = (( lump_p - lumpbuffer ) + 3) & ~3;
|
|
|
|
if( lumpsize != disksize )
|
|
MsgDev( D_ERROR, "%s is corrupted (buffer is %s bytes, written %s)\n", lumpname, Q_memprint( lumpsize ), Q_memprint( disksize ));
|
|
|
|
bool result = W_SaveLump( output_wad, lumpname, lumpbuffer, lumpsize, TYP_MIPTEX, ATTR_NONE ) >= 0;
|
|
|
|
Mem_Free( lumpbuffer );
|
|
|
|
return result;
|
|
}
|
|
|
|
bool MIP_CheckForReplace( dlumpinfo_t *find, rgbdata_t *image, int &width, int &height )
|
|
{
|
|
// NOTE: we can replace this lump but this is unsafe
|
|
if( find != NULL )
|
|
{
|
|
size_t lumpsize = ((sizeof( mip_t ) + ((( width * height ) * 85 )>>6) + sizeof( short ) + 768) + 3) & ~3;
|
|
|
|
switch( GetReplaceLevel( ))
|
|
{
|
|
case REP_IGNORE:
|
|
MsgDev( D_ERROR, "MIP_CreateMiptex: %s already exist\n", find->name );
|
|
if( image ) Mem_Free( image );
|
|
return false;
|
|
case REP_NORMAL:
|
|
if( FBitSet( find->attribs, ATTR_READONLY ))
|
|
{
|
|
// g-cont. i left this limitation as a protect of the replacement of compressed lumps
|
|
MsgDev( D_ERROR, "W_ReplaceLump: %s is read-only\n", find->name );
|
|
if( image ) Mem_Free( image );
|
|
return false;
|
|
}
|
|
if( lumpsize != find->size )
|
|
{
|
|
MsgDev( D_ERROR, "W_ReplaceLump: %s.mip [%s] should be [%s]\n",
|
|
find->name, Q_memprint( lumpsize ), Q_memprint( find->size ));
|
|
if( image ) Mem_Free( image );
|
|
return false;
|
|
}
|
|
break;
|
|
case REP_FORCE:
|
|
if( lumpsize != find->size )
|
|
{
|
|
size_t oldpos;
|
|
mip_t test;
|
|
|
|
oldpos = tell( W_GetHandle( output_wad ) ); // don't forget restore original position
|
|
|
|
if( lseek( W_GetHandle( output_wad ), find->filepos, SEEK_SET ) == -1 )
|
|
{
|
|
MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name );
|
|
lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET );
|
|
if( image ) Mem_Free( image );
|
|
return false;
|
|
}
|
|
|
|
if( read( W_GetHandle( output_wad ), &test, sizeof( test )) != sizeof( test ))
|
|
{
|
|
MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name );
|
|
lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET );
|
|
if( image ) Mem_Free( image );
|
|
return false;
|
|
}
|
|
|
|
// get new sizes from the saved lump to resample current
|
|
lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET );
|
|
height = test.height;
|
|
width = test.width;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} |