FireMonkey. Делаем скриншот любого контрола

Fire Monkey логотипСегодня в этой статье мы рассмотрим, как легко сделать снимок любого элемента управления или группы элементов управления в библиотеке FireMonkey. Для этого рассмотрим небольшой демо-проект.

Файл проекта можно скачать тут: fmx_make_control_screenshot.zip


Программа будет делать:

  1. Снимок любых элементов управления, которые находятся в контейнере «layoutSource: TLayout» (верхняя половина формы);
  2. Отображать его в «imgDest: TImage» (Нижняя половина формы);
  3. Сохранять полученный скриншот в файл «screenshot.png».
Внешний вид проекта

Внешний вид проекта

Немного теории

В библиотеке FireMonkey каждый элемент управления, являющимся предком от TControl и TControl3D, отвечает за свою отрисовку и передачу управления по отрисовке дочерним контролам. В момент отрисовки контрола. Упрощенная схема отрисовки представлена ниже:

02_scene_painting

  1. Текущая платформа запрашивает форму на свою отрисовку в указанных регионах (областях обновления изображения);
  2. Форма выполняет проверку на попадание элементов управления верхнего уровня (кем она непосредственно владеет) в указанные регионы;
  3. Элементы управления, которые частично или подностью попадают в области отрисовки передается управление на отрисовку;
  4. Каждый элемент управления вначале отрисовывает себя сам, затем передает управление по отрисовке каждому дочернему контролу.

Таким образом выполняется отрисовка формы. На самом деле процесс отрисовки более сложный и содержит ряд дополнительных шагов, о которых я не упомянул здесь.

Это упрощенное объяснение отрисовки. Но этого достаточно в рамках этот статьи. Теперь перейдем к практике.

Немного практики

У каждого элемента управления есть метод, который позволяет отрисовать себя и всех потомков на указанной канве:

procedure PaintTo(const ACanvas: TCanvas; const ADestRect: TRectF; const AParent: TFmxObject = nil);
  1. ACanvas — Канва, на которой необходимо произвести отрисовку
  2. ADestRect — Область отрисовки. Обратите внимание, что выводимое изображение будет подгоняться под эту область.
  3. 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).

Результат работы примера представлен ниже:

Результат работы примера. Первое изображение - до получения скриншота. Второе - после получения. Третье - файл png со скриншотом (с альфа каналом)

Результат работы примера. Первое изображение — до получения скриншота.
Второе — после получения.
Третье — файл png со скриншотом (с альфа каналом)

Файл проекта можно скачать тут: fmx_make_control_screenshot.zip

FireMonkey. Делаем скриншот любого контрола: 1 комментарий

  1. Павел

    1. У меня с фантазией плохо. Хотелось бы знать, зачем это нужно.
    2. Просто любопытно — насколько вероятен отказ в обслуживании BeginScene? Нехватка памяти? Почему в примере на негатив не Abort или Exit, а только обход рисования? Получается, мы всё равно какой-то пустой снимок делаем, а правильно ли это? Я-то сам как-то без if, просто try-finaly пишу — упадёт так упадёт, сразу понятно. Идеологический посыл какой?
    3. Не влияет ли на Scene.GetSceneScale на размеры изображения контролов? На ПК обычно SceneScale=1, а на мобильнике уже другой. Я бы сам проверил, но сейчас Делфи далеко.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *