opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 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.IO; |
||
32 | using System.Reflection; |
||
33 | using System.Text; |
||
34 | using System.Threading; |
||
35 | using log4net; |
||
36 | using Mono.Addins; |
||
37 | using Nini.Config; |
||
38 | using OpenMetaverse; |
||
39 | using OpenMetaverse.Imaging; |
||
40 | using CSJ2K; |
||
41 | using OpenSim.Framework; |
||
42 | using OpenSim.Region.Framework.Interfaces; |
||
43 | using OpenSim.Region.Framework.Scenes; |
||
44 | using OpenSim.Services.Interfaces; |
||
45 | |||
46 | namespace OpenSim.Region.CoreModules.Agent.TextureSender |
||
47 | { |
||
48 | public delegate void J2KDecodeDelegate(UUID assetID); |
||
49 | |||
50 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "J2KDecoderModule")] |
||
51 | public class J2KDecoderModule : ISharedRegionModule, IJ2KDecoder |
||
52 | { |
||
53 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
54 | |||
55 | /// <summary>Temporarily holds deserialized layer data information in memory</summary> |
||
56 | private readonly ExpiringCache<UUID, OpenJPEG.J2KLayerInfo[]> m_decodedCache = new ExpiringCache<UUID,OpenJPEG.J2KLayerInfo[]>(); |
||
57 | /// <summary>List of client methods to notify of results of decode</summary> |
||
58 | private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>(); |
||
59 | /// <summary>Cache that will store decoded JPEG2000 layer boundary data</summary> |
||
60 | private IImprovedAssetCache m_cache; |
||
61 | private IImprovedAssetCache Cache |
||
62 | { |
||
63 | get |
||
64 | { |
||
65 | if (m_cache == null) |
||
66 | m_cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); |
||
67 | |||
68 | return m_cache; |
||
69 | } |
||
70 | } |
||
71 | /// <summary>Reference to a scene (doesn't matter which one as long as it can load the cache module)</summary> |
||
72 | private UUID m_CreatorID = UUID.Zero; |
||
73 | private Scene m_scene; |
||
74 | |||
75 | #region ISharedRegionModule |
||
76 | |||
77 | private bool m_useCSJ2K = true; |
||
78 | |||
79 | public string Name { get { return "J2KDecoderModule"; } } |
||
80 | |||
81 | public J2KDecoderModule() |
||
82 | { |
||
83 | } |
||
84 | |||
85 | public void Initialise(IConfigSource source) |
||
86 | { |
||
87 | IConfig startupConfig = source.Configs["Startup"]; |
||
88 | if (startupConfig != null) |
||
89 | { |
||
90 | m_useCSJ2K = startupConfig.GetBoolean("UseCSJ2K", m_useCSJ2K); |
||
91 | } |
||
92 | } |
||
93 | |||
94 | public void AddRegion(Scene scene) |
||
95 | { |
||
96 | if (m_scene == null) |
||
97 | { |
||
98 | m_scene = scene; |
||
99 | m_CreatorID = scene.RegionInfo.RegionID; |
||
100 | } |
||
101 | |||
102 | scene.RegisterModuleInterface<IJ2KDecoder>(this); |
||
103 | |||
104 | } |
||
105 | |||
106 | public void RemoveRegion(Scene scene) |
||
107 | { |
||
108 | if (m_scene == scene) |
||
109 | m_scene = null; |
||
110 | } |
||
111 | |||
112 | public void PostInitialise() |
||
113 | { |
||
114 | } |
||
115 | |||
116 | public void Close() |
||
117 | { |
||
118 | } |
||
119 | |||
120 | public void RegionLoaded(Scene scene) |
||
121 | { |
||
122 | } |
||
123 | |||
124 | public Type ReplaceableInterface |
||
125 | { |
||
126 | get { return null; } |
||
127 | } |
||
128 | |||
129 | #endregion Region Module interface |
||
130 | |||
131 | #region IJ2KDecoder |
||
132 | |||
133 | public void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback) |
||
134 | { |
||
135 | OpenJPEG.J2KLayerInfo[] result; |
||
136 | |||
137 | // If it's cached, return the cached results |
||
138 | if (m_decodedCache.TryGetValue(assetID, out result)) |
||
139 | { |
||
140 | // m_log.DebugFormat( |
||
141 | // "[J2KDecoderModule]: Returning existing cached {0} layers j2k decode for {1}", |
||
142 | // result.Length, assetID); |
||
143 | |||
144 | callback(assetID, result); |
||
145 | } |
||
146 | else |
||
147 | { |
||
148 | // Not cached, we need to decode it. |
||
149 | // Add to notify list and start decoding. |
||
150 | // Next request for this asset while it's decoding will only be added to the notify list |
||
151 | // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated |
||
152 | bool decode = false; |
||
153 | lock (m_notifyList) |
||
154 | { |
||
155 | if (m_notifyList.ContainsKey(assetID)) |
||
156 | { |
||
157 | m_notifyList[assetID].Add(callback); |
||
158 | } |
||
159 | else |
||
160 | { |
||
161 | List<DecodedCallback> notifylist = new List<DecodedCallback>(); |
||
162 | notifylist.Add(callback); |
||
163 | m_notifyList.Add(assetID, notifylist); |
||
164 | decode = true; |
||
165 | } |
||
166 | } |
||
167 | |||
168 | // Do Decode! |
||
169 | if (decode) |
||
170 | Util.FireAndForget(delegate { Decode(assetID, j2kData); }); |
||
171 | } |
||
172 | } |
||
173 | |||
174 | public bool Decode(UUID assetID, byte[] j2kData) |
||
175 | { |
||
176 | OpenJPEG.J2KLayerInfo[] layers; |
||
177 | int components; |
||
178 | return Decode(assetID, j2kData, out layers, out components); |
||
179 | } |
||
180 | |||
181 | public bool Decode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components) |
||
182 | { |
||
183 | return DoJ2KDecode(assetID, j2kData, out layers, out components); |
||
184 | } |
||
185 | |||
186 | public Image DecodeToImage(byte[] j2kData) |
||
187 | { |
||
188 | if (m_useCSJ2K) |
||
189 | return J2kImage.FromBytes(j2kData); |
||
190 | else |
||
191 | { |
||
192 | ManagedImage mimage; |
||
193 | Image image; |
||
194 | if (OpenJPEG.DecodeToImage(j2kData, out mimage, out image)) |
||
195 | { |
||
196 | mimage = null; |
||
197 | return image; |
||
198 | } |
||
199 | else |
||
200 | return null; |
||
201 | } |
||
202 | } |
||
203 | |||
204 | |||
205 | #endregion IJ2KDecoder |
||
206 | |||
207 | /// <summary> |
||
208 | /// Decode Jpeg2000 Asset Data |
||
209 | /// </summary> |
||
210 | /// <param name="assetID">UUID of Asset</param> |
||
211 | /// <param name="j2kData">JPEG2000 data</param> |
||
212 | /// <param name="layers">layer data</param> |
||
213 | /// <param name="components">number of components</param> |
||
214 | /// <returns>true if decode was successful. false otherwise.</returns> |
||
215 | private bool DoJ2KDecode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components) |
||
216 | { |
||
217 | // m_log.DebugFormat( |
||
218 | // "[J2KDecoderModule]: Doing J2K decoding of {0} bytes for asset {1}", j2kData.Length, assetID); |
||
219 | |||
220 | bool decodedSuccessfully = true; |
||
221 | |||
222 | //int DecodeTime = 0; |
||
223 | //DecodeTime = Environment.TickCount; |
||
224 | |||
225 | // We don't get this from CSJ2K. Is it relevant? |
||
226 | components = 0; |
||
227 | |||
228 | if (!TryLoadCacheForAsset(assetID, out layers)) |
||
229 | { |
||
230 | if (m_useCSJ2K) |
||
231 | { |
||
232 | try |
||
233 | { |
||
234 | List<int> layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kData)); |
||
235 | |||
236 | if (layerStarts != null && layerStarts.Count > 0) |
||
237 | { |
||
238 | layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; |
||
239 | |||
240 | for (int i = 0; i < layerStarts.Count; i++) |
||
241 | { |
||
242 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); |
||
243 | |||
244 | if (i == 0) |
||
245 | layer.Start = 0; |
||
246 | else |
||
247 | layer.Start = layerStarts[i]; |
||
248 | |||
249 | if (i == layerStarts.Count - 1) |
||
250 | layer.End = j2kData.Length; |
||
251 | else |
||
252 | layer.End = layerStarts[i + 1] - 1; |
||
253 | |||
254 | layers[i] = layer; |
||
255 | } |
||
256 | } |
||
257 | } |
||
258 | catch (Exception ex) |
||
259 | { |
||
260 | m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + assetID + ": " + ex.Message); |
||
261 | decodedSuccessfully = false; |
||
262 | } |
||
263 | } |
||
264 | else |
||
265 | { |
||
266 | if (!OpenJPEG.DecodeLayerBoundaries(j2kData, out layers, out components)) |
||
267 | { |
||
268 | m_log.Warn("[J2KDecoderModule]: OpenJPEG failed to decode texture " + assetID); |
||
269 | decodedSuccessfully = false; |
||
270 | } |
||
271 | } |
||
272 | |||
273 | if (layers == null || layers.Length == 0) |
||
274 | { |
||
275 | m_log.Warn("[J2KDecoderModule]: Failed to decode layer data for texture " + assetID + ", guessing sane defaults"); |
||
276 | // Layer decoding completely failed. Guess at sane defaults for the layer boundaries |
||
277 | layers = CreateDefaultLayers(j2kData.Length); |
||
278 | decodedSuccessfully = false; |
||
279 | } |
||
280 | |||
281 | // Cache Decoded layers |
||
282 | SaveFileCacheForAsset(assetID, layers); |
||
283 | } |
||
284 | |||
285 | // Notify Interested Parties |
||
286 | lock (m_notifyList) |
||
287 | { |
||
288 | if (m_notifyList.ContainsKey(assetID)) |
||
289 | { |
||
290 | foreach (DecodedCallback d in m_notifyList[assetID]) |
||
291 | { |
||
292 | if (d != null) |
||
293 | d.DynamicInvoke(assetID, layers); |
||
294 | } |
||
295 | m_notifyList.Remove(assetID); |
||
296 | } |
||
297 | } |
||
298 | |||
299 | return decodedSuccessfully; |
||
300 | } |
||
301 | |||
302 | private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) |
||
303 | { |
||
304 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; |
||
305 | |||
306 | for (int i = 0; i < layers.Length; i++) |
||
307 | layers[i] = new OpenJPEG.J2KLayerInfo(); |
||
308 | |||
309 | // These default layer sizes are based on a small sampling of real-world texture data |
||
310 | // with extra padding thrown in for good measure. This is a worst case fallback plan |
||
311 | // and may not gracefully handle all real world data |
||
312 | layers[0].Start = 0; |
||
313 | layers[1].Start = (int)((float)j2kLength * 0.02f); |
||
314 | layers[2].Start = (int)((float)j2kLength * 0.05f); |
||
315 | layers[3].Start = (int)((float)j2kLength * 0.20f); |
||
316 | layers[4].Start = (int)((float)j2kLength * 0.50f); |
||
317 | |||
318 | layers[0].End = layers[1].Start - 1; |
||
319 | layers[1].End = layers[2].Start - 1; |
||
320 | layers[2].End = layers[3].Start - 1; |
||
321 | layers[3].End = layers[4].Start - 1; |
||
322 | layers[4].End = j2kLength; |
||
323 | |||
324 | return layers; |
||
325 | } |
||
326 | |||
327 | private void SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) |
||
328 | { |
||
329 | m_decodedCache.AddOrUpdate(AssetId, Layers, TimeSpan.FromMinutes(10)); |
||
330 | |||
331 | if (Cache != null) |
||
332 | { |
||
333 | string assetID = "j2kCache_" + AssetId.ToString(); |
||
334 | |||
335 | AssetBase layerDecodeAsset = new AssetBase(assetID, assetID, (sbyte)AssetType.Notecard, m_CreatorID.ToString()); |
||
336 | layerDecodeAsset.Local = true; |
||
337 | layerDecodeAsset.Temporary = true; |
||
338 | |||
339 | #region Serialize Layer Data |
||
340 | |||
341 | StringBuilder stringResult = new StringBuilder(); |
||
342 | string strEnd = "\n"; |
||
343 | for (int i = 0; i < Layers.Length; i++) |
||
344 | { |
||
345 | if (i == Layers.Length - 1) |
||
346 | strEnd = String.Empty; |
||
347 | |||
348 | stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); |
||
349 | } |
||
350 | |||
351 | layerDecodeAsset.Data = Util.UTF8.GetBytes(stringResult.ToString()); |
||
352 | |||
353 | #endregion Serialize Layer Data |
||
354 | |||
355 | Cache.Cache(layerDecodeAsset); |
||
356 | } |
||
357 | } |
||
358 | |||
359 | bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) |
||
360 | { |
||
361 | if (m_decodedCache.TryGetValue(AssetId, out Layers)) |
||
362 | { |
||
363 | return true; |
||
364 | } |
||
365 | else if (Cache != null) |
||
366 | { |
||
367 | string assetName = "j2kCache_" + AssetId.ToString(); |
||
368 | AssetBase layerDecodeAsset = Cache.Get(assetName); |
||
369 | |||
370 | if (layerDecodeAsset != null) |
||
371 | { |
||
372 | #region Deserialize Layer Data |
||
373 | |||
374 | string readResult = Util.UTF8.GetString(layerDecodeAsset.Data); |
||
375 | string[] lines = readResult.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); |
||
376 | |||
377 | if (lines.Length == 0) |
||
378 | { |
||
379 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (empty) " + assetName); |
||
380 | Cache.Expire(assetName); |
||
381 | return false; |
||
382 | } |
||
383 | |||
384 | Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; |
||
385 | |||
386 | for (int i = 0; i < lines.Length; i++) |
||
387 | { |
||
388 | string[] elements = lines[i].Split('|'); |
||
389 | if (elements.Length == 3) |
||
390 | { |
||
391 | int element1, element2; |
||
392 | |||
393 | try |
||
394 | { |
||
395 | element1 = Convert.ToInt32(elements[0]); |
||
396 | element2 = Convert.ToInt32(elements[1]); |
||
397 | } |
||
398 | catch (FormatException) |
||
399 | { |
||
400 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (format) " + assetName); |
||
401 | Cache.Expire(assetName); |
||
402 | return false; |
||
403 | } |
||
404 | |||
405 | Layers[i] = new OpenJPEG.J2KLayerInfo(); |
||
406 | Layers[i].Start = element1; |
||
407 | Layers[i].End = element2; |
||
408 | } |
||
409 | else |
||
410 | { |
||
411 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (layout) " + assetName); |
||
412 | Cache.Expire(assetName); |
||
413 | return false; |
||
414 | } |
||
415 | } |
||
416 | |||
417 | #endregion Deserialize Layer Data |
||
418 | |||
419 | return true; |
||
420 | } |
||
421 | } |
||
422 | |||
423 | return false; |
||
424 | } |
||
425 | } |
||
426 | } |