소스 검색

Layered Image Asset

Adds the ability to layer multiple textures on top of an existing image asset and then use them as a single texture.
Peter Robinson 3 년 전
부모
커밋
699804a397

+ 452 - 9
engine/source/2d/assets/ImageAsset.cc

@@ -117,6 +117,12 @@ static StringTableEntry cellWidthName               = StringTable->insert( "Widt
 static StringTableEntry cellHeightName              = StringTable->insert( "Height" );
 static StringTableEntry cellHeightName              = StringTable->insert( "Height" );
 static StringTableEntry cellNameEntryName           = StringTable->insert( "Name" );
 static StringTableEntry cellNameEntryName           = StringTable->insert( "Name" );
 
 
+static StringTableEntry imageLayerCustomNodeName    = StringTable->insert("ImageLayers");
+static StringTableEntry layerNodeName               = StringTable->insert("ImageLayer");
+static StringTableEntry layerImageFile              = StringTable->insert("ImageFile");
+static StringTableEntry layerPositionName           = StringTable->insert("Position");
+static StringTableEntry layerBlendColorName         = StringTable->insert("BlendColor");
+
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
 static EnumTable::Enums textureFilterLookup[] =
 static EnumTable::Enums textureFilterLookup[] =
@@ -174,11 +180,14 @@ ImageAsset::ImageAsset() :  mImageFile(StringTable->EmptyString),
                             mCellWidth(0),
                             mCellWidth(0),
                             mCellHeight(0),
                             mCellHeight(0),
 
 
+                            mBlendColor(1.0f, 1.0f, 1.0f),
+
                             mImageTextureHandle(NULL)
                             mImageTextureHandle(NULL)
 {
 {
     // Set Vector Associations.
     // Set Vector Associations.
     VECTOR_SET_ASSOCIATION( mFrames );
     VECTOR_SET_ASSOCIATION( mFrames );
     VECTOR_SET_ASSOCIATION( mExplicitFrames );
     VECTOR_SET_ASSOCIATION( mExplicitFrames );
+    VECTOR_SET_ASSOCIATION(mImageLayers);
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
@@ -201,6 +210,7 @@ void ImageAsset::initPersistFields()
     addProtectedField("FilterMode", TypeEnum, Offset(mLocalFilterMode, ImageAsset), &setFilterMode, &defaultProtectedGetFn, &writeFilterMode, 1, &textureFilterTable);   
     addProtectedField("FilterMode", TypeEnum, Offset(mLocalFilterMode, ImageAsset), &setFilterMode, &defaultProtectedGetFn, &writeFilterMode, 1, &textureFilterTable);   
     addProtectedField("ExplicitMode", TypeBool, Offset(mExplicitMode, ImageAsset), &setExplicitMode, &defaultProtectedGetFn, &writeExplicitMode, "");
     addProtectedField("ExplicitMode", TypeBool, Offset(mExplicitMode, ImageAsset), &setExplicitMode, &defaultProtectedGetFn, &writeExplicitMode, "");
     addProtectedField("CellRowOrder", TypeBool, Offset(mCellRowOrder, ImageAsset), &setCellRowOrder, &defaultProtectedGetFn, &writeCellRowOrder, "If true, cell number are applied left-to-right, top-to-bottom.");
     addProtectedField("CellRowOrder", TypeBool, Offset(mCellRowOrder, ImageAsset), &setCellRowOrder, &defaultProtectedGetFn, &writeCellRowOrder, "If true, cell number are applied left-to-right, top-to-bottom.");
+    addProtectedField("BlendColor", TypeColorF, Offset(mBlendColor, ImageAsset), &setBlendColor, &defaultProtectedGetFn, &writeBlendColor, "Changes the base color of the texture. You should only use this if you are using layers.");
     endGroup("Image Fields");
     endGroup("Image Fields");
 
 
     addGroup("X Values");
     addGroup("X Values");
@@ -1069,8 +1079,18 @@ void ImageAsset::initializeAsset( void )
     // Ensure the image-file is expanded.
     // Ensure the image-file is expanded.
     mImageFile = expandAssetFilePath( mImageFile );
     mImageFile = expandAssetFilePath( mImageFile );
 
 
+    // Time to load the layer files too.
+    for (auto& layer : mImageLayers)
+    {
+        const char* path = expandAssetFilePath(layer.mImageFile);
+        layer.LoadImage(path);
+    }
+
     // Calculate the image.
     // Calculate the image.
     calculateImage();
     calculateImage();
+
+    // Redraw the image.
+    redrawImage();
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
@@ -1086,6 +1106,9 @@ void ImageAsset::onAssetRefresh( void )
     
     
     // Compile image.
     // Compile image.
     calculateImage();
     calculateImage();
+
+    // Redraw the image.
+    redrawImage();
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -1126,7 +1149,7 @@ void ImageAsset::calculateImage( void )
         TextureManager::refresh( mImageFile );
         TextureManager::refresh( mImageFile );
 
 
     // Get image texture.
     // Get image texture.
-    mImageTextureHandle.set( mImageFile, TextureHandle::BitmapTexture, true, getForce16Bit() );
+    mImageTextureHandle.set( mImageFile, mImageLayers.size() > 0 ? TextureHandle::BitmapKeepTexture : TextureHandle::BitmapTexture, true, getForce16Bit() );
 
 
     // Is the texture valid?
     // Is the texture valid?
     if ( mImageTextureHandle.IsNull() )
     if ( mImageTextureHandle.IsNull() )
@@ -1377,6 +1400,45 @@ void ImageAsset::calculateExplicitMode( void )
     }
     }
 }
 }
 
 
