Extended image content processor Problems
You want to extend the default image content importer to control pixels, or you want to learn the content pipeline ).
Solution
Because XNA already provides a content importer that uses an image file as the source and eventually creates it as a Texture2D object, all you have to do is expand this content importer. In this tutorial, you can call the ReplaceColor method of the PixelBitmapContent helper class, which is provided by the content pipeline framework.
Note:If you are only interested in the alpha color, the image content processor automatically turns the pixel red into transparent. This tutorial describes how to extend the content importer.
Working Principle
This tutorial describes content pipelines and how to expand existing content processors. Therefore, it is very important to understand the workflow of content pipelines, as shown in 3-5.
Content Pipeline)
Before using an image file, you need to load it first. The loading process includes reading bytes, selecting useful data, and extracting data if necessary.
This is also true for other materials, such as 3D models. After model data is loaded from a file and processed in large quantities, a model object is created based on the data.
The entire processing process starts with a file on the disk and finally generates objects available for XNA managed by the content pipeline. In fact, each clip has its own content pipeline, as shown in Figure 3-5. A complete content pipeline consists of importer, processor, and serializer) and deserializer.
Figure 3-5 content pipeline Workflow
During compilation (Press F5 to compile), the source file will be read from the disk, its content will be processed, and the final result will be serialized to A. xnb binary file. You can find these. xnb files in the Content subdirectory of the directory where the. exe file is located. When the game is running, the. xnb binary file will be deserialized, so that all useful information becomes available without further processing. The most obvious advantage of this method is that all the processing processes only need to be performed once (during compilation), rather than every time the game runs. The second advantage is that. xnb files are platform-independent and can be used on both PC and Xbox 360 platforms.
Let's analyze the compilation process, because this process is executed every time the project is compiled. This process is divided into three sub-processes:
- Importer: reads source files and extracts useful data. The data is stored in a specified standard format. For example, for a model, the standard format is NodeContent object and for an image, the standard format is TextureContent object. This standard format is called a DOM object. You can see a table with the default DOM object in Table 3-1.
- Processor: Processes data contained in DOM objects and generates available objects in the game. For example, for a model, a processor can add normal data, calculate the tangent, and set effect.
- Serializer or TypeWriter: defines how to generate. xnb binary files from the output of the processor.
An additional advantage of this method is that when you want to change something during the compilation process, you may only need to change one of the Child processes. For example, if you want to create a model in a format not supported by XNA, you only need to write a new import tool that reads files and creates a NodeContent object, you can leave other work to the default content Pipeline component, because the default processor of NodeContent gets your object from there.
At runtime, only one small process needs to be executed:
- Anti-stringalizer or TypeReader: defines how to build game objects from binary data streams stored in. xnb files. Because computing is not required here, this process takes almost no time compared to the compilation process.
XNA comes with many default content loaders and processors. Combine TextureImporter and TextureProcessor to import almost any image format. Combine Ximporter, FbxImporter, and ModelImporter. You can import the. x or. Fbx model.
Note:Separating the import and processor proves useful. Both Ximporter and FbxImporter can import data from the disk and format them into a simple NodeContent object, both of which are passed to ModelProcessor for heavy work.
Table 3-1 default content importer and processor in XNA Framework
The XNA content pipeline comes with a default serializer that writes these objects to a binary file during compilation and an anti-stringalizer that reconstructs the object from a binary file during runtime. .
Use components of the default content pipeline framework
The key to writing/scaling a content processor is to reuse components that already exist in the content pipeline as much as possible.
In this tutorial, you will extend the default TextureProcessor so that you can make changes to the image data before it is loaded into the XNA project. During compilation, you want to read images from files and store the content in a 2D array to form images. You also want to change some pixels and put the results in a. xnb file.
When running a program, the. xnb file will read from the file and contain the changes you have applied.
First, read files from the disk and convert them into a 2D color array (regardless of the format of the image file), you should use the default importer.
Then, you need to extend TextureProcessor. This tutorial focuses on how to set a custom content pipeline, so you only need to change all the black to white. For more advanced applications, see the next tutorial.
Make sure that the output of the processor is a TextureContent object, so that you can save it. the xnb file can be loaded using the default deserialization tool. xnb file.
Figure 3-6 shows the entire process. Find the extended processor and the default component you borrowed from XNA.
Figure 3-6 Location of the content pipeline processor you will override
In this book, each time you process a content pipeline, I will display an image similar to Figure 3-6, so that you can clearly know which part you will process yourself. To expand an existing content processor to expand an existing content processor, You need to perform several steps first. Although these steps are for an experienced. for NET programmers, it is very simple. I will list them. You can refer to these steps in future tutorials. The following sections in this chapter will explain these steps.
- Add a new content pipeline project to the solution.
- Add a reference to Microsoft. XNA. Framework. Content. Pipeline in the new project.
- Add a pipe namespace.
- Specify the part to be extended (that is, the method you want to rewrite ).
- Compile a new content pipeline project.
- Add a reference to the new project in the main project.
- Select the new processor for the clip.
- Set Project dependencies.
- After initializing everything, write the code in the method created in step 1.
Add a new content pipeline project in Solution
To expand or create a new content importer/processor, you need to add a new project in the solution. Right-click your solution and select Add> New Project, as shown in Figure 3-7. In the displayed dialog box, select Content Pipeline Extension Library and enter a proper name, as shown in Figure 3-8.
Figure 3-7 Add a new project in Solution
Figure 3-8 create a content pipeline extension library
The project contains a new file that contains your defined namespace and a default Processor class. The new project has been added to the solution, as shown in Figure 3-9.
Figure 3-9 Content pipeline project added to Solution
Add a reference to Microsoft. XNA. Framework. Content. Pipeline
In the new project, make sure that you reference Microsoft. XNA. Framework. Content. Pipeline (version 2.0.0.0. Open the Project menu and select Add Reference. Add the correct reference from the list, as shown in Figure 3-10.
Figure 3-10 select XNA pipeline reference
Use a code block to add a Pipeline namespace
You also need to link the compiler to the new namespace and add the following code blocks:
using System.IO; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Content.Pipeline.Processors; using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
Part to be extended
The file already contains a ContentProcessor template. Replace the Code with the following code to specify the input and output of the new processor. All these custom processors use their base class (default texture processor) to process input, so this new ContentProcessor works in the same way as the default texture processor:
namespace MyImagePipeline{ [ContentProcessor(DisplayName = "ExtendedExample")] public class ExtentedTextureProcessor : TextureProcessor { public override TextureContent Process(TextureContent input, ContentProcessorContext context) { return base.Process(input, context); } }}
In this tutorial, we will extend the default TextureProcessor, so you need to inherit from the TextureProcessor class. For this reason, your Process method accepts a TextureContent object and outputs a TextureContent object.
After a while, you will compile a real Process method, but now this method only inherits from the default TextureProcessor class.
Note:ExtendedExample is the name of your new custom processor. If you omit the DisplayName label, your processor uses the class name. In this example, It is ExtendedTextureProcessor.
Note:Make sure that this DisplayName is placed in [ContentProcessor. If not, XNA will not treat this class as a content processor. The result is that your processor is not displayed when you browse the list of available content processors.
Compile a new content Pipeline Project
In the next step, you need to add the reference of this new project to the main project, so you need to compile this project first. Press F6 to compile, and press F5 to compile and run the entire solution.
Note:This compilation process takes several seconds. You can see the information about this process in the lower left corner of the window.
Add the new reference to the main program
After compiling the custom processor, you also need to apply it to the main program. Find Content in solution, right-click References, and select Add Reference, as shown in Figure 3-11.
Figure 3-11 Add a reference to a custom content Pipeline Project
In the displayed dialog box, find the Projects tab and select your content pipeline project from the list, as shown in Figure 3-12.
Figure 3-12 select a content Pipeline Project
Note: If the content pipeline project is not in the list, you must have omitted the previous compilation steps.
Select the new processor to process an image file
When you import an image file to a project, you can choose a new content processor to process the image file, as shown in Figure 3-13.
Figure 3-13 select a custom content processor to process the image
Now, when you compile a project, your custom processor can be used to process image files!
Set Project Dependencies
Every change you make to the custom content processor requires manual re-Compilation of the project. To solve this problem, you can make the main project dependent on the Content pipeline project, so that each time you recompile the main project, the content pipeline project will be compiled first (if you make some changes after the last compilation ). You can right-click the main Project and select Project Dependencies to set the dependency. In the pop-up dialog box, you need to select that the main program depends on the second project, as shown in Figure 3-14. In this way, when you press F5 to compile the main program, the. dll file of the content pipeline project will be compiled first.
Figure 3-14 select a project dependency
Extended default texture Processor
Now everything is initialized. You can write custom code.
You have created an ExtendedTextureProcessor class that inherits from the default TextureProcessor class. You have also declared that the Process method will be rewritten. All content processor is called Process and accepts a DOM object (in TextureProcessor case, TextureContent object, as shown in Table 3-1) and a ContentProcessorContext object as the parameter. This context object is used to create multiple builds (nested builds ). For example, when importing a Model, the object will contain the names of all texture files. These names need to be loaded together with the Model. You can learn more about this in the 4-12 tutorial.
As shown in Table 3-1, in the case of TextureProcessor, you need to return a TextureContent object. In this case, the texture serves as the input and output of the processor. The current processor only transmits the input to the base class:
[ContentProcessor(DisplayName = "ExtendedExample")] public class ExtentedTextureProcessor : TextureProcessor{ public override TextureContent Process(TextureContent input, ContentProcessorContext context) { return base.Process(input, context); }}
This step uses the Process method of the TextureProcessor class to Process the input (because you inherit from the default TextureProcessor class) and return the result image, so you are ready to extend this processing Process. Until now, you simply pass the input directly to the output, so your processor will get the same result as the default TextureProcessor.
Note:In this case, TextureProcessor is special because the input and output objects are of the same type. In more complex cases, you want the base. Process Method to convert an input object into an output model. For example, if you extend the model processor, you first need the base. Process Method to convert the input NodeContent object into a ModelContent object, which involves a lot of work. Once you have the default ModelContent object, you can easily expand/change them. See tutorial 4-12 to learn an example of an extended model processor.
In this tutorial, you do not want the input to be passed to the output immediately. You want to change some color values. One advantage of using standard DOM objects is that you can use the defined default content pipeline. For example, the TextureContent class has a useful ConvertBitmapType method that can change the format of the texture content. The following code changes the color of the content of the texContent object:
TextureContent texContent = base.Process(input, context);texContent.ConvertBitmapType(typeof(PixelBitmapContent<Color>));
Because the TextureContent class is an abstract class, if it is a 2D image, the texContent object will be instantiated as a TextureContent2D object (or a TextureContent3D or TextureContentCube object ). This means that an image can have multiple faces and multiple mipmaps (see tutorial 3-7 to learn about mipmap ). The following code selects the first face and the first mipmap. A simple 2D texture has only one face and one mipmap level.
PixelBitmapContent<Color> image = (PixelBitmapContent<Color>)input.Faces[0][0];
The PixelBitmapContent class has a useful ReplaceColor method, which can replace the specified color in the image with another color:
Color colorToReplace = Color.Black; image.ReplaceColor(Color.Black, Color.White);
This is what your custom processor does and finally returns the TextureContent object:
return texContent;
During compilation, this object will be passed to the serializer, which saves the object as a binary file. During the running process, the binary file is loaded by the anti-stringalizer and a Texture2D object is created. Now make sure to import an image in the XNA project, select a custom processor to process the image, and load the Texture2D object in the LoadContent method:
myTexture = Content.Load<Texture2D>("image");
Multiple Face/Mipmap
In this example, you inherit from the TextureContent processor, which will generate the mipmap (instead of SpriteTextureProcessor ). For more information about mipmap, see tutorial 3-7. If the imported image is a cubic image, the texture has six faces. To complete the tutorial, you also need to traverse the face and mipmap code:
for (int face = 0; face < texContent.Faces.Count; face++) { MipmapChain mipChain = texContent.Faces[face]; for (int mipLevel = 0; mipLevel < mipChain.Count; mipLevel++) { PixelBitmapContent<Color> image = (PixelBitmapContent)input.Faces[face][mipLevel]; image.ReplaceColor(Color.Black, Color.White); }}
The first index of the Faces attribute is the face of the image. The standard 2D image has only one face, And the cubic texture has six faces. The second index is the mipmap level, and only one level is available for images that do not use the mipmapp. This code converts the selected color (Black in this example) to white for each mipmap level of each face in the texture: in actual code, the blue hair of the girl is replaced with yellow ).
Code
Below are all the code for extending the default Content processor. Remember to make the code work normally, you need to add a reference to Microsoft. XNA. Framework. Content. Pipeline in the new project.
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Content.Pipeline.Processors; using System.IO; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; namespace MyImagePipeline{ [ContentProcessor(DisplayName = "ExtendedExample")] public class ExtentedTextureProcessor : TextureProcessor { public override TextureContent Process(TextureContent input, ContentProcessorContext context) { TextureContent texContent = base.Process(input, context); texContent.ConvertBitmapType(typeof(PixelBitmapContent<Color>)); for (int face = 0; face < texContent.Faces.Count; face++) { MipmapChain mipChain = texContent.Faces[face]; for (int mipLevel = 0; mipLevel < mipChain.Count; mipLevel++) { PixelBitmapContent<Color> image = (PixelBitmapContent<Color>) input.Faces[face][mipLevel]; image.ReplaceColor(Color.Black, Color.White); } } return texContent; } }}