/*
* 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_WIN)
#include "SkAutoCoInitialize.h"
#include "SkAutoMalloc.h"
#include "SkBitmap.h"
#include "SkImageEncoderPriv.h"
#include "SkIStream.h"
#include "SkImageEncoder.h"
#include "SkStream.h"
#include "SkTScopedComPtr.h"
#include "SkTemplates.h"
#include "SkUnPreMultiply.h"
#include <wincodec.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
bool SkEncodeImageWithWIC(SkWStream* stream, const SkPixmap& pixmap,
SkEncodedImageFormat format, int quality) {
GUID type;
switch (format) {
case SkEncodedImageFormat::kJPEG:
type = GUID_ContainerFormatJpeg;
break;
case SkEncodedImageFormat::kPNG:
type = GUID_ContainerFormatPng;
break;
default:
return false;
}
SkBitmap bitmapOrig;
if (!bitmapOrig.installPixels(pixmap)) {
return false;
}
bitmapOrig.setImmutable();
// First convert to BGRA if necessary.
SkBitmap bitmap;
if (!bitmap.tryAllocPixels(bitmapOrig.info().makeColorType(kBGRA_8888_SkColorType)) ||
!bitmapOrig.readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), 0, 0))
{
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 (SkEncodedImageFormat::kJPEG == format) {
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) & 0xFF);
dstRow[1] = (uint8_t) ((bgra >> 8) & 0xFF);
dstRow[2] = (uint8_t) ((bgra >> 16) & 0xFF);
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;
memset(&name, 0, sizeof(name));
name.dwType = PROPBAG2_TYPE_DATA;
name.vt = VT_R4;
name.pstrName = const_cast<LPOLESTR>(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);
}
#endif // defined(SK_BUILD_FOR_WIN)