Divide an image into blocks using GDI+ in C++


In the previous blog, we used OpenCV to divide an image into multiple blocks of a certain height and width, which is useful when we need to apply a certain image transformation block-wise. This blog will provide an alternative implementation using the Windows API, specifically the GDI+ library.

Approach

Similarly to the approach introduced in the previous blog (Divide an image into blocks using OpenCV in C++) using OpenCV, this alternative aims to crop the blocks one by one after computing their coordinates and dimensions. At the same time, we will make sure to crop the side blocks and lower blocks correctly even if their dimensions are different than other blocks.

From Divide an image into blocks using OpenCV in C++:

To elaborate on this a bit more: if the image width = 512 px and the block width = 128 px -> we will get 512 / 128 = 4 blocks. However if the image width = 576 px and the block width = 128 px -> we should get 4 blocks with 128 px and 1 block with width 64 px. Therefore on each loop we need to check/ compute the block dimensions.

Similarly, we also use while loops in this alternative to keep the code fast and optimal. The previously described code is implemented in the following snippet:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <Windows.h>
#include <minmax.h>
#include <gdiplus.h>
#include <SDKDDKVer.h>
#pragma comment(lib,"gdiplus.lib")
#include "atlimage.h"


bool saveToMemory(Gdiplus::Bitmap* tile, std::vector<BYTE>& data, std::string dataFormat)
{
  // write to IStream
  IStream* istream = nullptr;
  CreateStreamOnHGlobal(NULL, TRUE, &istream);

  // define encoding
  CLSID clsid;
  if (dataFormat.compare("bmp") == 0)
  {
    CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &clsid);
    Gdiplus::Status status = tile->Save(istream, &clsid, NULL);
    if (status != Gdiplus::Status::Ok)
      return false;
  }
  else if (dataFormat.compare("jpg") == 0)
  {
    CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &clsid);
    // Before we call Image::Save, we must initialize an EncoderParameters object.
    // The EncoderParameters object has an array of EncoderParameter objects.
    // In this case, there is only one EncoderParameter object in the array.
    // The one EncoderParameter object has an array of values.
    // In this case, there is only one value (of type ULONG)
    Gdiplus::EncoderParameters encoderParameters;
    encoderParameters.Count = 1;
    encoderParameters.Parameter[0].Guid = Gdiplus::EncoderQuality;
    encoderParameters.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
    encoderParameters.Parameter[0].NumberOfValues = 1;

    // Save the image as a JPEG with quality level 0.
    int quality = 70;
    encoderParameters.Parameter[0].Value = &quality;

    Gdiplus::Status status = tile->Save(istream, &clsid, &encoderParameters);
    if (status != Gdiplus::Status::Ok)
      return false;
  }
  else if (dataFormat.compare("png") == 0)
  {
    CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid);
    Gdiplus::Status status = tile->Save(istream, &clsid, NULL);
    if (status != Gdiplus::Status::Ok)
      return false;
  }

  // get memory handle associated with istream
  HGLOBAL hg = NULL;
  GetHGlobalFromStream(istream, &hg);

  // copy IStream to buffer
  int bufsize = GlobalSize(hg);
  data.resize(bufsize);

  // lock & unlock memory
  LPVOID pimage = GlobalLock(hg);
  if (pimage != 0)
    memcpy(&data[0], pimage, bufsize);

  GlobalUnlock(hg);
  istream->Release();
  return true;
}

int gdiplusDivideImage(Gdiplus::Bitmap* img, const int blockWidth, const int blockHeight, std::vector<std::vector<BYTE>>& blocks)
{
  int imgWidth = img->GetWidth();
  int imgHeight = img->GetHeight();

  int bhSize = 0;
  int bwSize = 0;
  int blockId = 0;
  int y0 = 0;

  while (y0 < imgHeight)
  {
    bhSize = ((y0 + blockHeight) > imgHeight) * (blockHeight - (y0 + blockHeight - imgHeight)) + ((y0 + blockHeight) <= imgHeight) * blockHeight;

    int x0 = 0;
    while (x0 < imgWidth)
    {
      bwSize = ((x0 + blockWidth) > imgWidth) * (blockWidth - (x0 + blockWidth - imgWidth)) + ((x0 + blockWidth) <= imgWidth) * blockWidth;
      blockId += 1;

      Gdiplus::Bitmap* bmp = img->Clone(x0, y0, bwSize, bhSize, PixelFormat24bppRGB);

      // encode block
      std::vector<BYTE> pngbytes;

      // encode block
      if (!(saveToMemory(bmp, pngbytes, "png")))
      {
        std::cout << "Cannot save block_" << std::to_string(x0) << "_" << std::to_string(y0) << "to png" << std::endl;
        return EXIT_FAILURE;
      }
      // update starting coordinates
      x0 = x0 + blockWidth;

      // append to vec
      blocks.push_back(pngbytes);
    }
    // update starting coordinates
    y0 = y0 + blockHeight;
  }
  return EXIT_SUCCESS;
}

The previous snippet includes two main functions:

  • saveToMemory() : used to save the image bytes to the memory using a specific encoding.

  • gdiplusDivideImage() : divides an input image into blocks with an integer output reflecting the run status of the function.

The main call

Let’s call the previous function in a main function and save the resulting blocks in a defined directory to visualize the results and verify, that the code is doing what it is supposed to. For the purpose of this test I chose to use the famous Lenna picture, that can downloaded from here. In code this can be done as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main()
{
  // initilialize GDI+
  CoInitialize(NULL);
  ULONG_PTR token;
  Gdiplus::GdiplusStartupInput tmp;
  Gdiplus::GdiplusStartup(&token, &tmp, NULL);

  // init vars
  std::vector<std::vector<BYTE>> imgblocks;

  // load img
  Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromFile(L"Lenna.png");

  // divide img
  int divstatus = gdiplusDivideImage(bitmap, blockw, blockh, imgblocks);

  // create repository for blocks
  if (!CreateDirectory(L"blocksFolder", NULL))
  {
    std::wcout << "Directory Error: Cannot create directory for blocks." << std::endl;
    return 1;
  }

  // save blocks
  for (int j = 0; j < imgblocks.size(); j++)
  {
    std::string blockId = std::to_string(j);
    std::string blockImgName = "blocksFolder/block#" + blockId + ".png";

    // write from memory to file for testing:
    std::ofstream fout(blockImgName, std::ios::binary);
    fout.write((char*)imgblocks[j].data(), imgblocks[j].size());
  }
  return 0;
}

The full code can be found in this gist: DivideImageUsingGdiplus.cpp.

Result

The resulting blocks should look something like this:

../_images/divided_lenna.png

Limitations

  • This code is practical and simple to use on Windows but unfortunately, on the contrary to the OpenCV variant, it does not support different operating systems like Linux for example.

  • In some cases the user might want to have equally sized blocks; in that case the dimensions of the blocks should be pre-computed if the user wants to use this snippet.

Conclusion

This post provided an alternative C++ implementation to the previous OpenCV code used to divide an image into multiple blocks with predefined height and width. Similarly, we also saved the resulting blocks to the hard drive in order to verify that the code is functional. The code is fairly simple and supports various image encoding types(PNG, JPEG etc.) but unlike the OpenCV implementation, it only supports Windows since it is based in part on the Win32 API, specifically the GDI+ library.