// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_ASH_SERVICES_RECORDING_OCTREE_COLOR_QUANTIZER_H_
#define CHROMEOS_ASH_SERVICES_RECORDING_OCTREE_COLOR_QUANTIZER_H_
#include <array>
#include <cstdint>
#include <memory>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "chromeos/ash/services/recording/gif_encoding_types.h"
#include "chromeos/ash/services/recording/rgb_video_frame.h"
namespace recording {
// GIF images can have a maximum number of 256 colors in their color tables.
// This means that the minimum number of bits needed to represent this count is
// 8, which is the max bit depth value.
inline constexpr size_t kMaxNumberOfColorsInPalette = 256;
// 8 bits for each color channel R, G, and B.
inline constexpr uint8_t kNumBitsPerColorChannel = 8;
// Defines an Octree-based (https://en.wikipedia.org/wiki/Octree) color
// quantizer that can efficiently extract the most important (up to
// `kMaxNumberOfColorsInPalette` (256)) colors from a given `rgb_video_frame`,
// and can be used to find the indices of the closest colors of the pixels in
// video frames based on the extracted palette.
class OctreeColorQuantizer {
public:
OctreeColorQuantizer();
explicit OctreeColorQuantizer(const RgbVideoFrame& rgb_video_frame);
OctreeColorQuantizer(OctreeColorQuantizer&&);
OctreeColorQuantizer& operator=(OctreeColorQuantizer&&);
~OctreeColorQuantizer();
// Fills in the given `out_color_palette` with the quantized (maximum of
// `kMaxNumberOfColorsInPalette`) list of colors extracted from the
// `rgb_video_frame` given to the constructor. It also assigns indices to
// `Node::palette_index_` of all the leaf nodes in this tree.
void ExtractColorPalette(ColorTable& out_color_palette);
// Fills in the given `out_pixel_color_indices` with the indices of the
// closest colors for each pixel in the given `rgb_video_frame` from the
// quantized color palette extracted by calling `ExtractColorPalette()` above.
// This means `ExtractColorPalette()` must be called once before calling this
// for every received video frame (provided that the same `color_palette` is
// still desired to be reused).
// This also implements the Floyd-Steinberg dithering, meaning that the
// resulting color indices in `out_pixel_color_indices` will be of a
// quantized and dithered image of the given `rgb_video_frame` using the given
// `color_palette`. The given `rgb_video_frame` will be modified in the
// process of dithering to diffuse the color errors in each pixel over the
// colors of neighboring pixels (See implementation for details).
void ExtractPixelColorIndices(RgbVideoFrame& rgb_video_frame,
const ColorTable& color_palette,
ColorIndices& out_pixel_color_indices) const;
private:
// Defines a node in the Octree. Each node is a parent of up to 8 child nodes.
// Depending on the colors in the given `rgb_video_frame`, not all nodes in
// `child_nodes_` are available.
class Node {
public:
Node();
Node(Node&&);
Node& operator=(Node&&);
~Node();
bool is_leaf() const { return ref_count_ > 0; }
// Gets the color that is represented by this node by averaging all the
// accumulated `red_`, `green_` and `blue_` channels over the number of
// times this node is referenced.
RgbColor GetColor() const;
// Returns the total sum of `ref_count_`s of all the immediate child nodes.
// This is used for sorting all the nodes that belong to the same level when
// we are reducing the colors to a maximum of `kMaxNumberOfColorsInPalette`.
size_t ChildrenRefCount() const;
// `red_`, `green_` and `blue_` Accumulate the values of the red, green and
// blue color channels respectively every time this node (if it's a leaf
// node) is referenced as we `InsertColor()`s into the Octree.
uint32_t red_ = 0;
uint32_t green_ = 0;
uint32_t blue_ = 0;
// The number of times this leaf node is referenced when we
// `InsertColor()`s. Note that only leaf nodes have reference counts.
size_t ref_count_ = 0;
// A placeholder for the index of the color represented by this leaf node
// when the color palette is built in `ExtractColorPalette()`.
size_t palette_index_ = -1u;
// Pointers to the next and previous nodes in a linked list tracking all the
// leaf nodes. The linked list starts at `leaf_nodes_head_` below.
// These pointers are safe to dangle as we never access them during the
// destruction of the tree. The only time we explicitly remove a node in
// `Reduce()` we call `RemoveLeafNode()` beforehand.
raw_ptr<Node, DisableDanglingPtrDetection> next_ = nullptr;
raw_ptr<Node, DisableDanglingPtrDetection> prev_ = nullptr;
// The sub nodes of this node. A maximum of 8 child nodes can be populated.
// A child node is added at index that is calculated from the RGB color
// components of the color being added according to the level of `this` node
// in the Octree. For example:
// - At level 0: The indices are formed by combining the most significant
// bits (the 8th bits) of the RGB components to form a single number
// (0 to 7).
// - At level 1: We form an index by combining the 7th bits of the RGB
// components together.
// And so on. Please see `GetColorIndexAtLevel()`.
std::array<std::unique_ptr<Node>, kNumBitsPerColorChannel> child_nodes_;
};
// Defines an array of lists of nodes at each level of the Octree, where the
// levels are the indices of this array (0 to 7).
using NodesPerLevel = std::array<std::vector<Node*>, kNumBitsPerColorChannel>;
// Inserts the given `color` into this Octree starting at the `root_` node.
// It uses the given `nodes_per_level` to track each newly created node by its
// level (0 to 7) in the Octree. `nodes_per_level` can be used later by
// `Reduce()` to start the leaf nodes reduction starting at the lower levels
// and going up.
void InsertColor(const RgbColor& color, NodesPerLevel& nodes_per_level);
// Called recursively to insert the given `color` into the Octree. The given
// `node` is the one being considered at the current iteration, and the one
// whose children `child_nodes_` are at the given `level` of the Octree.
// This means `root_::child_nodes_` are at level 0 of the Octree. Leaf
// nodes are at level 7.
// `nodes_per_level` will be populated as described above in `InsertColor()`.
void InsertColorInternal(Node* node,
const RgbColor& color,
int level,
NodesPerLevel& nodes_per_level);
// Reduces the number of leaf nodes (i.e. the number of colors) in this tree
// to a maximum of `kMaxNumberOfColorsInPalette` (256) colors. This is done by
// merging the color information of leaf nodes with their parent nodes (making
// the parent nodes become leaf nodes), and discarding those old leaf nodes.
// This is done in a way such that the most important colors (i.e. the ones
// with the highest ref counts) are preferred to be kept in the tree. The
// colors of the combined leaf nodes are averaged to produce a color that
// represents all the discarded nodes.
void Reduce(NodesPerLevel& nodes_per_level);
// Returns the index of the given `color` into a color palette that has been
// built by this Octree via `ExtractColorPalette()`. If `color` doesn't exist
// in the Octree, it returns the index of its closest color that is in the
// palette.
size_t FindColorIndex(const RgbColor& color) const;
// Called recursively by `FindColorIndex()` to get the index of the given
// `color`. `node` is the one currently being considered which exists at the
// given `level`. The index is found when we reach a leaf node by traversing
// the tree.
size_t FindColorIndexInternal(const Node* node,
int level,
const RgbColor& color) const;
// Inserts the given leaf `node` at the front of the linked list that tracks
// all leaf nodes. After this is called, `node` will become the new
// `leaf_nodes_head_`.
void InsertLeafNode(Node* node);
// Removes the given leaf `node` from the linked list that tracks all the leaf
// nodes in this tree.
void RemoveLeafNode(Node* node);
// The root node of this Octree. The immediate 8 child nodes of the root are
// at level 0, which is the very first level of the tree.
Node root_;
// The head node of a linked list that tracks all the leaf nodes in this tree.
// Note that leaf nodes are the ones that contain the color information.
// This list will be used to build a color palette after it has been reduced
// to `kMaxNumberOfColorsInPalette` in `Reduce()`.
// This is also safe to dangle as we never access it during the destruction of
// the tree.
raw_ptr<Node, DisableDanglingPtrDetection> leaf_nodes_head_;
// The total number of leaf nodes (i.e. the total number of colors in the this
// tree).
size_t leaf_nodes_count_ = 0;
};
} // namespace recording
#endif // CHROMEOS_ASH_SERVICES_RECORDING_OCTREE_COLOR_QUANTIZER_H_