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

#include "SkTypes.h"

#if defined(SK_BUILD_FOR_WIN32)

// Workaround for:
// http://connect.microsoft.com/VisualStudio/feedback/details/621653/
// http://crbug.com/225822
// In VS2010 both intsafe.h and stdint.h define the following without guards.
// SkTypes brought in windows.h and stdint.h and the following defines are
// not used by this file. However, they may be re-introduced by wincodec.h.
#undef INT8_MIN
#undef INT16_MIN
#undef INT32_MIN
#undef INT64_MIN
#undef INT8_MAX
#undef UINT8_MAX
#undef INT16_MAX
#undef UINT16_MAX
#undef INT32_MAX
#undef UINT32_MAX
#undef INT64_MAX
#undef UINT64_MAX

#include <wincodec.h>
#include "SkAutoCoInitialize.h"
#include "SkBitmap.h"
#include "SkImageEncoder.h"
#include "SkIStream.h"
#include "SkStream.h"
#include "SkTScopedComPtr.h"
#include "SkUnPreMultiply.h"

//All Windows SDKs back to XPSP2 export the CLSID_WICImagingFactory symbol.
//In the Windows8 SDK the CLSID_WICImagingFactory symbol is still exported
//but CLSID_WICImagingFactory is then #defined to CLSID_WICImagingFactory2.
//Undo this #define if it has been done so that we link against the symbols
//we intended to link against on all SDKs.
#if defined(CLSID_WICImagingFactory)
#undef CLSID_WICImagingFactory
#endif

class SkImageEncoder_WIC : public SkImageEncoder {
public:
    SkImageEncoder_WIC(Type t) : fType(t) {}

protected:
    virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);

private:
    Type fType;
};

