2009
04.12

ImageShack.us screenshot uploader

Hey,
Today I will explain the steps it took to code the ImageShack.us screen shot uploader. First… Why was it coded? I was wondering, why should I hit Print Screen, go to MS Paint, hit paste, save it as PNG, go to imageshack.us, hit browse, select the file, hit upload, wait for the other page to load, and copy the direct link to the clipboard, then go and delete the unneeded screen shot file? There had to be a shorter way. So I thought, if I made a program to capture the screen, and upload it to imageshack.us, and get the direct link, and copy it to the clipboard, with a single button, EVERYTHING would be easier, faster, simpler. So the first step was to capture the screen shot. So basically, I needed to copy all the pixels of the screen to a bitmap. First thing in my mind was GetPixel. If I did two for loops, to get all the pixels of the screen, into a bitmap, would be OK, but that would be SLOW. Now into actual code:

1. Get the screen DC (Device Context)
For this you use the API GetDC. PSDK says that you need to specify NULL as the hWnd parameter to get the whole screen’s DC.
So: GetDC(NULL);
Next thing was to create a compatible bitmap, with the same Width, Height and bit depth just like the screen DC.
2. Create a compatible bitmap
Now for this we use the CreateCompatibleBitmap:
int nWidth = GetDeviceCaps(HORZRES),
nHeight = GetDeviceCaps(VERTRES);
HBITMAP hbmScreen = CreateCompatibleBitmap(hdc, nWidth, nHeight);

Note that this bitmap is empty, we still need to input the pixels we need.
3. Filling the bitmap
First we need to put the empty bitmap in use in another DC, so we can write to the bitmap through the DC. We could also write to the bitmap directly, but it is more practical this way.

HDC hdcCompatible = CreateCompatibleDC(hdc);
int nSaved = SaveDC(hdcCompatible);
 
SelectObject(hdcCompatible, hbmScreen);
 
//Write to bitmap here...
 
//Cleanup
RestoreDC(hdcCompatible, nSaved);
DeleteDC(hdcCompatible);

Here we have two options (that I can think of):

a. GetPixel method

int i, j;
 
