Monday, February 20, 2012

DPX Image Viewer (Using Qt4)

This is a small project to view DPX image sequence, and also write it.

Previewing DPX image is surprisingly simple, because they use no compression, although some of them may use packed pixels (example: 10-bit DPX images).

In this example, we are using RED ONE camera's clips, which being decoded into single image per frame, producing image sequence with file name format like A001_C001_ABCDE.000025.dpx.

First we decode the clip using either software provided by RED, that can be downloaded from it's website, or any other programs that can decode RED CODE RAW to produce the sequences. In this case we only concern about the following options:
  1. Bit-depth - can be either 10 or 16-Bits per channel, always interleaved.

  2. Byte ordering - can be either LSB (the default) or MSB.
Second step is to write the DPX file structure according to SMPTE standard, or you can use mine below:



Now lets construct our UI to show the DPX, in this example we'll be using Qt4 (you can use Mac's native GUI if you like, the example is on the way). I'm using Qt because of it's simplicity (you don't have to write any code for a complete Application just to show an image (or any widget). For example, to show a label the code can be as simple as the following:



And the result should be as follow (on Mac OS X):



Of course it looks different than what we saw on Linux (the label was displayed exactly as "Label", without any decorations, and it's the only thing inside the Window. It's because Qt uses the size of the label (which is autosize) to construct the Window container, try to resize the window to some differences.

We will discuss GUI aspect later in this blog, for now we need to implement some of DPX loading code, which doesn't have anyting to do with the GUI.

Please note that in order to use this approach, you will have to download Qt4 toolkit from it's website here.

Now create a new C++ source (name it main.cpp), with the following code, you can copy paste them if you like, or just download it.



I assume that you are using my DPX header structures above, and save them in a file named "dpx.h".
To compile the code you need 'qmake' and a small file with extension .pro, create it with the following text:




First, run qmake from Terminal using command like this:

qmake -o TestLabel TestLabel.pro

On Mac OS X, that should produce a project file named "TestLabel.xcodeproj" in the same directory.

Now run xcodebuild with no arguments to get an output in the same directory:

xcodebuild

If there's no mistake, you will get TestLabel.app in the same directory.
Either run it from Terminal using:

open TestLabel.app

or just double-click it's icon from Finder.

First it will ask for user input, navigate to your existing DPX file (preferably created using step discussed earlier in this blog), then it will try to load the image and display it as follow:



If you get no result, most likely because the DPX image is not in 10-bit format, byte order is in MSB format, or uses different pixel format than RGB interleaved (ImageHeader::ImageElement[0].descriptor should be 50 for this example).

There's nothing special with this example, just provide you with a simple step to begin developing applications for reading/writing or manipulating DPX Image file.

This example uses C++, but if you prefer C, you can use other GUI tool that does not use C++ by default, like Cocoa (for Mac OS X), or Gtk+ for Linux.

This example ignores a bunch of important fields that should be checked in a DPX image file, but you should do the most important field is the 32-bit magic number at the beginning of the file.  This magic should be 0x53445058, if you get 0x58504453 then your machine's byte order is different than the one used for writing the DPX, and any other values would be read incorrectly!

Machine's byte ordering could be confusing. A simple way to understand DPX magic is, in my opinion, to assume all DPX was being written with 0x53445058 magic, so if it is being read as 0x58504453, then the reading machine should flip the bytes, regardless of the endianess of these machines.

You can flip 32-bit values using simple macro as follow:


#define FLIP32(val) \
(uint32_t)(((val & 0xFF000000)>>24) | ((val & 0xFF0000) >> 8) | \
((val & 0xFF00) << 8) | ((val & 0xFF) << 24))


The byte order convention used for this magic seems to have nothing to do with the pixel data, I've experience a weird color due to wrong ordering of ARGB32 values, which displayed correctly on Mac, but the Red and Blue channels seem to be interpreted incorrectly by Qt4 on Linux (using the very same source code).

Some software also writes 10-bit DPX using Method B for packing, according to value found in packing field of the Image Header section, but the pixels were actually packed using Method A!

How to display the image if you're not using Qt?
Actually, our LoadDPX() function above can be split into a few more specific functions and provide the code to render 10-bit DPX image data for different image viewer. An image viewer can display images using different color space, or Pixel Format which using different color channel ordering than the actual image (this is why we need the for (...) block to reorder the data above), and for 10-bit per channel image data, a macro such as UINT_10_TO_8() above might be needed.

A 10-bit per channel DPX image data actually being stored using the following format (assuming the RGB format):

METHOD A:

|---------------------------------------- 32 Bit Data ---------------------------------------|
R R R R R R R R R R G G G G G G G G G G B B B B B B B B B B 0 0

METHOD B:


|---------------------------------------- 32 Bit Data ---------------------------------------|
0 0 R R R R R R R R R R G G G G G G G G G G B B B B B B B B B B

So the simplest way to display such images is to use RGBA or RGB32 pixel values (for RGB32 values, simply fill alpha channel with 0xFF), which is 32-Bit values.

In other words, we shrink the pixel data into 8-Bit per channel for display.

But some image viewer (or Bitmap data interpreter), like NSBitmapImageRep object on Cocoa, can take 16-Bit per channel images.

Here's an example loading DPX using Cocoa and NSBitmapImageRep:




The above code uses RGB16 structure, which defined as follow:

typedef struct RGB16 {
uint16_t redgreenblue;
} RGB16;


If you get incorrect color, just swap the Red and Blue channel, or use the following struct:

typedef struct BGR16 {
uint16_t bluegreenred;
} BGR16;






The image seems to be displayed incorrectly, this is because we are using NSMakeRect() with dimension 720x576, and don't adjust the size, but you can adjust the size of the window :-)

Actually, instead of using NSBitmapImageRep, you might want to use CGImage and render the image into NSView using the following example of code:


CGImageRef img = [image CGImage];
CGFloat fWidth = rect.size.width;
CGFloat fHeight = (fWidth * CGImageGetHeight(img)) / CGImageGetWidth(img);
CGFloat yOffset = (rect.size.height - fHeight) / 2;
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextDrawImage(context, CGRectMake(0, yOffset, fWidth, fHeight), img);




Now you can resize the window anyway you want, and the image will still be displayed in the correct aspect ratio!

The above code can also be applied to NSBitmapRep as well, but I use CGImage just to show you the possibility.


How to load DPX, apply some transformation to it, and save it back as new image?
It's quite simple, here's an example to save as PAL 720x576 image. It's using CGBitmapContext from previous Cocoa example, but of course you will have o modify the main() function to show at least a command button for this purpose. So now main() can be as follow:


int main(int argc, const char** argv)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
NSOpenPanel* dlg = [NSOpenPanel openPanel];
[dlg setTitle:@"Open DPX File"];
[dlg setCanChooseFiles:TRUE];
[dlg setCanChooseDirectories:FALSE];
[dlg setAllowedFileTypes:[NSArray arrayWithObject:@"dpx"]];
if ([dlg runModal] != NSOKButton)
return 0;
DPXView* view = [DPXView new];
[view loadDPX:[[dlg URL] path]];
NSUInteger styles = NSTitledWindowMask|NSClosableWindowMask|NSResizableWindowMask;
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 720, 576+50)
  styleMask:styles
backing:NSBackingStoreBuffered
  defer:NO];
