/* libs/graphics/effects/SkShaderExtras.cpp
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/

#include "SkComposeShader.h"
#include "SkColorFilter.h"
#include "SkColorPriv.h"
#include "SkXfermode.h"

//////////////////////////////////////////////////////////////////////////////////////
    
SkComposeShader::SkComposeShader(SkShader* sA, SkShader* sB, SkXfermode* mode)
{
    fShaderA = sA;  sA->ref();
    fShaderB = sB;  sB->ref();
    // mode may be null
    fMode = mode;   mode->safeRef();
}

SkComposeShader::SkComposeShader(SkFlattenableReadBuffer& buffer) :
    INHERITED(buffer)
{
    fShaderA = static_cast<SkShader*>(buffer.readFlattenable());
    fShaderB = static_cast<SkShader*>(buffer.readFlattenable());
    fMode = static_cast<SkXfermode*>(buffer.readFlattenable());
}

SkComposeShader::~SkComposeShader()
{
    fMode->safeUnref(); // may be null
    fShaderB->unref();
    fShaderA->unref();
}

void SkComposeShader::beginSession()
{
    this->INHERITED::beginSession();
    fShaderA->beginSession();
    fShaderB->beginSession();
}

void SkComposeShader::endSession()
{
    fShaderA->endSession();
    fShaderB->endSession();
    this->INHERITED::endSession();
}

class SkAutoAlphaRestore {
public:
    SkAutoAlphaRestore(SkPaint* paint, uint8_t newAlpha)
    {
        fAlpha = paint->getAlpha();
        fPaint = paint;
        paint->setAlpha(newAlpha);
    }
    ~SkAutoAlphaRestore()
    {
        fPaint->setAlpha(fAlpha);
    }
private:
    SkPaint*    fPaint;
    uint8_t     fAlpha;
};

void SkComposeShader::flatten(SkFlattenableWriteBuffer& buffer)
{
    this->INHERITED::flatten(buffer);
    buffer.writeFlattenable(fShaderA);
    buffer.writeFlattenable(fShaderB);
    buffer.writeFlattenable(fMode);
}

/*  We call setContext on our two worker shaders. However, we
    always let them see opaque alpha, and if the paint really
    is translucent, then we apply that after the fact.
*/
bool SkComposeShader::setContext(const SkBitmap& device,
                                 const SkPaint& paint,
                                 const SkMatrix& matrix)
{
    if (!this->INHERITED::setContext(device, paint, matrix))
        return false;

    // we preconcat our localMatrix (if any) with the device matrix
    // before calling our sub-shaders

    SkMatrix tmpM;
    
    (void)this->getLocalMatrix(&tmpM);
    tmpM.setConcat(matrix, tmpM);
    
    SkAutoAlphaRestore  restore(const_cast<SkPaint*>(&paint), 0xFF);

    return  fShaderA->setContext(device, paint, tmpM) &&
            fShaderB->setContext(device, paint, tmpM);
}

// larger is better (fewer times we have to loop), but we shouldn't
// take up too much stack-space (each element is 4 bytes)
#define TMP_COLOR_COUNT     64

void SkComposeShader::shadeSpan(int x, int y, SkPMColor result[], int count)
{
    SkShader*   shaderA = fShaderA;
    SkShader*   shaderB = fShaderB;
    SkXfermode* mode = fMode;
    unsigned    scale = SkAlpha255To256(this->getPaintAlpha());
    
    SkPMColor   tmp[TMP_COLOR_COUNT];

    if (NULL == mode)   // implied SRC_OVER
    {
        // TODO: when we have a good test-case, should use SkBlitRow::Proc32
        // for these loops
        do {
            int n = count;
            if (n > TMP_COLOR_COUNT)
                n = TMP_COLOR_COUNT;
            
            shaderA->shadeSpan(x, y, result, n);
            shaderB->shadeSpan(x, y, tmp, n);

            if (256 == scale)
            {
                for (int i = 0; i < n; i++)
                    result[i] = SkPMSrcOver(tmp[i], result[i]);
            }
            else
            {
                for (int i = 0; i < n; i++)
                    result[i] = SkAlphaMulQ(SkPMSrcOver(tmp[i], result[i]), scale);
            }
            
            result += n;
            x += n;
            count -= n;
        } while (count > 0);
    }
    else    // use mode for the composition
    {
        do {
            int n = count;
            if (n > TMP_COLOR_COUNT)
                n = TMP_COLOR_COUNT;
            
            shaderA->shadeSpan(x, y, result, n);
            shaderB->shadeSpan(x, y, tmp, n);
            mode->xfer32(result, tmp, n, NULL);

            if (256 == scale)
            {
                for (int i = 0; i < n; i++)
                    result[i] = SkAlphaMulQ(result[i], scale);
            }

            result += n;
            x += n;
            count -= n;
        } while (count > 0);
    }
}