Add image encoder/decoder for Windows.
authorbungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 22 Jun 2011 20:42:34 +0000 (20:42 +0000)
committerbungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 22 Jun 2011 20:42:34 +0000 (20:42 +0000)
http://codereview.appspot.com/4634078/

git-svn-id: http://skia.googlecode.com/svn/trunk@1676 2bbb7eff-a529-9590-31e7-b0007b416f81

gyp/images.gyp
src/ports/SkImageDecoder_WIC.cpp [new file with mode: 0644]

index e052f1e34d32b77cbe53f96edef609d95f423aa6..8f0a06bd1ecb8338694a66135e10b993e6ffcddd 100644 (file)
@@ -1,4 +1,4 @@
-{
+{
   'includes': [
     'target_defaults.gypi',
   ],
@@ -47,6 +47,9 @@
         '../src/images/SkPageFlipper.cpp',
         '../src/images/SkScaledBitmapSampler.cpp',
         '../src/images/SkScaledBitmapSampler.h',
+
+        '../src/ports/SkImageDecoder_CG.cpp',
+        '../src/ports/SkImageDecoder_WIC.cpp',
       ],
       'conditions': [
         [ 'OS == "win"', {
             '../include/images/SkJpegUtility.h',
 
             '../src/images/SkFDStream.cpp',
+            '../src/images/SkImageDecoder_Factory.cpp',
             '../src/images/SkImageDecoder_libgif.cpp',
             '../src/images/SkImageDecoder_libjpeg.cpp',
             '../src/images/SkImageDecoder_libpng.cpp',
             '../src/images/SkImageDecoder_libpvjpeg.c',
+            '../src/images/SkImageEncoder_Factory.cpp',
             '../src/images/SkJpegUtility.cpp',
             '../src/images/SkMovie_gif.cpp',
           ],
+        },{ #else if OS != win
+          'sources!': [
+            '../src/ports/SkImageDecoder_WIC.cpp',
+          ],
         }],
         [ 'OS == "mac"', {
           'include_dirs': [
             '../include/utils/mac',
           ],
-          'sources': [
-            '../src/ports/SkImageDecoder_CG.cpp'
-          ],
           'sources!': [
             '../include/images/SkJpegUtility.h',
 
-            '../src/images/SkImageEncoder_Factory.cpp',
             '../src/images/SkImageDecoder_Factory.cpp',
             '../src/images/SkImageDecoder_libpng.cpp',
             '../src/images/SkImageDecoder_libgif.cpp',
             '../src/images/SkImageDecoder_libjpeg.cpp',
             '../src/images/SkImageDecoder_libpvjpeg.c',
+            '../src/images/SkImageEncoder_Factory.cpp',
             '../src/images/SkJpegUtility.cpp',
             '../src/images/SkMovie_gif.cpp',
           ],
+        },{ #else if OS != mac
+          'sources!': [
+            '../src/ports/SkImageDecoder_CG.cpp',
+          ],
         }],
         [ 'OS == "linux" or OS == "freebsd" or OS == "openbsd" or OS == "solaris"', {
           'sources!': [
diff --git a/src/ports/SkImageDecoder_WIC.cpp b/src/ports/SkImageDecoder_WIC.cpp
new file mode 100644 (file)
index 0000000..b53db3b
--- /dev/null
@@ -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 <Windows.h>
+#include <wincodec.h>
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkMovie.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+template<typename T>
+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<IWICImagingFactory> piImagingFactory;
+    if (SUCCEEDED(hr)) {
+        hr = CoCreateInstance(
+            CLSID_WICImagingFactory
+            , NULL
+            , CLSCTX_INPROC_SERVER
+            , IID_PPV_ARGS(&piImagingFactory)
+        );
+    }
+    
+    //Convert SkStream to IStream.
+    scoped_com_ptr<IStream> 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<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
+        );
+    }
+    
+    //Get the first frame from the decoder.
+    scoped_com_ptr<IWICBitmapFrameDecode> piBitmapFrameDecode;
+    if (SUCCEEDED(hr)) {
+        hr = piBitmapDecoder->GetFrame(0, &piBitmapFrameDecode);
+    }
+    
+    //Get the BitmapSource interface of the frame.
+    scoped_com_ptr<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 (SkImageDecoder::kDecodeBounds_Mode == mode) {
+            return true;
+        }
+        if (!this->allocPixelRef(bm, NULL)) {
+            return false;
+        }
+    }
+    
+    //Create a format converter.
+    scoped_com_ptr<IWICFormatConverter> 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<IWICBitmapSource> 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<BYTE *>(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<IWICImagingFactory> 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<IStream> piStream;
+    if (SUCCEEDED(hr)) {
+        hr = CreateStreamOnHGlobal(NULL, TRUE, &piStream);
+    }
+    
+    //Create an encode of the appropriate type.
+    scoped_com_ptr<IWICBitmapEncoder> 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<IWICBitmapFrameEncode> piBitmapFrameEncode;
+    scoped_com_ptr<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 = 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<BYTE*>(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));
+}
+