corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
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 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Collections.Generic;
29 using System.IO;
30 using System.Drawing;
31 using OpenMetaverse.Assets;
32  
33 namespace OpenMetaverse.Imaging
34 {
35 /// <summary>
36 /// A set of textures that are layered on texture of each other and "baked"
37 /// in to a single texture, for avatar appearances
38 /// </summary>
39 public class Baker
40 {
41 public static readonly UUID IMG_INVISIBLE = new UUID("3a367d1c-bef1-6d43-7595-e88c1e3aadb3");
42  
43 #region Properties
44 /// <summary>Final baked texture</summary>
45 public AssetTexture BakedTexture { get { return bakedTexture; } }
46 /// <summary>Component layers</summary>
47 public List<AppearanceManager.TextureData> Textures { get { return textures; } }
48 /// <summary>Width of the final baked image and scratchpad</summary>
49 public int BakeWidth { get { return bakeWidth; } }
50 /// <summary>Height of the final baked image and scratchpad</summary>
51 public int BakeHeight { get { return bakeHeight; } }
52 /// <summary>Bake type</summary>
53 public BakeType BakeType { get { return bakeType; } }
54 /// <summary>Is this one of the 3 skin bakes</summary>
55 private bool IsSkin { get { return bakeType == BakeType.Head || bakeType == BakeType.LowerBody || bakeType == BakeType.UpperBody; } }
56 #endregion
57  
58 #region Private fields
59 /// <summary>Final baked texture</summary>
60 private AssetTexture bakedTexture;
61 /// <summary>Component layers</summary>
62 private List<AppearanceManager.TextureData> textures = new List<AppearanceManager.TextureData>();
63 /// <summary>Width of the final baked image and scratchpad</summary>
64 private int bakeWidth;
65 /// <summary>Height of the final baked image and scratchpad</summary>
66 private int bakeHeight;
67 /// <summary>Bake type</summary>
68 private BakeType bakeType;
69 #endregion
70  
71 #region Constructor
72 /// <summary>
73 /// Default constructor
74 /// </summary>
75 /// <param name="bakeType">Bake type</param>
76 public Baker(BakeType bakeType)
77 {
78 this.bakeType = bakeType;
79  
80 if (bakeType == BakeType.Eyes)
81 {
82 bakeWidth = 128;
83 bakeHeight = 128;
84 }
85 else
86 {
87 bakeWidth = 512;
88 bakeHeight = 512;
89 }
90 }
91 #endregion
92  
93 #region Public methods
94 /// <summary>
95 /// Adds layer for baking
96 /// </summary>
97 /// <param name="tdata">TexturaData struct that contains texture and its params</param>
98 public void AddTexture(AppearanceManager.TextureData tdata)
99 {
100 lock (textures)
101 {
102 textures.Add(tdata);
103 }
104 }
105  
106 public void Bake()
107 {
108 bakedTexture = new AssetTexture(new ManagedImage(bakeWidth, bakeHeight,
109 ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump));
110  
111 // Base color for eye bake is white, color of layer0 for others
112 if (bakeType == BakeType.Eyes)
113 {
114 InitBakedLayerColor(Color4.White);
115 }
116 else if (textures.Count > 0)
117 {
118 InitBakedLayerColor(textures[0].Color);
119 }
120  
121 // Do we have skin texture?
122 bool SkinTexture = textures.Count > 0 && textures[0].Texture != null;
123  
124 if (bakeType == BakeType.Head)
125 {
126 DrawLayer(LoadResourceLayer("head_color.tga"), false);
127 AddAlpha(bakedTexture.Image, LoadResourceLayer("head_alpha.tga"));
128 MultiplyLayerFromAlpha(bakedTexture.Image, LoadResourceLayer("head_skingrain.tga"));
129 }
130  
131 if (!SkinTexture && bakeType == BakeType.UpperBody)
132 {
133 DrawLayer(LoadResourceLayer("upperbody_color.tga"), false);
134 }
135  
136 if (!SkinTexture && bakeType == BakeType.LowerBody)
137 {
138 DrawLayer(LoadResourceLayer("lowerbody_color.tga"), false);
139 }
140  
141 ManagedImage alphaWearableTexture = null;
142  
143 // Layer each texture on top of one other, applying alpha masks as we go
144 for (int i = 0; i < textures.Count; i++)
145 {
146 // Skip if we have no texture on this layer
147 if (textures[i].Texture == null) continue;
148  
149 // Is this Alpha wearable and does it have an alpha channel?
150 if (textures[i].TextureIndex >= AvatarTextureIndex.LowerAlpha &&
151 textures[i].TextureIndex <= AvatarTextureIndex.HairAlpha)
152 {
153 if (textures[i].Texture.Image.Alpha != null)
154 {
155 alphaWearableTexture = textures[i].Texture.Image.Clone();
156 }
157 else if (textures[i].TextureID == IMG_INVISIBLE)
158 {
159 alphaWearableTexture = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha);
160 }
161 continue;
162 }
163  
164 // Don't draw skin and tattoo on head bake first
165 // For head bake the skin and texture are drawn last, go figure
166 if (bakeType == BakeType.Head && (i == 0 || i == 1)) continue;
167  
168 ManagedImage texture = textures[i].Texture.Image.Clone();
169 //File.WriteAllBytes(bakeType + "-texture-layer-" + i + ".tga", texture.ExportTGA());
170  
171 // Resize texture to the size of baked layer
172 // FIXME: if texture is smaller than the layer, don't stretch it, tile it
173 if (texture.Width != bakeWidth || texture.Height != bakeHeight)
174 {
175 try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
176 catch (Exception) { continue; }
177 }
178  
179 // Special case for hair layer for the head bake
180 // If we don't have skin texture, we discard hair alpha
181 // and apply hair(i==2) pattern over the texture
182 if (!SkinTexture && bakeType == BakeType.Head && i == 2)
183 {
184 if (texture.Alpha != null)
185 {
186 for (int j = 0; j < texture.Alpha.Length; j++) texture.Alpha[j] = (byte)255;
187 }
188 MultiplyLayerFromAlpha(texture, LoadResourceLayer("head_hair.tga"));
189 }
190  
191 // Aply tint and alpha masks except for skin that has a texture
192 // on layer 0 which always overrides other skin settings
193 if (!(IsSkin && i == 0))
194 {
195 ApplyTint(texture, textures[i].Color);
196  
197 // For hair bake, we skip all alpha masks
198 // and use one from the texture, for both
199 // alpha and morph layers
200 if (bakeType == BakeType.Hair)
201 {
202 if (texture.Alpha != null)
203 {
204 bakedTexture.Image.Bump = texture.Alpha;
205 }
206 else
207 {
208 for (int j = 0; j < bakedTexture.Image.Bump.Length; j++) bakedTexture.Image.Bump[j] = byte.MaxValue;
209 }
210 }
211 // Apply parametrized alpha masks
212 else if (textures[i].AlphaMasks != null && textures[i].AlphaMasks.Count > 0)
213 {
214 // Combined mask for the layer, fully transparent to begin with
215 ManagedImage combinedMask = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha);
216  
217 int addedMasks = 0;
218  
219 // First add mask in normal blend mode
220 foreach (KeyValuePair<VisualAlphaParam, float> kvp in textures[i].AlphaMasks)
221 {
222 if (!MaskBelongsToBake(kvp.Key.TGAFile)) continue;
223  
224 if (kvp.Key.MultiplyBlend == false && (kvp.Value > 0f || !kvp.Key.SkipIfZero))
225 {
226 ApplyAlpha(combinedMask, kvp.Key, kvp.Value);
227 //File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA());
228 addedMasks++;
229 }
230 }
231  
232 // If there were no mask in normal blend mode make aplha fully opaque
233 if (addedMasks == 0) for (int l = 0; l < combinedMask.Alpha.Length; l++) combinedMask.Alpha[l] = 255;
234  
235 // Add masks in multiply blend mode
236 foreach (KeyValuePair<VisualAlphaParam, float> kvp in textures[i].AlphaMasks)
237 {
238 if (!MaskBelongsToBake(kvp.Key.TGAFile)) continue;
239  
240 if (kvp.Key.MultiplyBlend == true && (kvp.Value > 0f || !kvp.Key.SkipIfZero))
241 {
242 ApplyAlpha(combinedMask, kvp.Key, kvp.Value);
243 //File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA());
244 addedMasks++;
245 }
246 }
247  
248 if (addedMasks > 0)
249 {
250 // Apply combined alpha mask to the cloned texture
251 AddAlpha(texture, combinedMask);
252 }
253  
254 // Is this layer used for morph mask? If it is, use its
255 // alpha as the morth for the whole bake
256 if (Textures[i].TextureIndex == AppearanceManager.MorphLayerForBakeType(bakeType))
257 {
258 bakedTexture.Image.Bump = texture.Alpha;
259 }
260  
261 //File.WriteAllBytes(bakeType + "-masked-texture-" + i + ".tga", texture.ExportTGA());
262 }
263 }
264  
265 bool useAlpha = i == 0 && (BakeType == BakeType.Skirt || BakeType == BakeType.Hair);
266 DrawLayer(texture, useAlpha);
267 //File.WriteAllBytes(bakeType + "-layer-" + i + ".tga", texture.ExportTGA());
268 }
269  
270 // For head and tattoo, we add skin last
271 if (IsSkin && bakeType == BakeType.Head)
272 {
273 ManagedImage texture;
274 if (textures[0].Texture != null)
275 {
276 texture = textures[0].Texture.Image.Clone();
277 if (texture.Width != bakeWidth || texture.Height != bakeHeight)
278 {
279 try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
280 catch (Exception) { }
281 }
282 DrawLayer(texture, false);
283 }
284  
285 // Add head tattoo here (if available, order-dependant)
286 if (textures.Count > 1 && textures[1].Texture != null)
287 {
288 texture = textures[1].Texture.Image.Clone();
289 if (texture.Width != bakeWidth || texture.Height != bakeHeight)
290 {
291 try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
292 catch (Exception) { }
293 }
294 DrawLayer(texture, false);
295 }
296 }
297  
298 // Apply any alpha wearable textures to make parts of the avatar disappear
299 if (alphaWearableTexture != null)
300 {
301 AddAlpha(bakedTexture.Image, alphaWearableTexture);
302 }
303  
304 // We are done, encode asset for finalized bake
305 bakedTexture.Encode();
306 //File.WriteAllBytes(bakeType + ".tga", bakedTexture.Image.ExportTGA());
307 }
308  
309 private static object ResourceSync = new object();
310  
311 public static ManagedImage LoadResourceLayer(string fileName)
312 {
313 try
314 {
315 Bitmap bitmap = null;
316 lock (ResourceSync)
317 {
318 using (Stream stream = Helpers.GetResourceStream(fileName, Settings.RESOURCE_DIR))
319 {
320 bitmap = LoadTGAClass.LoadTGA(stream);
321 }
322 }
323 if (bitmap == null)
324 {
325 Logger.Log(String.Format("Failed loading resource file: {0}", fileName), Helpers.LogLevel.Error);
326 return null;
327 }
328 else
329 {
330 ManagedImage image = new ManagedImage(bitmap);
331 bitmap.Dispose();
332 return image;
333 }
334 }
335 catch (Exception e)
336 {
337 Logger.Log(String.Format("Failed loading resource file: {0} ({1})", fileName, e.Message),
338 Helpers.LogLevel.Error, e);
339 return null;
340 }
341 }
342  
343 /// <summary>
344 /// Converts avatar texture index (face) to Bake type
345 /// </summary>
346 /// <param name="index">Face number (AvatarTextureIndex)</param>
347 /// <returns>BakeType, layer to which this texture belongs to</returns>
348 public static BakeType BakeTypeFor(AvatarTextureIndex index)
349 {
350 switch (index)
351 {
352 case AvatarTextureIndex.HeadBodypaint:
353 return BakeType.Head;
354  
355 case AvatarTextureIndex.UpperBodypaint:
356 case AvatarTextureIndex.UpperGloves:
357 case AvatarTextureIndex.UpperUndershirt:
358 case AvatarTextureIndex.UpperShirt:
359 case AvatarTextureIndex.UpperJacket:
360 return BakeType.UpperBody;
361  
362 case AvatarTextureIndex.LowerBodypaint:
363 case AvatarTextureIndex.LowerUnderpants:
364 case AvatarTextureIndex.LowerSocks:
365 case AvatarTextureIndex.LowerShoes:
366 case AvatarTextureIndex.LowerPants:
367 case AvatarTextureIndex.LowerJacket:
368 return BakeType.LowerBody;
369  
370 case AvatarTextureIndex.EyesIris:
371 return BakeType.Eyes;
372  
373 case AvatarTextureIndex.Skirt:
374 return BakeType.Skirt;
375  
376 case AvatarTextureIndex.Hair:
377 return BakeType.Hair;
378  
379 default:
380 return BakeType.Unknown;
381 }
382 }
383 #endregion
384  
385 #region Private layer compositing methods
386  
387 private bool MaskBelongsToBake(string mask)
388 {
389 if ((bakeType == BakeType.LowerBody && mask.Contains("upper"))
390 || (bakeType == BakeType.LowerBody && mask.Contains("shirt"))
391 || (bakeType == BakeType.UpperBody && mask.Contains("lower")))
392 {
393 return false;
394 }
395 else
396 {
397 return true;
398 }
399 }
400  
401 private bool DrawLayer(ManagedImage source, bool addSourceAlpha)
402 {
403 if (source == null) return false;
404  
405 bool sourceHasColor;
406 bool sourceHasAlpha;
407 bool sourceHasBump;
408 int i = 0;
409  
410 sourceHasColor = ((source.Channels & ManagedImage.ImageChannels.Color) != 0 &&
411 source.Red != null && source.Green != null && source.Blue != null);
412 sourceHasAlpha = ((source.Channels & ManagedImage.ImageChannels.Alpha) != 0 && source.Alpha != null);
413 sourceHasBump = ((source.Channels & ManagedImage.ImageChannels.Bump) != 0 && source.Bump != null);
414  
415 addSourceAlpha = (addSourceAlpha && sourceHasAlpha);
416  
417 byte alpha = Byte.MaxValue;
418 byte alphaInv = (byte)(Byte.MaxValue - alpha);
419  
420 byte[] bakedRed = bakedTexture.Image.Red;
421 byte[] bakedGreen = bakedTexture.Image.Green;
422 byte[] bakedBlue = bakedTexture.Image.Blue;
423 byte[] bakedAlpha = bakedTexture.Image.Alpha;
424 byte[] bakedBump = bakedTexture.Image.Bump;
425  
426 byte[] sourceRed = source.Red;
427 byte[] sourceGreen = source.Green;
428 byte[] sourceBlue = source.Blue;
429 byte[] sourceAlpha = sourceHasAlpha ? source.Alpha : null;
430 byte[] sourceBump = sourceHasBump ? source.Bump : null;
431  
432 for (int y = 0; y < bakeHeight; y++)
433 {
434 for (int x = 0; x < bakeWidth; x++)
435 {
436 if (sourceHasAlpha)
437 {
438 alpha = sourceAlpha[i];
439 alphaInv = (byte)(Byte.MaxValue - alpha);
440 }
441  
442 if (sourceHasColor)
443 {
444 bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8);
445 bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8);
446 bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8);
447 }
448  
449 if (addSourceAlpha)
450 {
451 if (sourceAlpha[i] < bakedAlpha[i])
452 {
453 bakedAlpha[i] = sourceAlpha[i];
454 }
455 }
456  
457 if (sourceHasBump)
458 bakedBump[i] = sourceBump[i];
459  
460 ++i;
461 }
462 }
463  
464 return true;
465 }
466  
467 /// <summary>
468 /// Make sure images exist, resize source if needed to match the destination
469 /// </summary>
470 /// <param name="dest">Destination image</param>
471 /// <param name="src">Source image</param>
472 /// <returns>Sanitization was succefull</returns>
473 private bool SanitizeLayers(ManagedImage dest, ManagedImage src)
474 {
475 if (dest == null || src == null) return false;
476  
477 if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0)
478 {
479 dest.ConvertChannels(dest.Channels | ManagedImage.ImageChannels.Alpha);
480 }
481  
482 if (dest.Width != src.Width || dest.Height != src.Height)
483 {
484 try { src.ResizeNearestNeighbor(dest.Width, dest.Height); }
485 catch (Exception) { return false; }
486 }
487  
488 return true;
489 }
490  
491  
492 private void ApplyAlpha(ManagedImage dest, VisualAlphaParam param, float val)
493 {
494 ManagedImage src = LoadResourceLayer(param.TGAFile);
495  
496 if (dest == null || src == null || src.Alpha == null) return;
497  
498 if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0)
499 {
500 dest.ConvertChannels(ManagedImage.ImageChannels.Alpha | dest.Channels);
501 }
502  
503 if (dest.Width != src.Width || dest.Height != src.Height)
504 {
505 try { src.ResizeNearestNeighbor(dest.Width, dest.Height); }
506 catch (Exception) { return; }
507 }
508  
509 for (int i = 0; i < dest.Alpha.Length; i++)
510 {
511 byte alpha = src.Alpha[i] <= ((1 - val) * 255) ? (byte)0 : (byte)255;
512 if (alpha != 255)
513 {
514 }
515 if (param.MultiplyBlend)
516 {
517 dest.Alpha[i] = (byte)((dest.Alpha[i] * alpha) >> 8);
518 }
519 else
520 {
521 if (alpha > dest.Alpha[i])
522 {
523 dest.Alpha[i] = alpha;
524 }
525 }
526 }
527 }
528  
529 private void AddAlpha(ManagedImage dest, ManagedImage src)
530 {
531 if (!SanitizeLayers(dest, src)) return;
532  
533 for (int i = 0; i < dest.Alpha.Length; i++)
534 {
535 if (src.Alpha[i] < dest.Alpha[i])
536 {
537 dest.Alpha[i] = src.Alpha[i];
538 }
539 }
540 }
541  
542 private void MultiplyLayerFromAlpha(ManagedImage dest, ManagedImage src)
543 {
544 if (!SanitizeLayers(dest, src)) return;
545  
546 for (int i = 0; i < dest.Red.Length; i++)
547 {
548 dest.Red[i] = (byte)((dest.Red[i] * src.Alpha[i]) >> 8);
549 dest.Green[i] = (byte)((dest.Green[i] * src.Alpha[i]) >> 8);
550 dest.Blue[i] = (byte)((dest.Blue[i] * src.Alpha[i]) >> 8);
551 }
552 }
553  
554 private void ApplyTint(ManagedImage dest, Color4 src)
555 {
556 if (dest == null) return;
557  
558 for (int i = 0; i < dest.Red.Length; i++)
559 {
560 dest.Red[i] = (byte)((dest.Red[i] * ((byte)(src.R * byte.MaxValue))) >> 8);
561 dest.Green[i] = (byte)((dest.Green[i] * ((byte)(src.G * byte.MaxValue))) >> 8);
562 dest.Blue[i] = (byte)((dest.Blue[i] * ((byte)(src.B * byte.MaxValue))) >> 8);
563 }
564 }
565  
566 /// <summary>
567 /// Fills a baked layer as a solid *appearing* color. The colors are
568 /// subtly dithered on a 16x16 grid to prevent the JPEG2000 stage from
569 /// compressing it too far since it seems to cause upload failures if
570 /// the image is a pure solid color
571 /// </summary>
572 /// <param name="color">Color of the base of this layer</param>
573 private void InitBakedLayerColor(Color4 color)
574 {
575 InitBakedLayerColor(color.R, color.G, color.B);
576 }
577  
578 /// <summary>
579 /// Fills a baked layer as a solid *appearing* color. The colors are
580 /// subtly dithered on a 16x16 grid to prevent the JPEG2000 stage from
581 /// compressing it too far since it seems to cause upload failures if
582 /// the image is a pure solid color
583 /// </summary>
584 /// <param name="r">Red value</param>
585 /// <param name="g">Green value</param>
586 /// <param name="b">Blue value</param>
587 private void InitBakedLayerColor(float r, float g, float b)
588 {
589 byte rByte = Utils.FloatToByte(r, 0f, 1f);
590 byte gByte = Utils.FloatToByte(g, 0f, 1f);
591 byte bByte = Utils.FloatToByte(b, 0f, 1f);
592  
593 byte rAlt, gAlt, bAlt;
594  
595 rAlt = rByte;
596 gAlt = gByte;
597 bAlt = bByte;
598  
599 if (rByte < Byte.MaxValue)
600 rAlt++;
601 else rAlt--;
602  
603 if (gByte < Byte.MaxValue)
604 gAlt++;
605 else gAlt--;
606  
607 if (bByte < Byte.MaxValue)
608 bAlt++;
609 else bAlt--;
610  
611 int i = 0;
612  
613 byte[] red = bakedTexture.Image.Red;
614 byte[] green = bakedTexture.Image.Green;
615 byte[] blue = bakedTexture.Image.Blue;
616 byte[] alpha = bakedTexture.Image.Alpha;
617 byte[] bump = bakedTexture.Image.Bump;
618  
619 for (int y = 0; y < bakeHeight; y++)
620 {
621 for (int x = 0; x < bakeWidth; x++)
622 {
623 if (((x ^ y) & 0x10) == 0)
624 {
625 red[i] = rAlt;
626 green[i] = gByte;
627 blue[i] = bByte;
628 alpha[i] = Byte.MaxValue;
629 bump[i] = 0;
630 }
631 else
632 {
633 red[i] = rByte;
634 green[i] = gAlt;
635 blue[i] = bAlt;
636 alpha[i] = Byte.MaxValue;
637 bump[i] = 0;
638 }
639  
640 ++i;
641 }
642 }
643  
644 }
645 #endregion
646 }
647 }