Copy/Paste Texture to Clipboard

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Copy/Paste Texture to Clipboard

Post by Baker »

FitzQuake 0.85 has a dumptextures command that downloads the textures from the video card and writes them as TGA:

Code: Select all

/*
===============
TexMgr_Imagedump_f -- dump all current textures to TGA files
===============
*/
void TexMgr_Imagedump_f (void)
{
.
.
.
			buffer = malloc(glt->width*glt->height*4);
			glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
			Image_WriteTGA (tganame, buffer, glt->width, glt->height, 32, true);
.
.
.
	Con_Printf ("dumped %i textures to %s\n", numgltextures, dirname);
}
I plan on making it so a single texture can be copied or pasted to the clipboard Winodws, but looks like I'll have to do this incrementally.

Accessing the clipboard is easy enough ...

Code: Select all

char *Sys_GetClipboardData (void)
{
	HANDLE		th;
	char		*clipText, *s, *t;
	static char	clipboard[SYS_CLIPBOARD_SIZE];

	if (!OpenClipboard(NULL))
		return NULL;

	if (!(th = GetClipboardData(CF_TEXT)))
	{
		CloseClipboard ();
		return NULL;
	}

	if (!(clipText = GlobalLock(th)))
	{
		CloseClipboard ();
		return NULL;
	}

	s = clipText;
	t = clipboard;

	/*
	\e	Write an <escape> character.
	\a	Write a <bell> character.
	\b	Write a <backspace> character.
	\f	Write a <form-feed> character.
	\n	Write a <new-line> character.
	\r	Write a <carriage return> character.
	\t	Write a <tab> character.
	\v	Write a <vertical tab> character.
	\'	Write a <single quote> character.
	\\	Write a backslash character.
	*/

	// Filter out newlines, carriage return and backspace characters
	while (*s && t - clipboard < SYS_CLIPBOARD_SIZE - 1 && *s != '\n' && *s != '\r' && *s != '\b')
		*t++ = *s++;
	*t = 0;

	GlobalUnlock (th);
	CloseClipboard ();

	return clipboard;
}

// copies given text to clipboard
void Sys_CopyToClipboard(const char *text)
{
	char *clipText;
	HGLOBAL hglbCopy;

	if (!OpenClipboard(NULL))
		return;

	if (!EmptyClipboard())
	{
		CloseClipboard();
		return;
	}

	if (!(hglbCopy = GlobalAlloc(GMEM_DDESHARE, strlen(text) + 1)))
	{
		CloseClipboard();
		return;
	}

	if (!(clipText = GlobalLock(hglbCopy)))
	{
		CloseClipboard();
		return;
	}

	strcpy((char *) clipText, text);
	GlobalUnlock(hglbCopy);
	SetClipboardData(CF_TEXT, hglbCopy);

	CloseClipboard();
}
But looks like I'll have to dig into some Windows API stuffs to use CF_DIB. I downloaded some source codes like GIMP and such, but most of them use GTK obfuscating getting into the Windows procedure to allocatie a DIB and fill in the data.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Puzzle pieces ...

Code: Select all

HGLOBAL hData = data.GetGlobalData(CF_DIB);
BITMAPINFO* pData = (BITMAPINFO*) GlobalLock(hData);
if (pData)
{
// use it!
GlobalFree(hData);
}

Code: Select all

typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;
  LONG  biWidth;
  LONG  biHeight;
  WORD  biPlanes;
  WORD  biBitCount;
  DWORD biCompression;
  DWORD biSizeImage;
  LONG  biXPelsPerMeter;
  LONG  biYPelsPerMeter;
  DWORD biClrUsed;
  DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Hmmm; I've done this in .NET just using Clipboard.SetImage which seems to use CF_BITMAP behind the scenes. Might be useful info or might not! 8)
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:Hmmm; I've done this in .NET just using Clipboard.SetImage which seems to use CF_BITMAP behind the scenes. Might be useful info or might not! 8)
I'm thinking this is gonna be a bit more "raw" than something insulated like .NET but maybe I'll be wrong. I've been rather stunned so far as to how few open source image editors are solely native Windows coded in C or C++.

Still, I'm just not gonna be deterred. Stubborness ... the heart of engine coding :D I'll just keep pounding away and playing around until it happens.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Don't worry about the DirectQ-specific bits in here, this works:

Code: Select all

		byte *bmbits = (byte *) MainHunk->Alloc (width * height * 4);

		D3D_Transfer8BitTexture ((byte *) data, (unsigned *) bmbits, width * height, D3D_GetTexturePalette (d3d_QuakePalette.standard, 0));
		HBITMAP hBitmap = CreateBitmap (width, height, 1, 32, bmbits);

		OpenClipboard (NULL);

		if ((SetClipboardData (CF_BITMAP, hBitmap)) == NULL)
			Sys_Error ("SetClipboardData failed");

		CloseClipboard ();
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:Don't worry about the DirectQ-specific bits in here, this works:

