blob: 7815a89fcae03bbfe4fcf6531b876177e284868a [file] [log] [blame]
// Copyright 2010 Google Inc.
//
// This code is licensed under the same terms as WebM:
// Software License Agreement: http://www.webmproject.org/license/software/
// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
// -----------------------------------------------------------------------------
//
// Interface to the VP8 frame.
//
// Author: Mikolaj Zalewski (mikolajz@google.com)
#include <windows.h>
#include "decode_frame.h"
#include <cassert>
#include <cstdlib>
#include <memory>
#ifdef USE_LIBVPX
#include "vpx/vpx_decoder.h"
#include "vpx/vp8dx.h"
#else
#include "webp/decode.h"
#endif
#include "webpimg.h"
#ifdef WEBP_DEBUG_LOGGING
#include "stopwatch.h"
#endif // WEBP_DEBUG_LOGGING
const int kBytesPerPixel = 3;
YUVImage::~YUVImage() {
// Note: only Y point to allocated memory. U and V are pointers inside Y.
std::free(Y);
}
static void YUV420toRGB24Line(const BYTE* y_src,
const BYTE* u_src,
const BYTE* v_src,
int x_start,
int x_end,
BYTE* rgb_dst) {
assert(x_start != x_end);
y_src += x_start;
u_src += x_start/2;
v_src += x_start/2;
if (x_start & 1) {
WebpToRGB24(y_src[0], u_src[0], v_src[0], rgb_dst);
rgb_dst += 3;
++u_src;
++v_src;
++y_src;
x_start++;
}
int i;
int len = (x_end - x_start)/2;
for (i = 0; i < len; ++i) {
const int U = u_src[0];
const int V = v_src[0];
WebpToRGB24(y_src[0], U, V, rgb_dst);
WebpToRGB24(y_src[1], U, V, rgb_dst + 3);
++u_src;
++v_src;
y_src += 2;
rgb_dst += 6;
}
if (x_end & 1) { /* Rightmost pixel */
WebpToRGB24(y_src[0], (*u_src), (*v_src), rgb_dst);
}
}
HRESULT DecodeFrame::CreateFromVP8Stream(BYTE* vp8_bitstream, DWORD stream_size, ComPtr<DecodeFrame>* ppOutput) {
TRACE1("stream_size=%d\n", stream_size);
HRESULT result = S_OK;
ComPtr<DecodeFrame> output;
(*ppOutput).reset(NULL);
std::auto_ptr<YUVImage> pImage(new (std::nothrow) YUVImage());
if (pImage.get() == NULL)
return E_OUTOFMEMORY;
#ifdef WEBP_DEBUG_LOGGING
Stopwatch stopwatch;
StopwatchReadAndReset(&stopwatch);
#endif // WEBP_DEBUG_LOGGING
// Note: according to the documentation, the actual decoding should happen
// in CopyPixels. However, this would need to be implemented efficiently, as
// e.g., the photo viewers load the image row by row, with multiple calls to
// CopyPixels. Thus, for simplicity, we do all expect for YUV -> RGB
// conversion here.
// TODO: Do the decoding on the first call to CopyPixels to be OK with the
// letter of the documentation. Maybe we could do progressive decoding if the
// callers request the image in the top to bottom order?
#ifdef USE_LIBVPX
WebPResult decode_result =
VPXDecode(vp8_bitstream, stream_size, &pImage->Y,
&pImage->U, &pImage->V, &pImage->width,
&pImage->height);
pImage->yStride = pImage->width;
pImage->uvStride = ((pImage->yStride + 1) >> 1);
bool decodedImage = (decode_result == webp_success);
#else
pImage->Y = WebPDecodeYUV(vp8_bitstream, stream_size, &pImage->width,
&pImage->height, &pImage->U, &pImage->V, &pImage->yStride,
&pImage->uvStride);
bool decodedImage = (pImage->Y != NULL);
#endif
#ifdef WEBP_DEBUG_LOGGING
double time = StopwatchReadAndReset(&stopwatch);
TRACE1("Decode (VP8 -> YUV) time: %f\n", time);
#endif // WEBP_DEBUG_LOGGING
if (!decodedImage) {
// We don't know what was the problem, but we assume it's a problem with
// the content.
// TODO: get better error codes.
// Note: Win7 jpeg codec seems to prefer to return WINCODEC_ERR_BADHEADER
// even for problems in the bitstream.
TRACE("Couldn't decode VP8 stream.\n");
return WINCODEC_ERR_BADIMAGE;
}
// Sanity checks:
if (pImage->width < 0 || pImage->height < 0
|| pImage->uvStride < (pImage->width+1)/2
|| pImage->yStride < pImage->width) {
TRACE("Invalid sizes from decoder!\n");
return E_FAIL;
}
output.reset(new (std::nothrow) DecodeFrame(pImage.release()));
if (output.get() == NULL)
return E_OUTOFMEMORY;
(*ppOutput).reset(output.new_ref());
return S_OK;
}
HRESULT DecodeFrame::QueryInterface(REFIID riid, void **ppvObject) {
TRACE2("(%s, %p)\n", debugstr_guid(riid), ppvObject);
if (ppvObject == NULL)
return E_INVALIDARG;
*ppvObject = NULL;
if (!IsEqualGUID(riid, IID_IUnknown) &&
!IsEqualGUID(riid, IID_IWICBitmapFrameDecode) &&
!IsEqualGUID(riid, IID_IWICBitmapSource))
return E_NOINTERFACE;
this->AddRef();
*ppvObject = static_cast<IWICBitmapFrameDecode*>(this);
return S_OK;
}
HRESULT DecodeFrame::GetSize(UINT *puiWidth, UINT *puiHeight) {
TRACE2("(%p, %p)\n", puiWidth, puiHeight);
if (puiWidth == NULL || puiHeight == NULL)
return E_INVALIDARG;
*puiWidth = (UINT)image_->width;
*puiHeight = (UINT)image_->height;
TRACE2("ret: %u x %u\n", *puiWidth, *puiHeight);
return S_OK;
}
HRESULT DecodeFrame::GetPixelFormat(WICPixelFormatGUID *pPixelFormat) {
TRACE1("(%p)\n", pPixelFormat);
if (pPixelFormat == NULL)
return E_INVALIDARG;
*pPixelFormat = GUID_WICPixelFormat24bppBGR;
return S_OK;
}
HRESULT DecodeFrame::GetResolution(double *pDpiX, double *pDpiY) {
TRACE2("(%p, %p)\n", pDpiX, pDpiY);
// Let's assume square pixels. 96dpi seems to be a reasonable default.
*pDpiX = 96;
*pDpiY = 96;
return S_OK;
}
HRESULT DecodeFrame::CopyPalette(IWICPalette *pIPalette) {
TRACE1("(%p)\n", pIPalette);
return WINCODEC_ERR_PALETTEUNAVAILABLE;
}
HRESULT DecodeFrame::CopyPixels(const WICRect *prc, UINT cbStride, UINT cbBufferSize, BYTE *pbBuffer) {
TRACE4("(%p, %u, %u, %p)\n", prc, cbStride, cbBufferSize, pbBuffer);
if (pbBuffer == NULL)
return E_INVALIDARG;
WICRect rect = {0, 0, image_->width, image_->height};
if (prc)
rect = *prc;
if (rect.Width < 0 || rect.Height < 0 || rect.X < 0 || rect.Y < 0)
return E_INVALIDARG;
if (rect.X + rect.Width > image_->width ||
rect.Y + rect.Height > image_->height)
return E_INVALIDARG;
// Divisions instead of multiplications to avoid integer overflows:
if (cbStride / kBytesPerPixel < static_cast<UINT>(rect.Width))
return E_INVALIDARG;
if (cbBufferSize / cbStride < static_cast<UINT>(rect.Height))
return WINCODEC_ERR_INSUFFICIENTBUFFER;
if (rect.Width == 0 || rect.Height == 0)
return S_OK;
int y_stride = image_->yStride;
int uv_stride = image_->uvStride;
/* note that the U, V upsampling in height is happening here as the U, V
* buffers sent to successive odd-even pair of lines is same.
*/
BYTE *dst_buffer = pbBuffer;
for (int src_y = rect.Y; src_y < rect.Y + rect.Height; ++src_y) {
YUV420toRGB24Line(image_->Y + src_y * y_stride,
image_->U + (src_y >> 1) * uv_stride,
image_->V + (src_y >> 1) * uv_stride,
rect.X,
rect.X + rect.Width,
dst_buffer);
dst_buffer += cbStride;
}
return S_OK;
}
HRESULT DecodeFrame::GetMetadataQueryReader(IWICMetadataQueryReader **ppIMetadataQueryReader) {
TRACE1("(%p)\n", ppIMetadataQueryReader);
return WINCODEC_ERR_UNSUPPORTEDOPERATION;
}
HRESULT DecodeFrame::GetColorContexts(UINT cCount, IWICColorContext **ppIColorContexts, UINT *pcActualCount) {
TRACE3("(%d, %p, %p)\n", cCount, ppIColorContexts, pcActualCount);
if (pcActualCount == NULL)
return E_INVALIDARG;
*pcActualCount = 0;
return S_OK;
}
HRESULT DecodeFrame::GetThumbnail(IWICBitmapSource **ppIThumbnail) {
TRACE1("(%p)\n", ppIThumbnail);
return WINCODEC_ERR_CODECNOTHUMBNAIL;
}
HRESULT DummyFrame::QueryInterface(REFIID riid, void **ppvObject) {
TRACE2("(%s, %p)\n", debugstr_guid(riid), ppvObject);
if (ppvObject == NULL)
return E_INVALIDARG;
*ppvObject = NULL;
if (!IsEqualGUID(riid, IID_IUnknown) && !IsEqualGUID(riid, IID_IWICBitmapFrameDecode))
return E_NOINTERFACE;
this->AddRef();
*ppvObject = static_cast<IWICBitmapFrameDecode*>(this);
return S_OK;
}