Win32 API в Delphi

КОМПЬЮТЕРНЫЕ КУРСЫ "ПОИСК"

[Главная страница] [Win32 API] [Контакты]

Контекст в памяти


Как отобразить BMP файл на форме

dhc1

ТЕОРИЯ

Этот контекст используется для хранения изображений, которые затем будут скопированы на устройство вывода. Сам по себе контекст в памяти не создается. Он обязательно создается как совместимый с тем устройством или окном, на которое предполагается копировать информацию (вот он - совместимый контекст - переходник между программой и драйвером устройства!). Алгоритм работы с контекстом в памяти состоит из нескольких шагов:

  1. Получения хэндла контекста устройства (назовем его hDC - handle of Device Context) для окна, в которое будет осуществляться вывод изображения.
  2. Получения хэндла bitmap'a, который будет отображаться в окне.
  3. Получения совместимого с hDC контекста в памяти (для хранения изображения) с помощью функции CreateCompatibleDC() (обратите внимание на название функции - создать СОВМЕСТИМЫЙ контекст).
  4. Выбора изображения (hBitmap) как текущего для контекста в памяти (hCompatibleDC).
  5. Копирования изображения контекста в памяти (hCompatibleDC) на контекст устройства (hDC).
  6. Удаления совместимого контекста (hCompatibleDC);
  7. Принятия мер для того, чтобы замещенный bitmap из контекста в памяти не остался в памяти.
  8. Освобождения контекста устройства (hDC).

При необходимости шаги 6 и 7 можно поменять местами. Когда и как удалять замещенный bitmap, зависит от программиста и поставленной перед ним задачи.

Именно этот способ и используется в большинстве программ для копирования изображения.

Но, как известно, лучше один раз увидеть, чем сто раз услышать (по-английски это звучит еще более категорично - seeing is believing - увидеть, значит поверить). Поэтому давайте напишем небольшую программу, в которой продемонстрируем возможности Windows по манипулированию изображениями.

SEEING IS BELIEVING

Перед тем, как начинать писать программу, мы должны прояснить для себя еще одну тему. В каких единицах измеряются размеры окна и, соответственно, все смещения в окне? Для того чтобы ответить на эта вопрос, мы должны рассмотреть

Режимы отображения

Подавляющее большинство функций, работающих с оконными координатами, определяют координаты относительно начала рабочей области окна, т. е. от левого верхнего угла.

Таким образом, даже при перемещении окна координаты объектов внутри окна остаются неизменными. При этом единицы, в которых измеряются координаты, зависят от режима отображения (mapping mode), установленного для данного окна. Единицы измерения, зависящие от режима отображения, называют логическими единицами, а координаты в этом случае называют логическими координатами.

При выводе информации на конкретное устройство единицы логических координат преобразуются в физические единицы, которыми являются пиксели.

Для установки текущего режима отображения используется функция SetMappingMode(), которая описана следующим образом:

function SetMapMode(
    DC: HDC;
    p2: Integer
): Integer; stdcall;

ППервый аргумент этой функции - хэндл контекста устройства, для которого устанавливается данный режим. Второй аргумент определяет задаваемый режим отображения. При создании окна по умолчанию устанавливается режим ММ_ТЕХТ, т. е. все координаты исчисляются в пикселах.

СОДЕРЖИМОЕ


Наша программа будет отображать bitmap в окне и при необходимости производить его масштабирование:

program Project1;

uses
  Windows, Messages;

var
  wc: TWndClassEx;
  MainWnd: THandle;
  Mes: TMsg;

const
  ClassName = 'DCDemo';
  AppName = 'Program 1';


function WndProc(hWnd, Msg, WParam, LParam: Integer): Integer; stdcall;
var
  hwndDC, hCompatibleDC: HDC;
  PaintStruct: TPaintStruct;
  hBitmap, hOldBitmap: THandle;
  Rect: TRect;
  Bitmap: TBitmap;
begin
  Result := 0;
  case Msg of
    WM_DESTROY:
      PostQuitMessage(0);
    WM_PAINT: begin
      // получаем контекст устройства
      hwndDC := BeginPaint(hWnd, PaintStruct);
      // Загружаем bitmap, который будет отображаться в окне, из файла
      hBitmap := LoadImage(0, 'chemical.bmp', IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
      // Получаем размерность загруженного bitmap
      GetObject(hBitmap, SizeOf(TBitmap), @Bitmap);
      // Создаем совместимый с контекстом окна контекст в памяти
      hCompatibleDC := CreateCompatibleDC(hwndDC);
      // Делаем загруженный из файла bitmap текущим в совместимом формает
      hOldBitmap := SelectObject(hCompatibleDC, hBitmap);
      // Определяем размер рабочей области окна.
      GetClientRect(hWnd, Rect);
      // Копируем bitmap с совместимого на основной контекст с масштабированием
      StretchBlt(hwndDC, 0, 0, Rect.Right, Rect.Bottom, hCompatibleDC, 0, 0, Bitmap.bmWidth, Bitmap.bmHeight,  SRCCOPY);
      // Вновь делаем старый bitmap текущим
      SelectObject(hCompatibleDC, hOldBitmap);
      // Удаляем загруженный с диска bitmap
      DeleteObject(hBitmap);
      // Удалем совместимый конекст
      DeleteDC(hCompatibleDC);
      // Освобождаем основной контекст, завершая перерисовку рабочей области окна.
      EndPaint(hWnd, PaintStruct);
    end
  else
    Result := DefWindowProc(hWnd, Msg, WParam, LParam);
  end;
end;

begin
  wc.cbSize := SizeOf(wc);
  wc.style := CS_VREDRAW or CS_HREDRAW;
  wc.lpfnWndProc := @WndProc;
  wc.cbClsExtra := 0;
  wc.cbWndExtra := 0;
  wc.hInstance := HInstance;
  wc.hbrBackground := COLOR_WINDOW + 1;
  wc.hCursor := LoadCursor(0, IDC_ARROW);
  wc.hIcon := LoadIcon(0, IDI_APPLICATION);
  wc.hIconSm := wc.hIcon;
  wc.lpszMenuName := nil;
  wc.lpszClassName := ClassName;

  if RegisterClassEx(wc) = 0 then Exit;

   MainWnd := CreateWindowEx(WS_EX_CLIENTEDGE, ClassName, AppName, WS_OVERLAPPEDWINDOW,
    Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), 300,
    200, 0, 0, HInstance, nil);

  ShowWindow(MainWnd, SW_SHOWNORMAL);
  UpdateWindow(MainWnd);

  while GetMessage(Mes, 0,0,0) do
  begin
    TranslateMessage(Mes);
    DispatchMessage(Mes);
  end;
