I am trying to draw a line over the live video streaming in direct show application. Am successfully drawing a line but if I continuously want to create a line by calling the below function, i got an error E_OUTOFMEMORY from the SetAlphaBitmap(). Am calling drawlineOverlay() function one after the other and 8 of the calling is ok, but the ninth and subsequent ones do not. SetAlphaBitmap returns S_OK for the first 8 callings and then the 9th returns 8007000E. But the video streaming is going peacefully only the overlay diagram was hanged. If I continuously call that function after sometimes video streaming is stopped and hanging.
Why is this error occuring?
void drawlineOverlay(HWND m_hwndApp)
{
int cx, cy;
HRESULT hr;
HBITMAP hbm;
RECT rcClient;
GetResolution(&cx,&cy);
GetClientRect(m_hwndApp,&rcClient);
HDC hdc = GetDC(m_hwndApp);
if (hdc == NULL)
{
return E_FAIL;
}
HDC hdcBmp = CreateCompatibleDC(hdc);
if (hdcBmp == NULL)
{
return E_FAIL;
}
hbm = CreateCompatibleBitmap(hdc,cx,cy);
BITMAP bm;
if (0 == GetObject(hbm, sizeof(bm), &bm))
{
DeleteDC(hdcBmp);
return E_FAIL;
}
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcBmp, hbm);
if (hbmOld == 0)
{
DeleteDC(hdcBmp);
return E_FAIL;
}
//To draw line
drawLine1(xx1, yy1, xx2, yy2,hdcBmp,2);
VMR9AlphaBitmap bmpInfo;
ZeroMemory(&bmpInfo, sizeof(bmpInfo));
bmpInfo.dwFlags = VMRBITMAP_HDC | VMRBITMAP_SRCCOLORKEY;
bmpInfo.hdc = hdcBmp;
SetRect(&bmpInfo.rSrc, 0, 0, bm.bmWidth, bm.bmHeight);
bmpInfo.rDest.left = 0.f;
bmpInfo.rDest.top = 0.f;
bmpInfo.rDest.right = 1.0f;
bmpInfo.rDest.bottom = 1.0f;
// Set the transparency value (1.0 is opaque, 0.0 is transparent).
bmpInfo.fAlpha = 0.5f;
bmpInfo.clrSrcKey = RGB(0,0,0);
if(m_pVideoRender != NULL)
{
IVMRMixerBitmap9* pBmp;
hr = m_pVideoRender->QueryInterface(IID_IVMRMixerBitmap9, (LPVOID *)&pBmp);
if (SUCCEEDED(hr))
{
hr = pBmp->SetAlphaBitmap(&bmpInfo);
if(FAILED(hr))
{
PrintMessage(L"pBmp->SetAlphaBitmap hr = 0x%x GetLastError() = %d\r\n",hr,GetLastError());
}
pBmp->Release();
//SAFE_RELEASE(pBmp);
}
}
// Clean up.
ReleaseDC(m_hwndApp, hdc);
DeleteBitmap(hbm);
DeleteObject(SelectObject(hdcBmp, hbmOld));
DeleteDC(hdcBmp);
}
void drawLine1(int xx1, int yy1, int xx2, int yy2,HDC hdcBmp)
{
RECT clntRc;
int temp,s1,s2,swap;
double dx,dy,p,x,y;
x = xx1;
y = yy1;
dx = abs(xx2 - xx1);
dy = abs(yy2 - yy1);
s1 = sign(xx2 - xx1);
s2 = sign(yy2 - yy1);
swap = 0;
if (dy > dx)
{
temp = dx;
dx = dy;
dy = temp;
swap = 1;
}
p = 2*dy - dx;
for (int i = 0; i < dx; i++)
{
clntRc.left =x;
clntRc.top = y;
clntRc.right = x+g_Thickness;
clntRc.bottom =y+g_Thickness;
FillRect(hdcBmp,&clntRc,CreateSolidBrush(RGB(0,255,0)));
while (p >= 0)
{
p = p - 2*dx;
if (swap)
x += s1;
else
y += s2;
}
p = p + 2*dy;
if (swap)
y += s2;
else
x += s1;
}
}
CreateSolidBrush(RGB(0,255,0)) is leaking resources.
You need to DeleteObject the handle that is returned by CreateSolidBrush(RGB(0,255,0)): https://msdn.microsoft.com/en-us/library/windows/desktop/dd183523(v=vs.85).aspx
Related
I have ten images to create a video with animation , like that
https://youtu.be/iu9uazolcC8
it use freeImage and FFmpeg.
MovieWriter writer(fileName, IMG_WIDTH, IMG_HEIGHT);
vector<char*> ls = {
"D:/createVideo/src/c/1.jpg",
"D:/createVideo/src/c/2.jpg",
"D:/createVideo/src/c/3.jpg",
"D:/createVideo/src/c/4.jpg",
"D:/createVideo/src/c/5.jpg",
"D:/createVideo/src/c/6.jpg",
"D:/createVideo/src/c/7.jpg",
"D:/createVideo/src/c/8.jpg",
"D:/createVideo/src/c/9.jpg",
"D:/createVideo/src/c/10.jpg",
};
FREE_IMAGE_FORMAT pic_type = FIF_JPEG;
FIBITMAP *fiCopy = NULL;
FIBITMAP *corpImg = NULL;
uint8_t *ut = NULL;
for (int i=0;i<10;i++)
{
char *src_pic_path = ls[i];
FIBITMAP *fi = FreeImage_Load(pic_type, src_pic_path);
if(fi==NULL){
continue;
}
int w = IMG_WIDTH;
int h = IMG_HEIGHT;
unsigned int index = 0;
while (w > IMG_MIN_W_SCALE || h > IMG_MIN_H_SCALE)
{
fiCopy = FreeImage_RescaleRect(fi, IMG_WIDTH, IMG_HEIGHT,0, 0, w * 2, h * 2);
if(fiCopy == NULL){
break;
}
corpImg = FreeImage_Rescale(fiCopy, IMG_WIDTH, IMG_HEIGHT);
if(corpImg == NULL){
break;
}
ut = FreeImage_GetBits(corpImg);
if(ut == NULL){
break;
}
w -= ONE_W_PIECE;
h -= ONE_H_PIECE;
writer.addFrame(ut);
FreeImage_Unload(corpImg);
FreeImage_Unload(fiCopy);
index++;
}
}
but it so slowly, how to optimization that code? or how to faster to create that video?
would you know about an option to do that?
Environment:
Ubuntu 16.04 (x64)
C++
ffmpeg
Use-case
Multiple MPEG-TS fragments are rapidly decoded ( numerous every sec )
The format of the TS fragments is dynamic and can't be known ahead of time
The first A/V frames of each fragment are needed to be extracted
Problem statement
The code bellow successfully decodes A/V, BUT, has a huge memory leak ( MBytes/sec )
According to the docs seems all memory is freed as it should ( does it... ? )
Why do I get this huge mem leak, what am I missing in the following code snap ?
struct MEDIA_TYPE {
ffmpeg::AVMediaType eType;
union {
struct {
ffmpeg::AVPixelFormat colorspace;
int width, height;
float fFPS;
} video;
struct : WAVEFORMATEX {
short sSampleFormat;
} audio;
} format;
};
struct FRAME {
enum { MAX_PALNES = 3 + 1 };
int iStrmId;
int64_t pts; // Duration in 90Khz clock resolution
uint8_t** ppData; // Null terminated
int32_t* pStride;// Zero terminated
};
HRESULT ProcessTS(IN Operation op, IN uint8_t* pTS, IN uint32_t uiBytes, bool(*cb)(IN const MEDIA_TYPE& mt, IN FRAME& frame, IN PVOID pCtx), IN PVOID pCbCtx)
{
uiBytes -= uiBytes % 188;// align to 188 packet size
struct CONTEXT {
uint8_t* pTS;
uint32_t uiBytes;
int32_t iPos;
} ctx = { pTS, uiBytes, 0 };
LOGTRACE(TSDecoder, "ProcessTS(%d, 0x%.8x, %d, 0x%.8x, 0x%.8x), this=0x%.8x\r\n", (int)op, pTS, uiBytes, cb, pCbCtx, this);
ffmpeg::AVFormatContext* pFmtCtx = 0;
if (0 == (pFmtCtx = ffmpeg::avformat_alloc_context()))
return E_OUTOFMEMORY;
ffmpeg::AVIOContext* pIoCtx = ffmpeg::avio_alloc_context(pTS, uiBytes, 0, &ctx
, [](void *opaque, uint8_t *buf, int buf_size)->int {
auto pCtx = (CONTEXT*)opaque;
int size = pCtx->uiBytes;
if (pCtx->uiBytes - pCtx->iPos < buf_size)
size = pCtx->uiBytes - pCtx->iPos;
if (size > 0) {
memcpy(buf, pCtx->pTS + pCtx->iPos, size);
pCtx->iPos += size;
}
return size;
}
, 0
, [](void* opaque, int64_t offset, int whence)->int64_t {
auto pCtx = (CONTEXT*)opaque;
switch (whence)
{
case SEEK_SET:
pCtx->iPos = offset;
break;
case SEEK_CUR:
pCtx->iPos += offset;
break;
case SEEK_END:
pCtx->iPos = pCtx->uiBytes - offset;
break;
case AVSEEK_SIZE:
return pCtx->uiBytes;
}
return pCtx->iPos;
});
pFmtCtx->pb = pIoCtx;
int iRet = ffmpeg::avformat_open_input(&pFmtCtx, "fakevideo.ts", m_pInputFmt, 0);
if (ERROR_SUCCESS != iRet) {
assert(false);
pFmtCtx = 0;// a user-supplied AVFormatContext will be freed on failure.
return E_FAIL;
}
struct DecodeContext {
ffmpeg::AVStream* pStream;
ffmpeg::AVCodec* pDecoder;
int iFramesProcessed;
};
HRESULT hr = S_OK;
int iStreamsProcessed = 0;
bool bVideoFound = false;
int64_t ptsLast = 0;
int64_t dtsLast = 0;
auto pContext = (DecodeContext*)alloca(sizeof(DecodeContext) * pFmtCtx->nb_streams);
for (unsigned int i = 0; i < pFmtCtx->nb_streams; i++) {
assert(pFmtCtx->streams[i]->index == i);
pContext[i].pStream = pFmtCtx->streams[i];
pContext[i].pDecoder = ffmpeg::avcodec_find_decoder(pFmtCtx->streams[i]->codec->codec_id);
pContext[i].iFramesProcessed= 0;
if (0 == pContext[i].pDecoder)
continue;
if ((iRet = ffmpeg::avcodec_open2(pFmtCtx->streams[i]->codec, pContext[i].pDecoder, NULL)) < 0) {
_ASSERT(FALSE);
hr = E_FAIL;
goto ErrExit;
}
}
while (S_OK == hr) {
ffmpeg::AVFrame* pFrame = 0;
ffmpeg::AVPacket pkt;
ffmpeg::av_init_packet(&pkt);
if (ERROR_SUCCESS != (iRet = ffmpeg::av_read_frame(pFmtCtx, &pkt))) {
hr = E_FAIL;
break;
}
if ((0 == dtsLast) && (0 != pkt.dts))
dtsLast = pkt.dts;
if ((0 == ptsLast) && (0 != pkt.pts))
ptsLast = pkt.pts;
DecodeContext& ctx = pContext[pkt.stream_index];
if (Operation::DECODE_FIRST_FRAME_OF_EACH_STREAM == op) {
if (iStreamsProcessed == pFmtCtx->nb_streams) {
hr = S_FALSE;
goto Next;
}
if (ctx.iFramesProcessed > 0)
goto Next;
iStreamsProcessed++;
}
if (0 == ctx.pDecoder)
goto Next;
if (0 == (pFrame = ffmpeg::av_frame_alloc())) {
hr = E_OUTOFMEMORY;
goto Next;
}
LOGTRACE(TSDecoder, "ProcessTS(%d, 0x%.8x, %d, 0x%.8x, 0x%.8x), this=0x%.8x, decode, S:%d, T:%d\r\n", (int)op, pTS, uiBytes, cb, pCbCtx, this, pkt.stream_index, ctx.pStream->codec->codec_type);
int bGotFrame = false;
int iBytesUsed = 0;
MEDIA_TYPE mt;
memset(&mt, 0, sizeof(mt));
mt.eType = ctx.pStream->codec->codec_type;
switch (mt.eType) {
case ffmpeg::AVMediaType::AVMEDIA_TYPE_AUDIO:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if((iRet = ffmpeg::avcodec_decode_audio4(ctx.pStream->codec, pFrame, &bGotFrame, &pkt)) < 0) {
hr = E_FAIL;
goto Next;
}
_ASSERT(pkt.size == iRet);
// FFMPEG AAC decoder oddity, first call to 'avcodec_decode_audio4' results mute audio where the second result the expected audio
bGotFrame = false;
if ((iRet = ffmpeg::avcodec_decode_audio4(ctx.pStream->codec, pFrame, &bGotFrame, &pkt)) < 0) {
hr = E_FAIL;
goto Next;
}
_ASSERT(pkt.size == iRet);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (false == bGotFrame)
goto Next;
iBytesUsed = ctx.pStream->codec->frame_size;
mt.format.audio.nChannels = ctx.pStream->codec->channels;
mt.format.audio.nSamplesPerSec = ctx.pStream->codec->sample_rate;
mt.format.audio.wBitsPerSample = ffmpeg::av_get_bytes_per_sample(ctx.pStream->codec->sample_fmt) * 8;
mt.format.audio.nBlockAlign = mt.format.audio.nChannels * mt.format.audio.wBitsPerSample / 8;
mt.format.audio.sSampleFormat = (short)pFrame->format;
break;
case ffmpeg::AVMediaType::AVMEDIA_TYPE_VIDEO:
if ((iRet = ffmpeg::avcodec_decode_video2(ctx.pStream->codec, pFrame, &bGotFrame, &pkt)) < 0) {
hr = E_FAIL;
break;
}
if (false == bGotFrame)
goto Next;
assert(ffmpeg::AVPixelFormat::AV_PIX_FMT_YUV420P == ctx.pStream->codec->pix_fmt);// Thats is the only color space currently supported
iBytesUsed = (ctx.pStream->codec->width * ctx.pStream->codec->height * 3) / 2;
mt.format.video.width = ctx.pStream->codec->width;
mt.format.video.height = ctx.pStream->codec->height;
mt.format.video.colorspace = ctx.pStream->codec->pix_fmt;
mt.format.video.fFPS = (float)ctx.pStream->codec->framerate.num / ctx.pStream->codec->framerate.den;
bVideoFound = true;
break;
default:
goto Next;
}
ctx.iFramesProcessed++;
{
FRAME f = { ctx.pStream->index, ((0 == ptsLast) ? dtsLast : ptsLast), (uint8_t**)pFrame->data, (int32_t*)pFrame->linesize };
if ((iRet > 0) && (false == cb(mt, f, pCbCtx)))
hr = S_FALSE;// Breaks the loop
}
Next:
ffmpeg::av_free_packet(&pkt);
if (0 != pFrame) {
//ffmpeg::av_frame_unref(pFrame);
ffmpeg::av_frame_free(&pFrame);
pFrame = 0;
}
}
ErrExit:
for (unsigned int i = 0; i < pFmtCtx->nb_streams; i++)
ffmpeg::avcodec_close(pFmtCtx->streams[i]->codec);
pIoCtx->buffer = 0;// We have allocated the buffer, no need for ffmpeg to free it 4 us
pFmtCtx->pb = 0;
ffmpeg::av_free(pIoCtx);
ffmpeg::avformat_close_input(&pFmtCtx);
ffmpeg::avformat_free_context(pFmtCtx);
return hr;
}
You need to unref the packets before reusing them. And there's no need to allocate and deallocate them all the time.
Here's how I do it which might help you:
// Initialise a packet queue
std::list<AVPacket *> packets;
...
for (int c = 0; c < MAX_PACKETS; c++) {
ff->packets.push_back(av_packet_alloc());
}
while (!quit) {
... get packet from queue
int err = av_read_frame(ff->context, packet);
... process packet (audio, video, etc)
av_packet_unref(packet); // add back to queue for reuse
}
// Release packets
while (ff->packets.size()) { // free packets
AVPacket *packet = ff->packets.front();
av_packet_free(&packet);
ff->packets.pop_front();
}
In your code you've freed a packet which wasn't allocated in the first place.
I have been trying to inject individual contacts (Touch events) on Windows 8. I'm using the InjectTouchInput described here, so I'm able to inject two contacts with the next code:
#include "stdafx.h"
#include <iostream>
#include <string>
#include <sstream>
#include <windows.h>
using namespace std;
class calculate
{
POINTER_TOUCH_INFO contact[2];
public:
calculate(int m);
void setDots(long x, long y, long x2, long y2, int opt, string num_dots);
};
calculate::calculate(int m)
{
}
void calculate::setDots(long x, long y, long x2, long y2, int opt, string num_dots)
{
switch (opt)
{
case 0:
InitializeTouchInjection(2, TOUCH_FEEDBACK_INDIRECT);
memset(&contact[0], 0, sizeof(POINTER_TOUCH_INFO));
memset(&contact[1], 0, sizeof(POINTER_TOUCH_INFO));
contact[0].pointerInfo.pointerType = PT_TOUCH;
contact[0].pointerInfo.pointerId = 0; //Id 0 for contact 0
contact[0].pointerInfo.ptPixelLocation.y = y;
contact[0].pointerInfo.ptPixelLocation.x = x;
contact[0].touchFlags = TOUCH_FLAG_NONE;
contact[0].touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
contact[0].orientation = 90;
contact[0].pressure = 32000;
contact[0].rcContact.top = contact[0].pointerInfo.ptPixelLocation.y - 2;
contact[0].rcContact.bottom = contact[0].pointerInfo.ptPixelLocation.y + 2;
contact[0].rcContact.left = contact[0].pointerInfo.ptPixelLocation.x - 2;
contact[0].rcContact.right = contact[0].pointerInfo.ptPixelLocation.x + 2;
contact[1].pointerInfo.pointerType = PT_TOUCH;
contact[1].pointerInfo.pointerId = 1; //Id 0 for contact 1
contact[1].pointerInfo.ptPixelLocation.y = y2;
contact[1].pointerInfo.ptPixelLocation.x = x2;
contact[1].touchFlags = TOUCH_FLAG_NONE;
contact[1].touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
contact[1].orientation = 90;
contact[1].pressure = 32000;
contact[1].rcContact.top = contact[1].pointerInfo.ptPixelLocation.y - 2;
contact[1].rcContact.bottom = contact[1].pointerInfo.ptPixelLocation.y + 2;
contact[1].rcContact.left = contact[1].pointerInfo.ptPixelLocation.x - 2;
contact[1].rcContact.right = contact[1].pointerInfo.ptPixelLocation.x + 2;
break;
case 1:
contact[0].pointerInfo.ptPixelLocation.y = y;
contact[0].pointerInfo.ptPixelLocation.x = x;
contact[1].pointerInfo.ptPixelLocation.y = y2;
contact[1].pointerInfo.ptPixelLocation.x = x2;
contact[0].pointerInfo.pointerFlags = POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
contact[1].pointerInfo.pointerFlags = POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
break;
case 2:
/*if I inject this flag to first contact [0], both points stopped, but I'm able to inject this flag to the second contact [1] and all is OK*/
//contact[0].pointerInfo.pointerFlags = POINTER_FLAG_UP;
contact[0].pointerInfo.pointerFlags = POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
contact[1].pointerInfo.pointerFlags = POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
//contact[1].pointerInfo.pointerId = 0;
//contact[0].pointerInfo.pointerId = 1;
contact[0].pointerInfo.ptPixelLocation.x =x;
contact[0].pointerInfo.ptPixelLocation.y =y;
contact[1].pointerInfo.ptPixelLocation.x =x2;
contact[1].pointerInfo.ptPixelLocation.y =y2;
//num_dots = "single";
break;
case 3:
contact[0].pointerInfo.pointerFlags = POINTER_FLAG_UP;
contact[1].pointerInfo.pointerFlags = POINTER_FLAG_UP;
break;
}
bool s;
//check how many points to inject
if(num_dots == "single")
s = InjectTouchInput(1, contact);
else
s = InjectTouchInput(2, contact);
if(!s)
{
LPWSTR str = new wchar_t[255];
auto t = GetLastError();
wsprintf(str, L"error: %d\n", t);
OutputDebugString(str);
wsprintf(str, L" action: %d\n", opt);
OutputDebugString(str);
}
else
{
LPWSTR str = new wchar_t[255];
auto t = GetLastError();
wsprintf(str, L"action %d\n", opt);
OutputDebugString(str);
}
if(opt == 2)
Sleep(20);
}
int c =0;
int
WINAPI WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd)
{
bool g = true;
POINT ipoint;
POINT point;
calculate set(0);
if (GetCursorPos(&ipoint))
set.setDots(ipoint.x, ipoint.y, 0,0,0, "single");//initialize both points
do
{
if (GetCursorPos(&point)) {
//stop the loop pressing right click
if((GetKeyState(VK_RBUTTON) & 0x100) != 0)
g = false;
if((GetKeyState(VK_LBUTTON) & 0x100) != 0)
{
if(c<1)
{
if((GetKeyState(VK_LSHIFT) & 0x100) != 0)//injecting DOWN event
set.setDots(point.x, point.y, point.x - 50, point.y + 50, 1, "two");
else
set.setDots(point.x, point.y, 0, 0, 1, "single");
}
else
{
if((GetKeyState(VK_LSHIFT) & 0x100) != 0)//injecting UPDATE event (on move)
set.setDots(point.x, point.y, point.x - 50, point.y + 50, 2, "two");
else
set.setDots(point.x, point.y, 0,0,2, "single");
}
c++;
}
else
{
/*if((GetKeyState(VK_LSHIFT) & 0x100) != 0)
set.setDots(0,0,0,0,3, "two");
else
set.setDots(0,0,0,0,3, "single");*/
c = 0;
}
}
} while (g);
}
So, with this code, I'm able to inject one or two touches (if shift key is pressed), and draw on paint using paint brushes. But when I try to inject individual events for each contact, for example contact[0].pointerInfo.pointerFlags = POINTER_FLAG_UP;
contact[1].pointerInfo.pointerFlags = POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; both contacts stop, even when I try changing their ids, or changing the number of points to inject.
The above code is just an example, but I have tried using different flags for each contact without success :(
My goal is be able to inject two points, with individual events each one, and if first contact[0] has an UP event, the second contact should still running without stop the process...So, how can I inject individual contacts (touches) with different events each one?
Thank you!
I want to extract a thumbnail from an image, so I tried to use GDI+. What I did was to create a new small Bitmap to hold the thumbnail, and using Graphics::DrawImage() to draw the bitmap into it (and hence getting a thumbnail).
But when using this approach on a large number of images, it gets really slow. So is there a way to speed it up (by using only Windows API and not an external library).
Note: I know that some images store a thumbnail inside of them, but I am looking to extract a thumbnail from the ones that don't.
You can increase speed by loading thumbnail from EXIF (for JPEG and some other formats). But GetThumbnailImage's result isn't good. Here is another implementation:
Usage:
Gdiplus::Bitmap* thumb = GetThumbnail(filename, thumbWidth, thumbHeight);
Code:
PropertyItem* GetPropertyItemFromImage(Gdiplus::Image* bm, PROPID propId) {
UINT itemSize = bm->GetPropertyItemSize(propId);
if (!itemSize) {
return 0;
}
PropertyItem* item = reinterpret_cast<PropertyItem*>(malloc(itemSize));
if (bm->GetPropertyItem(propId, itemSize, item) != Ok) {
free(item);
return 0;
}
return item;
}
UINT VoidToInt(void* data, unsigned int size) {
switch (size) {
case 8:
return *reinterpret_cast<UINT*>(data);
case 4:
return *reinterpret_cast<DWORD*>(data);
case 2:
return *reinterpret_cast<WORD*>(data);
default:
return *reinterpret_cast<BYTE*>(data);
}
}
typedef IStream * (STDAPICALLTYPE *SHCreateMemStreamFuncType)(const BYTE *pInit, UINT cbInit);
SHCreateMemStreamFuncType SHCreateMemStreamFunc = 0;
bool IsVista() {
static int isVista = -1;
if (isVista == -1)
{
OSVERSIONINFO osver;
osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
isVista = (::GetVersionEx(&osver) &&
osver.dwPlatformId == VER_PLATFORM_WIN32_NT &&
(osver.dwMajorVersion >= 6));
}
return isVista != FALSE;
}
Gdiplus::Bitmap* BitmapFromMemory(BYTE* data, unsigned int imageSize) {
if (WinUtils::IsVista()) {
if (!SHCreateMemStreamFunc) {
HMODULE lib = LoadLibrary(_T("Shlwapi.dll"));
SHCreateMemStreamFunc = reinterpret_cast<SHCreateMemStreamFuncType>(GetProcAddress(lib, "SHCreateMemStream"));
if (!SHCreateMemStreamFunc) {
return 0;
}
}
Gdiplus::Bitmap * bitmap;
IStream* pStream = SHCreateMemStreamFunc(data, imageSize);
if (pStream) {
bitmap = Gdiplus::Bitmap::FromStream(pStream);
pStream->Release();
if (bitmap) {
if (bitmap->GetLastStatus() == Gdiplus::Ok) {
return bitmap;
}
delete bitmap;
}
}
} else {
HGLOBAL buffer = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
if (buffer) {
void* pBuffer = ::GlobalLock(buffer);
if (pBuffer) {
Gdiplus::Bitmap * bitmap;
CopyMemory(pBuffer, data, imageSize);
IStream* pStream = NULL;
if (::CreateStreamOnHGlobal(buffer, FALSE, &pStream) == S_OK) {
bitmap = Gdiplus::Bitmap::FromStream(pStream);
pStream->Release();
if (bitmap) {
if (bitmap->GetLastStatus() == Gdiplus::Ok) {
return bitmap;
}
delete bitmap;
}
}
::GlobalUnlock(buffer);
}
::GlobalFree(buffer);
}
}
return 0;
}
// Based on original method from http://danbystrom.se/2009/01/05/imagegetthumbnailimage-and-beyond/
Gdiplus::Bitmap* GetThumbnail(Gdiplus::Image* bm, int width, int height, Gdiplus::Size* realSize = 0) {
using namespace Gdiplus;
if (realSize) {
realSize->Width = bm->GetWidth();
realSize->Height = bm->GetHeight();
}
Size sz = AdaptProportionalSize(Size(width, height), Size(bm->GetWidth(), bm->GetHeight()));
Bitmap* res = new Bitmap(sz.Width, sz.Height);
Graphics gr(res);
gr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
UINT size = bm->GetPropertyItemSize(PropertyTagThumbnailData);
if (size) {
// Loading thumbnail from EXIF data (fast)
enum ThumbCompression { ThumbCompressionJPEG, ThumbCompressionRGB, ThumbCompressionYCbCr, ThumbCompressionUnknown }
compression = ThumbCompressionJPEG;
PropertyItem* thumbnailFormatItem = GetPropertyItemFromImage(bm, PropertyTagThumbnailFormat);
if (thumbnailFormatItem) {
UINT format = VoidToInt(thumbnailFormatItem->value, thumbnailFormatItem->length);
if (format == 0) {
compression = ThumbCompressionRGB;
} else if (format == 1) {
compression = ThumbCompressionJPEG;
} else {
compression = ThumbCompressionUnknown;
}
free(thumbnailFormatItem);
} else {
PropertyItem* compressionItem = GetPropertyItemFromImage(bm, PropertyTagThumbnailCompression);
if (compressionItem) {
WORD compressionTag = *reinterpret_cast<WORD*>(compressionItem->value);
if (compressionTag == 1) {
compression = ThumbCompressionRGB;
PropertyItem* photometricInterpretationItem = GetPropertyItemFromImage(bm, PropertyTagPhotometricInterp);
if (photometricInterpretationItem) {
WORD photoMetricInterpretationTag = VoidToInt(photometricInterpretationItem->value, photometricInterpretationItem->length);
free(photometricInterpretationItem);
if (photoMetricInterpretationTag == 6) {
compression = ThumbCompressionYCbCr;
}
}
} else if (compressionTag == 6) {
compression = ThumbCompressionJPEG;
}
free(compressionItem);
}
}
int originalThumbWidth = 0, originalThumbHeight = 0;
if (compression == ThumbCompressionJPEG || compression == ThumbCompressionRGB) {
PropertyItem* thumbDataItem = GetPropertyItemFromImage(bm, PropertyTagThumbnailData);
if (thumbDataItem) {
if (compression == ThumbCompressionJPEG) {
Bitmap* src = BitmapFromMemory(reinterpret_cast<BYTE*>(thumbDataItem->value), thumbDataItem->length);
if (src) {
gr.DrawImage(src, 0, 0, sz.Width, sz.Height);
delete src;
free(thumbDataItem);
return res;
}
} else if (compression == ThumbCompressionRGB) {
PropertyItem* widthItem = GetPropertyItemFromImage(bm, PropertyTagThumbnailImageWidth);
if (widthItem) {
originalThumbWidth = VoidToInt(widthItem->value, widthItem->length);
free(widthItem);
}
PropertyItem* heightItem = GetPropertyItemFromImage(bm, PropertyTagThumbnailImageHeight);
if (heightItem) {
originalThumbHeight = VoidToInt(heightItem->value, heightItem->length);
free(heightItem);
}
if (originalThumbWidth && originalThumbHeight) {
BITMAPINFOHEADER bih;
memset(&bih, 0, sizeof(bih));
bih.biSize = sizeof(bih);
bih.biWidth = originalThumbWidth;
bih.biHeight = -originalThumbHeight;
bih.biPlanes = 1;
bih.biBitCount = 24;
BITMAPINFO bi;
memset(&bi, 0, sizeof(bi));
bi.bmiHeader = bih;
BYTE* data = reinterpret_cast<BYTE*>(thumbDataItem->value);
BYTE temp;
// Convert RGB to BGR
for (unsigned int offset = 0; offset < thumbDataItem->length; offset += 3) {
temp = data[offset];
data[offset] = data[offset + 2];
data[offset + 2] = temp;
}
Bitmap src(&bi, thumbDataItem->value);
if (src.GetLastStatus() == Ok) {
gr.DrawImage(&src, 0, 0, sz.Width, sz.Height);
free(thumbDataItem);
return res;
}
}
} else {
// other type of compression not implemented
}
free(thumbDataItem);
}
}
}
// Fallback - Load full image and draw it (slow)
gr.DrawImage(bm, 0, 0, sz.Width, sz.Height);
return res;
}
Gdiplus::Bitmap* GetThumbnail(const CString& filename, int width, int height, Gdiplus::Size* realSize) {
using namespace Gdiplus;
Image bm(filename);
if (bm.GetLastStatus() != Ok) {
return 0;
}
return GetThumbnail(&bm, width, height, realSize);
}
Gdiplus::Size AdaptProportionalSize(const Gdiplus::Size& szMax, const Gdiplus::Size& szReal)
{
int nWidth;
int nHeight;
double sMaxRatio;
double sRealRatio;
if (szMax.Width < 1 || szMax.Height < 1 || szReal.Width < 1 || szReal.Height < 1)
return Size();
sMaxRatio = szMax.Width / static_cast<double>(szMax.Height);
sRealRatio = szReal.Width / static_cast<double>(szReal.Height);
if (sMaxRatio < sRealRatio) {
nWidth = min(szMax.Width, szReal.Width);
nHeight = static_cast<int>(round(nWidth / sRealRatio));
} else {
nHeight = min(szMax.Height, szReal.Height);
nWidth = static_cast<int>(round(nHeight * sRealRatio));
}
return Size(nWidth, nHeight);
}
Trying to take a screenshot of a window as a bitmap. The code below is creating a properly sized bitmap, but every pixel is black. In other words, GetDIBits is setting imageBuffer to all 0's.
The posted code saves a bitmap for every notepad open and visible on the screen. None of the asserts fail.
The BITMAPFILEHEADER and actual writing to a file is omitted, because the final for loop with asserts shows GetDIBits set imageBuffer to all 0's, so there's no need to examine code after that point.
(In the executable's properties, under Configuration Properties->General, I have Character Set to "Not Set" to avoid unicode necessities.)
#include "stdafx.h"
#include <vector>
#include <sstream>
#include <Windows.h>
#include <iostream>
#include <assert.h>
using namespace std;
BOOL CALLBACK getNotepadWindowsCallback(HWND window, LPARAM notepadWindowsLparam) {
if (NULL != GetParent(window)) {
return true;
}
if (false == IsWindowVisible(window)) {
return true;
}
char text[1024] = { 0 };
GetWindowText(window, text, sizeof(text));
if (NULL == strstr(text, " - Notepad")) {
return true;
}
reinterpret_cast<vector<HWND>*>(notepadWindowsLparam)->push_back(window);
return true;
}
vector<HWND> getNotepadWindows() {
vector<HWND> notepadWindows;
EnumWindows(getNotepadWindowsCallback, reinterpret_cast<LPARAM>(¬epadWindows));
return notepadWindows;
}
int _tmain(int argc, _TCHAR* argv[]) {
for (HWND notepadWindow : getNotepadWindows()) {
HDC notepadWindowDeviceContext = GetDC(notepadWindow);
assert(NULL != notepadWindowDeviceContext);
HDC memoryDeviceContext = CreateCompatibleDC(notepadWindowDeviceContext);
assert(NULL != memoryDeviceContext);
RECT notepadWindowRectangle;
if (0 == GetClientRect(notepadWindow, ¬epadWindowRectangle)) {
assert(true == false);
}
SIZE notepadWindowSize;
notepadWindowSize.cx = notepadWindowRectangle.right - notepadWindowRectangle.left + 1;
notepadWindowSize.cy = notepadWindowRectangle.bottom - notepadWindowRectangle.top + 1;
HBITMAP memoryBitmap = CreateCompatibleBitmap(notepadWindowDeviceContext, notepadWindowSize.cx, notepadWindowSize.cy);
assert(NULL != memoryBitmap);
HBITMAP defaultBitmap = static_cast<HBITMAP>(SelectObject(memoryDeviceContext, memoryBitmap));
assert(NULL != defaultBitmap);
assert(TRUE == BitBlt(memoryDeviceContext, 0, 0, notepadWindowSize.cx, notepadWindowSize.cy, notepadWindowDeviceContext, notepadWindowRectangle.left, notepadWindowRectangle.right, SRCCOPY));
BITMAPINFO bitmapinfo = { 0 };
bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapinfo.bmiHeader.biWidth = notepadWindowSize.cx;
bitmapinfo.bmiHeader.biHeight = notepadWindowSize.cy;
bitmapinfo.bmiHeader.biPlanes = 1;
bitmapinfo.bmiHeader.biBitCount = 4 * 8;
bitmapinfo.bmiHeader.biCompression = BI_RGB;
//bitmapinfo.bmiHeader.biSizeImage, per MSDN, may be set to zero for BI_RGB bitmaps
int imageBufferSize = notepadWindowSize.cx*notepadWindowSize.cy;
int* imageBuffer = new(int[imageBufferSize]);
// doing a memset here to initialize imageBuffer to 0's makes no change - leaving it out makes clear GetDIBits is setting imageBuffer to 0's, because it goes in with random garbage
assert(NULL != SelectObject(memoryDeviceContext, defaultBitmap)); // this must happen before GetDIBits, per MSDN, so memoryBitmap is not selected into a device context
int returnValue = GetDIBits(memoryDeviceContext, memoryBitmap, 0, notepadWindowSize.cy, static_cast<LPVOID>(imageBuffer), &bitmapinfo, DIB_RGB_COLORS);
assert(0 != returnValue);
cout << "returnValue is " << returnValue << endl; // shows proper number of lines is written
for (int i = 0; i < imageBufferSize; ++i) {
assert(0 == imageBuffer[i]);
}
DeleteDC(memoryDeviceContext);
ReleaseDC(NULL, notepadWindowDeviceContext);
}
}
Oops. Helps to give BitBlt notepadWindowRectangle.top instead of notepadWindowRectangle.right for its y1 parameter. Fixing that makes it work.