/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkBlitMask.h"
#include "SkColor.h"
#include "SkColorPriv.h"

static void D32_A8_Color(void* SK_RESTRICT dst, size_t dstRB,
                         const void* SK_RESTRICT maskPtr, size_t maskRB,
                         SkColor color, int width, int height) {
    SkPMColor pmc = SkPreMultiplyColor(color);
    size_t dstOffset = dstRB - (width << 2);
    size_t maskOffset = maskRB - width;
    SkPMColor* SK_RESTRICT device = (SkPMColor *)dst;
    const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr;

    do {
        int w = width;
        do {
            unsigned aa = *mask++;
            *device = SkBlendARGB32(pmc, *device, aa);
            device += 1;
        } while (--w != 0);
        device = (uint32_t*)((char*)device + dstOffset);
        mask += maskOffset;
    } while (--height != 0);
}

static void D32_A8_Opaque(void* SK_RESTRICT dst, size_t dstRB,
                          const void* SK_RESTRICT maskPtr, size_t maskRB,
                          SkColor color, int width, int height) {
    SkPMColor pmc = SkPreMultiplyColor(color);
    SkPMColor* SK_RESTRICT device = (SkPMColor*)dst;
    const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr;

    maskRB -= width;
    dstRB -= (width << 2);
    do {
        int w = width;
        do {
            unsigned aa = *mask++;
            *device = SkAlphaMulQ(pmc, SkAlpha255To256(aa)) + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa));
            device += 1;
        } while (--w != 0);
        device = (uint32_t*)((char*)device + dstRB);
        mask += maskRB;
    } while (--height != 0);
}

static void D32_A8_Black(void* SK_RESTRICT dst, size_t dstRB,
                         const void* SK_RESTRICT maskPtr, size_t maskRB,
                         SkColor, int width, int height) {
    SkPMColor* SK_RESTRICT device = (SkPMColor*)dst;
    const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr;

    maskRB -= width;
    dstRB -= (width << 2);
    do {
        int w = width;
        do {
            unsigned aa = *mask++;
            *device = (aa << SK_A32_SHIFT) + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa));
            device += 1;
        } while (--w != 0);
        device = (uint32_t*)((char*)device + dstRB);
        mask += maskRB;
    } while (--height != 0);
}

SkBlitMask::BlitLCD16RowProc SkBlitMask::BlitLCD16RowFactory(bool isOpaque) {
    BlitLCD16RowProc proc = PlatformBlitRowProcs16(isOpaque);
    if (proc) {
        return proc;
    }

    if (isOpaque) {
        return  SkBlitLCD16OpaqueRow;
    } else {
        return  SkBlitLCD16Row;
    }
}

static void D32_LCD16_Proc(void* SK_RESTRICT dst, size_t dstRB,
                           const void* SK_RESTRICT mask, size_t maskRB,
                           SkColor color, int width, int height) {

    SkPMColor*        dstRow = (SkPMColor*)dst;
    const uint16_t* srcRow = (const uint16_t*)mask;
    SkPMColor       opaqueDst;

    SkBlitMask::BlitLCD16RowProc proc = NULL;
    bool isOpaque = (0xFF == SkColorGetA(color));
    proc = SkBlitMask::BlitLCD16RowFactory(isOpaque);
    SkASSERT(proc != NULL);

    if (isOpaque) {
        opaqueDst = SkPreMultiplyColor(color);
    } else {
        opaqueDst = 0;  // ignored
    }

    do {
        proc(dstRow, srcRow, color, width, opaqueDst);
        dstRow = (SkPMColor*)((char*)dstRow + dstRB);
        srcRow = (const uint16_t*)((const char*)srcRow + maskRB);
    } while (--height != 0);
}

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