for(i = 0; i < nWidth; i++)
	for(j = 0; j < nHeight; j++);
		SetPixel(hdcCompatible, i, j, GetPixel(hdc, i, j);

b. BitBlt Method (which I chose)

BitBlt(hdcCompatible, // Copy pixels to our bitmap
	0,		// Left -- Top-left corner
	0,		// Top  -- Top-left corner
	nWidth,		// Destination width
	nHeight,	// Destination height
	hdc,		// Source DC
	0,		// Left -- Top-left corner
	0,		// Top  -- Top-left corner
	SRCCOPY);	// "Copies the source rectangle directly to the destination rectangle."

Right, so, method b is faster and shorter.

Now we got a screen shot into a bitmap. Next thing is to save into a file, and upload right? WRONG! The bitmap is far too big and takes much more bandwidth and time to upload. Our choice is to either compress it or convert it to another format. The compressed file is not of much use, because imageshack.us wants a picture file. So we need to convert it. For this I chose the PNG file. I would have chosen JPEG, but I prefer PNG. To save the bitmap as a PNG file I used GDI+. I got GetEncoderClsid from MSDN and used the Bitmap class to save the HBITMAP into a PNG file. Below is the SavePNG function commented.

char SavePNG(char *fName, // File name to save as
	HBITMAP hCap) // Bitmap containing the screenshot
{
	WCHAR *wc = NULL; // Pointer to an UNICODE string
	char nRet = 1; // Return value -> 1 if OK, 0 if error happened
	CLSID image2PNG; // Encoder holder
	Bitmap *scBitmap = NULL; // Bitmap pointer to allocate
 
	if(!hCap || !fName) {	// If file name or bitmap is NULL
		nRet = 0;			// Set return value to 0 (error)
		goto cleanup;		// cleanup & return
	}
 
	wc = (WCHAR*)malloc(sizeof(WCHAR) * (strlen(fName) + 1)); // Allocate an UNICODE string at the length of fName
	scBitmap = new Bitmap(hCap, (HPALETTE)NULL); // Allocate a bitmap containing the bits of hCap
	if(!scBitmap || !wc) { // If the bitmap or the UNICODE string couldn't be allocated
		nRet = 0;			// Set the return value to 0 (error)
		goto cleanup;		// cleanup & return
	}
	MultiByteToWideChar(CP_ACP, MB_COMPOSITE,
		fName, -1, wc, strlen(fName) + 1); 		//Convert the ANSI string to UNICODE and store it at wc
 
	GetEncoderClsid(L"image/png", &image2PNG);		// Get encoder for the PNG format ("image/png" is the PNG MIME)
	if(scBitmap->Save(wc, &image2PNG, NULL) != Ok)	// If the file could not be saved
		nRet = 0;									// Set the return value to 0 (error)
cleanup:
	if(scBitmap) delete scBitmap;	// If scBitmap is not NULL, free the allocated memory
	if(wc) free(wc);				// If wc is not NULL free the allocated memory
	return nRet;					// Return
}

(CodeBox plugin seems to mess comments up, sorry)

Next thing is to upload it at imageshack.us.
I needed to figure out how to upload it. Since I’ve been messing with HTTP a lot in the past, it wasn’t a big problem, it took me about five minutes.
I fired up wireshark (it’s a great application, get it if you haven’t already), and tried uploading multiple times. It seemed to send a POST request to “load.imageshack.us”.
It looks like this:
POST / HTTP/1.1<CRLF>
Host: load.imageshack.us<CRLF>
Content-type: multipart/form-data, boundary=H41N1GZZ<CRLF>
Content-Length: N<CRLF><CRLF>

(N stands for the length after )
Since the content type is “multipart/form-data”, it means it has a boundary, which is a unique indicator, that signals the beginning and the end of the file’s/request’s content. The beginning of a boundary has always two dashes before the unique string (which was specified by me as H41N1GZZ :p), so: --H41N1GZZ. You can have one beginning for each form data, and you can send multiple form data in a POST request. you just need to put another boundary beginning before each form data. So basically, there are multiple beginnings and a single end. The end contains two dashes before the unique string and two after it, so: --H41N1GZZ--.
Every form data has it’s own headers. In this case I tested and we needed only one form data, the file contents:


--H41N1GZZ
Content-Disposition: form-data; name="fileupload"; filename="randomfilename.png"<CRLF>
Content-Type: application/octet-stream<CRLF><CRLF>
<PNG binary data here>
<CRLF>--H41N1GZZ--<CRLF>

It was a little confusing calculating the exact length, but I got around it. Here is the code to upload the file: (Thanks to Uranium-239 which suggested to receive the data until the needed data has been received, before I was just receiving once, because I only needed the headers, but just in case that the headers size changed, I implemented this)

char *uploadFile(char *server, u_short port, char *fName)
{
	FILE *f;
	char *buf = NULL, *request = NULL, *bcp = NULL, dataFound = 0;
	long fSize;
	size_t fNameLen, reqLen = 0, bufLen, bufSiz = 1024;
	SOCKET sock = INVALID_SOCKET;
 
	if(!server || !fName)
		return NULL;
	if((f = fopen(fName, "rb")) == NULL)
		goto cleanup;
	fseek(f, 0, SEEK_END);
	if((fSize = ftell(f)) &lt;= 0)
		goto cleanup;
	rewind(f);
	if((buf = (char *)malloc(fSize)) == NULL)
		goto cleanup;
	if((signed)fread(buf, 1, fSize, f) != fSize)
		goto cleanup;
	if((request = (char *)malloc((fNameLen = strlen(fName)) + 135 + fSize)) == NULL)
		goto cleanup;
	sprintf(request, "--H41N1GZZ\r\n"
		"Content-Disposition: form-data; name=\"fileupload\"; filename=\"%s\"\r\n"
		"Content-Type: application/octet-stream\r\n\r\n", fName);
	reqLen += strlen(request);
	memcpy(request + reqLen, buf, fSize);
	reqLen += fSize;
	free(buf);
	sprintf(request + reqLen, "\r\n--H41N1GZZ--\r\n");
	reqLen += 14;
	if((sock = ConnectStream(server, port)) == INVALID_SOCKET) {
		buf = NULL;
		goto cleanup;
	}
	if((buf = (char *)malloc(bufSiz)) == NULL)
		goto cleanup;
	sprintf(buf, "POST / HTTP/1.1\r\n"
		"Host: load.imageshack.us\r\n"
		"Content-type: multipart/form-data, boundary=H41N1GZZ\r\n"
		"Content-Length: %d\r\n\r\n", reqLen);
	if(send(sock, buf, bufLen = strlen(buf), 0) != (signed)bufLen) {
		free(buf);
		buf = NULL;
		goto cleanup;
	}
	if(send(sock, request, reqLen, 0) != (signed)reqLen) {
		free(buf);
		buf = NULL;
		goto cleanup;
	}
	while(1) {
		char *tmp;
 
		memset(buf, 0, bufSiz);
		if(recv(sock, buf + bufSiz - 1024, 1024, 0) &lt;= 0)
			break;
		if(!bcp) {
			if(bcp = strstr(buf, "location: ")) {
				if(strstr(bcp, "\r\n")) {
					dataFound = 1;
					break;
				}
			}
		}
		bufSiz += 1024;
		if((tmp = (char *)realloc(buf, bufSiz)) == NULL) {
			free(buf);
			buf = NULL;
			break;
		}
		buf = tmp;
	}
cleanup:
	if(sock != INVALID_SOCKET) closesocket(sock);
	if(f) fclose(f);
	if(request) free(request);
	if(!dataFound) {
		if(buf) free(buf);
		return NULL;
	}
 
	return buf;
}

“location: ” is what we need. I analyzed the headers and were able to extract the direct link out of them.
Here is the function to that:

char *getUploadLink(char *fName)
{
	char *buf = uploadFile("load.imageshack.us", 80, fName), *bcp;
	char *ret;
 
	if(!buf)
		return NULL;
	if((bcp = strstr(buf, "location: http://")) == NULL) {
		free(buf);
		return NULL;
	}
	bcp += 17;
	if((ret = (char *)calloc(1, strchr(bcp, '\r') - bcp)) == NULL) {
		free(buf);
		return NULL;
	}
	sprintf(ret, "http://");
	strncpy(ret + 7, bcp, strchr(bcp, '/') - bcp + 1);
	if((bcp = strstr(buf, "l=")) == NULL) {
		free(buf);
		return NULL;
	}
	bcp += 2;
	strncpy(ret + strlen(ret), bcp, strchr(bcp, '\r') - bcp);
	free(buf);
	return ret;
}

Next thing, I needed it to run on the background. I thought of making a message-only window, which creates a system tray icon, and communicates with it. Then I needed to make the process automatic when the Print Screen key is pressed. I used to do this with GetAsyncKeyState, but then Napalm told me to use RegisterHotKey/UnregisterHotKey (Thanks Napalm!). Here is the window procedure:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static NOTIFYICONDATA nid;
 
	switch(uMsg)
	{
		case WM_CREATE:
			{
				HICON hIcon = LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_ICON1));
				if(!hIcon)
					return 1;
				RegisterHotKey(hWnd, 0, 0, VK_SNAPSHOT);
				RegisterHotKey(hWnd, 1, MOD_CONTROL, VK_SNAPSHOT);
				memset(&amp;nid, 0, sizeof(NOTIFYICONDATA));
				nid.cbSize				= sizeof(NOTIFYICONDATA);
				nid.hWnd				= hWnd;
				nid.uID					= 0;
				nid.uFlags				= NIF_ICON | NIF_MESSAGE | NIF_INFO;
				nid.uCallbackMessage	= WM_USER;
				nid.hIcon				= hIcon;
				nid.uTimeout			= 10000;
				nid.dwInfoFlags			= NIIF_USER;
				Shell_NotifyIcon(NIM_ADD, &amp;nid);
				DestroyIcon(hIcon);
			}
			break;
		case WM_USER:
			if(lParam == WM_LBUTTONDBLCLK)
				SendMessage(hWnd, WM_CLOSE, 0, 0);
			return 0;
		case WM_HOTKEY:
			switch(wParam)
			{
				case 0:
					{
						char *link, tmpFileName[MAX_PATH + 1];
						HBITMAP hbmCap = GetScreenshot(NULL, 0, 0,
							GetSystemMetrics(SM_CXSCREEN),
							GetSystemMetrics(SM_CYSCREEN));
 
						if(!GenerateTempFileName(tmpFileName))
							break;
						SendMessage(hWnd, WM_USER + 2, 0, 0);
						SavePNG(tmpFileName, hbmCap);
						link = getUploadLink(tmpFileName);
						DeleteFile(tmpFileName);
						DeleteObject(hbmCap);
						if(link) {
							SetClipboard(link);
							SendMessage(hWnd, WM_USER + 1, 0, (LPARAM)link);
						}
					}
					break;
				case 1:
					SelectHBM();
					break;
			}
			break;
		case WM_USER + 1:
			strcpy(nid.szInfoTitle, "Link copied to clipboard!");
			strcpy(nid.szInfo, (char *)lParam);
			Shell_NotifyIcon(NIM_MODIFY, &amp;nid);
			free((char *)lParam);
			return 0;
		case WM_USER + 2:
			nid.szInfoTitle[0] = '\0';
			strcpy(nid.szInfo, "Uploading screenshot...");
			Shell_NotifyIcon(NIM_MODIFY, &amp;nid);
			return 0;
		case WM_DESTROY:
			Shell_NotifyIcon(NIM_DELETE, &amp;nid);
			UnregisterHotKey(hWnd, 0);
			return 0;
		case WM_CLOSE:
			PostQuitMessage(0);
			return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

