[qt] Screenshoter accepts viewport center points and zoom or rects for positioning on the map.

This commit is contained in:
Daria Volvenkova 2019-11-06 22:37:20 +03:00 committed by gmoryes
parent e62820c1bc
commit 51140bd695
13 changed files with 395 additions and 68 deletions

View file

@ -622,7 +622,8 @@ void BackendRenderer::AcceptMessage(ref_ptr<Message> message)
{
ref_ptr<NotifyGraphicsReadyMessage> msg = message;
m_commutator->PostMessage(ThreadsCommutator::RenderThread,
make_unique_dp<NotifyGraphicsReadyMessage>(msg->GetCallback()),
make_unique_dp<NotifyGraphicsReadyMessage>(msg->GetCallback(),
msg->NeedInvalidate()),
MessagePriority::Normal);
break;
}

View file

@ -525,10 +525,10 @@ void DrapeEngine::SetModelViewListener(ModelViewChangedHandler && fn)
}
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_LINUX)
void DrapeEngine::NotifyGraphicsReady(GraphicsReadyHandler const & fn)
void DrapeEngine::NotifyGraphicsReady(GraphicsReadyHandler const & fn, bool needInvalidate)
{
m_threadCommutator->PostMessage(ThreadsCommutator::ResourceUploadThread,
make_unique_dp<NotifyGraphicsReadyMessage>(fn),
make_unique_dp<NotifyGraphicsReadyMessage>(fn, needInvalidate),
MessagePriority::Normal);
}
#endif

View file

@ -145,7 +145,7 @@ public:
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_LINUX)
using GraphicsReadyHandler = FrontendRenderer::GraphicsReadyHandler;
void NotifyGraphicsReady(GraphicsReadyHandler const & fn);
void NotifyGraphicsReady(GraphicsReadyHandler const & fn, bool needInvalidate);
#endif
void ClearUserMarksGroup(kml::MarkGroupId groupId);

View file

@ -1054,7 +1054,12 @@ void FrontendRenderer::AcceptMessage(ref_ptr<Message> message)
{
m_graphicsStage = GraphicsStage::WaitReady;
if (m_notFinishedTiles.empty())
InvalidateRect(m_userEventStream.GetCurrentScreen().ClipRect());
{
if (msg->NeedInvalidate())
InvalidateRect(m_userEventStream.GetCurrentScreen().ClipRect());
else
m_graphicsStage = GraphicsStage::WaitRendering;
}
}
break;
}

View file

@ -1014,15 +1014,18 @@ class NotifyGraphicsReadyMessage : public Message
public:
using GraphicsReadyCallback = std::function<void()>;
explicit NotifyGraphicsReadyMessage(GraphicsReadyCallback const & callback)
: m_callback(callback)
explicit NotifyGraphicsReadyMessage(GraphicsReadyCallback const & callback, bool needInvalidate)
: m_needInvalidate(needInvalidate)
, m_callback(callback)
{}
Type GetType() const override { return Type::NotifyGraphicsReady; }
bool NeedInvalidate() const { return m_needInvalidate; }
GraphicsReadyCallback GetCallback() { return m_callback; }
private:
bool m_needInvalidate;
GraphicsReadyCallback m_callback;
};

View file

@ -1251,10 +1251,10 @@ void Framework::SetViewportListener(TViewportChangedFn const & fn)
}
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_LINUX)
void Framework::NotifyGraphicsReady(TGraphicsReadyFn const & fn)
void Framework::NotifyGraphicsReady(TGraphicsReadyFn const & fn, bool needInvalidate)
{
if (m_drapeEngine != nullptr)
m_drapeEngine->NotifyGraphicsReady(fn);
m_drapeEngine->NotifyGraphicsReady(fn, needInvalidate);
}
#endif

View file

@ -627,7 +627,7 @@ public:
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_LINUX)
using TGraphicsReadyFn = df::DrapeEngine::GraphicsReadyHandler;
void NotifyGraphicsReady(TGraphicsReadyFn const & fn);
void NotifyGraphicsReady(TGraphicsReadyFn const & fn, bool needInvalidate);
#endif
void StopLocationFollow();

View file

@ -472,13 +472,6 @@ void DrawWidget::keyReleaseEvent(QKeyEvent * e)
m_emulatingLocation = false;
}
void DrawWidget::OnViewportChanged(ScreenBase const & screen)
{
TBase::OnViewportChanged(screen);
if (m_screenshotMode)
m_screenshoter->CheckViewport();
}
bool DrawWidget::Search(search::EverywhereSearchParams const & params)
{
return m_framework.SearchEverywhere(params);

View file

@ -100,7 +100,6 @@ protected:
void mousePressEvent(QMouseEvent * e) override;
void mouseMoveEvent(QMouseEvent * e) override;
void mouseReleaseEvent(QMouseEvent * e) override;
void OnViewportChanged(ScreenBase const & screen) override;
//@}
void keyPressEvent(QKeyEvent * e) override;

View file

@ -30,12 +30,21 @@ DEFINE_string(data_path, "", "Path to data directory.");
DEFINE_string(log_abort_level, base::ToString(base::GetDefaultLogAbortLevel()),
"Log messages severity that causes termination.");
DEFINE_string(resources_path, "", "Path to resources directory.");
DEFINE_string(kml_path, "", "Path to a directory with kml files to take screenshots.");
DEFINE_string(kml_path, "", "Activates screenshot mode. Path to a kml file or a directory "
"with kml files to take screenshots.");
DEFINE_string(points, "", "Activates screenshot mode. Points on the map and zoom level "
"[1..18] in format \"lat,lon,zoom[;lat,lon,zoom]\" or path to a file with points in "
"the same format. Each point and zoom define a place on the map to take screenshot.");
DEFINE_string(rects, "", "Activates screenshot mode. Rects on the map in format"
"\"lat_leftBottom,lon_leftBottom,lat_rightTop,lon_rightTop"
"[;lat_leftBottom,lon_leftBottom,lat_rightTop,lon_rightTop]\" or path to a file with "
"rects in the same format. Each rect defines a place on the map to take screenshot.");
DEFINE_string(dst_path, "", "Path to a directory to save screenshots.");
DEFINE_string(lang, "", "Device language.");
DEFINE_int32(width, 0, "Screenshot width.");
DEFINE_int32(height, 0, "Screenshot height.");
DEFINE_double(dpi_scale, 0.0, "Screenshot dpi scale.");
DEFINE_double(dpi_scale, 0.0, "Screenshot dpi scale (mdpi = 1.0, hdpi = 1.5, "
"xhdpiScale = 2.0, 6plus = 2.4, xxhdpi = 3.0, xxxhdpi = 3.5).");
using namespace std;
@ -165,10 +174,24 @@ int main(int argc, char * argv[])
if (!FLAGS_lang.empty())
(void)::setenv("LANGUAGE", FLAGS_lang.c_str(), 1);
if (!FLAGS_kml_path.empty())
if (!FLAGS_kml_path.empty() || !FLAGS_points.empty() || !FLAGS_rects.empty())
{
screenshotParams = std::make_unique<qt::ScreenshotParams>();
screenshotParams->m_kmlPath = FLAGS_kml_path;
if (!FLAGS_kml_path.empty())
{
screenshotParams->m_kmlPath = FLAGS_kml_path;
screenshotParams->m_mode = qt::ScreenshotParams::Mode::KmlFiles;
}
else if (!FLAGS_points.empty())
{
screenshotParams->m_points = FLAGS_points;
screenshotParams->m_mode = qt::ScreenshotParams::Mode::Points;
}
else if (!FLAGS_rects.empty())
{
screenshotParams->m_rects = FLAGS_rects;
screenshotParams->m_mode = qt::ScreenshotParams::Mode::Rects;
}
if (!FLAGS_dst_path.empty())
screenshotParams->m_dstPath = FLAGS_dst_path;
if (FLAGS_width > 0)

View file

@ -79,7 +79,7 @@ protected:
void Build();
void ShowInfoPopup(QMouseEvent * e, m2::PointD const & pt);
virtual void OnViewportChanged(ScreenBase const & screen);
void OnViewportChanged(ScreenBase const & screen);
// QOpenGLWidget overrides:
void initializeGL() override;

View file

@ -8,15 +8,108 @@
#include "platform/preferred_languages.hpp"
#include "indexer/scales.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <QtGui/QPixmap>
#include <chrono>
#include <functional>
#include <fstream>
#include <sstream>
namespace
{
bool ParsePoint(std::string const & s, char const * delim, m2::PointD & pt, int & zoom)
{
// Order in string is: lat, lon, zoom.
strings::SimpleTokenizer iter(s, delim);
if (!iter)
return false;
double lat;
double lon;
int zoomLevel;
if (strings::to_double(*iter, lat) && mercator::ValidLat(lat) && ++iter &&
strings::to_double(*iter, lon) && mercator::ValidLon(lon) && ++iter &&
strings::to_int(*iter, zoomLevel) &&
zoomLevel >= 1 && zoomLevel <= scales::GetUpperStyleScale())
{
pt = mercator::FromLatLon(lat, lon);
zoom = zoomLevel;
return true;
}
return false;
}
bool ParseRect(std::string const & s, char const * delim, m2::RectD & rect)
{
// Order in string is: latLeftBottom, lonLeftBottom, latRigthTop, lonRigthTop.
strings::SimpleTokenizer iter(s, delim);
if (!iter)
return false;
double latLeftBottom;
double lonLeftBottom;
double latRigthTop;
double lonRigthTop;
if (strings::to_double(*iter, latLeftBottom) && mercator::ValidLat(latLeftBottom) && ++iter &&
strings::to_double(*iter, lonLeftBottom) && mercator::ValidLon(lonLeftBottom) && ++iter &&
strings::to_double(*iter, latRigthTop) && mercator::ValidLat(latRigthTop) && ++iter &&
strings::to_double(*iter, lonRigthTop) && mercator::ValidLon(lonRigthTop))
{
rect = m2::RectD(mercator::FromLatLon(latLeftBottom, lonLeftBottom),
mercator::FromLatLon(latRigthTop, lonRigthTop));
return true;
}
return false;
}
bool ParsePointsStr(std::string const & pointsStr, std::list<std::pair<m2::PointD, int>> & points)
{
strings::SimpleTokenizer tupleIter(pointsStr, ";");
m2::PointD pt;
int zoom;
while (tupleIter)
{
if (ParsePoint(*tupleIter, ",", pt, zoom))
{
points.emplace_back(pt, zoom);
}
else
{
LOG(LWARNING, ("Failed to parse point and zoom:", *tupleIter));
return false;
}
++tupleIter;
}
return true;
}
bool ParseRectsStr(std::string const & rectsStr, std::list<m2::RectD> & rects)
{
strings::SimpleTokenizer tupleIter(rectsStr, ";");
m2::RectD rect;
while (tupleIter)
{
if (ParseRect(*tupleIter, ",", rect))
{
rects.push_back(rect);
}
else
{
LOG(LWARNING, ("Failed to parse rect:", *tupleIter));
return false;
}
++tupleIter;
}
return true;
}
} // namespace
namespace qt
{
Screenshoter::Screenshoter(ScreenshotParams const & screenshotParams, Framework & framework, QWidget * widget)
@ -37,36 +130,43 @@ void Screenshoter::Start()
m_screenshotParams.m_dstPath = base::JoinPath(m_screenshotParams.m_kmlPath, "screenshots");
LOG(LINFO, ("\nScreenshoter parameters:"
"\n mode:", m_screenshotParams.m_mode,
"\n kml_path:", m_screenshotParams.m_kmlPath,
"\n points:", m_screenshotParams.m_points,
"\n rects:", m_screenshotParams.m_rects,
"\n dst_path:", m_screenshotParams.m_dstPath,
"\n width:", m_screenshotParams.m_width,
"\n height:", m_screenshotParams.m_height,
"\n dpi_scale:", m_screenshotParams.m_dpiScale));
if (!Platform::IsDirectory(m_screenshotParams.m_kmlPath) || !Platform::MkDirChecked(m_screenshotParams.m_dstPath))
if (!Platform::MkDirChecked(m_screenshotParams.m_dstPath))
{
ChangeState(State::FileError);
return;
}
Platform::FilesList files;
Platform::GetFilesByExt(m_screenshotParams.m_kmlPath, kKmlExtension, files);
for (auto const & file : files)
{
auto const filePath = base::JoinPath(m_screenshotParams.m_kmlPath, file);
m_filesToProcess.push_back(filePath);
}
m_filesCount = m_filesToProcess.size();
if (!PrepareItemsToProcess())
return;
ChangeState(State::Ready);
ProcessNextKml();
ProcessNextItem();
}
void Screenshoter::ProcessNextItem()
{
CHECK_EQUAL(m_state, State::Ready, ());
switch (m_screenshotParams.m_mode)
{
case ScreenshotParams::Mode::KmlFiles: return ProcessNextKml();
case ScreenshotParams::Mode::Points: return ProcessNextPoint();
case ScreenshotParams::Mode::Rects: return ProcessNextRect();
}
UNREACHABLE();
}
void Screenshoter::ProcessNextKml()
{
CHECK_EQUAL(m_state, State::Ready, ());
std::string const postfix = "_" + languages::GetCurrentNorm();
std::unique_ptr<kml::FileData> kmlData;
while (kmlData == nullptr && !m_filesToProcess.empty())
@ -103,12 +203,61 @@ void Screenshoter::ProcessNextKml()
ChangeState(State::WaitPosition);
auto const newCatId = bookmarkManager.GetBmGroupsIdList().front();
m_dataRect = bookmarkManager.GetCategoryRect(newCatId);
ExpandBookmarksRectForPreview(m_dataRect);
CHECK(m_dataRect.IsValid(), ());
auto rect = bookmarkManager.GetCategoryRect(newCatId);
ExpandBookmarksRectForPreview(rect);
CHECK(rect.IsValid(), ());
if (!CheckViewport())
m_framework.ShowBookmarkCategory(newCatId, false);
m_framework.ShowBookmarkCategory(newCatId, false);
WaitPosition();
}
void Screenshoter::ProcessNextRect()
{
if (m_rectsToProcess.empty())
{
ChangeState(State::Done);
return;
}
std::string const postfix = "_" + languages::GetCurrentNorm();
std::stringstream ss;
ss << "rect_" << std::setfill('0') << std::setw(4) <<
m_itemsCount - m_rectsToProcess.size() << postfix;
m_nextScreenshotName = ss.str();
auto const rect = m_rectsToProcess.front();
m_rectsToProcess.pop_front();
CHECK(rect.IsValid(), ());
ChangeState(State::WaitPosition);
m_framework.ShowRect(rect, -1 /* maxScale */,
false /* animation */, false /* useVisibleViewport */);
WaitPosition();
}
void Screenshoter::ProcessNextPoint()
{
if (m_pointsToProcess.empty())
{
ChangeState(State::Done);
return;
}
std::string const postfix = "_" + languages::GetCurrentNorm();
std::stringstream ss;
ss << "point_" << std::setfill('0') << std::setw(4) <<
m_itemsCount - m_pointsToProcess.size() << postfix;
m_nextScreenshotName = ss.str();
auto const pointZoom = m_pointsToProcess.front();
m_pointsToProcess.pop_front();
ChangeState(State::WaitPosition);
m_framework.SetViewportCenter(pointZoom.first, pointZoom.second, false /* animation */);
WaitPosition();
}
void Screenshoter::PrepareCountries()
@ -158,25 +307,11 @@ void Screenshoter::OnCountryChanged(storage::CountryId countryId)
}
}
bool Screenshoter::CheckViewport()
void Screenshoter::OnPositionReady()
{
if (m_state != State::WaitPosition)
return false;
auto const currentViewport = m_framework.GetCurrentViewport();
double const kEpsRect = 1e-2;
double const kEpsCenter = 1e-3;
double const minCheckedZoomLevel = 5;
if (m_framework.GetDrawScale() < minCheckedZoomLevel ||
(m_dataRect.Center().EqualDxDy(currentViewport.Center(), kEpsCenter) &&
(fabs(m_dataRect.SizeX() - currentViewport.SizeX()) < kEpsRect ||
fabs(m_dataRect.SizeY() - currentViewport.SizeY()) < kEpsRect)))
{
PrepareCountries();
return true;
}
return false;
return;
PrepareCountries();
}
void Screenshoter::OnGraphicsReady()
@ -188,12 +323,20 @@ void Screenshoter::OnGraphicsReady()
SaveScreenshot();
}
void Screenshoter::WaitPosition()
{
m_framework.NotifyGraphicsReady([this]()
{
GetPlatform().RunTask(Platform::Thread::Gui, [this] { OnPositionReady(); });
}, false /* needInvalidate */ );
}
void Screenshoter::WaitGraphics()
{
m_framework.NotifyGraphicsReady([this]()
{
GetPlatform().RunTask(Platform::Thread::Gui, [this] { OnGraphicsReady(); });
});
{
GetPlatform().RunTask(Platform::Thread::Gui, [this] { OnGraphicsReady(); });
}, true /* needInvalidate */ );
}
void Screenshoter::SaveScreenshot()
@ -216,22 +359,142 @@ void Screenshoter::SaveScreenshot()
}
m_nextScreenshotName.clear();
ProcessNextKml();
ProcessNextItem();
}
void Screenshoter::ChangeState(State newState)
void Screenshoter::ChangeState(State newState, std::string const & msg)
{
m_state = newState;
if (m_screenshotParams.m_statusChangedFn)
{
std::ostringstream ss;
ss << "[" << m_filesCount - m_filesToProcess.size() << "/" << m_filesCount << "] file: "
ss << "[" << m_itemsCount - GetItemsToProcessCount() << "/" << m_itemsCount << "] file: "
<< m_nextScreenshotName << " state: " << DebugPrint(newState);
if (!msg.empty())
ss << " msg: " << msg;
LOG(LINFO, (ss.str()));
m_screenshotParams.m_statusChangedFn(ss.str(), newState == Screenshoter::State::Done);
}
}
size_t Screenshoter::GetItemsToProcessCount()
{
switch (m_screenshotParams.m_mode)
{
case ScreenshotParams::Mode::KmlFiles: return m_filesToProcess.size();
case ScreenshotParams::Mode::Points: return m_pointsToProcess.size();
case ScreenshotParams::Mode::Rects: return m_rectsToProcess.size();
}
UNREACHABLE();
}
bool Screenshoter::PrepareItemsToProcess()
{
switch (m_screenshotParams.m_mode)
{
case ScreenshotParams::Mode::KmlFiles: return PrepareKmlFiles();
case ScreenshotParams::Mode::Points: return PreparePoints();
case ScreenshotParams::Mode::Rects: return PrepareRects();
}
UNREACHABLE();
}
bool Screenshoter::PrepareKmlFiles()
{
if (!Platform::IsDirectory(m_screenshotParams.m_kmlPath) &&
!Platform::IsFileExistsByFullPath(m_screenshotParams.m_kmlPath))
{
ChangeState(State::FileError, "Invalid kml path.");
return false;
}
if (!Platform::IsDirectory(m_screenshotParams.m_kmlPath))
{
m_filesToProcess.push_back(m_screenshotParams.m_kmlPath);
}
else
{
Platform::FilesList files;
Platform::GetFilesByExt(m_screenshotParams.m_kmlPath, kKmlExtension, files);
for (auto const & file : files)
{
auto const filePath = base::JoinPath(m_screenshotParams.m_kmlPath, file);
m_filesToProcess.push_back(filePath);
}
}
m_itemsCount = m_filesToProcess.size();
return true;
}
bool Screenshoter::PreparePoints()
{
if (!LoadPoints(m_screenshotParams.m_points))
{
ChangeState(State::ParamsError, "Invalid points.");
return false;
}
m_itemsCount = m_pointsToProcess.size();
return true;
}
bool Screenshoter::PrepareRects()
{
if (!LoadRects(m_screenshotParams.m_rects))
{
ChangeState(State::ParamsError, "Invalid rects.");
return false;
}
m_itemsCount = m_rectsToProcess.size();
return true;
}
bool Screenshoter::LoadRects(std::string const & rects)
{
if (Platform::IsFileExistsByFullPath(rects))
{
std::ifstream fin(rects);
std::string line;
while (std::getline(fin, line))
{
if (!ParseRectsStr(line, m_rectsToProcess))
{
m_rectsToProcess.clear();
return false;
}
}
}
else if (!ParseRectsStr(rects, m_rectsToProcess))
{
m_rectsToProcess.clear();
return false;
}
return !m_rectsToProcess.empty();
}
bool Screenshoter::LoadPoints(std::string const & points)
{
if (Platform::IsFileExistsByFullPath(points))
{
std::ifstream fin(points);
std::string line;
while (std::getline(fin, line))
{
if (!ParsePointsStr(line, m_pointsToProcess))
{
m_pointsToProcess.clear();
return false;
}
}
}
else if (!ParsePointsStr(points, m_pointsToProcess))
{
m_pointsToProcess.clear();
return false;
}
return !m_pointsToProcess.empty();
}
std::string DebugPrint(Screenshoter::State state)
{
switch (state)
@ -243,8 +506,20 @@ std::string DebugPrint(Screenshoter::State state)
case Screenshoter::State::WaitGraphics: return "WaitGraphics";
case Screenshoter::State::Ready: return "Ready";
case Screenshoter::State::FileError: return "FileError";
case Screenshoter::State::ParamsError: return "ParamsError";
case Screenshoter::State::Done: return "Done";
}
UNREACHABLE();
}
std::string DebugPrint(ScreenshotParams::Mode mode)
{
switch (mode)
{
case ScreenshotParams::Mode::KmlFiles: return "KmlFiles";
case ScreenshotParams::Mode::Points: return "Points";
case ScreenshotParams::Mode::Rects: return "Rects";
}
UNREACHABLE();
}
} // namespace qt

