• Difficulty Level : Hard
• Last Updated : 09 Feb, 2018

Quadtrees are an effective method to store and locate data of points in a two-dimensional plane. Another effective use of quadtrees is in the field of image manipulation.

Unlike in storage of points, in image manipulation we get a complete quadtree with the leaf nodes consisting of individual pixels of the image. Due to this, we can utilize an array to store the nodes of the tree. This leads to less memory needed (compared to linked representation) for processing.

The lowest level of the quadtree would contain N nodes equivalent to the number of pixels in the image. The next level would contain N / 4 nodes.
Thus, the total number of nodes necessary can be found by: N + N / 4 + N / 16 + … + 1.
To get an upperbound we can use sum of geometric progression to infinity
Nodes = N / (1 – 1 / 4) = 4 / 3 * N
This is the size of the array necessary.
Thus, the amount of memory needed is O(N)

 `class` `Point(``object``):``    ``def` `__init__(``self``, x, y):``        ``self``.x ``=` `x``        ``self``.y ``=` `y``class` `Pixel(``object``):``    ``def` `__init__(``self``, color ``=` `[``0``, ``0``, ``0``], ``            ``topLeft ``=` `Point(``0``, ``0``), ``            ``bottomRight ``=` `Point(``0``, ``0``)):``        ``self``.R ``=` `color[``0``]``        ``self``.G ``=` `color[``1``]``        ``self``.B ``=` `color[``2``]``        ``self``.topLeft ``=` `topLeft``        ``self``.bottomRight ``=` `bottomRight`

The above code in Python shown demonstrates the class definition of a pixel.