end.

АНАЛИЗ

Первый шаг алгортима - получить хэндл контекста устройства - мы выполянем посредством вызова функции BeginPaint(hWnd, PaintStruct). Аргумент hWnd очевиден - мы получаем контекст данного окна. Что же касается PaintStruct ...

Понятно, что окно далеко не всегда должно перерисовываться полностью. К примеру, только часть окна перекрывается другим окном. Естественно, что и перерисоваться должна только часть окна. В этом случае полная перерисовка всего окна будет только лишней тратой времени и ресурсов компьютера. Windows «знает» о том, какая часть окна перекрыта. При необходимости перерисовки (при вызове BeginPaint) система заполняет структуру типа TPaintStruct, адрес которой передается системе как второй аргумент BeginPaint. Среди всех полей структуры типа TPaintStruct основным (на мой взгляд) является поле, содержащее координаты той области (clipping region), которая должна быть перерисована. В нашем примере информация, хранящаяся в этой структуре, не используется, но я прошу читателя обратить внимание на эту структуру и в дальнейшем использовать ее. Получив от функции BeginPaint хэндл контекста устройства (hwndDC), будем считать первый шаг выполненным.

Второй шаг - получение хэндла bitmap'a, который будет отображаться в окне - мы делаем, вызывая функцию Loadlmage. Я не случайно воспользовался именно этой функцией. Во-первых, возможности этой функции достаточно широки. Она позволяет загружать графические образы как из ресурсов, записанных в исполняемом файле, так и из файлов, содержащих только изображения. Графическим образом может быть bitmap, курсор и иконка. Кроме этого, функция позволяет управлять параметрами отображения и загрузки образа. Во-вторых, подавляющее большинство функций работают с ресурсами, сохраненными в исполняемом файле, и у программистов, начинающих осваивать Win32, попытки загрузить что-либо из файла сопровождаются некоторыми трудностями. Эта функция описана следующим образом:

function LoadImage(
    hInst: HINST;
    ImageName: PWideChar;
    ImageType: UINT;
    X, Y: Integer;
    Flags: UINT
): THandle; stdcall;

Первый, второй и последний аргументы этой функции работают в связке. Первый apryмeнт(hlnst) - хэндл программы. Второй аргумент -ImageName - определяет загружаемый объект. Последний аргумент - Flags - содержит флаги, определяющие режим загрузки объекта. Среди этих флагов есть флаг LR_LOADFROMFILE. Его название определяет его назначение - если этот флаг установлен, загрузка происходит из внешнего файла. От значения первого и последнего аргументов зависит, как будет интерпретирован второй аргумент.

Третий аргумент - тип образа, он может принимать значения IMAGE_BITMAP, IMAGE_CURSOR, IMAGE_ICON и IMAGE__ENHMETAFILE. Четвертый и пятый аргументы указывают ширину и высоту иконки или курсора и в нашем примере не используются.

Функция Loadlmage возвращает нам хэндл загруженного bitmap'a (hBitmap) (или nil, если где-то что-то не так), после чего мы можем считать второй шаг нашего алгоритма пройденным.

Третий шаг - получение совместимого контекста в памяти - выполняется посредством вызова функции CreateCompatibleDC. Единственный аргумент этой функции - хэндл контекста (hDC), для которого создается совместимый контекст.

Четвертый шаг мы реализуем вызовом функции SelectObject. Первым аргументом указываем хэндл контекста, в котором замещается текущий элемент (в данном случае это хэндл только что созданного контекста в памяти hCompatibleDC), а вторым - хэндл элемента, которым замещается текущий элемент (хэндл загруженного bitmap'a hBitmap). Немаловажно, что эта функция возвращает хэндл ЗАМЕЩЕННОГО элемента (hOldBitmap), т. е, впоследствии с этим элементом могут также производиться манипуляции.

А вот на пятом шаге происходит то, ради чего мы заварили всю эту кашу с загрузкой bitmap'ов, совместимыми контекстами и прочим. Для копирования bitmap'a (с масштабированием) с одного контекста на другой, мы используем функцию StretchBlt, одну из «могучих bit», по меткому выражению Чарльза Петцольда. К их числу, помимо StretchBlt, относятся BitBlt и PatBlt.

Наверное, StretchBlt является самой «могучей» из них. И наверное, ее мощь и обусловила наличие у этой функции «всего-навсего» одиннадцати аргументов.

Источник: П.В. Румянцев. Азбука программирования в WIN32 API.