/*
* Copyright (c) 2006-2014, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
namespace OpenMetaverse.Imaging
{
public class ManagedImage
{
[Flags]
public enum ImageChannels
{
Gray = 1,
Color = 2,
Alpha = 4,
Bump = 8
};
public enum ImageResizeAlgorithm
{
NearestNeighbor
}
///
/// Image width
///
public int Width;
///
/// Image height
///
public int Height;
///
/// Image channel flags
///
public ImageChannels Channels;
///
/// Red channel data
///
public byte[] Red;
///
/// Green channel data
///
public byte[] Green;
///
/// Blue channel data
///
public byte[] Blue;
///
/// Alpha channel data
///
public byte[] Alpha;
///
/// Bump channel data
///
public byte[] Bump;
///
/// Create a new blank image
///
/// width
/// height
/// channel flags
public ManagedImage(int width, int height, ImageChannels channels)
{
Width = width;
Height = height;
Channels = channels;
int n = width * height;
if ((channels & ImageChannels.Gray) != 0)
{
Red = new byte[n];
}
else if ((channels & ImageChannels.Color) != 0)
{
Red = new byte[n];
Green = new byte[n];
Blue = new byte[n];
}
if ((channels & ImageChannels.Alpha) != 0)
Alpha = new byte[n];
if ((channels & ImageChannels.Bump) != 0)
Bump = new byte[n];
}
#if !NO_UNSAFE
///
///
///
///
public ManagedImage(System.Drawing.Bitmap bitmap)
{
Width = bitmap.Width;
Height = bitmap.Height;
int pixelCount = Width * Height;
if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
Channels = ImageChannels.Alpha | ImageChannels.Color;
Red = new byte[pixelCount];
Green = new byte[pixelCount];
Blue = new byte[pixelCount];
Alpha = new byte[pixelCount];
System.Drawing.Imaging.BitmapData bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
unsafe
{
byte* pixel = (byte*)bd.Scan0;
for (int i = 0; i < pixelCount; i++)
{
// GDI+ gives us BGRA and we need to turn that in to RGBA
Blue[i] = *(pixel++);
Green[i] = *(pixel++);
Red[i] = *(pixel++);
Alpha[i] = *(pixel++);
}
}
bitmap.UnlockBits(bd);
}
else if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format16bppGrayScale)
{
Channels = ImageChannels.Gray;
Red = new byte[pixelCount];
throw new NotImplementedException("16bpp grayscale image support is incomplete");
}
else if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
Channels = ImageChannels.Color;
Red = new byte[pixelCount];
Green = new byte[pixelCount];
Blue = new byte[pixelCount];
System.Drawing.Imaging.BitmapData bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
unsafe
{
byte* pixel = (byte*)bd.Scan0;
for (int i = 0; i < pixelCount; i++)
{
// GDI+ gives us BGR and we need to turn that in to RGB
Blue[i] = *(pixel++);
Green[i] = *(pixel++);
Red[i] = *(pixel++);
}
}
bitmap.UnlockBits(bd);
}
else if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppRgb)
{
Channels = ImageChannels.Color;
Red = new byte[pixelCount];
Green = new byte[pixelCount];
Blue = new byte[pixelCount];
System.Drawing.Imaging.BitmapData bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
unsafe
{
byte* pixel = (byte*)bd.Scan0;
for (int i = 0; i < pixelCount; i++)
{
// GDI+ gives us BGR and we need to turn that in to RGB
Blue[i] = *(pixel++);
Green[i] = *(pixel++);
Red[i] = *(pixel++);
pixel++; // Skip over the empty byte where the Alpha info would normally be
}
}
bitmap.UnlockBits(bd);
}
else
{
throw new NotSupportedException("Unrecognized pixel format: " + bitmap.PixelFormat.ToString());
}
}
#endif
///
/// Convert the channels in the image. Channels are created or destroyed as required.
///
/// new channel flags
public void ConvertChannels(ImageChannels channels)
{
if (Channels == channels)
return;
int n = Width * Height;
ImageChannels add = Channels ^ channels & channels;
ImageChannels del = Channels ^ channels & Channels;
if ((add & ImageChannels.Color) != 0)
{
Red = new byte[n];
Green = new byte[n];
Blue = new byte[n];
}
else if ((del & ImageChannels.Color) != 0)
{
Red = null;
Green = null;
Blue = null;
}
if ((add & ImageChannels.Alpha) != 0)
{
Alpha = new byte[n];
FillArray(Alpha, 255);
}
else if ((del & ImageChannels.Alpha) != 0)
Alpha = null;
if ((add & ImageChannels.Bump) != 0)
Bump = new byte[n];
else if ((del & ImageChannels.Bump) != 0)
Bump = null;
Channels = channels;
}
///
/// Resize or stretch the image using nearest neighbor (ugly) resampling
///
/// new width
/// new height
public void ResizeNearestNeighbor(int width, int height)
{
if (width == Width && height == Height)
return;
byte[]
red = null,
green = null,
blue = null,
alpha = null,
bump = null;
int n = width * height;
int di = 0, si;
if (Red != null) red = new byte[n];
if (Green != null) green = new byte[n];
if (Blue != null) blue = new byte[n];
if (Alpha != null) alpha = new byte[n];
if (Bump != null) bump = new byte[n];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
si = (y * Height / height) * Width + (x * Width / width);
if (Red != null) red[di] = Red[si];
if (Green != null) green[di] = Green[si];
if (Blue != null) blue[di] = Blue[si];
if (Alpha != null) alpha[di] = Alpha[si];
if (Bump != null) bump[di] = Bump[si];
di++;
}
}
Width = width;
Height = height;
Red = red;
Green = green;
Blue = blue;
Alpha = alpha;
Bump = bump;
}
///
/// Create a byte array containing 32-bit RGBA data with a bottom-left
/// origin, suitable for feeding directly into OpenGL
///
/// A byte array containing raw texture data
public byte[] ExportRaw()
{
byte[] raw = new byte[Width * Height * 4];
if ((Channels & ImageChannels.Alpha) != 0)
{
if ((Channels & ImageChannels.Color) != 0)
{
// RGBA
for (int h = 0; h < Height; h++)
{
for (int w = 0; w < Width; w++)
{
int pos = (Height - 1 - h) * Width + w;
int srcPos = h * Width + w;
raw[pos * 4 + 0] = Red[srcPos];
raw[pos * 4 + 1] = Green[srcPos];
raw[pos * 4 + 2] = Blue[srcPos];
raw[pos * 4 + 3] = Alpha[srcPos];
}
}
}
else
{
// Alpha only
for (int h = 0; h < Height; h++)
{
for (int w = 0; w < Width; w++)
{
int pos = (Height - 1 - h) * Width + w;
int srcPos = h * Width + w;
raw[pos * 4 + 0] = Alpha[srcPos];
raw[pos * 4 + 1] = Alpha[srcPos];
raw[pos * 4 + 2] = Alpha[srcPos];
raw[pos * 4 + 3] = Byte.MaxValue;
}
}
}
}
else
{
// RGB
for (int h = 0; h < Height; h++)
{
for (int w = 0; w < Width; w++)
{
int pos = (Height - 1 - h) * Width + w;
int srcPos = h * Width + w;
raw[pos * 4 + 0] = Red[srcPos];
raw[pos * 4 + 1] = Green[srcPos];
raw[pos * 4 + 2] = Blue[srcPos];
raw[pos * 4 + 3] = Byte.MaxValue;
}
}
}
return raw;
}
///
/// Create a byte array containing 32-bit RGBA data with a bottom-left
/// origin, suitable for feeding directly into OpenGL
///
/// A byte array containing raw texture data
public System.Drawing.Bitmap ExportBitmap()
{
byte[] raw = new byte[Width * Height * 4];
if ((Channels & ImageChannels.Alpha) != 0)
{
if ((Channels & ImageChannels.Color) != 0)
{
// RGBA
for (int pos = 0; pos < Height * Width; pos++)
{
raw[pos * 4 + 0] = Blue[pos];
raw[pos * 4 + 1] = Green[pos];
raw[pos * 4 + 2] = Red[pos];
raw[pos * 4 + 3] = Alpha[pos];
}
}
else
{
// Alpha only
for (int pos = 0; pos < Height * Width; pos++)
{
raw[pos * 4 + 0] = Alpha[pos];
raw[pos * 4 + 1] = Alpha[pos];
raw[pos * 4 + 2] = Alpha[pos];
raw[pos * 4 + 3] = Byte.MaxValue;
}
}
}
else
{
// RGB
for (int pos = 0; pos < Height * Width; pos++)
{
raw[pos * 4 + 0] = Blue[pos];
raw[pos * 4 + 1] = Green[pos];
raw[pos * 4 + 2] = Red[pos];
raw[pos * 4 + 3] = Byte.MaxValue;
}
}
System.Drawing.Bitmap b = new System.Drawing.Bitmap(
Width,
Height,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Drawing.Imaging.BitmapData bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(raw, 0, bd.Scan0, Width * Height * 4);
b.UnlockBits(bd);
return b;
}
public byte[] ExportTGA()
{
byte[] tga = new byte[Width * Height * ((Channels & ImageChannels.Alpha) == 0 ? 3 : 4) + 32];
int di = 0;
tga[di++] = 0; // idlength
tga[di++] = 0; // colormaptype = 0: no colormap
tga[di++] = 2; // image type = 2: uncompressed RGB
tga[di++] = 0; // color map spec is five zeroes for no color map
tga[di++] = 0; // color map spec is five zeroes for no color map
tga[di++] = 0; // color map spec is five zeroes for no color map
tga[di++] = 0; // color map spec is five zeroes for no color map
tga[di++] = 0; // color map spec is five zeroes for no color map
tga[di++] = 0; // x origin = two bytes
tga[di++] = 0; // x origin = two bytes
tga[di++] = 0; // y origin = two bytes
tga[di++] = 0; // y origin = two bytes
tga[di++] = (byte)(Width & 0xFF); // width - low byte
tga[di++] = (byte)(Width >> 8); // width - hi byte
tga[di++] = (byte)(Height & 0xFF); // height - low byte
tga[di++] = (byte)(Height >> 8); // height - hi byte
tga[di++] = (byte)((Channels & ImageChannels.Alpha) == 0 ? 24 : 32); // 24/32 bits per pixel
tga[di++] = (byte)((Channels & ImageChannels.Alpha) == 0 ? 32 : 40); // image descriptor byte
int n = Width * Height;
if ((Channels & ImageChannels.Alpha) != 0)
{
if ((Channels & ImageChannels.Color) != 0)
{
// RGBA
for (int i = 0; i < n; i++)
{
tga[di++] = Blue[i];
tga[di++] = Green[i];
tga[di++] = Red[i];
tga[di++] = Alpha[i];
}
}
else
{
// Alpha only
for (int i = 0; i < n; i++)
{
tga[di++] = Alpha[i];
tga[di++] = Alpha[i];
tga[di++] = Alpha[i];
tga[di++] = Byte.MaxValue;
}
}
}
else
{
// RGB
for (int i = 0; i < n; i++)
{
tga[di++] = Blue[i];
tga[di++] = Green[i];
tga[di++] = Red[i];
}
}
return tga;
}
private static void FillArray(byte[] array, byte value)
{
if (array != null)
{
for (int i = 0; i < array.Length; i++)
array[i] = value;
}
}
public void Clear()
{
FillArray(Red, 0);
FillArray(Green, 0);
FillArray(Blue, 0);
FillArray(Alpha, 0);
FillArray(Bump, 0);
}
public ManagedImage Clone()
{
ManagedImage image = new ManagedImage(Width, Height, Channels);
if (Red != null) image.Red = (byte[])Red.Clone();
if (Green != null) image.Green = (byte[])Green.Clone();
if (Blue != null) image.Blue = (byte[])Blue.Clone();
if (Alpha != null) image.Alpha = (byte[])Alpha.Clone();
if (Bump != null) image.Bump = (byte[])Bump.Clone();
return image;
}
}
}