NSView* pContents = [window contentView];
NSUInteger autoMask = NSViewMinXMargin|NSViewMaxXMargin|
NSViewMinYMargin|NSViewMaxYMargin|
NSViewWidthSizable|NSViewHeightSizable;
[view setFrame:NSMakeRect(0, 50, 720, 576)];
[window setTitle:@"Test DPX"];
[pContents addSubview:view];
[view setAutoresizingMask:autoMask];
NSRect rcBtn = NSMakeRect(10, 10, 50, 35);
NSButton* btnSave = [[NSButton alloc] initWithFrame:rcBtn];
[btnSave setTitle:@"Save as PAL size"];
[btnSave setBezelStyle:11];
[pContents addSubview:btnSave];
[btnSave sizeToFit];
[btnSave setTarget:view];
[btnSave setAction:@selector(saveAsPAL:)];
[window orderFront:NULL];
[NSApp run];
[pool release];
return 0;
}



Have fun!



Thursday, November 10, 2011

Create Button Using Pure XCB Code on Mac OS X

First create two images sing GIMP or any other image editor of your favorite, but it must be saved as C code.
The images are only 1 x 36 pixels each (yes, that's correct, only 1 pixel width). It will be repeated automatically.

Here's my example on the button, modify it according to your needs:


I write the above code my self, but pasted into PasteBin just to get the syntax highlighting style, it's easier to read the code this way.

The following is the output of the program:

XCB Button Test


NOTE:
Mac OS X Snow Leopard is using version of libxcb which is located at different location (as compared to Linux), so you must add -I/usr/X11/include -L/usr/X11/lib -lxcb -lxcb-atom to compile the code.

The complete compilation command should be:

gcc -I/usr/X11/include -L/usr/X11/lib -lxcb -lxcb-atom -o MacButton-Test MacButton.c

Further more, libxcb itself is a work on progress, if using the newer version (X11R7.6) you don't even need to include xcb_atom.h, and there's no WM_NAME, it's defined as XCB_ATOM_WM_NAME and XCB_ATOM_STRING. These items are actually just a typedef of uint32_t (integer 32 bit).

You can try it now!