After that, Unknown suggested that I implemented a function to upload only a part of the screen. (Thanks Unknown!)
So I thought: I must draw some sort of rectangle when left mouse button is held. If I just did it directly at the screen DC, not only flickering would happen, but the screen would change too. So I did this: I made another window with the size of the screen, got the screen shot, copied it in the window, made the window visible, and made it topmost. Then I implemented the rectangle. Flickering would happen, but there was another problem: The user could change the window with Alt-Tab. To fix this I created another temporary desktop and did the work there. The flickering would happen because in WM_PAINT, I was BitBlt-ing the screen shot, then drawing the rectangle afterwards. I fixed it with yet another DC.
Well, this was all. I hope you liked it.
Press Print Screen to capture all the screen.
Press Ctrl + Print Screen to select a part of the screen to capture.
You can download the full project here: http://www.x-n2o.com/pub/imageshackus_uploader.zip
Here is the full code:

// ImageShack.us screenshot uploader
// by X-N2O
// 10.04.2009
// main.cpp
 
#undef UNICODE
#define _WIN32_WINNT	0x501
#define WINVER			0x501
#define _WIN32_IE		0x600
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "gdiplus.lib")
#include <winsock2.h>
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <ObjBase.h>
#include <GdiPlus.h>
#include <GdiPlusEnums.h>
#include "resource.h"
 
