| // 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_ |