text: /*
   * Copyright 2009 Vincent Povirk for CodeWeavers
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
   */
  
  #include "config.h"
  
  #include <stdarg.h>
  
  #define COBJMACROS
  
  #include "windef.h"
  #include "winbase.h"
  #include "winreg.h"
  #include "wingdi.h"
  #include "objbase.h"
  #include "wincodec.h"
  
  #include "wincodecs_private.h"
  
  #include "wine/debug.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(wincodecs);
  
  struct bmp_pixelformat {
      const WICPixelFormatGUID *guid;
      UINT bpp;
      DWORD compression;
      DWORD redmask;
      DWORD greenmask;
      DWORD bluemask;
      DWORD alphamask;
  };
  
  static const struct bmp_pixelformat formats[] = {
      {&GUID_WICPixelFormat24bppBGR, 24, BI_RGB},
      {&GUID_WICPixelFormat16bppBGR555, 16, BI_RGB},
      {&GUID_WICPixelFormat16bppBGR565, 16, BI_BITFIELDS, 0xf800, 0x7e0, 0x1f, 0},
      {&GUID_WICPixelFormat32bppBGR, 32, BI_RGB},
  #if 0
      /* Windows doesn't seem to support this one. */
      {&GUID_WICPixelFormat32bppBGRA, 32, BI_BITFIELDS, 0xff0000, 0xff00, 0xff, 0xff000000},
  #endif
      {NULL}
  };
  
  typedef struct BmpFrameEncode {
      const IWICBitmapFrameEncodeVtbl *lpVtbl;
      LONG ref;
      IStream *stream;
      BOOL initialized;
      UINT width, height;
      BYTE *bits;
      const struct bmp_pixelformat *format;
      double xres, yres;
      UINT lineswritten;
      UINT stride;
      BOOL committed;
  } BmpFrameEncode;
  
  static HRESULT WINAPI BmpFrameEncode_QueryInterface(IWICBitmapFrameEncode *iface, REFIID iid,
      void **ppv)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv);
  
      if (!ppv) return E_INVALIDARG;
  
      if (IsEqualIID(&IID_IUnknown, iid) ||
          IsEqualIID(&IID_IWICBitmapFrameEncode, iid))
      {
          *ppv = This;
      }
      else
      {
          *ppv = NULL;
          return E_NOINTERFACE;
      }
  
      IUnknown_AddRef((IUnknown*)*ppv);
      return S_OK;
  }
  
  static ULONG WINAPI BmpFrameEncode_AddRef(IWICBitmapFrameEncode *iface)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      ULONG ref = InterlockedIncrement(&This->ref);
  
      TRACE("(%p) refcount=%u\n", iface, ref);
  
      return ref;
  }
  
  static ULONG WINAPI BmpFrameEncode_Release(IWICBitmapFrameEncode *iface)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      ULONG ref = InterlockedDecrement(&This->ref);
  
      TRACE("(%p) refcount=%u\n", iface, ref);
  
      if (ref == 0)
      {
          if (This->stream) IStream_Release(This->stream);
          HeapFree(GetProcessHeap(), 0, This->bits);
          HeapFree(GetProcessHeap(), 0, This);
      }
  
      return ref;
  }
  
  static HRESULT WINAPI BmpFrameEncode_Initialize(IWICBitmapFrameEncode *iface,
      IPropertyBag2 *pIEncoderOptions)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      TRACE("(%p,%p)\n", iface, pIEncoderOptions);
  
      if (This->initialized) return WINCODEC_ERR_WRONGSTATE;
  
      This->initialized = TRUE;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_SetSize(IWICBitmapFrameEncode *iface,
      UINT uiWidth, UINT uiHeight)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      TRACE("(%p,%u,%u)\n", iface, uiWidth, uiHeight);
  
      if (!This->initialized || This->bits) return WINCODEC_ERR_WRONGSTATE;
  
      This->width = uiWidth;
      This->height = uiHeight;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_SetResolution(IWICBitmapFrameEncode *iface,
      double dpiX, double dpiY)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      TRACE("(%p,%0.2f,%0.2f)\n", iface, dpiX, dpiY);
  
      if (!This->initialized || This->bits) return WINCODEC_ERR_WRONGSTATE;
  
      This->xres = dpiX;
      This->yres = dpiY;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_SetPixelFormat(IWICBitmapFrameEncode *iface,
      WICPixelFormatGUID *pPixelFormat)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      int i;
      TRACE("(%p,%s)\n", iface, debugstr_guid(pPixelFormat));
  
      if (!This->initialized || This->bits) return WINCODEC_ERR_WRONGSTATE;
  
      for (i=0; formats[i].guid; i++)
      {
          if (memcmp(formats[i].guid, pPixelFormat, sizeof(GUID)) == 0)
              break;
      }
  
      if (!formats[i].guid) i = 0;
  
      This->format = &formats[i];
      memcpy(pPixelFormat, This->format->guid, sizeof(GUID));
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_SetColorContexts(IWICBitmapFrameEncode *iface,
      UINT cCount, IWICColorContext **ppIColorContext)
  {
      FIXME("(%p,%u,%p): stub\n", iface, cCount, ppIColorContext);
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI BmpFrameEncode_SetPalette(IWICBitmapFrameEncode *iface,
      IWICPalette *pIPalette)
  {
      FIXME("(%p,%p): stub\n", iface, pIPalette);
      return WINCODEC_ERR_UNSUPPORTEDOPERATION;
  }
  
  static HRESULT WINAPI BmpFrameEncode_SetThumbnail(IWICBitmapFrameEncode *iface,
      IWICBitmapSource *pIThumbnail)
  {
      FIXME("(%p,%p): stub\n", iface, pIThumbnail);
      return WINCODEC_ERR_UNSUPPORTEDOPERATION;
  }
  
  static HRESULT BmpFrameEncode_AllocateBits(BmpFrameEncode *This)
  {
      if (!This->bits)
      {
          if (!This->initialized || !This->width || !This->height || !This->format)
              return WINCODEC_ERR_WRONGSTATE;
  
          This->stride = (((This->width * This->format->bpp)+31)/32)*4;
          This->bits = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, This->stride * This->height);
          if (!This->bits) return E_OUTOFMEMORY;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_WritePixels(IWICBitmapFrameEncode *iface,
      UINT lineCount, UINT cbStride, UINT cbBufferSize, BYTE *pbPixels)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      HRESULT hr;
      WICRect rc;
      TRACE("(%p,%u,%u,%u,%p)\n", iface, lineCount, cbStride, cbBufferSize, pbPixels);
  
      if (!This->initialized || !This->width || !This->height || !This->format)
          return WINCODEC_ERR_WRONGSTATE;
  
      hr = BmpFrameEncode_AllocateBits(This);
      if (FAILED(hr)) return hr;
  
      rc.X = 0;
      rc.Y = 0;
      rc.Width = This->width;
      rc.Height = lineCount;
  
      hr = copy_pixels(This->format->bpp, pbPixels, This->width, lineCount, cbStride,
          &rc, This->stride, This->stride*(This->height-This->lineswritten),
          This->bits + This->stride*This->lineswritten);
  
      if (SUCCEEDED(hr))
          This->lineswritten += lineCount;
  
      return hr;
  }
  
  static HRESULT WINAPI BmpFrameEncode_WriteSource(IWICBitmapFrameEncode *iface,
      IWICBitmapSource *pIBitmapSource, WICRect *prc)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      HRESULT hr;
      WICRect rc;
      WICPixelFormatGUID guid;
      TRACE("(%p,%p,%p)\n", iface, pIBitmapSource, prc);
  
      if (!This->initialized || !This->width || !This->height)
          return WINCODEC_ERR_WRONGSTATE;
  
      if (!This->format)
      {
          hr = IWICBitmapSource_GetPixelFormat(pIBitmapSource, &guid);
          if (FAILED(hr)) return hr;
          hr = BmpFrameEncode_SetPixelFormat(iface, &guid);
          if (FAILED(hr)) return hr;
      }
  
      hr = IWICBitmapSource_GetPixelFormat(pIBitmapSource, &guid);
      if (FAILED(hr)) return hr;
      if (memcmp(&guid, This->format->guid, sizeof(GUID)) != 0)
      {
          /* should use WICConvertBitmapSource to convert, but that's unimplemented */
          ERR("format %s unsupported\n", debugstr_guid(&guid));
          return E_FAIL;
      }
  
      if (This->xres == 0.0 || This->yres == 0.0)
      {
          double xres, yres;
          hr = IWICBitmapSource_GetResolution(pIBitmapSource, &xres, &yres);
          if (FAILED(hr)) return hr;
          hr = BmpFrameEncode_SetResolution(iface, xres, yres);
          if (FAILED(hr)) return hr;
      }
  
      if (!prc)
      {
          UINT width, height;
          hr = IWICBitmapSource_GetSize(pIBitmapSource, &width, &height);
          if (FAILED(hr)) return hr;
          rc.X = 0;
          rc.Y = 0;
          rc.Width = width;
          rc.Height = height;
          prc = &rc;
      }
  
      if (prc->Width != This->width) return E_INVALIDARG;
  
      hr = BmpFrameEncode_AllocateBits(This);
      if (FAILED(hr)) return hr;
  
      hr = IWICBitmapSource_CopyPixels(pIBitmapSource, prc, This->stride,
          This->stride*(This->height-This->lineswritten),
          This->bits + This->stride*This->lineswritten);
  
      This->lineswritten += rc.Height;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_Commit(IWICBitmapFrameEncode *iface)
  {
      BmpFrameEncode *This = (BmpFrameEncode*)iface;
      BITMAPFILEHEADER bfh;
      BITMAPV5HEADER bih;
      UINT info_size;
      LARGE_INTEGER pos;
      ULONG byteswritten;
      HRESULT hr;
  
      TRACE("(%p)\n", iface);
  
      if (!This->bits || This->committed || This->height != This->lineswritten)
          return WINCODEC_ERR_WRONGSTATE;
  
      bfh.bfType = 0x4d42; /* "BM" */
      bfh.bfReserved1 = 0;
      bfh.bfReserved2 = 0;
  
      bih.bV5Size = info_size = sizeof(BITMAPINFOHEADER);
      bih.bV5Width = This->width;
      bih.bV5Height = -This->height; /* top-down bitmap */
      bih.bV5Planes = 1;
      bih.bV5BitCount = This->format->bpp;
      bih.bV5Compression = This->format->compression;
      bih.bV5SizeImage = This->stride*This->height;
      bih.bV5XPelsPerMeter = (This->xres+0.0127) / 0.0254;
      bih.bV5YPelsPerMeter = (This->yres+0.0127) / 0.0254;
      bih.bV5ClrUsed = 0;
      bih.bV5ClrImportant = 0;
  
      if (This->format->compression == BI_BITFIELDS)
      {
          if (This->format->alphamask)
              bih.bV5Size = info_size = sizeof(BITMAPV4HEADER);
          else
              info_size = sizeof(BITMAPINFOHEADER)+12;
          bih.bV5RedMask = This->format->redmask;
          bih.bV5GreenMask = This->format->greenmask;
          bih.bV5BlueMask = This->format->bluemask;
          bih.bV5AlphaMask = This->format->alphamask;
          bih.bV5AlphaMask = LCS_DEVICE_RGB;
      }
  
      bfh.bfSize = sizeof(BITMAPFILEHEADER) + info_size + bih.bV5SizeImage;
      bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + info_size;
  
      pos.QuadPart = 0;
      hr = IStream_Seek(This->stream, pos, STREAM_SEEK_SET, NULL);
      if (FAILED(hr)) return hr;
  
      hr = IStream_Write(This->stream, &bfh, sizeof(BITMAPFILEHEADER), &byteswritten);
      if (FAILED(hr)) return hr;
      if (byteswritten != sizeof(BITMAPFILEHEADER)) return E_FAIL;
  
      hr = IStream_Write(This->stream, &bih, info_size, &byteswritten);
      if (FAILED(hr)) return hr;
      if (byteswritten != info_size) return E_FAIL;
  
      hr = IStream_Write(This->stream, This->bits, bih.bV5SizeImage, &byteswritten);
      if (FAILED(hr)) return hr;
      if (byteswritten != bih.bV5SizeImage) return E_FAIL;
  
      This->committed = TRUE;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpFrameEncode_GetMetadataQueryWriter(IWICBitmapFrameEncode *iface,
      IWICMetadataQueryWriter **ppIMetadataQueryWriter)
  {
      FIXME("(%p, %p): stub\n", iface, ppIMetadataQueryWriter);
      return E_NOTIMPL;
  }
  
  static const IWICBitmapFrameEncodeVtbl BmpFrameEncode_Vtbl = {
      BmpFrameEncode_QueryInterface,
      BmpFrameEncode_AddRef,
      BmpFrameEncode_Release,
      BmpFrameEncode_Initialize,
      BmpFrameEncode_SetSize,
      BmpFrameEncode_SetResolution,
      BmpFrameEncode_SetPixelFormat,
      BmpFrameEncode_SetColorContexts,
      BmpFrameEncode_SetPalette,
      BmpFrameEncode_SetThumbnail,
      BmpFrameEncode_WritePixels,
      BmpFrameEncode_WriteSource,
      BmpFrameEncode_Commit,
      BmpFrameEncode_GetMetadataQueryWriter
  };
  
  typedef struct BmpEncoder {
      const IWICBitmapEncoderVtbl *lpVtbl;
      LONG ref;
      IStream *stream;
      IWICBitmapFrameEncode *frame;
  } BmpEncoder;
  
  static HRESULT WINAPI BmpEncoder_QueryInterface(IWICBitmapEncoder *iface, REFIID iid,
      void **ppv)
  {
      BmpEncoder *This = (BmpEncoder*)iface;
      TRACE("(%p,%s,%p)\n", iface, debugstr_guid(iid), ppv);
  
      if (!ppv) return E_INVALIDARG;
  
      if (IsEqualIID(&IID_IUnknown, iid) ||
          IsEqualIID(&IID_IWICBitmapEncoder, iid))
      {
          *ppv = This;
      }
      else
      {
          *ppv = NULL;
          return E_NOINTERFACE;
      }
  
      IUnknown_AddRef((IUnknown*)*ppv);
      return S_OK;
  }
  
  static ULONG WINAPI BmpEncoder_AddRef(IWICBitmapEncoder *iface)
  {
      BmpEncoder *This = (BmpEncoder*)iface;
      ULONG ref = InterlockedIncrement(&This->ref);
  
      TRACE("(%p) refcount=%u\n", iface, ref);
  
      return ref;
  }
  
  static ULONG WINAPI BmpEncoder_Release(IWICBitmapEncoder *iface)
  {
      BmpEncoder *This = (BmpEncoder*)iface;
      ULONG ref = InterlockedDecrement(&This->ref);
  
      TRACE("(%p) refcount=%u\n", iface, ref);
  
      if (ref == 0)
      {
          if (This->stream) IStream_Release(This->stream);
          if (This->frame) IWICBitmapFrameEncode_Release(This->frame);
          HeapFree(GetProcessHeap(), 0, This);
      }
  
      return ref;
  }
  
  static HRESULT WINAPI BmpEncoder_Initialize(IWICBitmapEncoder *iface,
      IStream *pIStream, WICBitmapEncoderCacheOption cacheOption)
  {
      BmpEncoder *This = (BmpEncoder*)iface;
  
      TRACE("(%p,%p,%u)\n", iface, pIStream, cacheOption);
  
      IStream_AddRef(pIStream);
      This->stream = pIStream;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpEncoder_GetContainerFormat(IWICBitmapEncoder *iface,
      GUID *pguidContainerFormat)
  {
      FIXME("(%p,%s): stub\n", iface, debugstr_guid(pguidContainerFormat));
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI BmpEncoder_GetEncoderInfo(IWICBitmapEncoder *iface,
      IWICBitmapEncoderInfo **ppIEncoderInfo)
  {
      FIXME("(%p,%p): stub\n", iface, ppIEncoderInfo);
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI BmpEncoder_SetColorContexts(IWICBitmapEncoder *iface,
      UINT cCount, IWICColorContext **ppIColorContext)
  {
      FIXME("(%p,%u,%p): stub\n", iface, cCount, ppIColorContext);
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI BmpEncoder_SetPalette(IWICBitmapEncoder *iface, IWICPalette *pIPalette)
  {
      TRACE("(%p,%p)\n", iface, pIPalette);
      return WINCODEC_ERR_UNSUPPORTEDOPERATION;
  }
  
  static HRESULT WINAPI BmpEncoder_SetThumbnail(IWICBitmapEncoder *iface, IWICBitmapSource *pIThumbnail)
  {
      TRACE("(%p,%p)\n", iface, pIThumbnail);
      return WINCODEC_ERR_UNSUPPORTEDOPERATION;
  }
  
  static HRESULT WINAPI BmpEncoder_SetPreview(IWICBitmapEncoder *iface, IWICBitmapSource *pIPreview)
  {
      TRACE("(%p,%p)\n", iface, pIPreview);
      return WINCODEC_ERR_UNSUPPORTEDOPERATION;
  }
  
  static HRESULT WINAPI BmpEncoder_CreateNewFrame(IWICBitmapEncoder *iface,
      IWICBitmapFrameEncode **ppIFrameEncode, IPropertyBag2 **ppIEncoderOptions)
  {
      BmpEncoder *This = (BmpEncoder*)iface;
      BmpFrameEncode *encode;
      HRESULT hr;
  
      TRACE("(%p,%p,%p)\n", iface, ppIFrameEncode, ppIEncoderOptions);
  
      if (This->frame) return WINCODEC_ERR_UNSUPPORTEDOPERATION;
  
      if (!This->stream) return WINCODEC_ERR_NOTINITIALIZED;
  
      hr = CreatePropertyBag2(ppIEncoderOptions);
      if (FAILED(hr)) return hr;
  
      encode = HeapAlloc(GetProcessHeap(), 0, sizeof(BmpFrameEncode));
      if (!encode)
      {
          IPropertyBag2_Release(*ppIEncoderOptions);
          *ppIEncoderOptions = NULL;
          return E_OUTOFMEMORY;
      }
      encode->lpVtbl = &BmpFrameEncode_Vtbl;
      encode->ref = 2;
      IStream_AddRef(This->stream);
      encode->stream = This->stream;
      encode->initialized = FALSE;
      encode->width = 0;
      encode->height = 0;
      encode->bits = NULL;
      encode->format = NULL;
      encode->xres = 0.0;
      encode->yres = 0.0;
      encode->lineswritten = 0;
      encode->committed = FALSE;
  
      *ppIFrameEncode = (IWICBitmapFrameEncode*)encode;
      This->frame = (IWICBitmapFrameEncode*)encode;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpEncoder_Commit(IWICBitmapEncoder *iface)
  {
      BmpEncoder *This = (BmpEncoder*)iface;
      BmpFrameEncode *frame = (BmpFrameEncode*)This->frame;
      TRACE("(%p)\n", iface);
  
      if (!frame || !frame->committed) return WINCODEC_ERR_WRONGSTATE;
  
      return S_OK;
  }
  
  static HRESULT WINAPI BmpEncoder_GetMetadataQueryWriter(IWICBitmapEncoder *iface,
      IWICMetadataQueryWriter **ppIMetadataQueryWriter)
  {
      FIXME("(%p,%p): stub\n", iface, ppIMetadataQueryWriter);
      return E_NOTIMPL;
  }
  
  static const IWICBitmapEncoderVtbl BmpEncoder_Vtbl = {
      BmpEncoder_QueryInterface,
      BmpEncoder_AddRef,
      BmpEncoder_Release,
      BmpEncoder_Initialize,
      BmpEncoder_GetContainerFormat,
      BmpEncoder_GetEncoderInfo,
      BmpEncoder_SetColorContexts,
      BmpEncoder_SetPalette,
      BmpEncoder_SetThumbnail,
      BmpEncoder_SetPreview,
      BmpEncoder_CreateNewFrame,
      BmpEncoder_Commit,
      BmpEncoder_GetMetadataQueryWriter
  };
  
  HRESULT BmpEncoder_CreateInstance(IUnknown *pUnkOuter, REFIID iid, void** ppv)
  {
      BmpEncoder *This;
      HRESULT ret;
  
      TRACE("(%p,%s,%p)\n", pUnkOuter, debugstr_guid(iid), ppv);
  
      *ppv = NULL;
  
      if (pUnkOuter) return CLASS_E_NOAGGREGATION;
  
      This = HeapAlloc(GetProcessHeap(), 0, sizeof(BmpEncoder));
      if (!This) return E_OUTOFMEMORY;
  
      This->lpVtbl = &BmpEncoder_Vtbl;
      This->ref = 1;
      This->stream = NULL;
      This->frame = NULL;
  
      ret = IUnknown_QueryInterface((IUnknown*)This, iid, ppv);
      IUnknown_Release((IUnknown*)This);
  
      return ret;
  }
  