FeaturedCanvasDecals

in Tutorials, UE4

Dynamic Textures: Decals

With this tutorial we are going to add a tool to our previous drawing canvas to print decals. It’s a good example to explain how to load images and extract their pixel information using C++ code.

Part 1: Drawing canvas
Part 2: Adding decals

C++ section

So, we need to add two new functions, one to initialize the decal tool and other to use it, and the data structure to store the pixel information of the decal.

DrawingCanvas.h

#pragma once

#include <memory>
#include "Engine/Texture2D.h"
#include "Object.h"
#include "DrawingCanvas.generated.h"

UCLASS(Blueprintable, BlueprintType)
class TUTORIAL_CANVAS_API UDrawingCanvas : public UObject
{
	GENERATED_BODY()

public:

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Variables)
		UTexture2D* dynamicCanvas;

	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void InitializeCanvas(const int32 pixelsH, const int32 pixelsV);
	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void UpdateCanvas();
	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void ClearCanvas();

	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void InitializeDrawingTools(const int32 brushRadius);
	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void DrawDot(const int32 pixelCoordX, const int32 pixelCoordY);

	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void InitializeDecal(const UTexture2D* decalTexture);
	UFUNCTION(BlueprintCallable, Category = DrawingTools)
		void DrawDecal(const int32 pixelCoordX, const int32 pixelCoordY);

	UDrawingCanvas();
	~UDrawingCanvas();

private:

	// canvas
	std::unique_ptr<uint8[]> canvasPixelData;
	int canvasWidth;
	int canvasHeight;
	int bytesPerPixel;
	int bufferPitch;
	int bufferSize;

	// draw brush tool
	std::unique_ptr<uint8[]> canvasBrushMask;
	int radius;
	int brushBufferSize;

	// decal tool
	std::unique_ptr<uint8[]> canvasDecalImage;
	int decalWidth;
	int decalHeight;
	int decalBufferSize;

	std::unique_ptr<FUpdateTextureRegion2D> echoUpdateTextureRegion;
	void setPixelColor(uint8*& pointer, uint8 red, uint8 green, uint8 blue, uint8 alpha);
};

DrawingCanvas.cpp

To initialize this tool we need to extract the info of the color channels for each pixel and store it in our decal data structure. If the texture is used in the rendering thread we need to lock the access to the source image in order to make sure nothing else can access it while we are using it, if we didn’t, we could cause memory corruptions or crashes.

void UDrawingCanvas::InitializeDecal(const UTexture2D* decalTexture)
{
	//Access to the mipmap 0 of the texture to get the raw data image
	FTexture2DMipMap* MyMipMap = &decalTexture->PlatformData->Mips[0];
	FByteBulkData* RawImageData = &MyMipMap->BulkData;

	//lock image
	uint8* FormatedImageData = static_cast<uint8*>(RawImageData->Lock(LOCK_READ_ONLY));

	//initialize our data structure variables
	decalWidth = decalTexture->GetSurfaceWidth();
	decalHeight = decalTexture->GetSurfaceHeight();

	decalBufferSize = decalWidth * decalHeight * bytesPerPixel;//4
	canvasDecalImage = std::unique_ptr<uint8[]>(new uint8[decalBufferSize]);

	uint8* decalPixelPtr = canvasDecalImage.get();

	//copy channel color values
	memcpy(decalPixelPtr, FormatedImageData, decalBufferSize);

	//unlock image
	RawImageData->Unlock();
}

As we are using the channel data in the same order than the input structure we can copy the data directly using a memcpy call, other method to access only to a certain channel value is to use an array fixed access like this:

for (int numPixel = 0; numPixel < decalBufferSize; numPixel += 4)
{
	decalPixelPtr[numPixel] = FormatedImageData[numPixel];	//blue
	decalPixelPtr[numPixel + 1] = FormatedImageData[numPixel + 1];	//green
	decalPixelPtr[numPixel + 2] = FormatedImageData[numPixel + 2];	//red
	decalPixelPtr[numPixel + 3] = FormatedImageData[numPixel + 3];	//alpha
}

To draw the decal we can reuse the code of the brush tool, just we need to change the related data structure and use the dimensions of the decal instead of the radius parameter.

void UDrawingCanvas::DrawDecal(const int32 pixelCoordX, const int32 pixelCoordY)
{
	uint8* canvasPixelPtr = canvasPixelData.get();
	const uint8* canvasDecalPixelPtr = canvasDecalImage.get();
	for (int px = -(decalWidth / 2); px < (decalWidth / 2); ++px)
	{
		for (int py = -(decalHeight / 2); py < (decalHeight / 2); ++py)
		{
			int32 tbx = px + decalWidth / 2;
			int32 tby = py + decalHeight / 2;

			canvasDecalPixelPtr = canvasDecalImage.get() + (tbx + tby * decalWidth) * bytesPerPixel;
			if (*(canvasDecalPixelPtr + 3) == 255) // check the alpha value of the pixel of the brush mask
			{
				int32 tx = pixelCoordX + px;
				int32 ty = pixelCoordY + py;
				if (tx >= 0 && tx < canvasWidth && ty >= 0 && ty < canvasHeight)
				{
					canvasPixelPtr = canvasPixelData.get() + (tx + ty * canvasWidth) * bytesPerPixel;
					setPixelColor(canvasPixelPtr, *(canvasDecalPixelPtr + 2), *(canvasDecalPixelPtr + 1), *(canvasDecalPixelPtr), *(canvasDecalPixelPtr + 3));
				}
			}
		}
	}

	UpdateCanvas();
}

Blueprint section

For this example we are going to use our Pinvin texture.

pinvin_texture

To initialize we only need to add the InitializeDecal node to our initialization flow and set the texture as input parameter.

init_decal

To draw the decal into the canvas we can use the same input parameters than the drawDot but with the DrawDecal node of our canvas class

draw_decal_node

And that’s all!

canvas_decal_gif

Tutorial files

Support this blog!

For the past year I've been dedicating more of my time to the creation of tutorials, mainly about game development. If you think these posts have either helped or inspired you, please consider supporting this blog. Thank you so much for your contribution!

Write a Comment

Comment