/*
* 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
///
/// A Wrapper around openjpeg to encode and decode images to and from byte arrays
///
public class OpenJPEG
{
/// TGA Header size
public const int TGA_HEADER_SIZE = 32;
#region JPEG2000 Structs
///
/// Defines the beginning and ending file positions of a layer in an
/// LRCP-progression JPEG2000 file
///
[System.Diagnostics.DebuggerDisplay("Start = {Start} End = {End} Size = {End - Start}")]
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct J2KLayerInfo
{
public int Start;
public int End;
}
///
/// This structure is used to marshal both encoded and decoded images.
/// MUST MATCH THE STRUCT IN dotnet.h!
///
[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
}
///
/// Information about a single packet in a JPEG2000 stream
///
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct MarshalledPacket
{
/// Packet start position
public int start_pos;
/// Packet header end position
public int end_ph_pos;
/// Packet end position
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
/// OpenJPEG is not threadsafe, so this object is used to lock
/// during calls into unmanaged code
private static object OpenJPEGLock = new object();
///
/// Encode a object into a byte array
///
/// The object to encode
/// true to enable lossless conversion, only useful for small images ie: sculptmaps
/// A byte array containing the encoded Image object
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;
}
///
/// Encode a object into a byte array
///
/// The object to encode
/// a byte array of the encoded image
public static byte[] Encode(ManagedImage image)
{
return Encode(image, false);
}
///
/// Decode JPEG2000 data to an and
///
///
/// JPEG2000 encoded data
/// ManagedImage object to decode to
/// Image object to decode to
/// True if the decode succeeds, otherwise false
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;
}
}
///
///
///
///
///
///
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;
}
///
///
///
///
///
///
///
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;
}
///
/// Encode a object into a byte array
///
/// The source object to encode
/// true to enable lossless decoding
/// A byte array containing the source Bitmap object
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
}