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!

Instructions:
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):
A few limitations: it supports only "RGB Color" mode and 8 Bits/Channel.
And yes, alpha channel is supported.

Instructions:
- Paste the code (below) into a new file: Engine/source/gfx/bitmap/loaders/bitmapPsd.cpp or save© this file.
- Re-generate projects (run generateProjects.bat) or manually add the file to solution.
- Build. While the engine is linking, continue to next step (update scripts)
- In script: tools/materialEditor/main.cs: in function MaterialEditorPlugin::onWorldEditorStartup( %this )
- In script: tools/materialEditor/scripts/materialEditor.ed.cs: in function MaterialEditorGui::searchForTexture(%this,%material, %texture)
Add *.psd to the list for MaterialEditorGui.textureFormats
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.
About the author
Game developer.
#2
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.
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
for the messages as
Con::errorf("Can't read -- non-PSD file specified.");
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
shouldent that be
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 ? 
Torque Owner Jean-louis Amadi
Game Alchemy Entertainment
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?