/*
* 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"
// 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 "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkIStream.h"
#include "SkMovie.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 SkImageDecoder_WIC : public SkImageDecoder {
public:
// Decoding modes corresponding to SkImageDecoder::Mode, plus an extra mode for decoding
// only the format.
enum WICModes {
kDecodeFormat_WICMode,
kDecodeBounds_WICMode,
kDecodePixels_WICMode,
};
/**
* Helper function to decode an SkStream.
* @param stream SkStream to decode. Must be at the beginning.
* @param bm SkBitmap to decode into. Only used if wicMode is kDecodeBounds_WICMode or
* kDecodePixels_WICMode, in which case it must not be NULL.
* @param format Out parameter for the SkImageDecoder::Format of the SkStream. Only used if
* wicMode is kDecodeFormat_WICMode.
*/
bool decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode, Format* format) const;
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE;
};
struct FormatConversion {
GUID fGuidFormat;
SkImageDecoder::Format fFormat;
};
static const FormatConversion gFormatConversions[] = {
{ GUID_ContainerFormatBmp, SkImageDecoder::kBMP_Format },
{ GUID_ContainerFormatGif, SkImageDecoder::kGIF_Format },
{ GUID_ContainerFormatIco, SkImageDecoder::kICO_Format },
{ GUID_ContainerFormatJpeg, SkImageDecoder::kJPEG_Format },
{ GUID_ContainerFormatPng, SkImageDecoder::kPNG_Format },
};
static SkImageDecoder::Format GuidContainerFormat_to_Format(REFGUID guid) {
for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
if (IsEqualGUID(guid, gFormatConversions[i].fGuidFormat)) {
return gFormatConversions[i].fFormat;
}
}
return SkImageDecoder::kUnknown_Format;
}
bool SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
WICModes wicMode;
switch (mode) {
case SkImageDecoder::kDecodeBounds_Mode:
wicMode = kDecodeBounds_WICMode;
break;
case SkImageDecoder::kDecodePixels_Mode:
wicMode = kDecodePixels_WICMode;
break;
}
return this->decodeStream(stream, bm, wicMode, NULL);
}
bool SkImageDecoder_WIC::decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode,
Format* format) const {
//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
, NULL
, CLSCTX_INPROC_SERVER
, IID_PPV_ARGS(&piImagingFactory)
);
}
//Convert SkStream to IStream.
SkTScopedComPtr<IStream> piStream;
if (SUCCEEDED(hr)) {
hr = SkIStream::CreateFromSkStream(stream, false, &piStream);
}
//Make sure we're at the beginning of the stream.
if (SUCCEEDED(hr)) {
LARGE_INTEGER liBeginning = { 0 };
hr = piStream->Seek(liBeginning, STREAM_SEEK_SET, NULL);
}
//Create the decoder from the stream content.
SkTScopedComPtr<IWICBitmapDecoder> piBitmapDecoder;
if (SUCCEEDED(hr)) {
hr = piImagingFactory->CreateDecoderFromStream(
piStream.get() //Image to be decoded
, NULL //No particular vendor
, WICDecodeMetadataCacheOnDemand //Cache metadata when needed
, &piBitmapDecoder //Pointer to the decoder
);
}
if (kDecodeFormat_WICMode == wicMode) {
SkASSERT(format != NULL);
//Get the format
if (SUCCEEDED(hr)) {
GUID guidFormat;
hr = piBitmapDecoder->GetContainerFormat(&guidFormat);
if (SUCCEEDED(hr)) {
*format = GuidContainerFormat_to_Format(guidFormat);
return true;
}
}
return false;
}
//Get the first frame from the decoder.
SkTScopedComPtr<IWICBitmapFrameDecode> piBitmapFrameDecode;
if (SUCCEEDED(hr)) {
hr = piBitmapDecoder->GetFrame(0, &piBitmapFrameDecode);
}
//Get the BitmapSource interface of the frame.
SkTScopedComPtr<IWICBitmapSource> piBitmapSourceOriginal;
if (SUCCEEDED(hr)) {
hr = piBitmapFrameDecode->QueryInterface(
IID_PPV_ARGS(&piBitmapSourceOriginal)
);
}
//Get the size of the bitmap.
UINT width;
UINT height;
if (SUCCEEDED(hr)) {
hr = piBitmapSourceOriginal->GetSize(&width, &height);
}
//Exit early if we're only looking for the bitmap bounds.
if (SUCCEEDED(hr)) {
bm->setConfig(SkBitmap::kARGB_8888_Config, width, height);
if (kDecodeBounds_WICMode == wicMode) {
return true;
}
if (!this->allocPixelRef(bm, NULL)) {
return false;
}
}
//Create a format converter.
SkTScopedComPtr<IWICFormatConverter> piFormatConverter;
if (SUCCEEDED(hr)) {
hr = piImagingFactory->CreateFormatConverter(&piFormatConverter);
}
GUID destinationPixelFormat;
if (this->getRequireUnpremultipliedColors()) {
destinationPixelFormat = GUID_WICPixelFormat32bppBGRA;
} else {
destinationPixelFormat = GUID_WICPixelFormat32bppPBGRA;
}
if (SUCCEEDED(hr)) {
hr = piFormatConverter->Initialize(
piBitmapSourceOriginal.get() //Input bitmap to convert
, destinationPixelFormat //Destination pixel format
, WICBitmapDitherTypeNone //Specified dither patterm
, NULL //Specify a particular palette
, 0.f //Alpha threshold
, WICBitmapPaletteTypeCustom //Palette translation type
);
}
//Get the BitmapSource interface of the format converter.
SkTScopedComPtr<IWICBitmapSource> piBitmapSourceConverted;
if (SUCCEEDED(hr)) {
hr = piFormatConverter->QueryInterface(
IID_PPV_ARGS(&piBitmapSourceConverted)
);
}
//Copy the pixels into the bitmap.
if (SUCCEEDED(hr)) {
SkAutoLockPixels alp(*bm);
bm->eraseColor(SK_ColorTRANSPARENT);
const UINT stride = (UINT) bm->rowBytes();
hr = piBitmapSourceConverted->CopyPixels(
NULL, //Get all the pixels
stride,
stride * height,
reinterpret_cast<BYTE *>(bm->getPixels())
);
// Note: we don't need to premultiply here since we specified PBGRA
if (SkBitmap::ComputeIsOpaque(*bm)) {
bm->setAlphaType(kOpaque_SkAlphaType);
}
}
return SUCCEEDED(hr);
}
/////////////////////////////////////////////////////////////////////////
extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
SkImageDecoder* decoder = image_decoder_from_stream(stream);
if (NULL == decoder) {
// If no image decoder specific to the stream exists, use SkImageDecoder_WIC.
return SkNEW(SkImageDecoder_WIC);
} else {
return decoder;
}
}
/////////////////////////////////////////////////////////////////////////
SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
return NULL;
}
/////////////////////////////////////////////////////////////////////////
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 kBMP_Type:
type = GUID_ContainerFormatBmp;
break;
case kICO_Type:
type = GUID_ContainerFormatIco;
break;
case kJPEG_Type:
type = GUID_ContainerFormatJpeg;
break;
case kPNG_Type:
type = GUID_ContainerFormatPng;
break;
default:
return false;
}
//Convert to 8888 if needed.
const SkBitmap* bitmap;
SkBitmap bitmapCopy;
if (SkBitmap::kARGB_8888_Config == bitmapOrig.config() && bitmapOrig.isOpaque()) {
bitmap = &bitmapOrig;
} else {
if (!bitmapOrig.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config)) {
return false;
}
bitmap = &bitmapCopy;
}
// We cannot use PBGRA so we need to unpremultiply ourselves
if (!bitmap->isOpaque()) {
SkAutoLockPixels alp(*bitmap);
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);
}
}
}
//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
, NULL
, 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, NULL, &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.
const WICPixelFormatGUID formatDesired = GUID_WICPixelFormat32bppBGRA;
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)) {
SkAutoLockPixels alp(*bitmap);
const UINT stride = (UINT) bitmap->rowBytes();
hr = piBitmapFrameEncode->WritePixels(
height
, stride
, stride * height
, reinterpret_cast<BYTE*>(bitmap->getPixels()));
}
if (SUCCEEDED(hr)) {
hr = piBitmapFrameEncode->Commit();
}
if (SUCCEEDED(hr)) {
hr = piEncoder->Commit();
}
return SUCCEEDED(hr);
}
///////////////////////////////////////////////////////////////////////////////
static SkImageEncoder* sk_imageencoder_wic_factory(SkImageEncoder::Type t) {
switch (t) {
case SkImageEncoder::kBMP_Type:
case SkImageEncoder::kICO_Type:
case SkImageEncoder::kJPEG_Type:
case SkImageEncoder::kPNG_Type:
break;
default:
return NULL;
}
return SkNEW_ARGS(SkImageEncoder_WIC, (t));
}
static SkImageEncoder_EncodeReg gEReg(sk_imageencoder_wic_factory);
static SkImageDecoder::Format get_format_wic(SkStreamRewindable* stream) {
SkImageDecoder::Format format;
SkImageDecoder_WIC codec;
if (!codec.decodeStream(stream, NULL, SkImageDecoder_WIC::kDecodeFormat_WICMode, &format)) {
format = SkImageDecoder::kUnknown_Format;
}
return format;
}
static SkImageDecoder_FormatReg gFormatReg(get_format_wic);