UltimateZoneBuilder/Source/Core/Data/PreviewManager.cs
MaxED 7ba352216d Visual mode: fixed a crash when trying to load non existing sprite, defined in configuration or via "//@sprite" parameter in DECORATE.
MissingThing icon is now used when the editor is unable to load thing sprite.
An error is added to error logger when the editor is unable to load thing sprite.
2013-08-21 10:45:54 +00:00

279 lines
6.6 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 System.Drawing.Drawing2D;
#endregion
namespace CodeImp.DoomBuilder.Data
{
public class PreviewManager
{
#region ================== Constants
// Image format
private const PixelFormat IMAGE_FORMAT = PixelFormat.Format32bppArgb;
// Dimensions of a single preview image
public static readonly int[] PREVIEW_SIZES = new int[] { 48, 64, 80, 96, 112, 128 };
#endregion
#region ================== Variables
// Dimensions of a single preview image
private int maxpreviewwidth = 64;
private int maxpreviewheight = 64;
// Images
private List<Bitmap> images;
// Processing
private Queue<ImageData> imageque;
// Disposing
private bool isdisposed = false;
#endregion
#region ================== Properties
// Constants
public int MaxImageWidth { get { return maxpreviewwidth; } }
public int MaxImageHeight { get { return maxpreviewheight; } }
// Disposing
internal bool IsDisposed { get { return isdisposed; } }
// Loading
internal bool IsLoading
{
get
{
return (imageque.Count > 0);
}
}
#endregion
#region ================== Constructor / Disposer
// Constructor
internal PreviewManager()
{
// Initialize
images = new List<Bitmap>();
imageque = new Queue<ImageData>();
maxpreviewwidth = PREVIEW_SIZES[General.Settings.PreviewImageSize];
maxpreviewheight = PREVIEW_SIZES[General.Settings.PreviewImageSize];
// We have no destructor
GC.SuppressFinalize(this);
}
// Disposer
internal void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
foreach(Bitmap b in images) b.Dispose();
images = null;
// Done
isdisposed = true;
}
}
#endregion
#region ================== Private Methods
// This makes a preview for the given image and updates the image settings
private void MakeImagePreview(ImageData img)
{
int previewwidth, previewheight;
int imagewidth, imageheight;
Bitmap preview;
Graphics g;
lock(img)
{
// Load image if needed
if(!img.IsImageLoaded) img.LoadImage();
if(!img.LoadFailed)
{
imagewidth = img.Width;
imageheight = img.Height;
}
else
{
imagewidth = img.GetBitmap().Size.Width;
imageheight = img.GetBitmap().Size.Height;
}
// Determine preview size
float scalex = (img.Width > maxpreviewwidth) ? (maxpreviewwidth / (float)imagewidth) : 1.0f;
float scaley = (img.Height > maxpreviewheight) ? (maxpreviewheight / (float)imageheight) : 1.0f;
float scale = Math.Min(scalex, scaley);
previewwidth = (int)(imagewidth * scale);
previewheight = (int)(imageheight * scale);
if(previewwidth < 1) previewwidth = 1;
if(previewheight < 1) previewheight = 1;
// Make new image
preview = new Bitmap(previewwidth, previewheight, IMAGE_FORMAT);
g = Graphics.FromImage(preview);
g.PageUnit = GraphicsUnit.Pixel;
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.None;
g.Clear(Color.Transparent);
// 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(img.GetBitmap(), imgrect);
g.Dispose();
// Unload image if no longer needed
if(!img.IsReferenced) img.UnloadImage();
lock(images)
{
// Set numbers
img.PreviewIndex = images.Count;
img.PreviewState = ImageLoadState.Ready;
// Add to previews list
images.Add(preview);
}
}
}
#endregion
#region ================== Public Methods
// This draws a preview centered in a target
internal void DrawPreview(int previewindex, Graphics target, Point targetpos)
{
Bitmap image;
// Get the preview we need
lock(images) { image = images[previewindex]; }
// Adjust offset for the size of the preview image
targetpos.X += (maxpreviewwidth - image.Width) >> 1;
targetpos.Y += (maxpreviewheight - image.Height) >> 1;
// Draw from atlas to target
lock(image)
{
target.DrawImageUnscaled(image, targetpos.X, targetpos.Y);
}
}
// This returns a copy of the preview
internal Bitmap GetPreviewCopy(int previewindex)
{
Bitmap image;
// Get the preview we need
lock(images) { image = images[previewindex]; }
// Make a copy
lock(image)
{
return new Bitmap(image);
}
}
// Background loading
// Return true when we have more work to do, so that the
// thread will not wait too long before calling again
internal bool BackgroundLoad()
{
// Get next item
ImageData image = null;
lock(imageque)
{
// Fetch next image to process
if(imageque.Count > 0) image = imageque.Dequeue();
}
// Any image to process?
if(image != null)
{
// Make image preview?
if(!image.IsPreviewLoaded) MakeImagePreview(image);
}
return (image != null);
}
// This adds an image for preview creation
internal void AddImage(ImageData image)
{
lock(imageque)
{
// Add to list
image.PreviewState = ImageLoadState.Loading;
imageque.Enqueue(image);
}
}
#if DEBUG
internal void DumpAtlases()
{
lock(images)
{
int index = 0;
foreach(Bitmap a in images)
{
lock(a)
{
string file = Path.Combine(General.AppPath, "atlas" + index++ + ".png");
a.Save(file, ImageFormat.Png);
}
}
}
}
#endif
#endregion
}
}