calls_group_viewport_raster.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/group/calls_group_viewport_raster.h"
  8. #include "calls/group/calls_group_common.h"
  9. #include "calls/group/calls_group_viewport_tile.h"
  10. #include "calls/group/calls_group_members_row.h"
  11. #include "data/data_peer.h"
  12. #include "media/view/media_view_pip.h"
  13. #include "webrtc/webrtc_video_track.h"
  14. #include "ui/image/image_prepare.h"
  15. #include "ui/painter.h"
  16. #include "lang/lang_keys.h"
  17. #include "styles/style_calls.h"
  18. #include "styles/palette.h"
  19. namespace Calls::Group {
  20. namespace {
  21. constexpr auto kBlurRadius = 15;
  22. } // namespace
  23. Viewport::RendererSW::RendererSW(not_null<Viewport*> owner)
  24. : _owner(owner)
  25. , _pinIcon(st::groupCallVideoTile.pin)
  26. , _pinBackground(
  27. (st::groupCallVideoTile.pinPadding.top()
  28. + st::groupCallVideoTile.pin.icon.height()
  29. + st::groupCallVideoTile.pinPadding.bottom()) / 2,
  30. st::radialBg) {
  31. }
  32. void Viewport::RendererSW::paintFallback(
  33. Painter &&p,
  34. const QRegion &clip,
  35. Ui::GL::Backend backend) {
  36. auto bg = clip;
  37. auto hq = PainterHighQualityEnabler(p);
  38. const auto bounding = clip.boundingRect();
  39. for (auto &[tile, tileData] : _tileData) {
  40. tileData.stale = true;
  41. }
  42. for (const auto &tile : _owner->_tiles) {
  43. if (!tile->visible()) {
  44. continue;
  45. }
  46. paintTile(p, tile.get(), bounding, bg);
  47. }
  48. const auto fullscreen = _owner->_fullscreen;
  49. const auto color = fullscreen ? QColor(0, 0, 0) : st::groupCallBg->c;
  50. for (const auto &rect : bg) {
  51. p.fillRect(rect, color);
  52. }
  53. for (auto i = _tileData.begin(); i != _tileData.end();) {
  54. if (i->second.stale) {
  55. i = _tileData.erase(i);
  56. } else {
  57. ++i;
  58. }
  59. }
  60. }
  61. void Viewport::RendererSW::validateUserpicFrame(
  62. not_null<VideoTile*> tile,
  63. TileData &data) {
  64. if (!_userpicFrame) {
  65. data.userpicFrame = QImage();
  66. return;
  67. } else if (!data.userpicFrame.isNull()) {
  68. return;
  69. }
  70. const auto size = tile->trackOrUserpicSize();
  71. data.userpicFrame = Images::BlurLargeImage(
  72. PeerData::GenerateUserpicImage(
  73. tile->row()->peer(),
  74. tile->row()->ensureUserpicView(),
  75. size.width(),
  76. 0),
  77. kBlurRadius);
  78. }
  79. void Viewport::RendererSW::paintTile(
  80. Painter &p,
  81. not_null<VideoTile*> tile,
  82. const QRect &clip,
  83. QRegion &bg) {
  84. const auto track = tile->track();
  85. const auto markGuard = gsl::finally([&] {
  86. tile->track()->markFrameShown();
  87. });
  88. const auto data = track->frameWithInfo(true);
  89. auto &tileData = _tileData[tile];
  90. tileData.stale = false;
  91. _userpicFrame = (data.format == Webrtc::FrameFormat::None);
  92. _pausedFrame = (track->state() == Webrtc::VideoState::Paused);
  93. validateUserpicFrame(tile, tileData);
  94. if (_userpicFrame || !_pausedFrame) {
  95. tileData.blurredFrame = QImage();
  96. } else if (tileData.blurredFrame.isNull()) {
  97. tileData.blurredFrame = Images::BlurLargeImage(
  98. data.original.scaled(
  99. VideoTile::PausedVideoSize(),
  100. Qt::KeepAspectRatio).mirrored(tile->mirror(), false),
  101. kBlurRadius);
  102. }
  103. const auto &image = _userpicFrame
  104. ? tileData.userpicFrame
  105. : _pausedFrame
  106. ? tileData.blurredFrame
  107. : data.original.mirrored(tile->mirror(), false);
  108. const auto frameRotation = _userpicFrame ? 0 : data.rotation;
  109. Assert(!image.isNull());
  110. const auto background = _owner->_fullscreen
  111. ? QColor(0, 0, 0)
  112. : st::groupCallMembersBg->c;
  113. const auto fill = [&](QRect rect) {
  114. const auto intersected = rect.intersected(clip);
  115. if (!intersected.isEmpty()) {
  116. p.fillRect(intersected, background);
  117. bg -= intersected;
  118. }
  119. };
  120. using namespace Media::View;
  121. const auto geometry = tile->geometry();
  122. const auto x = geometry.x();
  123. const auto y = geometry.y();
  124. const auto width = geometry.width();
  125. const auto height = geometry.height();
  126. const auto scaled = FlipSizeByRotation(
  127. image.size(),
  128. frameRotation
  129. ).scaled(QSize(width, height), Qt::KeepAspectRatio);
  130. const auto left = (width - scaled.width()) / 2;
  131. const auto top = (height - scaled.height()) / 2;
  132. const auto target = QRect(QPoint(x + left, y + top), scaled);
  133. if (UsePainterRotation(frameRotation)) {
  134. if (frameRotation) {
  135. p.save();
  136. p.rotate(frameRotation);
  137. }
  138. p.drawImage(RotatedRect(target, frameRotation), image);
  139. if (frameRotation) {
  140. p.restore();
  141. }
  142. } else if (frameRotation) {
  143. p.drawImage(target, RotateFrameImage(image, frameRotation));
  144. } else {
  145. p.drawImage(target, image);
  146. }
  147. bg -= target;
  148. if (left > 0) {
  149. fill({ x, y, left, height });
  150. }
  151. if (const auto right = left + scaled.width(); right < width) {
  152. fill({ x + right, y, width - right, height });
  153. }
  154. if (top > 0) {
  155. fill({ x, y, width, top });
  156. }
  157. if (const auto bottom = top + scaled.height(); bottom < height) {
  158. fill({ x, y + bottom, width, height - bottom });
  159. }
  160. paintTileControls(p, x, y, width, height, tile);
  161. paintTileOutline(p, x, y, width, height, tile);
  162. }
  163. void Viewport::RendererSW::paintTileOutline(
  164. Painter &p,
  165. int x,
  166. int y,
  167. int width,
  168. int height,
  169. not_null<VideoTile*> tile) {
  170. if (!tile->row()->speaking()) {
  171. return;
  172. }
  173. const auto outline = st::groupCallOutline;
  174. const auto &color = st::groupCallMemberActiveIcon;
  175. p.setPen(Qt::NoPen);
  176. p.fillRect(x, y, outline, height - outline, color);
  177. p.fillRect(x + outline, y, width - outline, outline, color);
  178. p.fillRect(
  179. x + width - outline,
  180. y + outline,
  181. outline,
  182. height - outline,
  183. color);
  184. p.fillRect(x, y + height - outline, width - outline, outline, color);
  185. }
  186. void Viewport::RendererSW::paintTileControls(
  187. Painter &p,
  188. int x,
  189. int y,
  190. int width,
  191. int height,
  192. not_null<VideoTile*> tile) {
  193. p.setClipRect(x, y, width, height);
  194. const auto guard = gsl::finally([&] { p.setClipping(false); });
  195. const auto wide = _owner->wide();
  196. if (wide) {
  197. // Pin.
  198. const auto pinInner = tile->pinInner();
  199. VideoTile::PaintPinButton(
  200. p,
  201. tile->pinned(),
  202. x + pinInner.x(),
  203. y + pinInner.y(),
  204. _owner->widget()->width(),
  205. &_pinBackground,
  206. &_pinIcon);
  207. // Back.
  208. const auto backInner = tile->backInner();
  209. VideoTile::PaintBackButton(
  210. p,
  211. x + backInner.x(),
  212. y + backInner.y(),
  213. _owner->widget()->width(),
  214. &_pinBackground);
  215. }
  216. const auto &st = st::groupCallVideoTile;
  217. const auto nameTop = y + (height
  218. - st.namePosition.y()
  219. - st::semiboldFont->height);
  220. if (_pausedFrame) {
  221. p.fillRect(x, y, width, height, QColor(0, 0, 0, kShadowMaxAlpha));
  222. const auto middle = (st::groupCallVideoPlaceholderHeight
  223. - st::groupCallPaused.height()) / 2;
  224. const auto pausedSpace = (nameTop - y)
  225. - st::groupCallPaused.height()
  226. - st::semiboldFont->height;
  227. const auto pauseIconSkip = middle - st::groupCallVideoPlaceholderIconTop;
  228. const auto pauseTextSkip = st::groupCallVideoPlaceholderTextTop
  229. - st::groupCallVideoPlaceholderIconTop;
  230. const auto pauseIconTop = !_owner->wide()
  231. ? (y + (height - st::groupCallPaused.height()) / 2)
  232. : (pausedSpace < 3 * st::semiboldFont->height)
  233. ? (pausedSpace / 3)
  234. : std::min(
  235. y + (height / 2) - pauseIconSkip,
  236. (nameTop
  237. - st::semiboldFont->height * 3
  238. - st::groupCallPaused.height()));
  239. const auto pauseTextTop = (pausedSpace < 3 * st::semiboldFont->height)
  240. ? (nameTop - (pausedSpace / 3) - st::semiboldFont->height)
  241. : std::min(
  242. pauseIconTop + pauseTextSkip,
  243. nameTop - st::semiboldFont->height * 2);
  244. st::groupCallPaused.paint(
  245. p,
  246. x + (width - st::groupCallPaused.width()) / 2,
  247. pauseIconTop,
  248. width);
  249. if (_owner->wide()) {
  250. p.drawText(
  251. QRect(x, pauseTextTop, width, y + height - pauseTextTop),
  252. tr::lng_group_call_video_paused(tr::now),
  253. style::al_top);
  254. }
  255. }
  256. const auto shown = _owner->_controlsShownRatio;
  257. if (shown == 0.) {
  258. return;
  259. }
  260. const auto fullShift = st.namePosition.y() + st::normalFont->height;
  261. const auto shift = anim::interpolate(fullShift, 0, shown);
  262. // Shadow.
  263. if (_shadow.isNull()) {
  264. _shadow = Images::GenerateShadow(
  265. st.shadowHeight,
  266. 0,
  267. kShadowMaxAlpha);
  268. }
  269. const auto shadowRect = QRect(
  270. x,
  271. y + (height - anim::interpolate(0, st.shadowHeight, shown)),
  272. width,
  273. st.shadowHeight);
  274. const auto shadowFill = shadowRect.intersected({ x, y, width, height });
  275. if (shadowFill.isEmpty()) {
  276. return;
  277. }
  278. const auto factor = style::DevicePixelRatio();
  279. if (!_pausedFrame) {
  280. p.drawImage(
  281. shadowFill,
  282. _shadow,
  283. QRect(
  284. 0,
  285. (shadowFill.y() - shadowRect.y()) * factor,
  286. _shadow.width(),
  287. shadowFill.height() * factor));
  288. }
  289. const auto row = tile->row();
  290. row->lazyInitialize(st::groupCallMembersListItem);
  291. // Mute.
  292. const auto &icon = st::groupCallVideoCrossLine.icon;
  293. const auto iconLeft = x + width - st.iconPosition.x() - icon.width();
  294. const auto iconTop = y + (height
  295. - st.iconPosition.y()
  296. - icon.height()
  297. + shift);
  298. row->paintMuteIcon(
  299. p,
  300. { iconLeft, iconTop, icon.width(), icon.height() },
  301. MembersRowStyle::Video);
  302. // Name.
  303. p.setPen(st::groupCallVideoTextFg);
  304. const auto hasWidth = width
  305. - st.iconPosition.x() - icon.width()
  306. - st.namePosition.x();
  307. const auto nameLeft = x + st.namePosition.x();
  308. row->name().drawLeftElided(
  309. p,
  310. nameLeft,
  311. nameTop + shift,
  312. hasWidth,
  313. width);
  314. }
  315. } // namespace Calls::Group