View file

@ -22,6 +22,16 @@ struct ScreenshotParams
using TStatusChangedFn = std::function<void(std::string const & /* status */, bool finished)>;
enum class Mode
{
Points,
Rects,
KmlFiles
};
Mode m_mode;
std::string m_points;
std::string m_rects;
std::string m_kmlPath;
std::string m_dstPath;
uint32_t m_width = kDefaultWidth;
@ -38,7 +48,8 @@ public:
void Start();
void OnCountryChanged(storage::CountryId countryId);
bool CheckViewport();
void OnPositionReady();
void OnGraphicsReady();
private:
@ -51,14 +62,29 @@ private:
WaitGraphics,
Ready,
FileError,
ParamsError,
Done
};
void ProcessNextItem();
void ProcessNextKml();
void ProcessNextRect();
void ProcessNextPoint();
void PrepareCountries();
void SaveScreenshot();
void WaitPosition();
void WaitGraphics();
void ChangeState(State newState);
void ChangeState(State newState, std::string const & msg = "");
size_t GetItemsToProcessCount();
bool PrepareItemsToProcess();
bool PrepareKmlFiles();
bool PreparePoints();
bool PrepareRects();
bool LoadRects(std::string const & rects);
bool LoadPoints(std::string const & points);
friend std::string DebugPrint(State state);
@ -67,11 +93,13 @@ private:
Framework & m_framework;
QWidget * m_widget;
std::list<std::string> m_filesToProcess;
size_t m_filesCount = 0;
std::list<std::pair<m2::PointD, int>> m_pointsToProcess;
std::list<m2::RectD> m_rectsToProcess;
size_t m_itemsCount = 0;
std::set<storage::CountryId> m_countriesToDownload;
std::string m_nextScreenshotName;
m2::RectD m_dataRect;
};
std::string DebugPrint(Screenshoter::State state);
std::string DebugPrint(ScreenshotParams::Mode mode);
} // namespace qt