This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/ww3d2/texturethumbnail.cpp

691 lines
No EOL
20 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** 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.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "texturethumbnail.h"
#include "hashtemplate.h"
#include "missingtexture.h"
#include "targa.h"
#include "ww3dformat.h"
#include "ddsfile.h"
#include "textureloader.h"
#include "bitmaphandler.h"
#include "ffactory.h"
#include "rawfile.h"
#include "mixfile.h"
#include <windows.h>
DLListClass<ThumbnailManagerClass> ThumbnailManagerClass::ThumbnailManagerList;
static bool message_box_displayed=false;
const char* ThumbFileHeader="THU4";
static void Create_Hash_Name(StringClass& name, const StringClass& thumb_name)
{
name=thumb_name;
int len=name.Get_Length();
WWASSERT(!stricmp(&name[len-4],".tga") || !stricmp(&name[len-4],".dds"));
name[len-4]='\0';
}
/* file_auto_ptr my_tga_file(_TheFileFactory,filename);
if (my_tga_file->Is_Available()) {
my_tga_file->Open();
unsigned size=my_tga_file->Size();
char* tga_memory=new char[size];
my_tga_file->Read(tga_memory,size);
my_tga_file->Close();
StringClass pth("data\\");
pth+=filename;
RawFileClass tmp_tga_file(pth);
tmp_tga_file.Create();
tmp_tga_file.Write(tga_memory,size);
tmp_tga_file.Close();
delete[] tga_memory;
}
*/
ThumbnailClass::ThumbnailClass(
ThumbnailManagerClass* manager,
const char* name,
unsigned char* bitmap,
unsigned w,
unsigned h,
unsigned original_w,
unsigned original_h,
unsigned original_mip_level_count,
WW3DFormat original_format,
bool allocated,
unsigned long date_time)
:
Manager(manager),
Name(name),
Bitmap(bitmap),
Allocated(allocated),
Width(w),
Height(h),
OriginalTextureWidth(original_w),
OriginalTextureHeight(original_h),
OriginalTextureMipLevelCount(original_mip_level_count),
OriginalTextureFormat(original_format),
DateTime(date_time)
{
Manager->Insert_To_Hash(this);
}
// ----------------------------------------------------------------------------
//
// Load texture and generate mipmap levels if requested. The function tries
// to create texture that matches targa format. If suitable format is not
// available, it selects closest matching format and performs color space
// conversion.
//
// ----------------------------------------------------------------------------
ThumbnailClass::ThumbnailClass(ThumbnailManagerClass* manager, const StringClass& filename)
:
Manager(manager),
Bitmap(0),
Name(filename),
Allocated(false),
Width(0),
Height(0),
OriginalTextureWidth(0),
OriginalTextureHeight(0),
OriginalTextureMipLevelCount(0),
OriginalTextureFormat(WW3D_FORMAT_UNKNOWN),
DateTime(0)
{
unsigned reduction_factor=3;
// First, try loading image from a DDS file
DDSFileClass dds_file(filename,reduction_factor);
if (dds_file.Is_Available() && dds_file.Load()) {
DateTime=dds_file.Get_Date_Time();
int len=Name.Get_Length();
WWASSERT(len>4);
Name[len-3]='d';
Name[len-2]='d';
Name[len-1]='s';
unsigned level=0;
while (dds_file.Get_Width(level)>32 || dds_file.Get_Height(level)>32) {
if (level>=dds_file.Get_Mip_Level_Count()) break;
level++;
}
OriginalTextureWidth=dds_file.Get_Full_Width();
OriginalTextureHeight=dds_file.Get_Full_Height();
OriginalTextureFormat=dds_file.Get_Format();
OriginalTextureMipLevelCount=dds_file.Get_Mip_Level_Count();
Width=dds_file.Get_Width(0);
Height=dds_file.Get_Height(0);
Bitmap=new unsigned char[Width*Height*2];
Allocated=true;
dds_file.Copy_Level_To_Surface(
0, // Level
WW3D_FORMAT_A4R4G4B4,
Width,
Height,
Bitmap,
Width*2);
}
// If DDS file can't be used try loading from TGA
else {
// Make sure the file can be opened. If not, return missing texture.
Targa targa;
if (TARGA_ERROR_HANDLER(targa.Open(filename,TGA_READMODE),filename)) return;
// DX8 uses image upside down compared to TGA
targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN;
WW3DFormat src_format,dest_format;
unsigned src_bpp=0;
Get_WW3D_Format(src_format,src_bpp,targa);
if (src_format==WW3D_FORMAT_UNKNOWN) {
WWDEBUG_SAY(("Unknown texture format for %s\n",filename));
return;
}
// Destination size will be the next power of two square from the larger width and height...
OriginalTextureWidth=targa.Header.Width;
OriginalTextureHeight=targa.Header.Height;
OriginalTextureFormat=src_format;
Width=targa.Header.Width>>reduction_factor;
Height=targa.Header.Height>>reduction_factor;
OriginalTextureMipLevelCount=1;
unsigned iw=1;
unsigned ih=1;
while (iw<OriginalTextureWidth && ih<OriginalTextureHeight) {
iw+=iw;
ih+=ih;
OriginalTextureMipLevelCount++;
}
while (Width>32 || Height>32) {
reduction_factor++;
Width>>=2;
Height>>=2;
}
unsigned poweroftwowidth = 1;
while (poweroftwowidth < Width) {
poweroftwowidth <<= 1;
}
unsigned poweroftwoheight = 1;
while (poweroftwoheight < Height) {
poweroftwoheight <<= 1;
}
Width=poweroftwowidth;
Height=poweroftwoheight;
unsigned src_width=targa.Header.Width;
unsigned src_height=targa.Header.Height;
// NOTE: We load the palette but we do not yet support paletted textures!
char palette[256*4];
targa.SetPalette(palette);
if (TARGA_ERROR_HANDLER(targa.Load(filename, TGAF_IMAGE, false),filename)) return;
// Get time stamp from the tga file
{
file_auto_ptr my_tga_file(_TheFileFactory,filename);
WWASSERT(my_tga_file->Is_Available());
my_tga_file->Open();
DateTime=my_tga_file->Get_Date_Time();
my_tga_file->Close();
}
unsigned char* src_surface=(unsigned char*)targa.GetImage();
int len=Name.Get_Length();
WWASSERT(len>4);
Name[len-3]='t';
Name[len-2]='g';
Name[len-1]='a';
Bitmap=new unsigned char[Width*Height*2];
Allocated=true;
dest_format=WW3D_FORMAT_A8R8G8B8;
BitmapHandlerClass::Copy_Image(
Bitmap,
Width,
Height,
Width*2,
WW3D_FORMAT_A4R4G4B4,
src_surface,
src_width,
src_height,
src_width*src_bpp,
src_format,
(unsigned char*)targa.GetPalette(),
targa.Header.CMapDepth>>3,
false);
}
Manager->Insert_To_Hash(this);
}
ThumbnailClass::~ThumbnailClass()
{
if (Allocated) delete[] Bitmap;
Manager->Remove_From_Hash(this);
}
void ThumbnailManagerClass::Create_Thumbnails()
{
SimpleFileFactoryClass ff;
ff.Set_Sub_Directory("Data\\");
MixFileFactoryClass mix(MixFileName, &ff);
FileFactoryClass* old_file_factory=_TheFileFactory;
_TheFileFactory=&mix;
if (mix.Is_Valid()) {
DynamicVectorClass<StringClass> list;
list.Set_Growth_Step (1000);
mix.Build_Filename_List(list);
for (int i=0;i<list.Count();++i) {
int len=list[i].Get_Length();
if (!stricmp(&list[i][len-4],".tga") || !stricmp(&list[i][len-4],".dds")) {
if (!Peek_Thumbnail_Instance(list[i])) {
new ThumbnailClass(this,list[i]);
}
}
}
}
_TheFileFactory=old_file_factory;
}
void ThumbnailManagerClass::Load()
{
WWASSERT(!ThumbnailMemory);
// If the thumbnail hash table file is available, init hash table
DateTime=0;
file_auto_ptr thumb_file(_TheFileFactory, ThumbnailFileName);
if (thumb_file->Is_Available()) {
thumb_file->Open(FileClass::READ);
DateTime=thumb_file->Get_Date_Time();
char tmp[4];
thumb_file->Read(tmp,4);
if (tmp[0]==ThumbFileHeader[0] &&
tmp[1]==ThumbFileHeader[1] &&
tmp[2]==ThumbFileHeader[2] &&
tmp[3]==ThumbFileHeader[3]) {
int total_thumb_count;
int total_header_length;
int total_data_length;
thumb_file->Read(&total_thumb_count,sizeof(int));
thumb_file->Read(&total_header_length,sizeof(int));
thumb_file->Read(&total_data_length,sizeof(int));
if (total_thumb_count) {
WWASSERT(total_data_length && total_header_length);
ThumbnailMemory=new unsigned char[total_data_length];
// Load thumbs
for (int i=0;i<total_thumb_count;++i) {
char name[256];
int offset;
int width;
int height;
int original_width;
int original_height;
int original_mip_level_count;
WW3DFormat original_format;
int name_len;
unsigned long date_time;
thumb_file->Read(&date_time,sizeof(unsigned long));
thumb_file->Read(&offset,sizeof(int));
thumb_file->Read(&width,sizeof(int));
thumb_file->Read(&height,sizeof(int));
thumb_file->Read(&original_width,sizeof(int));
thumb_file->Read(&original_height,sizeof(int));
thumb_file->Read(&original_mip_level_count,sizeof(int));
thumb_file->Read(&original_format,sizeof(int));
thumb_file->Read(&name_len,sizeof(int));
WWASSERT(name_len<255);
thumb_file->Read(name,name_len);
name[name_len]='\0';
// If per-texture time stamp test is enabled, thumbnail is only used if its time stamp
// matches the texture's time stamp.
bool valid=true;
if (Is_Per_Texture_Time_Stamp_Used()) {
file_auto_ptr texture_file(_TheFileFactory, name);
if (texture_file->Is_Available()) {
texture_file->Open();
if (texture_file->Get_Date_Time()!=date_time) {
valid=false;
}
texture_file->Close();
}
else {
valid=false;
}
}
if (valid) {
new ThumbnailClass(
this,
name,
ThumbnailMemory+offset-total_header_length,
width,
height,
original_width,
original_height,
original_mip_level_count,
original_format,
false,
date_time);
}
}
thumb_file->Read(ThumbnailMemory,total_data_length);
}
}
thumb_file->Close();
}
Changed=false;
}
void ThumbnailManagerClass::Save(bool force)
{
// Save only if changed
if (!Changed && !force) return;
Changed=false;
// If the thumbnail hash table was modified, save it to disk
HashTemplateIterator<StringClass,ThumbnailClass*> ite(ThumbnailHash);
int total_header_length=0;
int total_data_length=0;
int total_thumb_count=0;
total_header_length+=4; // header
total_header_length+=4; // thumb count
total_header_length+=4; // header size
total_header_length+=4; // data length
for (ite.First();!ite.Is_Done();ite.Next()) {
ThumbnailClass* thumb=ite.Peek_Value();
total_header_length+=4; // per-thumb date-time
total_header_length+=4; // int bitmap offset
total_header_length+=4; // int bitmap width
total_header_length+=4; // int bitmap height
total_header_length+=4; // int original bitmap width
total_header_length+=4; // int original bitmap height
total_header_length+=4; // int original mip level count
total_header_length+=4; // int original format
total_header_length+=4; // int name string length
total_header_length+=strlen(thumb->Get_Name());
total_data_length+=thumb->Get_Width()*thumb->Get_Height()*2;
total_thumb_count++;
}
int offset=total_header_length;
file_auto_ptr thumb_file(_TheWritingFileFactory, ThumbnailFileName);
if (thumb_file->Is_Available()) {
thumb_file->Delete();
}
thumb_file->Create();
thumb_file->Open(FileClass::WRITE);
thumb_file->Write(ThumbFileHeader,4);
thumb_file->Write(&total_thumb_count,sizeof(int));
thumb_file->Write(&total_header_length,sizeof(int));
thumb_file->Write(&total_data_length,sizeof(int));
// Save names and offsets
for (ite.First();!ite.Is_Done();ite.Next()) {
ThumbnailClass* thumb=ite.Peek_Value();
const char* name=thumb->Get_Name();
int name_len=strlen(name);
int width=thumb->Get_Width();
int height=thumb->Get_Height();
int original_width=thumb->Get_Original_Texture_Width();
int original_height=thumb->Get_Original_Texture_Height();
int original_mip_level_count=thumb->Get_Original_Texture_Mip_Level_Count();
WW3DFormat original_format=thumb->Get_Original_Texture_Format();
unsigned long date_time=thumb->Get_Date_Time();
thumb_file->Write(&date_time,sizeof(unsigned long));
thumb_file->Write(&offset,sizeof(int));
thumb_file->Write(&width,sizeof(int));
thumb_file->Write(&height,sizeof(int));
thumb_file->Write(&original_width,sizeof(int));
thumb_file->Write(&original_height,sizeof(int));
thumb_file->Write(&original_mip_level_count,sizeof(int));
thumb_file->Write(&original_format,sizeof(int));
thumb_file->Write(&name_len,sizeof(int));
thumb_file->Write(name,name_len);
offset+=width*height*2;
}
// Save bitmaps
offset=total_header_length;
for (ite.First();!ite.Is_Done();ite.Next()) {
ThumbnailClass* thumb=ite.Peek_Value();
int width=thumb->Get_Width();
int height=thumb->Get_Height();
thumb_file->Write(thumb->Peek_Bitmap(),width*height*2);
}
if (DateTime) thumb_file->Set_Date_Time(DateTime);
thumb_file->Close();
}
// ----------------------------------------------------------------------------
ThumbnailManagerClass::ThumbnailManagerClass(const char* thumbnail_filename, const char* mix_filename)
:
ThumbnailMemory(NULL),
ThumbnailFileName(thumbnail_filename),
MixFileName(mix_filename),
PerTextureTimeStampUsed(false),
CreateThumbnailIfNotFound(false),
Changed(false),
DateTime(0)
{
Load();
}
// ----------------------------------------------------------------------------
ThumbnailManagerClass::~ThumbnailManagerClass()
{
Save();
HashTemplateIterator<StringClass,ThumbnailClass*> ite(ThumbnailHash);
ite.First();
while (!ite.Is_Done()) {
ThumbnailClass* thumb=ite.Peek_Value();
delete thumb;
ite.First();
}
if (ThumbnailMemory) delete[] ThumbnailMemory;
ThumbnailMemory=NULL;
}
// ----------------------------------------------------------------------------
void ThumbnailManagerClass::Add_Thumbnail_Manager(const char* thumbnail_filename, const char* mix_filename)
{
// First loop over all thumbnail managers to see if we already have this one created. This isn't
// supposed to be called often at all and there are usually just couple managers alive,
// so we'll do pure string compares here...
ThumbnailManagerClass* man=ThumbnailManagerList.Head();
while (man) {
if (man->ThumbnailFileName==thumbnail_filename) return;
man=man->Succ();
}
// Always update thumbnail files when they're out of date
Update_Thumbnail_File(mix_filename,false);
// Not found, create and add to the list.
man=new ThumbnailManagerClass(thumbnail_filename,mix_filename);
ThumbnailManagerList.Add_Tail(man);
}
// ----------------------------------------------------------------------------
void ThumbnailManagerClass::Remove_Thumbnail_Manager(const char* thumbnail_filename)
{
ThumbnailManagerClass* man=ThumbnailManagerList.Head();
while (man) {
if (man->ThumbnailFileName==thumbnail_filename) {
delete man;
return;
}
man=man->Succ();
}
}
// ----------------------------------------------------------------------------
ThumbnailClass* ThumbnailManagerClass::Peek_Thumbnail_Instance(const StringClass& name)
{
ThumbnailClass* thumb=Get_From_Hash(name);
if (!thumb) {
if (Is_Thumbnail_Created_If_Not_Found()) {
thumb=new ThumbnailClass(this,name);
if (!thumb->Peek_Bitmap()) {
delete thumb;
thumb=NULL;
}
}
}
return thumb;
}
void ThumbnailManagerClass::Insert_To_Hash(ThumbnailClass* thumb)
{
Changed=true;
StringClass hash_name(0,true);
Create_Hash_Name(hash_name,thumb->Get_Name());
ThumbnailHash.Insert(hash_name,thumb);
}
ThumbnailClass* ThumbnailManagerClass::Get_From_Hash(const StringClass& name)
{
StringClass hash_name(0,true);
Create_Hash_Name(hash_name,name);
return ThumbnailHash.Get(hash_name);
}
void ThumbnailManagerClass::Remove_From_Hash(ThumbnailClass* thumb)
{
Changed=true;
StringClass hash_name(0,true);
Create_Hash_Name(hash_name,thumb->Get_Name());
ThumbnailHash.Remove(hash_name);
}
void ThumbnailManagerClass::Update_Thumbnail_File(const char* mix_file_name,bool display_message_box)
{
SimpleFileFactoryClass ff;
ff.Set_Sub_Directory("Data\\");
StringClass thumb_file_name(mix_file_name,true);
int len=thumb_file_name.Get_Length();
WWASSERT(len>4);
thumb_file_name[len-3]='t';
thumb_file_name[len-2]='h';
thumb_file_name[len-1]='u';
FileClass* mix_file=ff.Get_File(mix_file_name);
FileClass* thumb_file=ff.Get_File(thumb_file_name);
WWASSERT(mix_file && thumb_file);
// If mix file isn't found but thumb file is, delete the obsolete thumb file
mix_file->Open(FileClass::READ);
thumb_file->Open(FileClass::READ); //|FileClass::WRITE); Changed this to READ only since we never use the handle for writing and it may cause contention amongst slave servers ST - 12/14/2001 8:26PM
if (!mix_file->Is_Available()) {
if (thumb_file->Is_Available()) {
thumb_file->Delete();
}
mix_file->Close();
thumb_file->Close();
ff.Return_File(mix_file);
ff.Return_File(thumb_file);
return;
}
unsigned long mix_date_time=mix_file->Get_Date_Time();
if (thumb_file->Is_Available()) {
unsigned long thumb_date_time=thumb_file->Get_Date_Time();
if (mix_date_time!=thumb_date_time) {
thumb_file->Delete();
}
// Read header to make sure the thumb file is of correct version.
char tmp[4];
thumb_file->Read(tmp,4);
if (tmp[0]!=ThumbFileHeader[0] ||
tmp[1]!=ThumbFileHeader[1] ||
tmp[2]!=ThumbFileHeader[2] ||
tmp[3]!=ThumbFileHeader[3]) {
thumb_file->Delete();
}
}
if (thumb_file->Is_Available()) {
mix_file->Close();
thumb_file->Close();
ff.Return_File(mix_file);
ff.Return_File(thumb_file);
return;
}
if (display_message_box && !message_box_displayed) {
message_box_displayed=true;
::MessageBox(NULL,
"Some or all texture thumbnails need to be updated.\n"
"This will take a while. The update will only be done once\n"
"each time a mix file changes and thumb database hasn't been\n"
"updated.",
"Updating texture thumbnails",
MB_OK);
}
// we don't currently have a thumbnail file (either we just deleted it or it never existed, we don't care)
// so we must create one now.
ThumbnailManagerClass* manager=new ThumbnailManagerClass(thumb_file_name, mix_file_name);
manager->DateTime=mix_date_time; // Set the datetime to mixfile's datetime.
manager->Create_Thumbnails();
manager->Save(true);
delete manager;
// close files and return pointers
mix_file->Close();
thumb_file->Close();
ff.Return_File(mix_file);
ff.Return_File(thumb_file);
}
// Verify that up-to-date thumbnails exist for all textures
void ThumbnailManagerClass::Pre_Init(bool display_message_box)
{
WWASSERT(!ThumbnailManagerList.Head());
// Collect all mix file names
DynamicVectorClass<StringClass> mix_names;
char cur_dir[256];
GetCurrentDirectory(sizeof(cur_dir),cur_dir);
StringClass new_dir(cur_dir,true);
new_dir+="\\Data";
SetCurrentDirectory(new_dir);
WIN32_FIND_DATA find_data;
HANDLE handle=FindFirstFile("*.mix",&find_data);
if (handle!=INVALID_HANDLE_VALUE) {
for (;;) {
if (!(find_data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
mix_names.Add(find_data.cFileName);
}
if (!FindNextFile(handle,&find_data)) {
FindClose(handle);
break;
}
}
}
SetCurrentDirectory(cur_dir);
// First generate thumbnails for always.dat
Update_Thumbnail_File("always.dat",display_message_box);
// Then loop over all .mix files
for (int i=0;i<mix_names.Count();++i) {
Update_Thumbnail_File(mix_names[i],display_message_box);
}
}
void ThumbnailManagerClass::Init()
{
// Renegade uses always.dat... Should all our games use, or should this be moved to somewhere else?
Add_Thumbnail_Manager("always.thu","always.dat");
Add_Thumbnail_Manager("always2.thu","always2.dat");
}
void ThumbnailManagerClass::Deinit()
{
while (ThumbnailManagerClass* man=ThumbnailManagerList.Head()) {
delete man;
}
}