Game Development Community

dev|Pro Game Development Curriculum

PSD reader for Torque 3D

by Fyodor -bank- Osokin · 02/03/2012 (4:42 am) · 11 comments

We all know, that Torque 3D have a great feature - live material updating.. But lets make artists' life even more easier!

Teach your Torque to load PSD files as textures!

static.garagegames.com/static/upload/blogs/21500.png

Instructions:

  1. Paste the code (below) into a new file: Engine/source/gfx/bitmap/loaders/bitmapPsd.cpp or save&copy this file.
  2. Re-generate projects (run generateProjects.bat) or manually add the file to solution.
  3. Build. While the engine is linking, continue to next step (update scripts)
  4. In script: tools/materialEditor/main.cs:
  5. in function MaterialEditorPlugin::onWorldEditorStartup( %this )
    Add *.psd to the list for MaterialEditorGui.textureFormats
  6. In script: tools/materialEditor/scripts/materialEditor.ed.cs:
  7. in function MaterialEditorGui::searchForTexture(%this,%material, %texture)
    Add *.psd to the list for %formats

Congratulations! You can now use the PSD files in Torque! No need to export textures just to check how it looks in engine. Hit Ctrl+S (save file) and switch to Torque see the changes!

The code itself (should be saved in Engine/source/gfx/bitmap/loaders/bitmapPsd.cpp):
//-----------------------------------------------------------------------------
// Torque 3D PSD Loader
//
// Inspired by "crunch" (DXT texture compression library) PSD loader implementation
// This modification is distributed under the same license,
// as the crunch itself (ZLIB license), see http://code.google.com/p/crunch/ for details
// Torque 3D implementation by Dedicated Logic [Feb, 03 2012]
//-----------------------------------------------------------------------------

#include "core/stream/stream.h"
#include "gfx/bitmap/gBitmap.h"

static bool sReadPSD(Stream &stream, GBitmap *bitmap);
static bool sWritePSD(GBitmap *bitmap, Stream &stream, U32 compressionLevel);

static struct _privateRegisterPSD
{
   _privateRegisterPSD()
   {
      GBitmap::Registration reg;

      reg.extensions.push_back( "psd" );

      reg.readFunc = sReadPSD;
      reg.writeFunc = sWritePSD;

      GBitmap::sRegisterFormat( reg );
   }
} sStaticRegisterPSD;

//------------------------------------------------------------------------------

static void sPSDReadRawRGBA(Stream *stream, U8 *data, U32 numPixels, U16 channels)
{
   for(S32 ch = 0; ch < 4; ch++)
   {
      U8 *currentPixel = data + ch;
      if(ch < channels)
      {
         for(S32 i = 0; i < numPixels; i++)
         {
            stream->read(currentPixel);
            currentPixel += 4;
         }
      }
      else
      {
         for(S32 i = 0; i < numPixels; i++)
         {
            *currentPixel = (ch == 3) ? 255 : 0;
            currentPixel += 4;
         }
      }
   }
}

//------------------------------------------------------------------------------

#define skipStream(skip) 
   stream->setPosition(stream->getPosition() + skip);

static U8 _readU8(Stream * stream)
{
   U8 val;
   stream->read(&val);
   return val;
}

#define readU8() _readU8(stream);

static void sPSDReadRLE(Stream *stream, U8 *data, U32 numPixels, U16 channels, U32 height)
{
   // TIFF-based RLE
   // When RLE compression is used, we need to skip 2 bytes (for each row) per channel, not sure what it contains though
   skipStream(channels * height * 2);
   // Read it!
   for(S32 ch = 0; ch < 4; ch++)
   {
      U8 *currentPixel = data + ch;
      if(ch < channels)
      {
         S32 pixelsRead = 0;
         while(pixelsRead < numPixels)
         {
            S32 rowLen = readU8();
            if(rowLen == 128)
               continue;
            if(rowLen < 128)
            {
               rowLen++;
               pixelsRead += rowLen;
               while(rowLen)
               {
                  *currentPixel = readU8();
                  currentPixel += 4;
                  rowLen--;
               }
            }
            else
            {
               U32 byte = readU8();
               rowLen ^= 0x0FF;
               rowLen += 2;
               pixelsRead += rowLen;
               while(rowLen)
               {
                  *currentPixel = byte;
                  currentPixel += 4;
                  rowLen--;
               }
            }
         }
      }
      else
      {
         for(S32 i = 0; i < numPixels; i++)
         {
            *currentPixel = (ch == 3 ? 255 : 0);
            currentPixel += 4;
         }
      }
   }
}

struct PSDHeader
{
   U32 fileId;          // PSD identifier (expecting "8BPS") 4
   U16 version;         // PSD version (expecting 1) 2
   U16 channels;        // Number of channels stored in PSD 2
   U16 depth;           // Depth (expecting 8 bits) 2
   U16 colorMode;       // Color mode (expecting pure RGB) 2
   U32 width;           // the width of the image. 4
   U32 height;          // the height of the image. 4
   U16 compressionType; // Expect 1 for RLE or 0 for raw uncompressed data. Anything else is unsupported! 2
};

static U16 _readU16(Stream *stream)
{
   U8 val1;
   U8 val2;
   stream->read(&val1);
   stream->read(&val2);
   return (val1 << 8) + val2;
}

#define readU16() _readU16(stream);

static U32 _readU32(Stream *stream)
{
   U16 val1 = readU16();
   U16 val2 = readU16();
   return (val1 << 16) + val2;
}

#define readU32() _readU32(stream);

static bool sPSDReadAndBuildHeader(Stream *stream, PSDHeader *header)
{
   // Identify if it is a real PSD
   header->fileId = readU32();
   if(header->fileId != 0x38425053) // Valid PSD file have "8BPS" header
   {
      Con::errorf("Can't read -- non-PSD file specified.");
      return false;
   }
   // File version
   header->version = readU16();
   if(header->version != 1)
   {
      Con::errorf("Unsupported PSD version! Got %d, expected 1.", header->version);
      return false;
   }

   // Next 6 bytes are reserved, skip it!
   skipStream(6);
   // Read header
   header->channels = readU16();
   header->height = readU32();
   header->width = readU32();
   header->depth = readU16();
   header->colorMode = readU16();

   // The PSD stores part of additional data in a header, so we should skip it
   U32 tmp = readU32();
   skipStream(tmp); // Some color modes stores indexed color of its palette
   tmp = readU32();
   skipStream(tmp); // xmpmeta
   tmp = readU32();
   skipStream(tmp); // Reserved data?

   header->compressionType = readU16();

   // Now, check everything, to be sure the format is valid for reading
   if(header->channels < 0 || header->channels > 16)
   {
      Con::errorf("Unsupported number of channels found: %d. We can read PSD with 0>16 only!", header->channels);
      return false;
   }
   if(header->depth != 8)
   {
      Con::errorf("Unsupported bit depth: %d, we can load only 8 bits!", header->depth);
      return false;
   }
   if(header->colorMode != 3)
   {
      //TODO: Implement mode 1 (grayscale)
      Con::errorf("Unsupported color mode: %d, we can load only RGB (mode 3)!", header->colorMode);
      return false;
   }

   return true;
}

#undef readU32
#undef readU16
#undef readU8
#undef skipStream

//------------------------------------------------------------------------------

static bool sReadPSD(Stream &stream, GBitmap *bitmap)
{
   PSDHeader header;
   if(!sPSDReadAndBuildHeader(&stream, &header))
      return false;

   // Sanity check
   U32 numPixels = header.width * header.height;
   if( numPixels == 0 )
   {
      Con::errorf( "Texture has width and/or height set to 0" );
      return false;
   }

   bitmap->allocateBitmap( header.width, header.height, false, GFXFormatR8G8B8A8 );
   U8 *buffer = bitmap->getAddress(0, 0);
   switch(header.compressionType)
   {
   case 0: // Uncompressed RGBA data (8-bit raw image)
      sPSDReadRawRGBA(&stream, buffer, numPixels, header.channels);
      break;
   case 1: // RLE
      sPSDReadRLE(&stream, buffer, numPixels, header.channels, header.height);
      break;
   default:
      Con::printf("Unsupported compression method in PSD: %d", header.compressionType);
      return false;
      break;
   }

   // If we read 4 channels, means we have an alpha channel
   bitmap->setHasTransparency( header.channels == 4 );

   return true;
}

static bool sWritePSD(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
   AssertISV(false, "GBitmap::writePSD - doesn't support writing PSD files!")
   return false;
}

A few limitations: it supports only "RGB Color" mode and 8 Bits/Channel.
And yes, alpha channel is supported.

#1
02/03/2012 (5:47 am)
It' seems great!

Hum... 8bits/channel == (8 bits for RED, 8 for GREEN, 8 for BLUE and 8 for ALPHA) ? so it's for every pictures of 24 bits or of 32 bits with a single layer? isn't?

Or, is a channel is in fact a layer? can you specify please?
#2
02/03/2012 (5:57 am)
It means 8 bits per channel (RGBA), so it should be 24-bit bitmap or 32-bit bitmap (without/with alpha respectively).
The color mode is set in Photoshop: in menu Image -> Mode -> "RGB Color" and "8 Bits/Channel".

Btw, this doesn't load PSD "layers", it loads pre-generated bitmap from the PSD file.
When Photoshop saves the file, it merges all layers and adds a bitmap internally (inside PSD file), so it can be retrieved it without parsing/loading all layers.
#3
02/03/2012 (6:23 am)
Thank you ;) it's a great resource!
#4
02/03/2012 (12:16 pm)
Fantastic, bank! :) Thank you for sharing it!
#5
02/03/2012 (9:38 pm)
awesome. ..trunk it GG!
#6
02/04/2012 (5:11 pm)
This is perfect! Thanks!
#7
02/04/2012 (5:24 pm)
cool stuff mate, this is deff. getting put in. Thanks for sharing this mate.
#8
02/06/2012 (10:03 pm)
Just add: #include "console/console.h" at the top of the *cpp file

for the messages as
Con::errorf("Can't read -- non-PSD file specified.");

#9
02/11/2012 (11:35 am)
Nice this is a much needed add for T2D as well, It'll make updating my sprite sheets so much easier. Can we get a .fla too?
#10
03/27/2012 (9:31 am)
Just added this in, but was wondering about something in the original source...

, *.jng. *.tga

shouldent that be

, *.jng, *.tga
#11
11/06/2012 (12:01 am)
i don't know your words! because i am chaese. my english is not good !i want get the 3D torque engine. i have see the <3D Game programming all in one>, ---the author is Kenneth C Finney ,form Canada .i love creating games, so you can halp me ?