From 242bb89c0d239662b05f5a396d45ad4f28b1596e Mon Sep 17 00:00:00 2001 From: "bungeman@google.com" Date: Wed, 22 Jun 2011 20:42:34 +0000 Subject: Add image encoder/decoder for Windows. http://codereview.appspot.com/4634078/ git-svn-id: http://skia.googlecode.com/svn/trunk@1676 2bbb7eff-a529-9590-31e7-b0007b416f81 --- src/ports/SkImageDecoder_WIC.cpp | 435 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 src/ports/SkImageDecoder_WIC.cpp (limited to 'src/ports/SkImageDecoder_WIC.cpp') diff --git a/src/ports/SkImageDecoder_WIC.cpp b/src/ports/SkImageDecoder_WIC.cpp new file mode 100644 index 0000000000..b53db3bcae --- /dev/null +++ b/src/ports/SkImageDecoder_WIC.cpp @@ -0,0 +1,435 @@ +/* + Copyright 2010 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "SkImageDecoder.h" +#include "SkImageEncoder.h" +#include "SkMovie.h" +#include "SkStream.h" +#include "SkTemplates.h" + +template +class scoped_com_ptr { +private: + T *fPtr; + + scoped_com_ptr(scoped_com_ptr const &); + scoped_com_ptr & operator=(scoped_com_ptr const &); + +public: + explicit scoped_com_ptr(T *ptr = NULL) : fPtr(ptr) { } + ~scoped_com_ptr() { + if (NULL != fPtr) { + fPtr->Release(); + fPtr = NULL; + } + } + T &operator*() const { return *fPtr; } + T *operator->() const { return fPtr; } + /** + * Returns the address of the underlying pointer. + * This is dangerous -- it breaks encapsulation and the reference escapes. + * Must only be used on instances currently pointing to NULL, + * and only to initialize the instance. + */ + T **operator&() { SkASSERT(fPtr == NULL); return &fPtr; } + T *get() const { return fPtr; } +}; + +/** + * An instance of this class initializes COM on creation + * and closes the COM library on destruction. + */ +class AutoCoInitialize : SkNoncopyable { +private: + HRESULT hr; +public: + AutoCoInitialize() : + hr( + CoInitializeEx( + NULL + , COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE + ) + ) + { } + ~AutoCoInitialize() { + if (SUCCEEDED(this->hr)) { + CoUninitialize(); + } + } + HRESULT getHR() { return this->hr; } +}; + +class SkImageDecoder_WIC : public SkImageDecoder { +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode); +}; + +/** + * Converts a SkStream to an IStream. + * The caller must call Release() on the returned IStream. + */ +static HRESULT SkStreamToIStream(SkStream* stream, IStream** ppStream) { + //TODO(bungeman): use a real IStream wrapper + HRESULT hr = S_OK; + + size_t len = stream->getLength(); + + //Reserve memory for content of IStream. + HGLOBAL hdata = NULL; + if (SUCCEEDED(hr)) { + hdata = GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, len); + if (NULL == hdata) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + + //Lock memory. + void* data = NULL; + if (SUCCEEDED(hr)) { + data = GlobalLock(hdata); + if (NULL == data) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + + //Write SkStream data to memory. + if (SUCCEEDED(hr)) { + size_t read = stream->read(data, len); + if (read != len) { + hr = E_FAIL; + } + } + + //Unlock memory. + if (NULL != data) { + data = NULL; + SetLastError(NO_ERROR); + GlobalUnlock(hdata); + DWORD lastError = GetLastError(); + if (SUCCEEDED(hr) && NO_ERROR != lastError) { + hr = HRESULT_FROM_WIN32(lastError); + } + } + + //Create IStream from memory. + if (SUCCEEDED(hr)) { + hr = CreateStreamOnHGlobal(hdata, TRUE, ppStream); + } + //If we failed for any reason, free the memory. + if (FAILED(hr)) { + if (NULL != hdata) { + GlobalFree(hdata); + } + } + + return hr; +} + +bool SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { + //Initialize COM. + AutoCoInitialize scopedCo; + HRESULT hr = scopedCo.getHR(); + + //Create Windows Imaging Component ImagingFactory. + scoped_com_ptr piImagingFactory; + if (SUCCEEDED(hr)) { + hr = CoCreateInstance( + CLSID_WICImagingFactory + , NULL + , CLSCTX_INPROC_SERVER + , IID_PPV_ARGS(&piImagingFactory) + ); + } + + //Convert SkStream to IStream. + scoped_com_ptr piStream; + if (SUCCEEDED(hr)) { + hr = SkStreamToIStream(stream, &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. + scoped_com_ptr 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 + ); + } + + //Get the first frame from the decoder. + scoped_com_ptr piBitmapFrameDecode; + if (SUCCEEDED(hr)) { + hr = piBitmapDecoder->GetFrame(0, &piBitmapFrameDecode); + } + + //Get the BitmapSource interface of the frame. + scoped_com_ptr 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 (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + if (!this->allocPixelRef(bm, NULL)) { + return false; + } + } + + //Create a format converter. + scoped_com_ptr piFormatConverter; + if (SUCCEEDED(hr)) { + hr = piImagingFactory->CreateFormatConverter(&piFormatConverter); + } + + if (SUCCEEDED(hr)) { + hr = piFormatConverter->Initialize( + piBitmapSourceOriginal.get() //Input bitmap to convert + , GUID_WICPixelFormat32bppPBGRA //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. + scoped_com_ptr piBitmapSourceConverted; + if (SUCCEEDED(hr)) { + hr = piFormatConverter->QueryInterface( + IID_PPV_ARGS(&piBitmapSourceConverted) + ); + } + + //Copy the pixels into the bitmap. + if (SUCCEEDED(hr)) { + bm->lockPixels(); + bm->eraseColor(0); + const int stride = bm->rowBytes(); + hr = piBitmapSourceConverted->CopyPixels( + NULL, //Get all the pixels + stride, + stride * height, + reinterpret_cast(bm->getPixels()) + ); + bm->unlockPixels(); + } + + return SUCCEEDED(hr); +} + +///////////////////////////////////////////////////////////////////////// + +SkImageDecoder* SkImageDecoder::Factory(SkStream* stream) { + return SkNEW(SkImageDecoder_WIC); +} + +///////////////////////////////////////////////////////////////////////// + +SkMovie* SkMovie::DecodeStream(SkStream* 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& bm + , int quality) +{ + GUID type; + switch (fType) { + case kJPEG_Type: + type = GUID_ContainerFormatJpeg; + break; + case kPNG_Type: + type = GUID_ContainerFormatPng; + break; + default: + return false; + } + + //Initialize COM. + AutoCoInitialize scopedCo; + HRESULT hr = scopedCo.getHR(); + + //Create Windows Imaging Component ImagingFactory. + scoped_com_ptr piImagingFactory; + if (SUCCEEDED(hr)) { + hr = CoCreateInstance( + CLSID_WICImagingFactory + , NULL + , CLSCTX_INPROC_SERVER + , IID_PPV_ARGS(&piImagingFactory) + ); + } + + //Create the stream to hold the output of the encoder. + scoped_com_ptr piStream; + if (SUCCEEDED(hr)) { + hr = CreateStreamOnHGlobal(NULL, TRUE, &piStream); + } + + //Create an encode of the appropriate type. + scoped_com_ptr piEncoder; + if (SUCCEEDED(hr)) { + hr = piImagingFactory->CreateEncoder(type, NULL, &piEncoder); + } + + if (SUCCEEDED(hr)) { + hr = piEncoder->Initialize(piStream.get(), WICBitmapEncoderNoCache); + } + + //Create a the frame. + scoped_com_ptr piBitmapFrameEncode; + scoped_com_ptr 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 = bm.width(); + const UINT height = bm.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)) { + hr = piBitmapFrameEncode->WritePixels( + height + , bm.rowBytes() + , bm.rowBytes()*height + , reinterpret_cast(bm.getPixels())); + } + + if (SUCCEEDED(hr)) { + hr = piBitmapFrameEncode->Commit(); + } + + if (SUCCEEDED(hr)) { + hr = piEncoder->Commit(); + } + + //Rewind the IStream with the output of the encoder. + if (SUCCEEDED(hr)) { + LARGE_INTEGER liBeginning = { 0 }; + hr = piStream->Seek(liBeginning, STREAM_SEEK_SET, NULL); + } + + //Write the content of the IStream to the SkWStream. + if (SUCCEEDED(hr)) { + //TODO(bungeman): use a real IStream(SkWStream) wrapper + const unsigned int BUFFER_SIZE = 1024; + void* buffer = new BYTE[BUFFER_SIZE]; + ULONG bytesRead = 0; + while (true) { + hr = piStream->Read(buffer, BUFFER_SIZE, &bytesRead); + if (FAILED(hr)) { + break; + } + bool wrote = stream->write(buffer, bytesRead); + if (!wrote) { + hr = E_FAIL; + break; + } + if (BUFFER_SIZE != bytesRead) { + break; + } + } + stream->flush(); + delete[] buffer; + } + + return SUCCEEDED(hr); +} + +SkImageEncoder* SkImageEncoder::Create(Type t) { + switch (t) { + case kJPEG_Type: + case kPNG_Type: + break; + default: + return NULL; + } + return SkNEW_ARGS(SkImageEncoder_WIC, (t)); +} + -- cgit v1.2.3