static void blit_lcd32_opaque_row(SkPMColor* SK_RESTRICT dst,
                                  const SkPMColor* SK_RESTRICT src,
                                  SkColor color, int width) {
    int srcR = SkColorGetR(color);
    int srcG = SkColorGetG(color);
    int srcB = SkColorGetB(color);

    for (int i = 0; i < width; i++) {
        SkPMColor mask = src[i];
        if (0 == mask) {
            continue;
        }

        SkPMColor d = dst[i];

        int maskR = SkGetPackedR32(mask);
        int maskG = SkGetPackedG32(mask);
        int maskB = SkGetPackedB32(mask);

        // Now upscale them to 0..256, so we can use SkAlphaBlend
        maskR = SkAlpha255To256(maskR);
        maskG = SkAlpha255To256(maskG);
        maskB = SkAlpha255To256(maskB);

        int dstR = SkGetPackedR32(d);
        int dstG = SkGetPackedG32(d);
        int dstB = SkGetPackedB32(d);

        // LCD blitting is only supported if the dst is known/required
        // to be opaque
        dst[i] = SkPackARGB32(0xFF,
                              SkAlphaBlend(srcR, dstR, maskR),
                              SkAlphaBlend(srcG, dstG, maskG),
                              SkAlphaBlend(srcB, dstB, maskB));
    }
}

static void blit_lcd32_row(SkPMColor* SK_RESTRICT dst,
                           const SkPMColor* SK_RESTRICT src,
                           SkColor color, int width) {
    int srcA = SkColorGetA(color);
    int srcR = SkColorGetR(color);
    int srcG = SkColorGetG(color);
    int srcB = SkColorGetB(color);

    srcA = SkAlpha255To256(srcA);

    for (int i = 0; i < width; i++) {
        SkPMColor mask = src[i];
        if (0 == mask) {
            continue;
        }

        SkPMColor d = dst[i];

        int maskR = SkGetPackedR32(mask);
        int maskG = SkGetPackedG32(mask);
        int maskB = SkGetPackedB32(mask);

        // Now upscale them to 0..256, so we can use SkAlphaBlend
        maskR = SkAlpha255To256(maskR);
        maskG = SkAlpha255To256(maskG);
        maskB = SkAlpha255To256(maskB);

        maskR = maskR * srcA >> 8;
        maskG = maskG * srcA >> 8;
        maskB = maskB * srcA >> 8;

        int dstR = SkGetPackedR32(d);
        int dstG = SkGetPackedG32(d);
        int dstB = SkGetPackedB32(d);

        // LCD blitting is only supported if the dst is known/required
        // to be opaque
        dst[i] = SkPackARGB32(0xFF,
                              SkAlphaBlend(srcR, dstR, maskR),
                              SkAlphaBlend(srcG, dstG, maskG),
                              SkAlphaBlend(srcB, dstB, maskB));
    }
}

static void D32_LCD32_Blend(void* SK_RESTRICT dst, size_t dstRB,
                            const void* SK_RESTRICT mask, size_t maskRB,
                            SkColor color, int width, int height) {
    SkASSERT(height > 0);
    SkPMColor* SK_RESTRICT dstRow = (SkPMColor*)dst;
    const SkPMColor* SK_RESTRICT srcRow = (const SkPMColor*)mask;

    do {
        blit_lcd32_row(dstRow, srcRow, color, width);
        dstRow = (SkPMColor*)((char*)dstRow + dstRB);
        srcRow = (const SkPMColor*)((const char*)srcRow + maskRB);
    } while (--height != 0);
}

static void D32_LCD32_Opaque(void* SK_RESTRICT dst, size_t dstRB,
                             const void* SK_RESTRICT mask, size_t maskRB,
                             SkColor color, int width, int height) {
    SkASSERT(height > 0);
    SkPMColor* SK_RESTRICT dstRow = (SkPMColor*)dst;
    const SkPMColor* SK_RESTRICT srcRow = (const SkPMColor*)mask;

    do {
        blit_lcd32_opaque_row(dstRow, srcRow, color, width);
        dstRow = (SkPMColor*)((char*)dstRow + dstRB);
        srcRow = (const SkPMColor*)((const char*)srcRow + maskRB);
    } while (--height != 0);
}

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

static SkBlitMask::ColorProc D32_A8_Factory(SkColor color) {
    if (SK_ColorBLACK == color) {
        return D32_A8_Black;
    } else if (0xFF == SkColorGetA(color)) {
        return D32_A8_Opaque;
    } else {
        return D32_A8_Color;
    }
}

static SkBlitMask::ColorProc D32_LCD32_Factory(SkColor color) {
    return (0xFF == SkColorGetA(color)) ? D32_LCD32_Opaque : D32_LCD32_Blend;
}

