aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDiva Canto2010-12-08 18:53:15 -0800
committerDiva Canto2010-12-08 18:53:15 -0800
commit1070cffcf905f77d694e140d0c9e978f5d0052c0 (patch)
tree76ae656390dd231d8c221ebff0891fe04782a400
parentAdded an exception handler on CreateObject handler, just in case there's an e... (diff)
downloadopensim-SC_OLD-1070cffcf905f77d694e140d0c9e978f5d0052c0.zip
opensim-SC_OLD-1070cffcf905f77d694e140d0c9e978f5d0052c0.tar.gz
opensim-SC_OLD-1070cffcf905f77d694e140d0c9e978f5d0052c0.tar.bz2
opensim-SC_OLD-1070cffcf905f77d694e140d0c9e978f5d0052c0.tar.xz
Added ability for GetTexture to serve multiple formats. The format may come as an extra query parameter in the URL format=<format> (this was tested and working) or it may come in the Accept header (code added, but not tested). The result of the conversion is placed in the asset cache, under the name <uuid>-<format>.
-rw-r--r--OpenSim/Framework/WebUtil.cs81
-rw-r--r--OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs244
2 files changed, 283 insertions, 42 deletions
diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs
index d16f9bf..1c856af 100644
--- a/OpenSim/Framework/WebUtil.cs
+++ b/OpenSim/Framework/WebUtil.cs
@@ -26,6 +26,7 @@
26 */ 26 */
27 27
28using System; 28using System;
29using System.Collections;
29using System.Collections.Generic; 30using System.Collections.Generic;
30using System.Collections.Specialized; 31using System.Collections.Specialized;
31using System.IO; 32using System.IO;
@@ -363,5 +364,85 @@ namespace OpenSim.Framework
363 } 364 }
364 365
365 #endregion Stream 366 #endregion Stream
367
368 public class QBasedComparer : IComparer
369 {
370 public int Compare(Object x, Object y)
371 {
372 float qx = GetQ(x);
373 float qy = GetQ(y);
374 if (qx < qy)
375 return -1;
376 if (qx == qy)
377 return 0;
378 return 1;
379 }
380
381 private float GetQ(Object o)
382 {
383 // Example: image/png;q=0.9
384
385 if (o is String)
386 {
387 string mime = (string)o;
388 string[] parts = mime.Split(new char[] { ';' });
389 if (parts.Length > 1)
390 {
391 string[] kvp = parts[1].Split(new char[] { '=' });
392 if (kvp.Length == 2 && kvp[0] == "q")
393 {
394 float qvalue = 1F;
395 float.TryParse(kvp[1], out qvalue);
396 return qvalue;
397 }
398 }
399 }
400
401 return 1F;
402 }
403 }
404
405 /// <summary>
406 /// Takes the value of an Accept header and returns the preferred types
407 /// ordered by q value (if it exists).
408 /// Example input: image/jpg;q=0.7, image/png;q=0.8, image/jp2
409 /// Exmaple output: ["jp2", "png", "jpg"]
410 /// NOTE: This doesn't handle the semantics of *'s...
411 /// </summary>
412 /// <param name="accept"></param>
413 /// <returns></returns>
414 public static string[] GetPreferredImageTypes(string accept)
415 {
416
417 if (accept == null || accept == string.Empty)
418 return new string[0];
419
420 string[] types = accept.Split(new char[] { ',' });
421 if (types.Length > 0)
422 {
423 List<string> list = new List<string>(types);
424 list.RemoveAll(delegate(string s) { return !s.ToLower().StartsWith("image"); });
425 ArrayList tlist = new ArrayList(list);
426 tlist.Sort(new QBasedComparer());
427
428 string[] result = new string[tlist.Count];
429 for (int i = 0; i < tlist.Count; i++)
430 {
431 string mime = (string)tlist[i];
432 string[] parts = mime.Split(new char[] { ';' });
433 string[] pair = parts[0].Split(new char[] { '/' });
434 if (pair.Length == 2)
435 result[i] = pair[1].ToLower();
436 else // oops, we don't know what this is...
437 result[i] = pair[0];
438 }
439
440 return result;
441 }
442
443 return new string[0];
444 }
445
446
366 } 447 }
367} 448}
diff --git a/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs b/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs
index 97581e5..ac2ad7a 100644
--- a/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs
+++ b/OpenSim/Region/CoreModules/Avatar/Assets/GetTextureModule.cs
@@ -28,6 +28,8 @@
28using System; 28using System;
29using System.Collections; 29using System.Collections;
30using System.Collections.Specialized; 30using System.Collections.Specialized;
31using System.Drawing;
32using System.Drawing.Imaging;
31using System.Reflection; 33using System.Reflection;
32using System.IO; 34using System.IO;
33using System.Web; 35using System.Web;
@@ -35,6 +37,7 @@ using log4net;
35using Nini.Config; 37using Nini.Config;
36using OpenMetaverse; 38using OpenMetaverse;
37using OpenMetaverse.StructuredData; 39using OpenMetaverse.StructuredData;
40using OpenMetaverse.Imaging;
38using OpenSim.Framework; 41using OpenSim.Framework;
39using OpenSim.Framework.Servers; 42using OpenSim.Framework.Servers;
40using OpenSim.Framework.Servers.HttpServer; 43using OpenSim.Framework.Servers.HttpServer;
@@ -74,6 +77,12 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
74 private Scene m_scene; 77 private Scene m_scene;
75 private IAssetService m_assetService; 78 private IAssetService m_assetService;
76 79
80 public const string DefaultFormat = "x-j2c";
81
82 // TODO: Change this to a config option
83 const string REDIRECT_URL = null;
84
85
77 #region IRegionModule Members 86 #region IRegionModule Members
78 87
79 public void Initialise(Scene pScene, IConfigSource pSource) 88 public void Initialise(Scene pScene, IConfigSource pSource)
@@ -96,7 +105,7 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
96 { 105 {
97 UUID capID = UUID.Random(); 106 UUID capID = UUID.Random();
98 107
99 m_log.Info("[GETTEXTURE]: /CAPS/" + capID); 108 m_log.InfoFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName);
100 caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture)); 109 caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture));
101 } 110 }
102 111
@@ -104,12 +113,12 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
104 113
105 private byte[] ProcessGetTexture(string path, Stream request, OSHttpRequest httpRequest, OSHttpResponse httpResponse) 114 private byte[] ProcessGetTexture(string path, Stream request, OSHttpRequest httpRequest, OSHttpResponse httpResponse)
106 { 115 {
107 // TODO: Change this to a config option 116 //m_log.DebugFormat("[GETTEXTURE]: called in {0}", m_scene.RegionInfo.RegionName);
108 const string REDIRECT_URL = null;
109 117
110 // Try to parse the texture ID from the request URL 118 // Try to parse the texture ID from the request URL
111 NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); 119 NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query);
112 string textureStr = query.GetOne("texture_id"); 120 string textureStr = query.GetOne("texture_id");
121 string format = query.GetOne("format");
113 122
114 if (m_assetService == null) 123 if (m_assetService == null)
115 { 124 {
@@ -121,33 +130,85 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
121 UUID textureID; 130 UUID textureID;
122 if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID)) 131 if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID))
123 { 132 {
124 //m_log.DebugFormat("[GETTEXTURE]: {0}", textureID); 133 string[] formats;
125 AssetBase texture; 134 if (format != null && format != string.Empty)
135 {
136 formats = new string[1] { format.ToLower() };
137 }
138 else
139 {
140 formats = WebUtil.GetPreferredImageTypes(httpRequest.Headers.Get("Accept"));
141 if (formats.Length == 0)
142 formats = new string[1] { DefaultFormat }; // default
126 143
127 if (!String.IsNullOrEmpty(REDIRECT_URL)) 144 }
145 // OK, we have an array with preferred formats, possibly with only one entry
146
147 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
148 foreach (string f in formats)
128 { 149 {
129 // Only try to fetch locally cached textures. Misses are redirected 150 if (FetchTexture(httpRequest, httpResponse, textureID, f))
130 texture = m_assetService.GetCached(textureID.ToString()); 151 break;
152 }
131 153
132 if (texture != null) 154 }
133 { 155 else
134 if (texture.Type != (sbyte)AssetType.Texture) 156 {
135 { 157 m_log.Warn("[GETTEXTURE]: Failed to parse a texture_id from GetTexture request: " + httpRequest.Url);
136 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; 158 }
137 httpResponse.Send(); 159
138 return null; 160 httpResponse.Send();
139 } 161 return null;
140 SendTexture(httpRequest, httpResponse, texture); 162 }
141 } 163
142 else 164 /// <summary>
165 ///
166 /// </summary>
167 /// <param name="httpRequest"></param>
168 /// <param name="httpResponse"></param>
169 /// <param name="textureID"></param>
170 /// <param name="format"></param>
171 /// <returns>False for "caller try another codec"; true otherwise</returns>
172 private bool FetchTexture(OSHttpRequest httpRequest, OSHttpResponse httpResponse, UUID textureID, string format)
173 {
174 m_log.DebugFormat("[GETTEXTURE]: {0} with requested format {1}", textureID, format);
175 AssetBase texture;
176
177 string fullID = textureID.ToString();
178 if (format != DefaultFormat)
179 fullID = fullID + "-" + format;
180
181 if (!String.IsNullOrEmpty(REDIRECT_URL))
182 {
183 // Only try to fetch locally cached textures. Misses are redirected
184 texture = m_assetService.GetCached(fullID);
185
186 if (texture != null)
187 {
188 if (texture.Type != (sbyte)AssetType.Texture)
143 { 189 {
144 string textureUrl = REDIRECT_URL + textureID.ToString(); 190 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
145 m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl); 191 return true;
146 httpResponse.RedirectLocation = textureUrl;
147 } 192 }
193 WriteTextureData(httpRequest, httpResponse, texture, format);
148 } 194 }
149 else 195 else
150 { 196 {
197 string textureUrl = REDIRECT_URL + textureID.ToString();
198 m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl);
199 httpResponse.RedirectLocation = textureUrl;
200 return true;
201 }
202 }
203 else // no redirect
204 {
205 // try the cache
206 texture = m_assetService.GetCached(fullID);
207
208 if (texture == null)
209 {
210 //m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
211
151 // Fetch locally or remotely. Misses return a 404 212 // Fetch locally or remotely. Misses return a 404
152 texture = m_assetService.Get(textureID.ToString()); 213 texture = m_assetService.Get(textureID.ToString());
153 214
@@ -156,32 +217,49 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
156 if (texture.Type != (sbyte)AssetType.Texture) 217 if (texture.Type != (sbyte)AssetType.Texture)
157 { 218 {
158 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; 219 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
159 httpResponse.Send(); 220 return true;
160 return null; 221 }
222 if (format == DefaultFormat)
223 {
224 WriteTextureData(httpRequest, httpResponse, texture, format);
225 return true;
226 }
227 else
228 {
229 AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.CreatorID);
230 newTexture.Data = ConvertTextureData(texture, format);
231 if (newTexture.Data.Length == 0)
232 return false; // !!! Caller try another codec, please!
233
234 newTexture.Flags = AssetFlags.Collectable;
235 newTexture.Temporary = true;
236 m_assetService.Store(newTexture);
237 WriteTextureData(httpRequest, httpResponse, newTexture, format);
238 return true;
161 } 239 }
162 SendTexture(httpRequest, httpResponse, texture);
163 }
164 else
165 {
166 m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
167 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
168 } 240 }
169 } 241 }
170 } 242 else // it was on the cache
171 else 243 {
172 { 244 //m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
173 m_log.Warn("[GETTEXTURE]: Failed to parse a texture_id from GetTexture request: " + httpRequest.Url); 245 WriteTextureData(httpRequest, httpResponse, texture, format);
246 return true;
247 }
248
174 } 249 }
175 250
176 httpResponse.Send(); 251 // not found
177 return null; 252 m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
253 httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
254 return true;
255
178 } 256 }
179 257
180 private void SendTexture(OSHttpRequest request, OSHttpResponse response, AssetBase texture) 258 private void WriteTextureData(OSHttpRequest request, OSHttpResponse response, AssetBase texture, string format)
181 { 259 {
182 string range = request.Headers.GetOne("Range"); 260 string range = request.Headers.GetOne("Range");
183 //m_log.DebugFormat("[GETTEXTURE]: Range {0}", range); 261 //m_log.DebugFormat("[GETTEXTURE]: Range {0}", range);
184 if (!String.IsNullOrEmpty(range)) 262 if (!String.IsNullOrEmpty(range)) // JP2's only
185 { 263 {
186 // Range request 264 // Range request
187 int start, end; 265 int start, end;
@@ -212,15 +290,19 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
212 } 290 }
213 else 291 else
214 { 292 {
215 m_log.Warn("Malformed Range header: " + range); 293 m_log.Warn("[GETTEXTURE]: Malformed Range header: " + range);
216 response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; 294 response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
217 } 295 }
218 } 296 }
219 else 297 else // JP2's or other formats
220 { 298 {
221 // Full content request 299 // Full content request
300 response.StatusCode = (int)System.Net.HttpStatusCode.OK;
222 response.ContentLength = texture.Data.Length; 301 response.ContentLength = texture.Data.Length;
223 response.ContentType = texture.Metadata.ContentType; 302 if (format == DefaultFormat)
303 response.ContentType = texture.Metadata.ContentType;
304 else
305 response.ContentType = "image/" + format;
224 response.Body.Write(texture.Data, 0, texture.Data.Length); 306 response.Body.Write(texture.Data, 0, texture.Data.Length);
225 } 307 }
226 } 308 }
@@ -240,5 +322,83 @@ namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps
240 start = end = 0; 322 start = end = 0;
241 return false; 323 return false;
242 } 324 }
325
326
327 private byte[] ConvertTextureData(AssetBase texture, string format)
328 {
329 m_log.DebugFormat("[GETTEXTURE]: Converting texture {0} to {1}", texture.ID, format);
330 byte[] data = new byte[0];
331
332 MemoryStream imgstream = new MemoryStream();
333 Bitmap mTexture = new Bitmap(1, 1);
334 ManagedImage managedImage;
335 Image image = (Image)mTexture;
336
337 try
338 {
339 // Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular jpeg data
340
341 imgstream = new MemoryStream();
342
343 // Decode image to System.Drawing.Image
344 if (OpenJPEG.DecodeToImage(texture.Data, out managedImage, out image))
345 {
346 // Save to bitmap
347 mTexture = new Bitmap(image);
348
349 EncoderParameters myEncoderParameters = new EncoderParameters();
350 myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L);
351
352 // Save bitmap to stream
353 ImageCodecInfo codec = GetEncoderInfo("image/" + format);
354 if (codec != null)
355 {
356 mTexture.Save(imgstream, codec, myEncoderParameters);
357 // Write the stream to a byte array for output
358 data = imgstream.ToArray();
359 }
360 else
361 m_log.WarnFormat("[GETTEXTURE]: No such codec {0}", format);
362
363 }
364 }
365 catch (Exception e)
366 {
367 m_log.WarnFormat("[GETTEXTURE]: Unable to convert texture {0} to {1}: {2}", texture.ID, format, e.Message);
368 }
369 finally
370 {
371 // Reclaim memory, these are unmanaged resources
372 // If we encountered an exception, one or more of these will be null
373 if (mTexture != null)
374 mTexture.Dispose();
375
376 if (image != null)
377 image.Dispose();
378
379 if (imgstream != null)
380 {
381 imgstream.Close();
382 imgstream.Dispose();
383 }
384 }
385
386 return data;
387 }
388
389 // From msdn
390 private static ImageCodecInfo GetEncoderInfo(String mimeType)
391 {
392 ImageCodecInfo[] encoders;
393 encoders = ImageCodecInfo.GetImageEncoders();
394 for (int j = 0; j < encoders.Length; ++j)
395 {
396 if (encoders[j].MimeType == mimeType)
397 return encoders[j];
398 }
399 return null;
400 }
401
402
243 } 403 }
244} 404}