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 ;
using System.Collections.Generic ;
using System.Drawing ;
using System.Drawing.Imaging ;
2014-12-30 20:39:45 +00:00
using System.IO ;
2019-12-29 02:54:12 +00:00
using System.Linq ;
2014-12-03 09:06:05 +00:00
using CodeImp.DoomBuilder.Controls ;
2009-04-19 18:07:22 +00:00
using CodeImp.DoomBuilder.IO ;
2014-12-30 20:39:45 +00:00
using CodeImp.DoomBuilder.Rendering ;
2009-04-19 18:07:22 +00:00
#endregion
namespace CodeImp.DoomBuilder.Data
{
2022-03-05 15:57:23 +00:00
public enum TextureNamespace
{
TEXTURE ,
WALLTEXTURE ,
FLAT ,
SPRITE ,
PATCH
}
2016-03-04 08:10:56 +00:00
internal sealed unsafe class TEXTURESImage : ImageData
2009-04-19 18:07:22 +00:00
{
#region = = = = = = = = = = = = = = = = = = Variables
2014-11-25 11:52:01 +00:00
private readonly List < TexturePatch > patches ; //mxd
2016-06-08 00:06:20 +00:00
private readonly bool optional ; //mxd
private readonly bool nulltexture ; //mxd
2022-03-05 15:57:23 +00:00
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
2023-07-29 11:32:42 +00:00
public TEXTURESImage ( string name , string virtualpath , int width , int height , int offsetx , int offsety , float scalex , float scaley ,
2022-03-05 15:57:23 +00:00
bool worldpanning , TextureNamespace texturenamespace , bool optional , bool nulltexture )
2009-04-19 18:07:22 +00:00
{
// Initialize
this . width = width ;
this . height = height ;
2023-07-29 11:32:42 +00:00
this . offsetx = offsetx ;
this . offsety = offsety ;
2010-01-02 20:22:05 +00:00
this . scale . x = scalex ;
this . scale . y = scaley ;
2010-05-08 13:14:48 +00:00
this . worldpanning = worldpanning ;
2016-06-08 00:06:20 +00:00
this . optional = optional ; //mxd
this . nulltexture = nulltexture ; //mxd
2014-07-15 08:08:57 +00:00
this . patches = new List < TexturePatch > ( 1 ) ;
2022-03-05 15:57:23 +00:00
this . texturenamespace = texturenamespace ;
2014-11-25 11:52:01 +00:00
//mxd
2009-04-19 18:07:22 +00:00
SetName ( name ) ;
2016-03-04 08:10:56 +00:00
this . virtualname = ( ! string . IsNullOrEmpty ( virtualpath ) ? virtualpath : "[TEXTURES]" ) + Path . AltDirectorySeparatorChar + this . name ;
2009-04-19 18:07:22 +00:00
// We have no destructor
GC . SuppressFinalize ( this ) ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Methods
2014-12-03 09:06:05 +00:00
//mxd
protected override void SetName ( string name )
{
if ( ! General . Map . Config . UseLongTextureNames )
{
if ( name . Length > DataManager . CLASIC_IMAGE_NAME_LENGTH )
name = name . Substring ( 0 , DataManager . CLASIC_IMAGE_NAME_LENGTH ) ;
name = name . ToUpperInvariant ( ) ;
}
base . SetName ( name ) ;
this . shortname = this . displayname . ToUpperInvariant ( ) ;
if ( this . shortname . Length > DataManager . CLASIC_IMAGE_NAME_LENGTH )
{
this . shortname = this . shortname . Substring ( 0 , DataManager . CLASIC_IMAGE_NAME_LENGTH ) ;
}
2019-05-30 22:20:12 +00:00
ComputeNamesWidth ( ) ; // biwa
2014-12-03 09:06:05 +00:00
}
2009-04-19 18:07:22 +00:00
// This adds a patch to the texture
public void AddPatch ( TexturePatch patch )
{
// Add it
2014-07-15 08:08:57 +00:00
patches . Add ( patch ) ;
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
{
2014-10-07 09:56:36 +00:00
// Checks
2019-12-29 02:54:12 +00:00
if ( width = = 0 | | height = = 0 ) return new LocalLoadResult ( null ) ;
2014-10-07 09:56:36 +00:00
2009-04-19 18:07:22 +00:00
Graphics g = null ;
2019-12-29 02:54:12 +00:00
Bitmap bitmap = null ;
List < LogMessage > messages = new List < LogMessage > ( ) ;
// 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 bitmapdata = bitmap . LockBits ( new Rectangle ( 0 , 0 , width , height ) , ImageLockMode . WriteOnly , PixelFormat . Format32bppArgb ) ;
PixelColor * 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
bitmap . UnlockBits ( bitmapdata ) ;
g = Graphics . FromImage ( bitmap ) ;
}
catch ( Exception e )
{
// Unable to make bitmap
2019-12-29 12:25:53 +00:00
messages . Add ( new LogMessage ( ErrorType . Error , "Unable to load texture image \"" + this . Name + "\". " + e . GetType ( ) . Name + ": " + e . Message ) ) ;
2019-12-29 02:54:12 +00:00
}
2009-04-19 18:07:22 +00:00
2019-12-29 02:54:12 +00:00
int missingpatches = 0 ; //mxd
if ( patches . Count = = 0 ) //mxd
{
//mxd. Empty image will suffice here, I suppose...
if ( nulltexture )
2016-02-15 14:06:46 +00:00
{
2019-12-29 02:54:12 +00:00
return new LocalLoadResult ( bitmap , messages ) ;
2016-02-15 14:06:46 +00:00
}
2019-12-29 02:54:12 +00:00
// No patches!
2019-12-29 12:25:53 +00:00
messages . Add ( new LogMessage ( ErrorType . Error , "No patches are defined for texture \"" + this . Name + "\"" ) ) ;
2019-12-29 02:54:12 +00:00
}
else if ( ! messages . Any ( x = > x . Type = = ErrorType . Error ) )
{
// Go for all patches
foreach ( TexturePatch p in patches )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
//mxd. Some patches (like "TNT1A0") should be skipped
if ( p . Skip ) continue ;
2016-02-15 14:06:46 +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 ) ;
if ( patchdata ! = null )
{
// Copy patch data to memory
byte [ ] membytes = new byte [ ( int ) patchdata . Length ] ;
lock ( patchdata ) //mxd
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
patchdata . Seek ( 0 , SeekOrigin . Begin ) ;
patchdata . Read ( membytes , 0 , ( int ) patchdata . Length ) ;
}
MemoryStream mem = new MemoryStream ( membytes ) ;
mem . Seek ( 0 , SeekOrigin . Begin ) ;
2016-08-29 10:06:16 +00:00
2020-01-14 16:25:35 +00:00
Bitmap patchbmp = ImageDataFormat . TryLoadImage ( mem , ImageDataFormat . DOOMPICTURE , General . Map . Data . Palette ) ;
if ( patchbmp = = null )
2019-12-29 02:54:12 +00:00
{
//mxd. Probably that's a flat?..
if ( General . Map . Config . MixTexturesFlats )
2016-08-29 10:06:16 +00:00
{
2020-01-14 16:25:35 +00:00
patchbmp = ImageDataFormat . TryLoadImage ( mem , ImageDataFormat . DOOMFLAT , General . Map . Data . Palette ) ;
2016-08-29 10:06:16 +00:00
}
2009-04-19 18:07:22 +00:00
2020-01-14 16:25:35 +00:00
if ( patchbmp = = null )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
// Data is in an unknown format!
if ( ! nulltexture ) messages . Add ( new LogMessage ( optional ? ErrorType . Warning : ErrorType . Error , "Patch lump \"" + Path . Combine ( patchlocation , p . LumpName ) + "\" data format could not be read, while loading texture \"" + this . Name + "\"" ) ) ;
missingpatches + + ; //mxd
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
}
2013-12-02 15:02:01 +00:00
2020-01-14 16:25:35 +00:00
if ( patchbmp ! = null )
2019-12-29 02:54:12 +00:00
{
2020-01-14 16:25:35 +00:00
//mxd. Apply transformations from TexturePatch
patchbmp = TransformPatch ( bitmap , p , patchbmp ) ;
2019-12-29 02:54:12 +00:00
2020-01-14 16:25:35 +00:00
// Draw the patch on the texture image
Rectangle tgtrect = new Rectangle ( p . X , p . Y , patchbmp . Size . Width , patchbmp . Size . Height ) ;
g . DrawImageUnscaledAndClipped ( patchbmp , tgtrect ) ;
patchbmp . Dispose ( ) ;
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
// Done
mem . Dispose ( ) ;
}
else
{
//mxd. ZDoom can use any known graphic as patch
if ( General . Map . Config . MixTexturesFlats )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
ImageData img = General . Map . Data . GetTextureImage ( p . LumpName ) ;
if ( ! ( img is UnknownImage ) & & img ! = this )
2015-12-30 11:53:03 +00:00
{
2021-05-13 19:36:29 +00:00
//mxd. Apply transformations from TexturePatch. We don't want to modify the original bitmap here, so make a copy
// biwa. Make sure to get the image without color correction, as the final texture would be too bright if the patch
// is also a texture
Bitmap bmp = new Bitmap ( img . LocalGetBitmap ( false ) ) ;
Bitmap patchbmp = TransformPatch ( bitmap , p , bmp ) ;
2019-12-29 02:54:12 +00:00
// Draw the patch on the texture image
Rectangle tgtrect = new Rectangle ( p . X , p . Y , patchbmp . Size . Width , patchbmp . Size . Height ) ;
g . DrawImageUnscaledAndClipped ( patchbmp , tgtrect ) ;
patchbmp . Dispose ( ) ;
continue ;
2015-12-30 11:53:03 +00:00
}
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
// Missing a patch lump!
if ( ! nulltexture ) messages . Add ( new LogMessage ( optional ? ErrorType . Warning : ErrorType . Error , "Missing patch lump \"" + p . LumpName + "\" while loading texture \"" + this . Name + "\"" ) ) ;
missingpatches + + ; //mxd
2009-04-19 18:07:22 +00:00
}
}
2019-12-29 02:54:12 +00:00
}
2009-04-19 18:07:22 +00:00
2019-12-29 02:54:12 +00:00
// Dispose bitmap if load failed
if ( ! nulltexture & & ( 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 ;
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
return new LocalLoadResult ( bitmap , messages ) ;
}
//mxd
2019-12-29 22:24:08 +00:00
private Bitmap TransformPatch ( Bitmap bitmap , TexturePatch p , Bitmap patchbmp )
2015-12-30 11:53:03 +00:00
{
//mxd. Flip
2016-03-16 23:26:53 +00:00
if ( p . FlipX | | p . FlipY )
2015-12-30 11:53:03 +00:00
{
RotateFlipType flip ;
2016-03-16 23:26:53 +00:00
if ( p . FlipX & & ! p . FlipY ) flip = RotateFlipType . RotateNoneFlipX ;
else if ( ! p . FlipX & & p . FlipY ) flip = RotateFlipType . RotateNoneFlipY ;
2016-02-22 08:04:06 +00:00
else flip = RotateFlipType . RotateNoneFlipXY ;
2015-12-30 11:53:03 +00:00
patchbmp . RotateFlip ( flip ) ;
}
//mxd. Then rotate. I do it this way because RotateFlip function rotates THEN flips, and GZDoom does it the other way around.
2016-03-16 23:26:53 +00:00
if ( p . Rotate ! = 0 )
2015-12-30 11:53:03 +00:00
{
RotateFlipType rotate ;
2016-03-16 23:26:53 +00:00
switch ( p . Rotate )
2015-12-30 11:53:03 +00:00
{
2016-02-22 08:04:06 +00:00
case 90 : rotate = RotateFlipType . Rotate90FlipNone ; break ;
case 180 : rotate = RotateFlipType . Rotate180FlipNone ; break ;
default : rotate = RotateFlipType . Rotate270FlipNone ; break ;
2015-12-30 11:53:03 +00:00
}
patchbmp . RotateFlip ( rotate ) ;
}
// Adjust patch alpha, apply tint or blend
2016-03-16 23:26:53 +00:00
if ( p . BlendStyle ! = TexturePathBlendStyle . NONE | | p . RenderStyle ! = TexturePathRenderStyle . COPY )
2015-12-30 11:53:03 +00:00
{
BitmapData bmpdata = null ;
try
{
bmpdata = patchbmp . LockBits ( new Rectangle ( 0 , 0 , patchbmp . Size . Width , patchbmp . Size . Height ) , ImageLockMode . ReadWrite , PixelFormat . Format32bppArgb ) ;
}
catch ( Exception e )
{
2016-03-16 23:26:53 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Cannot lock image \"" + p . LumpName + "\" for alpha adjustment. " + e . GetType ( ) . Name + ": " + e . Message ) ;
2015-12-30 11:53:03 +00:00
}
if ( bmpdata ! = null )
{
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
int numpixels = bmpdata . Width * bmpdata . Height ;
2016-03-16 23:26:53 +00:00
int patchalpha = ( int ) Math . Round ( General . Clamp ( p . Alpha , 0f , 1f ) * 255 ) ; //convert alpha to [0-255] range
2015-12-30 11:53:03 +00:00
//mxd. Blend/Tint support
2016-03-16 23:26:53 +00:00
if ( p . BlendStyle = = TexturePathBlendStyle . BLEND )
2015-12-30 11:53:03 +00:00
{
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
{
2016-03-16 23:26:53 +00:00
cp - > r = ( byte ) ( ( cp - > r * p . BlendColor . r ) * PixelColor . BYTE_TO_FLOAT ) ;
cp - > g = ( byte ) ( ( cp - > g * p . BlendColor . g ) * PixelColor . BYTE_TO_FLOAT ) ;
cp - > b = ( byte ) ( ( cp - > b * p . BlendColor . b ) * PixelColor . BYTE_TO_FLOAT ) ;
2015-12-30 11:53:03 +00:00
}
}
2016-03-16 23:26:53 +00:00
else if ( p . BlendStyle = = TexturePathBlendStyle . TINT )
2015-12-30 11:53:03 +00:00
{
2016-03-16 23:26:53 +00:00
float tintammount = p . BlendColor . a * PixelColor . BYTE_TO_FLOAT ; // -0.1f;
2015-12-30 11:53:03 +00:00
if ( tintammount > 0 )
{
2016-03-16 23:26:53 +00:00
float br = p . BlendColor . r * PixelColor . BYTE_TO_FLOAT * tintammount ;
float bg = p . BlendColor . g * PixelColor . BYTE_TO_FLOAT * tintammount ;
float bb = p . BlendColor . b * PixelColor . BYTE_TO_FLOAT * tintammount ;
float invtintammount = 1.0f - tintammount ;
2015-12-30 11:53:03 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
{
2016-03-16 23:26:53 +00:00
cp - > r = ( byte ) ( ( ( cp - > r * PixelColor . BYTE_TO_FLOAT ) * invtintammount + br ) * 255.0f ) ;
cp - > g = ( byte ) ( ( ( cp - > g * PixelColor . BYTE_TO_FLOAT ) * invtintammount + bg ) * 255.0f ) ;
cp - > b = ( byte ) ( ( ( cp - > b * PixelColor . BYTE_TO_FLOAT ) * invtintammount + bb ) * 255.0f ) ;
2015-12-30 11:53:03 +00:00
}
}
}
//mxd. Apply RenderStyle
2016-03-16 23:26:53 +00:00
if ( p . RenderStyle = = TexturePathRenderStyle . BLEND )
2015-12-30 11:53:03 +00:00
{
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
cp - > a = ( byte ) ( ( cp - > a * patchalpha ) * PixelColor . BYTE_TO_FLOAT ) ;
}
//mxd. We need a copy of underlying part of texture for these styles
2016-03-16 23:26:53 +00:00
else if ( p . RenderStyle ! = TexturePathRenderStyle . COPY )
2015-12-30 11:53:03 +00:00
{
// Copy portion of texture
2016-03-16 23:26:53 +00:00
int lockWidth = ( p . X + patchbmp . Size . Width > bitmap . Width ) ? bitmap . Width - p . X : patchbmp . Size . Width ;
int lockHeight = ( p . Y + patchbmp . Size . Height > bitmap . Height ) ? bitmap . Height - p . Y : patchbmp . Size . Height ;
2015-12-30 11:53:03 +00:00
Bitmap source = new Bitmap ( patchbmp . Size . Width , patchbmp . Size . Height ) ;
using ( Graphics sg = Graphics . FromImage ( source ) )
2016-03-16 23:26:53 +00:00
sg . DrawImageUnscaled ( bitmap , new Rectangle ( - p . X , - p . Y , lockWidth , lockHeight ) ) ;
2015-12-30 11:53:03 +00:00
// Lock texture
BitmapData texturebmpdata = null ;
try
{
texturebmpdata = source . LockBits ( new Rectangle ( 0 , 0 , source . Width , source . Height ) , ImageLockMode . ReadWrite , PixelFormat . Format32bppArgb ) ;
}
catch ( Exception e )
{
2016-02-15 14:06:46 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Cannot lock texture \"" + this . Name + "\" to apply render style. " + e . GetType ( ) . Name + ": " + e . Message ) ;
2015-12-30 11:53:03 +00:00
}
if ( texturebmpdata ! = null )
{
PixelColor * texturepixels = ( PixelColor * ) ( texturebmpdata . Scan0 . ToPointer ( ) ) ;
PixelColor * tcp = texturepixels + numpixels - 1 ;
2016-03-16 23:26:53 +00:00
switch ( p . RenderStyle )
2015-12-30 11:53:03 +00:00
{
2016-03-16 23:26:53 +00:00
case TexturePathRenderStyle . ADD :
2015-12-30 11:53:03 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
{
cp - > r = ( byte ) Math . Min ( 255 , cp - > r + tcp - > r ) ;
cp - > g = ( byte ) Math . Min ( 255 , cp - > g + tcp - > g ) ;
cp - > b = ( byte ) Math . Min ( 255 , cp - > b + tcp - > b ) ;
cp - > a = ( byte ) ( ( cp - > a * patchalpha ) * PixelColor . BYTE_TO_FLOAT ) ;
tcp - - ;
}
break ;
2016-03-16 23:26:53 +00:00
case TexturePathRenderStyle . SUBTRACT :
2015-12-30 11:53:03 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
{
cp - > r = ( byte ) Math . Max ( 0 , tcp - > r - cp - > r ) ;
cp - > g = ( byte ) Math . Max ( 0 , tcp - > g - cp - > g ) ;
cp - > b = ( byte ) Math . Max ( 0 , tcp - > b - cp - > b ) ;
cp - > a = ( byte ) ( ( cp - > a * patchalpha ) * PixelColor . BYTE_TO_FLOAT ) ;
tcp - - ;
}
break ;
2016-03-16 23:26:53 +00:00
case TexturePathRenderStyle . REVERSE_SUBTRACT :
2015-12-30 11:53:03 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
{
cp - > r = ( byte ) Math . Max ( 0 , cp - > r - tcp - > r ) ;
cp - > g = ( byte ) Math . Max ( 0 , cp - > g - tcp - > g ) ;
cp - > b = ( byte ) Math . Max ( 0 , cp - > b - tcp - > b ) ;
cp - > a = ( byte ) ( ( cp - > a * patchalpha ) * PixelColor . BYTE_TO_FLOAT ) ;
tcp - - ;
}
break ;
2016-03-16 23:26:53 +00:00
case TexturePathRenderStyle . MODULATE :
2015-12-30 11:53:03 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
{
cp - > r = ( byte ) ( ( cp - > r * tcp - > r ) * PixelColor . BYTE_TO_FLOAT ) ;
cp - > g = ( byte ) ( ( cp - > g * tcp - > g ) * PixelColor . BYTE_TO_FLOAT ) ;
cp - > b = ( byte ) ( ( cp - > b * tcp - > b ) * PixelColor . BYTE_TO_FLOAT ) ;
tcp - - ;
}
break ;
}
source . UnlockBits ( texturebmpdata ) ;
}
}
patchbmp . UnlockBits ( bmpdata ) ;
}
}
return patchbmp ;
}
2009-04-19 18:07:22 +00:00
#endregion
}
}