SkBlitMask::ColorProc SkBlitMask::ColorFactory(SkColorType ct,
                                               SkMask::Format format,
                                               SkColor color) {
    ColorProc proc = PlatformColorProcs(ct, format, color);
    if (proc) {
        return proc;
    }

    switch (ct) {
        case kN32_SkColorType:
            switch (format) {
                case SkMask::kA8_Format:
                    return D32_A8_Factory(color);
                case SkMask::kLCD16_Format:
                    return D32_LCD16_Proc;
                case SkMask::kLCD32_Format:
                    return D32_LCD32_Factory(color);
                default:
                    break;
            }
            break;
        default:
            break;
    }
    return NULL;
}

bool SkBlitMask::BlitColor(const SkBitmap& device, const SkMask& mask,
                           const SkIRect& clip, SkColor color) {
    ColorProc proc = ColorFactory(device.colorType(), mask.fFormat, color);
    if (proc) {
        int x = clip.fLeft;
        int y = clip.fTop;
        proc(device.getAddr32(x, y), device.rowBytes(), mask.getAddr(x, y),
             mask.fRowBytes, color, clip.width(), clip.height());
        return true;
    }
    return false;
}

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

static void BW_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
                             const uint8_t* SK_RESTRICT mask,
                             const SkPMColor* SK_RESTRICT src, int count) {
    int i, octuple = (count + 7) >> 3;
    for (i = 0; i < octuple; ++i) {
        int m = *mask++;
        if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); }
        if (m & 0x40) { dst[1] = SkPMSrcOver(src[1], dst[1]); }
        if (m & 0x20) { dst[2] = SkPMSrcOver(src[2], dst[2]); }
        if (m & 0x10) { dst[3] = SkPMSrcOver(src[3], dst[3]); }
        if (m & 0x08) { dst[4] = SkPMSrcOver(src[4], dst[4]); }
        if (m & 0x04) { dst[5] = SkPMSrcOver(src[5], dst[5]); }
        if (m & 0x02) { dst[6] = SkPMSrcOver(src[6], dst[6]); }
        if (m & 0x01) { dst[7] = SkPMSrcOver(src[7], dst[7]); }
        src += 8;
        dst += 8;
    }
    count &= 7;
    if (count > 0) {
        int m = *mask;
        do {
            if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); }
            m <<= 1;
            src += 1;
            dst += 1;
        } while (--count > 0);
    }
}

static void BW_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
                              const uint8_t* SK_RESTRICT mask,
                              const SkPMColor* SK_RESTRICT src, int count) {
    int i, octuple = (count + 7) >> 3;
    for (i = 0; i < octuple; ++i) {
        int m = *mask++;
        if (m & 0x80) { dst[0] = src[0]; }
        if (m & 0x40) { dst[1] = src[1]; }
        if (m & 0x20) { dst[2] = src[2]; }
        if (m & 0x10) { dst[3] = src[3]; }
        if (m & 0x08) { dst[4] = src[4]; }
        if (m & 0x04) { dst[5] = src[5]; }
        if (m & 0x02) { dst[6] = src[6]; }
        if (m & 0x01) { dst[7] = src[7]; }
        src += 8;
        dst += 8;
    }
    count &= 7;
    if (count > 0) {
        int m = *mask;
        do {
            if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); }
            m <<= 1;
            src += 1;
            dst += 1;
        } while (--count > 0);
    }
}

static void A8_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
                             const uint8_t* SK_RESTRICT mask,
                             const SkPMColor* SK_RESTRICT src, int count) {
    for (int i = 0; i < count; ++i) {
        if (mask[i]) {
            dst[i] = SkBlendARGB32(src[i], dst[i], mask[i]);
        }
    }
}

// expand the steps that SkAlphaMulQ performs, but this way we can
//  exand.. add.. combine
// instead of
// expand..combine add expand..combine
//
#define EXPAND0(v, m, s)    ((v) & (m)) * (s)
#define EXPAND1(v, m, s)    (((v) >> 8) & (m)) * (s)
#define COMBINE(e0, e1, m)  ((((e0) >> 8) & (m)) | ((e1) & ~(m)))

