/* libs/graphics/sgl/SkBitmapSampler.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 "SkBitmapSampler.h"

static SkTileModeProc get_tilemode_proc(SkShader::TileMode mode)
{
    switch (mode) {
    case SkShader::kClamp_TileMode:
        return do_clamp;
    case SkShader::kRepeat_TileMode:
        return do_repeat_mod;
    case SkShader::kMirror_TileMode:
        return do_mirror_mod;
    default:
        SkASSERT(!"unknown mode");
        return NULL;
    }
}

SkBitmapSampler::SkBitmapSampler(const SkBitmap& bm, bool filter,
                                 SkShader::TileMode tmx, SkShader::TileMode tmy)
    : fBitmap(bm), fFilterBitmap(filter), fTileModeX(tmx), fTileModeY(tmy)
{
    SkASSERT(bm.width() > 0 && bm.height() > 0);

    fMaxX = SkToU16(bm.width() - 1);
    fMaxY = SkToU16(bm.height() - 1);
    
    fTileProcX = get_tilemode_proc(tmx);
    fTileProcY = get_tilemode_proc(tmy);
}

void SkBitmapSampler::setPaint(const SkPaint& paint)
{
}

class SkNullBitmapSampler : public SkBitmapSampler {
public:
    SkNullBitmapSampler(const SkBitmap& bm, bool filter,
                        SkShader::TileMode tmx, SkShader::TileMode tmy)
        : SkBitmapSampler(bm, filter, tmx, tmy) {}

    virtual SkPMColor sample(SkFixed x, SkFixed y) const { return 0; }
};

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

#define BITMAP_CLASSNAME_PREFIX(name)           ARGB32##name
#define BITMAP_PIXEL_TO_PMCOLOR(bitmap, x, y)   *bitmap.getAddr32(x, y)
#include "SkBitmapSamplerTemplate.h"

#include "SkColorPriv.h"

#define BITMAP_CLASSNAME_PREFIX(name)           RGB16##name
#define BITMAP_PIXEL_TO_PMCOLOR(bitmap, x, y)   SkPixel16ToPixel32(*bitmap.getAddr16(x, y))
#include "SkBitmapSamplerTemplate.h"

#define BITMAP_CLASSNAME_PREFIX(name)           Index8##name
#define BITMAP_PIXEL_TO_PMCOLOR(bitmap, x, y)   bitmap.getIndex8Color(x, y)
#include "SkBitmapSamplerTemplate.h"

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
///////////////// The Bilinear versions

#include "SkFilterProc.h"

class ARGB32_Bilinear_Sampler : public SkBitmapSampler {
public:
    ARGB32_Bilinear_Sampler(const SkBitmap& bm, SkShader::TileMode tmx, SkShader::TileMode tmy)
        : SkBitmapSampler(bm, true, tmx, tmy)
    {
        fPtrProcTable = SkGetBilinearFilterPtrProcTable();
    }

    virtual SkPMColor sample(SkFixed x, SkFixed y) const
    {
        const uint32_t *p00, *p01, *p10, *p11;

        // turn pixel centers into the top-left of our filter-box
        x -= SK_FixedHalf;
        y -= SK_FixedHalf;
    
        // compute our pointers
        {
            const SkBitmap* bitmap = &fBitmap;
            int ix = x >> 16;
            int iy = y >> 16;
            
            int             maxX = fMaxX;
            SkTileModeProc  procX = fTileProcX;
            int             maxY = fMaxY;
            SkTileModeProc  procY = fTileProcY;

            int tmpx = procX(ix, maxX);
            int tmpy = procY(iy, maxY);
            p00 = bitmap->getAddr32(tmpx, tmpy);

            int tmpx1 = procX(ix + 1, maxX);
            p01 = bitmap->getAddr32(tmpx1, tmpy);

            int tmpy1 = procY(iy + 1, maxY);
            p10 = bitmap->getAddr32(tmpx, tmpy1);

            p11 = bitmap->getAddr32(tmpx1, tmpy1);
        }

        SkFilterPtrProc proc = SkGetBilinearFilterPtrProc(fPtrProcTable, x, y);
        return proc(p00, p01, p10, p11);
    }
    
private:
    const SkFilterPtrProc* fPtrProcTable;
};

class RGB16_Bilinear_Sampler : public SkBitmapSampler {
public:
    RGB16_Bilinear_Sampler(const SkBitmap& bm, SkShader::TileMode tmx, SkShader::TileMode tmy)
        : SkBitmapSampler(bm, true, tmx, tmy)
    {
        fProcTable = SkGetBilinearFilterProcTable();
    }

    virtual SkPMColor sample(SkFixed x, SkFixed y) const
    {
        const uint16_t *p00, *p01, *p10, *p11;

        // turn pixel centers into the top-left of our filter-box
        x -= SK_FixedHalf;
        y -= SK_FixedHalf;
    
        // compute our pointers
        {
            const SkBitmap* bitmap = &fBitmap;
            int ix = x >> 16;
            int iy = y >> 16;
            
            int             maxX = fMaxX;
            SkTileModeProc  procX = fTileProcX;
            int             maxY = fMaxY;
            SkTileModeProc  procY = fTileProcY;

            int tmpx = procX(ix, maxX);
            int tmpy = procY(iy, maxY);
            p00 = bitmap->getAddr16(tmpx, tmpy);

            int tmpx1 = procX(ix + 1, maxX);
            p01 = bitmap->getAddr16(tmpx1, tmpy);

            int tmpy1 = procY(iy + 1, maxY);
            p10 = bitmap->getAddr16(tmpx, tmpy1);

            p11 = bitmap->getAddr16(tmpx1, tmpy1);
        }

        SkFilterProc proc = SkGetBilinearFilterProc(fProcTable, x, y);
        uint32_t c = proc(SkExpand_rgb_16(*p00), SkExpand_rgb_16(*p01),
                          SkExpand_rgb_16(*p10), SkExpand_rgb_16(*p11));

        return SkPixel16ToPixel32((uint16_t)SkCompact_rgb_16(c));
    }
    
private:
    const SkFilterProc* fProcTable;
};

// If we had a init/term method on sampler, we could avoid the per-pixel
// call to lockColors/unlockColors

class Index8_Bilinear_Sampler : public SkBitmapSampler {
public:
    Index8_Bilinear_Sampler(const SkBitmap& bm, SkShader::TileMode tmx, SkShader::TileMode tmy)
        : SkBitmapSampler(bm, true, tmx, tmy)
    {
        fPtrProcTable = SkGetBilinearFilterPtrProcTable();
    }

    virtual SkPMColor sample(SkFixed x, SkFixed y) const
    {
        const SkBitmap* bitmap = &fBitmap;

        const uint8_t *p00, *p01, *p10, *p11;

         // turn pixel centers into the top-left of our filter-box
        x -= SK_FixedHalf;
        y -= SK_FixedHalf;
    
       // compute our pointers
        {
            int ix = x >> 16;
            int iy = y >> 16;
            
            int             maxX = fMaxX;
            SkTileModeProc  procX = fTileProcX;
            int             maxY = fMaxY;
            SkTileModeProc  procY = fTileProcY;

            int tmpx = procX(ix, maxX);
            int tmpy = procY(iy, maxY);
            p00 = bitmap->getAddr8(tmpx, tmpy);

            int tmpx1 = procX(ix + 1, maxX);
            p01 = bitmap->getAddr8(tmpx1, tmpy);

            int tmpy1 = procY(iy + 1, maxY);
            p10 = bitmap->getAddr8(tmpx, tmpy1);

            p11 = bitmap->getAddr8(tmpx1, tmpy1);
        }

        const SkPMColor* colors = bitmap->getColorTable()->lockColors();

        SkFilterPtrProc proc = SkGetBilinearFilterPtrProc(fPtrProcTable, x, y);
        uint32_t c = proc(&colors[*p00], &colors[*p01], &colors[*p10], &colors[*p11]);

        bitmap->getColorTable()->unlockColors(false);

        return c;
    }
    
private:
    const SkFilterPtrProc* fPtrProcTable;
};

class A8_Bilinear_Sampler : public SkBitmapSampler {
public:
    A8_Bilinear_Sampler(const SkBitmap& bm, SkShader::TileMode tmx, SkShader::TileMode tmy)
        : SkBitmapSampler(bm, true, tmx, tmy)
    {
        fProcTable = SkGetBilinearFilterProcTable();
    }

    virtual void setPaint(const SkPaint& paint)
    {
        fColor = SkPreMultiplyColor(paint.getColor());
    }

    virtual SkPMColor sample(SkFixed x, SkFixed y) const
    {
        const uint8_t *p00, *p01, *p10, *p11;

        // turn pixel centers into the top-left of our filter-box
        x -= SK_FixedHalf;
        y -= SK_FixedHalf;
    
        // compute our pointers
        {
            const SkBitmap* bitmap = &fBitmap;
            int ix = x >> 16;
            int iy = y >> 16;
            
            int             maxX = fMaxX;
            SkTileModeProc  procX = fTileProcX;
            int             maxY = fMaxY;
            SkTileModeProc  procY = fTileProcY;

            int tmpx = procX(ix, maxX);
            int tmpy = procY(iy, maxY);
            p00 = bitmap->getAddr8(tmpx, tmpy);

            int tmpx1 = procX(ix + 1, maxX);
            p01 = bitmap->getAddr8(tmpx1, tmpy);

            int tmpy1 = procY(iy + 1, maxY);
            p10 = bitmap->getAddr8(tmpx, tmpy1);

            p11 = bitmap->getAddr8(tmpx1, tmpy1);
        }

        SkFilterProc proc = SkGetBilinearFilterProc(fProcTable, x, y);
        int alpha = proc(*p00, *p01, *p10, *p11);
        return SkAlphaMulQ(fColor, SkAlpha255To256(alpha));
    }
    
private:
    const SkFilterProc* fProcTable;
    SkPMColor           fColor;
};

class A8_NoFilter_Sampler : public SkBitmapSampler {
public:
    A8_NoFilter_Sampler(const SkBitmap& bm, SkShader::TileMode tmx, SkShader::TileMode tmy)
        : SkBitmapSampler(bm, false, tmx, tmy)
    {
    }

    virtual void setPaint(const SkPaint& paint)
    {
        fColor = SkPreMultiplyColor(paint.getColor());
    }

    virtual SkPMColor sample(SkFixed x, SkFixed y) const
    {
        int ix = SkFixedFloor(x);
        int iy = SkFixedFloor(y);
        
        int alpha = *fBitmap.getAddr8(fTileProcX(ix, fMaxX), fTileProcY(iy, fMaxY));
        return SkAlphaMulQ(fColor, SkAlpha255To256(alpha));
    }
    
private:
    const SkFilterProc* fProcTable;
    SkPMColor           fColor;
};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

SkBitmapSampler* SkBitmapSampler::Create(const SkBitmap& bm, bool doFilter,
                                         SkShader::TileMode tmx,
                                         SkShader::TileMode tmy)
{
    switch (bm.getConfig()) {
    case SkBitmap::kARGB_8888_Config:
        if (doFilter)
            return SkNEW_ARGS(ARGB32_Bilinear_Sampler, (bm, tmx, tmy));

        if (tmx == tmy) {
            switch (tmx) {
            case SkShader::kClamp_TileMode:
                return SkNEW_ARGS(ARGB32_Point_Clamp_Sampler, (bm));
            case SkShader::kRepeat_TileMode:
                if (is_pow2(bm.width()) && is_pow2(bm.height()))
                    return SkNEW_ARGS(ARGB32_Point_Repeat_Pow2_Sampler, (bm));
                else
                    return SkNEW_ARGS(ARGB32_Point_Repeat_Mod_Sampler, (bm));
            case SkShader::kMirror_TileMode:
                if (is_pow2(bm.width()) && is_pow2(bm.height()))
                    return SkNEW_ARGS(ARGB32_Point_Mirror_Pow2_Sampler, (bm));
                else
                    return SkNEW_ARGS(ARGB32_Point_Mirror_Mod_Sampler, (bm));
            default:
                SkASSERT(!"unknown mode");
            }
        }
        else {  // tmx != tmy
            return SkNEW_ARGS(ARGB32_Point_Sampler, (bm, tmx, tmy));
        }
        break;

    case SkBitmap::kRGB_565_Config:
        if (doFilter)
            return SkNEW_ARGS(RGB16_Bilinear_Sampler, (bm, tmx, tmy));

        if (tmx == tmy) {
            switch (tmx) {
            case SkShader::kClamp_TileMode:
                return SkNEW_ARGS(RGB16_Point_Clamp_Sampler, (bm));
            case SkShader::kRepeat_TileMode:
                if (is_pow2(bm.width()) && is_pow2(bm.height()))
                    return SkNEW_ARGS(RGB16_Point_Repeat_Pow2_Sampler, (bm));
                else
                    return SkNEW_ARGS(RGB16_Point_Repeat_Mod_Sampler, (bm));
            case SkShader::kMirror_TileMode:
                if (is_pow2(bm.width()) && is_pow2(bm.height()))
                    return SkNEW_ARGS(RGB16_Point_Mirror_Pow2_Sampler, (bm));
                else
                    return SkNEW_ARGS(RGB16_Point_Mirror_Mod_Sampler, (bm));
            default:
                SkASSERT(!"unknown mode");
            }
        }
        else {  // tmx != tmy
            return SkNEW_ARGS(RGB16_Point_Sampler, (bm, tmx, tmy));
        }
        break;

    case SkBitmap::kIndex8_Config:
        if (doFilter)
            return SkNEW_ARGS(Index8_Bilinear_Sampler, (bm, tmx, tmy));

        if (tmx == tmy) {
            switch (tmx) {
            case SkShader::kClamp_TileMode:
                return SkNEW_ARGS(Index8_Point_Clamp_Sampler, (bm));
            case SkShader::kRepeat_TileMode:
                if (is_pow2(bm.width()) && is_pow2(bm.height()))
                    return SkNEW_ARGS(Index8_Point_Repeat_Pow2_Sampler, (bm));
                else
                    return SkNEW_ARGS(Index8_Point_Repeat_Mod_Sampler, (bm));
            case SkShader::kMirror_TileMode:
                if (is_pow2(bm.width()) && is_pow2(bm.height()))
                    return SkNEW_ARGS(Index8_Point_Mirror_Pow2_Sampler, (bm));
                else
                    return SkNEW_ARGS(Index8_Point_Mirror_Mod_Sampler, (bm));
            default:
                SkASSERT(!"unknown mode");
            }
        }
        else {  // tmx != tmy
            return SkNEW_ARGS(Index8_Point_Sampler, (bm, tmx, tmy));
        }
        break;

    case SkBitmap::kA8_Config:
        if (doFilter)
            return SkNEW_ARGS(A8_Bilinear_Sampler, (bm, tmx, tmy));
        else
            return SkNEW_ARGS(A8_NoFilter_Sampler, (bm, tmx, tmy));
        break;

    default:
        SkASSERT(!"unknown device");
    }
    return SkNEW_ARGS(SkNullBitmapSampler, (bm, doFilter, tmx, tmy));
}