Сегодня в этой статье мы рассмотрим, как легко сделать снимок любого элемента управления или группы элементов управления в библиотеке FireMonkey. Для этого рассмотрим небольшой демо-проект.
Файл проекта можно скачать тут: fmx_make_control_screenshot.zip
Содержание
Программа будет делать:
- Снимок любых элементов управления, которые находятся в контейнере «layoutSource: TLayout» (верхняя половина формы);
- Отображать его в «imgDest: TImage» (Нижняя половина формы);
- Сохранять полученный скриншот в файл «screenshot.png».
Немного теории
В библиотеке FireMonkey каждый элемент управления, являющимся предком от TControl и TControl3D, отвечает за свою отрисовку и передачу управления по отрисовке дочерним контролам. В момент отрисовки контрола. Упрощенная схема отрисовки представлена ниже:
- Текущая платформа запрашивает форму на свою отрисовку в указанных регионах (областях обновления изображения);
- Форма выполняет проверку на попадание элементов управления верхнего уровня (кем она непосредственно владеет) в указанные регионы;
- Элементы управления, которые частично или подностью попадают в области отрисовки передается управление на отрисовку;
- Каждый элемент управления вначале отрисовывает себя сам, затем передает управление по отрисовке каждому дочернему контролу.
Таким образом выполняется отрисовка формы. На самом деле процесс отрисовки более сложный и содержит ряд дополнительных шагов, о которых я не упомянул здесь.
Это упрощенное объяснение отрисовки. Но этого достаточно в рамках этот статьи. Теперь перейдем к практике.
Немного практики
У каждого элемента управления есть метод, который позволяет отрисовать себя и всех потомков на указанной канве:
procedure PaintTo(const ACanvas: TCanvas; const ADestRect: TRectF; const AParent: TFmxObject = nil);
- ACanvas — Канва, на которой необходимо произвести отрисовку
- ADestRect — Область отрисовки. Обратите внимание, что выводимое изображение будет подгоняться под эту область.
- AParent (не обязательное) — Если вы хотите получить изображение в рамках другого контейнера, то нужно указать это.
План получения скриншота следующий:
procedure TForm51.ActionMakeScreenshotExecute(Sender: TObject); var BitmapBuffer: TBitmap; SourceRect: TRectF; begin // Фиксируем размер снимаемой области SourceRect := TRectF.Create(0, 0, layoutSource.Width, layoutSource.Height); // Создаем временный буфер для получения скриншота BitmapBuffer := TBitmap.Create(Round(SourceRect.Width), Round(SourceRect.Height)); try // Переводим канву в режим отрисовки - начинаем процесс отрисовки сцены if BitmapBuffer.Canvas.BeginScene then try // Говорим контролу отрисовать себя в канве нашего буфера в указанной области layoutSource.PaintTo(BitmapBuffer.Canvas, SourceRect); finally // Завершаем процесс отрисовки, заканчивая формируемую сцену BitmapBuffer.Canvas.EndScene; end; imgDest.Bitmap.Assign(BitmapBuffer); BitmapBuffer.SaveToFile('./screenshot.png'); finally FreeAndNil(BitmapBuffer); end; end;
Если вы хотите вывести только один контрол, нужно вызывать метод TControl.PaintTo для того контрола, для которого вы хотите получить скриншот (не заьыв про размеры SourceRect).
Результат работы примера представлен ниже:
Файл проекта можно скачать тут: fmx_make_control_screenshot.zip
1. У меня с фантазией плохо. Хотелось бы знать, зачем это нужно.
2. Просто любопытно — насколько вероятен отказ в обслуживании BeginScene? Нехватка памяти? Почему в примере на негатив не Abort или Exit, а только обход рисования? Получается, мы всё равно какой-то пустой снимок делаем, а правильно ли это? Я-то сам как-то без if, просто try-finaly пишу — упадёт так упадёт, сразу понятно. Идеологический посыл какой?
3. Не влияет ли на Scene.GetSceneScale на размеры изображения контролов? На ПК обычно SceneScale=1, а на мобильнике уже другой. Я бы сам проверил, но сейчас Делфи далеко.