diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs
index d16f9bf213..1c856af89e 100644
--- a/OpenSim/Framework/WebUtil.cs
+++ b/OpenSim/Framework/WebUtil.cs
@@ -26,6 +26,7 @@
*/
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
@@ -363,5 +364,85 @@ namespace OpenSim.Framework
}
#endregion Stream
+
+ public class QBasedComparer : IComparer
+ {
+ public int Compare(Object x, Object y)
+ {
+ float qx = GetQ(x);
+ float qy = GetQ(y);
+ if (qx < qy)
+ return -1;
+ if (qx == qy)
+ return 0;
+ return 1;
+ }
+
+ private float GetQ(Object o)
+ {
+ // Example: image/png;q=0.9
+
+ if (o is String)
+ {
+ string mime = (string)o;
+ string[] parts = mime.Split(new char[] { ';' });
+ if (parts.Length > 1)
+ {
+ string[] kvp = parts[1].Split(new char[] { '=' });
+ if (kvp.Length == 2 && kvp[0] == "q")
+ {
+ float qvalue = 1F;
+ float.TryParse(kvp[1], out qvalue);
+ return qvalue;
+ }
+ }
+ }
+
+ return 1F;
+ }
+ }
+
+ ///
+ /// Takes the value of an Accept header and returns the preferred types
+ /// ordered by q value (if it exists).
+ /// Example input: image/jpg;q=0.7, image/png;q=0.8, image/jp2
+ /// Exmaple output: ["jp2", "png", "jpg"]
+ /// NOTE: This doesn't handle the semantics of *'s...
+ ///
+ ///
+ ///
+ public static string[] GetPreferredImageTypes(string accept)
+ {
+
+ if (accept == null || accept == string.Empty)
+ return new string[0];
+
+ string[] types = accept.Split(new char[] { ',' });
+ if (types.Length > 0)
+ {
+ List list = new List(types);
+ list.RemoveAll(delegate(string s) { return !s.ToLower().StartsWith("image"); });
+ ArrayList tlist = new ArrayList(list);
+ tlist.Sort(new QBasedComparer());
+
+ string[] result = new string[tlist.Count];
+ for (int i = 0; i < tlist.Count; i++)
+ {
+ string mime = (string)tlist[i];
+ string[] parts = mime.Split(new char[] { ';' });
+ string[] pair = parts[0].Split(new char[] { '/' });
+ if (pair.Length == 2)
+ result[i] = pair[1].ToLower();
+ else // oops, we don't know what this is...
+ result[i] = pair[0];
+ }
+
+ return result;
+ }
+
+ return new string[0];
+ }
+
+
}
}
diff --git a/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs b/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs
index 97581e5206..a8da330364 100644
--- a/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs
+++ b/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs
@@ -28,6 +28,8 @@
using System;
using System.Collections;
using System.Collections.Specialized;
+using System.Drawing;
+using System.Drawing.Imaging;
using System.Reflection;
using System.IO;
using System.Web;
@@ -35,6 +37,7 @@ using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
+using OpenMetaverse.Imaging;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
@@ -74,6 +77,12 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
private Scene m_scene;
private IAssetService m_assetService;
+ public const string DefaultFormat = "x-j2c";
+
+ // TODO: Change this to a config option
+ const string REDIRECT_URL = null;
+
+
#region IRegionModule Members
public void Initialise(Scene pScene, IConfigSource pSource)
@@ -96,7 +105,7 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
{
UUID capID = UUID.Random();
- m_log.Info("[GETTEXTURE]: /CAPS/" + capID);
+ m_log.InfoFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName);
caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture));
}
@@ -104,12 +113,12 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
private byte[] ProcessGetTexture(string path, Stream request, OSHttpRequest httpRequest, OSHttpResponse httpResponse)
{
- // TODO: Change this to a config option
- const string REDIRECT_URL = null;
+ //m_log.DebugFormat("[GETTEXTURE]: called in {0}", m_scene.RegionInfo.RegionName);
// Try to parse the texture ID from the request URL
NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query);
string textureStr = query.GetOne("texture_id");
+ string format = query.GetOne("format");
if (m_assetService == null)
{
@@ -121,52 +130,27 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
UUID textureID;
if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID))
{
- //m_log.DebugFormat("[GETTEXTURE]: {0}", textureID);
- AssetBase texture;
-
- if (!String.IsNullOrEmpty(REDIRECT_URL))
+ string[] formats;
+ if (format != null && format != string.Empty)
{
- // Only try to fetch locally cached textures. Misses are redirected
- texture = m_assetService.GetCached(textureID.ToString());
-
- if (texture != null)
- {
- if (texture.Type != (sbyte)AssetType.Texture)
- {
- httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
- httpResponse.Send();
- return null;
- }
- SendTexture(httpRequest, httpResponse, texture);
- }
- else
- {
- string textureUrl = REDIRECT_URL + textureID.ToString();
- m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl);
- httpResponse.RedirectLocation = textureUrl;
- }
+ formats = new string[1] { format.ToLower() };
}
else
{
- // Fetch locally or remotely. Misses return a 404
- texture = m_assetService.Get(textureID.ToString());
+ formats = WebUtil.GetPreferredImageTypes(httpRequest.Headers.Get("Accept"));
+ if (formats.Length == 0)
+ formats = new string[1] { DefaultFormat }; // default
- if (texture != null)
- {
- if (texture.Type != (sbyte)AssetType.Texture)
- {
- httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
- httpResponse.Send();
- return null;
- }
- SendTexture(httpRequest, httpResponse, texture);
- }
- else
- {
- m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
- httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
- }
}
+ // OK, we have an array with preferred formats, possibly with only one entry
+
+ httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
+ foreach (string f in formats)
+ {
+ if (FetchTexture(httpRequest, httpResponse, textureID, f))
+ break;
+ }
+
}
else
{
@@ -177,11 +161,105 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
return null;
}
- private void SendTexture(OSHttpRequest request, OSHttpResponse response, AssetBase texture)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// False for "caller try another codec"; true otherwise
+ private bool FetchTexture(OSHttpRequest httpRequest, OSHttpResponse httpResponse, UUID textureID, string format)
+ {
+ m_log.DebugFormat("[GETTEXTURE]: {0} with requested format {1}", textureID, format);
+ AssetBase texture;
+
+ string fullID = textureID.ToString();
+ if (format != DefaultFormat)
+ fullID = fullID + "-" + format;
+
+ if (!String.IsNullOrEmpty(REDIRECT_URL))
+ {
+ // Only try to fetch locally cached textures. Misses are redirected
+ texture = m_assetService.GetCached(fullID);
+
+ if (texture != null)
+ {
+ if (texture.Type != (sbyte)AssetType.Texture)
+ {
+ httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
+ return true;
+ }
+ WriteTextureData(httpRequest, httpResponse, texture, format);
+ }
+ else
+ {
+ string textureUrl = REDIRECT_URL + textureID.ToString();
+ m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl);
+ httpResponse.RedirectLocation = textureUrl;
+ return true;
+ }
+ }
+ else // no redirect
+ {
+ // try the cache
+ texture = m_assetService.GetCached(fullID);
+
+ if (texture == null)
+ {
+ //m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
+
+ // Fetch locally or remotely. Misses return a 404
+ texture = m_assetService.Get(textureID.ToString());
+
+ if (texture != null)
+ {
+ if (texture.Type != (sbyte)AssetType.Texture)
+ {
+ httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
+ return true;
+ }
+ if (format == DefaultFormat)
+ {
+ WriteTextureData(httpRequest, httpResponse, texture, format);
+ return true;
+ }
+ else
+ {
+ AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
+ newTexture.Data = ConvertTextureData(texture, format);
+ if (newTexture.Data.Length == 0)
+ return false; // !!! Caller try another codec, please!
+
+ newTexture.Flags = AssetFlags.Collectable;
+ newTexture.Temporary = true;
+ m_assetService.Store(newTexture);
+ WriteTextureData(httpRequest, httpResponse, newTexture, format);
+ return true;
+ }
+ }
+ }
+ else // it was on the cache
+ {
+ //m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
+ WriteTextureData(httpRequest, httpResponse, texture, format);
+ return true;
+ }
+
+ }
+
+ // not found
+ m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
+ httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
+ return true;
+
+ }
+
+ private void WriteTextureData(OSHttpRequest request, OSHttpResponse response, AssetBase texture, string format)
{
string range = request.Headers.GetOne("Range");
//m_log.DebugFormat("[GETTEXTURE]: Range {0}", range);
- if (!String.IsNullOrEmpty(range))
+ if (!String.IsNullOrEmpty(range)) // JP2's only
{
// Range request
int start, end;
@@ -212,15 +290,19 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
}
else
{
- m_log.Warn("Malformed Range header: " + range);
+ m_log.Warn("[GETTEXTURE]: Malformed Range header: " + range);
response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
}
}
- else
+ else // JP2's or other formats
{
// Full content request
+ response.StatusCode = (int)System.Net.HttpStatusCode.OK;
response.ContentLength = texture.Data.Length;
- response.ContentType = texture.Metadata.ContentType;
+ if (format == DefaultFormat)
+ response.ContentType = texture.Metadata.ContentType;
+ else
+ response.ContentType = "image/" + format;
response.Body.Write(texture.Data, 0, texture.Data.Length);
}
}
@@ -240,5 +322,83 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
start = end = 0;
return false;
}
+
+
+ private byte[] ConvertTextureData(AssetBase texture, string format)
+ {
+ m_log.DebugFormat("[GETTEXTURE]: Converting texture {0} to {1}", texture.ID, format);
+ byte[] data = new byte[0];
+
+ MemoryStream imgstream = new MemoryStream();
+ Bitmap mTexture = new Bitmap(1, 1);
+ ManagedImage managedImage;
+ Image image = (Image)mTexture;
+
+ try
+ {
+ // Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular jpeg data
+
+ imgstream = new MemoryStream();
+
+ // Decode image to System.Drawing.Image
+ if (OpenJPEG.DecodeToImage(texture.Data, out managedImage, out image))
+ {
+ // Save to bitmap
+ mTexture = new Bitmap(image);
+
+ EncoderParameters myEncoderParameters = new EncoderParameters();
+ myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L);
+
+ // Save bitmap to stream
+ ImageCodecInfo codec = GetEncoderInfo("image/" + format);
+ if (codec != null)
+ {
+ mTexture.Save(imgstream, codec, myEncoderParameters);
+ // Write the stream to a byte array for output
+ data = imgstream.ToArray();
+ }
+ else
+ m_log.WarnFormat("[GETTEXTURE]: No such codec {0}", format);
+
+ }
+ }
+ catch (Exception e)
+ {
+ m_log.WarnFormat("[GETTEXTURE]: Unable to convert texture {0} to {1}: {2}", texture.ID, format, e.Message);
+ }
+ finally
+ {
+ // Reclaim memory, these are unmanaged resources
+ // If we encountered an exception, one or more of these will be null
+ if (mTexture != null)
+ mTexture.Dispose();
+
+ if (image != null)
+ image.Dispose();
+
+ if (imgstream != null)
+ {
+ imgstream.Close();
+ imgstream.Dispose();
+ }
+ }
+
+ return data;
+ }
+
+ // From msdn
+ private static ImageCodecInfo GetEncoderInfo(String mimeType)
+ {
+ ImageCodecInfo[] encoders;
+ encoders = ImageCodecInfo.GetImageEncoders();
+ for (int j = 0; j < encoders.Length; ++j)
+ {
+ if (encoders[j].MimeType == mimeType)
+ return encoders[j];
+ }
+ return null;
+ }
+
+
}
}
diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs
index 383d95f73d..f81030c746 100644
--- a/OpenSim/Region/Framework/Scenes/Scene.cs
+++ b/OpenSim/Region/Framework/Scenes/Scene.cs
@@ -4394,7 +4394,7 @@ namespace OpenSim.Region.Framework.Scenes
}
///
- ///
+ /// Perform the given action for each object
///
///
// public void ForEachObject(Action action)
diff --git a/OpenSim/Server/Handlers/Simulation/ObjectHandlers.cs b/OpenSim/Server/Handlers/Simulation/ObjectHandlers.cs
index 04ff83f9f3..984b843401 100644
--- a/OpenSim/Server/Handlers/Simulation/ObjectHandlers.cs
+++ b/OpenSim/Server/Handlers/Simulation/ObjectHandlers.cs
@@ -84,32 +84,43 @@ namespace OpenSim.Server.Handlers.Simulation
return responsedata;
}
- // Next, let's parse the verb
- string method = (string)request["http-method"];
- if (method.Equals("POST"))
+ try
{
- DoObjectPost(request, responsedata, regionID);
- return responsedata;
+ // Next, let's parse the verb
+ string method = (string)request["http-method"];
+ if (method.Equals("POST"))
+ {
+ DoObjectPost(request, responsedata, regionID);
+ return responsedata;
+ }
+ else if (method.Equals("PUT"))
+ {
+ DoObjectPut(request, responsedata, regionID);
+ return responsedata;
+ }
+ //else if (method.Equals("DELETE"))
+ //{
+ // DoObjectDelete(request, responsedata, agentID, action, regionHandle);
+ // return responsedata;
+ //}
+ else
+ {
+ m_log.InfoFormat("[OBJECT HANDLER]: method {0} not supported in object message", method);
+ responsedata["int_response_code"] = HttpStatusCode.MethodNotAllowed;
+ responsedata["str_response_string"] = "Method not allowed";
+
+ return responsedata;
+ }
}
- else if (method.Equals("PUT"))
+ catch (Exception e)
{
- DoObjectPut(request, responsedata, regionID);
- return responsedata;
- }
- //else if (method.Equals("DELETE"))
- //{
- // DoObjectDelete(request, responsedata, agentID, action, regionHandle);
- // return responsedata;
- //}
- else
- {
- m_log.InfoFormat("[OBJECT HANDLER]: method {0} not supported in object message", method);
- responsedata["int_response_code"] = HttpStatusCode.MethodNotAllowed;
- responsedata["str_response_string"] = "Mthod not allowed";
+ m_log.WarnFormat("[OBJECT HANDLER]: Caught exception {0}", e.StackTrace);
+ responsedata["int_response_code"] = HttpStatusCode.InternalServerError;
+ responsedata["str_response_string"] = "Internal server error";
return responsedata;
- }
+ }
}
protected void DoObjectPost(Hashtable request, Hashtable responsedata, UUID regionID)
diff --git a/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs b/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs
index d25a766362..c2f43cab49 100644
--- a/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs
+++ b/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs
@@ -562,7 +562,7 @@ namespace OpenSim.Services.Connectors.Simulation
protected virtual string ObjectPath()
{
- return "/object/";
+ return "object/";
}
public bool CreateObject(GridRegion destination, ISceneObject sog, bool isLocalCall)