#define SECONDARY_DESKTOP
 
using namespace Gdiplus;
 
HINSTANCE g_hInst;
HBITMAP g_hbmSelected = NULL;
HWND g_hWnd;
char g_cWait;
 
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
	UINT num = 0, size = 0, j;
	ImageCodecInfo* pImageCodecInfo = NULL;
 
	GetImageEncodersSize(&num, &size);
	if(!size) return -1;
	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if(pImageCodecInfo == NULL) return -1;
	GetImageEncoders(num, size, pImageCodecInfo);
 
	for(j = 0; j < num; j++) {
		if(!wcscmp(pImageCodecInfo[j].MimeType, format)) {
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;
		}
	}
 
	free(pImageCodecInfo);
	return -1;
}
 
HBITMAP GetScreenshot(HWND hWnd, int x, int y, int cx, int cy)
{
	HDC hdc = GetDC(hWnd), hdcCompatible = CreateCompatibleDC(hdc);
	HBITMAP hbmRet = CreateCompatibleBitmap(hdc, cx, cy);
	HBITMAP hbmOld = (HBITMAP)SelectObject(hdcCompatible, hbmRet);
 
	BitBlt(hdcCompatible, 0, 0, GetDeviceCaps(hdc, HORZRES),
		GetDeviceCaps(hdc, VERTRES), hdc, x, y, SRCCOPY);
 
	SelectObject(hdcCompatible, hbmOld);
	DeleteDC(hdcCompatible);
	ReleaseDC(hWnd, hdc);
	return hbmRet;
}
 
char SavePNG(char *fName, HBITMAP hCap)
{
	WCHAR *wc = NULL;
	char nRet = 1;
	CLSID image2PNG;
	Bitmap *scBitmap = NULL;
 
	if(!hCap || !fName) {
		nRet = 0;
		goto cleanup;
	}
 
	wc = (WCHAR*)malloc(sizeof(WCHAR) * (strlen(fName) + 1));
	scBitmap = new Bitmap(hCap, (HPALETTE)NULL);
	if(!scBitmap || !wc) {
		nRet = 0;
		goto cleanup;
	}
	MultiByteToWideChar(CP_ACP, MB_COMPOSITE, fName, -1, wc, strlen(fName) + 1);
 
	GetEncoderClsid(L"image/png", &image2PNG);
	if(scBitmap->Save(wc, &image2PNG, NULL) != Ok)
		nRet = 0;
cleanup:
	if(scBitmap) delete scBitmap;
	if(wc) free(wc);
	return nRet;
}
 