+void ImageAsset::completeLayerChange(const bool doRedraw)
+{
+    if (doRedraw)
+    {
+        redrawImage();
+
+        if (getOwned())
+        {
+            refreshAsset();
+        }
+    }
+}
+
+void ImageAsset::redrawImage()
+{
+    if (mImageLayers.size() > 0)
+    {
+        GBitmap* map = mImageTextureHandle.getBitmap();
+        if (map == nullptr)
+        {
+            // This should reload the texture and keep it in memory
+            mImageTextureHandle.set(mImageFile, TextureHandle::BitmapKeepTexture, true, getForce16Bit());
+            map = mImageTextureHandle.getBitmap();
+            if (map == nullptr)
+            {
+                // We still can't come up with a bitmap, so warn and return
+                Con::warnf("ImageAsset::redrawImage() - Unable to load bitmap for image '%s'.", mImageFile);
+            }
+        }
+        map->clearImage();
+        for (auto &layer : mImageLayers)
+        {
+            map->mergeLayer(layer.mPosition, layer.mBitmap, layer.mBlendColor);
+        }
+
+        mImageTextureHandle.refresh();
+    }
+}
+
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
 bool ImageAsset::setFilterMode( void* obj, const char* data )
 bool ImageAsset::setFilterMode( void* obj, const char* data )
@@ -1385,6 +1447,236 @@ bool ImageAsset::setFilterMode( void* obj, const char* data )
     return false;
     return false;
 }
 }
 
 
+void ImageAsset::addLayer(const char* imagePath, const Point2I position, const ColorF blendColor, const bool doRedraw)
+{
+    insertLayer(mImageLayers.size(), imagePath, position, blendColor, doRedraw);
+}
+
+void ImageAsset::insertLayer(const U32 index, const char* imagePath, const Point2I position, const ColorF blendColor, const bool doRedraw)
+{
+    if (mImageLayers.size() == 0)
+    {
+        Point2I p(0, 0);
+        ImageLayer baseLayer(mImageFile, p, mBlendColor);
+        mImageLayers.push_back(baseLayer);
+
+        if (getOwned())
+        {
+            baseLayer.LoadImage(mImageFile);
+        }
+    }
+    ImageLayer layer(imagePath, position, blendColor);
+
+    if (getOwned())
+    {
+        const char* path = expandAssetFilePath(layer.mImageFile);
+        layer.LoadImage(path);
+    }
+
+    if (index == 0 || index >= mImageLayers.size())
+    {
+        mImageLayers.push_back(layer);
+    }
+    else
+    {
+        typeImageLayerVector::iterator iter = mImageLayers.begin() + index;
+        mImageLayers.insert(iter, layer);
+    }
+
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::removeLayer(const U32 index, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::removeLayer() - Invalid Index of %d.", index);
+        return;
+    }
+
+    mImageLayers.erase(index);
+
+    if (mImageLayers.size() == 1)
+    {
+        mImageLayers.clear();
+    }
+
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::moveLayerForward(const U32 index, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::moveLayerForward() - Invalid Index of %d.", index);
+        return;
+    }
+
+    if (index == mImageLayers.size() - 1)
+    {
+        return;
+    }
+
+    ImageLayer layer = mImageLayers[index];
+    mImageLayers.erase(index);
+    mImageLayers.insert(mImageLayers.begin() + index + 1, layer);
+
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::moveLayerBackward(const U32 index, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::moveLayerBackward() - Invalid Index of %d.", index);
+        return;
+    }
+
+    if (index == 1)
+    {
+        return;
+    }
+
+    moveLayerForward(index - 1, doRedraw);
+}
+
+void ImageAsset::moveLayerToFront(const U32 index, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::moveLayerToFront() - Invalid Index of %d.", index);
+        return;
+    }
+
+    if (index == mImageLayers.size() - 1)
+    {
+        return;
+    }
+
+    ImageLayer layer = mImageLayers[index];
+    mImageLayers.erase(index);
+    mImageLayers.push_back(layer);//The back of the array is the front of the image
+
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::moveLayerToBack(const U32 index, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::moveLayerToBack() - Invalid Index of %d.", index);
+        return;
+    }
+
+    if (index == 1)
+    {
+        return;
+    }
+
+    ImageLayer layer = mImageLayers[index];
+    mImageLayers.erase(index);
+    mImageLayers.insert(mImageLayers.begin() + 1, layer);//The front of the array is the back of the image
+
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::setLayerImage(const U32 index, const char* imagePath, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::setLayerImage() - Invalid Index of %d.", index);
+        return;
+    }
+
+    if (getOwned())
+    {
+        const char* path = expandAssetFilePath(imagePath);
+        mImageLayers[index].mImageFile = StringTable->insert(path);
+        mImageLayers[index].LoadImage(path);
+    }
+    else
+    {
+        mImageLayers[index].mImageFile = StringTable->insert(imagePath);
+    }
+
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::setLayerPosition(const U32 index, const Point2I position, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::setLayerPosition() - Invalid Index of %d.", index);
+        return;
+    }
+    mImageLayers[index].mPosition.set(position.x, position.y);
+    completeLayerChange(doRedraw);
+}
+
+void ImageAsset::setLayerBlendColor(const U32 index, const ColorF blendColor, const bool doRedraw)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::setLayerBlendColor() - Invalid Index of %d.", index);
+        return;
+    }
+    mImageLayers[index].mBlendColor.set(blendColor.red, blendColor.green, blendColor.blue, blendColor.alpha);
+    completeLayerChange(doRedraw);
+}
+
+const char* ImageAsset::getLayerImage(const U32 index)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::getLayerImage() - Invalid Index of %d.", index);
+        return StringTable->EmptyString;
+    }
+    return mImageLayers[index].mImageFile;
+}
+
+const Point2I ImageAsset::getLayerPosition(const U32 index)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::getLayerPosition() - Invalid Index of %d.", index);
+        return Point2I();
+    }
+    return mImageLayers[index].mPosition;
+}
+
+const ColorF ImageAsset::getLayerBlendColor(const U32 index)
+{
+    if (index < 1 || index >= mImageLayers.size())
+    {
+        Con::warnf("ImageAsset::getLayerBlendColor() - Invalid Index of %d.", index);
+        return ColorF();
+    }
+    return mImageLayers[index].mBlendColor;
+}
+
+void ImageAsset::setBlendColor(const ColorF color, const bool doRedraw)
+{
+    mBlendColor.set(color.red, color.green, color.blue, color.alpha);
+
+    if (mImageLayers.size() == 0)
+    {
+        Con::warnf("ImageAsset::setBlendColor() - The ImageAsset blend color will not be applied if layers are not being used.");
+        return;
+    }
+    
+    mImageLayers[0].mBlendColor.set(color.red, color.green, color.blue, color.alpha);
+
+    if (doRedraw)
+    {
+        redrawImage();
+    }
+}
+
+const ColorF ImageAsset::getBlendColor()
+{
+    return mBlendColor;
+}
+
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
 void ImageAsset::onTamlCustomWrite( TamlCustomNodes& customNodes )
 void ImageAsset::onTamlCustomWrite( TamlCustomNodes& customNodes )
@@ -1395,10 +1687,6 @@ void ImageAsset::onTamlCustomWrite( TamlCustomNodes& customNodes )
     // Call parent.
     // Call parent.
     Parent::onTamlCustomWrite( customNodes );
     Parent::onTamlCustomWrite( customNodes );
 
 
-    // Finish if not in explicit mode.
-    if ( !mExplicitMode )
-        return;
-
     if (mExplicitFrames.size() > 0)
     if (mExplicitFrames.size() > 0)
     {
     {
         // Add cell custom node.
         // Add cell custom node.
@@ -1420,18 +1708,42 @@ void ImageAsset::onTamlCustomWrite( TamlCustomNodes& customNodes )
             pCellNode->addField( cellHeightName, pixelArea.mPixelHeight );
             pCellNode->addField( cellHeightName, pixelArea.mPixelHeight );
         }
         }
     }
     }
+
+    if (mImageLayers.size() > 0)
+    {
+        TamlCustomNode* pCustomLayerNodes = customNodes.addNode(imageLayerCustomNodeName);
+
+        for (typeImageLayerVector::iterator frameItr = mImageLayers.begin() + 1; frameItr != mImageLayers.end(); ++frameItr)
+        {
+            const ImageLayer& layer = *frameItr;
+            TamlCustomNode* pLayerNode = pCustomLayerNodes->addNode(layerNodeName);
+
+            pLayerNode->addField(layerImageFile, layer.mImageFile);
+            pLayerNode->addField(layerPositionName, layer.mPosition);
+            pLayerNode->addField(layerBlendColorName, layer.mBlendColor);
+        }
+    }
 }
 }
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
-void ImageAsset::onTamlCustomRead( const TamlCustomNodes& customNodes )
+void ImageAsset::onTamlCustomRead(const TamlCustomNodes& customNodes)
 {
 {
     // Debug Profiling.
     // Debug Profiling.
     PROFILE_SCOPE(ImageAsset_OnTamlCustomRead);
     PROFILE_SCOPE(ImageAsset_OnTamlCustomRead);
-    
+
     // Call parent.
     // Call parent.
-    Parent::onTamlCustomRead( customNodes );
-    
+    Parent::onTamlCustomRead(customNodes);
+
+    // Load the explicit cells
+    loadTamlExplicitCells(customNodes);
+
+    // Load image layers
+    loadTamlImageLayers(customNodes);
+}
+
+void ImageAsset::loadTamlExplicitCells(const TamlCustomNodes& customNodes)
+{
     // Find cell custom node.
     // Find cell custom node.
     const TamlCustomNode* pCustomCellNodes = customNodes.findNode( cellCustomNodeCellsName );
     const TamlCustomNode* pCustomCellNodes = customNodes.findNode( cellCustomNodeCellsName );
     
     
@@ -1554,6 +1866,84 @@ void ImageAsset::onTamlCustomRead( const TamlCustomNodes& customNodes )
     }
     }
 }
 }
 
 
+void ImageAsset::loadTamlImageLayers(const TamlCustomNodes& customNodes)
+{
+    // Find layer custom node.
+    const TamlCustomNode* pCustomLayerNodes = customNodes.findNode(imageLayerCustomNodeName);
+
+    // Continue if we have explicit layers.
+    if (pCustomLayerNodes != NULL)
+    {
+        // Fetch children layer nodes.
+        const TamlCustomNodeVector& layerNodes = pCustomLayerNodes->getChildren();
+
+        // Iterate layers.
+        for (TamlCustomNodeVector::const_iterator layerNodeItr = layerNodes.begin(); layerNodeItr != layerNodes.end(); ++layerNodeItr)
+        {
+            // Fetch layer node.
+            TamlCustomNode* pLayerNode = *layerNodeItr;
+
+            // Fetch node name.
+            StringTableEntry nodeName = pLayerNode->getNodeName();
+
+            // Is this a valid alias?
+            if (nodeName != layerNodeName)
+            {
+                // No, so warn.
+                Con::warnf("ImageAsset::onTamlCustomRead() - Encountered an unknown custom name of '%s'.  Only '%s' is valid.", nodeName, layerNodeName);
+                continue;
+            }
+
+            Point2I layerPosition(0, 0);
+            const char* imagePath = StringTable->EmptyString;
+            ColorF blendColor = ColorF(1, 1, 1, 1);
+
+            // Fetch fields.
+            const TamlCustomFieldVector& fields = pLayerNode->getFields();
+
+            // Iterate property fields.
+            for (TamlCustomFieldVector::const_iterator fieldItr = fields.begin(); fieldItr != fields.end(); ++fieldItr)
+            {
+                // Fetch field.
+                const TamlCustomField* pField = *fieldItr;
+
+                // Fetch field name.
+                StringTableEntry fieldName = pField->getFieldName();
+
+                // Check common fields.
+                if (fieldName == layerImageFile)
+                {
+                    imagePath = pField->getFieldValue();
+                }
+                else if (fieldName == layerPositionName)
+                {
+                    pField->getFieldValue(layerPosition);
+                }
+                else if (fieldName == layerBlendColorName)
+                {
+                    pField->getFieldValue(blendColor);
+                }
+                else
+                {
+                    // Unknown name so warn.
+                    Con::warnf("ImageAsset::onTamlCustomRead() - Encountered an unknown custom field name of '%s'.", fieldName);
+                    continue;
+                }
+            }
+
+            // Is the image name valid?
+            if (imagePath == StringTable->EmptyString)
+            {
+                // No, so warn
+                Con::warnf("ImageAsset::onTamlCustomRead() - Image File of '%s' is invalid or was not set.", imagePath);
+            }
+
+            // Add image layer.
+            addLayer(imagePath, layerPosition, blendColor, false);
+        }
+    }
+}
+
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
 static void WriteCustomTamlSchema( const AbstractClassRep* pClassRep, TiXmlElement* pParentElement )
 static void WriteCustomTamlSchema( const AbstractClassRep* pClassRep, TiXmlElement* pParentElement )
@@ -1564,6 +1954,8 @@ static void WriteCustomTamlSchema( const AbstractClassRep* pClassRep, TiXmlEleme
 
 
     char buffer[1024];
     char buffer[1024];
 
 
+    //---Cells---
+
     // Create ImageAsset node element.
     // Create ImageAsset node element.
     TiXmlElement* pImageAssetNodeElement = new TiXmlElement( "xs:element" );
     TiXmlElement* pImageAssetNodeElement = new TiXmlElement( "xs:element" );
     dSprintf( buffer, sizeof(buffer), "%s.%s", pClassRep->getClassName(), cellCustomNodeCellsName );
     dSprintf( buffer, sizeof(buffer), "%s.%s", pClassRep->getClassName(), cellCustomNodeCellsName );
@@ -1616,6 +2008,57 @@ static void WriteCustomTamlSchema( const AbstractClassRep* pClassRep, TiXmlEleme
     pImageAssetHeight->SetAttribute( "name", cellHeightName );
     pImageAssetHeight->SetAttribute( "name", cellHeightName );
     pImageAssetHeight->SetAttribute( "type", "xs:unsignedInt" );
     pImageAssetHeight->SetAttribute( "type", "xs:unsignedInt" );
     pImageAssetComplexTypeElement->LinkEndChild( pImageAssetHeight );
     pImageAssetComplexTypeElement->LinkEndChild( pImageAssetHeight );
+
+    //---Layers---
+
+    char buffer2[1024];
+
+    // Create ImageAsset node element.
+    TiXmlElement* pImageAssetNodeElementL = new TiXmlElement("xs:element");
+    dSprintf(buffer, sizeof(buffer2), "%s.%s", pClassRep->getClassName(), imageLayerCustomNodeName);
+    pImageAssetNodeElementL->SetAttribute("name", buffer2);
+    pImageAssetNodeElementL->SetAttribute("minOccurs", 0);
+    pImageAssetNodeElementL->SetAttribute("maxOccurs", 1);
+    pParentElement->LinkEndChild(pImageAssetNodeElementL);
+
+    // Create complex type.
+    TiXmlElement* pImageAssetNodeComplexTypeElementL = new TiXmlElement("xs:complexType");
+    pImageAssetNodeElementL->LinkEndChild(pImageAssetNodeComplexTypeElementL);
+
+    // Create choice element.
+    TiXmlElement* pImageAssetNodeChoiceElementL = new TiXmlElement("xs:choice");
+    pImageAssetNodeChoiceElementL->SetAttribute("minOccurs", 0);
+    pImageAssetNodeChoiceElementL->SetAttribute("maxOccurs", "unbounded");
+    pImageAssetNodeComplexTypeElementL->LinkEndChild(pImageAssetNodeChoiceElementL);
+
+    // Create ImageAsset element.
+    TiXmlElement* pImageAssetElementL = new TiXmlElement("xs:element");
+    pImageAssetElementL->SetAttribute("name", layerNodeName);
+    pImageAssetElementL->SetAttribute("minOccurs", 0);
+    pImageAssetElementL->SetAttribute("maxOccurs", 1);
+    pImageAssetNodeChoiceElementL->LinkEndChild(pImageAssetElementL);
+
+    // Create complex type Element.
+    TiXmlElement* pImageAssetComplexTypeElementL = new TiXmlElement("xs:complexType");
+    pImageAssetElementL->LinkEndChild(pImageAssetComplexTypeElementL);
+
+    // Create "ImageFile" attribute.
+    TiXmlElement* pLayerImageFile = new TiXmlElement("xs:attribute");
+    pLayerImageFile->SetAttribute("name", layerImageFile);
+    pLayerImageFile->SetAttribute("type", "xs:string");
+    pImageAssetComplexTypeElementL->LinkEndChild(pLayerImageFile);
+
+    // Create "Position" attribute.
+    TiXmlElement* pLayerPosition = new TiXmlElement("xs:attribute");
+    pLayerPosition->SetAttribute("name", layerPositionName);
+    pLayerPosition->SetAttribute("type", "Point2I_ConsoleType");
+    pImageAssetComplexTypeElementL->LinkEndChild(pLayerPosition);
+
+    // Create "BlendColor" attribute.
+    TiXmlElement* pLayerBlendColor = new TiXmlElement("xs:attribute");
+    pLayerBlendColor->SetAttribute("name", layerBlendColorName);
+    pLayerBlendColor->SetAttribute("type", "ColorF_ConsoleType");
+    pImageAssetComplexTypeElementL->LinkEndChild(pLayerBlendColor);
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------

+ 67 - 0
engine/source/2d/assets/ImageAsset.h

@@ -158,9 +158,32 @@ public:
         TexelArea mTexelArea;
         TexelArea mTexelArea;
     };
     };
 
 
