Custom binary preview

Wednesday, March 14, 2012 0 Comments

In this final post about the new binary preview feature I'm going to show you how to create your custom preview provider.

A lot of game studios are used to use their own graphics file formats. They are able to edit and work with them using internal tools. This tools are able to read the files and show the user the representative values, so this tools have to do something similar to serialization and deserialization. This is more or less what we need to create our custom preview provider.

For the blog-post example I'll create a new binary file format called .hssff, yeah "hssff" from the incomparable David Hasselhoff. This new binary file will contain a jpg image and a couple of values that will be the custom binary file atttributes.

What we need

We only need these two things, we'll see that it's very easy using the c# serialization:

  • A mechanism to translate a .hssff to a Hssff c# object.
  • A mechanism to translate a Hssff c# object to a .hssff file.

Creating the hssff serializable object

First we need to set the object as serializable and implement the ISerializable interface, quite simple:

[Serializable()]
public class Hssff :ISerializable
{
    ...
    ...

The hssff object needs two basic methods implemented:

  • GetObjectData, this is the method in charge of providing the information to be serialized,
  • Hssff constructor, this special constructor will build a new Hssff object from the deserialized object information.

This is the implementation of both methods:

public Hssff(SerializationInfo info, StreamingContext ctxt)
{
    this.HssffImage =
        (Image)info.GetValue("HssffImage", typeof(Image));
    this.HowMachoDavidIs =
        (string)info.GetValue("HowMachoDavidIs", typeof(string));
    this.HeartBrakerValue =
        (int)info.GetValue("HeartBrakerValue", typeof(int));
    this.TheBuchananFactor =
        (int)info.GetValue("TheBuchananFactor", typeof(int));
    this.TheKnightRiderFactor =
        (int)info.GetValue("TheKnightRiderFactor", typeof(int));
}

public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
    info.AddValue("HssffImage", this.HssffImage);
    info.AddValue("HowMachoDavidIs", this.HowMachoDavidIs);
    info.AddValue("HeartBrakerValue", this.HeartBrakerValue);
    info.AddValue("TheBuchananFactor", this.TheBuchananFactor);
    info.AddValue("TheKnightRiderFactor", this.TheKnightRiderFactor);
}

Serialize and deserialize the c# hssff object

I have an extra object in charge of the serialization and deserialization of the Hssff object, it's the HssffSerializer class. Basically, the HssffSerializer class serializes an Hssff into an external file and it also deserializes the object from an external file.

public void SerializeObject(string filename, Hssff objToSerialize)
{
    Stream stream = File.Open(filename, FileMode.Create);
    BinaryFormatter bFormatter = new BinaryFormatter();
    bFormatter.Serialize(stream, objToSerialize);
    stream.Close();
}

public Hssff DeSerializeObject(string filename)
{
    Hssff objectToSerialize;
    Stream stream = File.Open(filename, FileMode.Open);
    BinaryFormatter bFormatter = new BinaryFormatter();
    objectToSerialize = (Hssff)bFormatter.Deserialize(stream);
    stream.Close();
    return objectToSerialize;
}

Custom preview tool

Now that we have the ability to "translate" a .hssff file in both ways, we can create our convert.exe tool needed to generate the preview images for Plastic SCM. Check this blog-post if you don't know what I'm talking about.

Basically, our convert.exe tool has to be able to perform three things:

  • Create a small thumbnail from our hssff object (used in Plastic SCM preview system).
  • Create a full size image from our hssff object (used in Plastic SCM diff system).
  • Save both images into a external file to be read by Plastic SCM.

Actually using the HssffSerializer class we can read the hssff file and recover a Hssff object from it. So let's do it by using it:

private static Hssff ReadHssObject(string hssffFilePath)
{
    HssffSerializer serializer = new HssffSerializer();
    return serializer.DeSerializeObject(hssffFilePath);
}

Now that we have the Hssff file, we can access to the Image inside it. Let's create a thumbnail from it:

private static void GenerateThumbnail(Hssff deserialHssff, string dstFile)
{
    Size thumbnailSize = GetThumbnailSize(deserialHssff.HssffImage);
    System.Drawing.Image NewImage =
        deserialHssff.HssffImage.GetThumbnailImage(
            thumbnailSize.Width, thumbnailSize.Height, null, IntPtr.Zero);

    deserialHssff.HssffImage.Dispose();
    NewImage.Save(dstFile);
}

private static Size GetThumbnailSize(Image original)
{
    int originalWidth = original.Width;
    int originalHeight = original.Height;

    double factor = 1;
    if (originalWidth > originalHeight)
        factor = (double)MAX_THUMNAIL_PIXELS / originalWidth;
    else
        factor = (double)MAX_THUMNAIL_PIXELS / originalHeight;

    Size newSize = new Size(
        (int)(originalWidth * factor),
        (int)(originalHeight * factor));

    return newSize
}

And now a full size image:

private static void GenerateFullImage(Hssff deserialHssff, string dstFile)
{
    deserialHssff.HssffImage.Save(dstFile);
}

If our convert.exe program is invoked with the --thumbnail option, we will generate the thumbnail image. If not, we will generate the full size image. Easy :)

Custom attributes tool

The identify.exe tool is even easier. We need it to do the following:

  • Read all the text parameters from the Hssff object.
  • Print the parameters using the standard output.

You can use again our ReadHssObject method inside the HssffSerializer class in order to "translate" the file into the Hssff object. Then, we just need to print those values:

static void Main(string[] args)
{
    string hssffFilePath = args[0];
    Hssff deserializedHssff = ReadHssObject(hssffFilePath);
    IdentifyHssff(deserializedHssff);
}

private static void IdentifyHssff(Hssff deserializedHssff)
{
    Console.WriteLine(
        string.Format("Heart breaker value: {0}",
            deserializedHssff.HeartBrakerValue));
    Console.WriteLine(
        string.Format("How Macho David is: {0}",
            deserializedHssff.HowMachoDavidIs));
    Console.WriteLine(
        string.Format("The Buchananan factor: {0}",
            deserializedHssff.TheBuchananFactor));
    Console.WriteLine(
        string.Format("The Knight Rider factor: {0}",
            deserializedHssff.TheKnightRiderFactor));
}

Plastic SCM will read the standard output of our identify.exe program and the values read will be shown on the Properties section in the preview and diff feature.

Adding our custom preview tool

Now that we have all the external tools to provide Plastic SCM with images and properties descriptions, we need to connect them to Plastic SCM.

Custom template configuration
  1. Open the Plastic SCM Preferences window.
  2. Inside the Preview Tools tab select Add.
  3. Choose the User-defined tool option in the template provider section.
  4. Type a name for the template.
  5. Add the full path to the convert.exe tool.
  6. Set the following line for the thumbail preview command line options: -thumbnail "@src" "@output".
  7. And this other for the full image size: "@src" "@output"
  8. Finally, set the full path to the identify tool.
  9. And configure the command options as follows: "@src"

Testing it

:) Let's give it a try with two revisions of a .hssff file. This is the first revision of our brand new topSecret.hssff file:

First revision

And this is going to be the second revision:

Changed revision

Let's check the differences before committing the file:

Custom preview differences

It's awesome how we can now compare the differences of our custom files format, you can extend the complexity with your real custom files, enjoy it!

All the source code used in this blog-post is available here.

Note: This example was done using c# but you can use your favorite coding language or even more hacker, using command line scripts!

0 comentarios: