[C++] Create a screenshot

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

[C++] Create a screenshot

Post by vitek »

It should be pretty easy to do for OpenGL and the software drivers. I'll post that when I get a second of time to do it.

Code: Select all

#ifdef _IRR_COMPILE_WITH_DIRECT3D_8_
#include <d3d8.h>

bool captureScreenShot(IVideoDriver* driver, const c8* filename) 
{ 
   IDirect3DDevice8* pDevice = driver->getExposedVideoData().D3D8.D3DDev8; 

   HRESULT hr; 

   D3DDISPLAYMODE mode; 
   if (FAILED(hr = pDevice->GetDisplayMode(&mode))) 
      return false; 

   LPDIRECT3DSURFACE8 surf; 
   if (FAILED(hr = pDevice->CreateImageSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, &surf))) 
      return false; 

   if (FAILED(hr = pDevice->GetFrontBuffer(surf))) 
   { 
      surf->Release(); 
      return false; 
   } 
  
   hr = D3DXSaveSurfaceToFile(filename, D3DXIFF_BMP, surf, NULL, NULL); 

   surf->Release(); 

   return SUCCEEDED(hr) != FALSE; 
} 
#endif 
Last edited by vitek on Wed Jul 12, 2006 12:33 am, edited 1 time in total.
andrei25ni
Posts: 326
Joined: Wed Dec 14, 2005 10:08 pm

Post by andrei25ni »

It should be pretty easy to do for OpenGL and the software drivers. I'll post that when I get a second of time to do it.
Yes, please...
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

It should be

Code: Select all

