Here is some sample code showing the Watermarks property in action.
Loading a PNG file from app resources into a ‘Frames’ type watermark
Loading an animated watermark of type ‘Frames’
Creating a GIF watermark
Loading a PNG file from app resources into a ‘Frames’ type watermark
C#
var aWatermarks = new JArray();
jsonDefaultConfig["Watermarks"] = aWatermarks;
var aWatermark = new JObject();
aWatermarks.Add(aWatermark);
// Call a helper function to get a PNG file from app resources, convert it into a hex string, get it into the data structure required by FBRecorder
if (!LoadPngAsWatermark(aWatermark)) {
Console.WriteLine("Error: unable to load PNG sample image to json.");
}
//
// The helper function to load a PNG file from resources to a hexstring for use in the Watermarks property
//
static bool LoadPngAsWatermark(JObject pWatermark)
{
bool bResult = false;
System.Drawing.Bitmap pPngImage = null;
try
{
// Get the PNG file called 'samplePNG' out of resources
System.Reflection.Assembly pAssembly = System.Reflection.Assembly.GetExecutingAssembly();
string sResourcePath = pAssembly.GetName().Name + ".Properties.Resources";
var pResourceManager = new System.Resources.ResourceManager(sResourcePath, pAssembly);
pPngImage = (System.Drawing.Bitmap)pResourceManager.GetObject("samplePNG");
pWatermark["Width"] = pPngImage.Width;
pWatermark["Height"] = pPngImage.Height;
pWatermark["DstWidth"] = pPngImage.Width;
pWatermark["DstHeight"] = pPngImage.Height;
pWatermark["DrawAtX"] = 400;
pWatermark["DrawAtY"] = 400;
pWatermark["BeforeResize"] = true;
pWatermark["Type"] = "Frames";
// We just have one frame in this watermark
var aFrames = new JArray();
pWatermark["Frames"] = aFrames;
aFrames.Add(new JObject());
var aFrame = aFrames[0];
aFrame["Delay"] = 10000;
//Get the R,G,B and A values for each pixel into a hex string - this is what the Watermark property needs.
string sHexImage = "";
for (int y = 0; y < pPngImage.Height; y++) {
for (int x = 0; x < pPngImage.Width; x++) {
Color pixelColor = pPngImage.GetPixel(x, y);
sHexImage += pixelColor.R.ToString("x2");
sHexImage += pixelColor.G.ToString("x2");
sHexImage += pixelColor.B.ToString("x2");
sHexImage += pixelColor.A.ToString("x2");
}
}
aFrame["FrameHexString"] = sHexImage;
bResult = true;
}
catch (Exception exp)
{
Console.WriteLine("Error: LoadGifAsWatermark exception: " + exp.Message);
}
if (pPngImage != null)
pPngImage.Dispose();
return bResult;
}
C++
auto lmbdGetImageFromResources = [](int32_t& i32Width, int32_t& i32Height) ->std::string
{
std::string sResult = "";
// Find a pointer to the PNG image resource
HRSRC hResource = FindResource(nullptr, MAKEINTRESOURCE(IDR_PNG_IMAGE), L"PNG_IMAGES");
if (!hResource) {
printf("FindResource error %d", GetLastError());
return "";
}
HGLOBAL hResourceMemory = LoadResource(nullptr, hResource);
if (!hResourceMemory) {
printf("LoadResource error %d", GetLastError());
return "";
}
LPVOID pResourceData = LockResource(hResourceMemory);
if (!pResourceData) {
printf("LockResource error %d", GetLastError());
return "";
}
DWORD dwResourceSize = SizeofResource(nullptr, hResource);
// Use GDI+ API to decode PNG image to RGBA32
CComPtr<IStream> pStream;
Gdiplus::GdiplusStartupInput gdiplusStartupInput = {};
ULONG_PTR gdiplusToken = 0;
Gdiplus::Status gdipStatus = Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if (gdipStatus != Gdiplus::Ok) {
printf("GdiplusStartup error %d", (int32_t)gdipStatus);
return "";
}
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream);
if (hr != S_OK) {
printf("CreateStreamOnHGlobal error %x", hr);
return "";
}
hr = pStream->SetSize({ dwResourceSize, 0 });
if (hr != S_OK) {
printf("IStream SetSize error %x", hr);
return "";
}
HGLOBAL hGlobal = nullptr;
hr = GetHGlobalFromStream(pStream, &hGlobal);
if (hr != S_OK) {
printf("GetHGlobalFromStream error %x", hr);
return "";
}
uint8_t* pStreamBuffer = nullptr;
pStreamBuffer = (uint8_t*)GlobalLock(hGlobal);
if (!pStreamBuffer) {
printf("GlobalLock error");
return "";
}
memcpy(pStreamBuffer, pResourceData, dwResourceSize);
GlobalUnlock(hGlobal);
hr = pStream->Seek({ 0, 0 }, STREAM_SEEK_SET, nullptr);
if (hr != S_OK) {
printf("IStream Seek error %x", hr);
return "";
}
std::unique_ptr<Gdiplus::Bitmap> pBitmap;
pBitmap = std::unique_ptr<Gdiplus::Bitmap>(Gdiplus::Bitmap::FromStream(pStream));
if (pBitmap->GetWidth() == 0) {
printf("Width is 0");
return "";
}
if (pBitmap->GetHeight() == 0) {
printf("Height is 0");
return "";
}
i32Width = pBitmap->GetWidth();
i32Height = pBitmap->GetHeight();
Gdiplus::Rect rLock = { 0, 0, (INT)pBitmap->GetWidth(), (INT)pBitmap->GetHeight() };
Gdiplus::BitmapData bdLockData = {};
Gdiplus::Status gpStatus = pBitmap->LockBits(&rLock, Gdiplus::ImageLockMode::ImageLockModeRead, PixelFormat32bppARGB, &bdLockData);
if (gpStatus != Gdiplus::Ok) {
printf("pImage->LockBits error %d", (int32_t)gpStatus);
return "";
}
std::string sHexChars = "0123456789abcdef";
// Allocate enough space in the string, 4 bytes per pixel, 2 hex chars per byte
sResult.resize(pBitmap->GetHeight() * pBitmap->GetWidth() * 4 * 2);
int32_t i32ResultIndex = 0;
// Encode image to hex
for (uint32_t h = 0; h < pBitmap->GetHeight(); h++) {
uint8_t* pScanLine = (uint8_t*)bdLockData.Scan0 + h * bdLockData.Stride;
// 4 bytes per pixel
for (uint32_t w = 0; w < pBitmap->GetWidth() * 4; w++) {
sResult[i32ResultIndex++] = sHexChars[(pScanLine[w] >> 4) & 0xf];
sResult[i32ResultIndex++] = sHexChars[pScanLine[w] & 0xf];
}
}
gpStatus = pBitmap->UnlockBits(&bdLockData);
if (gpStatus != Gdiplus::Ok) {
printf("pImage->UnlockBits error %d", (int32_t)gpStatus);
return "";
}
return sResult;
};
int32_t i32Width = 0;
int32_t i32Height = 0;
std::string sEncodedImageHex = lmbdGetImageFromResources(i32Width, i32Height);
auto& jsonWatermark = jsonSettings["Watermarks"][2];
jsonWatermark["Width"] = i32Width;
jsonWatermark["Height"] = i32Height;
jsonWatermark["DstWidth"] = i32Width; // no scaling
jsonWatermark["DstHeight"] = i32Height;
jsonWatermark["DrawAtX"] = 500;
jsonWatermark["DrawAtY"] = 500;
jsonWatermark["BeforeResize"] = true;
jsonWatermark["Type"] = "Frames";
auto& jsonFrames = jsonWatermark["Frames"];
auto& jsonFrame = jsonFrames[0];
jsonFrame["Delay"] = 10000; //10 seconds
jsonFrame["FrameHexString"] = sEncodedImageHex;
Loading an animated watermark of type ‘Frames’
C#
settings = pRecorder.GetConfigJSONForProfile(HIGH_SCREEN);
int iWidth = 0;
int iHeight = 0;
// See below for definition for CreateRGBAWatermarkHexStrings()
// It creates a number of frames, putting the image data in an array of hex strings
string[] Images = CreateRGBAWatermarksHexStrings(out iWidth, out iHeight);
if (Images.Length > 0) {
// The watermarks property is an array, each item defining a watermark. We only have one watermark in this example.
var aWatermarks = new JArray();
settings["Watermarks"] = aWatermarks;
var aWatermark = new JObject();
// We just have the one watermark, but it has a number of frames of animation
aWatermarks.Add(aWatermark);
aWatermark["Width"] = iWidth;
aWatermark["Height"] = iHeight;
aWatermark["DstWidth"] = iWidth; // no scaling
aWatermark["DstHeight"] = iHeight;
aWatermark["DrawAtX"] = 0;
aWatermark["DrawAtY"] = 200;
aWatermark["BeforeResize"] = true;
aWatermark["Type"] = "Frames";
// Because the type is 'Frames', we need to create an array that defines each image in the watermark.
var aFrames = new JArray();
aWatermark["Frames"] = aFrames;
// Go through all of the images created by the helper function and add them as frames.
foreach (var sImage in Images) {
var pFrame = new JObject();
aFrames.Add(pFrame);
pFrame["Delay"] = 1000; // one second between frames
pFrame["FrameHexString"] = sImage;
}
}
pRecorder.SetConfigJSON(settings);
// Now go on to do a recording
The helper function to create frames of animation – just a flashing rectangle, for demonstration purposes – and put the image data in an array of hex strings.
static string[] CreateRGBAWatermarksHexStrings(out int iFrameWidth, out int iFrameHeight)
{
List<string> sResult = new List<string>();
// The size of the watermark image, returned back to caller
iFrameWidth = 200;
iFrameHeight = 200;
Random rand = new Random(Environment.TickCount);
// Ten frames of animation. Each frame is a rectangle filled with a random color.
for (int i=0; i<10; i++) {
//create an image
Bitmap colorBmp = new Bitmap(iFrameWidth, iFrameHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var g = Graphics.FromImage(colorBmp);
// Draw a rectangle in a random color
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillRectangle(new SolidBrush(Color.FromArgb(0, 0, 0, 0)), new RectangleF(0, 0, iFrameWidth, iFrameHeight));
g.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(255, rand.Next(50, 255), rand.Next(50, 255), rand.Next(50, 255))), 15), new Rectangle(0, 0, iFrameWidth, iFrameHeight));
g.Dispose();
// Get the image color data into a string
StringBuilder sHexImage = new StringBuilder(colorBmp.Height * colorBmp.Width * 2 * 4);//RGBA bytes, each 2 characters
for (int y = 0; y < colorBmp.Height; y++) {
for (int x = 0; x < colorBmp.Width; x++) {
Color c = colorBmp.GetPixel(x, y);
int avg = (c.R + c.G + c.B) / 3;
sHexImage.Append(c.R.ToString("X2"));
sHexImage.Append(c.G.ToString("X2"));
sHexImage.Append(c.B.ToString("X2"));
sHexImage.Append(c.A.ToString("X2"));//2 characters per byte - RGBA odder
}
}
// And add the string to the list
sResult.Add(sHexImage.ToString());
}
// Return the list of strings as an array
return sResult.ToArray();
}
C++
auto& jsonWatermarks = jsonSettings["Watermarks"];
auto& jsonWatermark = jsonSettings["Watermarks"][0];
jsonWatermark["Width"] = 100;
jsonWatermark["Height"] = 100;
jsonWatermark["DstWidth"] = 100; // No scaling
jsonWatermark["DstHeight"] = 100;
jsonWatermark["DrawAtX"] = 50;
jsonWatermark["DrawAtY"] = 50;
jsonWatermark["BeforeResize"] = true;
jsonWatermark["Type"] = "Frames";
auto& jsonFrames = jsonWatermark["Frames"];
// Function to create a hexstring that represents a solid rectangle of a random color.
auto lmbdGetRandomImage = [](int32_t i32Width, int32_t i32Height) ->std::string
{
std::string sResult = "";
std::string sHexChars = "0123456789abcdef";
//width x height x 4 (colors) * 2 (each color 2 bytes in hex)
for (int32_t i = 0; i < i32Width * i32Height * 4 * 2; i++) {
sResult += sHexChars[std::rand() % 16];
}
return sResult;
};
// Use the helper function above to create 5 frames each of which is a rectangle of random colour.
for (int32_t i = 0; i < 5; i++) {
jsonFrames[i]["Delay"] = 1000;
//100 (width) x 100 (height) x 4 (colors) * 2 (each color 2 bytes in hex)
jsonFrames[i]["FrameHexString"] = lmbdGetRandomImage(100, 100);
}
Creating a GIF watermark
C#
var aWatermarks = jsonDefaultConfig["Watermarks"];
var aWatermark = new JObject();
aWatermarks.Add(aWatermark);
// Get a GIF file out of the app resources, into a hex string, into an item in the Watermarks array
if (!LoadGifAsWatermark(aWatermark)) {
Console.WriteLine("Error: unable to load GIF sample image to json.");
}
// A helper function that reads a GIF image stored in the application resources, into a hexstring for use
// by the Watermark property of FBRecorder.
static bool LoadGifAsWatermark(JObject pWatermark)
{
bool bResult = false;
Image pGifImage = null;
try {
// Find the GIF file in the app resources
System.Reflection.Assembly pAssembly = System.Reflection.Assembly.GetExecutingAssembly();
string sResourcePath = pAssembly.GetName().Name + ".Properties.Resources";
var pResourceManager = new System.Resources.ResourceManager(sResourcePath, pAssembly);
pGifImage = (Image)pResourceManager.GetObject("sample_gif_image");
// Get the GIF into a memory stream and get that as an array of bytes
MemoryStream pMemoryStream = new MemoryStream();
pGifImage.Save(pMemoryStream, ImageFormat.Gif);
pMemoryStream.Flush();
pMemoryStream.Position = 0;
byte[] pImageBytes = pMemoryStream.ToArray();
// Get the GIF bytes into a hex string
StringBuilder sHexImage = new StringBuilder(pImageBytes.Length * 2);//RGBA bytes, each 2 characters
for (int i = 0; i < pImageBytes.Length; i++) {
sHexImage.Append(pImageBytes[i].ToString("X2"));
}
pWatermark["ImageData"] = sHexImage.ToString();
pWatermark["DstWidth"] = pGifImage.Width / 2; //resize the watermark before drawing
pWatermark["DstHeight"] = pGifImage.Height / 2; //resize the watermark before drawing
pWatermark["DrawAtX"] = 0;
pWatermark["DrawAtY"] = 400;
pWatermark["BeforeResize"] = true;
pWatermark["Type"] = "GIF";
bResult = true;
}
catch(Exception exp) {
Console.WriteLine("Error: LoadGifAsWatermark exception: " + exp.Message);
}
if (pGifImage != null)
pGifImage.Dispose();
return bResult;
}
C++
jsonWatermark = jsonSettings["Watermarks"][0];
jsonWatermark["Width"] = 100; // just sample sizes
jsonWatermark["Height"] = 100;
jsonWatermark["DstWidth"] = 100; // No scaling
jsonWatermark["DstHeight"] = 100;
jsonWatermark["DrawAtX"] = 300;
jsonWatermark["DrawAtY"] = 300;
jsonWatermark["BeforeResize"] = true;
jsonWatermark["Type"] = "GIF";
jsonWatermark["ImageData"] = []() -> std::string
{
//read gif data from file
FILE* f;
fopen_s(&f, "D:\\sample.gif", "rb");
fseek(f, 0, SEEK_END);
size_t stFileSize = ftell(f);
fseek(f, 0, SEEK_SET);
// read the file into an array
std::vector<uint8_t> vecGifData(stFileSize);
fread(vecGifData.data(), 1, stFileSize, f);
// go through the values in the array and convert binary to hex
std::string sResult = "";
for (size_t i = 0; i < stFileSize; i++) {
sResult += [](uint8_t uiByte) -> std::string
{
std::string sHexChars = "0123456789abcdef";
return (std::string)"" + sHexChars[(uiByte >> 4) & 0xf] + sHexChars[uiByte& 0xf];
}(vecGifData[i]);
}
return sResult;
}();