SOCKET ConnectStream(char *server, u_short port)
{
	HOSTENT *host;
	SOCKET ret = INVALID_SOCKET;
	SOCKADDR_IN sin;
	size_t sinLen;
 
	if(server == NULL)
		return INVALID_SOCKET;
	if((host = gethostbyname(server)) == NULL)
		return INVALID_SOCKET;
	if((ret = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR)
		return INVALID_SOCKET;
	sinLen = sizeof(SOCKADDR_IN);
	memset(&sin, 0, sinLen);
	sin.sin_family		= AF_INET;
	sin.sin_addr.s_addr	= ((IN_ADDR*)host->h_addr)->s_addr;
	sin.sin_port		= htons(port);
 
	if(connect(ret, (SOCKADDR*)&sin, sinLen) == SOCKET_ERROR) {
		closesocket(ret);
		return INVALID_SOCKET;
	}
	return ret;
}
 
char *uploadFile(char *server, u_short port, char *fName)
{
	FILE *f;
	char *buf = NULL, *request = NULL, *bcp = NULL, dataFound = 0;
	long fSize;
	size_t fNameLen, reqLen = 0, bufLen, bufSiz = 1024;
	SOCKET sock = INVALID_SOCKET;
 
	if(!server || !fName)
		return NULL;
	if((f = fopen(fName, "rb")) == NULL)
		goto cleanup;
	fseek(f, 0, SEEK_END);
	if((fSize = ftell(f)) <= 0)
		goto cleanup;
	rewind(f);
	if((buf = (char *)malloc(fSize)) == NULL)
		goto cleanup;
	if((signed)fread(buf, 1, fSize, f) != fSize)
		goto cleanup;
	if((request = (char *)malloc((fNameLen = strlen(fName)) + 135 + fSize)) == NULL)
		goto cleanup;
	sprintf(request, "--H41N1GZZ\r\n"
		"Content-Disposition: form-data; name=\"fileupload\"; filename=\"%s\"\r\n"
		"Content-Type: application/octet-stream\r\n\r\n", fName);
	reqLen += strlen(request);
	memcpy(request + reqLen, buf, fSize);
	reqLen += fSize;
	free(buf);
	sprintf(request + reqLen, "\r\n--H41N1GZZ--\r\n");
	reqLen += 14;
	if((sock = ConnectStream(server, port)) == INVALID_SOCKET) {
		buf = NULL;
		goto cleanup;
	}
	if((buf = (char *)malloc(bufSiz)) == NULL)
		goto cleanup;
	sprintf(buf, "POST / HTTP/1.1\r\n"
		"Host: load.imageshack.us\r\n"
		"Content-type: multipart/form-data, boundary=H41N1GZZ\r\n"
		"Content-Length: %d\r\n\r\n", reqLen);
	if(send(sock, buf, bufLen = strlen(buf), 0) != (signed)bufLen) {
		free(buf);
		buf = NULL;
		goto cleanup;
	}
	if(send(sock, request, reqLen, 0) != (signed)reqLen) {
		free(buf);
		buf = NULL;
		goto cleanup;
	}
	while(1) {
		char *tmp;
 
		memset(buf, 0, bufSiz);
		if(recv(sock, buf + bufSiz - 1024, 1024, 0) <= 0)
			break;
		if(!bcp) {
			if(bcp = strstr(buf, "location: ")) {
				if(strstr(bcp, "\r\n")) {
					dataFound = 1;
					break;
				}
			}
		}
		bufSiz += 1024;
		if((tmp = (char *)realloc(buf, bufSiz)) == NULL) {
			free(buf);
			buf = NULL;
			break;
		}
		buf = tmp;
	}
cleanup:
	if(sock != INVALID_SOCKET) closesocket(sock);
	if(f) fclose(f);
	if(request) free(request);
	if(!dataFound) {
		if(buf) free(buf);
		return NULL;
	}
 
	return buf;
}
 
char *getUploadLink(char *fName)
{
	char *buf = uploadFile("load.imageshack.us", 80, fName), *bcp;
	char *ret;
 
	if(!buf)
		return NULL;
	if((bcp = strstr(buf, "location: http://")) == NULL) {
		free(buf);
		return NULL;
	}
	bcp += 17;
	if((ret = (char *)calloc(1, strchr(bcp, '\r') - bcp)) == NULL) {
		free(buf);
		return NULL;
	}
	sprintf(ret, "http://");
	strncpy(ret + 7, bcp, strchr(bcp, '/') - bcp + 1);
	if((bcp = strstr(buf, "l=")) == NULL) {
		free(buf);
		return NULL;
	}
	bcp += 2;
	strncpy(ret + strlen(ret), bcp, strchr(bcp, '\r') - bcp);
	free(buf);
	return ret;
}
 
BOOL GenerateTempFileName(char *buf)
{
	char tmpPath[MAX_PATH - 13];
 
	if(!GetTempPath(MAX_PATH - 14, tmpPath))
		return FALSE;
	return (GetTempFileName(tmpPath, "PNG", 0, buf) ? TRUE : FALSE);
}
 
char SetClipboard(char *buf)
{
	HGLOBAL hGlobal;
	char *szLocked;
	size_t bufLen;
 
	if(!buf)
		return 1;
	if(!OpenClipboard(NULL))
		return 1;
	EmptyClipboard();
 
	hGlobal = GlobalAlloc(GMEM_MOVEABLE, (bufLen = strlen(buf))+1);
	if(!hGlobal)
		return 1;
	szLocked = (char *)GlobalLock(hGlobal);
	if(!szLocked) {
		GlobalFree(hGlobal);
		return 1;
	}
	memcpy(szLocked, buf, bufLen);
	GlobalUnlock(hGlobal);
	SetClipboardData(CF_TEXT, hGlobal);
	CloseClipboard();
	GlobalFree(hGlobal);
	return 0;
}
 
LRESULT CALLBACK SHBMWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HBITMAP hbm = NULL;
	static POINT ptStart, ptEnd;
	static char mouseDown = 0;
	static HBITMAP hbmCopy = NULL;
	static HBRUSH hBrush = NULL;
	static HPEN hPen = NULL;
 
	switch(uMsg)
	{
		case WM_CREATE:
			RegisterHotKey(hWnd, 2, 0, VK_ESCAPE);
			RegisterHotKey(hWnd, 3, 0, VK_RETURN);
			memset(&ptStart, 0, sizeof(ptStart));
			memset(&ptEnd, 0, sizeof(ptEnd));
			SetWindowLong(hWnd, GWL_STYLE,
				GetWindowLong(hWnd, GWL_STYLE) & ~WS_CAPTION);
 			SetWindowPos(hWnd, NULL, 0, 0, GetSystemMetrics(SM_CXSCREEN),
 				GetSystemMetrics(SM_CYSCREEN), SWP_NOZORDER);
			ShowWindow(hWnd, 1);
			return 0;
		case WM_LBUTTONDOWN:
			GetCursorPos(&ptStart);
			mouseDown = 1;
			InvalidateRect(hWnd, NULL, FALSE);
			return 0;
		case WM_LBUTTONUP:
			GetCursorPos(&ptEnd);
			mouseDown = 0;
			InvalidateRect(hWnd, NULL, FALSE);
			return 0;
		case WM_MOUSEMOVE:
			InvalidateRect(hWnd, NULL, FALSE);
			return 0;
		case WM_PAINT:
			{
				PAINTSTRUCT ps;
 
				if(BeginPaint(hWnd, &ps)) {
					HDC hdc = NULL, hdc2 = NULL;
					RECT rc;
					int nSaved, nSaved2;
 
					hdc = CreateCompatibleDC(ps.hdc);
					nSaved = SaveDC(hdc2);
					if(!hPen)
						hPen = CreatePen(PS_SOLID, 2, 0x000000FF);
					if(!hbm)
						hbm = (HBITMAP)GetWindowLong(hWnd, GWL_USERDATA);
 					SelectObject(hdc, hbm);
					if(!hBrush)
						hBrush = CreatePatternBrush(hbm);
					hdc2 = CreateCompatibleDC(hdc);
					nSaved2 = SaveDC(hdc2);
					if(!hbmCopy) {
						hbmCopy = CreateCompatibleBitmap(hdc, 
							GetDeviceCaps(hdc, HORZRES), 
							GetDeviceCaps(hdc, VERTRES));
					}
					SelectObject(hdc2, hbmCopy);
					SelectObject(hdc2, hBrush);
					SelectObject(hdc2, hPen);
					BitBlt(hdc2, 0, 0,
						GetDeviceCaps(hdc, HORZRES),
						GetDeviceCaps(hdc, VERTRES), hdc, 0, 0, SRCCOPY);
					if(mouseDown) {
						POINT ptTmp;
 
						GetCursorPos(&ptTmp);
						rc.left = (ptTmp.x < ptStart.x ? ptTmp.x : ptStart.x) - 2;
						rc.top = (ptTmp.y < ptStart.y ? ptTmp.y : ptStart.y) - 2;
						rc.right = (ptTmp.x > ptStart.x ? ptTmp.x : ptStart.x) + 2;
						rc.bottom = (ptTmp.y > ptStart.y ? ptTmp.y : ptStart.y) + 2;
					} else {
						rc.left = (ptEnd.x < ptStart.x ? ptEnd.x : ptStart.x) - 2;
						rc.top = (ptEnd.y < ptStart.y ? ptEnd.y : ptStart.y) - 2;
						rc.right = (ptEnd.x > ptStart.x ? ptEnd.x : ptStart.x) + 2;
						rc.bottom = (ptEnd.y > ptStart.y ? ptEnd.y : ptStart.y) + 2;
					}
					Rectangle(hdc2, rc.left, rc.top, rc.right, rc.bottom);
					BitBlt(ps.hdc, 0, 0,
						GetDeviceCaps(hdc, HORZRES),
						GetDeviceCaps(hdc, VERTRES), hdc2, 0, 0, SRCCOPY);
					RestoreDC(hdc, nSaved);
					RestoreDC(hdc2, nSaved2);
 					DeleteDC(hdc);
					DeleteDC(hdc2);
					EndPaint(hWnd, &ps);
				}
			}
			return 0;
		case WM_HOTKEY:
			switch(wParam)
			{
				case 2:
					SendMessage(hWnd, WM_CLOSE, 0, 0);
					break;
				case 3:
					{
						RECT rc;
 
						if(ptStart.x == ptEnd.x || ptStart.y == ptEnd.y)
							break;
						rc.left = ptEnd.x < ptStart.x ? ptEnd.x : ptStart.x;
						rc.top = ptEnd.y < ptStart.y ? ptEnd.y : ptStart.y;
						rc.right = ptEnd.x > ptStart.x ? ptEnd.x : ptStart.x;
						rc.bottom = ptEnd.y > ptStart.y ? ptEnd.y : ptStart.y;
						g_hbmSelected = GetScreenshot(hWnd, rc.left, rc.top,
							rc.right - rc.left, rc.bottom - rc.top);
						SendMessage(hWnd, WM_CLOSE, 0, 0);
					}
					break;
			}
			return 0;
		case WM_CLOSE:
			if(hPen) DeleteObject(hPen);
			if(hBrush) DeleteObject(hBrush);
			if(hbmCopy) DeleteObject(hbmCopy);
			hPen = NULL;
			hbmCopy = NULL;
			hBrush = NULL;
			hbm = NULL;
			UnregisterHotKey(hWnd, 2);
			UnregisterHotKey(hWnd, 3);
			PostQuitMessage(0);
			return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
char InitSelectHBM()
{
	WNDCLASSEX myCls;
 
	memset(&myCls, 0, sizeof(myCls));
    myCls.cbSize        = sizeof(WNDCLASSEX);
    myCls.style         = 0;
    myCls.lpfnWndProc   = SHBMWindowProc;
    myCls.cbClsExtra    = 0;
    myCls.cbWndExtra    = 0;
    myCls.hInstance     = g_hInst;
    myCls.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    myCls.hCursor       = LoadCursor(NULL, IDC_ARROW);
    myCls.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    myCls.lpszMenuName  = NULL;
    myCls.lpszClassName = "SLCTHBM";
    myCls.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
	return RegisterClassEx(&myCls) ? 1 : 0;
}
 
DWORD WINAPI SelectHBMThread(LPVOID lpVoid)
{
	MSG msg;
	HWND hWnd;
	HBITMAP hbm = GetScreenshot(NULL, 0, 0,
		GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
	char *link, tmpFileName[MAX_PATH + 1];
#ifdef SECONDARY_DESKTOP
	HDESK hCurDesk = GetThreadDesktop(GetCurrentThreadId()), hTmpDesk;
	if(!hCurDesk) return 1;
	hTmpDesk = CreateDesktop("Temp__Desktop", NULL, NULL, 0,
		GENERIC_ALL, NULL);
	if(!hTmpDesk) return 1;
	if(!SetThreadDesktop(hTmpDesk)) {
		CloseDesktop(hTmpDesk);
		return 1;
	}
	if(!SwitchDesktop(hTmpDesk)) {
		SetThreadDesktop(hCurDesk);
		CloseDesktop(hTmpDesk);
		return 1;
	}
#endif
	hWnd = CreateWindowEx(0, "SLCTHBM", "  ", WS_OVERLAPPED, 0, 0, 0, 0,
		HWND_DESKTOP, (HMENU)0, g_hInst, NULL);
	if(!hWnd)
		return 1;
	SetWindowLong(hWnd, GWL_USERDATA, (LONG)hbm);
 
	while(GetMessage(&msg, hWnd, 0, 0) > 0)
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
#ifdef SECONDARY_DESKTOP
	SetThreadDesktop(hCurDesk);
	SwitchDesktop(hCurDesk);
	CloseDesktop(hTmpDesk);
#endif
	DeleteObject(hbm);
	g_cWait = 0;
	if(g_hbmSelected) {
		if(!GenerateTempFileName(tmpFileName))
			return 1;
		SendMessage(g_hWnd, WM_USER + 2, 0, 0);
		SavePNG(tmpFileName, g_hbmSelected);
		DeleteObject(g_hbmSelected);
		g_hbmSelected = NULL;
		link = getUploadLink(tmpFileName);
		DeleteFile(tmpFileName);
		if(link) {
			SetClipboard(link);
			SendMessage(g_hWnd, WM_USER + 1, 0, (LPARAM)link);
		}
	}
 
	return 0;
}
 
void SelectHBM()
{
	HANDLE hThread = CreateThread(NULL, 0,
		(LPTHREAD_START_ROUTINE)SelectHBMThread, 0, 0, NULL);
	if(!hThread) return;
	g_cWait = 1;
	while(g_cWait)
		WaitForSingleObject(hThread, 10);
	CloseHandle(hThread);
}
 
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static NOTIFYICONDATA nid;
 
	switch(uMsg)
	{
		case WM_CREATE:
			{
				HICON hIcon = LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_ICON1));
				if(!hIcon)
					return 1;
				RegisterHotKey(hWnd, 0, 0, VK_SNAPSHOT);
				RegisterHotKey(hWnd, 1, MOD_CONTROL, VK_SNAPSHOT);
				memset(&nid, 0, sizeof(NOTIFYICONDATA));
				nid.cbSize				= sizeof(NOTIFYICONDATA);
				nid.hWnd				= hWnd;
				nid.uID					= 0;
				nid.uFlags				= NIF_ICON | NIF_MESSAGE | NIF_INFO;
				nid.uCallbackMessage	= WM_USER;
				nid.hIcon				= hIcon;
				nid.uTimeout			= 10000;
				nid.dwInfoFlags			= NIIF_USER;
				Shell_NotifyIcon(NIM_ADD, &nid);
				DestroyIcon(hIcon);
			}
			break;
		case WM_USER:
			if(lParam == WM_LBUTTONDBLCLK)
				SendMessage(hWnd, WM_CLOSE, 0, 0);
			return 0;
		case WM_HOTKEY:
			switch(wParam)
			{
				case 0:
					{
						char *link, tmpFileName[MAX_PATH + 1];
						HBITMAP hbmCap = GetScreenshot(NULL, 0, 0,
							GetSystemMetrics(SM_CXSCREEN),
							GetSystemMetrics(SM_CYSCREEN));
 
						if(!GenerateTempFileName(tmpFileName))
							break;
						SendMessage(hWnd, WM_USER + 2, 0, 0);
						SavePNG(tmpFileName, hbmCap);
						link = getUploadLink(tmpFileName);
						DeleteFile(tmpFileName);
						DeleteObject(hbmCap);
						if(link) {
							SetClipboard(link);
							SendMessage(hWnd, WM_USER + 1, 0, (LPARAM)link);				
						}
					}
					break;
				case 1:
					SelectHBM();
					break;
			}
			break;
		case WM_USER + 1:
			strcpy(nid.szInfoTitle, "Link copied to clipboard!");
			strcpy(nid.szInfo, (char *)lParam);
			Shell_NotifyIcon(NIM_MODIFY, &nid);
			free((char *)lParam);
			return 0;
		case WM_USER + 2:
			nid.szInfoTitle[0] = '\0';
			strcpy(nid.szInfo, "Uploading screenshot...");
			Shell_NotifyIcon(NIM_MODIFY, &nid);
			return 0;
		case WM_DESTROY:
			Shell_NotifyIcon(NIM_DELETE, &nid);
			UnregisterHotKey(hWnd, 0);
			return 0;
		case WM_CLOSE:
			PostQuitMessage(0);
			return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	WSADATA wsd;
	SOCKET sock = INVALID_SOCKET;
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	HWND hWnd;
	MSG msg;
	WNDCLASSEX myCls;
 
	if(GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Ok)
		return 1;
	if(WSAStartup(0x202, &wsd)) {
		GdiplusShutdown(gdiplusToken);
		return 1;
	}
	InitCommonControls();
	g_hInst = hInstance;
	memset(&myCls, 0, sizeof(myCls));
	myCls.cbSize        = sizeof(WNDCLASSEX);
	myCls.lpszClassName = "IMGHCKU";
	myCls.hInstance     = g_hInst;
	myCls.lpfnWndProc   = WindowProc;
	if(!RegisterClassEx(&myCls) || !InitSelectHBM()) {
		WSACleanup();
		GdiplusShutdown(gdiplusToken);
		return 1;
	}
 
	g_hWnd = hWnd = CreateWindowEx(0, "IMGHCKU", NULL, 0, 0, 0, 0, 0,
		HWND_MESSAGE, (HMENU)0, g_hInst, NULL);
	if(!hWnd) {
		WSACleanup();
		GdiplusShutdown(gdiplusToken);
		return 1;
	}
 
	while(GetMessage(&msg, hWnd, 0, 0) > 0)
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	WSACleanup();
	GdiplusShutdown(gdiplusToken);
	return (int)msg.wParam;
}

2 comments so far

Add Your Comment
  1. Thanks for the useful info. It’s so interesting

  2. I haven’t tested your code, but the idea is nice. I use a program called Jing (link: http://www.jingproject.com/) for this purpose. But it’s .NET, which is blah.

    I had to do something with images recently and also went for GDI+ at first, but quickly found that it’s disturbingly slow. Though I suppose it doesn’t make a difference for something like this where you’re not doing lots of redrawing.

    Anyways, good job.