This is the screenshot my program takes:
I try to make a C++ program that takes a screenshot and saves it as png. Everthing works except it takes a screenshot of just the top left of the screen.
The problem is that my application takes a picture of the top left corner of my desktop.
How can I take a screenshot of the WHOLE screen? What do I have to change in my code to reach my goal?
This is my code:
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL) {
return -1;
}
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
void TakeScreenshot(const wchar_t* file_name) {
// Get the dimensions of the whole desktop
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Create a bitmap to hold the screenshot
HDC screen_dc = GetDC(NULL);
HDC mem_dc = CreateCompatibleDC(screen_dc);
HBITMAP bitmap = CreateCompatibleBitmap(screen_dc, width, height);
HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);
// Copy the screen contents to the bitmap
BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY);
// Save the bitmap to a file
Gdiplus::Bitmap image(bitmap, NULL);
CLSID png_clsid;
GetEncoderClsid(L"image/png", &png_clsid);
image.Save((WCHAR*)file_name, &png_clsid, NULL);
// Clean up
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
}
int main()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
TakeScreenshot(L"test.png");
GdiplusShutdown(gdiplusToken);
}
Updated code:
I tried using GetDpiForSystem() because my application should be DPI-aware. But still same result.
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
printf("Error: GetImageEncodersSize returned size 0\n");
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL) {
printf("Error: malloc failed to allocate memory for ImageCodecInfo\n");
return -1;
}
if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) {
printf("Error: GetImageEncoders failed\n");
free(pImageCodecInfo);
return -1;
}
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
void TakeScreenshot(const wchar_t* file_name)
{
// Get the dimensions of the whole desktop
int dpi_x = GetDpiForSystem();
int dpi_y = GetDpiForSystem();
int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x);
int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y);
if (width == 0 || height == 0) {
printf("Error: GetSystemMetrics returned invalid screen dimensions\n");
return;
}
printf("Width: %d\n", width);
printf("Height: %d\n", height);
// Create a bitmap to hold the screenshot
HDC screen_dc = GetDC(NULL);
if (screen_dc == NULL) {
printf("Error: GetDC failed to get a handle to the screen device context\n");
return;
}
HDC mem_dc = CreateCompatibleDC(screen_dc);
if (mem_dc == NULL) {
printf("Error: CreateCompatibleDC failed to create a compatible device context\n");
ReleaseDC(NULL, screen_dc);
return;
}
// Create a bitmap that is scaled to the appropriate DPI
HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL);
if (bitmap == NULL) {
printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n");
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
return;
}
HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);
// Set the DPI of the memory DC to match the system DPI
SetGraphicsMode(mem_dc, GM_ADVANCED);
XFORM xform;
xform.eM11 = (FLOAT)dpi_x / 96;
xform.eM12 = xform.eM21 = xform.eM22 = 0;
xform.eDx = xform.eDy = 0;
SetWorldTransform(mem_dc, &xform);
// Copy the screen contents to the bitmap
if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) {
printf("Error:BitBlt failed to copy screen contents to bitmap\n");
return;
}
// Save the bitmap to a file
Gdiplus::Bitmap image(bitmap, NULL);
if (image.GetLastStatus() != Ok) {
printf("Error: Bitmap constructor failed to create a Bitmap object\n");
return;
}
CLSID png_clsid;
int r = GetEncoderClsid(L"image/png", &png_clsid);
if (r == -1)
{
printf("Error: unable to find image encoder for MIME type 'image/png'\n");
return;
}
if (image.Save(file_name, &png_clsid, NULL) != Ok) {
printf("Error: Bitmap::Save failed to save image\n");
return;
}
// Clean up
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
TakeScreenshot(L"test.png");
GdiplusShutdown(gdiplusToken);
return 0;
}
It is important for the code to be DPI-aware because the DPI of a display can vary from one system to another. For example, a user might have a high-resolution display with a DPI of 192, while another user might have a lower-resolution display with a DPI of 96.
I added this code before calling TakeScreenshot(): DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);.
After TakeScreenshot() I added: SetThreadDpiAwarenessContext(dpi_awareness_context);.
This makes sure my application is DPI-aware, as #AndreasWenzel said in the comments.
This code works perfectly:
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
printf("Error: GetImageEncodersSize returned size 0\n");
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL) {
printf("Error: malloc failed to allocate memory for ImageCodecInfo\n");
return -1;
}
if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) {
printf("Error: GetImageEncoders failed\n");
free(pImageCodecInfo);
return -1;
}
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
void TakeScreenshot(const wchar_t* file_name)
{
// Get the dimensions of the whole desktop
int dpi_x = GetDpiForSystem();
int dpi_y = GetDpiForSystem();
int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x);
int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y);
if (width == 0 || height == 0) {
printf("Error: GetSystemMetrics returned invalid screen dimensions\n");
return;
}
printf("Width: %d\n", width);
printf("Height: %d\n", height);
// Create a bitmap to hold the screenshot
HDC screen_dc = GetDC(NULL);
if (screen_dc == NULL) {
printf("Error: GetDC failed to get a handle to the screen device context\n");
return;
}
HDC mem_dc = CreateCompatibleDC(screen_dc);
if (mem_dc == NULL) {
printf("Error: CreateCompatibleDC failed to create a compatible device context\n");
ReleaseDC(NULL, screen_dc);
return;
}
// Create a bitmap that is scaled to the appropriate DPI
HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL);
if (bitmap == NULL) {
printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n");
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
return;
}
HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);
// Set the DPI of the memory DC to match the system DPI
SetGraphicsMode(mem_dc, GM_ADVANCED);
XFORM xform;
xform.eM11 = (FLOAT)dpi_x / 96;
xform.eM12 = xform.eM21 = xform.eM22 = 0;
xform.eDx = xform.eDy = 0;
SetWorldTransform(mem_dc, &xform);
// Copy the screen contents to the bitmap
if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) {
printf("Error:BitBlt failed to copy screen contents to bitmap\n");
return;
}
// Save the bitmap to a file
Gdiplus::Bitmap image(bitmap, NULL);
if (image.GetLastStatus() != Ok) {
printf("Error: Bitmap constructor failed to create a Bitmap object\n");
return;
}
CLSID png_clsid;
int r = GetEncoderClsid(L"image/png", &png_clsid);
if (r == -1)
{
printf("Error: unable to find image encoder for MIME type 'image/png'\n");
return;
}
if (image.Save(file_name, &png_clsid, NULL) != Ok) {
printf("Error: Bitmap::Save failed to save image\n");
return;
}
// Clean up
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
TakeScreenshot(L"test.png");
SetThreadDpiAwarenessContext(dpi_awareness_context);
GdiplusShutdown(gdiplusToken);
return 0;
}
Related
I am receiving those screenshots in WM_PAINT, and then save it to memory. The screenshots have around 8-10kB size. And I am trying to display those screenshots in a 15 FPS, but the problem is that its displaying sometimes glitched screenshots like this:
[the gray square with dots inside it, that should not be there, its covering the half of the screenshot :c ]
Image displayed like this, is like every second or third screenshot. I have no idea how to fix this im stuck a couple days at this.
Here is the server code (window procedure) where i am trying to display these screenshots
LRESULT CALLBACK ClientWndProc(HWND Chwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
std::vector<char> buffer(50000);
switch (uMsg)
{
case WM_CREATE:
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
SetTimer(Chwnd, SCREEN_TIMER, 1000/15, NULL);
break;
}
case WM_TIMER:
{
if (wParam == SCREEN_TIMER)
{
SOCKET selectedSocket = clientSockets[itemIndex];
auto it = std::find(clientSockets.begin(), clientSockets.end(), selectedSocket);
send(selectedSocket, "capscr", strlen("capscr"), 0);
InvalidateRect(Chwnd, NULL, TRUE);
}
break;
}
case WM_PAINT:
{
SOCKET selectedSocket = clientSockets[itemIndex];
auto it = std::find(clientSockets.begin(), clientSockets.end(), selectedSocket);
if (it != clientSockets.end())
{
int bytesReceived = 0;
int totalBytesReceived = 0;
int expectedBytes = buffer.size();
Sleep(1000/15);
do
{
bytesReceived = recv(selectedSocket, buffer.data() + totalBytesReceived, buffer.size() - totalBytesReceived, 0);
totalBytesReceived += bytesReceived;
} while (totalBytesReceived < expectedBytes && bytesReceived > 0);
//MessageBoxA(NULL, buffer.data(), "s", MB_OK);
HGLOBAL hGlobal = GlobalAlloc(GHND, expectedBytes);
void* pData = GlobalLock(hGlobal);
memcpy(pData, buffer.data(), buffer.size());
GlobalUnlock(hGlobal);
IStream* pStream = NULL;
CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
Gdiplus::Bitmap bitmap(pStream);
Gdiplus::Status status = bitmap.GetLastStatus();
PAINTSTRUCT ps;
HDC hdc = BeginPaint(Chwnd, &ps);
int imgWidth = bitmap.GetWidth();
int imgHeight = bitmap.GetHeight();
Gdiplus::Graphics graphics(hdc);
RECT clientRect;
GetClientRect(Chwnd, &clientRect);
graphics.DrawImage(&bitmap, 0, 0, imgWidth, imgHeight);
EndPaint(Chwnd, &ps);
GlobalFree(hGlobal);
}
break;
}
case WM_ERASEBKGND:
return TRUE;
case WM_CLOSE:
{
DestroyWindow(hwndClient);
break;
}
case WM_DESTROY:
DestroyWindow(hwndClient);
return 0;
default:
return DefWindowProc(Chwnd, uMsg, wParam, lParam);
}
return 0;
}
client code sending screenshots:
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return 0;
}
void shutdownGdiPlus()
{
Gdiplus::GdiplusShutdown(gdiPlusToken);
gdiPlusToken = NULL;
}
bool initGdiPlusIfNeeded()
{
// If already initialized then return true
if (gdiPlusToken != NULL)
return true;
static Gdiplus::GdiplusStartupInput gdiPlusStartupInput;
return (success = GdiplusStartup(&gdiPlusToken, &gdiPlusStartupInput, NULL)) == Gdiplus::Status::Ok;
}
std::pair<ULONGLONG, char*> getScreenShotAsByteArray()
{
if (!initGdiPlusIfNeeded())
return {};
IStream* iStream;
HRESULT res = CreateStreamOnHGlobal(NULL, true, &iStream);
const HDC srcDC = ::GetDC(NULL);
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
const HDC memDC = CreateCompatibleDC(srcDC);
const HBITMAP membit = CreateCompatibleBitmap(srcDC, screenWidth, screenHeight);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(memDC, membit);
BitBlt(memDC, 0, 0, screenWidth, screenHeight, srcDC, 0, 0, SRCCOPY);
// Create a bitmap to store the previous screenshot
HBITMAP prevBitmap = CreateCompatibleBitmap(srcDC, screenWidth, screenHeight);
HDC prevDC = CreateCompatibleDC(srcDC);
SelectObject(prevDC, prevBitmap);
// Get the size of the bitmaps in bytes
BITMAP bmp;
GetObject(prevBitmap, sizeof(BITMAP), &bmp);
int prevBmpSize = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
GetObject(membit, sizeof(BITMAP), &bmp);
int currBmpSize = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
// Allocate memory for the bitmap data
char* prevBmpData = new char[prevBmpSize];
char* currBmpData = new char[currBmpSize];
// Get the raw pixel data of the bitmaps
GetBitmapBits(prevBitmap, prevBmpSize, prevBmpData);
GetBitmapBits(membit, currBmpSize, currBmpData);
// Compare the bitmap data
bool isDifferent = memcmp(prevBmpData, currBmpData, currBmpSize) != 0;
// Free the allocated memory
delete[] prevBmpData;
delete[] currBmpData;
// Check if the current screenshot is different from the previous screenshot
if (isDifferent)
{
// Screenshot is different, take a new screenshot
int newScreenWidth = 700;
int newScreenHeight = 500;
Gdiplus::Bitmap fullScreenBitmap(membit, NULL);
Gdiplus::Bitmap newBitmap(newScreenWidth, newScreenHeight);
Gdiplus::Graphics graphics(&newBitmap);
graphics.DrawImage(&fullScreenBitmap, 0, 0, newScreenWidth, newScreenHeight);
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
ULONG quality = 0;
EncoderParameters encoderParams;
encoderParams.Count = 1;
encoderParams.Parameter[0].Guid = EncoderQuality;
encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
encoderParams.Parameter[0].NumberOfValues = 1;
encoderParams.Parameter[0].Value = &quality;
newBitmap.Save(iStream, &clsid, &encoderParams);
ULARGE_INTEGER pos{ 0, 0 };
const LARGE_INTEGER pos2{ 0, 0 };
iStream->Seek(pos2, STREAM_SEEK_SET, &pos);
ULONG bytesWritten = 0;
STATSTG statstg;
iStream->Stat(&statstg, STATFLAG_DEFAULT);
const ULONGLONG bufferLen = statstg.cbSize.QuadPart;
char* imageBuffer = new char[bufferLen];
iStream->Read(imageBuffer, bufferLen, &bytesWritten);
iStream->Release();
DeleteObject(memDC);
DeleteObject(membit);
::ReleaseDC(NULL, srcDC);
std::pair<ULONGLONG, char*> result(bufferLen, imageBuffer);
return result;
}
else
{
return {};
}
}
void sendScreens() {
if (clientsocket == INVALID_SOCKET) {
// handle error, the socket is not valid or not connected
return;
}
std::pair<ULONGLONG, char*> image = getScreenShotAsByteArray();
ULONGLONG bufferLen = image.first;
char* imageBuffer = image.second;
int bytesSent = send(clientsocket, imageBuffer, bufferLen, 0);
if(bytesSent < 0)
{
return;
}
std::cout << "Sent Bytes: " << bytesSent << "\n";
// wait for the desired interval before sending the next screenshot
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point end = start + std::chrono::milliseconds(FPS);
std::this_thread::sleep_until(end);
}
I trying to render a special image which is just a bunch of pixels with no format. It is a sort of a raw image with 42x 42 pixels. The images are palettes so I am curious how I should handle that situation too.
How can I convert an array of pixels to a texture in SDL2?
Do I apply palettes in the end?
More details:
Currently I am opening the image and adding some transparent (black bytes) and storing this into a char array. These pixels I will need to render.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <SDL.h>
#include <SDL_image.h>
#include <stdio.h>
#include <windows.h>
#include <vector>
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
FILE* OpenCelFile(const char * filepath) {
FILE* ptr = fopen(filepath, "rb");
return ptr;
}
DWORD FrameCount[1] = { 0 };
DWORD DataStart[1] = { 0 };
DWORD dummy[1] = { 0 };
signed char CommandByte[1] = { 0 };
void SkipToData(FILE* pFile, DWORD* dataStart, int bytesRead)
{
int MoveForwardBytes = *dataStart - bytesRead;
fseek(pFile, MoveForwardBytes, SEEK_CUR);
}
// There is more to this , but it isn't 100% important.
void ReadCelHeader(FILE* pFile)
{
fread((void*)FrameCount, 4, 1, pFile);
fread((void*)DataStart, 4, 1, pFile);
SkipToData(pFile, DataStart, 8);
}
void ReadCommandByte(FILE* pFile)
{
fread((void*)CommandByte, 1, 1, pFile);
}
std::vector<char> backBuffer;
int CreateRawImageBuffer(FILE* pFile) {
ReadCommandByte(pFile);
int bytesRead = 0;
// handel transparent bytes;
if (*(BYTE*)CommandByte >= 0x80) {
// this is a negative byte
signed int skipBytes = *(BYTE*)CommandByte - 256;
// convert it to positive number.
skipBytes = abs(skipBytes);
for (skipBytes; skipBytes != NULL; skipBytes--) {
backBuffer.push_back(0x00);
bytesRead++;
}
}
// set real pixels
if (*(BYTE*)CommandByte < 0x80) {
signed int byteCount = *(BYTE*)CommandByte;
for (byteCount; byteCount != NULL; byteCount--) {
BYTE t_char[1] = {0x00};
fread((void*)t_char, 1, 1, pFile);
backBuffer.push_back(*t_char);
bytesRead++;
}
}
return bytesRead;
}
int main(int argc, char* args[]) {
SDL_Window* window = NULL;
SDL_Surface* screenSurface = NULL;
bool RunningMainGameLoop = true;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "could not initialize sdl2: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Renderunkimage", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
if (window == NULL) {
fprintf(stderr, "could not create window: %s\n", SDL_GetError());
return 1;
}
FILE* ptr = OpenCelFile("C:\\Users\\luzer\\source\\repos\\unkimages\\unkimage.cel");
ReadCelHeader(ptr);
int TotalFramePixels = 48 * 48;
int fc = *FrameCount;
int pixelsAdded = { 0 };
for (fc; fc != 0; fc--) {
for (TotalFramePixels; TotalFramePixels != NULL; TotalFramePixels = TotalFramePixels - pixelsAdded) {
pixelsAdded = CreateRawImageBuffer(ptr);
}
}
screenSurface = SDL_GetWindowSurface(window);
SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0x00, 0x00, 0x00));
SDL_UpdateWindowSurface(window);
while (RunningMainGameLoop)
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
RunningMainGameLoop = false;
break;
}
}
// handle renders...
}
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
You need to use SDL_Surface first using the following:
SDL_CreateRGBSurfaceFrom(data, IMAGE_WIDTH, IMAGE_HEIGHT, 8, IMAGE_WIDTH /*pitch*/, 0, 0, 0, 0);
The images I am loading are really just single byte per colour and then I have to apply a palette to them. there is a sort of RLE encoding or compression which is why I have to load them , otherwise you just load them and pass them to a surface.
here is the whole code for anyone interested.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <SDL.h>
#include <SDL_image.h>
#include <stdio.h>
#include <windows.h>
#include <vector>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <array>
#define SCREEN_WIDTH 200
#define SCREEN_HEIGHT 200
#define TICK_INTERVAL 45
static Uint32 next_time;
SDL_Color palData[256];
DWORD FrameCount[1] = { 0 };
DWORD DataStart[1] = { 0 };
DWORD dummy[1] = { 0 };
signed char CommandByte[1] = { 0 };
int TotalFramePixels = 48 * 48;
int IMAGE_WIDTH = 48;
int IMAGE_HEIGHT = 48;
struct Color {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct celFrame {
byte ImageData[2304]; //fixed size of image. 48*48
};
FILE* OpenCelFile(const char * filepath) {
FILE* ptr = fopen(filepath, "rb");
return ptr;
}
void LoadPalette(const char* pszFileName)
{
FILE* PalFile = fopen(pszFileName, "rb");
for (int idx = 0; idx < 255; idx++) {
fread(&palData[idx].r, 1, 1, PalFile);
fread(&palData[idx].g, 1, 1, PalFile);
fread(&palData[idx].b, 1, 1, PalFile);
}
}
void SkipToData(FILE* pFile, DWORD* dataStart, int bytesRead)
{
int MoveForwardBytes = *dataStart - bytesRead;
fseek(pFile, MoveForwardBytes, SEEK_CUR);
}
// There is more to this , but it isn't 100% important.
void ReadCelHeader(FILE* pFile)
{
fread((void*)FrameCount, 4, 1, pFile);
fread((void*)DataStart, 4, 1, pFile);
SkipToData(pFile, DataStart, 8);
}
void ReadCommandByte(FILE* pFile)
{
fread((void*)CommandByte, 1, 1, pFile);
}
int CreateRawImageBuffer(FILE* pFile, struct celFrame* s_celFrame, int bytesRead) {
ReadCommandByte(pFile);
// handel transparent bytes;
if (*(BYTE*)CommandByte >= 0x80) {
// this is a negative byte
signed int skipBytes = *(BYTE*)CommandByte - 256;
// convert it to positive number.
skipBytes = abs(skipBytes);
for (skipBytes; skipBytes != NULL; skipBytes--) {
s_celFrame->ImageData[bytesRead] = 0xff;
bytesRead++;
}
}
// set real pixels
if (*(BYTE*)CommandByte < 0x80) {
signed int byteCount = *(BYTE*)CommandByte;
for (byteCount; byteCount != NULL; byteCount--) {
BYTE t_char[1] = {0xff};
fread((void*)t_char, 1, 1, pFile);
s_celFrame->ImageData[bytesRead] = *t_char;
bytesRead++;
}
}
if (bytesRead >= TotalFramePixels) {
return bytesRead;
}
CreateRawImageBuffer(pFile, s_celFrame, bytesRead);
}
Uint32 time_left(void)
{
Uint32 now;
now = SDL_GetTicks();
if (next_time <= now)
return 0;
else
return next_time - now;
}
int main(int argc, char* args[]) {
SDL_Window* window = NULL;
SDL_Surface* screenSurface = NULL;
SDL_Renderer* renderer = NULL;
SDL_Surface* PentagramSurface[8] = {0};
SDL_Texture* pentagramTexture[8] = { 0 };
int frameCount = 0;
bool RunningMainGameLoop = true;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "could not initialize sdl2: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("RenderCEL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
if (window == nullptr) {
fprintf(stderr, "could not create window: %s\n", SDL_GetError());
return 1;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == nullptr) {
std::cout << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
return 1;
}
FILE* pfile = OpenCelFile("C:\\Users\\luzer\\source\\repos\\RenderCEL\\PentSpin.cel");
LoadPalette("C:\\Users\\luzer\\source\\luzer\\RenderCEL\\cut2.pal");
ReadCelHeader(pfile);
int fc = *FrameCount;
int pixelsAdded = { 0 };
celFrame s_celFrame[8] = {0};
// Create individual frames of each image
// by storing them in structs
for (int fidx = 0; fidx != fc; fidx++) {
int bytes_read = CreateRawImageBuffer(pfile, &s_celFrame[fidx], 0);
PentagramSurface[fidx] = SDL_CreateRGBSurfaceFrom(&s_celFrame[fidx], IMAGE_WIDTH, IMAGE_HEIGHT, 8, IMAGE_WIDTH /*pitch*/, 0, 0, 0, 0);
SDL_SetPaletteColors(PentagramSurface[fidx]->format->palette, palData, 0, 256);
pentagramTexture[fidx] = SDL_CreateTextureFromSurface(renderer, PentagramSurface[fidx]);
SDL_FreeSurface(PentagramSurface[fidx]);
}
screenSurface = SDL_GetWindowSurface(window);
SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0x00, 0x00, 0x00));
while (RunningMainGameLoop)
{
next_time = SDL_GetTicks() + TICK_INTERVAL;
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
RunningMainGameLoop = false;
break;
}
}
// handle renders...
SDL_Rect drect = {0,0,48,48};
SDL_RenderClear(renderer);
SDL_RenderCopyEx(renderer, pentagramTexture[frameCount], NULL, NULL, 0, NULL, SDL_FLIP_VERTICAL);
SDL_RenderPresent(renderer);
SDL_Delay(time_left());
next_time += TICK_INTERVAL;
frameCount++;
if (frameCount == 8) { frameCount = 0; }
}
for (int idx = 0; idx < 8; idx++) {
SDL_DestroyTexture(pentagramTexture[idx]);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
I got distorted image when try to convert YUV420p to RGB24 using
sws_scale.
Code:
ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
fprintf(stderr, "Error decoding video frame\n");
return ret;
}
if (*got_frame)
{
printf("video_frame%s n:%d coded_n:%d pts:%s\n",
cached ? "(cached)" : "",
video_frame_count++, frame->coded_picture_number,
"#"/*av_ts2timestr(frame->pts, &video_dec_ctx->time_base)*/);
/* copy decoded frame to destination buffer:
* this is required since rawvideo expects non aligned data */
av_image_copy(video_dst_data, video_dst_linesize,
(const uint8_t **)(frame->data), frame->linesize,
video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
/* write to rawvideo file */
fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
AVPicture pic;
avpicture_alloc( &pic, AV_PIX_FMT_RGB24, frame->width, frame->height);
SwsContext *ctxt = sws_getContext(frame->width, frame->height, static_cast<AVPixelFormat>(frame->format),
frame->width, frame->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
if ( NULL == ctxt )
{
//Log("failed to get sws context");
}
if ( 0 < sws_scale(ctxt, frame->data, frame->linesize, 0, frame->height, pic.data, pic.linesize))
{
char szPic[256] = { 0 };
sprintf( szPic, "decoded/%d.bmp", video_frame_count );
FILE *pf = fopen(szPic,"w");
if ( NULL != pf )
{
BITMAPFILEHEADER bmpFileHeader = {0};
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfType = 0x4D42;
bmpFileHeader.bfSize = sizeof(bmpFileHeader) + sizeof(BITMAPINFOHEADER) + pic.linesize[0] * frame->height;
bmpFileHeader.bfOffBits = sizeof(bmpFileHeader) + sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER bmiHeader = { 0 };
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmiHeader.biWidth = frame->width;
bmiHeader.biHeight = 0 - frame->height;
bmiHeader.biPlanes = 1;
bmiHeader.biBitCount = 24;
bmiHeader.biCompression = BI_RGB;
bmiHeader.biSizeImage = pic.linesize[0] * frame->height;
bmiHeader.biXPelsPerMeter = 0;
bmiHeader.biYPelsPerMeter = 0;
bmiHeader.biClrUsed = 0;
bmiHeader.biClrImportant = 0;
fwrite( &bmpFileHeader, 1, sizeof(bmpFileHeader), pf );
fwrite( &bmiHeader, 1, sizeof(bmiHeader), pf );
fwrite( pic.data[0], 1, pic.linesize[0] * frame->height, pf );
fclose( pf );
}
}
// pic.data[0] now contains the image data in RGB format (3 bytes)
// and pic.linesize[0] is the pitch of the data (ie. size of a row in memory, which can be larger than width*sizeof(pixel))
avpicture_free(&pic);
sws_freeContext(ctxt);
}
above only decode frame then convert this from to RGB24, then write a bitmap.
original video frame like this,
but converted image,
is there missing some code or some code is wrong?
thanks in advance.
fwrite( pic.data[0], 1, pic.linesize[0] * frame->height, pf );
For an image of e.g. 1280x720, linesize is typically larger, e.g. 1312, so you'll be writing more data than image size if you write linesize*height. You want to write (in a loop) width pixels offset by linesize bytes:
uint8_t *ptr = pic.data[0];
for (int y = 0; y < frame->height; y++) {
fwrite(ptr, 1, frame->width, pf);
ptr += pic.linesize[0];
}
And then it should work correctly.
maybe these codes can help you. these works good.
int got_frame = 0;
auto len = avcodec_decode_video2(m_avCodecContext
, m_avFrame
, &got_frame
, &avpkt);
if (len < 0)
{
return;
}
if (got_frame /*&& !silentMode*/)
{
//if (videoRenderer != nullptr)
{
if (frameSize == NULL)
{
return;
}
uint8_t *dst_data[4];
int dst_linesize[4];
int dst_w, dst_h;
int ret = 0;
if (1)// avcodec_alloc_frame()
{
auto stride = m_avFrame->linesize;
auto scan0 = m_avFrame->data;
SwsContext *scaleContext = sws_getContext(m_avCodecContext->width
, m_avCodecContext->height
, m_avCodecContext->pix_fmt
, m_avCodecContext->width
, m_avCodecContext->height
, PixelFormat::PIX_FMT_BGR24
, SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (scaleContext == NULL)
{
//TODO: log error
return;
}
try
{
//*vb->signal = 1;
ret = avpicture_alloc(&m_dst_picture
, PixelFormat::PIX_FMT_BGR24
, m_avCodecContext->width
, m_avCodecContext->height);
// AVFrame *picture_RGB;
// uint8_t *bufferRGB;
// picture_RGB = avcodec_alloc_frame();
// bufferRGB = (uint8_t*)malloc(720*576*(24/8)/*avpicture_get_size(PIX_FMT_RGB24, 720, 576)*/);
// avpicture_fill((AVPicture *)picture_RGB, bufferRGB, PIX_FMT_RGB24, 720, 576);
if (ret < 0)
{
return;
}
int retScale = sws_scale(scaleContext
, scan0
, stride
, 0
, m_avCodecContext->height
, m_dst_picture.data //picture_RGB->data
, m_dst_picture.linesize //picture_RGB->linesize
);
if (1)
{
HWND hwnd = m_pParent->GetSafeHwnd();
SetFocus(hwnd);
CRect rc;
m_pParent->GetClientRect(rc);
CDC *cdc = m_pParent->GetDC();
char* bitmap = (char*)m_dst_picture.data[0];
// static unsigned int i = 0;
// bmp_save(bitmap, m_avCodecContext->width, m_avCodecContext->height, i++);
BITMAPINFO bmpinfo;
bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.bmiHeader.biWidth = m_avCodecContext->width;
bmpinfo.bmiHeader.biHeight = -m_avCodecContext->height;
bmpinfo.bmiHeader.biPlanes = 1;
bmpinfo.bmiHeader.biBitCount = 24;
bmpinfo.bmiHeader.biCompression = BI_RGB;
bmpinfo.bmiHeader.biSizeImage =
m_avCodecContext->width * m_avCodecContext->height * (24 / 8);
bmpinfo.bmiHeader.biXPelsPerMeter = 100;
bmpinfo.bmiHeader.biYPelsPerMeter = 100;
bmpinfo.bmiHeader.biClrUsed = 0;
bmpinfo.bmiHeader.biClrImportant = 0;
HBITMAP hBitmap = CreateDIBitmap(cdc->GetSafeHdc(), &bmpinfo.bmiHeader, CBM_INIT, bitmap, &bmpinfo/*bi*/, DIB_RGB_COLORS);
DrawBitmap(cdc, hBitmap, m_pParent);
::DeleteObject(hBitmap);
::DeleteObject(cdc->GetSafeHdc());
}
avpicture_free(&m_dst_picture);
sws_freeContext(scaleContext);
}
catch (int e)
{
sws_freeContext(scaleContext);
}
}
}
}
void DrawBitmap(CDC *pDC, HBITMAP hbitmap,CWnd *wnd)
{
CBitmap *pBitmap = CBitmap::FromHandle(hbitmap);
BITMAP bm;
pBitmap -> GetBitmap(&bm);
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
HGDIOBJ gob= MemDC.SelectObject(pBitmap);
CRect rc;
wnd->GetClientRect(rc);
pDC->SetStretchBltMode( COLORONCOLOR);
pDC->StretchBlt(0, 0,rc.Width(),rc.Height() , &MemDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
MemDC.SelectObject(gob);
DeleteObject(pBitmap);
DeleteObject(MemDC);
DeleteObject(&bm);
ReleaseDC(wnd->GetSafeHwnd(), MemDC);
}
void initDecoder()
{
m_avCodecContext = avcodec_alloc_context();
if (!m_avCodecContext)
{
//failed to allocate codec context
Cleanup();
return;
}
m_avCodecContext->flags = 0;
uint8_t startCode[] = { 0x00, 0x00, 0x01 };
//////////////////////////////////////////////////////////////////////////
//I thought for play live video you can comment these lines.
if (m_sProps != NULL)
{
// USES_CONVERSION;
// ::MessageBox(NULL, A2T(sprops), TEXT("sprops"), MB_OK);
unsigned spropCount;
SPropRecord* spropRecords = parseSPropParameterSets(m_sProps, spropCount);
try
{
for (unsigned i = 0; i < spropCount; ++i)
{
AddExtraData(startCode, sizeof(startCode));
AddExtraData(spropRecords[i].sPropBytes, spropRecords[i].sPropLength);
}
}
catch (void*)
{
//extradata exceeds size limit
delete[] spropRecords;
Cleanup();
return;
}
delete[] spropRecords;
m_avCodecContext->extradata = extraDataBuffer;
m_avCodecContext->extradata_size = extraDataSize;
}
AddExtraData(startCode, sizeof(startCode));
bInitEx = true;
av_register_all();
avcodec_register_all();
m_codecId = CODEC_ID_H264;
m_avCodec = avcodec_find_decoder(m_codecId);
if (m_avCodec == NULL)
{
return;
}
if (avcodec_open(m_avCodecContext, m_avCodec) < 0)
{
//failed to open codec
Cleanup();
return;
}
if (m_avCodecContext->codec_id == CODEC_ID_H264)
{
m_avCodecContext->flags2 |= CODEC_FLAG2_CHUNKS;
//avCodecContext->flags2 |= CODEC_FLAG2_SHOW_ALL;
}
m_avFrame = avcodec_alloc_frame();
if (!m_avFrame)
{
//failed to allocate frame
Cleanup();
return;
}
}
I've adapted this code from another article here on SO. It takes a screenshot of the desktop and writes it to a file named "test.jpg."
I'm interested in saving the JPEG data directly to a buffer to be sent over the network. I'm pretty sure GdipSaveImageToStream is what I need, but I can't figure out how it works. The GpImage parameter is particularly confusing.
I appreciate any help you can provide.
#include "stdafx.h"
#include "windows.h"
#include "gdiplus.h"
using namespace Gdiplus;
using namespace Gdiplus::DllExports;
int GetEncoderClsid(WCHAR *format, CLSID *pClsid)
{
unsigned int num = 0, size = 0;
GetImageEncodersSize(&num, &size);
if(size == 0) return -1;
ImageCodecInfo *pImageCodecInfo = (ImageCodecInfo *)(malloc(size));
if(pImageCodecInfo == NULL) return -1;
GetImageEncoders(num, size, pImageCodecInfo);
for(unsigned int j = 0; j < num; ++j)
{
if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0){
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
int GetScreeny(LPWSTR lpszFilename, ULONG uQuality) // by Napalm
{
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HWND hMyWnd = GetDesktopWindow(); // get my own window
RECT r; // the area we are going to capture
int w, h; // the width and height of the area
HDC dc; // the container for the area
int nBPP;
HDC hdcCapture;
LPBYTE lpCapture;
int nCapture;
int iRes;
CLSID imageCLSID;
Bitmap *pScreenShot;
HGLOBAL hMem;
int result;
// get the area of my application's window
//GetClientRect(hMyWnd, &r);
GetWindowRect(hMyWnd, &r);
dc = GetWindowDC(hMyWnd);// GetDC(hMyWnd) ;
w = r.right - r.left;
h = r.bottom - r.top;
nBPP = GetDeviceCaps(dc, BITSPIXEL);
hdcCapture = CreateCompatibleDC(dc);
// create the buffer for the screenshot
BITMAPINFO bmiCapture = {
sizeof(BITMAPINFOHEADER), w, -h, 1, nBPP, BI_RGB, 0, 0, 0, 0, 0,
};
// create a container and take the screenshot
HBITMAP hbmCapture = CreateDIBSection(dc, &bmiCapture,
DIB_PAL_COLORS, (LPVOID *)&lpCapture, NULL, 0);
// failed to take it
if(!hbmCapture)
{
DeleteDC(hdcCapture);
DeleteDC(dc);
GdiplusShutdown(gdiplusToken);
printf("failed to take the screenshot. err: %d\n", GetLastError());
return 0;
}
// copy the screenshot buffer
nCapture = SaveDC(hdcCapture);
SelectObject(hdcCapture, hbmCapture);
BitBlt(hdcCapture, 0, 0, w, h, dc, 0, 0, SRCCOPY);
RestoreDC(hdcCapture, nCapture);
DeleteDC(hdcCapture);
DeleteDC(dc);
GpImage *bob;
IStream *ssStr;
// save the buffer to a file
pScreenShot = new Bitmap(hbmCapture, (HPALETTE)NULL);
EncoderParameters encoderParams;
encoderParams.Count = 1;
encoderParams.Parameter[0].NumberOfValues = 1;
encoderParams.Parameter[0].Guid = EncoderQuality;
encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
encoderParams.Parameter[0].Value = &uQuality;
GetEncoderClsid(L"image/jpeg", &imageCLSID);
iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok);
delete pScreenShot;
DeleteObject(hbmCapture);
GdiplusShutdown(gdiplusToken);
return iRes;
}
int _tmain(int argc, _TCHAR* argv[])
{
GetScreeny(L"test.jpg", 75);
return 0;
}
Short answer: Use the IStream version of Gdiplus::Image::Save. Use CreateHStreamOnGlobal to make a temporary IStream that you can convert back to a buffer;
Long winded version with code sample.
Replace this line:
iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok);
With this block of code:
// Note: For the sake of brevity and readability, I'm deliberately not checking
// the return value of any of these calls. In production code, you should do diligent error
// checking on each function call. Also, there may be an optimization where you can just
// use the memory of the stream itself (by calling GetHGlobalFromStream and GlobalLock)
// But that's an exercise left to the reader.
{
IStream *pStream = NULL;
LARGE_INTEGER liZero = {};
ULARGE_INTEGER pos = {};
STATSTG stg = {};
ULONG bytesRead=0;
HRESULT hrRet=S_OK;
BYTE* buffer = NULL; // this is your buffer that will hold the jpeg bytes
DWORD dwBufferSize = 0; // this is the size of that buffer;
hrRet = CreateStreamOnHGlobal(NULL, TRUE, &pStream))
hrRet = pScreenShot->Save(pStream, &imageCLSID, &encoderParams) == 0 ? S_OK : E_FAIL;
hrRet = pStream->Seek(liZero, STREAM_SEEK_SET, &pos);
hrRet = pStream->Stat(&stg, STATFLAG_NONAME);
// allocate a byte buffer big enough to hold the jpeg stream in memory
buffer = new BYTE[stg.cbSize.LowPart];
hrRet = (buffer == NULL) ? E_OUTOFMEMORY : S_OK;
dwBufferSize = stg.cbSize.LowPart;
// copy the stream into memory
hrRet = pStream->Read(buffer, stg.cbSize.LowPart, &bytesRead);
// now go save "buffer" and "dwBufferSize" off somewhere. This is the jpeg buffer
// don't forget to free it when you are done
// After success or if any of the above calls fail, don't forget to release the stream
if (pStream)
{
pStream->Release();
}
}
I have created procedures to save window screenshot to file. It works for PNG and BMP, but not for JPG (and GIF).
Here is code for capturing HBITMAP:
HBITMAP Signature::getScreenHBITMAP() {
// get screen rectangle
RECT windowRect;
GetWindowRect(getMainWnd(), &windowRect);
// bitmap dimensions
int bitmap_dx = windowRect.right - windowRect.left;
int bitmap_dy = windowRect.bottom - windowRect.top;
// create bitmap info header
BITMAPINFOHEADER infoHeader;
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = bitmap_dx;
infoHeader.biHeight = bitmap_dy;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
infoHeader.biSizeImage = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
// dibsection information
BITMAPINFO info;
info.bmiHeader = infoHeader;
HDC winDC = GetWindowDC(getMainWnd());
HDC memDC = CreateCompatibleDC(winDC);
BYTE* memory = 0;
HBITMAP bitmap = CreateDIBSection(winDC, &info, DIB_RGB_COLORS, (void**)&memory, 0, 0);
SelectObject(memDC, bitmap);
// Copies screen upside down (as it is already upside down) - if need normal layout, change to BitBlt function call
StretchBlt(memDC, 0, 0, bitmap_dx, bitmap_dy, winDC, 0, bitmap_dy, bitmap_dx, bitmap_dy * -1, SRCCOPY);
DeleteDC(memDC);
ReleaseDC(getMainWnd(), winDC);
return bitmap;
}
And here is code for image saving:
HRESULT Imaging_SaveToFile(HBITMAP handle, LPTSTR filename, LPCTSTR format){
HRESULT res;
res = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if ((res == S_OK) || (res == S_FALSE)) {
IImagingFactory* factory=NULL;
if (CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&factory) == S_OK) {
UINT count;
ImageCodecInfo* imageCodecInfo=NULL;
if (factory->GetInstalledEncoders(&count, &imageCodecInfo) == S_OK) {
// Get the particular encoder to use
LPTSTR formatString;
if (wcscmp(format, L"png") == 0) {
formatString = _T("image/png");
} else if (wcscmp(format, L"jpg") == 0) {
formatString = _T("image/jpeg");
} else if (wcscmp(format, L"gif") == 0) {
formatString = _T("image/gif");
} else if (wcscmp(format, L"bmp") == 0) {
formatString = _T("image/bmp");
} else {
CoUninitialize();
return S_FALSE;
}
CLSID encoderClassId;
if (count == 0) {
CoUninitialize();
return S_FALSE;
}
for(int i=0; i < (int)count; i++) {
if (wcscmp(imageCodecInfo[i].MimeType, formatString) == 0) {
encoderClassId= imageCodecInfo[i].Clsid;
free(imageCodecInfo);
break;
} else {
continue;
}
CoUninitialize();
return S_FALSE;
}
IImageEncoder* imageEncoder=NULL;
if (factory->CreateImageEncoderToFile(&encoderClassId, filename, &imageEncoder) == S_OK) {
IImageSink* imageSink = NULL;
res = imageEncoder->GetEncodeSink(&imageSink);
if (res != S_OK) {
CoUninitialize();
return res;
}
BITMAP bm;
GetObject (handle, sizeof(BITMAP), &bm);
ImageInfo* imageInfo = new ImageInfo();
imageInfo->Width = bm.bmWidth;
imageInfo->Height = bm.bmHeight;
imageInfo->RawDataFormat = IMGFMT_MEMORYBMP; //ImageFormatMemoryBMP;
imageInfo->Flags |= SinkFlagsTopDown | SinkFlagsFullWidth;
// Get pixel format from hBitmap
PixelFormatID pixelFormat;
int numColors = 0;
switch (bm.bmBitsPixel) {
case 1: {
pixelFormat = PixelFormat1bppIndexed;
numColors = 1;
break;
}
case 4: {
pixelFormat = PixelFormat4bppIndexed;
numColors = 16;
break;
}
case 8: {
pixelFormat = PixelFormat8bppIndexed;
numColors = 256;
break;
}
case 24: {
pixelFormat = PixelFormat24bppRGB;
break;
}
default: {
pixelFormat = PixelFormat32bppARGB;
numColors = 3; // according to MSDN 16 and 32 bpp numColors should be 3
break;
}
}
imageInfo->PixelFormat = pixelFormat;
if (pixelFormat == PixelFormat32bppARGB) imageInfo->Flags |= SinkFlagsHasAlpha;
res = imageSink->BeginSink(imageInfo, NULL);
if (res != S_OK) {
CoUninitialize();
return res;
}
ColorPalette* palette = NULL;
if (numColors > 0) {
palette = (ColorPalette*)malloc(sizeof(ColorPalette) + (numColors - 1) * sizeof(ARGB));
palette->Count = numColors;
for (int i=0; i<numColors; i++) {
int rgb = i*64;
int red = rgb & 0x00FF;
int green = (rgb >> 8) & 0x00FF;
int blue = (rgb >> 16) & 0x00FF;
palette->Entries[i] = MAKEARGB(0, red, green, blue);
}
} else {
palette = (ColorPalette*)malloc(sizeof(ColorPalette));
palette->Count = 0;
if (pixelFormat == PixelFormat32bppARGB) palette->Flags = PALFLAG_HASALPHA;
}
res = imageSink->SetPalette(palette);
if (res != S_OK) {
CoUninitialize();
return res;
}
BitmapData* bmData = new BitmapData();
bmData->Height = bm.bmHeight;
bmData->Width = bm.bmWidth;
bmData->Scan0 = bm.bmBits;
bmData->PixelFormat = pixelFormat;
UINT bitsPerLine = imageInfo->Width * bm.bmBitsPixel;
UINT bitAlignment = sizeof(LONG) * 8;
UINT bitStride = bitAlignment * (bitsPerLine / bitAlignment); // The image buffer is always padded to LONG boundaries
if ((bitsPerLine % bitAlignment) != 0) bitStride += bitAlignment; // Add a bit more for the leftover values
bmData->Stride = (bitStride / 8);
RECT rect;
rect.top = 0;
rect.bottom = bm.bmHeight;
rect.left = 0;
rect.right = bm.bmWidth;
res = imageSink->PushPixelData(&rect, bmData, TRUE);
if (res != S_OK) {
CoUninitialize();
return res;
}
res = imageSink->EndSink(S_OK);
if (res != S_OK) {
CoUninitialize();
return res;
}
imageSink->Release();
res = imageEncoder->TerminateEncoder();
if (res != S_OK) {
CoUninitialize();
return res;
}
}
}
}
CoUninitialize();
} else {
return res;
}
return res;
}
I used code from Koders.com as an example and tried to follow MSDN description of image encoding when modified this example.
Found also that others have similar issue, but with no answer:
social.msdn.microsoft.com/Forums/en-US/windowsmobiledev/thread/1c368cc1-cc5b-419e-a7d2-2a39c90ae83d/
groups.google.com/group/microsoft.public.windowsce.embedded.vc/browse_thread/thread/8cd67e16ac29627b/9242e82721c48ace?hl=hu&pli=1
I also found solution which uses GDI+ wrapper:
www.ernzo.com/LibGdiplus.aspx
www.codeproject.com/KB/windows/gdiplusandwinmobile.aspx
But I cannot use this GDI+ lib. Also I do not need whole GDI+. Tried to create similar saving procedure like in this wrapper, but with no success.
EDIT
Here is fixed and working solution (Thanks PhilMY for answer):
HRESULT Imaging_SaveToFile(HBITMAP handle, LPTSTR filename, LPCTSTR format){
HRESULT res;
res = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if ((res == S_OK) || (res == S_FALSE)) {
IImagingFactory* factory=NULL;
if (CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&factory) == S_OK) {
UINT count;
ImageCodecInfo* imageCodecInfo=NULL;
if (factory->GetInstalledEncoders(&count, &imageCodecInfo) == S_OK) {
// Get the particular encoder to use
LPTSTR formatString;
if (wcscmp(format, L"png") == 0) {
formatString = _T("image/png");
} else if (wcscmp(format, L"jpg") == 0) {
formatString = _T("image/jpeg");
} else if (wcscmp(format, L"gif") == 0) {
formatString = _T("image/gif");
} else if (wcscmp(format, L"bmp") == 0) {
formatString = _T("image/bmp");
} else {
CoUninitialize();
return S_FALSE;
}
CLSID encoderClassId;
if (count == 0) {
CoUninitialize();
return S_FALSE;
}
for(int i=0; i < (int)count; i++) {
if (wcscmp(imageCodecInfo[i].MimeType, formatString) == 0) {
encoderClassId= imageCodecInfo[i].Clsid;
free(imageCodecInfo);
break;
} else {
continue;
}
CoUninitialize();
return S_FALSE;
}
IImageEncoder* imageEncoder=NULL;
if (factory->CreateImageEncoderToFile(&encoderClassId, filename, &imageEncoder) == S_OK) {
IImageSink* imageSink = NULL;
res = imageEncoder->GetEncodeSink(&imageSink);
if (res != S_OK) {
CoUninitialize();
return res;
}
BITMAP bm;
GetObject (handle, sizeof(BITMAP), &bm);
PixelFormatID pixelFormat;
switch (bm.bmBitsPixel) {
case 1: {
pixelFormat = PixelFormat1bppIndexed;
break;
}
case 4: {
pixelFormat = PixelFormat4bppIndexed;
break;
}
case 8: {
pixelFormat = PixelFormat8bppIndexed;
break;
}
case 24: {
pixelFormat = PixelFormat24bppRGB;
break;
}
default: {
pixelFormat = PixelFormat32bppARGB;
break;
}
}
BitmapData* bmData = new BitmapData();
bmData->Height = bm.bmHeight;
bmData->Width = bm.bmWidth;
bmData->Scan0 = bm.bmBits;
bmData->PixelFormat = pixelFormat;
UINT bitsPerLine = bm.bmWidth * bm.bmBitsPixel;
UINT bitAlignment = sizeof(LONG) * 8;
UINT bitStride = bitAlignment * (bitsPerLine / bitAlignment); // The image buffer is always padded to LONG boundaries
if ((bitsPerLine % bitAlignment) != 0) bitStride += bitAlignment; // Add a bit more for the leftover values
bmData->Stride = (bitStride / 8);
IBitmapImage* pBitmap;
factory->CreateBitmapFromBuffer(bmData, &pBitmap);
IImage* pImage;
pBitmap->QueryInterface(IID_IImage, (void**)&pImage);
res = pImage->PushIntoSink(imageSink);
if (res != S_OK) {
CoUninitialize();
return res;
}
pBitmap->Release();
pImage->Release();
imageSink->Release();
imageEncoder->TerminateEncoder();
imageEncoder->Release();
}
}
}
CoUninitialize();
} else {
return res;
}
return res;
}
I have some similar code that worked for JPEGs on WinCE 6.0.
The main differences are:
I check ImageCodecInfo::FormatID against ImageFormatJPEG to match an encoder
I call SetEncoderParameters to set ENCODER_QUALITY
I copy the source bitmap into an IBitmapImage then use IImage::PushIntoSink