+    class ImageLayer
+    {
+    public:
+        ImageLayer() {}
+
+        ImageLayer(const char* imageFile, const Point2I position, const ColorF blendColor) :
+            mPosition(position), mBlendColor(blendColor), mBitmap(nullptr) 
+        {
+            mImageFile = StringTable->insert(imageFile);
+        }
+
+        void LoadImage(const char* path)
+        {
+            mBitmap = GBitmap::load(path);
+        }
+
+        StringTableEntry mImageFile;
+        Point2I mPosition;
+        ColorF mBlendColor;
+        GBitmap* mBitmap;
+    };
+
 private:
 private:
     typedef Vector<FrameArea> typeFrameAreaVector;
     typedef Vector<FrameArea> typeFrameAreaVector;
     typedef Vector<FrameArea::PixelArea> typeExplicitFrameAreaVector;
     typedef Vector<FrameArea::PixelArea> typeExplicitFrameAreaVector;
+    typedef Vector<ImageLayer> typeImageLayerVector;
 
 
     /// Configuration.
     /// Configuration.
     StringTableEntry            mImageFile;
     StringTableEntry            mImageFile;
@@ -181,6 +204,8 @@ private:
     typeFrameAreaVector         mFrames;
     typeFrameAreaVector         mFrames;
     typeExplicitFrameAreaVector mExplicitFrames;
     typeExplicitFrameAreaVector mExplicitFrames;
     TextureHandle               mImageTextureHandle;
     TextureHandle               mImageTextureHandle;
+    typeImageLayerVector        mImageLayers;
+    ColorF                      mBlendColor;
 
 
 public:
 public:
     ImageAsset();
     ImageAsset();
@@ -267,6 +292,26 @@ public:
     
     
     inline void forceCalculation( void ) { calculateImage(); }
     inline void forceCalculation( void ) { calculateImage(); }
 
 
+    /// Layers
+    void addLayer(const char* imagePath, const Point2I position, const ColorF blendColor, const bool doRedraw = true);
+    void insertLayer(const U32 index, const char* imagePath, const Point2I position, const ColorF blendColor, const bool doRedraw = true);
+    void removeLayer(const U32 index, const bool doRedraw = true);
+    void moveLayerForward(const U32 index, const bool doRedraw = true);
+    void moveLayerBackward(const U32 index, const bool doRedraw = true);
+    void moveLayerToFront(const U32 index, const bool doRedraw = true);
+    void moveLayerToBack(const U32 index, const bool doRedraw = true);
+    void setLayerImage(const U32 index, const char* imagePath, const bool doRedraw = true);
+    void setLayerPosition(const U32 index, const Point2I position, const bool doRedraw = true);
+    void setLayerBlendColor(const U32 index, const ColorF color, const bool doRedraw = true);
+    const U32 getLayerCount() { return mImageLayers.size() - 1; } //We pretend layer 0 doesn't exist as it is internally created
+    const char* getLayerImage(const U32 index);
+    const Point2I getLayerPosition(const U32 index);
+    const ColorF getLayerBlendColor(const U32 index);
+    void forceRedrawImage() { redrawImage(); }
+    void setBlendColor(const ColorF color, const bool doRedraw = true);
+    const ColorF getBlendColor();
+
+
     /// Declare Console Object.
     /// Declare Console Object.
     DECLARE_CONOBJECT(ImageAsset);
     DECLARE_CONOBJECT(ImageAsset);
 
 
@@ -277,6 +322,9 @@ private:
     void calculateExplicitMode( void );
     void calculateExplicitMode( void );
     void setTextureFilter( const TextureFilterMode filterMode );
     void setTextureFilter( const TextureFilterMode filterMode );
 
 
+    void completeLayerChange(const bool doRedraw);
+    void redrawImage();
+
 protected:
 protected:
     virtual void initializeAsset( void );
     virtual void initializeAsset( void );
     virtual void onAssetRefresh( void );
     virtual void onAssetRefresh( void );
@@ -287,6 +335,9 @@ protected:
     virtual void onTamlCustomWrite( TamlCustomNodes& customNodes );
     virtual void onTamlCustomWrite( TamlCustomNodes& customNodes );
     virtual void onTamlCustomRead( const TamlCustomNodes& customNodes );
     virtual void onTamlCustomRead( const TamlCustomNodes& customNodes );
 
 
+    virtual void loadTamlExplicitCells(const TamlCustomNodes& customNodes);
+    virtual void loadTamlImageLayers(const TamlCustomNodes& customNodes);
+
 
 
 protected:
 protected:
     static void textureEventCallback( const U32 eventCode, void *userData );
     static void textureEventCallback( const U32 eventCode, void *userData );
@@ -330,6 +381,22 @@ protected:
 
 
     static bool setCellHeight( void* obj, const char* data )                { static_cast<ImageAsset*>(obj)->setCellHeight(dAtoi(data)); return false; }
     static bool setCellHeight( void* obj, const char* data )                { static_cast<ImageAsset*>(obj)->setCellHeight(dAtoi(data)); return false; }
     static bool writeCellHeight( void* obj, StringTableEntry pFieldName )   { ImageAsset* pImageAsset = static_cast<ImageAsset*>(obj); return !pImageAsset->getExplicitMode() && pImageAsset->getCellHeight() != 0; }
     static bool writeCellHeight( void* obj, StringTableEntry pFieldName )   { ImageAsset* pImageAsset = static_cast<ImageAsset*>(obj); return !pImageAsset->getExplicitMode() && pImageAsset->getCellHeight() != 0; }
