Sample Code – Watermarks

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;
}();