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.
To initialize we only need to add the InitializeDecal node to our initialization flow and set the texture as input parameter.
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
And that’s all!
Tutorial files
You may also like:
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!