共用方式為


2D Cell Animation Part II: Open Packaging Conventions

Last week we looked at how we could use the Ink APIs in WPF to create 2D animation authoring application. This week we are going to look at how we can use the Open Packaging Conventions APIs in WPF to save and load our animation. Once you can get the source on the .NET 3.0 Community site.

The Open Packaging Conventions (OPC) APIs can be found in the System.IO.Packaging namespace. Before we get into the code let’s look at the ideas behind the OPC. The Office 2007 file formats use OPC along with the XPS file type. One of the reasons to use OPC is that we only have to learn one API to be able to read and write many formats. An OPC based file consists of three parts; the Package which holds the other parts, the PackageParts which holds the data and the PackageRelationships which describe what the PackageParts are and how they are related to other parts.

We are going to use a ZipPackage which is provided as a default implementation of the Package abstract class. The ZipPackage stores the package as a zip file and the parts as xml files. This is very helpful when developing a new file format because we can just change our file extension to .zip and explore how the file is laid out.

Now we’ll take a look at how we save a file using OPC. Here is the save code from Ink-A-Mator:

private const string ResourceRelationshipType =

@"https://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource";

// Create a new ZipPackage and save the animation in it.

using (Package p = ZipPackage.Open(fileName, FileMode.Create))

{

// Save out each of our frames.

for (int i = 0; i < this.frames.Count; i++)

{

// We need to create a part to save to.

PackagePart part = p.CreatePart(PackUriHelper.CreatePartUri(new Uri(string.Format("Frame{0}{1}", i, ".frm"), UriKind.Relative)), "Ink/WPF");

 

// Create a relationship for our part so that we can walk through relationships on open.

p.CreateRelationship(part.Uri, TargetMode.Internal, ResourceRelationshipType);

 

// Get a memory stream to save our part.

using (Stream s = part.GetStream())

{

// save each frame into the stream

this.frames[i].Save(s);

 

// don't forget to close the stream.

s.Close();

}

}

}

 

Let’s look at each line in detail. The first line declares a const string ResourceRelationshipType which is just a URI for our part type. The URI has no significance it is just what I have chosen to represent my frame parts. Next we create our ZipPackage using the static Open method. Notice that we have wrapped this line in a using statement because the Package class implements IDisposable. Then we loop through our collection of animation frames and create a PackagePart and PackageRelationship for each frame. To create the part we call the CreatePart method on our package instance. This method takes two arguments the URI of this part and the type of this part. We use the PackUriHelper.CreatePartUri static method to help us create the part. We just want to store the name of each frame in the form “Frame0.frm”. The StrokeCollection stores the .frm files in Ink Serialized Format (ISF).

 

Next we are going to use our package to create a PackageRelationship. We use relationships like a table of contents for the OPC file, it allows us to ask for parts by relationship when we read files back. Then we’re going to use the part instance we created to write our frames content to using the GetStream method. This method returns a stream that we can use to write our content too. Remember that our frames are just StrokeCollections which have a Save method to write to a stream, so we just have to pass the part stream to the Save method. Last we call close on the part stream.

 

The image below shows the layout of our package if we unzip our saved file. Now we will examine the parts in the package.

 

 

The _rels folder contains a single file .rels which represent the part relationships we created:

 

Package/_rels/.rels:

 

<Relationships xmlns="https://schemas.openxmlformats.org/package/2006/relationships">

<Relationship Type="https://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource" Target="/Frame0.frm" Id="R1856367c1f81411f" />

<Relationship Type="https://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource" Target="/Frame1.frm" Id="R77e932075fd84ad2" />

<Relationship Type="https://schemas.microsoft.com/samples/Ink-A-Mator/Required-Resource" Target="/Frame2.frm" Id="R2485e76da2c64a2d" />

… All the other 48 frames

</Relationships>

 

Here we can see that the .rels file contains a collection of relationships. The Type attribute of a Relationship is useful when we load this file as we will see shortly. The Target attribute points to another file in our Package which is a PackagePart, which holds the contents of a single frame. As seen in the image above. Lastly the [Content_Types].xml file contains the types we have defined for this Package.

[Content_Types].xml:

<?xml version="1.0" encoding="utf-8"?>

<Types xmlns="https://schemas.openxmlformats.org/package/2006/content-types">

<Default Extension="frm" ContentType="Ink/WPF" />

<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />

</Types>

We have two types in our Package. Our Frame type with the extension .frm and the relationships with the extension .rels.

That’s all there is to saving a file using the OPC file format. Now we will take a look at our Load method and see how easy it is to open a file using OPC.

 

// Open our file using the Open Packaging APIs

using (Package p = ZipPackage.Open(fileName, FileMode.Open))

{

// We pull frames by part so we need the package relationship.

PackageRelationshipCollection rels = p.GetRelationshipsByType(ResourceRelationshipType);

 

// Walk through our frames in part form.

foreach (PackageRelationship rel in rels)

{

// Get the address of this frame part.

Uri uri = PackUriHelper.ResolvePartUri( new Uri("/", UriKind.Relative), rel.TargetUri);

 

// Get the frame part with the address.

PackagePart part = p.GetPart(uri);

 

// Put the frame to memory

using (Stream s = part.GetStream())

{

// A frame just consists of a stroke collection so load it.

this.Paper.Strokes = new StrokeCollection(s);

 

// Close our stream.

s.Close();

 

// Move to the next frame.

OnNextFrame(this, new RoutedEventArgs());

}

}

 

// Move back to the last frame.

OnPreviousFrame(this, new RoutedEventArgs());

}

 

Most of this code should be familiar to you now because it is very similar to the Save method. Once again we use the ZipPackage.Open method, this time we are using the Open FileMode. Next we are going to call GetRelationshipsByType and pass our frame relationship type. This method is going to return us a collection of relationships. This is basically the object form of the .rels file we looked at. Since we have one relationship for each frame we are going to loop through the relationships to create each file. Using the ResolvePartUri static helper method we pass in target uri from the relationship. Once we have this URI we can call GetPart passing the URI which will return us an instance of frame PackagePart. Now we just have to call the GetStream method on the part and create a new instance of a StrokeCollection passing the stream of our ISF file. The other calls here were explained last week and deal with the Ink API.

The OPC APIs have some other interesting functionality that we didn’t make use of here such as digital signatures, but you should have enough of an understanding to read and write your own files. For more information on OpenXML and working with the Office 2007 formats check out the articles on the Open XML Developer website.

Comments