DynamicTextureModule.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  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. using System;
  28. using System.Collections.Generic;
  29. using System.Drawing;
  30. using System.Drawing.Imaging;
  31. using Nini.Config;
  32. using OpenMetaverse;
  33. using OpenMetaverse.Imaging;
  34. using OpenSim.Framework;
  35. using OpenSim.Region.Framework.Interfaces;
  36. using OpenSim.Region.Framework.Scenes;
  37. using log4net;
  38. using System.Reflection;
  39. using Mono.Addins;
  40. namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
  41. {
  42. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DynamicTextureModule")]
  43. public class DynamicTextureModule : ISharedRegionModule, IDynamicTextureManager
  44. {
  45. // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  46. private const int ALL_SIDES = -1;
  47. public const int DISP_EXPIRE = 1;
  48. public const int DISP_TEMP = 2;
  49. /// <summary>
  50. /// If true then where possible dynamic textures are reused.
  51. /// </summary>
  52. public bool ReuseTextures { get; set; }
  53. /// <summary>
  54. /// If false, then textures which have a low data size are not reused when ReuseTextures = true.
  55. /// </summary>
  56. /// <remarks>
  57. /// LL viewers 3.3.4 and before appear to not fully render textures pulled from the viewer cache if those
  58. /// textures have a relatively high pixel surface but a small data size. Typically, this appears to happen
  59. /// if the data size is smaller than the viewer's discard level 2 size estimate. So if this is setting is
  60. /// false, textures smaller than the calculation in IsSizeReuseable are always regenerated rather than reused
  61. /// to work around this problem.</remarks>
  62. public bool ReuseLowDataTextures { get; set; }
  63. private Dictionary<UUID, Scene> RegisteredScenes = new Dictionary<UUID, Scene>();
  64. private Dictionary<string, IDynamicTextureRender> RenderPlugins =
  65. new Dictionary<string, IDynamicTextureRender>();
  66. private Dictionary<UUID, DynamicTextureUpdater> Updaters = new Dictionary<UUID, DynamicTextureUpdater>();
  67. /// <summary>
  68. /// Record dynamic textures that we can reuse for a given data and parameter combination rather than
  69. /// regenerate.
  70. /// </summary>
  71. /// <remarks>
  72. /// Key is string.Format("{0}{1}", data
  73. /// </remarks>
  74. private Cache m_reuseableDynamicTextures;
  75. /// <summary>
  76. /// This constructor is only here because of the Unit Tests...
  77. /// Don't use it.
  78. /// </summary>
  79. public DynamicTextureModule()
  80. {
  81. m_reuseableDynamicTextures = new Cache(CacheMedium.Memory, CacheStrategy.Conservative);
  82. m_reuseableDynamicTextures.DefaultTTL = new TimeSpan(24, 0, 0);
  83. }
  84. #region IDynamicTextureManager Members
  85. public void RegisterRender(string handleType, IDynamicTextureRender render)
  86. {
  87. if (!RenderPlugins.ContainsKey(handleType))
  88. {
  89. RenderPlugins.Add(handleType, render);
  90. }
  91. }
  92. /// <summary>
  93. /// Called by code which actually renders the dynamic texture to supply texture data.
  94. /// </summary>
  95. /// <param name="updaterId"></param>
  96. /// <param name="texture"></param>
  97. public void ReturnData(UUID updaterId, IDynamicTexture texture)
  98. {
  99. DynamicTextureUpdater updater = null;
  100. lock (Updaters)
  101. {
  102. if (Updaters.ContainsKey(updaterId))
  103. {
  104. updater = Updaters[updaterId];
  105. }
  106. }
  107. if (updater != null)
  108. {
  109. if (RegisteredScenes.ContainsKey(updater.SimUUID))
  110. {
  111. Scene scene = RegisteredScenes[updater.SimUUID];
  112. UUID newTextureID = updater.DataReceived(texture.Data, scene);
  113. if (ReuseTextures
  114. && !updater.BlendWithOldTexture
  115. && texture.IsReuseable
  116. && (ReuseLowDataTextures || IsDataSizeReuseable(texture)))
  117. {
  118. m_reuseableDynamicTextures.Store(
  119. GenerateReusableTextureKey(texture.InputCommands, texture.InputParams), newTextureID);
  120. }
  121. updater.newTextureID = newTextureID;
  122. }
  123. lock (Updaters)
  124. {
  125. if (Updaters.ContainsKey(updater.UpdaterID))
  126. Updaters.Remove(updater.UpdaterID);
  127. }
  128. }
  129. }
  130. /// <summary>
  131. /// Determines whether the texture is reuseable based on its data size.
  132. /// </summary>
  133. /// <remarks>
  134. /// This is a workaround for a viewer bug where very small data size textures relative to their pixel size
  135. /// are not redisplayed properly when pulled from cache. The calculation here is based on the typical discard
  136. /// level of 2, a 'rate' of 0.125 and 4 components (which makes for a factor of 0.5).
  137. /// </remarks>
  138. /// <returns></returns>
  139. private bool IsDataSizeReuseable(IDynamicTexture texture)
  140. {
  141. // Console.WriteLine("{0} {1}", texture.Size.Width, texture.Size.Height);
  142. int discardLevel2DataThreshold = (int)Math.Ceiling((texture.Size.Width >> 2) * (texture.Size.Height >> 2) * 0.5);
  143. // m_log.DebugFormat(
  144. // "[DYNAMIC TEXTURE MODULE]: Discard level 2 threshold {0}, texture data length {1}",
  145. // discardLevel2DataThreshold, texture.Data.Length);
  146. return discardLevel2DataThreshold < texture.Data.Length;
  147. }
  148. public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
  149. string extraParams)
  150. {
  151. return AddDynamicTextureURL(simID, primID, contentType, url, extraParams, false, 255);
  152. }
  153. public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
  154. string extraParams, bool SetBlending, byte AlphaValue)
  155. {
  156. return AddDynamicTextureURL(simID, primID, contentType, url, extraParams, SetBlending,
  157. (DISP_TEMP|DISP_EXPIRE), AlphaValue, ALL_SIDES);
  158. }
  159. public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
  160. string extraParams, bool SetBlending,
  161. int disp, byte AlphaValue, int face)
  162. {
  163. if (RenderPlugins.ContainsKey(contentType))
  164. {
  165. DynamicTextureUpdater updater = new DynamicTextureUpdater();
  166. updater.SimUUID = simID;
  167. updater.PrimID = primID;
  168. updater.ContentType = contentType;
  169. updater.Url = url;
  170. updater.UpdaterID = UUID.Random();
  171. updater.Params = extraParams;
  172. updater.BlendWithOldTexture = SetBlending;
  173. updater.FrontAlpha = AlphaValue;
  174. updater.Face = face;
  175. updater.Disp = disp;
  176. lock (Updaters)
  177. {
  178. if (!Updaters.ContainsKey(updater.UpdaterID))
  179. {
  180. Updaters.Add(updater.UpdaterID, updater);
  181. }
  182. }
  183. RenderPlugins[contentType].AsyncConvertUrl(updater.UpdaterID, url, extraParams);
  184. return updater.newTextureID;
  185. }
  186. return UUID.Zero;
  187. }
  188. public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data,
  189. string extraParams)
  190. {
  191. return AddDynamicTextureData(simID, primID, contentType, data, extraParams, false,
  192. (DISP_TEMP|DISP_EXPIRE), 255, ALL_SIDES);
  193. }
  194. public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data,
  195. string extraParams, bool SetBlending, byte AlphaValue)
  196. {
  197. return AddDynamicTextureData(simID, primID, contentType, data, extraParams, SetBlending,
  198. (DISP_TEMP|DISP_EXPIRE), AlphaValue, ALL_SIDES);
  199. }
  200. public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data,
  201. string extraParams, bool SetBlending, int disp, byte AlphaValue, int face)
  202. {
  203. if (!RenderPlugins.ContainsKey(contentType))
  204. return UUID.Zero;
  205. Scene scene;
  206. RegisteredScenes.TryGetValue(simID, out scene);
  207. if (scene == null)
  208. return UUID.Zero;
  209. SceneObjectPart part = scene.GetSceneObjectPart(primID);
  210. if (part == null)
  211. return UUID.Zero;
  212. // If we want to reuse dynamic textures then we have to ignore any request from the caller to expire
  213. // them.
  214. if (ReuseTextures)
  215. disp = disp & ~DISP_EXPIRE;
  216. DynamicTextureUpdater updater = new DynamicTextureUpdater();
  217. updater.SimUUID = simID;
  218. updater.PrimID = primID;
  219. updater.ContentType = contentType;
  220. updater.BodyData = data;
  221. updater.UpdaterID = UUID.Random();
  222. updater.Params = extraParams;
  223. updater.BlendWithOldTexture = SetBlending;
  224. updater.FrontAlpha = AlphaValue;
  225. updater.Face = face;
  226. updater.Url = "Local image";
  227. updater.Disp = disp;
  228. object objReusableTextureUUID = null;
  229. if (ReuseTextures && !updater.BlendWithOldTexture)
  230. {
  231. string reuseableTextureKey = GenerateReusableTextureKey(data, extraParams);
  232. objReusableTextureUUID = m_reuseableDynamicTextures.Get(reuseableTextureKey);
  233. if (objReusableTextureUUID != null)
  234. {
  235. // If something else has removed this temporary asset from the cache, detect and invalidate
  236. // our cached uuid.
  237. if (scene.AssetService.GetMetadata(objReusableTextureUUID.ToString()) == null)
  238. {
  239. m_reuseableDynamicTextures.Invalidate(reuseableTextureKey);
  240. objReusableTextureUUID = null;
  241. }
  242. }
  243. }
  244. // We cannot reuse a dynamic texture if the data is going to be blended with something already there.
  245. if (objReusableTextureUUID == null)
  246. {
  247. lock (Updaters)
  248. {
  249. if (!Updaters.ContainsKey(updater.UpdaterID))
  250. {
  251. Updaters.Add(updater.UpdaterID, updater);
  252. }
  253. }
  254. // m_log.DebugFormat(
  255. // "[DYNAMIC TEXTURE MODULE]: Requesting generation of new dynamic texture for {0} in {1}",
  256. // part.Name, part.ParentGroup.Scene.Name);
  257. RenderPlugins[contentType].AsyncConvertData(updater.UpdaterID, data, extraParams);
  258. }
  259. else
  260. {
  261. // m_log.DebugFormat(
  262. // "[DYNAMIC TEXTURE MODULE]: Reusing cached texture {0} for {1} in {2}",
  263. // objReusableTextureUUID, part.Name, part.ParentGroup.Scene.Name);
  264. // No need to add to updaters as the texture is always the same. Not that this functionality
  265. // apppears to be implemented anyway.
  266. updater.UpdatePart(part, (UUID)objReusableTextureUUID);
  267. }
  268. return updater.newTextureID;
  269. }
  270. private string GenerateReusableTextureKey(string data, string extraParams)
  271. {
  272. return string.Format("{0}{1}", data, extraParams);
  273. }
  274. public void GetDrawStringSize(string contentType, string text, string fontName, int fontSize,
  275. out double xSize, out double ySize)
  276. {
  277. xSize = 0;
  278. ySize = 0;
  279. if (RenderPlugins.ContainsKey(contentType))
  280. {
  281. RenderPlugins[contentType].GetDrawStringSize(text, fontName, fontSize, out xSize, out ySize);
  282. }
  283. }
  284. #endregion
  285. #region ISharedRegionModule Members
  286. public void Initialise(IConfigSource config)
  287. {
  288. IConfig texturesConfig = config.Configs["Textures"];
  289. if (texturesConfig != null)
  290. {
  291. ReuseTextures = texturesConfig.GetBoolean("ReuseDynamicTextures", false);
  292. ReuseLowDataTextures = texturesConfig.GetBoolean("ReuseDynamicLowDataTextures", false);
  293. if (ReuseTextures)
  294. {
  295. m_reuseableDynamicTextures = new Cache(CacheMedium.Memory, CacheStrategy.Conservative);
  296. m_reuseableDynamicTextures.DefaultTTL = new TimeSpan(24, 0, 0);
  297. }
  298. }
  299. }
  300. public void PostInitialise()
  301. {
  302. }
  303. public void AddRegion(Scene scene)
  304. {
  305. if (!RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID))
  306. {
  307. RegisteredScenes.Add(scene.RegionInfo.RegionID, scene);
  308. scene.RegisterModuleInterface<IDynamicTextureManager>(this);
  309. }
  310. }
  311. public void RegionLoaded(Scene scene)
  312. {
  313. }
  314. public void RemoveRegion(Scene scene)
  315. {
  316. if (RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID))
  317. RegisteredScenes.Remove(scene.RegionInfo.RegionID);
  318. }
  319. public void Close()
  320. {
  321. }
  322. public string Name
  323. {
  324. get { return "DynamicTextureModule"; }
  325. }
  326. public Type ReplaceableInterface
  327. {
  328. get { return null; }
  329. }
  330. #endregion
  331. #region Nested type: DynamicTextureUpdater
  332. public class DynamicTextureUpdater
  333. {
  334. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  335. public bool BlendWithOldTexture = false;
  336. public string BodyData;
  337. public string ContentType;
  338. public byte FrontAlpha = 255;
  339. public string Params;
  340. public UUID PrimID;
  341. public UUID SimUUID;
  342. public UUID UpdaterID;
  343. public int Face;
  344. public int Disp;
  345. public string Url;
  346. public UUID newTextureID;
  347. public DynamicTextureUpdater()
  348. {
  349. BodyData = null;
  350. }
  351. /// <summary>
  352. /// Update the given part with the new texture.
  353. /// </summary>
  354. /// <returns>
  355. /// The old texture UUID.
  356. /// </returns>
  357. public UUID UpdatePart(SceneObjectPart part, UUID textureID)
  358. {
  359. UUID oldID;
  360. lock (part)
  361. {
  362. // mostly keep the values from before
  363. Primitive.TextureEntry tmptex = part.Shape.Textures;
  364. // FIXME: Need to return the appropriate ID if only a single face is replaced.
  365. oldID = tmptex.DefaultTexture.TextureID;
  366. // not using parts number of faces because that fails on old meshs
  367. if (Face == ALL_SIDES)
  368. {
  369. oldID = tmptex.DefaultTexture.TextureID;
  370. tmptex.DefaultTexture.TextureID = textureID;
  371. for(int i = 0; i < tmptex.FaceTextures.Length; i++)
  372. {
  373. if(tmptex.FaceTextures[i] != null)
  374. tmptex.FaceTextures[i].TextureID = textureID;
  375. }
  376. }
  377. else
  378. {
  379. try
  380. {
  381. Primitive.TextureEntryFace texface = tmptex.CreateFace((uint)Face);
  382. oldID = texface.TextureID;
  383. texface.TextureID = textureID;
  384. tmptex.FaceTextures[Face] = texface;
  385. }
  386. catch (Exception)
  387. {
  388. tmptex.DefaultTexture.TextureID = textureID;
  389. }
  390. }
  391. part.UpdateTextureEntry(tmptex.GetBytes());
  392. }
  393. return oldID;
  394. }
  395. /// <summary>
  396. /// Called once new texture data has been received for this updater.
  397. /// </summary>
  398. /// <param name="data"></param>
  399. /// <param name="scene"></param>
  400. /// <param name="isReuseable">True if the data given is reuseable.</param>
  401. /// <returns>The asset UUID given to the incoming data.</returns>
  402. public UUID DataReceived(byte[] data, Scene scene)
  403. {
  404. SceneObjectPart part = scene.GetSceneObjectPart(PrimID);
  405. if (part == null || data == null || data.Length <= 1)
  406. {
  407. string msg =
  408. String.Format("DynamicTextureModule: Error preparing image using URL {0}", Url);
  409. scene.SimChat(Utils.StringToBytes(msg), ChatTypeEnum.Say,
  410. 0, part.ParentGroup.RootPart.AbsolutePosition, part.Name, part.UUID, false);
  411. return UUID.Zero;
  412. }
  413. byte[] assetData = null;
  414. AssetBase oldAsset = null;
  415. if (BlendWithOldTexture)
  416. {
  417. Primitive.TextureEntryFace curFace;
  418. if(Face == ALL_SIDES)
  419. curFace = part.Shape.Textures.DefaultTexture;
  420. else
  421. {
  422. try
  423. {
  424. curFace = part.Shape.Textures.GetFace((uint)Face);
  425. }
  426. catch
  427. {
  428. curFace = null;
  429. }
  430. }
  431. if (curFace != null)
  432. {
  433. oldAsset = scene.AssetService.Get(curFace.TextureID.ToString());
  434. if (oldAsset != null)
  435. assetData = BlendTextures(data, oldAsset.Data, FrontAlpha);
  436. }
  437. }
  438. if (assetData == null)
  439. {
  440. assetData = new byte[data.Length];
  441. Array.Copy(data, assetData, data.Length);
  442. }
  443. // Create a new asset for user
  444. AssetBase asset
  445. = new AssetBase(
  446. UUID.Random(), "DynamicImage" + Util.RandomClass.Next(1, 10000), (sbyte)AssetType.Texture,
  447. scene.RegionInfo.RegionID.ToString());
  448. asset.Data = assetData;
  449. asset.Description = String.Format("URL image : {0}", Url);
  450. if (asset.Description.Length > 128)
  451. asset.Description = asset.Description.Substring(0, 128);
  452. asset.Local = true; // dynamic images aren't saved in the assets server
  453. asset.Temporary = ((Disp & DISP_TEMP) != 0);
  454. scene.AssetService.Store(asset); // this will only save the asset in the local asset cache
  455. IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface<IJ2KDecoder>();
  456. if (cacheLayerDecode != null)
  457. {
  458. if (!cacheLayerDecode.Decode(asset.FullID, asset.Data))
  459. m_log.WarnFormat(
  460. "[DYNAMIC TEXTURE MODULE]: Decoding of dynamically generated asset {0} for {1} in {2} failed",
  461. asset.ID, part.Name, part.ParentGroup.Scene.Name);
  462. }
  463. UUID oldID = UpdatePart(part, asset.FullID);
  464. if (oldID != UUID.Zero && ((Disp & DISP_EXPIRE) != 0))
  465. {
  466. if (oldAsset == null)
  467. oldAsset = scene.AssetService.Get(oldID.ToString());
  468. if (oldAsset != null)
  469. {
  470. if (oldAsset.Temporary)
  471. {
  472. scene.AssetService.Delete(oldID.ToString());
  473. }
  474. }
  475. }
  476. return asset.FullID;
  477. }
  478. private byte[] BlendTextures(byte[] frontImage, byte[] backImage, byte newAlpha)
  479. {
  480. ManagedImage managedImage;
  481. Image image;
  482. if (!OpenJPEG.DecodeToImage(frontImage, out managedImage, out image) || image == null)
  483. return null;
  484. Bitmap image1 = new Bitmap(image);
  485. image.Dispose();
  486. if (!OpenJPEG.DecodeToImage(backImage, out managedImage, out image) || image == null)
  487. {
  488. image1.Dispose();
  489. return null;
  490. }
  491. Bitmap image2 = new Bitmap(image);
  492. image.Dispose();
  493. if (newAlpha < 255)
  494. SetAlpha(ref image1, newAlpha);
  495. using(Bitmap joint = MergeBitMaps(image1, image2))
  496. {
  497. image1.Dispose();
  498. image2.Dispose();
  499. byte[] result = new byte[0];
  500. try
  501. {
  502. result = OpenJPEG.EncodeFromImage(joint, true);
  503. }
  504. catch (Exception e)
  505. {
  506. m_log.ErrorFormat(
  507. "[DYNAMICTEXTUREMODULE]: OpenJpeg Encode Failed. Exception {0}{1}",
  508. e.Message, e.StackTrace);
  509. }
  510. return result;
  511. }
  512. }
  513. public Bitmap MergeBitMaps(Bitmap front, Bitmap back)
  514. {
  515. Bitmap joint;
  516. Graphics jG;
  517. joint = new Bitmap(back.Width, back.Height, PixelFormat.Format32bppArgb);
  518. using(jG = Graphics.FromImage(joint))
  519. {
  520. jG.DrawImage(back, 0, 0, back.Width, back.Height);
  521. jG.DrawImage(front, 0, 0, back.Width, back.Height);
  522. return joint;
  523. }
  524. }
  525. private void SetAlpha(ref Bitmap b, byte alpha)
  526. {
  527. for (int w = 0; w < b.Width; w++)
  528. {
  529. for (int h = 0; h < b.Height; h++)
  530. {
  531. b.SetPixel(w, h, Color.FromArgb(alpha, b.GetPixel(w, h)));
  532. }
  533. }
  534. }
  535. }
  536. #endregion
  537. }
  538. }