Skip to content

Instantly share code, notes, and snippets.

@ohaval
Last active April 22, 2023 09:03
Show Gist options
  • Save ohaval/f5182a14a8c65cc40d1c4a400bb02389 to your computer and use it in GitHub Desktop.
Save ohaval/f5182a14a8c65cc40d1c4a400bb02389 to your computer and use it in GitHub Desktop.
Save an image from the clipboard to a file
/*
I wanted to play with the clipboard via Windows API so I wrote a small program that
checks if the clipboard contains an image, and if so saves the image as a .bmp file.
This program obtains the handle on the clipboard data, put together all of the
information and raw data a .bmp file holds, and writes it to the disk.
With small changes it's possible to make this code run in a loop and save an image
from the clipboard every X seconds.
It's useful to read Microsoft's documentation (for developers) on the clipboard:
https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard
*/
#include <iostream>
#include <fstream>
#include <Windows.h>
LPSTR pFilename = NULL;
int WriteBitmapFile(PBITMAPFILEHEADER pFileHeader, PBITMAPINFOHEADER pInfoHeader, PBYTE pImageData, DWORD dwImageDataSize) {
/*
The format of a .bmp file is:
1) File header
2) Info header
3) Raw bytes (representing the pixels)
(https://en.wikipedia.org/wiki/BMP_file_format)
*/
std::ofstream fout;
fout.open(pFilename, std::ios::out | std::ios::binary);
if (!fout)
{
printf("ofstream::open failed (err %d)\n", GetLastError());
return 1;
}
fout.write((char*)(pFileHeader), sizeof(BITMAPFILEHEADER));
fout.write((char*)(pInfoHeader), sizeof(BITMAPINFOHEADER));
fout.write((char*)(pImageData), dwImageDataSize);
fout.close();
return 0;
}
int SaveFromBitmapInfo(PBITMAPINFO pBitmapInfo) {
// This program currently doesn't support other types of compression
if (pBitmapInfo->bmiHeader.biCompression != BI_RGB) {
printf("[!] Bitmap compression is different from BI_RGB\n");
return 0;
}
BITMAPFILEHEADER bmfh = { 0 };
bmfh.bfType = 0x4D42; // BMP magic
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // 14 and 40 bytes respectively
bmfh.bfSize = bmfh.bfOffBits +
pBitmapInfo->bmiHeader.biWidth * pBitmapInfo->bmiHeader.biHeight * pBitmapInfo->bmiHeader.biBitCount / 8;
return WriteBitmapFile(&bmfh, &pBitmapInfo->bmiHeader, (PBYTE)&pBitmapInfo->bmiColors, bmfh.bfSize - bmfh.bfOffBits);
}
int SaveBitmapFromClipboard() {
HANDLE hGlobal = NULL;
int rv = 1;
if (!IsClipboardFormatAvailable(CF_DIB)) {
printf("[!] CF_DIB is not an avialable format\n");
return 1;
}
printf("[*] BITMAPINFO format is available\n");
if (!OpenClipboard(NULL)) {
printf("[!] OpenClipboard failed (err %d)\n", GetLastError());
return 1;
}
hGlobal = GetClipboardData(CF_DIB);
if (hGlobal == NULL) {
printf("[!] GetClipboardData failed (err %d)\n", GetLastError());
return 1;
}
PBITMAPINFO pClipboardData = (PBITMAPINFO)GlobalLock(hGlobal);
if (pClipboardData == NULL) {
printf("[!] GlobalLock failed (err %d)\n", GetLastError());
return 1;
}
rv = SaveFromBitmapInfo(pClipboardData);
GlobalUnlock(hGlobal);
if (!CloseClipboard()) {
printf("[!] CloseClipboard failed (err %d)\n", GetLastError());
return 1;
}
return rv;
}
int main(int argc, char** argv) {
if (argc != 2) {
printf("USAGE: SaveImageFromClipboard.exe FILENAME.bmp\n");
exit(1);
}
pFilename = argv[1];
return SaveBitmapFromClipboard();
}
@tamlin-mike
Copy link

Please note this code produces invalid .bmp file in case width*Bpp is not naturally DWORD-aligned.
Example: 24-bit with width=1026. Line stride should be 3080, but writing the image data in one go like this produces an invalid bmp with stride only 3078.
Seemingly Visual Studio has some leniency for such broken bmp's, since at least VS2019 actually can load and display it. mspaint however can not.

@ohaval
Copy link
Author

ohaval commented Apr 22, 2023

@tamlin-mike Thanks for the note!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment