Continuing from the tutorial where we learned how to initialize a Direct2D context and perform basic drawing, in this new sample we are going to learn how to load bitmap images and draw them directly or via brushes. So go ahead and read it if you haven’t and clone the source code as we are going to use it as a starting point. We will be needing some images to load and draw, too, so use the ones you have at hand. We will be using the free Platformer Tiles from Kenney.nl. Select two images you like, add them to the project and set their Build Action to Content. Now, in the MyViewProvider class, define two member variables of type SharpDX.Direct2D1.Bitmap1 and one of type SharpDX.Direct2D1.BitmapBrush1. We will use them to store the image data and the brush object properties respectively.
Image loading with WIC
For loading the images, we will be using the Windows Imaging Component API under the SharpDX.WIC namespace, which is implemented in the SharpDX.Direct2D1.dll assembly. This is a native and extensible Windows API that consists of a set of low-level codecs for encoding and decoding commonly used image file formats such as JPG and PNG. Relying on it we will skip the tedious job of implementing our own file format parsers. To manipulate WIC objects, first we need to instantiate an ImagingFactory object, which doesn’t need any constructor parameters. Then, we need to open the file that holds the bitmap data. For this, we can use SharpDX’s NativeFileStream, a very useful class that is similar to the old .NET’s FileStream and is more straightforward than WinRT’s StorageFile. And don’t forget to prepend Package.Current.InstalledLocation.Path to the file’s path because using a relative path won’t work.
- ImagingFactory imagingFactory = newImagingFactory();
- NativeFileStream fileStream = newNativeFileStream(Package.Current.InstalledLocation.Path + filePath,
- NativeFileMode.Open, NativeFileAccess.Read);
Now we can create a BitmapDecoder instance, passing both the factory and the file stream. A third parameter is needed, we can set it to DecodeOptions.CacheOnDemand for the time being as we won’t be needing to take advantage of special cache handling. With this, now we can call GetFrame on the newly create instance to retrieve the frame index 0 (since a static image will only have one frame, while other filetypes like GIF can have more than one).
- BitmapDecoder bitmapDecoder = newBitmapDecoder(imagingFactory, fileStream, DecodeOptions.CacheOnDemand);
- BitmapFrameDecode frame = bitmapDecoder.GetFrame(0);
Having obtained the frame, now we can create a SharpDX.Direct2D1.Bitmap1 with it, can’t we? Not so fast. The pixel data is still encoded in the image’s original colour format, and we should be converting all our bitmaps to the same pixel format for the sake of normalization; for this, we can use SharpDX.WIC.FormatConverter. Start by instantiating a copy, passing the imaging factory as the only constructor parameter, and call its Initialize method specifying the bitmap frame we previously extracted and the desired target pixel format. The catch is that default pixel formats are stored as the unique identifier associated to the format encoder/decoder (a unique System.Guid), and the available ones are declared inside SharpDX.WIC.PixelFormat. We will be using RGBA, 32 bits per pixel with premultiplied alpha (PixelFormat.Format32bppPRGBA), but BGRA can be used too (and must be used if working with a software device since it’s the only one supported).
- FormatConverter converter = newFormatConverter(imagingFactory);
- converter.Initialize(frame, SharpDX.WIC.PixelFormat.Format32bppPRGBA);
Having the correct pixel format, we can finally create the desired SharpDX.Direct2D1.Bitmap1 through the SharpDX.Direct2D1.Bitmap1.FromWicBitmap function. It receives a Direct2D context and a SharpDX.Direct2D1.BitmapSource. And guess who inherits from that class? That’s it, the format converter we previously created.
- newBitmap = SharpDX.Direct2D1.Bitmap1.FromWicBitmap(d2dContext, converter);
Creating a bitmap brush
Now we can proceed to create the SharpDX.Direct2D1.BitmapBrush1 brush used for filling geometric shapes. It’s as simple as instantiating it, passing as parameters the Direct2D context, the desired bitmap and a SharpDX.Direct2D1.BitmapBrushProperties1 where we can define the ExtendModeX and ExtendModeY to specify how the brush behaves when drawing regions bigger than the bitmap’s size. You can choose between Clamp, Wrap and Mirror for repeating the last column/row of pixels, repeating the entire image or mirroring it, respectively. The following image shows the Wrap and Mirror modes applied to both axes:
- terrainBrush = newBitmapBrush1(d2dContext, terrainBitmap, newBitmapBrushProperties1()
- ExtendModeX = ExtendMode.Wrap,
- ExtendModeY = ExtendMode.Wrap,
Drawing bitmaps and brushes
Finally we can proceed to draw bitmaps to the screen. As before, this must be done between a BeginDraw/EndDraw block. Let’s start by filling a rectangle with the bitmap brush, centered on the screen, and with a width greater than the bitmap’s size to see the wrapping in action.
- int halfWidth = this.swapChain.Description.ModeDescription.Width / 2;
- int halfHeight = this.swapChain.Description.ModeDescription.Height / 2;
- d2dContext.FillRectangle(newRectangleF(halfWidth – 350, halfHeight, 700, 70), terrainBrush);
But wait, something’s not right, is it? This happens because while we are specifying a region to be filled in coordinates relative to the upper left corner of the screen, the brush uses the origin of coordinates as its point of alignment. We can fix this by specifying a translation SharpDX.Matrix3x2 to offset it and changing the rectangle’s X and Y coordinates to (0, 0).
- d2dContext.Transform = Matrix3x2.Translation(halfWidth – 350, halfHeight);
- d2dContext.FillRectangle(newRectangleF(0, 0, 700, 70), terrainBrush);
Finally, let’s specify another transform matrix and draw a single bitmap. This is achieved via the DrawBitmap function, which receives the bitmap to be drawn, a float value specifying the opacity (alpha value) to be used and an interpolation mode to apply in case the drawing size is smaller or larger than the bitmap’s size.
- d2dContext.Transform = Matrix3x2.Translation(halfWidth, halfHeight – playerBitmap.Size.Height);
- d2dContext.DrawBitmap(playerBitmap, 1.0f, SharpDX.Direct2D1.BitmapInterpolationMode.Linear);
As always, the source code can be downloaded from GitHub. This time the SharpDX libraries are referenced from NuGet and the project is a Windows 8.1 application, making it editable only from Visual Studio 2013.