/// <summary> /// Make sure images exist, resize source if needed to match the destination /// </summary> /// <param name="dest">Destination image</param> /// <param name="src">Source image</param> /// <returns>Sanitization was succefull</returns> private bool SanitizeLayers(ManagedImage dest, ManagedImage src) { if (dest == null || src == null) { return(false); } if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0) { dest.ConvertChannels(dest.Channels | ManagedImage.ImageChannels.Alpha); } if (dest.Width != src.Width || dest.Height != src.Height) { try { src.ResizeNearestNeighbor(dest.Width, dest.Height); } catch (Exception) { return(false); } } return(true); }
private void ApplyAlpha(ManagedImage dest, VisualAlphaParam param, float val) { ManagedImage src = LoadResourceLayer(param.TGAFile); if (dest == null || src == null || src.Alpha == null) { return; } if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0) { dest.ConvertChannels(ManagedImage.ImageChannels.Alpha | dest.Channels); } if (dest.Width != src.Width || dest.Height != src.Height) { try { src.ResizeNearestNeighbor(dest.Width, dest.Height); } catch (Exception) { return; } } for (int i = 0; i < dest.Alpha.Length; i++) { byte alpha = src.Alpha[i] <= ((1 - val) * 255) ? (byte)0 : (byte)255; if (alpha != 255) { } if (param.MultiplyBlend) { dest.Alpha[i] = (byte)((dest.Alpha[i] * alpha) >> 8); } else { if (alpha > dest.Alpha[i]) { dest.Alpha[i] = alpha; } } } }
/// <summary> /// Make sure images exist, resize source if needed to match the destination /// </summary> /// <param name="dest">Destination image</param> /// <param name="src">Source image</param> /// <returns>Sanitization was succefull</returns> private bool SanitizeLayers(ManagedImage dest, ManagedImage src) { if (dest == null || src == null) return false; if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0) { dest.ConvertChannels(dest.Channels | ManagedImage.ImageChannels.Alpha); } if (dest.Width != src.Width || dest.Height != src.Height) { try { src.ResizeNearestNeighbor(dest.Width, dest.Height); } catch (Exception) { return false; } } return true; }
public void Bake() { bakedTexture = new AssetTexture(new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump)); // Base color for eye bake is white, color of layer0 for others if (bakeType == BakeType.Eyes) { InitBakedLayerColor(Color4.White); } else if (textures.Count > 0) { InitBakedLayerColor(textures[0].Color); } // Do we have skin texture? bool SkinTexture = textures.Count > 0 && textures[0].Texture != null; if (bakeType == BakeType.Head) { DrawLayer(LoadResourceLayer("head_color.tga"), false); AddAlpha(bakedTexture.Image, LoadResourceLayer("head_alpha.tga")); MultiplyLayerFromAlpha(bakedTexture.Image, LoadResourceLayer("head_skingrain.tga")); } if (!SkinTexture && bakeType == BakeType.UpperBody) { DrawLayer(LoadResourceLayer("upperbody_color.tga"), false); } if (!SkinTexture && bakeType == BakeType.LowerBody) { DrawLayer(LoadResourceLayer("lowerbody_color.tga"), false); } ManagedImage alphaWearableTexture = null; // Layer each texture on top of one other, applying alpha masks as we go for (int i = 0; i < textures.Count; i++) { // Skip if we have no texture on this layer if (textures[i].Texture == null) { continue; } // Is this Alpha wearable and does it have an alpha channel? if (textures[i].TextureIndex >= AvatarTextureIndex.LowerAlpha && textures[i].TextureIndex <= AvatarTextureIndex.HairAlpha) { if (textures[i].Texture.Image.Alpha != null) { alphaWearableTexture = textures[i].Texture.Image.Clone(); } else if (textures[i].TextureID == IMG_INVISIBLE) { alphaWearableTexture = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha); } continue; } // Don't draw skin and tattoo on head bake first // For head bake the skin and texture are drawn last, go figure if (bakeType == BakeType.Head && (i == 0 || i == 1)) { continue; } ManagedImage texture = textures[i].Texture.Image.Clone(); //File.WriteAllBytes(bakeType + "-texture-layer-" + i + ".tga", texture.ExportTGA()); // Resize texture to the size of baked layer // FIXME: if texture is smaller than the layer, don't stretch it, tile it if (texture.Width != bakeWidth || texture.Height != bakeHeight) { try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); } catch (Exception) { continue; } } // Special case for hair layer for the head bake // If we don't have skin texture, we discard hair alpha // and apply hair(i==2) pattern over the texture if (!SkinTexture && bakeType == BakeType.Head && i == 2) { if (texture.Alpha != null) { for (int j = 0; j < texture.Alpha.Length; j++) { texture.Alpha[j] = (byte)255; } } MultiplyLayerFromAlpha(texture, LoadResourceLayer("head_hair.tga")); } // Aply tint and alpha masks except for skin that has a texture // on layer 0 which always overrides other skin settings if (!(IsSkin && i == 0)) { ApplyTint(texture, textures[i].Color); // For hair bake, we skip all alpha masks // and use one from the texture, for both // alpha and morph layers if (bakeType == BakeType.Hair) { if (texture.Alpha != null) { bakedTexture.Image.Bump = texture.Alpha; } else { for (int j = 0; j < bakedTexture.Image.Bump.Length; j++) { bakedTexture.Image.Bump[j] = byte.MaxValue; } } } // Apply parametrized alpha masks else if (textures[i].AlphaMasks != null && textures[i].AlphaMasks.Count > 0) { // Combined mask for the layer, fully transparent to begin with ManagedImage combinedMask = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha); int addedMasks = 0; // First add mask in normal blend mode foreach (KeyValuePair <VisualAlphaParam, float> kvp in textures[i].AlphaMasks) { if (!MaskBelongsToBake(kvp.Key.TGAFile)) { continue; } if (kvp.Key.MultiplyBlend == false && (kvp.Value > 0f || !kvp.Key.SkipIfZero)) { ApplyAlpha(combinedMask, kvp.Key, kvp.Value); //File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA()); addedMasks++; } } // If there were no mask in normal blend mode make aplha fully opaque if (addedMasks == 0) { for (int l = 0; l < combinedMask.Alpha.Length; l++) { combinedMask.Alpha[l] = 255; } } // Add masks in multiply blend mode foreach (KeyValuePair <VisualAlphaParam, float> kvp in textures[i].AlphaMasks) { if (!MaskBelongsToBake(kvp.Key.TGAFile)) { continue; } if (kvp.Key.MultiplyBlend == true && (kvp.Value > 0f || !kvp.Key.SkipIfZero)) { ApplyAlpha(combinedMask, kvp.Key, kvp.Value); //File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA()); addedMasks++; } } if (addedMasks > 0) { // Apply combined alpha mask to the cloned texture AddAlpha(texture, combinedMask); } // Is this layer used for morph mask? If it is, use its // alpha as the morth for the whole bake if (Textures[i].TextureIndex == AppearanceManager.MorphLayerForBakeType(bakeType)) { bakedTexture.Image.Bump = texture.Alpha; } //File.WriteAllBytes(bakeType + "-masked-texture-" + i + ".tga", texture.ExportTGA()); } } bool useAlpha = i == 0 && (BakeType == BakeType.Skirt || BakeType == BakeType.Hair); DrawLayer(texture, useAlpha); //File.WriteAllBytes(bakeType + "-layer-" + i + ".tga", texture.ExportTGA()); } // For head and tattoo, we add skin last if (IsSkin && bakeType == BakeType.Head) { ManagedImage texture; if (textures[0].Texture != null) { texture = textures[0].Texture.Image.Clone(); if (texture.Width != bakeWidth || texture.Height != bakeHeight) { try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); } catch (Exception) { } } DrawLayer(texture, false); } // Add head tattoo here (if available, order-dependant) if (textures.Count > 1 && textures[1].Texture != null) { texture = textures[1].Texture.Image.Clone(); if (texture.Width != bakeWidth || texture.Height != bakeHeight) { try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); } catch (Exception) { } } DrawLayer(texture, false); } } // Apply any alpha wearable textures to make parts of the avatar disappear if (alphaWearableTexture != null) { AddAlpha(bakedTexture.Image, alphaWearableTexture); } // We are done, encode asset for finalized bake bakedTexture.Encode(); //File.WriteAllBytes(bakeType + ".tga", bakedTexture.Image.ExportTGA()); }
private bool DrawLayer(ManagedImage source, bool useSourceAlpha) { bool sourceHasColor; bool sourceHasAlpha; bool sourceHasBump; int i = 0; sourceHasColor = ((source.Channels & ManagedImage.ImageChannels.Color) != 0 && source.Red != null && source.Green != null && source.Blue != null); sourceHasAlpha = ((source.Channels & ManagedImage.ImageChannels.Alpha) != 0 && source.Alpha != null); sourceHasBump = ((source.Channels & ManagedImage.ImageChannels.Bump) != 0 && source.Bump != null); useSourceAlpha = (useSourceAlpha && sourceHasAlpha); if (source.Width != _bakeWidth || source.Height != _bakeHeight) { try { source.ResizeNearestNeighbor(_bakeWidth, _bakeHeight); } catch { return(false); } } int alpha = 255; //int alphaInv = 255 - alpha; int alphaInv = 256 - alpha; byte[] bakedRed = _bakedTexture.Image.Red; byte[] bakedGreen = _bakedTexture.Image.Green; byte[] bakedBlue = _bakedTexture.Image.Blue; byte[] bakedAlpha = _bakedTexture.Image.Alpha; byte[] bakedBump = _bakedTexture.Image.Bump; byte[] sourceRed = source.Red; byte[] sourceGreen = source.Green; byte[] sourceBlue = source.Blue; byte[] sourceAlpha = null; byte[] sourceBump = null; if (sourceHasAlpha) { sourceAlpha = source.Alpha; } if (sourceHasBump) { sourceBump = source.Bump; } for (int y = 0; y < _bakeHeight; y++) { for (int x = 0; x < _bakeWidth; x++) { if (sourceHasAlpha) { alpha = sourceAlpha[i]; //alphaInv = 255 - alpha; alphaInv = 256 - alpha; } if (sourceHasColor) { bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8); bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8); bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8); } if (useSourceAlpha) { bakedAlpha[i] = sourceAlpha[i]; } if (sourceHasBump) { bakedBump[i] = sourceBump[i]; } ++i; } } return(true); }
private bool DrawLayer(ManagedImage source, bool useSourceAlpha) { bool sourceHasColor; bool sourceHasAlpha; bool sourceHasBump; int i = 0; sourceHasColor = ((source.Channels & ManagedImage.ImageChannels.Color) != 0 && source.Red != null && source.Green != null && source.Blue != null); sourceHasAlpha = ((source.Channels & ManagedImage.ImageChannels.Alpha) != 0 && source.Alpha != null); sourceHasBump = ((source.Channels & ManagedImage.ImageChannels.Bump) != 0 && source.Bump != null); useSourceAlpha = (useSourceAlpha && sourceHasAlpha); if (source.Width != _bakeWidth || source.Height != _bakeHeight) { try { source.ResizeNearestNeighbor(_bakeWidth, _bakeHeight); } catch { return false; } } int alpha = 255; //int alphaInv = 255 - alpha; int alphaInv = 256 - alpha; byte[] bakedRed = _bakedTexture.Image.Red; byte[] bakedGreen = _bakedTexture.Image.Green; byte[] bakedBlue = _bakedTexture.Image.Blue; byte[] bakedAlpha = _bakedTexture.Image.Alpha; byte[] bakedBump = _bakedTexture.Image.Bump; byte[] sourceRed = source.Red; byte[] sourceGreen = source.Green; byte[] sourceBlue = source.Blue; byte[] sourceAlpha = null; byte[] sourceBump = null; if (sourceHasAlpha) sourceAlpha = source.Alpha; if (sourceHasBump) sourceBump = source.Bump; for (int y = 0; y < _bakeHeight; y++) { for (int x = 0; x < _bakeWidth; x++) { if (sourceHasAlpha) { alpha = sourceAlpha[i]; //alphaInv = 255 - alpha; alphaInv = 256 - alpha; } if (sourceHasColor) { bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8); bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8); bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8); } if (useSourceAlpha) bakedAlpha[i] = sourceAlpha[i]; if (sourceHasBump) bakedBump[i] = sourceBump[i]; ++i; } } return true; }