+
+    static bool setBlendColor(void* obj, const char* data) 
+    { 
+        if (Utility::mGetStringElementCount(data) < 4)
+        {
+            Con::warnf("ImageAsset::BlendColor - Blend colors require four values (red/green/blue/alpha).");
+            return false;
+        }
+        static_cast<ImageAsset*>(obj)->setBlendColor(
+            ColorF(dAtof(Utility::mGetStringElement(data, 0)),
+            dAtof(Utility::mGetStringElement(data, 1)),
+            dAtof(Utility::mGetStringElement(data, 2)),
+            dAtof(Utility::mGetStringElement(data, 3))), true); 
+        return false; 
+    }
+    static bool writeBlendColor(void* obj, StringTableEntry pFieldName) { return static_cast<ImageAsset*>(obj)->getBlendColor() != ColorF(1.0f, 1.0f, 1.0f, 1.0f); }
 };
 };
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------

+ 423 - 0
engine/source/2d/assets/ImageAsset_ScriptBinding.h

@@ -602,4 +602,427 @@ ConsoleMethodWithDocs(ImageAsset, setExplicitMode, ConsoleVoid, 3, 3, (explicitM
     object->setExplicitMode(dAtob(argv[2]));
     object->setExplicitMode(dAtob(argv[2]));
 }
 }
 
 
