383 lines
13 KiB
C++
383 lines
13 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 "bitmaphandler.h"
|
|
#include "wwdebug.h"
|
|
|
|
void Bitmap_Assert(bool condition)
|
|
{
|
|
WWASSERT(condition);
|
|
}
|
|
|
|
void BitmapHandlerClass::Create_Mipmap_B8G8R8A8(
|
|
unsigned char* dest_surface,
|
|
unsigned dest_surface_pitch,
|
|
unsigned char* src_surface,
|
|
unsigned src_surface_pitch,
|
|
unsigned width,
|
|
unsigned height)
|
|
{
|
|
unsigned src_pitch=src_surface_pitch/4;
|
|
for (unsigned y=0;y<height;y+=2) {
|
|
unsigned* dest=(unsigned*)dest_surface;
|
|
dest_surface+=dest_surface_pitch;
|
|
unsigned* src=(unsigned*)src_surface;
|
|
src_surface+=src_surface_pitch;
|
|
for (unsigned x=0;x<width;x+=2) {
|
|
unsigned bgra3=src[src_pitch];
|
|
unsigned bgra1=*src++;
|
|
unsigned bgra4=src[src_pitch];
|
|
unsigned bgra2=*src++;
|
|
*dest++=Combine_A8R8G8B8(bgra1,bgra2,bgra3,bgra4);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BitmapHandlerClass::Copy_Image_Generate_Mipmap(
|
|
unsigned width,
|
|
unsigned height,
|
|
unsigned char* dest_surface,
|
|
unsigned dest_pitch,
|
|
WW3DFormat dest_format,
|
|
unsigned char* src_surface,
|
|
unsigned src_pitch,
|
|
WW3DFormat src_format,
|
|
unsigned char* mip_surface,
|
|
unsigned mip_pitch)
|
|
{
|
|
// Optimized loop if source and destination are 32 bit
|
|
if (src_format==dest_format && src_format==WW3D_FORMAT_A8R8G8B8) {
|
|
dest_pitch/=4;
|
|
src_pitch/=4;
|
|
mip_pitch/=4;
|
|
for (unsigned y=0;y<height/2;++y) {
|
|
unsigned* dest_ptr=(unsigned*)dest_surface;
|
|
dest_ptr+=2*y*dest_pitch;
|
|
unsigned* src_ptr=(unsigned*)src_surface;
|
|
src_ptr+=y*2*src_pitch;
|
|
unsigned* mip_ptr=(unsigned*)mip_surface;
|
|
mip_ptr+=y*mip_pitch;
|
|
unsigned b8g8r8a8_00;
|
|
unsigned b8g8r8a8_01;
|
|
unsigned b8g8r8a8_10;
|
|
unsigned b8g8r8a8_11;
|
|
for (unsigned x=0;x<width/2;x++) {
|
|
b8g8r8a8_10=src_ptr[src_pitch];
|
|
dest_ptr[dest_pitch]=b8g8r8a8_10;
|
|
b8g8r8a8_00=*src_ptr++;
|
|
*dest_ptr++=b8g8r8a8_00;
|
|
|
|
b8g8r8a8_11=src_ptr[src_pitch];
|
|
dest_ptr[dest_pitch]=b8g8r8a8_11;
|
|
b8g8r8a8_01=*src_ptr++;
|
|
*dest_ptr++=b8g8r8a8_01;
|
|
|
|
*mip_ptr++=Combine_A8R8G8B8(b8g8r8a8_00,b8g8r8a8_01,b8g8r8a8_10,b8g8r8a8_11);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
WWASSERT(src_format!=WW3D_FORMAT_P8); // This function doesn't support paletted formats
|
|
unsigned src_bpp=Get_Bytes_Per_Pixel(src_format);
|
|
unsigned dest_bpp=Get_Bytes_Per_Pixel(dest_format);
|
|
|
|
for (unsigned y=0;y<height/2;++y) {
|
|
unsigned char* dest_ptr=dest_surface+2*y*dest_pitch;
|
|
unsigned char* src_ptr=src_surface+y*2*src_pitch;
|
|
unsigned char* mip_ptr=mip_surface+y*mip_pitch;
|
|
unsigned b8g8r8a8_00;
|
|
unsigned b8g8r8a8_01;
|
|
unsigned b8g8r8a8_10;
|
|
unsigned b8g8r8a8_11;
|
|
for (unsigned x=0;x<width/2;x++,dest_ptr+=dest_bpp*2,src_ptr+=src_bpp*2,mip_ptr+=dest_bpp) {
|
|
Read_B8G8R8A8(b8g8r8a8_00,src_ptr,src_format,NULL,0);
|
|
Write_B8G8R8A8(dest_ptr,dest_format,b8g8r8a8_00);
|
|
|
|
Read_B8G8R8A8(b8g8r8a8_01,src_ptr+src_bpp,src_format,NULL,0);
|
|
Write_B8G8R8A8(dest_ptr+dest_bpp,dest_format,b8g8r8a8_01);
|
|
|
|
Read_B8G8R8A8(b8g8r8a8_10,src_ptr+src_pitch,src_format,NULL,0);
|
|
Write_B8G8R8A8(dest_ptr+dest_pitch,dest_format,b8g8r8a8_10);
|
|
|
|
Read_B8G8R8A8(b8g8r8a8_11,src_ptr+src_bpp+src_pitch,src_format,NULL,0);
|
|
Write_B8G8R8A8(dest_ptr+dest_bpp+dest_pitch,dest_format,b8g8r8a8_11);
|
|
|
|
unsigned b8g8r8a8=Combine_A8R8G8B8(b8g8r8a8_00,b8g8r8a8_01,b8g8r8a8_10,b8g8r8a8_11);
|
|
|
|
Write_B8G8R8A8(mip_ptr,dest_format,b8g8r8a8);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Copy image from source surface to destination surface with stretch and color
|
|
// space conversion if needed. If 'generate_mip_level' is set, process image
|
|
// in 2x2 blocks and generate mipmap on top of the original source image while
|
|
// copying.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void BitmapHandlerClass::Copy_Image(
|
|
unsigned char* dest_surface,
|
|
unsigned dest_surface_width,
|
|
unsigned dest_surface_height,
|
|
unsigned dest_surface_pitch,
|
|
WW3DFormat dest_surface_format,
|
|
unsigned char* src_surface,
|
|
unsigned src_surface_width,
|
|
unsigned src_surface_height,
|
|
unsigned src_surface_pitch,
|
|
WW3DFormat src_surface_format,
|
|
const unsigned char* src_palette,
|
|
unsigned src_palette_bpp,
|
|
bool generate_mip_level)
|
|
{
|
|
WWASSERT(dest_surface_width);
|
|
WWASSERT(dest_surface_height);
|
|
|
|
// Bumpmap?
|
|
if (dest_surface_format==WW3D_FORMAT_U8V8 ||
|
|
dest_surface_format==WW3D_FORMAT_L6V5U5 ||
|
|
dest_surface_format==WW3D_FORMAT_X8L8V8U8) {
|
|
|
|
unsigned src_bpp=Get_Bytes_Per_Pixel(src_surface_format);
|
|
|
|
for( unsigned y=0; y<dest_surface_height; y++ ) {
|
|
unsigned char* dest_ptr=dest_surface;
|
|
dest_ptr+=y*dest_surface_pitch;
|
|
unsigned char* src_ptr_mid=src_surface;
|
|
src_ptr_mid+=y*src_surface_pitch;
|
|
unsigned char* src_ptr_next_line = ( src_ptr_mid + src_surface_pitch );
|
|
unsigned char* src_ptr_prev_line = ( src_ptr_mid - src_surface_pitch );
|
|
|
|
if( y == src_surface_height-1 ) // Don't go past the last line
|
|
src_ptr_next_line = src_ptr_mid;
|
|
if( y == 0 ) // Don't go before first line
|
|
src_ptr_prev_line = src_ptr_mid;
|
|
|
|
for( unsigned x=0; x<dest_surface_width; x++ ) {
|
|
unsigned pixel00;
|
|
unsigned pixel01;
|
|
unsigned pixelM1;
|
|
unsigned pixel10;
|
|
unsigned pixel1M;
|
|
|
|
Read_B8G8R8A8(pixel00,src_ptr_mid,src_surface_format,NULL,0);
|
|
Read_B8G8R8A8(pixel01,src_ptr_mid+src_bpp,src_surface_format,NULL,0);
|
|
Read_B8G8R8A8(pixelM1,src_ptr_mid-src_bpp,src_surface_format,NULL,0);
|
|
Read_B8G8R8A8(pixel10,src_ptr_prev_line,src_surface_format,NULL,0);
|
|
Read_B8G8R8A8(pixel1M,src_ptr_next_line,src_surface_format,NULL,0);
|
|
|
|
// Convert to luminance
|
|
unsigned char bv00;
|
|
unsigned char bv01;
|
|
unsigned char bvM1;
|
|
unsigned char bv10;
|
|
unsigned char bv1M;
|
|
Write_B8G8R8A8(&bv00,WW3D_FORMAT_L8,pixel00);
|
|
Write_B8G8R8A8(&bv01,WW3D_FORMAT_L8,pixel01);
|
|
Write_B8G8R8A8(&bvM1,WW3D_FORMAT_L8,pixelM1);
|
|
Write_B8G8R8A8(&bv10,WW3D_FORMAT_L8,pixel10);
|
|
Write_B8G8R8A8(&bv1M,WW3D_FORMAT_L8,pixel1M);
|
|
int v00=bv00,v01=bv01,vM1=bvM1,v10=bv10,v1M=bv1M;
|
|
|
|
int iDu = (vM1-v01); // The delta-u bump value
|
|
int iDv = (v1M-v10); // The delta-v bump value
|
|
|
|
if( (v00 < vM1) && (v00 < v01) ) { // If we are at valley
|
|
iDu = vM1-v00; // Choose greater of 1st order diffs
|
|
if( iDu < v00-v01 )
|
|
iDu = v00-v01;
|
|
}
|
|
|
|
// The luminance bump value (land masses are less shiny)
|
|
unsigned short uL = ( v00>1 ) ? 63 : 127;
|
|
|
|
switch(dest_surface_format) {
|
|
case WW3D_FORMAT_U8V8:
|
|
*dest_ptr++ = (unsigned char)iDu;
|
|
*dest_ptr++ = (unsigned char)iDv;
|
|
break;
|
|
|
|
case WW3D_FORMAT_L6V5U5:
|
|
*(unsigned short*)dest_ptr = (unsigned short)( ( (iDu>>3) & 0x1f ) << 0 );
|
|
*(unsigned short*)dest_ptr |= (unsigned short)( ( (iDv>>3) & 0x1f ) << 5 );
|
|
*(unsigned short*)dest_ptr |= (unsigned short)( ( ( uL>>2) & 0x3f ) << 10 );
|
|
dest_ptr += 2;
|
|
break;
|
|
|
|
case WW3D_FORMAT_X8L8V8U8:
|
|
*dest_ptr++ = (unsigned char)iDu;
|
|
*dest_ptr++ = (unsigned char)iDv;
|
|
*dest_ptr++ = (unsigned char)uL;
|
|
*dest_ptr++ = (unsigned char)0L;
|
|
break;
|
|
|
|
default:
|
|
WWASSERT(0); // Unknown bumpmap format
|
|
break;
|
|
}
|
|
|
|
// Move one pixel to the left (src is 32-bpp)
|
|
src_ptr_mid+=src_bpp;
|
|
src_ptr_prev_line+=src_bpp;
|
|
src_ptr_next_line+=src_bpp;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (src_surface_format==dest_surface_format && (src_surface_format==WW3D_FORMAT_A8R8G8B8 || src_surface_format==WW3D_FORMAT_X8R8G8B8)) {
|
|
// One-to-one copy or scaling?
|
|
dest_surface_pitch/=4;
|
|
src_surface_pitch/=4;
|
|
if (dest_surface_width==src_surface_width && dest_surface_height==src_surface_height) {
|
|
// Generate the next mip level while copying the current surface?
|
|
if (generate_mip_level) {
|
|
if (dest_surface_width==1) {
|
|
*(unsigned*)dest_surface=*(unsigned*)src_surface;
|
|
}
|
|
else {
|
|
for (unsigned y=0;y<dest_surface_height/2;++y) {
|
|
unsigned* dest_ptr=(unsigned*)dest_surface;
|
|
dest_ptr+=2*y*dest_surface_pitch;
|
|
unsigned* src_ptr=(unsigned*)src_surface;
|
|
unsigned* mip_ptr=src_ptr;
|
|
src_ptr+=y*2*src_surface_pitch;
|
|
mip_ptr+=y*src_surface_pitch;
|
|
unsigned b8g8r8a8_00;
|
|
unsigned b8g8r8a8_01;
|
|
unsigned b8g8r8a8_10;
|
|
unsigned b8g8r8a8_11;
|
|
for (unsigned x=0;x<dest_surface_width/2;x++) {
|
|
b8g8r8a8_10=src_ptr[src_surface_pitch];
|
|
dest_ptr[dest_surface_pitch]=b8g8r8a8_10;
|
|
b8g8r8a8_00=*src_ptr++;
|
|
*dest_ptr++=b8g8r8a8_00;
|
|
|
|
b8g8r8a8_11=src_ptr[src_surface_pitch];
|
|
dest_ptr[dest_surface_pitch]=b8g8r8a8_11;
|
|
b8g8r8a8_01=*src_ptr++;
|
|
*dest_ptr++=b8g8r8a8_01;
|
|
|
|
*mip_ptr++=Combine_A8R8G8B8(b8g8r8a8_00,b8g8r8a8_01,b8g8r8a8_10,b8g8r8a8_11);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (unsigned y=0;y<dest_surface_height;++y) {
|
|
unsigned* dest_ptr=(unsigned*)dest_surface;
|
|
dest_ptr+=y*dest_surface_pitch;
|
|
const unsigned* src_ptr=(unsigned*)src_surface;
|
|
src_ptr+=y*src_surface_pitch;
|
|
for (unsigned x=0;x<dest_surface_width;++x) {
|
|
*dest_ptr++=*src_ptr++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
// For now do only point-sampling
|
|
for (unsigned y=0;y<dest_surface_height;++y) {
|
|
unsigned* dest_ptr=(unsigned*)dest_surface;
|
|
dest_ptr+=y*dest_surface_pitch;
|
|
unsigned src_y=y*src_surface_height/dest_surface_height;
|
|
const unsigned* src_ptr=(unsigned*)src_surface;
|
|
src_ptr+=src_y*src_surface_pitch;
|
|
for (unsigned x=0;x<dest_surface_width;++x) {
|
|
unsigned src_x=x*src_surface_width/dest_surface_width;
|
|
*dest_ptr++=src_ptr[src_x];
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
unsigned dest_bpp=Get_Bytes_Per_Pixel(dest_surface_format);
|
|
unsigned src_bpp=Get_Bytes_Per_Pixel(src_surface_format);
|
|
|
|
// One-to-one copy or scaling?
|
|
if (dest_surface_width==src_surface_width && dest_surface_height==src_surface_height) {
|
|
// Generate the next mip level while copying the current surface?
|
|
if (generate_mip_level) {
|
|
WWASSERT(src_surface_format!=WW3D_FORMAT_P8); // Paletted textures can't be mipmapped
|
|
if (dest_surface_width==1) {
|
|
unsigned char* dest_ptr=dest_surface;
|
|
unsigned char* src_ptr=src_surface;
|
|
unsigned b8g8r8a8;
|
|
Read_B8G8R8A8(b8g8r8a8,src_ptr,src_surface_format,src_palette,src_palette_bpp);
|
|
Write_B8G8R8A8(dest_ptr,dest_surface_format,b8g8r8a8);
|
|
}
|
|
else {
|
|
for (unsigned y=0;y<dest_surface_height/2;++y) {
|
|
unsigned char* dest_ptr=dest_surface+2*y*dest_surface_pitch;
|
|
unsigned char* src_ptr=src_surface+y*2*src_surface_pitch;
|
|
unsigned char* mip_ptr=src_surface+y*src_surface_pitch;
|
|
unsigned b8g8r8a8_00;
|
|
unsigned b8g8r8a8_01;
|
|
unsigned b8g8r8a8_10;
|
|
unsigned b8g8r8a8_11;
|
|
for (unsigned x=0;x<dest_surface_width/2;x++,dest_ptr+=dest_bpp*2,src_ptr+=src_bpp*2,mip_ptr+=src_bpp) {
|
|
Read_B8G8R8A8(b8g8r8a8_00,src_ptr,src_surface_format,src_palette,src_palette_bpp);
|
|
Write_B8G8R8A8(dest_ptr,dest_surface_format,b8g8r8a8_00);
|
|
|
|
Read_B8G8R8A8(b8g8r8a8_01,src_ptr+src_bpp,src_surface_format,src_palette,src_palette_bpp);
|
|
Write_B8G8R8A8(dest_ptr+dest_bpp,dest_surface_format,b8g8r8a8_01);
|
|
|
|
Read_B8G8R8A8(b8g8r8a8_10,src_ptr+src_surface_pitch,src_surface_format,src_palette,src_palette_bpp);
|
|
Write_B8G8R8A8(dest_ptr+dest_surface_pitch,dest_surface_format,b8g8r8a8_10);
|
|
|
|
Read_B8G8R8A8(b8g8r8a8_11,src_ptr+src_bpp+src_surface_pitch,src_surface_format,src_palette,src_palette_bpp);
|
|
Write_B8G8R8A8(dest_ptr+dest_bpp+dest_surface_pitch,dest_surface_format,b8g8r8a8_11);
|
|
|
|
unsigned b8g8r8a8=Combine_A8R8G8B8(b8g8r8a8_00,b8g8r8a8_01,b8g8r8a8_10,b8g8r8a8_11);
|
|
|
|
Write_B8G8R8A8(mip_ptr,src_surface_format,b8g8r8a8);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (unsigned y=0;y<dest_surface_height;++y) {
|
|
unsigned char* dest_ptr=dest_surface+y*dest_surface_pitch;
|
|
const unsigned char* src_ptr=src_surface+y*src_surface_pitch;
|
|
for (unsigned x=0;x<dest_surface_width;++x,dest_ptr+=dest_bpp,src_ptr+=src_bpp) {
|
|
Copy_Pixel(dest_ptr,dest_surface_format,src_ptr,src_surface_format,src_palette,src_palette_bpp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
// For now do only point-sampling
|
|
for (unsigned y=0;y<dest_surface_height;++y) {
|
|
unsigned char* dest_ptr=dest_surface+y*dest_surface_pitch;
|
|
unsigned src_y=y*src_surface_height/dest_surface_height;
|
|
const unsigned char* src_ptr=src_surface+src_y*src_surface_pitch;
|
|
for (unsigned x=0;x<dest_surface_width;++x,dest_ptr+=dest_bpp) {
|
|
unsigned src_x=x*src_surface_width/dest_surface_width;
|
|
src_x*=src_bpp;
|
|
Copy_Pixel(dest_ptr,dest_surface_format,src_ptr+src_x,src_surface_format,src_palette,src_palette_bpp);
|
|
}
|
|
}
|
|
}
|
|
}
|