u8 *pixels=new u8[device->width*device->height*4];
glReadPixels(0,0,device->width,device->height,
GL_BGRA,GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
Unfortunately there is no portable BMP writer, so you have to put the pixels into some format on your own. You can use the pixels pointer to generate a CImage from it which would be a good place to implement save methods.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I'm thinking about creating an IImageWriter interface and a few implementations as well as an IVideoDriver::createScreenShot() ala LightFeather. I would find it to be useful, and I'm sure others would also.

Travis
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Would be really cool. A simple BMP writer shouldn't be too hard, and png and jpeg should be also possible with the libraries.
I just saw that X11 has a XWriteBitmapFile which could be used as a native interface.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I've got the screen capture code [DirectX, OpenGL, Software, Software2] and a few image writers [BMP, PNG, PPM] written up. I don't really see the need to use external stuff for bitmaps, the format is so very simple and the header structure is already declared in the image loader. I should have something polished up in the next few days. The interfaces look like this...

Code: Select all

class IVideoDriver
{
  //...

  //! Get a pointer to a screenshot image
  virtual void IImage* createScreenShot() = 0;

  //...
};

class IImageWriter
{
  //...

  // write the image to file
  virtual bool writeImage(io::IWriteFile* file, IImage* image) = 0;

  // check to see if this writer creates files of said type
  virtual bool isAWritableFileExtension(const c8* fileName) = 0;

  //...
};
The isAWritableFileExtension is so I can add some sort of image writer registry/manager code to make using it a little prettier...

Code: Select all

  // add an external image writer
  virtual void addExternalImageWriter(IImageWriter* writer) = 0;

  // figure out the destination file type based on file name
  virtual bool writeImageToFile(IImage* image, const c8* filename);
and to use it all...

Code: Select all

  whatever->writeImageToFile(driver->createScreenShot(), fileName);
Travis
Halan
Posts: 447
Joined: Tue Oct 04, 2005 8:17 pm
Location: Germany, Freak City
Contact:

Post by Halan »

looking forward to it :D
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Okay, so here's what I've got so far. It all seems to work just fine. I'm working on finishing up more image writers. You really only need one, but it's nice to have options.

I added this abstract method to IVideoDriver.h and then redeclared it in each of the C*Driver*.h files.

Code: Select all

//! Returns an image created from the last rendered frame.
virtual IImage* createScreenShot() = 0;
Then implemented each of the methods in the appropriate files...

Code: Select all

//! Returns an image created from the last rendered frame.
IImage* CSoftwareDriver::createScreenShot()
{
   return new CImage(BackBuffer->getColorFormat(), BackBuffer);
}

//! Returns an image created from the last rendered frame.
IImage* CSoftwareDriver2::createScreenShot()
{
   return new CImage(BackBuffer->getColorFormat(), BackBuffer);
}

//! Returns an image created from the last rendered frame.
IImage* CD3D8Driver::createScreenShot()
{
   HRESULT hr;

   // query the screen dimensions of the current adapter
   D3DDISPLAYMODE displayMode;
   pID3DDevice->GetDisplayMode(&displayMode);

   // create the image surface to store the front buffer image [always A8R8G8B8]
   LPDIRECT3DSURFACE8 lpSurface;
   if (FAILED(hr = pID3DDevice->CreateImageSurface(displayMode.Width, displayMode.Height, D3DFMT_A8R8G8B8, &lpSurface)))
      return 0;
 
   // read the front buffer into the image surface
   if (FAILED(hr = pID3DDevice->GetFrontBuffer(lpSurface)))
   {
      lpSurface->Release();
      return 0;
   }

   // get the size of the main window
   RECT clientRect;
   {
      POINT clientPoint;
      clientPoint.x = 0;
      clientPoint.y = 0;

      ClientToScreen( (HWND)getExposedVideoData().D3D8.HWnd, &clientPoint );

      clientRect.left   = clientPoint.x;
      clientRect.top    = clientPoint.y;
      clientRect.right  = clientRect.left + ScreenSize.Width;
      clientRect.bottom = clientRect.top  + ScreenSize.Height;
   }

   // lock the surface that is our window area
   D3DLOCKED_RECT lockedRect;
   if (FAILED(lpSurface->LockRect(&lockedRect, &clientRect, D3DLOCK_READONLY)))
   {
      lpSurface->Release();
      return 0;
   }

   // this could throw, but we aren't going to worry about that case very much
   IImage* newImage = new CImage(ECF_A8R8G8B8, ScreenSize);

   // d3d pads the image, so we need to copy the correct number of bytes
   u32* pPixels = (u32*)newImage->lock();
   if (pPixels)
   {
      u8 * sP = (u8 *)lockedRect.pBits;
      u32* dP = (u32*)pPixels;

      s32 y;
      for (y = 0; y < ScreenSize.Height; ++y)
      {
         memcpy(dP, sP, ScreenSize.Width * 4);

         sP += lockedRect.Pitch;
         dP += ScreenSize.Width;
      }

      newImage->unlock();
   }

   // we can unlock and release the surface
   lpSurface->UnlockRect();
 
   // release the image surface
   lpSurface->Release();

   // return status of save operation to caller
   return newImage;
}


//! Returns an image created from the last rendered frame.
IImage* CD3D9Driver::createScreenShot()
{
   HRESULT hr;

   // query the screen dimensions of the current adapter
   D3DDISPLAYMODE displayMode;
   pID3DDevice->GetDisplayMode(0, &displayMode);

   // create the image surface to store the front buffer image [always A8R8G8B8]
   LPDIRECT3DSURFACE9 lpSurface;
   if (FAILED(hr = pID3DDevice->CreateOffscreenPlainSurface(displayMode.Width, displayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &lpSurface, 0)))
      return 0;
 
   // read the front buffer into the image surface
   if (FAILED(hr = pID3DDevice->GetFrontBufferData(0, lpSurface)))
   {
      lpSurface->Release();
      return 0;
   }

   // get the size of the main window
   RECT clientRect;
   {
      POINT clientPoint;
      clientPoint.x = 0;
      clientPoint.y = 0;

      ClientToScreen( (HWND)getExposedVideoData().D3D9.HWnd, &clientPoint );

      clientRect.left   = clientPoint.x;
      clientRect.top    = clientPoint.y;
      clientRect.right  = clientRect.left + ScreenSize.Width;
      clientRect.bottom = clientRect.top  + ScreenSize.Height;
   }

   // lock the entire surface
   D3DLOCKED_RECT lockedRect;
   if (FAILED(lpSurface->LockRect(&lockedRect, &clientRect, D3DLOCK_READONLY)))
   {
      lpSurface->Release();
      return 0;
   }

   // this could throw, but we aren't going to worry about that case very much
   IImage* newImage = new CImage(ECF_A8R8G8B8, ScreenSize);

   // d3d pads the image, so we need to copy the correct number of bytes
   u32* pPixels = (u32*)newImage->lock();
   if (pPixels)
   {
      u8 * sP = (u8 *)lockedRect.pBits;
      u32* dP = (u32*)pPixels;

      s32 y;
      for (y = 0; y < ScreenSize.Height; ++y)
      {
         memcpy(dP, sP, ScreenSize.Width * 4);

         sP += lockedRect.Pitch;
         dP += ScreenSize.Width;
      }

      newImage->unlock();
   }

   // we can unlock and release the surface
   lpSurface->UnlockRect();
 
   // release the image surface
   lpSurface->Release();

   // return status of save operation to caller
   return newImage;
}

//! Returns an image created from the last rendered frame.
//! Returns an image created from the last rendered frame.
IImage* COpenGLDriver::createScreenShot()
{
   IImage* newImage = new CImage(ECF_A8R8G8B8, ScreenSize);

   u32* pPixels = (u32*)newImage->lock();
   if (!pPixels)
   {
      newImage->drop();
      return 0;
   }

   glReadPixels(0, 0, ScreenSize.Width, ScreenSize.Height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pPixels);

   // opengl images are inverted, so we have to fix that here.
   s32 y0 = 0, y1 = ScreenSize.Height - 1;
   for (; y0 < y1; ++y0, --y1)
   {
      u32* topRow = &pPixels[y0 * ScreenSize.Width];
      u32* botRow = &pPixels[y1 * ScreenSize.Width];

      s32 x;
      for(x = 0; x < ScreenSize.Width; ++x)
      {
         u32 pixel = topRow[x];
         topRow[x] = botRow[x];
         botRow[x] = pixel;
      }
   }

   newImage->unlock();

   if (glGetError() != 0)
   {
      newImage->drop();
      return 0;
   }

   return newImage;
}
As mentioned in my previous post I created an interface for writing the images to disk. I've still got a bit of work to do here, but it works just fine. I've got bmp, jpeg and possibly tga working. I want to get psd, pcx and png also. I'll post that code when I finish it.

Travis
Last edited by vitek on Wed Aug 02, 2006 1:56 am, edited 2 times in total.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Some utility functions for converting color formats. I ran these through a small test suite and they all appear to work. I'm pretty sure they're not going to work with big-endian systems.

As you might notice, I implemented the no-op conversions to actually do a memcpy. This is just so that when I'm writing image data to disk later I don't have to write two code paths, one that converts the data and one that doesn't. Another choice would have been to just fail if the data is not in the right format.

Code: Select all

void convert_A1R5G5B5toR8G8B8(const void* sP, s32 sN, void* dP)
{
   u16* sB = (u16*)sP;
   u8 * dB = (u8 *)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      dB[2] = ((*sB >> 10) & 0x1f) << 3;
      dB[1] = ((*sB >>  5) & 0x1f) << 3;
      dB[0] = ((*sB >>  0) & 0x1f) << 3;

      sB += 1;
      dB += 3;
   }
}

void convert_A1R5G5B5toA8R8G8B8(const void* sP, s32 sN, void* dP)
{
   u16* sB = (u16*)sP;
   u32* dB = (u32 *)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 a = ((*sB >> 15) & 0x01) * 255;
      s32 r = ((*sB >> 10) & 0x1f) << 3;
      s32 g = ((*sB >>  5) & 0x1f) << 3;
      s32 b = ((*sB >>  0) & 0x1f) << 3;

      *dB = (a << 24) | (r << 16) | (g << 8) | (b);

      sB += 1;
      dB += 1;
   }
}

void convert_A1R5G5B5toA1R5G5B5(const void* sP, s32 sN, void* dP)
{
   memcpy(dP, sP, sN * 2);
}

void convert_A1R5G5B5toR5G6B5(const void* sP, s32 sN, void* dP)
{
   u16* sB = (u16*)sP;
   u16* dB = (u16*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 r = ((*sB >> 10) & 0x1f);
      s32 b = ((*sB >>  0) & 0x1f);
      s32 g = ((*sB >>  5) & 0x1f) << 1;

      *dB = (r << 11) | (g << 5) | (b);

      sB += 1;
      dB += 1;
   }
}

void convert_A8R8G8B8toR8G8B8(const void* sP, s32 sN, void* dP)
{
   u8* sB = (u8*)sP;
   u8* dB = (u8*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      // sB[3] is alpha
      dB[0] = sB[0];
      dB[1] = sB[1];
      dB[2] = sB[2];

      sB += 4;
      dB += 3;
   }
}

void convert_A8R8G8B8toA8R8G8B8(const void* sP, s32 sN, void* dP)
{
   memcpy(dP, sP, sN * 4);
}

void convert_A8R8G8B8toA1R5G5B5(const void* sP, s32 sN, void* dP)
{
   u8 * sB = (u8 *)sP;
   u16* dB = (u16*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 b = sB[0] >> 3;
      s32 g = sB[1] >> 3;
      s32 r = sB[2] >> 3;
      s32 a = sB[3] >> 7;

      *dB = (a << 15) | (r << 10) | (g << 5) | (b);

      sB += 4;
      dB += 1;
   }
}

void convert_A8R8G8B8toR5G6B5(const void* sP, s32 sN, void* dP)
{
   u8 * sB = (u8 *)sP;
   u16* dB = (u16*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 r = sB[2] >> 3;
      s32 g = sB[1] >> 2;
      s32 b = sB[0] >> 3;

      dB[0] = (r << 11) | (g << 5) | (b);

      sB += 4;
      dB += 1;
   }
}

void convert_R8G8B8toR8G8B8(const void* sP, s32 sN, void* dP)
{
   memcpy(dP, sP, sN * 3);
}

void convert_R8G8B8toA8R8G8B8(const void* sP, s32 sN, void* dP)
{
   u8 * sB = (u8 *)sP;
   u32* dB = (u32*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      *dB = (0xff000000) | (sB[2] << 16) | (sB[1] << 8) | sB[0];

      sB += 3;
      dB += 1;
   }
}

void convert_R8G8B8toA1R5G5B5(const void* sP, s32 sN, void* dP)
{
   u8 * sB = (u8 *)sP;
   u16* dB = (u16*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 r = sB[2] >> 3;
      s32 g = sB[1] >> 3;
      s32 b = sB[0] >> 3;

      *dB = (0x8000) | (r << 10) | (g << 5) | (b);

      sB += 3;
      dB += 1;
   }
}

void convert_R8G8B8toR5G6B5(const void* sP, s32 sN, void* dP)
{
   u8 * sB = (u8 *)sP;
   u16* dB = (u16*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 r = sB[2] >> 3;
      s32 g = sB[1] >> 2;
      s32 b = sB[0] >> 3;

      *dB = (r << 11) | (g << 5) | (b);

      sB += 3;
      dB += 1;
   }
}

void convert_R5G6B5toR5G6B5(const void* sP, s32 sN, void* dP)
{
   memcpy(dP, sP, sN * 2);
}

void convert_R5G6B5toR8G8B8(const void* sP, s32 sN, void* dP)
{
   u16* sB = (u16*)sP;
   u8 * dB = (u8 *)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      dB[2] = ((*sB >> 11) & 0x1f) << 3;
      dB[1] = ((*sB >>  5) & 0x3f) << 2;
      dB[0] = ((*sB >>  0) & 0x1f) << 3;

      sB += 4;
      dB += 3;
   }
}

void convert_R5G6B5toA8R8G8B8(const void* sP, s32 sN, void* dP)
{
   u16* sB = (u16*)sP;
   u32* dB = (u32*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 r = ((*sB >> 11) & 0x1f) << 3;
      s32 g = ((*sB >>  5) & 0x3f) << 2;
      s32 b = ((*sB >>  0) & 0x1f) << 3;

      *dB = (0xff000000) | (r << 16) | (g << 8) | (b);

      sB += 1;
      dB += 1;
   }
}

void convert_R5G6B5toA1R5G5B5(const void* sP, s32 sN, void* dP)
{
   u16* sB = (u16*)sP;
   u16* dB = (u16*)dP;

   s32 x;
   for (x = 0; x < sN; ++x)
   {
      s32 r = (*sB >> 11) & 0x1f;
      s32 g = ((*sB >>  5) & 0x3f) >> 1;
      s32 b = (*sB >>  0) & 0x1f;

      *dB = (0x8000) | (r << 10) | (g << 5) | (b);

      sB += 1;
      dB += 1;
   }
}
Last edited by vitek on Tue Jul 25, 2006 5:12 am, edited 1 time in total.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Image writer interface...

Code: Select all

//! Interface for writing software image data.
class IImageWriter
{
public:
   //! destructor
   virtual ~IImageWriter()
   {
   };

   //! return true if this writer can write a file with the given extension
   virtual bool isAWriteableFileExtension(const c8* fileName) = 0;

   //! write image to file
   virtual bool writeImage(io::IWriteFile *file, IImage *image) = 0;
};
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Here is the bmp writer. We write all images as 24-bit RGB.

Code: Select all

class CImageWriterBMP : public IImageWriter
{
public:
   //! return true if this writer can write a file with the given extension
   virtual bool isAWriteableFileExtension(const c8* fileName);

   //! write image to file
   virtual bool writeImage(io::IWriteFile *file, IImage *image);
};


bool CImageWriterBMP::isAWriteableFileExtension(const c8* fileName)
{
   return strstr(fileName, ".bmp") != 0;
}

bool CImageWriterBMP::writeImage(io::IWriteFile* file, IImage* image)
{
   // we always write 24-bit color because nothing really reads 32-bit
   SBMPHeader imageHeader;
   imageHeader.Id = 0x4d42;
   imageHeader.Reserved = 0;
   imageHeader.BitmapDataOffset = sizeof(imageHeader);
   imageHeader.BitmapHeaderSize = 0x28;
   imageHeader.Width = image->getDimension().Width;
   imageHeader.Height = image->getDimension().Height;
   imageHeader.Planes = 1;
   imageHeader.BPP = 24;
   imageHeader.Compression = 0;
   imageHeader.PixelPerMeterX = 0;
   imageHeader.PixelPerMeterY = 0;
   imageHeader.Colors = 0;
   imageHeader.ImportantColors = 0;

   // data size is rounded up to nearest 4 bytes
   imageHeader.BitmapDataSize = (imageHeader.Width * imageHeader.Height) * 3;
   imageHeader.BitmapDataSize = (imageHeader.BitmapDataSize + 3) & ~3;

   // file size is data size plus offset to data
   imageHeader.FileSize = imageHeader.BitmapDataOffset + imageHeader.BitmapDataSize;

   // bitmaps are stored upside down and padded so we always do this
   void (*convertFORMATtoFORMAT)(const void*, s32, void*) = 0;
   switch(image->getColorFormat())
   {
   case ECF_R8G8B8:
      convertFORMATtoFORMAT = convert_R8G8B8toR8G8B8;
      break;
   case ECF_A8R8G8B8:
      convertFORMATtoFORMAT = convert_A8R8G8B8toR8G8B8;
      break;
   case ECF_A1R5G5B5:
      convertFORMATtoFORMAT = convert_A1R5G5B5toR8G8B8;
      break;
   case ECF_R5G6B5:
      convertFORMATtoFORMAT = convert_R5G6B5toR8G8B8;
      break;
   }

   // couldn't find a color converter
   if (!convertFORMATtoFORMAT)
      return false;

   // write the bitmap header
   if (file->write(&imageHeader, sizeof(imageHeader)) != sizeof(imageHeader))
      return false;

   u8* scan_lines = (u8*)image->lock();
   if (!scan_lines)
      return false;

   // size of one pixel in bytes
   u32 pixel_size = image->getBytesPerPixel();

   // length of one row of the source image in bytes
   u32 row_stride = (pixel_size * imageHeader.Width);

   // length of one row in bytes, rounded up to nearest 4-byte boundary
   u32 row_size = ((3 * imageHeader.Width) + 3) & ~3;

   // allocate and clear memory for our scan line
   u8* row_pointer = new u8[row_size];
   memset(row_pointer, 0, row_size);

   // convert the image to 24-bit RGB and flip it over
   s32 y;
   for (y = imageHeader.Height - 1; 0 <= y; --y)
   {
      // source, length [pixels], destination
      convertFORMATtoFORMAT(&scan_lines[y * row_stride], imageHeader.Width, row_pointer);
      if (file->write(row_pointer, row_size) < row_size)
         break;
   }

   // clean up our scratch area
   delete [] row_pointer;

   // give back image handle
   image->unlock();

   return y < 0;
}
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