Insertion
Unlike in a classic quadtree, for image manipulation, we can insert all the nodes in O(N) time complexity.
First, we insert all the leaf nodes directly into the last N positions of the array. The following code snippet demonstrates this:

 `# Store leaves into array``count ``=` `0``for` `i ``in` `range``(image.size[``0``] ``-` `1``, ``0``, ``-``2``):``    ``for` `j ``in` `range``(image.size[``1``] ``-` `1``, ``0``, ``-``2``):``        ``self``.tree[``self``.size ``-` `1` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i, j], ``                ``Point(i, j), ``                ``Point(i, j))``        ``self``.tree[``self``.size ``-` `2` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i, j ``-` `1``], ``                ``Point(i, j ``-` `1``),``                ``Point(i, j ``-` `1``))``        ``self``.tree[``self``.size ``-` `3` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i ``-` `1``, j], ``                ``Point(i ``-` `1``, j), ``                ``Point(i ``-` `1``, j))``        ``self``.tree[``self``.size ``-` `4` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i ``-` `1``, j ``-` `1``], ``                ``Point(i ``-` `1``, j ``-` `1``), ``                ``Point(i ``-` `1``, j ``-` `1``))``        ``count ``+``=` `1`

In the above snippet, self.tree is the array of nodes, self.size is the total size of the array, and self.image is the pixels of the image, count is used to traverse the tree.

For nodes that aren’t leaves, the R, G, B values are calculated by taking the average of the values of the children.
The topLeft and bottomRight are obtained by taking the maximum and minimum values of the x and y of the children.

 `# Calculate and create parent nodes``for` `i ``in` `range``(``self``.size ``-` `4` `*` `count ``-` `1``, ``-``1``, ``-``1``):``    ``self``.tree[i] ``=` `Pixel(``        ``[(``self``.tree[``4` `*` `i ``+` `1``].R ``+` `self``.tree[``4` `*` `i ``+` `2``].R ``+` `self``.tree[``4` `*` `i ``+` `3``].R ``+` `self``.tree[``4` `*` `i ``+` `4``].R) ``/` `4``,``         ``(``self``.tree[``4` `*` `i ``+` `1``].G ``+` `self``.tree[``4` `*` `i ``+` `2``].G ``+` `self``.tree[``4` `*` `i ``+` `3``].G ``+` `self``.tree[``4` `*` `i ``+` `4``].G) ``/` `4``,``         ``(``self``.tree[``4` `*` `i ``+` `1``].B ``+` `self``.tree[``4` `*` `i ``+` `2``].B ``+` `self``.tree[``4` `*` `i ``+` `3``].B ``+` `self``.tree[``4` `*` `i ``+` `4``].B) ``/` `4``],``        ``self``.tree[``4` `*` `i ``+` `1``].topLeft,``        ``self``.tree[``4` `*` `i ``+` `4``].bottomRight)`

Here we can see, we take the values of R, G, B of the four children, and we divide by 4, thus getting the average.

Calculation of these values happen in O(1) time assuming values of the children are known. Since we are moving in reverse order, the values of the children are computed before that of parents.
Thus insertion occurs in O(N).

Application in Image Manipulation
Let us say we wish to convert a high quality image to a thumbnail. Once we have created a quadtree for the image, by selecting a height of the quadtree we can select the quality of the image we obtain. If the height is equal to the height of the quadtree, then we retain the original image. At lower heights, we obtain images of lesser quality.

In case we do not wish to reduce the quality of the image, we can try to compress the image by what is known as “pruning”. In this, leafs with colours close to that of their parents are removed. This is continuously done until no further leaves can be removed. The lowest level is then taken to form the image, using only leaf nodes. While this does not reduce the quality of the image drastically, it can lead to a small compression of the image.

Complete Code:

 `from` `PIL ``import` `Image``import` `math``class` `Point(``object``):``    ``def` `__init__(``self``, x, y):``        ``self``.x ``=` `x``        ``self``.y ``=` `y`` ` `class` `Pixel(``object``):``    ``def` `__init__(``self``, color ``=` `[``0``, ``0``, ``0``], ``            ``topLeft ``=` `Point(``0``, ``0``), ``            ``bottomRight ``=` `Point(``0``, ``0``)):``        ``self``.R ``=` `color[``0``]``        ``self``.G ``=` `color[``1``]``        ``self``.B ``=` `color[``2``]``        ``self``.topLeft ``=` `topLeft``        ``self``.bottomRight ``=` `bottomRight`` ` `class` `quadtree():``    ``def` `__init__(``self``, image):`` ` `        ``# Total number of nodes of tree``        ``self``.size ``=` `0`` ` `        ``# Store image pixelmap``        ``self``.image ``=` `image.load()`` ` `        ``# Array of nodes``        ``self``.tree ``=` `[]``        ``self``.x ``=` `image.size[``0``]``        ``self``.y ``=` `image.size[``1``]`` ` `        ``# Total number of leaf nodes``        ``size ``=` `image.size[``0``] ``*` `image.size[``1``]`` ` `        ``# Count number of nodes``        ``while``(size >``=` `1``):``            ``self``.size ``+``=` `size``            ``size ``/``=` `4`` ` `        ``size ``=` `image.size[``0``] ``*` `image.size[``1``]`` ` `        ``# Initialize array elements``        ``for` `i ``in` `range``(``0``, ``self``.size):``            ``self``.tree.append(Pixel())`` ` `        ``# Store leaves into array``        ``count ``=` `0``        ``for` `i ``in` `range``(image.size[``0``] ``-` `1``, ``0``, ``-``2``):``            ``for` `j ``in` `range``(image.size[``1``] ``-` `1``, ``0``, ``-``2``):``                ``self``.tree[``self``.size ``-` `1` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i, j], ``                        ``Point(i, j), ``                        ``Point(i, j))``                ``self``.tree[``self``.size ``-` `2` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i, j ``-` `1``], ``                        ``Point(i, j ``-` `1``),``                        ``Point(i, j ``-` `1``))``                ``self``.tree[``self``.size ``-` `3` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i ``-` `1``, j], ``                        ``Point(i ``-` `1``, j), ``                        ``Point(i ``-` `1``, j))``                ``self``.tree[``self``.size ``-` `4` `-` `4` `*` `count] ``=` `Pixel(``self``.image[i ``-` `1``, j ``-` `1``], ``                        ``Point(i ``-` `1``, j ``-` `1``), ``                        ``Point(i ``-` `1``, j ``-` `1``))``                ``count ``+``=` `1`` ` `        ``# Calculate and create parent nodes``        ``for` `i ``in` `range``(``self``.size ``-` `4` `*` `count ``-` `1``, ``-``1``, ``-``1``):``            ``self``.tree[i] ``=` `Pixel(``                ``[(``self``.tree[``4` `*` `i ``+` `1``].R ``+` `self``.tree[``4` `*` `i ``+` `2``].R ``+` `self``.tree[``4` `*` `i ``+` `3``].R ``+` `self``.tree[``4` `*` `i ``+` `4``].R) ``/` `4``,``                 ``(``self``.tree[``4` `*` `i ``+` `1``].G ``+` `self``.tree[``4` `*` `i ``+` `2``].G ``+` `self``.tree[``4` `*` `i ``+` `3``].G ``+` `self``.tree[``4` `*` `i ``+` `4``].G) ``/` `4``,``                 ``(``self``.tree[``4` `*` `i ``+` `1``].B ``+` `self``.tree[``4` `*` `i ``+` `2``].B ``+` `self``.tree[``4` `*` `i ``+` `3``].B ``+` `self``.tree[``4` `*` `i ``+` `4``].B) ``/` `4``],``                ``self``.tree[``4` `*` `i ``+` `1``].topLeft,``                ``self``.tree[``4` `*` `i ``+` `4``].bottomRight)`` ` `    ``def` `disp(``self``, level):``        ``start ``=` `0`` ` `        ``# Calculate position of starting node of height``        ``for` `i ``in` `range``(``0``, level):``            ``start ``=` `4` `*` `start ``+` `1`` ` `        ``# Invalid height given``        ``if` `(start > ``self``.size):``            ``return`` ` `        ``# Create a new image``        ``img ``=` `Image.new(``"RGB"``, (``self``.x, ``self``.y), ``"black"``)``        ``pixels ``=` `img.load()`` ` `        ``# Move from starting to last node on given height``        ``for` `i ``in` `self``.tree[start : ``4` `*` `start]:``            ``x1 ``=` `i.topLeft.x``            ``y1 ``=` `i.topLeft.y``            ``x2 ``=` `i.bottomRight.x``            ``y2 ``=` `i.bottomRight.y``            ``for` `x ``in` `range``(x1, x2 ``+` `1``):``                ``for` `y ``in` `range``(y1, y2 ``+` `1``):`` ` `                    ``# Set colour``                    ``pixels[x, y] ``=` `(i.R, i.G, i.B)`` ` `        ``# Display image``        ``img.show()`