clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Collections.Generic;
30 using System.Drawing;
31 using System.Drawing.Imaging;
32 using Nini.Config;
33 using OpenMetaverse;
34 using OpenMetaverse.Imaging;
35 using OpenSim.Framework;
36 using OpenSim.Region.Framework.Interfaces;
37 using OpenSim.Region.Framework.Scenes;
38 using log4net;
39 using System.Reflection;
40 using Mono.Addins;
41  
42 namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
43 {
44 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DynamicTextureModule")]
45 public class DynamicTextureModule : ISharedRegionModule, IDynamicTextureManager
46 {
47 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
48  
49 private const int ALL_SIDES = -1;
50  
51 public const int DISP_EXPIRE = 1;
52 public const int DISP_TEMP = 2;
53  
54 /// <summary>
55 /// If true then where possible dynamic textures are reused.
56 /// </summary>
57 public bool ReuseTextures { get; set; }
58  
59 /// <summary>
60 /// If false, then textures which have a low data size are not reused when ReuseTextures = true.
61 /// </summary>
62 /// <remarks>
63 /// LL viewers 3.3.4 and before appear to not fully render textures pulled from the viewer cache if those
64 /// textures have a relatively high pixel surface but a small data size. Typically, this appears to happen
65 /// if the data size is smaller than the viewer's discard level 2 size estimate. So if this is setting is
66 /// false, textures smaller than the calculation in IsSizeReuseable are always regenerated rather than reused
67 /// to work around this problem.</remarks>
68 public bool ReuseLowDataTextures { get; set; }
69  
70 private Dictionary<UUID, Scene> RegisteredScenes = new Dictionary<UUID, Scene>();
71  
72 private Dictionary<string, IDynamicTextureRender> RenderPlugins =
73 new Dictionary<string, IDynamicTextureRender>();
74  
75 private Dictionary<UUID, DynamicTextureUpdater> Updaters = new Dictionary<UUID, DynamicTextureUpdater>();
76  
77 /// <summary>
78 /// Record dynamic textures that we can reuse for a given data and parameter combination rather than
79 /// regenerate.
80 /// </summary>
81 /// <remarks>
82 /// Key is string.Format("{0}{1}", data
83 /// </remarks>
84 private Cache m_reuseableDynamicTextures;
85  
86 /// <summary>
87 /// This constructor is only here because of the Unit Tests...
88 /// Don't use it.
89 /// </summary>
90 public DynamicTextureModule()
91 {
92 m_reuseableDynamicTextures = new Cache(CacheMedium.Memory, CacheStrategy.Conservative);
93 m_reuseableDynamicTextures.DefaultTTL = new TimeSpan(24, 0, 0);
94 }
95  
96 #region IDynamicTextureManager Members
97  
98 public void RegisterRender(string handleType, IDynamicTextureRender render)
99 {
100 if (!RenderPlugins.ContainsKey(handleType))
101 {
102 RenderPlugins.Add(handleType, render);
103 }
104 }
105  
106 /// <summary>
107 /// Called by code which actually renders the dynamic texture to supply texture data.
108 /// </summary>
109 /// <param name="updaterId"></param>
110 /// <param name="texture"></param>
111 public void ReturnData(UUID updaterId, IDynamicTexture texture)
112 {
113 DynamicTextureUpdater updater = null;
114  
115 lock (Updaters)
116 {
117 if (Updaters.ContainsKey(updaterId))
118 {
119 updater = Updaters[updaterId];
120 }
121 }
122  
123 if (updater != null)
124 {
125 if (RegisteredScenes.ContainsKey(updater.SimUUID))
126 {
127 Scene scene = RegisteredScenes[updater.SimUUID];
128 UUID newTextureID = updater.DataReceived(texture.Data, scene);
129  
130 if (ReuseTextures
131 && !updater.BlendWithOldTexture
132 && texture.IsReuseable
133 && (ReuseLowDataTextures || IsDataSizeReuseable(texture)))
134 {
135 m_reuseableDynamicTextures.Store(
136 GenerateReusableTextureKey(texture.InputCommands, texture.InputParams), newTextureID);
137 }
138 }
139 }
140  
141 if (updater.UpdateTimer == 0)
142 {
143 lock (Updaters)
144 {
145 if (!Updaters.ContainsKey(updater.UpdaterID))
146 {
147 Updaters.Remove(updater.UpdaterID);
148 }
149 }
150 }
151 }
152  
153 /// <summary>
154 /// Determines whether the texture is reuseable based on its data size.
155 /// </summary>
156 /// <remarks>
157 /// This is a workaround for a viewer bug where very small data size textures relative to their pixel size
158 /// are not redisplayed properly when pulled from cache. The calculation here is based on the typical discard
159 /// level of 2, a 'rate' of 0.125 and 4 components (which makes for a factor of 0.5).
160 /// </remarks>
161 /// <returns></returns>
162 private bool IsDataSizeReuseable(IDynamicTexture texture)
163 {
164 // Console.WriteLine("{0} {1}", texture.Size.Width, texture.Size.Height);
165 int discardLevel2DataThreshold = (int)Math.Ceiling((texture.Size.Width >> 2) * (texture.Size.Height >> 2) * 0.5);
166  
167 // m_log.DebugFormat(
168 // "[DYNAMIC TEXTURE MODULE]: Discard level 2 threshold {0}, texture data length {1}",
169 // discardLevel2DataThreshold, texture.Data.Length);
170  
171 return discardLevel2DataThreshold < texture.Data.Length;
172 }
173  
174 public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
175 string extraParams, int updateTimer)
176 {
177 return AddDynamicTextureURL(simID, primID, contentType, url, extraParams, updateTimer, false, 255);
178 }
179  
180 public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
181 string extraParams, int updateTimer, bool SetBlending, byte AlphaValue)
182 {
183 return AddDynamicTextureURL(simID, primID, contentType, url,
184 extraParams, updateTimer, SetBlending,
185 (int)(DISP_TEMP|DISP_EXPIRE), AlphaValue, ALL_SIDES);
186 }
187  
188 public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
189 string extraParams, int updateTimer, bool SetBlending,
190 int disp, byte AlphaValue, int face)
191 {
192 if (RenderPlugins.ContainsKey(contentType))
193 {
194 DynamicTextureUpdater updater = new DynamicTextureUpdater();
195 updater.SimUUID = simID;
196 updater.PrimID = primID;
197 updater.ContentType = contentType;
198 updater.Url = url;
199 updater.UpdateTimer = updateTimer;
200 updater.UpdaterID = UUID.Random();
201 updater.Params = extraParams;
202 updater.BlendWithOldTexture = SetBlending;
203 updater.FrontAlpha = AlphaValue;
204 updater.Face = face;
205 updater.Disp = disp;
206  
207 lock (Updaters)
208 {
209 if (!Updaters.ContainsKey(updater.UpdaterID))
210 {
211 Updaters.Add(updater.UpdaterID, updater);
212 }
213 }
214  
215 RenderPlugins[contentType].AsyncConvertUrl(updater.UpdaterID, url, extraParams);
216 return updater.UpdaterID;
217 }
218 return UUID.Zero;
219 }
220  
221 public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data,
222 string extraParams, int updateTimer)
223 {
224 return AddDynamicTextureData(simID, primID, contentType, data, extraParams, updateTimer, false, 255);
225 }
226  
227 public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data,
228 string extraParams, int updateTimer, bool SetBlending, byte AlphaValue)
229 {
230 return AddDynamicTextureData(simID, primID, contentType, data, extraParams, updateTimer, SetBlending,
231 (int) (DISP_TEMP|DISP_EXPIRE), AlphaValue, ALL_SIDES);
232 }
233  
234 public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data,
235 string extraParams, int updateTimer, bool SetBlending, int disp, byte AlphaValue, int face)
236 {
237 if (!RenderPlugins.ContainsKey(contentType))
238 return UUID.Zero;
239  
240 Scene scene;
241 RegisteredScenes.TryGetValue(simID, out scene);
242  
243 if (scene == null)
244 return UUID.Zero;
245  
246 SceneObjectPart part = scene.GetSceneObjectPart(primID);
247  
248 if (part == null)
249 return UUID.Zero;
250  
251 // If we want to reuse dynamic textures then we have to ignore any request from the caller to expire
252 // them.
253 if (ReuseTextures)
254 disp = disp & ~DISP_EXPIRE;
255  
256 DynamicTextureUpdater updater = new DynamicTextureUpdater();
257 updater.SimUUID = simID;
258 updater.PrimID = primID;
259 updater.ContentType = contentType;
260 updater.BodyData = data;
261 updater.UpdateTimer = updateTimer;
262 updater.UpdaterID = UUID.Random();
263 updater.Params = extraParams;
264 updater.BlendWithOldTexture = SetBlending;
265 updater.FrontAlpha = AlphaValue;
266 updater.Face = face;
267 updater.Url = "Local image";
268 updater.Disp = disp;
269  
270 object objReusableTextureUUID = null;
271  
272 if (ReuseTextures && !updater.BlendWithOldTexture)
273 {
274 string reuseableTextureKey = GenerateReusableTextureKey(data, extraParams);
275 objReusableTextureUUID = m_reuseableDynamicTextures.Get(reuseableTextureKey);
276  
277 if (objReusableTextureUUID != null)
278 {
279 // If something else has removed this temporary asset from the cache, detect and invalidate
280 // our cached uuid.
281 if (scene.AssetService.GetMetadata(objReusableTextureUUID.ToString()) == null)
282 {
283 m_reuseableDynamicTextures.Invalidate(reuseableTextureKey);
284 objReusableTextureUUID = null;
285 }
286 }
287 }
288  
289 // We cannot reuse a dynamic texture if the data is going to be blended with something already there.
290 if (objReusableTextureUUID == null)
291 {
292 lock (Updaters)
293 {
294 if (!Updaters.ContainsKey(updater.UpdaterID))
295 {
296 Updaters.Add(updater.UpdaterID, updater);
297 }
298 }
299  
300 // m_log.DebugFormat(
301 // "[DYNAMIC TEXTURE MODULE]: Requesting generation of new dynamic texture for {0} in {1}",
302 // part.Name, part.ParentGroup.Scene.Name);
303  
304 RenderPlugins[contentType].AsyncConvertData(updater.UpdaterID, data, extraParams);
305 }
306 else
307 {
308 // m_log.DebugFormat(
309 // "[DYNAMIC TEXTURE MODULE]: Reusing cached texture {0} for {1} in {2}",
310 // objReusableTextureUUID, part.Name, part.ParentGroup.Scene.Name);
311  
312 // No need to add to updaters as the texture is always the same. Not that this functionality
313 // apppears to be implemented anyway.
314 updater.UpdatePart(part, (UUID)objReusableTextureUUID);
315 }
316  
317 return updater.UpdaterID;
318 }
319  
320 private string GenerateReusableTextureKey(string data, string extraParams)
321 {
322 return string.Format("{0}{1}", data, extraParams);
323 }
324  
325 public void GetDrawStringSize(string contentType, string text, string fontName, int fontSize,
326 out double xSize, out double ySize)
327 {
328 xSize = 0;
329 ySize = 0;
330 if (RenderPlugins.ContainsKey(contentType))
331 {
332 RenderPlugins[contentType].GetDrawStringSize(text, fontName, fontSize, out xSize, out ySize);
333 }
334 }
335  
336 #endregion
337  
338 #region ISharedRegionModule Members
339  
340 public void Initialise(IConfigSource config)
341 {
342 IConfig texturesConfig = config.Configs["Textures"];
343 if (texturesConfig != null)
344 {
345 ReuseTextures = texturesConfig.GetBoolean("ReuseDynamicTextures", false);
346 ReuseLowDataTextures = texturesConfig.GetBoolean("ReuseDynamicLowDataTextures", false);
347  
348 if (ReuseTextures)
349 {
350 m_reuseableDynamicTextures = new Cache(CacheMedium.Memory, CacheStrategy.Conservative);
351 m_reuseableDynamicTextures.DefaultTTL = new TimeSpan(24, 0, 0);
352 }
353 }
354 }
355  
356 public void PostInitialise()
357 {
358 }
359  
360 public void AddRegion(Scene scene)
361 {
362 if (!RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID))
363 {
364 RegisteredScenes.Add(scene.RegionInfo.RegionID, scene);
365 scene.RegisterModuleInterface<IDynamicTextureManager>(this);
366 }
367 }
368  
369 public void RegionLoaded(Scene scene)
370 {
371 }
372  
373 public void RemoveRegion(Scene scene)
374 {
375 if (RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID))
376 RegisteredScenes.Remove(scene.RegionInfo.RegionID);
377 }
378  
379 public void Close()
380 {
381 }
382  
383 public string Name
384 {
385 get { return "DynamicTextureModule"; }
386 }
387  
388 public Type ReplaceableInterface
389 {
390 get { return null; }
391 }
392  
393 #endregion
394  
395 #region Nested type: DynamicTextureUpdater
396  
397 public class DynamicTextureUpdater
398 {
399 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
400  
401 public bool BlendWithOldTexture = false;
402 public string BodyData;
403 public string ContentType;
404 public byte FrontAlpha = 255;
405 public string Params;
406 public UUID PrimID;
407 public bool SetNewFrontAlpha = false;
408 public UUID SimUUID;
409 public UUID UpdaterID;
410 public int UpdateTimer;
411 public int Face;
412 public int Disp;
413 public string Url;
414  
415 public DynamicTextureUpdater()
416 {
417 UpdateTimer = 0;
418 BodyData = null;
419 }
420  
421 /// <summary>
422 /// Update the given part with the new texture.
423 /// </summary>
424 /// <returns>
425 /// The old texture UUID.
426 /// </returns>
427 public UUID UpdatePart(SceneObjectPart part, UUID textureID)
428 {
429 UUID oldID;
430  
431 lock (part)
432 {
433 // mostly keep the values from before
434 Primitive.TextureEntry tmptex = part.Shape.Textures;
435  
436 // FIXME: Need to return the appropriate ID if only a single face is replaced.
437 oldID = tmptex.DefaultTexture.TextureID;
438  
439 if (Face == ALL_SIDES)
440 {
441 oldID = tmptex.DefaultTexture.TextureID;
442 tmptex.DefaultTexture.TextureID = textureID;
443 }
444 else
445 {
446 try
447 {
448 Primitive.TextureEntryFace texface = tmptex.CreateFace((uint)Face);
449 texface.TextureID = textureID;
450 tmptex.FaceTextures[Face] = texface;
451 }
452 catch (Exception)
453 {
454 tmptex.DefaultTexture.TextureID = textureID;
455 }
456 }
457  
458 // I'm pretty sure we always want to force this to true
459 // I'm pretty sure noone whats to set fullbright true if it wasn't true before.
460 // tmptex.DefaultTexture.Fullbright = true;
461  
462 part.UpdateTextureEntry(tmptex.GetBytes());
463 }
464  
465 return oldID;
466 }
467  
468 /// <summary>
469 /// Called once new texture data has been received for this updater.
470 /// </summary>
471 /// <param name="data"></param>
472 /// <param name="scene"></param>
473 /// <param name="isReuseable">True if the data given is reuseable.</param>
474 /// <returns>The asset UUID given to the incoming data.</returns>
475 public UUID DataReceived(byte[] data, Scene scene)
476 {
477 SceneObjectPart part = scene.GetSceneObjectPart(PrimID);
478  
479 if (part == null || data == null || data.Length <= 1)
480 {
481 string msg =
482 String.Format("DynamicTextureModule: Error preparing image using URL {0}", Url);
483 scene.SimChat(Utils.StringToBytes(msg), ChatTypeEnum.Say,
484 0, part.ParentGroup.RootPart.AbsolutePosition, part.Name, part.UUID, false);
485  
486 return UUID.Zero;
487 }
488  
489 byte[] assetData = null;
490 AssetBase oldAsset = null;
491  
492 if (BlendWithOldTexture)
493 {
494 Primitive.TextureEntryFace defaultFace = part.Shape.Textures.DefaultTexture;
495 if (defaultFace != null)
496 {
497 oldAsset = scene.AssetService.Get(defaultFace.TextureID.ToString());
498  
499 if (oldAsset != null)
500 assetData = BlendTextures(data, oldAsset.Data, SetNewFrontAlpha, FrontAlpha);
501 }
502 }
503  
504 if (assetData == null)
505 {
506 assetData = new byte[data.Length];
507 Array.Copy(data, assetData, data.Length);
508 }
509  
510 // Create a new asset for user
511 AssetBase asset
512 = new AssetBase(
513 UUID.Random(), "DynamicImage" + Util.RandomClass.Next(1, 10000), (sbyte)AssetType.Texture,
514 scene.RegionInfo.RegionID.ToString());
515 asset.Data = assetData;
516 asset.Description = String.Format("URL image : {0}", Url);
517 if (asset.Description.Length > AssetBase.MAX_ASSET_DESC)
518 asset.Description = asset.Description.Substring(0, AssetBase.MAX_ASSET_DESC);
519 asset.Local = true; // dynamic images aren't saved in the assets server
520 asset.Temporary = ((Disp & DISP_TEMP) != 0);
521 scene.AssetService.Store(asset); // this will only save the asset in the local asset cache
522  
523 IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface<IJ2KDecoder>();
524 if (cacheLayerDecode != null)
525 {
526 if (!cacheLayerDecode.Decode(asset.FullID, asset.Data))
527 m_log.WarnFormat(
528 "[DYNAMIC TEXTURE MODULE]: Decoding of dynamically generated asset {0} for {1} in {2} failed",
529 asset.ID, part.Name, part.ParentGroup.Scene.Name);
530 }
531  
532 UUID oldID = UpdatePart(part, asset.FullID);
533  
534 if (oldID != UUID.Zero && ((Disp & DISP_EXPIRE) != 0))
535 {
536 if (oldAsset == null)
537 oldAsset = scene.AssetService.Get(oldID.ToString());
538  
539 if (oldAsset != null)
540 {
541 if (oldAsset.Temporary)
542 {
543 scene.AssetService.Delete(oldID.ToString());
544 }
545 }
546 }
547  
548 return asset.FullID;
549 }
550  
551 private byte[] BlendTextures(byte[] frontImage, byte[] backImage, bool setNewAlpha, byte newAlpha)
552 {
553 ManagedImage managedImage;
554 Image image;
555  
556 if (OpenJPEG.DecodeToImage(frontImage, out managedImage, out image))
557 {
558 Bitmap image1 = new Bitmap(image);
559  
560 if (OpenJPEG.DecodeToImage(backImage, out managedImage, out image))
561 {
562 Bitmap image2 = new Bitmap(image);
563  
564 if (setNewAlpha)
565 SetAlpha(ref image1, newAlpha);
566  
567 Bitmap joint = MergeBitMaps(image1, image2);
568  
569 byte[] result = new byte[0];
570  
571 try
572 {
573 result = OpenJPEG.EncodeFromImage(joint, true);
574 }
575 catch (Exception e)
576 {
577 m_log.ErrorFormat(
578 "[DYNAMICTEXTUREMODULE]: OpenJpeg Encode Failed. Exception {0}{1}",
579 e.Message, e.StackTrace);
580 }
581  
582 return result;
583 }
584 }
585  
586 return null;
587 }
588  
589 public Bitmap MergeBitMaps(Bitmap front, Bitmap back)
590 {
591 Bitmap joint;
592 Graphics jG;
593  
594 joint = new Bitmap(back.Width, back.Height, PixelFormat.Format32bppArgb);
595 jG = Graphics.FromImage(joint);
596  
597 jG.DrawImage(back, 0, 0, back.Width, back.Height);
598 jG.DrawImage(front, 0, 0, back.Width, back.Height);
599  
600 return joint;
601 }
602  
603 private void SetAlpha(ref Bitmap b, byte alpha)
604 {
605 for (int w = 0; w < b.Width; w++)
606 {
607 for (int h = 0; h < b.Height; h++)
608 {
609 b.SetPixel(w, h, Color.FromArgb(alpha, b.GetPixel(w, h)));
610 }
611 }
612 }
613 }
614  
615 #endregion
616 }
617 }