aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-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}