The tga file writer...

Code: Select all

// this goes with the STGAHeader decl in CImageLoaderTGA.h
struct STGAFooter
{
   u32 ExtensionOffset;
   u32 DeveloperOffset;
   c8  Signature[18];
} PACK_STRUCT;


class CImageWriterTGA : public IImageWriter
{
public:
   //! return true if this writer can write a file with the given extension
   virtual bool isAWriteableFileExtension(const c8* fileName);

   //! write image to file
   virtual bool writeImage(io::IWriteFile *file, IImage *image);
};

bool CImageWriterTGA::isAWriteableFileExtension(const c8* fileName)
{
   return strstr(fileName, ".tga") != 0;
}

bool CImageWriterTGA::writeImage(io::IWriteFile *file, IImage *image)
{
   STGAHeader imageHeader;
   imageHeader.IdLength = 0;
   imageHeader.ColorMapType = 0;
   imageHeader.ImageType = 2;
   imageHeader.FirstEntryIndex[0] = 0;
   imageHeader.FirstEntryIndex[1] = 0;
   imageHeader.ColorMapLength = 0;
   imageHeader.ColorMapEntrySize = 0;
   imageHeader.XOrigin[0] = 0;
   imageHeader.XOrigin[1] = 0;
   imageHeader.YOrigin[0] = 0;
   imageHeader.YOrigin[1] = 0;
   imageHeader.ImageWidth = image->getDimension().Width;
   imageHeader.ImageHeight = image->getDimension().Height;

   // top left of image is the top. the image loader needs to
   // be fixed to only swap/flip
   imageHeader.ImageDescriptor = (1 << 5);

   // chances are good we'll need to swizzle data, so i'm going
   // to convert and write one scan line at a time. it's also
   // a bit cleaner this way
   void (*convertFORMATtoFORMAT)(const void*, s32, void*) = 0;
   switch(image->getColorFormat())
   {
   case ECF_A8R8G8B8:
      convertFORMATtoFORMAT = convert_A8R8G8B8toA8R8G8B8;
      imageHeader.PixelDepth = 32;
      imageHeader.ImageDescriptor |= 8;
      break;
   case ECF_A1R5G5B5:
      convertFORMATtoFORMAT = convert_A1R5G5B5toA8R8G8B8;
      imageHeader.PixelDepth = 32;
      imageHeader.ImageDescriptor |= 1;
      break;
   case ECF_R5G6B5:
      convertFORMATtoFORMAT = convert_R5G6B5toR8G8B8;
      imageHeader.PixelDepth = 24;
      imageHeader.ImageDescriptor |= 0;
      break;
   case ECF_R8G8B8:
      convertFORMATtoFORMAT = convert_R8G8B8toR8G8B8;
      imageHeader.PixelDepth = 24;
      imageHeader.ImageDescriptor |= 0;
      break;
   }

   // couldn't find a color converter
   if (!convertFORMATtoFORMAT)
      return false;

   if (file->write(&imageHeader, sizeof(imageHeader)) != sizeof(imageHeader))
      return false;

   u8* scan_lines = (u8*)image->lock();
   if (!scan_lines)
      return false;

   // size of one pixel in bytes
   u32 pixel_size = image->getBytesPerPixel();

   // length of one row of the source image in bytes
   u32 row_stride = (pixel_size * imageHeader.ImageWidth);

   // length of one output row in bytes
   u32 row_size = ((imageHeader.PixelDepth / 8) * imageHeader.ImageWidth);

   // allocate a row do translate data into
   u8* row_pointer = new u8[row_size];

   u32 y;
   for (y = 0; y < imageHeader.ImageHeight; ++y)
   {
      convertFORMATtoFORMAT(&scan_lines[y * row_stride], imageHeader.ImageWidth, row_pointer);
      if (file->write(row_pointer, row_size) != row_size)
         break;
   }

   delete [] row_pointer;

   image->unlock();

   STGAFooter imageFooter;
   imageFooter.ExtensionOffset = 0;
   imageFooter.DeveloperOffset = 0;
   strncpy(imageFooter.Signature, "TRUEVISION-XFILE.", 18);

   if (file->write(&imageFooter, sizeof(imageFooter)) != sizeof(imageFooter))
      return false;

   return imageHeader.ImageHeight < y;
}
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Here's an ascii PPM reader...