+/*! Adds a layer to the ImageAsset
+@param imageFile The file for the image to layer over the texture.
+@param position The position for the layer to be at with (0, 0) in the top left corner.
+@param blendColor The blending color to be applied to this new layer.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, addLayer, ConsoleVoid, 5, 6, (imageFile, position, blendColor, [doRedraw]))
+{
+    if (argc < 5)
+    {
+        Con::warnf("ImageAsset::addLayer() - Invalid number of parameters!");
+        return;
+    }
+
+    F32 x, y;
+    x = 0.0f;
+    y = 0.0f;
+    if (Utility::mGetStringElementCount(argv[3]) >= 2)
+    {
+        x = dAtof(Utility::mGetStringElement(argv[3], 0));
+        y = dAtof(Utility::mGetStringElement(argv[3], 1));
+    }
+    Point2I pos(x, y);
+
+    const U32 colorCount = Utility::mGetStringElementCount(argv[4]);
+    if (colorCount != 4)
+    {
+        Con::warnf("Scene::addLayer() - Invalid color! Colors require four values (red / green / blue / alpha)!");
+        return;
+    }
+    ColorF blendColor = ColorF(dAtof(Utility::mGetStringElement(argv[4], 0)),
+        dAtof(Utility::mGetStringElement(argv[4], 1)),
+        dAtof(Utility::mGetStringElement(argv[4], 2)),
+        dAtof(Utility::mGetStringElement(argv[4], 3)));
+
+    bool redraw = true;
+    if (argc == 6)
+    {
+        redraw = dAtob(argv[5]);
+    }
+
+    object->addLayer(argv[2], pos, blendColor, redraw);
+}
+
+/*! Inserts a layer into the stack of layers on a texture at a given index.
+@param index The one-based index to insert the new layer at. Must be less or equal to the number of layers.
+@param imageFile The file for the image to layer over the texture.
+@param position The position for the layer to be at with (0, 0) in the top left corner.
+@param blendColor The blending color to be applied to this new layer.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, insertLayer, ConsoleVoid, 6, 7, (index, imageFile, position, blendColor, [doRedraw]))
+{
+    if (argc < 6)
+    {
+        Con::warnf("ImageAsset::insertLayer() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    F32 x, y;
+    x = 0.0f;
+    y = 0.0f;
+    if (Utility::mGetStringElementCount(argv[4]) >= 2)
+    {
+        x = dAtof(Utility::mGetStringElement(argv[4], 0));
+        y = dAtof(Utility::mGetStringElement(argv[4], 1));
+    }
+    Point2I pos(x, y);
+
+    const U32 colorCount = Utility::mGetStringElementCount(argv[5]);
+    if (colorCount != 4)
+    {
+        Con::warnf("Scene::insertLayer() - Invalid color! Colors require four values (red / green / blue / alpha)!");
+        return;
+    }
+    ColorF blendColor = ColorF(dAtof(Utility::mGetStringElement(argv[5], 0)),
+        dAtof(Utility::mGetStringElement(argv[5], 1)),
+        dAtof(Utility::mGetStringElement(argv[5], 2)),
+        dAtof(Utility::mGetStringElement(argv[5], 3)));
+
+    bool redraw = true;
+    if (argc == 7)
+    {
+        redraw = dAtob(argv[6]);
+    }
+
+    object->insertLayer(index, argv[3], pos, blendColor, redraw);
+}
+
+/*! Removes a layer from a texture at a given index.
+@param index The one-based index to remove the new layer from. Must be less or equal to the number of layers.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, removeLayer, ConsoleVoid, 3, 4, (index, [doRedraw]))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::removeLayer() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    bool redraw = true;
+    if (argc == 4)
+    {
+        redraw = dAtob(argv[3]);
+    }
+
+    object->removeLayer(index, redraw);
+}
+
+/*! Moves the layer at the index forward.
+@param index The one-based index to move forward. Must be less or equal to the number of layers.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, moveLayerForward, ConsoleVoid, 3, 4, (index, [doRedraw]))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::moveLayerForward() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    bool redraw = true;
+    if (argc == 4)
+    {
+        redraw = dAtob(argv[3]);
+    }
+
+    object->moveLayerForward(index, redraw);
+}
+
+/*! Moves the layer at the index backward.
+@param index The one-based index to move backward. Must be less or equal to the number of layers.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, moveLayerBackward, ConsoleVoid, 3, 4, (index, [doRedraw]))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::moveLayerBackward() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    bool redraw = true;
+    if (argc == 4)
+    {
+        redraw = dAtob(argv[3]);
+    }
+
+    object->moveLayerBackward(index, redraw);
+}
+
+/*! Moves the layer at the index to the front.
+@param index The one-based index to move to the front. Must be less or equal to the number of layers.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, moveLayerToFront, ConsoleVoid, 3, 4, (index, [doRedraw]))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::moveLayerToFront() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    bool redraw = true;
+    if (argc == 4)
+    {
+        redraw = dAtob(argv[3]);
+    }
+
+    object->moveLayerToFront(index, redraw);
+}
+
+/*! Moves the layer at the index to the back.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, moveLayerToBack, ConsoleVoid, 3, 4, (index, [doRedraw]))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::moveLayerToBack() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    bool redraw = true;
+    if (argc == 4)
+    {
+        redraw = dAtob(argv[3]);
+    }
+
+    object->moveLayerToBack(index, redraw);
+}
+
+/*! Sets the image for a layer at the given index.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@param image The path to an image to be used by the layer.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, setLayerImage, ConsoleVoid, 4, 5, (index, image, [doRedraw]))
+{
+    if (argc < 4)
+    {
+        Con::warnf("ImageAsset::setLayerImage() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    bool redraw = true;
+    if (argc == 5)
+    {
+        redraw = dAtob(argv[4]);
+    }
+
+    object->setLayerImage(index, argv[3], redraw);
+}
+
+/*! Sets the position for a layer at the given index.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@param position The space-deliminated (x, y) position to place the image with the top left corner as (0, 0). The layer can safely overflow the bounds of the original image.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, setLayerPosition, ConsoleVoid, 4, 5, (index, position, [doRedraw]))
+{
+    if (argc < 4)
+    {
+        Con::warnf("ImageAsset::setLayerPosition() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    F32 x, y;
+    x = 0.0f;
+    y = 0.0f;
+    if (Utility::mGetStringElementCount(argv[3]) >= 2)
+    {
+        x = dAtof(Utility::mGetStringElement(argv[3], 0));
+        y = dAtof(Utility::mGetStringElement(argv[3], 1));
+    }
+    Point2I pos(x, y);
+
+    bool redraw = true;
+    if (argc == 5)
+    {
+        redraw = dAtob(argv[4]);
+    }
+
+    object->setLayerPosition(index, pos, redraw);
+}
+
+/*! Sets the blend color for a layer at the given index.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@param blendColor The space-deliminated color used to blend the layer. Requires four decimal values between 0 and 1 that represent red, green, blue, and alpha.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, setLayerBlendColor, ConsoleVoid, 4, 5, (index, blendColor, [doRedraw]))
+{
+    if (argc < 4)
+    {
+        Con::warnf("ImageAsset::setLayerBlendColor() - Invalid number of parameters!");
+        return;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    const U32 colorCount = Utility::mGetStringElementCount(argv[3]);
+    if (colorCount != 4)
+    {
+        Con::warnf("Scene::setLayerBlendColor() - Invalid color! Colors require four values (red / green / blue / alpha)!");
+        return;
+    }
+    ColorF blendColor = ColorF(dAtof(Utility::mGetStringElement(argv[3], 0)),
+        dAtof(Utility::mGetStringElement(argv[3], 1)),
+        dAtof(Utility::mGetStringElement(argv[3], 2)),
+        dAtof(Utility::mGetStringElement(argv[3], 3)));
+
+    bool redraw = true;
+    if (argc == 5)
+    {
+        redraw = dAtob(argv[4]);
+    }
+
+    object->setLayerBlendColor(index, blendColor, redraw);
+}
+
+/*! Returns the number of layers.
+@return The number of layers used by the texture. Zero if only the base texture is used.
+*/
+ConsoleMethodWithDocs(ImageAsset, getLayerCount, ConsoleInt, 2, 2, ())
+{
+    return object->getLayerCount();
+}
+
+/*! Returns image used for a given layer.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@return The path to the image file.
+*/
+ConsoleMethodWithDocs(ImageAsset, getLayerImage, ConsoleString, 3, 3, (index))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::getLayerImage() - Invalid number of parameters!");
+        return StringTable->EmptyString;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    return object->getLayerImage(index);
+}
+
+/*! Returns position for a given layer.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@return (int y, int x) The layer position.
+*/
+ConsoleMethodWithDocs(ImageAsset, getLayerPosition, ConsoleString, 3, 3, (index))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::getLayerPosition() - Invalid number of parameters!");
+        return StringTable->EmptyString;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    char* retBuffer = Con::getReturnBuffer(64);
+    const Point2I& pos = object->getLayerPosition(index);
+    dSprintf(retBuffer, 64, "%d %d", pos.x, pos.y);
+    return retBuffer;
+}
+
+/*! Returns the blend color for a given layer.
+@param index The one-based index to move to the back. Must be less or equal to the number of layers.
+@return (float red / float green / float blue / float alpha) The layer blend color.
+*/
+ConsoleMethodWithDocs(ImageAsset, getLayerBlendColor, ConsoleString, 3, 3, (index))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::getLayerBlendColor() - Invalid number of parameters!");
+        return StringTable->EmptyString;
+    }
+
+    U32 index = dAtoi(argv[2]);
+
+    return object->getLayerBlendColor(index).scriptThis();
+}
+
+/*! Forces the texture to redraw its layers. Redrawing can be expensive and is automatically done on every change to a layer unless prevented.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, forceRedraw, ConsoleVoid, 2, 2, ())
+{
+    if (object->getLayerCount() > 0)
+    {
+        object->forceRedrawImage();
+    }
+}
+
+/*! Sets the blend color for the base image when using layers.
+@param blendColor The space-deliminated color used to blend the layer. Requires four decimal values between 0 and 1 that represent red, green, blue, and alpha.
+@param doRedraw Optional value that prevents a redraw of the texture when false. Defaults to true.
+@return No return value.
+*/
+ConsoleMethodWithDocs(ImageAsset, setBlendColor, ConsoleVoid, 3, 4, (blendColor, [doRedraw]))
+{
+    if (argc < 3)
+    {
+        Con::warnf("ImageAsset::setBlendColor() - Invalid number of parameters!");
+        return;
+    }
+
+    const U32 colorCount = Utility::mGetStringElementCount(argv[2]);
+    if (colorCount != 4)
+    {
+        Con::warnf("Scene::setBlendColor() - Invalid color! Colors require four values (red / green / blue / alpha)!");
+        return;
+    }
+    ColorF blendColor = ColorF(dAtof(Utility::mGetStringElement(argv[2], 0)),
+        dAtof(Utility::mGetStringElement(argv[2], 1)),
+        dAtof(Utility::mGetStringElement(argv[2], 2)),
+        dAtof(Utility::mGetStringElement(argv[2], 3)));
+
+    bool redraw = true;
+    if (argc == 4)
+    {
+        redraw = dAtob(argv[3]);
+    }
+
+    object->setBlendColor(blendColor, redraw);
+}
+
+/*! Returns the blend color for the base texture when using layers.
+@return (float red / float green / float blue / float alpha) The base blend color.
+*/
+ConsoleMethodWithDocs(ImageAsset, getBlendColor, ConsoleString, 2, 2, ())
+{
+    return object->getBlendColor().scriptThis();
+}
+
 ConsoleMethodGroupEndWithDocs(ImageAsset)
 ConsoleMethodGroupEndWithDocs(ImageAsset)