static void A8_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
                              const uint8_t* SK_RESTRICT mask,
                              const SkPMColor* SK_RESTRICT src, int count) {
    for (int i = 0; i < count; ++i) {
        int m = mask[i];
        if (m) {
            m += (m >> 7);
#if 1
            // this is slightly slower than the expand/combine version, but it
            // is much closer to the old results, so we use it for now to reduce
            // rebaselining.
            dst[i] = SkAlphaMulQ(src[i], m) + SkAlphaMulQ(dst[i], 256 - m);
#else
            uint32_t v = src[i];
            uint32_t s0 = EXPAND0(v, rbmask, m);
            uint32_t s1 = EXPAND1(v, rbmask, m);
            v = dst[i];
            uint32_t d0 = EXPAND0(v, rbmask, m);
            uint32_t d1 = EXPAND1(v, rbmask, m);
            dst[i] = COMBINE(s0 + d0, s1 + d1, rbmask);
#endif
        }
    }
}

static int upscale31To255(int value) {
    value = (value << 3) | (value >> 2);
    return value;
}

static int src_alpha_blend(int src, int dst, int srcA, int mask) {

    return dst + SkAlphaMul(src - SkAlphaMul(srcA, dst), mask);
}

static void LCD16_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
                                const uint16_t* SK_RESTRICT mask,
                                const SkPMColor* SK_RESTRICT src, int count) {
    for (int i = 0; i < count; ++i) {
        uint16_t m = mask[i];
        if (0 == m) {
            continue;
        }

        SkPMColor s = src[i];
        SkPMColor d = dst[i];

        int srcA = SkGetPackedA32(s);
        int srcR = SkGetPackedR32(s);
        int srcG = SkGetPackedG32(s);
        int srcB = SkGetPackedB32(s);

        srcA += srcA >> 7;

        /*  We want all of these in 5bits, hence the shifts in case one of them
         *  (green) is 6bits.
         */
        int maskR = SkGetPackedR16(m) >> (SK_R16_BITS - 5);
        int maskG = SkGetPackedG16(m) >> (SK_G16_BITS - 5);
        int maskB = SkGetPackedB16(m) >> (SK_B16_BITS - 5);

        maskR = upscale31To255(maskR);
        maskG = upscale31To255(maskG);
        maskB = upscale31To255(maskB);

        int dstR = SkGetPackedR32(d);
        int dstG = SkGetPackedG32(d);
        int dstB = SkGetPackedB32(d);

        // LCD blitting is only supported if the dst is known/required
        // to be opaque
        dst[i] = SkPackARGB32(0xFF,
                              src_alpha_blend(srcR, dstR, srcA, maskR),
                              src_alpha_blend(srcG, dstG, srcA, maskG),
                              src_alpha_blend(srcB, dstB, srcA, maskB));
    }
}

static void LCD16_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
                                 const uint16_t* SK_RESTRICT mask,
                                 const SkPMColor* SK_RESTRICT src, int count) {
    for (int i = 0; i < count; ++i) {
        uint16_t m = mask[i];
        if (0 == m) {
            continue;
        }

        SkPMColor s = src[i];
        SkPMColor d = dst[i];

        int srcR = SkGetPackedR32(s);
        int srcG = SkGetPackedG32(s);
        int srcB = SkGetPackedB32(s);

        /*  We want all of these in 5bits, hence the shifts in case one of them
         *  (green) is 6bits.
         */
        int maskR = SkGetPackedR16(m) >> (SK_R16_BITS - 5);
        int maskG = SkGetPackedG16(m) >> (SK_G16_BITS - 5);
        int maskB = SkGetPackedB16(m) >> (SK_B16_BITS - 5);

        // Now upscale them to 0..32, so we can use blend32
        maskR = SkUpscale31To32(maskR);
        maskG = SkUpscale31To32(maskG);
        maskB = SkUpscale31To32(maskB);

        int dstR = SkGetPackedR32(d);
        int dstG = SkGetPackedG32(d);
        int dstB = SkGetPackedB32(d);

        // LCD blitting is only supported if the dst is known/required
        // to be opaque
        dst[i] = SkPackARGB32(0xFF,
                              SkBlend32(srcR, dstR, maskR),
                              SkBlend32(srcG, dstG, maskG),
                              SkBlend32(srcB, dstB, maskB));
    }
}

static void LCD32_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
                                const SkPMColor* SK_RESTRICT mask,
                                const SkPMColor* SK_RESTRICT src, int count) {
    for (int i = 0; i < count; ++i) {
        SkPMColor m = mask[i];
        if (0 == m) {
            continue;
        }

        SkPMColor s = src[i];
        int srcA = SkGetPackedA32(s);
        int srcR = SkGetPackedR32(s);
        int srcG = SkGetPackedG32(s);
        int srcB = SkGetPackedB32(s);

        srcA = SkAlpha255To256(srcA);

        SkPMColor d = dst[i];

        int maskR = SkGetPackedR32(m);
        int maskG = SkGetPackedG32(m);
        int maskB = SkGetPackedB32(m);

        // Now upscale them to 0..256
        maskR = SkAlpha255To256(maskR);
        maskG = SkAlpha255To256(maskG);
        maskB = SkAlpha255To256(maskB);

        int dstR = SkGetPackedR32(d);
        int dstG = SkGetPackedG32(d);
        int dstB = SkGetPackedB32(d);

        // LCD blitting is only supported if the dst is known/required
        // to be opaque
        dst[i] = SkPackARGB32(0xFF,
                              src_alpha_blend(srcR, dstR, srcA, maskR),
                              src_alpha_blend(srcG, dstG, srcA, maskG),
                              src_alpha_blend(srcB, dstB, srcA, maskB));
    }
}

static void LCD32_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
                                 const SkPMColor* SK_RESTRICT mask,
                                 const SkPMColor* SK_RESTRICT src, int count) {
    for (int i = 0; i < count; ++i) {
        SkPMColor m = mask[i];
        if (0 == m) {
            continue;
        }

        SkPMColor s = src[i];
        SkPMColor d = dst[i];

        int maskR = SkGetPackedR32(m);
        int maskG = SkGetPackedG32(m);
        int maskB = SkGetPackedB32(m);

        int srcR = SkGetPackedR32(s);
        int srcG = SkGetPackedG32(s);
        int srcB = SkGetPackedB32(s);

        int dstR = SkGetPackedR32(d);
        int dstG = SkGetPackedG32(d);
        int dstB = SkGetPackedB32(d);

        // Now upscale them to 0..256, so we can use SkAlphaBlend
        maskR = SkAlpha255To256(maskR);
        maskG = SkAlpha255To256(maskG);
        maskB = SkAlpha255To256(maskB);

        // LCD blitting is only supported if the dst is known/required
        // to be opaque
        dst[i] = SkPackARGB32(0xFF,
                              SkAlphaBlend(srcR, dstR, maskR),
                              SkAlphaBlend(srcG, dstG, maskG),
                              SkAlphaBlend(srcB, dstB, maskB));
    }
}

SkBlitMask::RowProc SkBlitMask::RowFactory(SkColorType ct,
                                           SkMask::Format format,
                                           RowFlags flags) {
// make this opt-in until chrome can rebaseline
    RowProc proc = PlatformRowProcs(ct, format, flags);
    if (proc) {
        return proc;
    }

    static const RowProc gProcs[] = {
        // need X coordinate to handle BW
        false ? (RowProc)BW_RowProc_Blend : NULL, // suppress unused warning
        false ? (RowProc)BW_RowProc_Opaque : NULL, // suppress unused warning
        (RowProc)A8_RowProc_Blend,      (RowProc)A8_RowProc_Opaque,
        (RowProc)LCD16_RowProc_Blend,   (RowProc)LCD16_RowProc_Opaque,
        (RowProc)LCD32_RowProc_Blend,   (RowProc)LCD32_RowProc_Opaque,
    };

    int index;
    switch (ct) {
        case kN32_SkColorType:
            switch (format) {
                case SkMask::kBW_Format:    index = 0; break;
                case SkMask::kA8_Format:    index = 2; break;
                case SkMask::kLCD16_Format: index = 4; break;
                case SkMask::kLCD32_Format: index = 6; break;
                default:
                    return NULL;
            }
            if (flags & kSrcIsOpaque_RowFlag) {
                index |= 1;
            }
            SkASSERT((size_t)index < SK_ARRAY_COUNT(gProcs));
            return gProcs[index];
        default:
            break;
    }
    return NULL;
}