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 ;
2020-01-12 19:17:18 +00:00
using System.Collections ;
2019-12-29 02:54:12 +00:00
using System.Collections.Generic ;
2009-04-19 18:07:22 +00:00
using System.Drawing ;
2015-05-27 12:38:03 +00:00
using System.Drawing.Drawing2D ;
2009-04-19 18:07:22 +00:00
using System.Drawing.Imaging ;
using System.IO ;
2019-12-29 02:54:12 +00:00
using System.Linq ;
2009-04-19 18:07:22 +00:00
using System.Runtime.InteropServices ;
2015-05-27 12:38:03 +00:00
using CodeImp.DoomBuilder.Geometry ;
using CodeImp.DoomBuilder.IO ;
using CodeImp.DoomBuilder.Rendering ;
2009-04-19 18:07:22 +00:00
using CodeImp.DoomBuilder.Windows ;
#endregion
namespace CodeImp.DoomBuilder.Data
{
2016-02-01 22:04:00 +00:00
public abstract unsafe class ImageData : IDisposable
2009-04-19 18:07:22 +00:00
{
#region = = = = = = = = = = = = = = = = = = Constants
2022-01-04 19:17:12 +00:00
public const int TEXTURE_INDEXED = 1 ;
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
// Properties
2014-11-25 11:52:01 +00:00
protected string name ;
protected long longname ;
2009-04-19 18:07:22 +00:00
protected int width ;
protected int height ;
2023-07-29 11:32:42 +00:00
protected int offsetx ;
protected int offsety ;
2010-01-02 20:22:05 +00:00
protected Vector2D scale ;
2010-05-08 13:14:48 +00:00
protected bool worldpanning ;
2014-11-25 11:52:01 +00:00
private bool usecolorcorrection ;
2015-11-17 17:50:56 +00:00
protected string filepathname ; //mxd. Absolute path to the image;
2014-12-03 09:06:05 +00:00
protected string shortname ; //mxd. Name in uppercase and clamped to DataManager.CLASIC_IMAGE_NAME_LENGTH
protected string virtualname ; //mxd. Path of this name is used in TextureBrowserForm
protected string displayname ; //mxd. Name to display in TextureBrowserForm
2016-01-26 22:29:12 +00:00
protected bool istranslucent ; //mxd. If true, has pixels with alpha > 0 && < 255
protected bool ismasked ; //mxd. If true, has pixels with zero alpha
2014-11-25 11:52:01 +00:00
protected bool hasLongName ; //mxd. Texture name is longer than DataManager.CLASIC_IMAGE_NAME_LENGTH
2014-07-15 08:08:57 +00:00
protected bool hasPatchWithSameName ; //mxd
2019-05-30 22:20:12 +00:00
protected int namewidth ; // biwa
protected int shortnamewidth ; // biwa
2022-01-04 19:17:12 +00:00
protected bool wantIndexed ; // volte
2022-03-05 15:57:23 +00:00
protected TextureNamespace texturenamespace ;
2015-10-02 14:47:34 +00:00
//mxd. Hashing
private static int hashcounter ;
private readonly int hashcode ;
2019-12-29 02:54:12 +00:00
// Loading
private ImageLoadState previewstate ;
private ImageLoadState imagestate ;
private bool loadfailed ;
2020-01-12 19:17:18 +00:00
// Alpha test
private BitArray alphatest ;
private int alphatestWidth = 64 ;
private int alphatestHeight = 64 ;
2019-12-29 02:54:12 +00:00
// GDI bitmap
2020-01-12 19:17:18 +00:00
private Bitmap loadedbitmap ;
2022-01-19 16:58:12 +00:00
private Bitmap uncorrectedbitmap ;
2019-12-29 19:48:11 +00:00
private Bitmap previewbitmap ;
2020-01-12 18:53:50 +00:00
private Bitmap spritepreviewbitmap ;
2019-12-29 19:48:11 +00:00
// Direct3D texture
private int mipmaplevels ; // 0 = all mipmaps
2012-06-01 19:53:14 +00:00
protected bool dynamictexture ;
2009-04-19 18:07:22 +00:00
private Texture texture ;
2022-01-04 19:17:12 +00:00
private Texture indexedTexture ;
2009-04-19 18:07:22 +00:00
// Disposing
2014-02-21 14:42:12 +00:00
protected bool isdisposed ;
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
public string Name { get { return name ; } }
public long LongName { get { return longname ; } }
2014-12-03 09:06:05 +00:00
public string ShortName { get { return shortname ; } } //mxd
2015-11-17 17:50:56 +00:00
public string FilePathName { get { return filepathname ; } } //mxd
2014-11-25 11:52:01 +00:00
public string VirtualName { get { return virtualname ; } } //mxd
public string DisplayName { get { return displayname ; } } //mxd
2022-03-05 15:57:23 +00:00
public TextureNamespace TextureNamespace { get { return texturenamespace ; } }
2015-09-27 21:09:14 +00:00
public bool IsTranslucent { get { return istranslucent ; } } //mxd
2016-01-26 22:29:12 +00:00
public bool IsMasked { get { return ismasked ; } } //mxd
2014-07-15 08:08:57 +00:00
public bool HasPatchWithSameName { get { return hasPatchWithSameName ; } } //mxd
2014-12-03 09:06:05 +00:00
internal bool HasLongName { get { return hasLongName ; } } //mxd
2009-04-19 18:07:22 +00:00
public bool UseColorCorrection { get { return usecolorcorrection ; } set { usecolorcorrection = value ; } }
2022-01-04 19:17:12 +00:00
public Texture Texture { get { return GetTexture ( false ) ; } }
public Texture IndexedTexture { get { return GetTexture ( true ) ; } }
2020-01-12 22:10:57 +00:00
public bool IsPreviewLoaded
{
get
{
if ( previewstate = = ImageLoadState . None )
General . Map . Data . QueueLoadPreview ( this ) ;
return ( previewstate = = ImageLoadState . Ready ) ;
}
}
public bool IsImageLoaded
{
get
{
if ( imagestate = = ImageLoadState . None )
General . Map . Data . QueueLoadImage ( this ) ;
return ( imagestate = = ImageLoadState . Ready ) ;
}
}
2009-04-19 18:07:22 +00:00
public bool LoadFailed { get { return loadfailed ; } }
public bool IsDisposed { get { return isdisposed ; } }
2019-12-29 21:59:57 +00:00
public bool AllowUnload { get ; set ; }
2009-04-19 18:07:22 +00:00
public ImageLoadState ImageState { get { return imagestate ; } internal set { imagestate = value ; } }
public ImageLoadState PreviewState { get { return previewstate ; } internal set { previewstate = value ; } }
2019-12-29 21:59:57 +00:00
public bool UsedInMap { get ; internal set ; }
2009-04-19 18:07:22 +00:00
public int MipMapLevels { get { return mipmaplevels ; } set { mipmaplevels = value ; } }
2016-03-04 08:10:56 +00:00
public virtual int Width { get { return width ; } }
public virtual int Height { get { return height ; } }
2023-07-29 11:32:42 +00:00
public int OffsetX { get { return offsetx ; } }
public int OffsetY { get { return offsety ; } }
2015-10-16 08:32:42 +00:00
//mxd. Scaled texture size is integer in ZDoom.
2016-03-04 08:10:56 +00:00
public virtual float ScaledWidth { get { return ( float ) Math . Round ( width * scale . x ) ; } }
public virtual float ScaledHeight { get { return ( float ) Math . Round ( height * scale . y ) ; } }
public virtual Vector2D Scale { get { return scale ; } }
2010-05-08 13:14:48 +00:00
public bool WorldPanning { get { return worldpanning ; } }
2019-05-30 22:20:12 +00:00
public int NameWidth { get { return namewidth ; } } // biwa
public int ShortNameWidth { get { return shortnamewidth ; } } // biwa
2015-09-16 12:10:43 +00:00
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
2015-05-27 12:38:03 +00:00
protected ImageData ( )
2009-04-19 18:07:22 +00:00
{
// Defaults
usecolorcorrection = true ;
2019-12-29 21:59:57 +00:00
AllowUnload = true ;
2015-10-02 14:47:34 +00:00
//mxd. Hashing
hashcode = hashcounter + + ;
2009-04-19 18:07:22 +00:00
}
// Destructor
~ ImageData ( )
{
this . Dispose ( ) ;
}
// Disposer
public virtual void Dispose ( )
{
// Not already disposed?
if ( ! isdisposed )
{
2020-01-12 19:17:18 +00:00
// Clean up
loadedbitmap ? . Dispose ( ) ;
2020-01-12 18:53:50 +00:00
previewbitmap ? . Dispose ( ) ;
2022-01-19 16:58:12 +00:00
uncorrectedbitmap ? . Dispose ( ) ;
2020-01-12 18:53:50 +00:00
spritepreviewbitmap ? . Dispose ( ) ;
texture ? . Dispose ( ) ;
2022-01-04 19:17:12 +00:00
indexedTexture ? . Dispose ( ) ;
2020-01-12 19:17:18 +00:00
loadedbitmap = null ;
2020-01-12 18:53:50 +00:00
previewbitmap = null ;
2022-01-19 16:58:12 +00:00
uncorrectedbitmap = null ;
2020-01-12 18:53:50 +00:00
spritepreviewbitmap = null ;
2019-12-29 02:54:12 +00:00
texture = null ;
2009-04-19 18:07:22 +00:00
2019-12-29 02:54:12 +00:00
// Done
imagestate = ImageLoadState . None ;
previewstate = ImageLoadState . None ;
isdisposed = true ;
2009-04-19 18:07:22 +00:00
}
}
#endregion
#region = = = = = = = = = = = = = = = = = = Management
// This adds a reference
// This sets the name
2014-11-25 11:52:01 +00:00
protected virtual void SetName ( string name )
2009-04-19 18:07:22 +00:00
{
this . name = name ;
2015-11-17 17:50:56 +00:00
this . filepathname = name ; //mxd
2014-12-03 09:06:05 +00:00
this . shortname = name ; //mxd
2014-11-25 11:52:01 +00:00
this . virtualname = name ; //mxd
this . displayname = name ; //mxd
this . longname = Lump . MakeLongName ( name ) ; //mxd
2019-05-30 22:20:12 +00:00
ComputeNamesWidth ( ) ; // biwa
2009-04-19 18:07:22 +00:00
}
2019-05-30 22:20:12 +00:00
// biwa. Computing the widths in the constructor of ImageBrowserItem accumulates to taking forever when loading many images,
// like when showing the texture browser of huge texture sets like OTEX
internal void ComputeNamesWidth ( )
{
//mxd. Calculate names width
namewidth = ( int ) Math . Ceiling ( General . Interface . MeasureString ( name , SystemFonts . MessageBoxFont , 10000 , StringFormat . GenericTypographic ) . Width ) + 6 ;
shortnamewidth = ( int ) Math . Ceiling ( General . Interface . MeasureString ( shortname , SystemFonts . MessageBoxFont , 10000 , StringFormat . GenericTypographic ) . Width ) + 6 ;
}
2020-01-12 18:37:27 +00:00
public int GetAlphaTestWidth ( )
{
2020-01-12 19:17:18 +00:00
return alphatestWidth ;
2020-01-12 18:37:27 +00:00
}
public int GetAlphaTestHeight ( )
{
2020-01-12 19:17:18 +00:00
return alphatestHeight ;
2020-01-12 18:37:27 +00:00
}
public bool AlphaTestPixel ( int x , int y )
{
2020-01-12 19:17:18 +00:00
if ( alphatest ! = null )
return alphatest . Get ( x + y * alphatestWidth ) ;
else
return true ;
2020-01-12 18:37:27 +00:00
}
public Image GetBackgroundBitmap ( )
{
2021-05-13 19:36:29 +00:00
return LocalGetBitmap ( usecolorcorrection ) ;
2020-01-12 18:37:27 +00:00
}
public Bitmap GetSkyboxBitmap ( )
{
2021-05-13 19:36:29 +00:00
return LocalGetBitmap ( usecolorcorrection ) ;
2020-01-12 18:37:27 +00:00
}
public Bitmap ExportBitmap ( )
{
2021-05-13 19:36:29 +00:00
return LocalGetBitmap ( usecolorcorrection ) ;
2020-01-12 18:37:27 +00:00
}
public Bitmap GetSpritePreview ( )
{
2020-01-12 18:53:50 +00:00
if ( spritepreviewbitmap = = null )
2021-05-13 19:36:29 +00:00
spritepreviewbitmap = LocalGetBitmap ( usecolorcorrection ) ;
2020-01-12 18:53:50 +00:00
return spritepreviewbitmap ;
2020-01-12 18:37:27 +00:00
}
2019-12-29 02:54:12 +00:00
// Loads the image directly. This is needed by the background loader for some patches.
2021-05-13 19:36:29 +00:00
// biwa. Just setting UseGammeCorrection before LocalGetBitmap was not enough, since its
// state is subject to race conditions at load time when using a texture as a patch
public Bitmap LocalGetBitmap ( bool withcolorcorrection )
{
// Note: if this turns out to be too slow, do NOT try to make it use GetBitmap or bitmap.
// Create a cache for the local background loader thread instead.
2019-12-29 02:54:12 +00:00
2021-05-13 19:36:29 +00:00
LocalLoadResult result = LocalLoadImage ( ) ;
if ( result . messages . Any ( x = > x . Type = = ErrorType . Error ) )
{
return Properties . Resources . Failed ;
}
ConvertImageFormat ( result , withcolorcorrection ) ;
return result . bitmap ;
}
public void LoadImageNow ( )
2020-01-12 22:10:57 +00:00
{
2021-06-26 20:20:39 +00:00
LoadImageNow ( true ) ;
2020-01-12 22:10:57 +00:00
}
2021-06-26 20:20:39 +00:00
public void LoadImageNow ( bool notify )
{
if ( imagestate ! = ImageLoadState . Ready )
{
imagestate = ImageLoadState . Loading ;
LoadImage ( notify ) ;
}
}
internal void BackgroundLoadImage ( )
2019-12-28 18:12:22 +00:00
{
LoadImage ( true ) ;
}
2009-04-19 18:07:22 +00:00
// This loads the image
2020-01-12 20:12:56 +00:00
protected void LoadImage ( bool notify )
2009-04-19 18:07:22 +00:00
{
2019-12-29 21:59:57 +00:00
if ( imagestate = = ImageLoadState . Ready & & previewstate ! = ImageLoadState . Loading )
return ;
2019-12-29 02:54:12 +00:00
// Do the loading
LocalLoadResult loadResult = LocalLoadImage ( ) ;
2022-01-31 18:49:26 +00:00
MakeUncorrectedImage ( loadResult ) ;
2021-05-13 19:36:29 +00:00
ConvertImageFormat ( loadResult , usecolorcorrection ) ;
2019-12-29 19:48:11 +00:00
MakeImagePreview ( loadResult ) ;
2020-01-12 19:17:18 +00:00
MakeAlphaTestImage ( loadResult ) ;
2009-04-19 18:07:22 +00:00
2019-12-30 05:22:32 +00:00
// Save memory by disposing the original image immediately if we only used it to load a preview image
bool onlyPreview = false ;
2020-01-12 22:10:57 +00:00
if ( imagestate ! = ImageLoadState . Loading )
2019-12-30 05:22:32 +00:00
{
loadResult . bitmap ? . Dispose ( ) ;
2020-01-12 22:10:57 +00:00
loadResult . bitmap = null ;
2022-01-19 16:58:12 +00:00
loadResult . uncorrected ? . Dispose ( ) ;
loadResult . uncorrected = null ;
2019-12-30 05:22:32 +00:00
onlyPreview = true ;
}
2019-12-29 02:54:12 +00:00
General . MainWindow . RunOnUIThread ( ( ) = >
{
2020-01-12 22:10:57 +00:00
if ( imagestate = = ImageLoadState . Loading & & ! onlyPreview )
2019-12-29 02:54:12 +00:00
{
2019-12-29 21:59:57 +00:00
// Log errors and warnings
foreach ( LogMessage message in loadResult . messages )
{
General . ErrorLogger . Add ( message . Type , message . Text ) ;
}
if ( loadResult . messages . Any ( x = > x . Type = = ErrorType . Error ) )
{
loadfailed = true ;
}
2020-01-12 19:17:18 +00:00
loadedbitmap ? . Dispose ( ) ;
2022-01-19 16:58:12 +00:00
uncorrectedbitmap ? . Dispose ( ) ;
2019-12-29 21:59:57 +00:00
texture ? . Dispose ( ) ;
2022-01-04 19:17:12 +00:00
indexedTexture ? . Dispose ( ) ;
2019-12-29 21:59:57 +00:00
imagestate = ImageLoadState . Ready ;
2020-01-12 19:17:18 +00:00
loadedbitmap = loadResult . bitmap ;
2022-01-19 16:58:12 +00:00
uncorrectedbitmap = loadResult . uncorrected ;
2020-01-12 19:17:18 +00:00
alphatest = loadResult . alphatest ;
alphatestWidth = loadResult . alphatestWidth ;
alphatestHeight = loadResult . alphatestHeight ;
2019-12-29 21:59:57 +00:00
if ( loadResult . uiThreadWork ! = null )
loadResult . uiThreadWork ( ) ;
2019-12-29 02:54:12 +00:00
}
2019-12-29 21:59:57 +00:00
else
2019-12-29 02:54:12 +00:00
{
2019-12-29 21:59:57 +00:00
loadResult . bitmap ? . Dispose ( ) ;
2019-12-29 02:54:12 +00:00
}
2019-12-29 21:59:57 +00:00
if ( previewstate = = ImageLoadState . Loading )
{
previewbitmap ? . Dispose ( ) ;
previewstate = ImageLoadState . Ready ;
previewbitmap = loadResult . preview ;
}
else
{
loadResult . preview ? . Dispose ( ) ;
}
2019-12-29 02:54:12 +00:00
} ) ;
// Notify the main thread about the change so that sectors can update their buffers
2020-01-12 20:12:56 +00:00
if ( notify )
{
if ( this is SpriteImage | | this is VoxelImage ) General . MainWindow . SpriteDataLoaded ( this . Name ) ;
else General . MainWindow . ImageDataLoaded ( this . name ) ;
}
}
2019-12-29 02:54:12 +00:00
protected class LocalLoadResult
{
public LocalLoadResult ( Bitmap bitmap , string error = null , Action uiThreadWork = null )
{
this . bitmap = bitmap ;
messages = new List < LogMessage > ( ) ;
if ( error ! = null )
messages . Add ( new LogMessage ( ErrorType . Error , error ) ) ;
this . uiThreadWork = uiThreadWork ;
}
public LocalLoadResult ( Bitmap bitmap , IEnumerable < LogMessage > messages , Action uiThreadWork = null )
{
this . bitmap = bitmap ;
this . messages = messages . ToList ( ) ;
this . uiThreadWork = uiThreadWork ;
}
public Bitmap bitmap ;
2019-12-29 19:48:11 +00:00
public Bitmap preview ;
2022-01-19 16:58:12 +00:00
public Bitmap uncorrected ;
2020-01-12 19:17:18 +00:00
public BitArray alphatest ;
public int alphatestWidth ;
public int alphatestHeight ;
2019-12-29 02:54:12 +00:00
public List < LogMessage > messages ;
public Action uiThreadWork ;
}
protected abstract LocalLoadResult LocalLoadImage ( ) ;
2009-04-19 18:07:22 +00:00
2019-12-29 02:54:12 +00:00
protected class LogMessage
{
public LogMessage ( ErrorType type , string text ) { Type = type ; Text = text ; }
public ErrorType Type { get ; set ; }
public string Text { get ; set ; }
}
2021-05-13 19:36:29 +00:00
void ConvertImageFormat ( LocalLoadResult loadResult , bool withcolorcorrection )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
// Bitmap loaded successfully?
Bitmap bitmap = loadResult . bitmap ;
if ( bitmap ! = null )
{
// Bitmap has incorrect format?
if ( bitmap . PixelFormat ! = PixelFormat . Format32bppArgb )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
//General.ErrorLogger.Add(ErrorType.Warning, "Image '" + name + "' does not have A8R8G8B8 pixel format. Conversion was needed.");
Bitmap oldbitmap = bitmap ;
try
{
// Convert to desired pixel format
bitmap = new Bitmap ( oldbitmap . Size . Width , oldbitmap . Size . Height , PixelFormat . Format32bppArgb ) ;
Graphics g = Graphics . FromImage ( bitmap ) ;
g . PageUnit = GraphicsUnit . Pixel ;
g . CompositingQuality = CompositingQuality . HighQuality ;
g . InterpolationMode = InterpolationMode . NearestNeighbor ;
g . SmoothingMode = SmoothingMode . None ;
g . PixelOffsetMode = PixelOffsetMode . None ;
g . Clear ( Color . Transparent ) ;
g . DrawImage ( oldbitmap , 0 , 0 , oldbitmap . Size . Width , oldbitmap . Size . Height ) ;
g . Dispose ( ) ;
oldbitmap . Dispose ( ) ;
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
catch ( Exception e )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
bitmap = oldbitmap ;
loadResult . messages . Add ( new LogMessage ( ErrorType . Warning , "Cannot lock image \"" + name + "\" for pixel format conversion. The image may not be displayed correctly.\n" + e . GetType ( ) . Name + ": " + e . Message ) ) ;
}
}
// This applies brightness correction on the image
2021-05-13 19:36:29 +00:00
if ( withcolorcorrection )
2019-12-29 02:54:12 +00:00
{
BitmapData bmpdata = null ;
2014-11-25 11:52:01 +00:00
2019-12-29 02:54:12 +00:00
try
{
// Try locking the bitmap
bmpdata = bitmap . LockBits ( new Rectangle ( 0 , 0 , bitmap . Size . Width , bitmap . Size . Height ) , ImageLockMode . ReadWrite , PixelFormat . Format32bppArgb ) ;
}
catch ( Exception e )
{
loadResult . messages . Add ( new LogMessage ( ErrorType . Warning , "Cannot lock image \"" + name + "\" for color correction. The image may not be displayed correctly.\n" + e . GetType ( ) . Name + ": " + e . Message ) ) ;
}
2009-04-19 18:07:22 +00:00
2019-12-29 02:54:12 +00:00
// Bitmap locked?
if ( bmpdata ! = null )
{
// Apply color correction
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
General . Colors . ApplyColorCorrection ( pixels , bmpdata . Width * bmpdata . Height ) ;
bitmap . UnlockBits ( bmpdata ) ;
2009-04-19 18:07:22 +00:00
}
}
2019-12-29 02:54:12 +00:00
}
else
{
// Loading failed
// We still mark the image as ready so that it will
// not try loading again until Reload Resources is used
bitmap = new Bitmap ( Properties . Resources . Failed ) ;
}
if ( bitmap ! = null )
{
width = bitmap . Size . Width ;
height = bitmap . Size . Height ;
// Do we still have to set a scale?
if ( ( scale . x = = 0.0f ) & & ( scale . y = = 0.0f ) )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
if ( ( General . Map ! = null ) & & ( General . Map . Config ! = null ) )
{
scale . x = General . Map . Config . DefaultTextureScale ;
scale . y = General . Map . Config . DefaultTextureScale ;
}
else
2012-06-01 19:53:14 +00:00
{
2019-12-29 02:54:12 +00:00
scale . x = 1.0f ;
scale . y = 1.0f ;
2012-06-01 19:53:14 +00:00
}
2019-12-29 02:54:12 +00:00
}
2012-06-01 19:53:14 +00:00
2019-12-29 02:54:12 +00:00
if ( ! loadfailed )
{
//mxd. Check translucency and calculate average color?
if ( General . Map ! = null & & General . Map . Data ! = null & & General . Map . Data . GlowingFlats ! = null & &
General . Map . Data . GlowingFlats . ContainsKey ( longname ) & &
General . Map . Data . GlowingFlats [ longname ] . CalculateTextureColor )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
BitmapData bmpdata = null ;
try
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
bmpdata = bitmap . LockBits ( new Rectangle ( 0 , 0 , bitmap . Size . Width , bitmap . Size . Height ) , ImageLockMode . ReadWrite , PixelFormat . Format32bppArgb ) ;
2009-04-19 18:07:22 +00:00
}
2019-12-29 02:54:12 +00:00
catch ( Exception e )
2009-04-19 18:07:22 +00:00
{
2019-12-29 02:54:12 +00:00
loadResult . messages . Add ( new LogMessage ( ErrorType . Error , "Cannot lock image \"" + this . filepathname + "\" for glow color calculation. " + e . GetType ( ) . Name + ": " + e . Message ) ) ;
2009-04-19 18:07:22 +00:00
}
2015-04-14 11:33:57 +00:00
2019-12-29 02:54:12 +00:00
if ( bmpdata ! = null )
2015-04-14 11:33:57 +00:00
{
2019-12-29 02:54:12 +00:00
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
int numpixels = bmpdata . Width * bmpdata . Height ;
uint r = 0 ;
uint g = 0 ;
uint b = 0 ;
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
2017-01-04 13:28:36 +00:00
{
2019-12-29 02:54:12 +00:00
r + = cp - > r ;
g + = cp - > g ;
b + = cp - > b ;
// Also check alpha
if ( cp - > a > 0 & & cp - > a < 255 ) istranslucent = true ;
else if ( cp - > a = = 0 ) ismasked = true ;
2017-01-04 13:28:36 +00:00
}
2019-12-29 02:54:12 +00:00
// Update glow data
int br = ( int ) ( r / numpixels ) ;
int bg = ( int ) ( g / numpixels ) ;
int bb = ( int ) ( b / numpixels ) ;
int max = Math . Max ( br , Math . Max ( bg , bb ) ) ;
// Black can't glow...
if ( max = = 0 )
2017-01-04 13:28:36 +00:00
{
2019-12-29 02:54:12 +00:00
General . Map . Data . GlowingFlats . Remove ( longname ) ;
2017-01-04 13:28:36 +00:00
}
2019-12-29 02:54:12 +00:00
else
2015-04-14 11:33:57 +00:00
{
2019-12-29 02:54:12 +00:00
// That's how it's done in GZDoom (and I may be totally wrong about this)
br = Math . Min ( 255 , br * 153 / max ) ;
bg = Math . Min ( 255 , bg * 153 / max ) ;
bb = Math . Min ( 255 , bb * 153 / max ) ;
General . Map . Data . GlowingFlats [ longname ] . Color = new PixelColor ( 255 , ( byte ) br , ( byte ) bg , ( byte ) bb ) ;
General . Map . Data . GlowingFlats [ longname ] . CalculateTextureColor = false ;
if ( ! General . Map . Data . GlowingFlats [ longname ] . Fullbright ) General . Map . Data . GlowingFlats [ longname ] . Brightness = ( br + bg + bb ) / 3 ;
2017-01-04 13:28:36 +00:00
}
2019-12-29 02:54:12 +00:00
// Release the data
bitmap . UnlockBits ( bmpdata ) ;
2017-01-04 13:28:36 +00:00
}
2019-12-29 02:54:12 +00:00
}
//mxd. Check if the texture is translucent
else
{
BitmapData bmpdata = null ;
try
2017-01-04 13:28:36 +00:00
{
2019-12-29 02:54:12 +00:00
bmpdata = bitmap . LockBits ( new Rectangle ( 0 , 0 , bitmap . Size . Width , bitmap . Size . Height ) , ImageLockMode . ReadWrite , PixelFormat . Format32bppArgb ) ;
}
catch ( Exception e )
{
loadResult . messages . Add ( new LogMessage ( ErrorType . Error , "Cannot lock image \"" + this . filepathname + "\" for translucency check. " + e . GetType ( ) . Name + ": " + e . Message ) ) ;
}
if ( bmpdata ! = null )
{
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
int numpixels = bmpdata . Width * bmpdata . Height ;
2015-04-14 11:33:57 +00:00
2019-12-29 02:54:12 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - )
2017-01-04 13:28:36 +00:00
{
2019-12-29 02:54:12 +00:00
// Check alpha
if ( cp - > a > 0 & & cp - > a < 255 ) istranslucent = true ;
else if ( cp - > a = = 0 ) ismasked = true ;
2015-09-27 21:09:14 +00:00
}
2019-12-29 02:54:12 +00:00
// Release the data
bitmap . UnlockBits ( bmpdata ) ;
2015-04-14 11:33:57 +00:00
}
}
2009-04-19 18:07:22 +00:00
}
}
2019-12-29 02:54:12 +00:00
loadResult . bitmap = bitmap ;
2009-04-19 18:07:22 +00:00
}
2019-12-29 19:48:11 +00:00
// Dimensions of a single preview image
const int MAX_PREVIEW_SIZE = 256 ; //mxd
2022-01-31 18:49:26 +00:00
// This makes a copy of the image before color correction
private void MakeUncorrectedImage ( LocalLoadResult loadResult )
2022-01-19 16:58:12 +00:00
{
if ( loadResult . bitmap = = null )
return ;
2022-01-31 18:49:26 +00:00
loadResult . uncorrected = new Bitmap ( loadResult . bitmap ) ;
2022-01-19 16:58:12 +00:00
}
2019-12-29 19:48:11 +00:00
// This makes a preview for the given image and updates the image settings
private void MakeImagePreview ( LocalLoadResult loadResult )
{
if ( loadResult . bitmap = = null )
return ;
Bitmap image = loadResult . bitmap ;
Bitmap preview ;
int imagewidth = image . Width ;
int imageheight = image . Height ;
// Determine preview size
float scalex = ( imagewidth > MAX_PREVIEW_SIZE ) ? ( MAX_PREVIEW_SIZE / ( float ) imagewidth ) : 1.0f ;
float scaley = ( imageheight > MAX_PREVIEW_SIZE ) ? ( MAX_PREVIEW_SIZE / ( float ) imageheight ) : 1.0f ;
float scale = Math . Min ( scalex , scaley ) ;
int previewwidth = ( int ) ( imagewidth * scale ) ;
int previewheight = ( int ) ( imageheight * scale ) ;
if ( previewwidth < 1 ) previewwidth = 1 ;
if ( previewheight < 1 ) previewheight = 1 ;
//mxd. Expected and actual image sizes and format match?
if ( previewwidth = = imagewidth & & previewheight = = imageheight & & image . PixelFormat = = PixelFormat . Format32bppArgb )
{
preview = new Bitmap ( image ) ;
}
else
{
// Make new image
preview = new Bitmap ( previewwidth , previewheight , PixelFormat . Format32bppArgb ) ;
Graphics g = Graphics . FromImage ( preview ) ;
g . PageUnit = GraphicsUnit . Pixel ;
//g.CompositingQuality = CompositingQuality.HighQuality; //mxd
g . InterpolationMode = InterpolationMode . NearestNeighbor ;
//g.SmoothingMode = SmoothingMode.HighQuality; //mxd
g . PixelOffsetMode = PixelOffsetMode . None ;
//g.Clear(Color.Transparent); //mxd
// Draw image onto atlas
Rectangle atlasrect = new Rectangle ( 0 , 0 , previewwidth , previewheight ) ;
RectangleF imgrect = General . MakeZoomedRect ( new Size ( imagewidth , imageheight ) , atlasrect ) ;
if ( imgrect . Width < 1.0f )
{
imgrect . X - = 0.5f - imgrect . Width * 0.5f ;
imgrect . Width = 1.0f ;
}
if ( imgrect . Height < 1.0f )
{
imgrect . Y - = 0.5f - imgrect . Height * 0.5f ;
imgrect . Height = 1.0f ;
}
g . DrawImage ( image , imgrect ) ;
g . Dispose ( ) ;
}
loadResult . preview = preview ;
}
2020-11-15 02:20:41 +00:00
unsafe void MakeAlphaTestImage ( LocalLoadResult loadResult )
2020-01-12 19:17:18 +00:00
{
if ( loadResult . bitmap = = null )
return ;
int width = loadResult . bitmap . Width ;
int height = loadResult . bitmap . Height ;
loadResult . alphatestWidth = width ;
loadResult . alphatestHeight = height ;
2020-11-15 02:20:41 +00:00
BitmapData bmpdata = loadResult . bitmap . LockBits ( new Rectangle ( 0 , 0 , loadResult . bitmap . Size . Width , loadResult . bitmap . Size . Height ) , ImageLockMode . ReadOnly , PixelFormat . Format32bppArgb ) ;
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
for ( int y = 0 ; y < height ; y + + )
2020-01-12 19:17:18 +00:00
{
2020-11-15 02:20:41 +00:00
PixelColor * line = pixels + y * width ;
for ( int x = 0 ; x < width ; x + + )
2020-01-12 19:17:18 +00:00
{
2020-11-15 02:20:41 +00:00
if ( line [ x ] . a = = 0 )
2020-01-12 19:17:18 +00:00
{
if ( loadResult . alphatest = = null )
loadResult . alphatest = new BitArray ( width * height , true ) ;
loadResult . alphatest . Set ( x + y * width , false ) ;
}
}
}
2020-11-15 02:20:41 +00:00
loadResult . bitmap . UnlockBits ( bmpdata ) ;
}
2022-01-04 19:17:12 +00:00
Texture GetTexture ( bool indexed = false )
2009-04-19 18:07:22 +00:00
{
2022-01-04 19:17:12 +00:00
if ( indexed & & indexedTexture ! = null )
return indexedTexture ;
if ( ! indexed & & texture ! = null )
2022-12-10 18:11:28 +00:00
return texture ;
2019-08-09 21:15:48 +00:00
2022-01-04 19:17:12 +00:00
if ( indexed & & ! wantIndexed )
{
// indexed texture requested, but we didn't generate it, so we have to reload the image
ReleaseTexture ( ) ;
imagestate = ImageLoadState . None ;
wantIndexed = true ;
}
2022-12-10 18:11:28 +00:00
2022-01-04 19:17:12 +00:00
if ( imagestate = = ImageLoadState . Loading )
2022-12-10 18:11:28 +00:00
return General . Map . Data . LoadingTexture ;
if ( loadfailed )
return General . Map . Data . FailedTexture ;
if ( imagestate = = ImageLoadState . None )
{
General . Map . Data . QueueLoadImage ( this ) ;
return General . Map . Data . LoadingTexture ;
}
if ( loadedbitmap = = null )
{
return General . Map . Data . LoadingTexture ;
}
if ( wantIndexed )
{
Bitmap indexedBitmap = CreateIndexedBitmap ( uncorrectedbitmap , General . Map . Data . Palette ) ;
indexedTexture = new Texture ( General . Map . Graphics , indexedBitmap ) ;
indexedTexture . UserData = TEXTURE_INDEXED ;
}
texture = new Texture ( General . Map . Graphics , loadedbitmap ) ;
loadedbitmap . Dispose ( ) ;
loadedbitmap = null ;
if ( uncorrectedbitmap ! = null )
{
uncorrectedbitmap . Dispose ( ) ;
uncorrectedbitmap = null ;
}
2016-02-06 00:04:02 +00:00
#if DEBUG
2019-12-29 19:48:11 +00:00
texture . Tag = name ; //mxd. Helps with tracking undisposed resources...
2016-02-06 00:04:02 +00:00
#endif
2022-12-10 18:11:28 +00:00
return indexed ? indexedTexture : texture ;
2022-01-04 19:17:12 +00:00
}
Bitmap CreateIndexedBitmap ( Bitmap original , Playpal palette )
{
int [ ] indices = new int [ original . Width * original . Height ] ;
Bitmap indexed = new Bitmap ( original . Width , original . Height , PixelFormat . Format32bppArgb ) ;
BitmapData indata = original . LockBits ( new Rectangle ( 0 , 0 , original . Size . Width , original . Size . Height ) , ImageLockMode . ReadOnly , PixelFormat . Format32bppArgb ) ;
PixelColor * inpixels = ( PixelColor * ) indata . Scan0 . ToPointer ( ) ;
General . Colors . QuantizeColorsToPlaypal ( inpixels , indices , palette ) ;
BitmapData outdata = indexed . LockBits ( new Rectangle ( 0 , 0 , indexed . Size . Width , indexed . Size . Height ) , ImageLockMode . WriteOnly , PixelFormat . Format32bppArgb ) ;
PixelColor * outpixels = ( PixelColor * ) outdata . Scan0 . ToPointer ( ) ;
for ( int i = 0 ; i < indices . Length ; i + + )
{
outpixels [ i ] . r = ( byte ) indices [ i ] ;
outpixels [ i ] . g = 0 ;
outpixels [ i ] . b = 0 ;
outpixels [ i ] . a = inpixels [ i ] . a ;
}
original . UnlockBits ( indata ) ;
indexed . UnlockBits ( outdata ) ;
return indexed ;
2012-06-01 19:53:14 +00:00
}
// This updates a dynamic texture
2020-04-19 12:19:18 +00:00
public void UpdateTexture ( Bitmap canvas )
2012-06-01 19:53:14 +00:00
{
2020-04-19 12:19:18 +00:00
if ( canvas . PixelFormat ! = PixelFormat . Format32bppArgb )
throw new Exception ( "Dynamic images must be in 32 bits ARGB format." ) ;
2012-06-01 19:53:14 +00:00
if ( ! dynamictexture )
throw new Exception ( "The image must be a dynamic image to support direct updating." ) ;
2017-05-09 03:06:07 +00:00
2020-04-19 12:19:18 +00:00
General . Map . Graphics . SetPixels ( GetTexture ( ) , canvas ) ;
2009-04-19 18:07:22 +00:00
}
// This destroys the Direct3D texture
2009-08-02 21:34:16 +00:00
public void ReleaseTexture ( )
2009-04-19 18:07:22 +00:00
{
2019-12-29 19:48:11 +00:00
texture ? . Dispose ( ) ;
2019-12-29 02:54:12 +00:00
texture = null ;
2022-01-04 19:17:12 +00:00
indexedTexture ? . Dispose ( ) ;
indexedTexture = null ;
2009-04-19 18:07:22 +00:00
}
// This returns a preview image
public virtual Image GetPreview ( )
{
2019-12-29 02:54:12 +00:00
// Preview ready?
if ( previewstate = = ImageLoadState . Ready )
{
// Make a copy
2019-12-29 19:48:11 +00:00
return new Bitmap ( previewbitmap ) ;
2019-12-29 02:54:12 +00:00
}
2020-01-12 22:10:57 +00:00
// Loading failed?
if ( loadfailed )
2019-12-29 02:54:12 +00:00
{
// Return error bitmap
return Properties . Resources . Failed ;
2016-06-13 23:37:55 +00:00
}
2019-12-29 02:54:12 +00:00
2020-01-12 22:10:57 +00:00
if ( previewstate = = ImageLoadState . None )
{
General . Map . Data . QueueLoadPreview ( this ) ;
}
// Return loading bitmap
return Properties . Resources . Hourglass ;
2009-04-19 18:07:22 +00:00
}
2015-10-02 14:47:34 +00:00
//mxd. This greatly speeds up Dictionary lookups
public override int GetHashCode ( )
{
return hashcode ;
}
2009-04-19 18:07:22 +00:00
#endregion
}
}