Code: Select all

		byte *bmbits = (byte *) MainHunk->Alloc (width * height * 4);

		D3D_Transfer8BitTexture ((byte *) data, (unsigned *) bmbits, width * height, D3D_GetTexturePalette (d3d_QuakePalette.standard, 0));
		HBITMAP hBitmap = CreateBitmap (width, height, 1, 32, bmbits);

		OpenClipboard (NULL);

		if ((SetClipboardData (CF_BITMAP, hBitmap)) == NULL)
			Sys_Error ("SetClipboardData failed");

		CloseClipboard ();
I was doing this a bit less efficiently than your example (filling in the bitmap header manually because I didn't know better).

And it would seem that GetBitmapBits does the reverse.

Thanks, MH ;)
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Upside down fix ....

glReadPixels (...)
FlipRows (...)
ApplyGamma (...) // Optional
hBitmap= CreateBitmap (...)

Code: Select all

static qbool FlipRows (int columns, int rows, int colordepth, byte *buffer)	
{      
    byte  *tb1, *tb2;   
    int		offset1, offset2;
	int		i,bufsize;   
   
    bufsize = columns * colordepth; 
   
    tb1= Q_malloc(bufsize);   
    tb2= Q_malloc(bufsize);
   
    for (i=0;i<(rows+1)/2;i++)    
    {   
        offset1= i * bufsize;   
        offset2=((rows-1)-i) * bufsize;      
           
        memcpy(tb1,				buffer+offset1,	bufsize);   
        memcpy(tb2,				buffer+offset2, bufsize);    
        memcpy(buffer+offset1,	tb2,			bufsize);   
        memcpy(buffer+offset2,	tb1,			bufsize);   
    }      
   
	free (tb1);
	free (tb2);  
    return true;   
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Also look at glGetTexImage: http://www.opengl.org/sdk/docs/man/xhtm ... xImage.xml

Hmm, I could do this in DirectQ using it's tab autocompletion on texturenames. "copytexture <tab>", etc. And also do "dumptexture" for a single texture. Also "pastetexture" to update a texture image from the clipboard - that might be really useful for experimenting with external textures (or even just different textures) without needing to rebuild or reload a map. In fact I imagine that mappers would find such a feature to be really good. :D
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:Also look at glGetTexImage: http://www.opengl.org/sdk/docs/man/xhtm ... xImage.xml
Actually, I was testing it with the screenshot command just to fine tune things. glReadPixels in the above post was a mistake.

Hmm, I could do this in DirectQ using it's tab autocompletion on texturenames. "copytexture <tab>", etc. And also do "dumptexture" for a single texture. Also "pastetexture" to update a texture image from the clipboard - that might be really useful for experimenting with external textures (or even just different textures) without needing to rebuild or reload a map. In fact I imagine that mappers would find such a feature to be really good. :D
Or alter the texture of a single surface that the crosshair is pointing to :D

Or, in fact, you could test model skins in real-time by copy/paste. Sure the texture would have to go through the whole padding, power of 2 routine.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Rather than start a new thread ...

I'm rewriting my engine a bit so commands can output to ...

1. The console
2. To file
3. Text to clipboard

Why? Well the edicts command is virtually useless since it dumps probably about 2MB of entity information to the console, which vastly exceeds the console buffer and any size you would ever sensibly make it.

Just take Con_Printf and modify it to have output options.

enum {PRINT_CONSOLE, PRINT_FILE, PRINT_CLIPBOARD}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Copy/Paste Texture to Clipboard

Post by Baker »

Not wanting to start a new thread ...

I wrote a function to get an image off the clipboard and convert it to RGBA. (I use RGBA instead of BGRA as MH strongly advocates because I'd have to adjust a lot of image functions I've written and I don't want to do that right now. image loaders, resample functions, color manipulation functions, etc.)

To get BGRA instead, just turn #if 1 into #if 0 in the code below.

Code: Select all

byte* Platform_Clipboard_To_RGBA_Alloc (int* outwidth, int* outheight)
{
	byte* ptr = NULL;
	
	if (OpenClipboard(NULL))
	{
		HBITMAP hBitmap = GetClipboardData (CF_BITMAP);
		BITMAP csBitmap;
		if (hBitmap && GetObject(hBitmap, sizeof(csBitmap), &csBitmap) && csBitmap.bmBitsPixel == 32)
		{
			// allocate buffer
			int i, bufsize = csBitmap.bmWidth * csBitmap.bmHeight * (csBitmap.bmBitsPixel / 8);	

			csBitmap.bmBits = ptr = Memory_malloc (bufsize, "bmbits buffer");
			GetBitmapBits((HBITMAP)hBitmap, bufsize, csBitmap.bmBits );
		
			// Convert BGRA --> RGBA, set alpha full since clipboard loses it somehow
			for (i = 0; i < bufsize; i += 4)
			{
#if 1 // Wants RGBA.  Set to 0 for BGRA
				byte temp = ptr[i + 0];
				ptr[i + 0] = ptr[i + 2];
				ptr[i + 2] = temp;
#endif
				ptr[i + 3] = 255; // Full alpha
			}
			*outwidth = csBitmap.bmWidth;
			*outheight = csBitmap.bmHeight;
		}
		CloseClipboard ();
	}
	return ptr;
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Copy/Paste Texture to Clipboard

Post by mh »

RGBA is OK for this kinda thing; it's not as performance-critical as lightmaps. :D
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply