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

#include "include/private/SkColorData.h"
#include "src/codec/SkCodecPriv.h"
#include "src/codec/SkMaskSwizzler.h"

static void swizzle_mask16_to_rgba_opaque(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask16_to_bgra_opaque(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask16_to_rgba_unpremul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask16_to_bgra_unpremul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask16_to_rgba_premul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask16_to_bgra_premul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

// TODO (msarett): We have promoted a two byte per pixel image to 8888, only to
// convert it back to 565. Instead, we should swizzle to 565 directly.
static void swizzle_mask16_to_565(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint16_t* srcPtr = ((uint16_t*) srcRow) + startX;
    uint16_t* dstPtr = (uint16_t*) dstRow;
    for (int i = 0; i < width; i++) {
        uint16_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPack888ToRGB16(red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask24_to_rgba_opaque(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask24_to_bgra_opaque(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask24_to_rgba_unpremul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask24_to_bgra_unpremul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask24_to_rgba_premul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask24_to_bgra_premul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask24_to_565(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    srcRow += 3 * startX;
    uint16_t* dstPtr = (uint16_t*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16;
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPack888ToRGB16(red, green, blue);
        srcRow += 3 * sampleX;
    }
}

static void swizzle_mask32_to_rgba_opaque(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask32_to_bgra_opaque(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask32_to_rgba_unpremul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask32_to_bgra_unpremul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask32_to_rgba_premul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask32_to_bgra_premul(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {

    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    SkPMColor* dstPtr = (SkPMColor*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        uint8_t alpha = masks->getAlpha(p);
        dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue);
        srcPtr += sampleX;
    }
}

static void swizzle_mask32_to_565(
        void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
        uint32_t startX, uint32_t sampleX) {
    // Use the masks to decode to the destination
    uint32_t* srcPtr = ((uint32_t*) srcRow) + startX;
    uint16_t* dstPtr = (uint16_t*) dstRow;
    for (int i = 0; i < width; i++) {
        uint32_t p = srcPtr[0];
        uint8_t red = masks->getRed(p);
        uint8_t green = masks->getGreen(p);
        uint8_t blue = masks->getBlue(p);
        dstPtr[i] = SkPack888ToRGB16(red, green, blue);
        srcPtr += sampleX;
    }
}

/*
 *
 * Create a new mask swizzler
 *
 */
SkMaskSwizzler* SkMaskSwizzler::CreateMaskSwizzler(const SkImageInfo& dstInfo,
        bool srcIsOpaque, SkMasks* masks, uint32_t bitsPerPixel,
        const SkCodec::Options& options) {

    // Choose the appropriate row procedure
    RowProc proc = nullptr;
    switch (bitsPerPixel) {
        case 16:
            switch (dstInfo.colorType()) {
                case kRGBA_8888_SkColorType:
                    if (srcIsOpaque) {
                        proc = &swizzle_mask16_to_rgba_opaque;
                    } else {
                        switch (dstInfo.alphaType()) {
                            case kUnpremul_SkAlphaType:
                                proc = &swizzle_mask16_to_rgba_unpremul;
                                break;
                            case kPremul_SkAlphaType:
                                proc = &swizzle_mask16_to_rgba_premul;
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case kBGRA_8888_SkColorType:
                    if (srcIsOpaque) {
                        proc = &swizzle_mask16_to_bgra_opaque;
                    } else {
                        switch (dstInfo.alphaType()) {
                            case kUnpremul_SkAlphaType:
                                proc = &swizzle_mask16_to_bgra_unpremul;
                                break;
                            case kPremul_SkAlphaType:
                                proc = &swizzle_mask16_to_bgra_premul;
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case kRGB_565_SkColorType:
                    proc = &swizzle_mask16_to_565;
                    break;
                default:
                    break;
            }
            break;
        case 24:
            switch (dstInfo.colorType()) {
                case kRGBA_8888_SkColorType:
                    if (srcIsOpaque) {
                        proc = &swizzle_mask24_to_rgba_opaque;
                    } else {
                        switch (dstInfo.alphaType()) {
                            case kUnpremul_SkAlphaType:
                                proc = &swizzle_mask24_to_rgba_unpremul;
                                break;
                            case kPremul_SkAlphaType:
                                proc = &swizzle_mask24_to_rgba_premul;
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case kBGRA_8888_SkColorType:
                    if (srcIsOpaque) {
                        proc = &swizzle_mask24_to_bgra_opaque;
                    } else {
                        switch (dstInfo.alphaType()) {
                            case kUnpremul_SkAlphaType:
                                proc = &swizzle_mask24_to_bgra_unpremul;
                                break;
                            case kPremul_SkAlphaType:
                                proc = &swizzle_mask24_to_bgra_premul;
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case kRGB_565_SkColorType:
                    proc = &swizzle_mask24_to_565;
                    break;
                default:
                    break;
            }
            break;
        case 32:
            switch (dstInfo.colorType()) {
                case kRGBA_8888_SkColorType:
                    if (srcIsOpaque) {
                        proc = &swizzle_mask32_to_rgba_opaque;
                    } else {
                        switch (dstInfo.alphaType()) {
                            case kUnpremul_SkAlphaType:
                                proc = &swizzle_mask32_to_rgba_unpremul;
                                break;
                            case kPremul_SkAlphaType:
                                proc = &swizzle_mask32_to_rgba_premul;
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case kBGRA_8888_SkColorType:
                    if (srcIsOpaque) {
                        proc = &swizzle_mask32_to_bgra_opaque;
                    } else {
                        switch (dstInfo.alphaType()) {
                            case kUnpremul_SkAlphaType:
                                proc = &swizzle_mask32_to_bgra_unpremul;
                                break;
                            case kPremul_SkAlphaType:
                                proc = &swizzle_mask32_to_bgra_premul;
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case kRGB_565_SkColorType:
                    proc = &swizzle_mask32_to_565;
                    break;
                default:
                    break;
            }
            break;
        default:
            SkASSERT(false);
            return nullptr;
    }

    int srcOffset = 0;
    int srcWidth = dstInfo.width();
    if (options.fSubset) {
        srcOffset = options.fSubset->left();
        srcWidth = options.fSubset->width();
    }

    return new SkMaskSwizzler(masks, proc, srcOffset, srcWidth);
}

/*
 *
 * Constructor for mask swizzler
 *
 */
SkMaskSwizzler::SkMaskSwizzler(SkMasks* masks, RowProc proc, int srcOffset, int subsetWidth)
    : fMasks(masks)
    , fRowProc(proc)
    , fSubsetWidth(subsetWidth)
    , fDstWidth(subsetWidth)
    , fSampleX(1)
    , fSrcOffset(srcOffset)
    , fX0(srcOffset)
{}

int SkMaskSwizzler::onSetSampleX(int sampleX) {
    // FIXME: Share this function with SkSwizzler?
    SkASSERT(sampleX > 0); // Surely there is an upper limit? Should there be
                           // way to report failure?
    fSampleX = sampleX;
    fX0 = get_start_coord(sampleX) + fSrcOffset;
    fDstWidth = get_scaled_dimension(fSubsetWidth, sampleX);

    // check that fX0 is valid
    SkASSERT(fX0 >= 0);
    return fDstWidth;
}

/*
 *
 * Swizzle the specified row
 *
 */
void SkMaskSwizzler::swizzle(void* dst, const uint8_t* SK_RESTRICT src) {
    SkASSERT(nullptr != dst && nullptr != src);
    fRowProc(dst, src, fDstWidth, fMasks, fX0, fSampleX);
}