Code: Select all

class CImageWriterPPM : public IImageWriter
{
public:
   //! return true if this writer can write a file with the given extension
   virtual bool isAWriteableFileExtension(const c8* fileName);

   //! write image to file
   virtual bool writeImage(io::IWriteFile *file, IImage *image);
};

bool CImageWriterPPM::isAWriteableFileExtension(const c8* fileName)
{
   return strstr(fileName, ".ppm") != 0;
}

#ifdef _IRR_WINDOWS_
#  define snprintf _snprintf
#endif

bool CImageWriterPPM::writeImage(io::IWriteFile *file, IImage *image)
{
   char cache[70];
   char size;

   const core::dimension2d<s32>& imageSize = image->getDimension();

   size = snprintf(cache, 70, "P3\n");
   if (file->write(cache, size) != size)
      return false;

   size = snprintf(cache, 70, "%d %d\n", imageSize.Width, imageSize.Height);
   if (file->write(cache, size) != size)
      return false;

   size = snprintf(cache, 70, "255\n");
   if (file->write(cache, size) != size)
      return false;

   s32 n = 0;

   s32 r;
   for (r = 0; r < imageSize.Height; ++r)
   {
      s32 c;
      for (c = 0; c < imageSize.Width; ++c, ++n)
      {
         const video::SColor& pixel = image->getPixel(c, r);
         size = snprintf(cache, 70, "%0.3d %0.3d %0.3d%s",
            pixel.getRed(), pixel.getGreen(), pixel.getBlue(), ((n % 5) == 4) ? "\n" : "  ");
         if (file->write(cache, size) != size)
            return false;
      }
   }

   return true;
}
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I have a jpeg and a png writer, but I don't trust the code yet. I have little experience with libjpeg and my code is a little ugly still. I'm currently distracted with some tasks at work, so I might not have time to finish this stuff up.

Anyways, here's a sample of how I use it...

Code: Select all

class MyEventReceiver : public IEventReceiver
{
public:
   MyEventReceiver(IrrlichtDevice* device)
      : Device(device)
      , Writer(0)
   {
      if (Device)
        Device->grab();
   }

   virtual ~MyEventReceiver()
   {
      if (Device)
         Device->drop();

      delete Writer;
   }

   virtual bool OnEvent(SEvent event)
   {
      if (!Device)
         return false;

      if (event.EventType != EET_KEY_EVENT)
         return false;
      
      if (!event.KeyInput.PressedDown)
         return false;
      
      if (event.KeyInput.Key != KEY_TAB)
         return false;

      // lazy initialize our image writer
      if (!Writer)
         Writer = new CImageWriterBMP;

      // get the screenshot from the driver
      video::IImage* image = Device->getVideoDriver()->createScreenShot();
      if (image)
      {
         c8 name[64];
         _snprintf(name, 64, "screenshot%u.bmp", Device->getTimer()->getRealTime());

         // get the file from the file system
         IWriteFile* file = Device->getFileSystem()->createAndWriteFile(name);
         if (file)
         {
            Writer->writeImage(file, image);
            file->drop();
         }

         image->drop();
      }

      return true;
   }

private:
   IrrlichtDevice* Device;
   IImageWriter* Writer;
};
Travis
JPulham
Posts: 320
Joined: Sat Nov 19, 2005 12:06 pm

Post by JPulham »

great work :shock: . I need this kinda stuff. For the future and GD 4E!
pushpork
Post Reply