bool SkImageEncoder_WIC::onEncode(SkWStream* stream
                                , const SkBitmap& bitmapOrig
                                , int quality)
{
    GUID type;
    switch (fType) {
        case kJPEG_Type:
            type = GUID_ContainerFormatJpeg;
            break;
        case kPNG_Type:
            type = GUID_ContainerFormatPng;
            break;
        default:
            return false;
    }

    // First convert to BGRA if necessary.
    SkBitmap bitmap;
    if (!bitmapOrig.copyTo(&bitmap, kBGRA_8888_SkColorType)) {
        return false;
    }

    // WIC expects unpremultiplied pixels.  Unpremultiply if necessary.
    if (kPremul_SkAlphaType == bitmap.alphaType()) {
        uint8_t* pixels = reinterpret_cast<uint8_t*>(bitmap.getPixels());
        for (int y = 0; y < bitmap.height(); ++y) {
            for (int x = 0; x < bitmap.width(); ++x) {
                uint8_t* bytes = pixels + y * bitmap.rowBytes() + x * bitmap.bytesPerPixel();
                SkPMColor* src = reinterpret_cast<SkPMColor*>(bytes);
                SkColor* dst = reinterpret_cast<SkColor*>(bytes);
                *dst = SkUnPreMultiply::PMColorToColor(*src);
            }
        }
    }

    // Finally, if we are performing a jpeg encode, we must convert to BGR.
    void* pixels = bitmap.getPixels();
    size_t rowBytes = bitmap.rowBytes();
    SkAutoMalloc pixelStorage;
    WICPixelFormatGUID formatDesired = GUID_WICPixelFormat32bppBGRA;
    if (kJPEG_Type == fType) {
        formatDesired = GUID_WICPixelFormat24bppBGR;
        rowBytes = SkAlign4(bitmap.width() * 3);
        pixelStorage.reset(rowBytes * bitmap.height());
        for (int y = 0; y < bitmap.height(); y++) {
            uint8_t* dstRow = SkTAddOffset<uint8_t>(pixelStorage.get(), y * rowBytes);
            for (int x = 0; x < bitmap.width(); x++) {
                uint32_t bgra = *bitmap.getAddr32(x, y);
                dstRow[0] = (uint8_t) (bgra >>  0);
                dstRow[1] = (uint8_t) (bgra >>  8);
                dstRow[2] = (uint8_t) (bgra >> 16);
                dstRow += 3;
            }
        }

        pixels = pixelStorage.get();
    }


    //Initialize COM.
    SkAutoCoInitialize scopedCo;
    if (!scopedCo.succeeded()) {
        return false;
    }

    HRESULT hr = S_OK;

    //Create Windows Imaging Component ImagingFactory.
    SkTScopedComPtr<IWICImagingFactory> piImagingFactory;
    if (SUCCEEDED(hr)) {
        hr = CoCreateInstance(
            CLSID_WICImagingFactory
            , nullptr
            , CLSCTX_INPROC_SERVER
            , IID_PPV_ARGS(&piImagingFactory)
        );
    }

    //Convert the SkWStream to an IStream.
    SkTScopedComPtr<IStream> piStream;
    if (SUCCEEDED(hr)) {
        hr = SkWIStream::CreateFromSkWStream(stream, &piStream);
    }

    //Create an encode of the appropriate type.
    SkTScopedComPtr<IWICBitmapEncoder> piEncoder;
    if (SUCCEEDED(hr)) {
        hr = piImagingFactory->CreateEncoder(type, nullptr, &piEncoder);
    }

    if (SUCCEEDED(hr)) {
        hr = piEncoder->Initialize(piStream.get(), WICBitmapEncoderNoCache);
    }

    //Create a the frame.
    SkTScopedComPtr<IWICBitmapFrameEncode> piBitmapFrameEncode;
    SkTScopedComPtr<IPropertyBag2> piPropertybag;
    if (SUCCEEDED(hr)) {
        hr = piEncoder->CreateNewFrame(&piBitmapFrameEncode, &piPropertybag);
    }

    if (SUCCEEDED(hr)) {
        PROPBAG2 name = { 0 };
        name.dwType = PROPBAG2_TYPE_DATA;
        name.vt = VT_R4;
        name.pstrName = L"ImageQuality";

        VARIANT value;
        VariantInit(&value);
        value.vt = VT_R4;
        value.fltVal = (FLOAT)(quality / 100.0);

        //Ignore result code.
        //  This returns E_FAIL if the named property is not in the bag.
        //TODO(bungeman) enumerate the properties,
        //  write and set hr iff property exists.
        piPropertybag->Write(1, &name, &value);
    }
    if (SUCCEEDED(hr)) {
        hr = piBitmapFrameEncode->Initialize(piPropertybag.get());
    }

    //Set the size of the frame.
    const UINT width = bitmap.width();
    const UINT height = bitmap.height();
    if (SUCCEEDED(hr)) {
        hr = piBitmapFrameEncode->SetSize(width, height);
    }

    //Set the pixel format of the frame.  If native encoded format cannot match BGRA,
    //it will choose the closest pixel format that it supports.
    WICPixelFormatGUID formatGUID = formatDesired;
    if (SUCCEEDED(hr)) {
        hr = piBitmapFrameEncode->SetPixelFormat(&formatGUID);
    }
    if (SUCCEEDED(hr)) {
        //Be sure the image format is the one requested.
        hr = IsEqualGUID(formatGUID, formatDesired) ? S_OK : E_FAIL;
    }

    //Write the pixels into the frame.
    if (SUCCEEDED(hr)) {
        hr = piBitmapFrameEncode->WritePixels(height,
                                              (UINT) rowBytes,
                                              (UINT) rowBytes * height,
                                              reinterpret_cast<BYTE*>(pixels));
    }

    if (SUCCEEDED(hr)) {
        hr = piBitmapFrameEncode->Commit();
    }

    if (SUCCEEDED(hr)) {
        hr = piEncoder->Commit();
    }

    return SUCCEEDED(hr);
}

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

#ifdef SK_USE_WIC_ENCODER
static SkImageEncoder* sk_imageencoder_wic_factory(SkImageEncoder::Type t) {
    switch (t) {
        case SkImageEncoder::kPNG_Type:
        case SkImageEncoder::kJPEG_Type:
            break;
        default:
            return nullptr;
    }
    return new SkImageEncoder_WIC(t);
}

static SkImageEncoder_EncodeReg gEReg(sk_imageencoder_wic_factory);
#endif

SkImageEncoder* CreateImageEncoder_WIC(SkImageEncoder::Type type) {
    return new SkImageEncoder_WIC(type);
}

#endif // defined(SK_BUILD_FOR_WIN32)
