| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "impeller/typographer/rectangle_packer.h" | |
| #include <algorithm> | |
| #include <vector> | |
| #include "flutter/fml/logging.h" | |
| namespace impeller { | |
| // Pack rectangles and track the current silhouette | |
| // Based, in part, on Jukka Jylanki's work at http://clb.demon.fi | |
| // and ported from Skia's implementation | |
| // https://github.com/google/skia/blob/b5de4b8ae95c877a9ecfad5eab0765bc22550301/src/gpu/RectanizerSkyline.cpp | |
| class SkylineRectanglePacker final : public RectanglePacker { | |
| public: | |
| SkylineRectanglePacker(int w, int h) : RectanglePacker(w, h) { Reset(); } | |
| ~SkylineRectanglePacker() final {} | |
| void Reset() final { | |
| area_so_far_ = 0; | |
| skyline_.clear(); | |
| skyline_.push_back(SkylineSegment{0, 0, width()}); | |
| } | |
| bool AddRect(int w, int h, IPoint16* loc) final; | |
| Scalar PercentFull() const final { | |
| return area_so_far_ / ((float)width() * height()); | |
| } | |
| private: | |
| struct SkylineSegment { | |
| int x_; | |
| int y_; | |
| int width_; | |
| }; | |
| std::vector<SkylineSegment> skyline_; | |
| int32_t area_so_far_; | |
| // Can a width x height rectangle fit in the free space represented by | |
| // the skyline segments >= 'skyline_index'? If so, return true and fill in | |
| // 'y' with the y-location at which it fits (the x location is pulled from | |
| // 'skyline_index's segment. | |
| bool RectangleFits(size_t skyline_index, int width, int height, int* y) const; | |
| // Update the skyline structure to include a width x height rect located | |
| // at x,y. | |
| void AddSkylineLevel(size_t skylineIndex, | |
| int x, | |
| int y, | |
| int width, | |
| int height); | |
| }; | |
| bool SkylineRectanglePacker::AddRect(int p_width, int p_height, IPoint16* loc) { | |
| if ((unsigned)p_width > (unsigned)width() || | |
| (unsigned)p_height > (unsigned)height()) { | |
| return false; | |
| } | |
| // find position for new rectangle | |
| int bestWidth = width() + 1; | |
| int bestX = 0; | |
| int bestY = height() + 1; | |
| int bestIndex = -1; | |
| for (auto i = 0u; i < skyline_.size(); ++i) { | |
| int y; | |
| if (RectangleFits(i, p_width, p_height, &y)) { | |
| // minimize y position first, then width of skyline | |
| if (y < bestY || (y == bestY && skyline_[i].width_ < bestWidth)) { | |
| bestIndex = i; | |
| bestWidth = skyline_[i].width_; | |
| bestX = skyline_[i].x_; | |
| bestY = y; | |
| } | |
| } | |
| } | |
| // add rectangle to skyline | |
| if (-1 != bestIndex) { | |
| AddSkylineLevel(bestIndex, bestX, bestY, p_width, p_height); | |
| loc->x_ = bestX; | |
| loc->y_ = bestY; | |
| area_so_far_ += p_width * p_height; | |
| return true; | |
| } | |
| loc->x_ = 0; | |
| loc->y_ = 0; | |
| return false; | |
| } | |
| bool SkylineRectanglePacker::RectangleFits(size_t skyline_index, | |
| int p_width, | |
| int p_height, | |
| int* ypos) const { | |
| int x = skyline_[skyline_index].x_; | |
| if (x + p_width > width()) { | |
| return false; | |
| } | |
| int widthLeft = p_width; | |
| size_t i = skyline_index; | |
| int y = skyline_[skyline_index].y_; | |
| while (widthLeft > 0) { | |
| y = std::max(y, skyline_[i].y_); | |
| if (y + p_height > height()) { | |
| return false; | |
| } | |
| widthLeft -= skyline_[i].width_; | |
| i++; | |
| FML_CHECK(i < skyline_.size() || widthLeft <= 0); | |
| } | |
| *ypos = y; | |
| return true; | |
| } | |
| void SkylineRectanglePacker::AddSkylineLevel(size_t skyline_index, | |
| int x, | |
| int y, | |
| int p_width, | |
| int p_height) { | |
| SkylineSegment newSegment; | |
| newSegment.x_ = x; | |
| newSegment.y_ = y + p_height; | |
| newSegment.width_ = p_width; | |
| skyline_.insert(skyline_.begin() + skyline_index, newSegment); | |
| FML_DCHECK(newSegment.x_ + newSegment.width_ <= width()); | |
| FML_DCHECK(newSegment.y_ <= height()); | |
| // delete width of the new skyline segment from following ones | |
| for (auto i = skyline_index + 1; i < skyline_.size(); ++i) { | |
| // The new segment subsumes all or part of skyline_[i] | |
| FML_DCHECK(skyline_[i - 1].x_ <= skyline_[i].x_); | |
| if (skyline_[i].x_ < skyline_[i - 1].x_ + skyline_[i - 1].width_) { | |
| int shrink = skyline_[i - 1].x_ + skyline_[i - 1].width_ - skyline_[i].x_; | |
| skyline_[i].x_ += shrink; | |
| skyline_[i].width_ -= shrink; | |
| if (skyline_[i].width_ <= 0) { | |
| // fully consumed | |
| skyline_.erase(skyline_.begin() + i); | |
| --i; | |
| } else { | |
| // only partially consumed | |
| break; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| // merge skylines | |
| for (auto i = 0u; i < skyline_.size() - 1; ++i) { | |
| if (skyline_[i].y_ == skyline_[i + 1].y_) { | |
| skyline_[i].width_ += skyline_[i + 1].width_; | |
| skyline_.erase(skyline_.begin() + i + 1); | |
| --i; | |
| } | |
| } | |
| } | |
| std::shared_ptr<RectanglePacker> RectanglePacker::Factory(int width, | |
| int height) { | |
| return std::make_shared<SkylineRectanglePacker>(width, height); | |
| } | |
| } // namespace impeller |