2009-04-19 18:07:22 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* 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 .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System ;
2021-05-16 20:48:45 +00:00
using System.Collections ;
2009-04-19 18:07:22 +00:00
using System.Collections.Generic ;
using System.Drawing ;
using System.Drawing.Imaging ;
using CodeImp.DoomBuilder.Rendering ;
using System.IO ;
2019-12-29 02:54:12 +00:00
using System.Linq ;
2009-04-19 18:07:22 +00:00
#endregion
namespace CodeImp.DoomBuilder.Data
{
internal sealed unsafe class TextureImage : ImageData
{
#region = = = = = = = = = = = = = = = = = = Variables
private List < TexturePatch > patches ;
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
2016-06-15 14:48:21 +00:00
public TextureImage ( string group , string name , int width , int height , float scalex , float scaley , bool worldpanning )
2009-04-19 18:07:22 +00:00
{
// Initialize
this . width = width ;
this . height = height ;
2010-01-02 20:22:05 +00:00
this . scale . x = scalex ;
this . scale . y = scaley ;
2016-06-15 14:48:21 +00:00
this . worldpanning = worldpanning ; //mxd
2009-04-19 18:07:22 +00:00
this . patches = new List < TexturePatch > ( ) ;
SetName ( name ) ;
2015-03-22 20:43:05 +00:00
virtualname = "[" + group + "]/" + this . name ; //mxd
2009-04-19 18:07:22 +00:00
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Methods
// This adds a patch to the texture
public void AddPatch ( TexturePatch patch )
{
// Add it
patches . Add ( patch ) ;
2014-07-15 08:08:57 +00:00
2016-03-16 23:26:53 +00:00
if ( patch . LumpName = = Name ) hasPatchWithSameName = true ; //mxd
2009-04-19 18:07:22 +00:00
}
// This loads the image
2019-12-29 02:54:12 +00:00
protected override LocalLoadResult LocalLoadImage ( )
2009-04-19 18:07:22 +00:00
{
2015-12-28 15:01:53 +00:00
// Checks
2019-12-29 02:54:12 +00:00
if ( width = = 0 | | height = = 0 ) return new LocalLoadResult ( null ) ;
2015-12-28 15:01:53 +00:00
2009-04-19 18:07:22 +00:00
BitmapData bitmapdata = null ;
PixelColor * pixels = ( PixelColor * ) 0 ;
2019-12-29 02:54:12 +00:00
Bitmap bitmap = null ;
List < LogMessage > messages = new List < LogMessage > ( ) ;
2021-05-16 14:47:42 +00:00
int [ ] columnumpatches = new int [ width ] ;
2021-05-16 20:48:45 +00:00
BitArray columnmasked = new BitArray ( width , false ) ;
2021-05-16 14:47:42 +00:00
Dictionary < TexturePatch , Bitmap > patchbmps = new Dictionary < TexturePatch , Bitmap > ( ) ;
bool fixnegativeoffsets = General . Map . Config . Compatibility . FixNegativePatchOffsets ;
2021-05-16 20:48:45 +00:00
bool fixmaskedoffsets = General . Map . Config . Compatibility . FixMaskedPatchOffsets ;
2019-12-29 02:54:12 +00:00
// Create texture bitmap
try
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
if ( bitmap ! = null ) bitmap . Dispose ( ) ;
bitmap = new Bitmap ( width , height , PixelFormat . Format32bppArgb ) ;
bitmapdata = bitmap . LockBits ( new Rectangle ( 0 , 0 , width , height ) , ImageLockMode . WriteOnly , PixelFormat . Format32bppArgb ) ;
pixels = ( PixelColor * ) bitmapdata . Scan0 . ToPointer ( ) ;
2020-09-11 20:39:18 +00:00
General . ZeroPixels ( pixels , width * height ) ;
2019-12-29 02:54:12 +00:00
}
catch ( Exception e )
{
// Unable to make bitmap
messages . Add ( new LogMessage ( ErrorType . Error , "Unable to load texture image \"" + this . Name + "\". " + e . GetType ( ) . Name + ": " + e . Message ) ) ;
}
2009-04-19 18:07:22 +00:00
2019-12-29 02:54:12 +00:00
int missingpatches = 0 ; //mxd
2013-07-29 08:50:50 +00:00
2019-12-29 02:54:12 +00:00
if ( ! messages . Any ( x = > x . Type = = ErrorType . Error ) )
{
2021-05-16 14:47:42 +00:00
// Load all patch bitmaps, and see if the patch has a negative vertical offset
foreach ( TexturePatch p in patches )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
// Get the patch data stream
string patchlocation = string . Empty ; //mxd
Stream patchdata = General . Map . Data . GetPatchData ( p . LumpName , p . HasLongName , ref patchlocation ) ;
2021-05-16 14:47:42 +00:00
if ( patchdata ! = null )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
// Get a reader for the data
2020-01-14 16:25:35 +00:00
Bitmap patchbmp = ImageDataFormat . TryLoadImage ( patchdata , ImageDataFormat . DOOMPICTURE , General . Map . Data . Palette ) ;
2021-05-16 14:47:42 +00:00
if ( patchbmp = = null )
2019-12-29 02:54:12 +00:00
{
//mxd. Probably that's a flat?..
2021-05-16 14:47:42 +00:00
if ( General . Map . Config . MixTexturesFlats )
2009-04-19 18:07:22 +00:00
{
2020-01-14 16:25:35 +00:00
patchbmp = ImageDataFormat . TryLoadImage ( patchdata , ImageDataFormat . DOOMFLAT , General . Map . Data . Palette ) ;
2009-04-19 18:07:22 +00:00
}
2021-05-16 14:47:42 +00:00
if ( patchbmp = = null )
2014-12-03 23:15:26 +00:00
{
2021-05-16 14:47:42 +00:00
// Data is in an unknown format!
messages . Add ( new LogMessage ( ErrorType . Error , "Patch lump \"" + Path . Combine ( patchlocation , p . LumpName ) + "\" data format could not be read, while loading texture \"" + this . Name + "\". Does this lump contain valid picture data at all?" ) ) ;
2019-12-29 02:54:12 +00:00
missingpatches + + ; //mxd
2009-04-19 18:07:22 +00:00
}
}
2019-12-29 02:54:12 +00:00
2021-05-16 14:47:42 +00:00
// Store the bitmap
patchbmps [ p ] = patchbmp ;
2009-04-19 18:07:22 +00:00
2021-05-16 14:47:42 +00:00
// Done
patchdata . Dispose ( ) ;
2019-12-29 02:54:12 +00:00
}
else
{
2021-05-16 14:47:42 +00:00
// Missing a patch lump!
messages . Add ( new LogMessage ( ErrorType . Error , "Missing patch lump \"" + p . LumpName + "\" while loading texture \"" + this . Name + "\". Did you forget to include required resources?" ) ) ;
2019-12-29 02:54:12 +00:00
missingpatches + + ; //mxd
}
2009-04-19 18:07:22 +00:00
}
2021-05-16 14:47:42 +00:00
// There's a bug in vanilla Doom where negative patch offsets are ignored (the patch y offset is set to 0). If
// the configuration is for an engine that doesn't fix the bug we have to emulate that behavior
// See https://doomwiki.org/wiki/Vertical_offsets_are_ignored_in_texture_patches
2021-05-16 20:48:45 +00:00
if ( ! fixnegativeoffsets | | ! fixmaskedoffsets )
2021-05-16 14:47:42 +00:00
{
// Check which columns have more than one patch
foreach ( TexturePatch p in patches )
{
2021-05-29 17:40:39 +00:00
if ( ! patchbmps . ContainsKey ( p ) | | patchbmps [ p ] = = null ) continue ;
2021-05-16 14:47:42 +00:00
2021-05-16 20:48:45 +00:00
bool ismaked = BitmapIsMasked ( patchbmps [ p ] ) ;
for ( int x = 0 ; x < patchbmps [ p ] . Width ; x + + )
2021-05-16 14:47:42 +00:00
{
int ox = p . X + x ;
if ( ox > = 0 & & ox < columnumpatches . Length )
2021-05-16 20:48:45 +00:00
{
if ( ! fixnegativeoffsets )
columnumpatches [ ox ] + + ;
if ( ! fixmaskedoffsets & & ismaked )
columnmasked [ ox ] = true ;
}
2021-05-16 14:47:42 +00:00
}
}
}
// Go for all patches
foreach ( TexturePatch p in patches )
{
if ( patchbmps . ContainsKey ( p ) & & patchbmps [ p ] ! = null )
{
// Draw the patch
2021-05-16 20:48:45 +00:00
DrawToPixelData ( patchbmps [ p ] , pixels , width , height , p . X , p . Y , fixnegativeoffsets , fixmaskedoffsets , columnumpatches , columnmasked ) ;
2021-05-16 14:47:42 +00:00
}
}
// Don't need the bitmaps anymore
foreach ( Bitmap bmp in patchbmps . Values )
bmp ? . Dispose ( ) ;
2019-12-29 02:54:12 +00:00
// Done
bitmap . UnlockBits ( bitmapdata ) ;
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
// Dispose bitmap if load failed
if ( ( bitmap ! = null ) & & ( messages . Any ( x = > x . Type = = ErrorType . Error ) | | missingpatches > = patches . Count ) ) //mxd. We can still display texture if at least one of the patches was loaded
{
bitmap . Dispose ( ) ;
bitmap = null ;
}
return new LocalLoadResult ( bitmap , messages ) ;
2009-04-19 18:07:22 +00:00
}
2020-01-14 16:25:35 +00:00
// This draws the picture to the given pixel color data
2021-05-16 20:48:45 +00:00
static unsafe void DrawToPixelData ( Bitmap bmp , PixelColor * target , int targetwidth , int targetheight , int x , int y , bool fixnegativeoffsets , bool fixmaskedoffsets , int [ ] columnumpatches , BitArray columnmasked )
2020-01-14 16:25:35 +00:00
{
// Get bitmap
int width = bmp . Size . Width ;
int height = bmp . Size . Height ;
2021-05-16 14:47:42 +00:00
int patchy = y ;
2020-01-14 16:25:35 +00:00
// Lock bitmap pixels
BitmapData bmpdata = bmp . LockBits ( new Rectangle ( 0 , 0 , width , height ) , ImageLockMode . ReadOnly , PixelFormat . Format32bppArgb ) ;
PixelColor * pixels = ( PixelColor * ) bmpdata . Scan0 . ToPointer ( ) ;
// Go for all pixels in the original image
for ( int ox = 0 ; ox < width ; ox + + )
{
2021-05-16 14:47:42 +00:00
int tx = x + ox ;
int drawheight = height ;
// If we have to emulate the negative vertical offset bug we also have to recalculate the height of the
// patch that is actually drawn, since it'll only draw as many pixels as it'd draw as if the negative
// vertical offset was taken into account
2021-05-16 20:48:45 +00:00
if ( ( patchy < 0 & & ! fixnegativeoffsets & & tx < width & & tx > = 0 & & tx < columnumpatches . Length & & columnumpatches [ tx ] > 1 ) & & ! ( ! fixmaskedoffsets & & tx > = 0 & & tx < columnmasked . Length & & columnmasked [ tx ] = = true ) )
2021-05-16 14:47:42 +00:00
drawheight = height + patchy ;
for ( int oy = 0 ; oy < drawheight ; oy + + )
2020-01-14 16:25:35 +00:00
{
// Copy this pixel?
if ( pixels [ oy * width + ox ] . a > 0.5f )
{
2021-05-16 20:48:45 +00:00
int realy = y ;
if ( ! fixmaskedoffsets & & tx > = 0 & & tx < columnmasked . Length & & columnmasked [ tx ] = = true )
{
if ( tx > = 0 & & tx < columnumpatches . Length & & columnumpatches [ tx ] = = 1 )
realy = 0 ;
}
else if ( y < 0 & & ! fixnegativeoffsets )
realy = 0 ;
2020-01-14 16:25:35 +00:00
// Calculate target pixel and copy when within bounds
2021-05-16 20:48:45 +00:00
int ty = realy + oy ;
2020-01-14 16:25:35 +00:00
if ( ( tx > = 0 ) & & ( tx < targetwidth ) & & ( ty > = 0 ) & & ( ty < targetheight ) )
target [ ty * targetwidth + tx ] = pixels [ oy * width + ox ] ;
}
}
}
// Done
bmp . UnlockBits ( bmpdata ) ;
}
2021-05-16 20:48:45 +00:00
/// <summary>
/// Checks if the given bitmap has masked pixels
/// </summary>
/// <param name="bmp">Bitmap to check</param>
/// <returns>true if masked, false if not masked</returns>
internal static unsafe bool BitmapIsMasked ( Bitmap bmp )
{
// Get bitmap
int width = bmp . Size . Width ;
int height = bmp . Size . Height ;
// Lock bitmap pixels
BitmapData bmpdata = bmp . LockBits ( new Rectangle ( 0 , 0 , width , height ) , ImageLockMode . ReadOnly , PixelFormat . Format32bppArgb ) ;
PixelColor * pixels = ( PixelColor * ) bmpdata . Scan0 . ToPointer ( ) ;
for ( int ox = 0 ; ox < width ; ox + + )
{
for ( int oy = 0 ; oy < height ; oy + + )
{
if ( pixels [ oy * width + ox ] . a < = 0.5f )
{
bmp . UnlockBits ( bmpdata ) ;
return true ;
}
}
}
bmp . UnlockBits ( bmpdata ) ;
return false ;
}
2020-01-14 16:25:35 +00:00
#endregion
}
2009-04-19 18:07:22 +00:00
}