Paranoia2/utils/makewad/miptex.cpp

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;
}