mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
ea57d45eb3
Probably fixed probable I/O race condition when loading images. Fixed Visual mode stuttering due to floating point precision degradation when running the editor for several days without restarting (internal timer is now reset when saving the map or creating a new one). Fixed, Nodes Viewer, cosmetic: Nodes Viewer window position was reset after pressing the "Rebuild Nodes" button. Added Eternity Game configurations by printz. Updated ZDoom_ACS.cfg (CheckClass). Updated ZDoom ACC (CheckClass).
434 lines
14 KiB
C#
434 lines
14 KiB
C#
|
|
#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;
|
|
using System.IO;
|
|
using CodeImp.DoomBuilder.Controls;
|
|
using CodeImp.DoomBuilder.IO;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.Data
|
|
{
|
|
internal sealed unsafe class TEXTURESImage : ImageData
|
|
{
|
|
#region ================== Variables
|
|
|
|
private readonly List<TexturePatch> patches; //mxd
|
|
private readonly bool optional; //mxd
|
|
private readonly bool nulltexture; //mxd
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor / Disposer
|
|
|
|
// Constructor
|
|
public TEXTURESImage(string name, string virtualpath, int width, int height, float scalex, float scaley,
|
|
bool worldpanning, bool isflat, bool optional, bool nulltexture)
|
|
{
|
|
// Initialize
|
|
this.width = width;
|
|
this.height = height;
|
|
this.scale.x = scalex;
|
|
this.scale.y = scaley;
|
|
this.worldpanning = worldpanning;
|
|
this.optional = optional; //mxd
|
|
this.nulltexture = nulltexture; //mxd
|
|
this.patches = new List<TexturePatch>(1);
|
|
|
|
//mxd
|
|
SetName(name);
|
|
this.virtualname = (!string.IsNullOrEmpty(virtualpath) ? virtualpath : "[TEXTURES]") + Path.AltDirectorySeparatorChar + this.name;
|
|
this.level = virtualname.Split(new[] { Path.AltDirectorySeparatorChar }).Length - 1;
|
|
this.isFlat = isflat;
|
|
|
|
// We have no destructor
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
//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);
|
|
|
|
if(General.Settings.CapitalizeTextureNames && !string.IsNullOrEmpty(this.displayname))
|
|
{
|
|
this.displayname = this.displayname.ToUpperInvariant();
|
|
}
|
|
|
|
if(this.displayname.Length > ImageBrowserItem.MAX_NAME_LENGTH)
|
|
{
|
|
this.displayname = this.displayname.Substring(0, ImageBrowserItem.MAX_NAME_LENGTH);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// This adds a patch to the texture
|
|
public void AddPatch(TexturePatch patch)
|
|
{
|
|
// Add it
|
|
patches.Add(patch);
|
|
if(patch.LumpName == Name) hasPatchWithSameName = true; //mxd
|
|
}
|
|
|
|
// This loads the image
|
|
protected override void LocalLoadImage()
|
|
{
|
|
// Checks
|
|
if(this.IsImageLoaded || width == 0 || height == 0) return;
|
|
|
|
Graphics g = null;
|
|
|
|
lock(this)
|
|
{
|
|
// Create texture bitmap
|
|
try
|
|
{
|
|
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();
|
|
General.ZeroMemory(new IntPtr(pixels), width * height * sizeof(PixelColor));
|
|
bitmap.UnlockBits(bitmapdata);
|
|
g = Graphics.FromImage(bitmap);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Unable to make bitmap
|
|
General.ErrorLogger.Add(ErrorType.Error, "Unable to load texture image \"" + this.Name + "\". " + e.GetType().Name + ": " + e.Message);
|
|
loadfailed = true;
|
|
}
|
|
|
|
int missingpatches = 0; //mxd
|
|
if(patches.Count == 0) //mxd
|
|
{
|
|
//mxd. Empty image will suffice here, I suppose...
|
|
if(nulltexture)
|
|
{
|
|
base.LocalLoadImage();
|
|
return;
|
|
}
|
|
|
|
// No patches!
|
|
General.ErrorLogger.Add(ErrorType.Warning, "No patches are defined for texture \"" + this.Name + "\"");
|
|
loadfailed = true;
|
|
}
|
|
else if(!loadfailed)
|
|
{
|
|
// Go for all patches
|
|
foreach(TexturePatch p in patches)
|
|
{
|
|
//mxd. Some patches (like "TNT1A0") should be skipped
|
|
if(p.Skip) continue;
|
|
|
|
// 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
|
|
{
|
|
patchdata.Seek(0, SeekOrigin.Begin);
|
|
patchdata.Read(membytes, 0, (int)patchdata.Length);
|
|
}
|
|
|
|
MemoryStream mem = new MemoryStream(membytes);
|
|
mem.Seek(0, SeekOrigin.Begin);
|
|
|
|
// Get a reader for the data
|
|
IImageReader reader = ImageDataFormat.GetImageReader(mem, ImageDataFormat.DOOMPICTURE, General.Map.Data.Palette);
|
|
if(reader is UnknownImageReader)
|
|
{
|
|
//mxd. Probably that's a flat?..
|
|
if(General.Map.Config.MixTexturesFlats)
|
|
{
|
|
reader = ImageDataFormat.GetImageReader(mem, ImageDataFormat.DOOMFLAT, General.Map.Data.Palette);
|
|
}
|
|
|
|
if(reader is UnknownImageReader)
|
|
{
|
|
// Data is in an unknown format!
|
|
if(!nulltexture) General.ErrorLogger.Add((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
|
|
}
|
|
}
|
|
|
|
if(!(reader is UnknownImageReader))
|
|
{
|
|
// Get the patch
|
|
mem.Seek(0, SeekOrigin.Begin);
|
|
Bitmap patchbmp = null;
|
|
try { patchbmp = reader.ReadAsBitmap(mem); }
|
|
catch(InvalidDataException)
|
|
{
|
|
// Data cannot be read!
|
|
if(!nulltexture) General.ErrorLogger.Add((optional ? ErrorType.Warning : ErrorType.Error), "Patch lump \"" + p.LumpName + "\" data format could not be read, while loading texture \"" + this.Name + "\"");
|
|
missingpatches++; //mxd
|
|
}
|
|
|
|
if(patchbmp != null)
|
|
{
|
|
//mxd. Apply transformations from TexturePatch
|
|
patchbmp = TransformPatch(p, patchbmp);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// Done
|
|
mem.Dispose();
|
|
}
|
|
else
|
|
{
|
|
//mxd. ZDoom can use any known graphic as patch
|
|
if(General.Map.Config.MixTexturesFlats)
|
|
{
|
|
ImageData img = General.Map.Data.GetTextureImage(p.LumpName);
|
|
if(!(img is UnknownImage) && img != this)
|
|
{
|
|
if(!img.IsImageLoaded) img.LoadImage();
|
|
|
|
//mxd. Apply transformations from TexturePatch. We don't want to modify the original bitmap here, so make a copy
|
|
Bitmap patchbmp = TransformPatch(p, new Bitmap(img.GetBitmap()));
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Missing a patch lump!
|
|
if(!nulltexture) General.ErrorLogger.Add((optional ? ErrorType.Warning : ErrorType.Error), "Missing patch lump \"" + p.LumpName + "\" while loading texture \"" + this.Name + "\"");
|
|
missingpatches++; //mxd
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dispose bitmap if load failed
|
|
if(!nulltexture && (bitmap != null) && (loadfailed || missingpatches >= patches.Count)) //mxd. We can still display texture if at least one of the patches was loaded
|
|
{
|
|
bitmap.Dispose();
|
|
bitmap = null;
|
|
loadfailed = true;
|
|
}
|
|
|
|
// Pass on to base
|
|
base.LocalLoadImage();
|
|
}
|
|
}
|
|
|
|
//mxd
|
|
private Bitmap TransformPatch(TexturePatch p, Bitmap patchbmp)
|
|
{
|
|
//mxd. Flip
|
|
if(p.FlipX || p.FlipY)
|
|
{
|
|
RotateFlipType flip;
|
|
if(p.FlipX && !p.FlipY) flip = RotateFlipType.RotateNoneFlipX;
|
|
else if(!p.FlipX && p.FlipY) flip = RotateFlipType.RotateNoneFlipY;
|
|
else flip = RotateFlipType.RotateNoneFlipXY;
|
|
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.
|
|
if(p.Rotate != 0)
|
|
{
|
|
RotateFlipType rotate;
|
|
switch(p.Rotate)
|
|
{
|
|
case 90: rotate = RotateFlipType.Rotate90FlipNone; break;
|
|
case 180: rotate = RotateFlipType.Rotate180FlipNone; break;
|
|
default: rotate = RotateFlipType.Rotate270FlipNone; break;
|
|
}
|
|
patchbmp.RotateFlip(rotate);
|
|
}
|
|
|
|
// Adjust patch alpha, apply tint or blend
|
|
if(p.BlendStyle != TexturePathBlendStyle.NONE || p.RenderStyle != TexturePathRenderStyle.COPY)
|
|
{
|
|
BitmapData bmpdata = null;
|
|
|
|
try
|
|
{
|
|
bmpdata = patchbmp.LockBits(new Rectangle(0, 0, patchbmp.Size.Width, patchbmp.Size.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Cannot lock image \"" + p.LumpName + "\" for alpha adjustment. " + e.GetType().Name + ": " + e.Message);
|
|
}
|
|
|
|
if(bmpdata != null)
|
|
{
|
|
PixelColor* pixels = (PixelColor*)(bmpdata.Scan0.ToPointer());
|
|
int numpixels = bmpdata.Width * bmpdata.Height;
|
|
int patchalpha = (int)Math.Round(General.Clamp(p.Alpha, 0f, 1f) * 255); //convert alpha to [0-255] range
|
|
|
|
//mxd. Blend/Tint support
|
|
if(p.BlendStyle == TexturePathBlendStyle.BLEND)
|
|
{
|
|
for(PixelColor* cp = pixels + numpixels - 1; cp >= pixels; cp--)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
else if(p.BlendStyle == TexturePathBlendStyle.TINT)
|
|
{
|
|
float tintammount = p.BlendColor.a * PixelColor.BYTE_TO_FLOAT;// -0.1f;
|
|
|
|
if(tintammount > 0)
|
|
{
|
|
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;
|
|
|
|
for(PixelColor* cp = pixels + numpixels - 1; cp >= pixels; cp--)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
//mxd. Apply RenderStyle
|
|
if(p.RenderStyle == TexturePathRenderStyle.BLEND)
|
|
{
|
|
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
|
|
else if(p.RenderStyle != TexturePathRenderStyle.COPY)
|
|
{
|
|
// Copy portion of texture
|
|
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;
|
|
|
|
Bitmap source = new Bitmap(patchbmp.Size.Width, patchbmp.Size.Height);
|
|
using(Graphics sg = Graphics.FromImage(source))
|
|
sg.DrawImageUnscaled(bitmap, new Rectangle(-p.X, -p.Y, lockWidth, lockHeight));
|
|
|
|
// Lock texture
|
|
BitmapData texturebmpdata = null;
|
|
|
|
try
|
|
{
|
|
texturebmpdata = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Cannot lock texture \"" + this.Name + "\" to apply render style. " + e.GetType().Name + ": " + e.Message);
|
|
}
|
|
|
|
if(texturebmpdata != null)
|
|
{
|
|
PixelColor* texturepixels = (PixelColor*)(texturebmpdata.Scan0.ToPointer());
|
|
PixelColor* tcp = texturepixels + numpixels - 1;
|
|
|
|
switch(p.RenderStyle)
|
|
{
|
|
case TexturePathRenderStyle.ADD:
|
|
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;
|
|
|
|
case TexturePathRenderStyle.SUBTRACT:
|
|
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;
|
|
|
|
case TexturePathRenderStyle.REVERSE_SUBTRACT:
|
|
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;
|
|
|
|
case TexturePathRenderStyle.MODULATE:
|
|
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;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|