mirror of
https://github.com/TechplexEngineer/plexview.git
synced 2026-07-31 04:07:24 +00:00
Initial commit
I wish I could figure out how to have PlexVie be out of the LibOMV tree but its just not working die to a DLL not found exception about libopenjpeg. (Yes the dll is in the bin folder) This will do for now
This commit is contained in:
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using OpenMetaverse.Assets;
|
||||
|
||||
namespace OpenMetaverse.Imaging
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of textures that are layered on texture of each other and "baked"
|
||||
/// in to a single texture, for avatar appearances
|
||||
/// </summary>
|
||||
public class Baker
|
||||
{
|
||||
public static readonly UUID IMG_INVISIBLE = new UUID("3a367d1c-bef1-6d43-7595-e88c1e3aadb3");
|
||||
|
||||
#region Properties
|
||||
/// <summary>Final baked texture</summary>
|
||||
public AssetTexture BakedTexture { get { return bakedTexture; } }
|
||||
/// <summary>Component layers</summary>
|
||||
public List<AppearanceManager.TextureData> Textures { get { return textures; } }
|
||||
/// <summary>Width of the final baked image and scratchpad</summary>
|
||||
public int BakeWidth { get { return bakeWidth; } }
|
||||
/// <summary>Height of the final baked image and scratchpad</summary>
|
||||
public int BakeHeight { get { return bakeHeight; } }
|
||||
/// <summary>Bake type</summary>
|
||||
public BakeType BakeType { get { return bakeType; } }
|
||||
/// <summary>Is this one of the 3 skin bakes</summary>
|
||||
private bool IsSkin { get { return bakeType == BakeType.Head || bakeType == BakeType.LowerBody || bakeType == BakeType.UpperBody; } }
|
||||
#endregion
|
||||
|
||||
#region Private fields
|
||||
/// <summary>Final baked texture</summary>
|
||||
private AssetTexture bakedTexture;
|
||||
/// <summary>Component layers</summary>
|
||||
private List<AppearanceManager.TextureData> textures = new List<AppearanceManager.TextureData>();
|
||||
/// <summary>Width of the final baked image and scratchpad</summary>
|
||||
private int bakeWidth;
|
||||
/// <summary>Height of the final baked image and scratchpad</summary>
|
||||
private int bakeHeight;
|
||||
/// <summary>Bake type</summary>
|
||||
private BakeType bakeType;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="bakeType">Bake type</param>
|
||||
public Baker(BakeType bakeType)
|
||||
{
|
||||
this.bakeType = bakeType;
|
||||
|
||||
if (bakeType == BakeType.Eyes)
|
||||
{
|
||||
bakeWidth = 128;
|
||||
bakeHeight = 128;
|
||||
}
|
||||
else
|
||||
{
|
||||
bakeWidth = 512;
|
||||
bakeHeight = 512;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public methods
|
||||
/// <summary>
|
||||
/// Adds layer for baking
|
||||
/// </summary>
|
||||
/// <param name="tdata">TexturaData struct that contains texture and its params</param>
|
||||
public void AddTexture(AppearanceManager.TextureData tdata)
|
||||
{
|
||||
lock (textures)
|
||||
{
|
||||
textures.Add(tdata);
|
||||
}
|
||||
}
|
||||
|
||||
public void Bake()
|
||||
{
|
||||
bakedTexture = new AssetTexture(new ManagedImage(bakeWidth, bakeHeight,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump));
|
||||
|
||||
// Base color for eye bake is white, color of layer0 for others
|
||||
if (bakeType == BakeType.Eyes)
|
||||
{
|
||||
InitBakedLayerColor(Color4.White);
|
||||
}
|
||||
else if (textures.Count > 0)
|
||||
{
|
||||
InitBakedLayerColor(textures[0].Color);
|
||||
}
|
||||
|
||||
// Do we have skin texture?
|
||||
bool SkinTexture = textures.Count > 0 && textures[0].Texture != null;
|
||||
|
||||
if (bakeType == BakeType.Head)
|
||||
{
|
||||
DrawLayer(LoadResourceLayer("head_color.tga"), false);
|
||||
AddAlpha(bakedTexture.Image, LoadResourceLayer("head_alpha.tga"));
|
||||
MultiplyLayerFromAlpha(bakedTexture.Image, LoadResourceLayer("head_skingrain.tga"));
|
||||
}
|
||||
|
||||
if (!SkinTexture && bakeType == BakeType.UpperBody)
|
||||
{
|
||||
DrawLayer(LoadResourceLayer("upperbody_color.tga"), false);
|
||||
}
|
||||
|
||||
if (!SkinTexture && bakeType == BakeType.LowerBody)
|
||||
{
|
||||
DrawLayer(LoadResourceLayer("lowerbody_color.tga"), false);
|
||||
}
|
||||
|
||||
ManagedImage alphaWearableTexture = null;
|
||||
|
||||
// Layer each texture on top of one other, applying alpha masks as we go
|
||||
for (int i = 0; i < textures.Count; i++)
|
||||
{
|
||||
// Skip if we have no texture on this layer
|
||||
if (textures[i].Texture == null) continue;
|
||||
|
||||
// Is this Alpha wearable and does it have an alpha channel?
|
||||
if (textures[i].TextureIndex >= AvatarTextureIndex.LowerAlpha &&
|
||||
textures[i].TextureIndex <= AvatarTextureIndex.HairAlpha)
|
||||
{
|
||||
if (textures[i].Texture.Image.Alpha != null)
|
||||
{
|
||||
alphaWearableTexture = textures[i].Texture.Image.Clone();
|
||||
}
|
||||
else if (textures[i].TextureID == IMG_INVISIBLE)
|
||||
{
|
||||
alphaWearableTexture = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't draw skin and tattoo on head bake first
|
||||
// For head bake the skin and texture are drawn last, go figure
|
||||
if (bakeType == BakeType.Head && (i == 0 || i == 1)) continue;
|
||||
|
||||
ManagedImage texture = textures[i].Texture.Image.Clone();
|
||||
//File.WriteAllBytes(bakeType + "-texture-layer-" + i + ".tga", texture.ExportTGA());
|
||||
|
||||
// Resize texture to the size of baked layer
|
||||
// FIXME: if texture is smaller than the layer, don't stretch it, tile it
|
||||
if (texture.Width != bakeWidth || texture.Height != bakeHeight)
|
||||
{
|
||||
try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
|
||||
catch (Exception) { continue; }
|
||||
}
|
||||
|
||||
// Special case for hair layer for the head bake
|
||||
// If we don't have skin texture, we discard hair alpha
|
||||
// and apply hair(i==2) pattern over the texture
|
||||
if (!SkinTexture && bakeType == BakeType.Head && i == 2)
|
||||
{
|
||||
if (texture.Alpha != null)
|
||||
{
|
||||
for (int j = 0; j < texture.Alpha.Length; j++) texture.Alpha[j] = (byte)255;
|
||||
}
|
||||
MultiplyLayerFromAlpha(texture, LoadResourceLayer("head_hair.tga"));
|
||||
}
|
||||
|
||||
// Aply tint and alpha masks except for skin that has a texture
|
||||
// on layer 0 which always overrides other skin settings
|
||||
if (!(IsSkin && i == 0))
|
||||
{
|
||||
ApplyTint(texture, textures[i].Color);
|
||||
|
||||
// For hair bake, we skip all alpha masks
|
||||
// and use one from the texture, for both
|
||||
// alpha and morph layers
|
||||
if (bakeType == BakeType.Hair)
|
||||
{
|
||||
if (texture.Alpha != null)
|
||||
{
|
||||
bakedTexture.Image.Bump = texture.Alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < bakedTexture.Image.Bump.Length; j++) bakedTexture.Image.Bump[j] = byte.MaxValue;
|
||||
}
|
||||
}
|
||||
// Apply parametrized alpha masks
|
||||
else if (textures[i].AlphaMasks != null && textures[i].AlphaMasks.Count > 0)
|
||||
{
|
||||
// Combined mask for the layer, fully transparent to begin with
|
||||
ManagedImage combinedMask = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha);
|
||||
|
||||
int addedMasks = 0;
|
||||
|
||||
// First add mask in normal blend mode
|
||||
foreach (KeyValuePair<VisualAlphaParam, float> kvp in textures[i].AlphaMasks)
|
||||
{
|
||||
if (!MaskBelongsToBake(kvp.Key.TGAFile)) continue;
|
||||
|
||||
if (kvp.Key.MultiplyBlend == false && (kvp.Value > 0f || !kvp.Key.SkipIfZero))
|
||||
{
|
||||
ApplyAlpha(combinedMask, kvp.Key, kvp.Value);
|
||||
//File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA());
|
||||
addedMasks++;
|
||||
}
|
||||
}
|
||||
|
||||
// If there were no mask in normal blend mode make aplha fully opaque
|
||||
if (addedMasks == 0) for (int l = 0; l < combinedMask.Alpha.Length; l++) combinedMask.Alpha[l] = 255;
|
||||
|
||||
// Add masks in multiply blend mode
|
||||
foreach (KeyValuePair<VisualAlphaParam, float> kvp in textures[i].AlphaMasks)
|
||||
{
|
||||
if (!MaskBelongsToBake(kvp.Key.TGAFile)) continue;
|
||||
|
||||
if (kvp.Key.MultiplyBlend == true && (kvp.Value > 0f || !kvp.Key.SkipIfZero))
|
||||
{
|
||||
ApplyAlpha(combinedMask, kvp.Key, kvp.Value);
|
||||
//File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA());
|
||||
addedMasks++;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedMasks > 0)
|
||||
{
|
||||
// Apply combined alpha mask to the cloned texture
|
||||
AddAlpha(texture, combinedMask);
|
||||
}
|
||||
|
||||
// Is this layer used for morph mask? If it is, use its
|
||||
// alpha as the morth for the whole bake
|
||||
if (Textures[i].TextureIndex == AppearanceManager.MorphLayerForBakeType(bakeType))
|
||||
{
|
||||
bakedTexture.Image.Bump = texture.Alpha;
|
||||
}
|
||||
|
||||
//File.WriteAllBytes(bakeType + "-masked-texture-" + i + ".tga", texture.ExportTGA());
|
||||
}
|
||||
}
|
||||
|
||||
bool useAlpha = i == 0 && (BakeType == BakeType.Skirt || BakeType == BakeType.Hair);
|
||||
DrawLayer(texture, useAlpha);
|
||||
//File.WriteAllBytes(bakeType + "-layer-" + i + ".tga", texture.ExportTGA());
|
||||
}
|
||||
|
||||
// For head and tattoo, we add skin last
|
||||
if (IsSkin && bakeType == BakeType.Head)
|
||||
{
|
||||
ManagedImage texture;
|
||||
if (textures[0].Texture != null)
|
||||
{
|
||||
texture = textures[0].Texture.Image.Clone();
|
||||
if (texture.Width != bakeWidth || texture.Height != bakeHeight)
|
||||
{
|
||||
try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
|
||||
catch (Exception) { }
|
||||
}
|
||||
DrawLayer(texture, false);
|
||||
}
|
||||
|
||||
// Add head tattoo here (if available, order-dependant)
|
||||
if (textures.Count > 1 && textures[1].Texture != null)
|
||||
{
|
||||
texture = textures[1].Texture.Image.Clone();
|
||||
if (texture.Width != bakeWidth || texture.Height != bakeHeight)
|
||||
{
|
||||
try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
|
||||
catch (Exception) { }
|
||||
}
|
||||
DrawLayer(texture, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any alpha wearable textures to make parts of the avatar disappear
|
||||
if (alphaWearableTexture != null)
|
||||
{
|
||||
AddAlpha(bakedTexture.Image, alphaWearableTexture);
|
||||
}
|
||||
|
||||
// We are done, encode asset for finalized bake
|
||||
bakedTexture.Encode();
|
||||
//File.WriteAllBytes(bakeType + ".tga", bakedTexture.Image.ExportTGA());
|
||||
}
|
||||
|
||||
private static object ResourceSync = new object();
|
||||
|
||||
public static ManagedImage LoadResourceLayer(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap bitmap = null;
|
||||
lock (ResourceSync)
|
||||
{
|
||||
using (Stream stream = Helpers.GetResourceStream(fileName, Settings.RESOURCE_DIR))
|
||||
{
|
||||
bitmap = LoadTGAClass.LoadTGA(stream);
|
||||
}
|
||||
}
|
||||
if (bitmap == null)
|
||||
{
|
||||
Logger.Log(String.Format("Failed loading resource file: {0}", fileName), Helpers.LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ManagedImage image = new ManagedImage(bitmap);
|
||||
bitmap.Dispose();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(String.Format("Failed loading resource file: {0} ({1})", fileName, e.Message),
|
||||
Helpers.LogLevel.Error, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts avatar texture index (face) to Bake type
|
||||
/// </summary>
|
||||
/// <param name="index">Face number (AvatarTextureIndex)</param>
|
||||
/// <returns>BakeType, layer to which this texture belongs to</returns>
|
||||
public static BakeType BakeTypeFor(AvatarTextureIndex index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case AvatarTextureIndex.HeadBodypaint:
|
||||
return BakeType.Head;
|
||||
|
||||
case AvatarTextureIndex.UpperBodypaint:
|
||||
case AvatarTextureIndex.UpperGloves:
|
||||
case AvatarTextureIndex.UpperUndershirt:
|
||||
case AvatarTextureIndex.UpperShirt:
|
||||
case AvatarTextureIndex.UpperJacket:
|
||||
return BakeType.UpperBody;
|
||||
|
||||
case AvatarTextureIndex.LowerBodypaint:
|
||||
case AvatarTextureIndex.LowerUnderpants:
|
||||
case AvatarTextureIndex.LowerSocks:
|
||||
case AvatarTextureIndex.LowerShoes:
|
||||
case AvatarTextureIndex.LowerPants:
|
||||
case AvatarTextureIndex.LowerJacket:
|
||||
return BakeType.LowerBody;
|
||||
|
||||
case AvatarTextureIndex.EyesIris:
|
||||
return BakeType.Eyes;
|
||||
|
||||
case AvatarTextureIndex.Skirt:
|
||||
return BakeType.Skirt;
|
||||
|
||||
case AvatarTextureIndex.Hair:
|
||||
return BakeType.Hair;
|
||||
|
||||
default:
|
||||
return BakeType.Unknown;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private layer compositing methods
|
||||
|
||||
private bool MaskBelongsToBake(string mask)
|
||||
{
|
||||
if ((bakeType == BakeType.LowerBody && mask.Contains("upper"))
|
||||
|| (bakeType == BakeType.LowerBody && mask.Contains("shirt"))
|
||||
|| (bakeType == BakeType.UpperBody && mask.Contains("lower")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool DrawLayer(ManagedImage source, bool addSourceAlpha)
|
||||
{
|
||||
if (source == null) return false;
|
||||
|
||||
bool sourceHasColor;
|
||||
bool sourceHasAlpha;
|
||||
bool sourceHasBump;
|
||||
int i = 0;
|
||||
|
||||
sourceHasColor = ((source.Channels & ManagedImage.ImageChannels.Color) != 0 &&
|
||||
source.Red != null && source.Green != null && source.Blue != null);
|
||||
sourceHasAlpha = ((source.Channels & ManagedImage.ImageChannels.Alpha) != 0 && source.Alpha != null);
|
||||
sourceHasBump = ((source.Channels & ManagedImage.ImageChannels.Bump) != 0 && source.Bump != null);
|
||||
|
||||
addSourceAlpha = (addSourceAlpha && sourceHasAlpha);
|
||||
|
||||
byte alpha = Byte.MaxValue;
|
||||
byte alphaInv = (byte)(Byte.MaxValue - alpha);
|
||||
|
||||
byte[] bakedRed = bakedTexture.Image.Red;
|
||||
byte[] bakedGreen = bakedTexture.Image.Green;
|
||||
byte[] bakedBlue = bakedTexture.Image.Blue;
|
||||
byte[] bakedAlpha = bakedTexture.Image.Alpha;
|
||||
byte[] bakedBump = bakedTexture.Image.Bump;
|
||||
|
||||
byte[] sourceRed = source.Red;
|
||||
byte[] sourceGreen = source.Green;
|
||||
byte[] sourceBlue = source.Blue;
|
||||
byte[] sourceAlpha = sourceHasAlpha ? source.Alpha : null;
|
||||
byte[] sourceBump = sourceHasBump ? source.Bump : null;
|
||||
|
||||
for (int y = 0; y < bakeHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < bakeWidth; x++)
|
||||
{
|
||||
if (sourceHasAlpha)
|
||||
{
|
||||
alpha = sourceAlpha[i];
|
||||
alphaInv = (byte)(Byte.MaxValue - alpha);
|
||||
}
|
||||
|
||||
if (sourceHasColor)
|
||||
{
|
||||
bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8);
|
||||
bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8);
|
||||
bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8);
|
||||
}
|
||||
|
||||
if (addSourceAlpha)
|
||||
{
|
||||
if (sourceAlpha[i] < bakedAlpha[i])
|
||||
{
|
||||
bakedAlpha[i] = sourceAlpha[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceHasBump)
|
||||
bakedBump[i] = sourceBump[i];
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure images exist, resize source if needed to match the destination
|
||||
/// </summary>
|
||||
/// <param name="dest">Destination image</param>
|
||||
/// <param name="src">Source image</param>
|
||||
/// <returns>Sanitization was succefull</returns>
|
||||
private bool SanitizeLayers(ManagedImage dest, ManagedImage src)
|
||||
{
|
||||
if (dest == null || src == null) return false;
|
||||
|
||||
if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0)
|
||||
{
|
||||
dest.ConvertChannels(dest.Channels | ManagedImage.ImageChannels.Alpha);
|
||||
}
|
||||
|
||||
if (dest.Width != src.Width || dest.Height != src.Height)
|
||||
{
|
||||
try { src.ResizeNearestNeighbor(dest.Width, dest.Height); }
|
||||
catch (Exception) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void ApplyAlpha(ManagedImage dest, VisualAlphaParam param, float val)
|
||||
{
|
||||
ManagedImage src = LoadResourceLayer(param.TGAFile);
|
||||
|
||||
if (dest == null || src == null || src.Alpha == null) return;
|
||||
|
||||
if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0)
|
||||
{
|
||||
dest.ConvertChannels(ManagedImage.ImageChannels.Alpha | dest.Channels);
|
||||
}
|
||||
|
||||
if (dest.Width != src.Width || dest.Height != src.Height)
|
||||
{
|
||||
try { src.ResizeNearestNeighbor(dest.Width, dest.Height); }
|
||||
catch (Exception) { return; }
|
||||
}
|
||||
|
||||
for (int i = 0; i < dest.Alpha.Length; i++)
|
||||
{
|
||||
byte alpha = src.Alpha[i] <= ((1 - val) * 255) ? (byte)0 : (byte)255;
|
||||
if (alpha != 255)
|
||||
{
|
||||
}
|
||||
if (param.MultiplyBlend)
|
||||
{
|
||||
dest.Alpha[i] = (byte)((dest.Alpha[i] * alpha) >> 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alpha > dest.Alpha[i])
|
||||
{
|
||||
dest.Alpha[i] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAlpha(ManagedImage dest, ManagedImage src)
|
||||
{
|
||||
if (!SanitizeLayers(dest, src)) return;
|
||||
|
||||
for (int i = 0; i < dest.Alpha.Length; i++)
|
||||
{
|
||||
if (src.Alpha[i] < dest.Alpha[i])
|
||||
{
|
||||
dest.Alpha[i] = src.Alpha[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MultiplyLayerFromAlpha(ManagedImage dest, ManagedImage src)
|
||||
{
|
||||
if (!SanitizeLayers(dest, src)) return;
|
||||
|
||||
for (int i = 0; i < dest.Red.Length; i++)
|
||||
{
|
||||
dest.Red[i] = (byte)((dest.Red[i] * src.Alpha[i]) >> 8);
|
||||
dest.Green[i] = (byte)((dest.Green[i] * src.Alpha[i]) >> 8);
|
||||
dest.Blue[i] = (byte)((dest.Blue[i] * src.Alpha[i]) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTint(ManagedImage dest, Color4 src)
|
||||
{
|
||||
if (dest == null) return;
|
||||
|
||||
for (int i = 0; i < dest.Red.Length; i++)
|
||||
{
|
||||
dest.Red[i] = (byte)((dest.Red[i] * ((byte)(src.R * byte.MaxValue))) >> 8);
|
||||
dest.Green[i] = (byte)((dest.Green[i] * ((byte)(src.G * byte.MaxValue))) >> 8);
|
||||
dest.Blue[i] = (byte)((dest.Blue[i] * ((byte)(src.B * byte.MaxValue))) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a baked layer as a solid *appearing* color. The colors are
|
||||
/// subtly dithered on a 16x16 grid to prevent the JPEG2000 stage from
|
||||
/// compressing it too far since it seems to cause upload failures if
|
||||
/// the image is a pure solid color
|
||||
/// </summary>
|
||||
/// <param name="color">Color of the base of this layer</param>
|
||||
private void InitBakedLayerColor(Color4 color)
|
||||
{
|
||||
InitBakedLayerColor(color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a baked layer as a solid *appearing* color. The colors are
|
||||
/// subtly dithered on a 16x16 grid to prevent the JPEG2000 stage from
|
||||
/// compressing it too far since it seems to cause upload failures if
|
||||
/// the image is a pure solid color
|
||||
/// </summary>
|
||||
/// <param name="r">Red value</param>
|
||||
/// <param name="g">Green value</param>
|
||||
/// <param name="b">Blue value</param>
|
||||
private void InitBakedLayerColor(float r, float g, float b)
|
||||
{
|
||||
byte rByte = Utils.FloatToByte(r, 0f, 1f);
|
||||
byte gByte = Utils.FloatToByte(g, 0f, 1f);
|
||||
byte bByte = Utils.FloatToByte(b, 0f, 1f);
|
||||
|
||||
byte rAlt, gAlt, bAlt;
|
||||
|
||||
rAlt = rByte;
|
||||
gAlt = gByte;
|
||||
bAlt = bByte;
|
||||
|
||||
if (rByte < Byte.MaxValue)
|
||||
rAlt++;
|
||||
else rAlt--;
|
||||
|
||||
if (gByte < Byte.MaxValue)
|
||||
gAlt++;
|
||||
else gAlt--;
|
||||
|
||||
if (bByte < Byte.MaxValue)
|
||||
bAlt++;
|
||||
else bAlt--;
|
||||
|
||||
int i = 0;
|
||||
|
||||
byte[] red = bakedTexture.Image.Red;
|
||||
byte[] green = bakedTexture.Image.Green;
|
||||
byte[] blue = bakedTexture.Image.Blue;
|
||||
byte[] alpha = bakedTexture.Image.Alpha;
|
||||
byte[] bump = bakedTexture.Image.Bump;
|
||||
|
||||
for (int y = 0; y < bakeHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < bakeWidth; x++)
|
||||
{
|
||||
if (((x ^ y) & 0x10) == 0)
|
||||
{
|
||||
red[i] = rAlt;
|
||||
green[i] = gByte;
|
||||
blue[i] = bByte;
|
||||
alpha[i] = Byte.MaxValue;
|
||||
bump[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
red[i] = rByte;
|
||||
green[i] = gAlt;
|
||||
blue[i] = bAlt;
|
||||
alpha[i] = Byte.MaxValue;
|
||||
bump[i] = 0;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image width
|
||||
/// </summary>
|
||||
public int Width;
|
||||
|
||||
/// <summary>
|
||||
/// Image height
|
||||
/// </summary>
|
||||
public int Height;
|
||||
|
||||
/// <summary>
|
||||
/// Image channel flags
|
||||
/// </summary>
|
||||
public ImageChannels Channels;
|
||||
|
||||
/// <summary>
|
||||
/// Red channel data
|
||||
/// </summary>
|
||||
public byte[] Red;
|
||||
|
||||
/// <summary>
|
||||
/// Green channel data
|
||||
/// </summary>
|
||||
public byte[] Green;
|
||||
|
||||
/// <summary>
|
||||
/// Blue channel data
|
||||
/// </summary>
|
||||
public byte[] Blue;
|
||||
|
||||
/// <summary>
|
||||
/// Alpha channel data
|
||||
/// </summary>
|
||||
public byte[] Alpha;
|
||||
|
||||
/// <summary>
|
||||
/// Bump channel data
|
||||
/// </summary>
|
||||
public byte[] Bump;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new blank image
|
||||
/// </summary>
|
||||
/// <param name="width">width</param>
|
||||
/// <param name="height">height</param>
|
||||
/// <param name="channels">channel flags</param>
|
||||
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
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bitmap"></param>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Convert the channels in the image. Channels are created or destroyed as required.
|
||||
/// </summary>
|
||||
/// <param name="channels">new channel flags</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize or stretch the image using nearest neighbor (ugly) resampling
|
||||
/// </summary>
|
||||
/// <param name="width">new width</param>
|
||||
/// <param name="height">new height</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a byte array containing 32-bit RGBA data with a bottom-left
|
||||
/// origin, suitable for feeding directly into OpenGL
|
||||
/// </summary>
|
||||
/// <returns>A byte array containing raw texture data</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a byte array containing 32-bit RGBA data with a bottom-left
|
||||
/// origin, suitable for feeding directly into OpenGL
|
||||
/// </summary>
|
||||
/// <returns>A byte array containing raw texture data</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* 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;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenMetaverse.Imaging
|
||||
{
|
||||
#if !NO_UNSAFE
|
||||
/// <summary>
|
||||
/// A Wrapper around openjpeg to encode and decode images to and from byte arrays
|
||||
/// </summary>
|
||||
public class OpenJPEG
|
||||
{
|
||||
/// <summary>TGA Header size</summary>
|
||||
public const int TGA_HEADER_SIZE = 32;
|
||||
|
||||
#region JPEG2000 Structs
|
||||
|
||||
/// <summary>
|
||||
/// Defines the beginning and ending file positions of a layer in an
|
||||
/// LRCP-progression JPEG2000 file
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerDisplay("Start = {Start} End = {End} Size = {End - Start}")]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct J2KLayerInfo
|
||||
{
|
||||
public int Start;
|
||||
public int End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This structure is used to marshal both encoded and decoded images.
|
||||
/// MUST MATCH THE STRUCT IN dotnet.h!
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct MarshalledImage
|
||||
{
|
||||
public IntPtr encoded; // encoded image data
|
||||
public int length; // encoded image length
|
||||
public int dummy; // padding for 64-bit alignment
|
||||
|
||||
public IntPtr decoded; // decoded image, contiguous components
|
||||
|
||||
public int width; // width of decoded image
|
||||
public int height; // height of decoded image
|
||||
public int layers; // layer count
|
||||
public int resolutions; // resolution count
|
||||
public int components; // component count
|
||||
public int packet_count; // packet count
|
||||
public IntPtr packets; // pointer to the packets array
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a single packet in a JPEG2000 stream
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct MarshalledPacket
|
||||
{
|
||||
/// <summary>Packet start position</summary>
|
||||
public int start_pos;
|
||||
/// <summary>Packet header end position</summary>
|
||||
public int end_ph_pos;
|
||||
/// <summary>Packet end position</summary>
|
||||
public int end_pos;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("start_pos: {0} end_ph_pos: {1} end_pos: {2}",
|
||||
start_pos, end_ph_pos, end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion JPEG2000 Structs
|
||||
|
||||
#region Unmanaged Function Declarations
|
||||
|
||||
|
||||
// allocate encoded buffer based on length field
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocEncoded(ref MarshalledImage image);
|
||||
|
||||
// allocate decoded buffer based on width and height fields
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocDecoded(ref MarshalledImage image);
|
||||
|
||||
// free buffers
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetFree(ref MarshalledImage image);
|
||||
|
||||
// encode raw to jpeg2000
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetEncode(ref MarshalledImage image, bool lossless);
|
||||
|
||||
// decode jpeg2000 to raw
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecode(ref MarshalledImage image);
|
||||
|
||||
// decode jpeg2000 to raw, get jpeg2000 file info
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecodeWithInfo(ref MarshalledImage image);
|
||||
|
||||
// invoke 64 bit openjpeg calls
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocEncoded64(ref MarshalledImage image);
|
||||
|
||||
// allocate decoded buffer based on width and height fields
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocDecoded64(ref MarshalledImage image);
|
||||
|
||||
// free buffers
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetFree64(ref MarshalledImage image);
|
||||
|
||||
// encode raw to jpeg2000
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetEncode64(ref MarshalledImage image, bool lossless);
|
||||
|
||||
// decode jpeg2000 to raw
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecode64(ref MarshalledImage image);
|
||||
|
||||
// decode jpeg2000 to raw, get jpeg2000 file info
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecodeWithInfo64(ref MarshalledImage image);
|
||||
#endregion Unmanaged Function Declarations
|
||||
|
||||
/// <summary>OpenJPEG is not threadsafe, so this object is used to lock
|
||||
/// during calls into unmanaged code</summary>
|
||||
private static object OpenJPEGLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Encode a <seealso cref="ManagedImage"/> object into a byte array
|
||||
/// </summary>
|
||||
/// <param name="image">The <seealso cref="ManagedImage"/> object to encode</param>
|
||||
/// <param name="lossless">true to enable lossless conversion, only useful for small images ie: sculptmaps</param>
|
||||
/// <returns>A byte array containing the encoded Image object</returns>
|
||||
public static byte[] Encode(ManagedImage image, bool lossless)
|
||||
{
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Color) == 0 ||
|
||||
((image.Channels & ManagedImage.ImageChannels.Bump) != 0 && (image.Channels & ManagedImage.ImageChannels.Alpha) == 0))
|
||||
throw new ArgumentException("JPEG2000 encoding is not supported for this channel combination");
|
||||
|
||||
byte[] encoded = null;
|
||||
MarshalledImage marshalled = new MarshalledImage();
|
||||
|
||||
// allocate and copy to input buffer
|
||||
marshalled.width = image.Width;
|
||||
marshalled.height = image.Height;
|
||||
marshalled.components = 3;
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Alpha) != 0) marshalled.components++;
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Bump) != 0) marshalled.components++;
|
||||
|
||||
lock (OpenJPEGLock)
|
||||
{
|
||||
|
||||
bool allocSuccess = (IntPtr.Size == 8) ? DotNetAllocDecoded64(ref marshalled) : DotNetAllocDecoded(ref marshalled);
|
||||
|
||||
if (!allocSuccess)
|
||||
throw new Exception("DotNetAllocDecoded failed");
|
||||
|
||||
int n = image.Width * image.Height;
|
||||
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Color) != 0)
|
||||
{
|
||||
Marshal.Copy(image.Red, 0, marshalled.decoded, n);
|
||||
Marshal.Copy(image.Green, 0, (IntPtr)(marshalled.decoded.ToInt64() + n), n);
|
||||
Marshal.Copy(image.Blue, 0, (IntPtr)(marshalled.decoded.ToInt64() + n * 2), n);
|
||||
}
|
||||
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Alpha) != 0) Marshal.Copy(image.Alpha, 0, (IntPtr)(marshalled.decoded.ToInt64() + n * 3), n);
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Bump) != 0) Marshal.Copy(image.Bump, 0, (IntPtr)(marshalled.decoded.ToInt64() + n * 4), n);
|
||||
|
||||
// codec will allocate output buffer
|
||||
bool encodeSuccess = (IntPtr.Size == 8) ? DotNetEncode64(ref marshalled, lossless) : DotNetEncode(ref marshalled, lossless);
|
||||
if (!encodeSuccess)
|
||||
throw new Exception("DotNetEncode failed");
|
||||
|
||||
// copy output buffer
|
||||
encoded = new byte[marshalled.length];
|
||||
Marshal.Copy(marshalled.encoded, encoded, 0, marshalled.length);
|
||||
|
||||
// free buffers
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a <seealso cref="ManagedImage"/> object into a byte array
|
||||
/// </summary>
|
||||
/// <param name="image">The <seealso cref="ManagedImage"/> object to encode</param>
|
||||
/// <returns>a byte array of the encoded image</returns>
|
||||
public static byte[] Encode(ManagedImage image)
|
||||
{
|
||||
return Encode(image, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode JPEG2000 data to an <seealso cref="System.Drawing.Image"/> and
|
||||
/// <seealso cref="ManagedImage"/>
|
||||
/// </summary>
|
||||
/// <param name="encoded">JPEG2000 encoded data</param>
|
||||
/// <param name="managedImage">ManagedImage object to decode to</param>
|
||||
/// <param name="image">Image object to decode to</param>
|
||||
/// <returns>True if the decode succeeds, otherwise false</returns>
|
||||
public static bool DecodeToImage(byte[] encoded, out ManagedImage managedImage, out Image image)
|
||||
{
|
||||
managedImage = null;
|
||||
image = null;
|
||||
|
||||
if (DecodeToImage(encoded, out managedImage))
|
||||
{
|
||||
try
|
||||
{
|
||||
image = managedImage.ExportBitmap();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed to export and load TGA data from decoded image", Helpers.LogLevel.Error, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="encoded"></param>
|
||||
/// <param name="managedImage"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DecodeToImage(byte[] encoded, out ManagedImage managedImage)
|
||||
{
|
||||
MarshalledImage marshalled = new MarshalledImage();
|
||||
|
||||
// Allocate and copy to input buffer
|
||||
marshalled.length = encoded.Length;
|
||||
|
||||
lock (OpenJPEGLock)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetAllocEncoded64(ref marshalled);
|
||||
else
|
||||
DotNetAllocEncoded(ref marshalled);
|
||||
|
||||
Marshal.Copy(encoded, 0, marshalled.encoded, encoded.Length);
|
||||
|
||||
// Codec will allocate output buffer
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetDecode64(ref marshalled);
|
||||
else
|
||||
DotNetDecode(ref marshalled);
|
||||
|
||||
int n = marshalled.width * marshalled.height;
|
||||
|
||||
switch (marshalled.components)
|
||||
{
|
||||
case 1: // Grayscale
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Green, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Blue, 0, n);
|
||||
break;
|
||||
|
||||
case 2: // Grayscale + alpha
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Green, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Blue, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Alpha, 0, n);
|
||||
break;
|
||||
|
||||
case 3: // RGB
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Green, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 2)), managedImage.Blue, 0, n);
|
||||
break;
|
||||
|
||||
case 4: // RGBA
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Green, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 2)), managedImage.Blue, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 3)), managedImage.Alpha, 0, n);
|
||||
break;
|
||||
|
||||
case 5: // RGBAB
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Green, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 2)), managedImage.Blue, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 3)), managedImage.Alpha, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 4)), managedImage.Bump, 0, n);
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger.Log("Decoded image with unhandled number of components: " + marshalled.components,
|
||||
Helpers.LogLevel.Error);
|
||||
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
|
||||
managedImage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="encoded"></param>
|
||||
/// <param name="layerInfo"></param>
|
||||
/// <param name="components"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DecodeLayerBoundaries(byte[] encoded, out J2KLayerInfo[] layerInfo, out int components)
|
||||
{
|
||||
bool success = false;
|
||||
layerInfo = null;
|
||||
components = 0;
|
||||
MarshalledImage marshalled = new MarshalledImage();
|
||||
|
||||
// Allocate and copy to input buffer
|
||||
marshalled.length = encoded.Length;
|
||||
|
||||
lock (OpenJPEGLock)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetAllocEncoded64(ref marshalled);
|
||||
else
|
||||
DotNetAllocEncoded(ref marshalled);
|
||||
|
||||
Marshal.Copy(encoded, 0, marshalled.encoded, encoded.Length);
|
||||
|
||||
// Run the decode
|
||||
bool decodeSuccess = (IntPtr.Size == 8) ? DotNetDecodeWithInfo64(ref marshalled) : DotNetDecodeWithInfo(ref marshalled);
|
||||
if (decodeSuccess)
|
||||
{
|
||||
components = marshalled.components;
|
||||
|
||||
// Sanity check
|
||||
if (marshalled.layers * marshalled.resolutions * marshalled.components == marshalled.packet_count)
|
||||
{
|
||||
// Manually marshal the array of opj_packet_info structs
|
||||
MarshalledPacket[] packets = new MarshalledPacket[marshalled.packet_count];
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < marshalled.packet_count; i++)
|
||||
{
|
||||
MarshalledPacket packet;
|
||||
packet.start_pos = Marshal.ReadInt32(marshalled.packets, offset);
|
||||
offset += 4;
|
||||
packet.end_ph_pos = Marshal.ReadInt32(marshalled.packets, offset);
|
||||
offset += 4;
|
||||
packet.end_pos = Marshal.ReadInt32(marshalled.packets, offset);
|
||||
offset += 4;
|
||||
//double distortion = (double)Marshal.ReadInt64(marshalled.packets, offset);
|
||||
offset += 8;
|
||||
|
||||
packets[i] = packet;
|
||||
}
|
||||
|
||||
layerInfo = new J2KLayerInfo[marshalled.layers];
|
||||
|
||||
for (int i = 0; i < marshalled.layers; i++)
|
||||
{
|
||||
int packetsPerLayer = marshalled.packet_count / marshalled.layers;
|
||||
MarshalledPacket startPacket = packets[packetsPerLayer * i];
|
||||
MarshalledPacket endPacket = packets[(packetsPerLayer * (i + 1)) - 1];
|
||||
layerInfo[i].Start = startPacket.start_pos;
|
||||
layerInfo[i].End = endPacket.end_pos;
|
||||
}
|
||||
|
||||
// More sanity checking
|
||||
if (layerInfo.Length == 0 || layerInfo[layerInfo.Length - 1].End <= encoded.Length - 1)
|
||||
{
|
||||
success = true;
|
||||
|
||||
for (int i = 0; i < layerInfo.Length; i++)
|
||||
{
|
||||
if (layerInfo[i].Start >= layerInfo[i].End ||
|
||||
(i > 0 && layerInfo[i].Start <= layerInfo[i - 1].End))
|
||||
{
|
||||
System.Text.StringBuilder output = new System.Text.StringBuilder(
|
||||
"Inconsistent packet data in JPEG2000 stream:\n");
|
||||
for (int j = 0; j < layerInfo.Length; j++)
|
||||
output.AppendFormat("Layer {0}: Start: {1} End: {2}\n", j, layerInfo[j].Start, layerInfo[j].End);
|
||||
Logger.DebugLog(output.ToString());
|
||||
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
for (int i = 0; i < layerInfo.Length; i++)
|
||||
{
|
||||
if (i < layerInfo.Length - 1)
|
||||
layerInfo[i].End = layerInfo[i + 1].Start - 1;
|
||||
else
|
||||
layerInfo[i].End = marshalled.length;
|
||||
}
|
||||
|
||||
Logger.DebugLog("Corrected JPEG2000 packet data");
|
||||
success = true;
|
||||
|
||||
for (int i = 0; i < layerInfo.Length; i++)
|
||||
{
|
||||
if (layerInfo[i].Start >= layerInfo[i].End ||
|
||||
(i > 0 && layerInfo[i].Start <= layerInfo[i - 1].End))
|
||||
{
|
||||
System.Text.StringBuilder output = new System.Text.StringBuilder(
|
||||
"Still inconsistent packet data in JPEG2000 stream, giving up:\n");
|
||||
for (int j = 0; j < layerInfo.Length; j++)
|
||||
output.AppendFormat("Layer {0}: Start: {1} End: {2}\n", j, layerInfo[j].Start, layerInfo[j].End);
|
||||
Logger.DebugLog(output.ToString());
|
||||
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(String.Format(
|
||||
"Last packet end in JPEG2000 stream extends beyond the end of the file. filesize={0} layerend={1}",
|
||||
encoded.Length, layerInfo[layerInfo.Length - 1].End), Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(String.Format(
|
||||
"Packet count mismatch in JPEG2000 stream. layers={0} resolutions={1} components={2} packets={3}",
|
||||
marshalled.layers, marshalled.resolutions, marshalled.components, marshalled.packet_count),
|
||||
Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a <seealso cref="System.Drawing.Bitmap"/> object into a byte array
|
||||
/// </summary>
|
||||
/// <param name="bitmap">The source <seealso cref="System.Drawing.Bitmap"/> object to encode</param>
|
||||
/// <param name="lossless">true to enable lossless decoding</param>
|
||||
/// <returns>A byte array containing the source Bitmap object</returns>
|
||||
public unsafe static byte[] EncodeFromImage(Bitmap bitmap, bool lossless)
|
||||
{
|
||||
BitmapData bd;
|
||||
ManagedImage decoded;
|
||||
|
||||
int bitmapWidth = bitmap.Width;
|
||||
int bitmapHeight = bitmap.Height;
|
||||
int pixelCount = bitmapWidth * bitmapHeight;
|
||||
int i;
|
||||
|
||||
if ((bitmap.PixelFormat & PixelFormat.Alpha) != 0 || (bitmap.PixelFormat & PixelFormat.PAlpha) != 0)
|
||||
{
|
||||
// Four layers, RGBA
|
||||
decoded = new ManagedImage(bitmapWidth, bitmapHeight,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
bd = bitmap.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// GDI+ gives us BGRA and we need to turn that in to RGBA
|
||||
decoded.Blue[i] = *(pixel++);
|
||||
decoded.Green[i] = *(pixel++);
|
||||
decoded.Red[i] = *(pixel++);
|
||||
decoded.Alpha[i] = *(pixel++);
|
||||
}
|
||||
}
|
||||
else if (bitmap.PixelFormat == PixelFormat.Format16bppGrayScale)
|
||||
{
|
||||
// One layer
|
||||
decoded = new ManagedImage(bitmapWidth, bitmapHeight,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
bd = bitmap.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format16bppGrayScale);
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// Normalize 16-bit data down to 8-bit
|
||||
ushort origVal = (byte)(*(pixel) + (*(pixel + 1) << 8));
|
||||
byte val = (byte)(((double)origVal / (double)UInt32.MaxValue) * (double)Byte.MaxValue);
|
||||
|
||||
decoded.Red[i] = val;
|
||||
decoded.Green[i] = val;
|
||||
decoded.Blue[i] = val;
|
||||
pixel += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Three layers, RGB
|
||||
decoded = new ManagedImage(bitmapWidth, bitmapHeight,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
bd = bitmap.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (i = 0; i < pixelCount; i++)
|
||||
{
|
||||
decoded.Blue[i] = *(pixel++);
|
||||
decoded.Green[i] = *(pixel++);
|
||||
decoded.Red[i] = *(pixel++);
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bd);
|
||||
byte[] encoded = Encode(decoded, lossless);
|
||||
return encoded;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
#if !NO_UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// Capability to load TGAs to Bitmap
|
||||
/// </summary>
|
||||
public class LoadTGAClass
|
||||
{
|
||||
struct tgaColorMap
|
||||
{
|
||||
public ushort FirstEntryIndex;
|
||||
public ushort Length;
|
||||
public byte EntrySize;
|
||||
|
||||
public void Read(System.IO.BinaryReader br)
|
||||
{
|
||||
FirstEntryIndex = br.ReadUInt16();
|
||||
Length = br.ReadUInt16();
|
||||
EntrySize = br.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
struct tgaImageSpec
|
||||
{
|
||||
public ushort XOrigin;
|
||||
public ushort YOrigin;
|
||||
public ushort Width;
|
||||
public ushort Height;
|
||||
public byte PixelDepth;
|
||||
public byte Descriptor;
|
||||
|
||||
public void Read(System.IO.BinaryReader br)
|
||||
{
|
||||
XOrigin = br.ReadUInt16();
|
||||
YOrigin = br.ReadUInt16();
|
||||
Width = br.ReadUInt16();
|
||||
Height = br.ReadUInt16();
|
||||
PixelDepth = br.ReadByte();
|
||||
Descriptor = br.ReadByte();
|
||||
}
|
||||
|
||||
public byte AlphaBits
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)(Descriptor & 0xF);
|
||||
}
|
||||
set
|
||||
{
|
||||
Descriptor = (byte)((Descriptor & ~0xF) | (value & 0xF));
|
||||
}
|
||||
}
|
||||
|
||||
public bool BottomUp
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Descriptor & 0x20) == 0x20;
|
||||
}
|
||||
set
|
||||
{
|
||||
Descriptor = (byte)((Descriptor & ~0x20) | (value ? 0x20 : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct tgaHeader
|
||||
{
|
||||
public byte IdLength;
|
||||
public byte ColorMapType;
|
||||
public byte ImageType;
|
||||
|
||||
public tgaColorMap ColorMap;
|
||||
public tgaImageSpec ImageSpec;
|
||||
|
||||
public void Read(System.IO.BinaryReader br)
|
||||
{
|
||||
this.IdLength = br.ReadByte();
|
||||
this.ColorMapType = br.ReadByte();
|
||||
this.ImageType = br.ReadByte();
|
||||
this.ColorMap = new tgaColorMap();
|
||||
this.ImageSpec = new tgaImageSpec();
|
||||
this.ColorMap.Read(br);
|
||||
this.ImageSpec.Read(br);
|
||||
}
|
||||
|
||||
public bool RleEncoded
|
||||
{
|
||||
get
|
||||
{
|
||||
return ImageType >= 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct tgaCD
|
||||
{
|
||||
public uint RMask, GMask, BMask, AMask;
|
||||
public byte RShift, GShift, BShift, AShift;
|
||||
public uint FinalOr;
|
||||
public bool NeedNoConvert;
|
||||
}
|
||||
|
||||
static uint UnpackColor(
|
||||
uint sourceColor, ref tgaCD cd)
|
||||
{
|
||||
if (cd.RMask == 0xFF && cd.GMask == 0xFF && cd.BMask == 0xFF)
|
||||
{
|
||||
// Special case to deal with 8-bit TGA files that we treat as alpha masks
|
||||
return sourceColor << 24;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint rpermute = (sourceColor << cd.RShift) | (sourceColor >> (32 - cd.RShift));
|
||||
uint gpermute = (sourceColor << cd.GShift) | (sourceColor >> (32 - cd.GShift));
|
||||
uint bpermute = (sourceColor << cd.BShift) | (sourceColor >> (32 - cd.BShift));
|
||||
uint apermute = (sourceColor << cd.AShift) | (sourceColor >> (32 - cd.AShift));
|
||||
uint result =
|
||||
(rpermute & cd.RMask) | (gpermute & cd.GMask)
|
||||
| (bpermute & cd.BMask) | (apermute & cd.AMask) | cd.FinalOr;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static unsafe void decodeLine(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
int line,
|
||||
int byp,
|
||||
byte[] data,
|
||||
ref tgaCD cd)
|
||||
{
|
||||
if (cd.NeedNoConvert)
|
||||
{
|
||||
// fast copy
|
||||
uint* linep = (uint*)((byte*)b.Scan0.ToPointer() + line * b.Stride);
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
uint* sptr = (uint*)ptr;
|
||||
for (int i = 0; i < b.Width; ++i)
|
||||
{
|
||||
linep[i] = sptr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* linep = (byte*)b.Scan0.ToPointer() + line * b.Stride;
|
||||
|
||||
uint* up = (uint*)linep;
|
||||
|
||||
int rdi = 0;
|
||||
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
for (int i = 0; i < b.Width; ++i)
|
||||
{
|
||||
uint x = 0;
|
||||
for (int j = 0; j < byp; ++j)
|
||||
{
|
||||
x |= ((uint)ptr[rdi]) << (j << 3);
|
||||
++rdi;
|
||||
}
|
||||
up[i] = UnpackColor(x, ref cd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decodeRle(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp)
|
||||
{
|
||||
try
|
||||
{
|
||||
int w = b.Width;
|
||||
// make buffer larger, so in case of emergency I can decode
|
||||
// over line ends.
|
||||
byte[] linebuffer = new byte[(w + 128) * byp];
|
||||
int maxindex = w * byp;
|
||||
int index = 0;
|
||||
|
||||
for (int j = 0; j < b.Height; ++j)
|
||||
{
|
||||
while (index < maxindex)
|
||||
{
|
||||
byte blocktype = br.ReadByte();
|
||||
|
||||
int bytestoread;
|
||||
int bytestocopy;
|
||||
|
||||
if (blocktype >= 0x80)
|
||||
{
|
||||
bytestoread = byp;
|
||||
bytestocopy = byp * (blocktype - 0x80);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytestoread = byp * (blocktype + 1);
|
||||
bytestocopy = 0;
|
||||
}
|
||||
|
||||
//if (index + bytestoread > maxindex)
|
||||
// throw new System.ArgumentException ("Corrupt TGA");
|
||||
|
||||
br.Read(linebuffer, index, bytestoread);
|
||||
index += bytestoread;
|
||||
|
||||
for (int i = 0; i != bytestocopy; ++i)
|
||||
{
|
||||
linebuffer[index + i] = linebuffer[index + i - bytestoread];
|
||||
}
|
||||
index += bytestocopy;
|
||||
}
|
||||
if (!bottomUp)
|
||||
decodeLine(b, b.Height - j - 1, byp, linebuffer, ref cd);
|
||||
else
|
||||
decodeLine(b, j, byp, linebuffer, ref cd);
|
||||
|
||||
if (index > maxindex)
|
||||
{
|
||||
Array.Copy(linebuffer, maxindex, linebuffer, 0, index - maxindex);
|
||||
index -= maxindex;
|
||||
}
|
||||
else
|
||||
index = 0;
|
||||
|
||||
}
|
||||
}
|
||||
catch (System.IO.EndOfStreamException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
static void decodePlain(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp)
|
||||
{
|
||||
int w = b.Width;
|
||||
byte[] linebuffer = new byte[w * byp];
|
||||
|
||||
for (int j = 0; j < b.Height; ++j)
|
||||
{
|
||||
br.Read(linebuffer, 0, w * byp);
|
||||
|
||||
if (!bottomUp)
|
||||
decodeLine(b, b.Height - j - 1, byp, linebuffer, ref cd);
|
||||
else
|
||||
decodeLine(b, j, byp, linebuffer, ref cd);
|
||||
}
|
||||
}
|
||||
|
||||
static void decodeStandard8(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr,
|
||||
System.IO.BinaryReader br)
|
||||
{
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x000000ff;
|
||||
cd.GMask = 0x000000ff;
|
||||
cd.BMask = 0x000000ff;
|
||||
cd.AMask = 0x000000ff;
|
||||
cd.RShift = 0;
|
||||
cd.GShift = 0;
|
||||
cd.BShift = 0;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0x00000000;
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 1, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 1, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeSpecial16(
|
||||
System.Drawing.Imaging.BitmapData b, tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00f00000;
|
||||
cd.GMask = 0x0000f000;
|
||||
cd.BMask = 0x000000f0;
|
||||
cd.AMask = 0xf0000000;
|
||||
cd.RShift = 12;
|
||||
cd.GShift = 8;
|
||||
cd.BShift = 4;
|
||||
cd.AShift = 16;
|
||||
cd.FinalOr = 0;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeStandard16(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr,
|
||||
System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00f80000; // from 0xF800
|
||||
cd.GMask = 0x0000fc00; // from 0x07E0
|
||||
cd.BMask = 0x000000f8; // from 0x001F
|
||||
cd.AMask = 0x00000000;
|
||||
cd.RShift = 8;
|
||||
cd.GShift = 5;
|
||||
cd.BShift = 3;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0xff000000;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
|
||||
static void decodeSpecial24(System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00f80000;
|
||||
cd.GMask = 0x0000fc00;
|
||||
cd.BMask = 0x000000f8;
|
||||
cd.AMask = 0xff000000;
|
||||
cd.RShift = 8;
|
||||
cd.GShift = 5;
|
||||
cd.BShift = 3;
|
||||
cd.AShift = 8;
|
||||
cd.FinalOr = 0;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeStandard24(System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00ff0000;
|
||||
cd.GMask = 0x0000ff00;
|
||||
cd.BMask = 0x000000ff;
|
||||
cd.AMask = 0x00000000;
|
||||
cd.RShift = 0;
|
||||
cd.GShift = 0;
|
||||
cd.BShift = 0;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0xff000000;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeStandard32(System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00ff0000;
|
||||
cd.GMask = 0x0000ff00;
|
||||
cd.BMask = 0x000000ff;
|
||||
cd.AMask = 0xff000000;
|
||||
cd.RShift = 0;
|
||||
cd.GShift = 0;
|
||||
cd.BShift = 0;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0x00000000;
|
||||
cd.NeedNoConvert = true;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 4, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 4, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
|
||||
public static System.Drawing.Size GetTGASize(string filename)
|
||||
{
|
||||
System.IO.FileStream f = System.IO.File.OpenRead(filename);
|
||||
|
||||
System.IO.BinaryReader br = new System.IO.BinaryReader(f);
|
||||
|
||||
tgaHeader header = new tgaHeader();
|
||||
header.Read(br);
|
||||
br.Close();
|
||||
|
||||
return new System.Drawing.Size(header.ImageSpec.Width, header.ImageSpec.Height);
|
||||
|
||||
}
|
||||
|
||||
public static System.Drawing.Bitmap LoadTGA(System.IO.Stream source)
|
||||
{
|
||||
byte[] buffer = new byte[source.Length];
|
||||
source.Read(buffer, 0, buffer.Length);
|
||||
|
||||
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
|
||||
|
||||
using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
|
||||
{
|
||||
tgaHeader header = new tgaHeader();
|
||||
header.Read(br);
|
||||
|
||||
if (header.ImageSpec.PixelDepth != 8 &&
|
||||
header.ImageSpec.PixelDepth != 16 &&
|
||||
header.ImageSpec.PixelDepth != 24 &&
|
||||
header.ImageSpec.PixelDepth != 32)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.AlphaBits > 8)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.Width > 4096 ||
|
||||
header.ImageSpec.Height > 4096)
|
||||
throw new ArgumentException("Image too large.");
|
||||
|
||||
System.Drawing.Bitmap b;
|
||||
System.Drawing.Imaging.BitmapData bd;
|
||||
|
||||
// Create a bitmap for the image.
|
||||
// Only include an alpha layer when the image requires one.
|
||||
if (header.ImageSpec.AlphaBits > 0 ||
|
||||
header.ImageSpec.PixelDepth == 8 || // Assume 8 bit images are alpha only
|
||||
header.ImageSpec.PixelDepth == 32) // Assume 32 bit images are ARGB
|
||||
{ // Image needs an alpha layer
|
||||
b = new System.Drawing.Bitmap(
|
||||
header.ImageSpec.Width,
|
||||
header.ImageSpec.Height,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
|
||||
bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
|
||||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||||
}
|
||||
else
|
||||
{ // Image does not need an alpha layer, so do not include one.
|
||||
b = new System.Drawing.Bitmap(
|
||||
header.ImageSpec.Width,
|
||||
header.ImageSpec.Height,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
|
||||
bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
|
||||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
}
|
||||
|
||||
switch (header.ImageSpec.PixelDepth)
|
||||
{
|
||||
case 8:
|
||||
decodeStandard8(bd, header, br);
|
||||
break;
|
||||
case 16:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial16(bd, header, br);
|
||||
else
|
||||
decodeStandard16(bd, header, br);
|
||||
break;
|
||||
case 24:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial24(bd, header, br);
|
||||
else
|
||||
decodeStandard24(bd, header, br);
|
||||
break;
|
||||
case 32:
|
||||
decodeStandard32(bd, header, br);
|
||||
break;
|
||||
default:
|
||||
b.UnlockBits(bd);
|
||||
b.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
b.UnlockBits(bd);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe ManagedImage LoadTGAImage(System.IO.Stream source)
|
||||
{
|
||||
return LoadTGAImage(source, false);
|
||||
}
|
||||
|
||||
public static unsafe ManagedImage LoadTGAImage(System.IO.Stream source, bool mask)
|
||||
{
|
||||
byte[] buffer = new byte[source.Length];
|
||||
source.Read(buffer, 0, buffer.Length);
|
||||
|
||||
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
|
||||
|
||||
using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
|
||||
{
|
||||
tgaHeader header = new tgaHeader();
|
||||
header.Read(br);
|
||||
|
||||
if (header.ImageSpec.PixelDepth != 8 &&
|
||||
header.ImageSpec.PixelDepth != 16 &&
|
||||
header.ImageSpec.PixelDepth != 24 &&
|
||||
header.ImageSpec.PixelDepth != 32)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.AlphaBits > 8)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.Width > 4096 ||
|
||||
header.ImageSpec.Height > 4096)
|
||||
throw new ArgumentException("Image too large.");
|
||||
|
||||
byte[] decoded = new byte[header.ImageSpec.Width * header.ImageSpec.Height * 4];
|
||||
System.Drawing.Imaging.BitmapData bd = new System.Drawing.Imaging.BitmapData();
|
||||
|
||||
fixed (byte* pdecoded = &decoded[0])
|
||||
{
|
||||
bd.Width = header.ImageSpec.Width;
|
||||
bd.Height = header.ImageSpec.Height;
|
||||
bd.PixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppPArgb;
|
||||
bd.Stride = header.ImageSpec.Width * 4;
|
||||
bd.Scan0 = (IntPtr)pdecoded;
|
||||
|
||||
switch (header.ImageSpec.PixelDepth)
|
||||
{
|
||||
case 8:
|
||||
decodeStandard8(bd, header, br);
|
||||
break;
|
||||
case 16:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial16(bd, header, br);
|
||||
else
|
||||
decodeStandard16(bd, header, br);
|
||||
break;
|
||||
case 24:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial24(bd, header, br);
|
||||
else
|
||||
decodeStandard24(bd, header, br);
|
||||
break;
|
||||
case 32:
|
||||
decodeStandard32(bd, header, br);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int n = header.ImageSpec.Width * header.ImageSpec.Height;
|
||||
ManagedImage image;
|
||||
|
||||
if (mask && header.ImageSpec.AlphaBits == 0 && header.ImageSpec.PixelDepth == 8)
|
||||
{
|
||||
image = new ManagedImage(header.ImageSpec.Width, header.ImageSpec.Height,
|
||||
ManagedImage.ImageChannels.Alpha);
|
||||
int p = 3;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
image.Alpha[i] = decoded[p];
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
image = new ManagedImage(header.ImageSpec.Width, header.ImageSpec.Height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
int p = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
image.Blue[i] = decoded[p++];
|
||||
image.Green[i] = decoded[p++];
|
||||
image.Red[i] = decoded[p++];
|
||||
image.Alpha[i] = decoded[p++];
|
||||
}
|
||||
}
|
||||
|
||||
br.Close();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
public static System.Drawing.Bitmap LoadTGA(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (System.IO.FileStream f = System.IO.File.OpenRead(filename))
|
||||
{
|
||||
return LoadTGA(f);
|
||||
}
|
||||
}
|
||||
catch (System.IO.DirectoryNotFoundException)
|
||||
{
|
||||
return null; // file not found
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
return null; // file not found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user