+ 1 - 1
engine/source/graphics/PNGImage.h

@@ -71,7 +71,7 @@ public:
     // Will "merge" the incoming image onto the current image on to an x, y position.
     // Will "merge" the incoming image onto the current image on to an x, y position.
     bool MergeOn(U32 x, U32 y, const PNGImage* inc);
     bool MergeOn(U32 x, U32 y, const PNGImage* inc);
 
 
-    // Will clean up any allocated memory from the PNGImage. This must be called or their may be a memory leak.
+    // Will clean up any allocated memory from the PNGImage. This must be called or there may be a memory leak.
     bool CleanMemoryUsage();
     bool CleanMemoryUsage();
 
 
     bool ClearImageData();
     bool ClearImageData();

+ 59 - 0
engine/source/graphics/gBitmap.cc

@@ -121,6 +121,65 @@ void GBitmap::deleteImage()
    SAFE_DELETE(pPalette);
    SAFE_DELETE(pPalette);
 }
 }
 
 
+void GBitmap::clearImage()
+{
+    ColorI clearColor(255, 255, 255, 0);
+    for (U32 h = 0; h < getHeight(); h++)
+    {
+        for (U32 w = 0; w < getWidth(); w++)
+        {
+            setColor(w, h, clearColor);
+        }
+    }
+}
+
+bool GBitmap::mergeLayer(const Point2I pos, const GBitmap* layer, const ColorF blendColor)
+{
+    if (layer != nullptr && (getFormat() == BitmapFormat::RGBA || getFormat() == BitmapFormat::RGB) && (layer->getFormat() == BitmapFormat::RGBA || layer->getFormat() == BitmapFormat::RGB))
+    {
+        for (U32 h = getMax(0, pos.y); h < getHeight() && (h - pos.y) < layer->getHeight(); h++)
+        {
+            for (U32 w = getMax(0, pos.x); w < getWidth() && (w - pos.x) < layer->getWidth(); w++)
+            {
+                const U8* layerByte = layer->getAddress(w - pos.x, h - pos.y);
+                U8* baseByte = getAddress(w, h);
+
+                float layerAlpha = blendColor.alpha;
+                if (layer->getFormat() == BitmapFormat::RGBA)
+                {
+                    layerAlpha = (layerByte[3] / 255.0f) * blendColor.alpha;
+                }
+                if (layerAlpha <= 0.005f)
+                {
+                    continue;
+                }
+
+                float baseAlpha = 1.0f;
+                if (getFormat() == BitmapFormat::RGBA)
+                {
+                    baseAlpha = (baseByte[3] / 255.0f);
+                }
+                float alpha = layerAlpha + baseAlpha * (1 - layerAlpha);
+
+                float red = ((float)layerByte[0]/255.0f * layerAlpha * blendColor.red) + ((float)baseByte[0]/255.0f * baseAlpha * (1 - layerAlpha)) / alpha;
+                float green = ((float)layerByte[1]/255.0f * layerAlpha * blendColor.green) + ((float)baseByte[1]/255.0f * baseAlpha * (1 - layerAlpha)) / alpha;
+                float blue = ((float)layerByte[2]/255.0f * layerAlpha * blendColor.blue) + ((float)baseByte[2]/255.0f * baseAlpha * (1 - layerAlpha)) / alpha;
+
+                baseByte[0] = (U8)mClamp(red * 255.0f, 0.0f, 255.0f);
+                baseByte[1] = (U8)mClamp(green * 255.0f, 0.0f, 255.0f);
+                baseByte[2] = (U8)mClamp(blue * 255.0f, 0.0f, 255.0f);
+
+                if (getFormat() == BitmapFormat::RGBA)
+                {
+                    baseByte[3] = (U8)mClamp(alpha * 255.0f, 0.0f, 255.0f);
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
 
 
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 void GBitmap::setPalette(GPalette* in_pPalette)
 void GBitmap::setPalette(GPalette* in_pPalette)

+ 3 - 0
engine/source/graphics/gBitmap.h

@@ -130,6 +130,7 @@ class GBitmap: public ResourceInstance
    static U32 sBitmapIdSource;
    static U32 sBitmapIdSource;
 
 
    void deleteImage();
    void deleteImage();
+   void clearImage();
 
 
    BitmapFormat internalFormat;
    BitmapFormat internalFormat;
   public:
   public:
@@ -149,6 +150,8 @@ class GBitmap: public ResourceInstance
                             ///  deleted on exit, or written out on a
                             ///  deleted on exit, or written out on a
                             ///  write.
                             ///  write.
 
 
+   bool mergeLayer(const Point2I pos, const GBitmap* layer, const ColorF blendColor);
+
    //-------------------------------------- Input/Output interface
    //-------------------------------------- Input/Output interface
   public:
   public:
    bool readJPEG(Stream& io_rStream);              // located in bitmapJpeg.cc
    bool readJPEG(Stream& io_rStream);              // located in bitmapJpeg.cc