Шаг 32.
Библиотека OWL.
Масштабирование изображения для принтера

    На этом шаге мы рассмотрим масштабирование изображения, выводимого на принтер.

    Хотя вывод информации на принтер более сложен, чем вывод в окно, оба процесса имеют много общего. Чтобы сгенерировать вывод для принтера, вы можете просто вызвать соответствующие функции GDI, адресуя их к вашему контексту устройства, в точности так же, как вы делаете это для окна. Вот почему вы можете использовать функцию главного окна Paint() для рисования изображений как на экране, так и на принтере. Разница только в контексте устройства, в который рисуется изображение.

    Драйверы принтера в Windows могут преобразовывать вызовы функции GDI в соответствующие графические команды для определенного принтера. Однако Windows не может масштабировать вывод таким образом, чтобы он выглядел одинаково для всех принтеров или даже так, чтобы он был похож на окно, из которого изображение "копируется". Это добавляет к обязанностям вашей программы преобразование логических координат изображения в физические с учетом разрешающей способности устройства печати.

    Например, рассмотрим точечный матричный принтер с горизонтальным разрешением 120 dpi (точек на дюйм) и лазерный принтер с горизонтальным разрешением 300 dpi. Если вы посылаете команду Rectangle(10, 10, 310, 100) на каждый из этих принтеров, вы получите различные результаты. На точечном матричном принтере прямоугольник будет иметь ширину почти три дюйма. Лазерный же принтер напечатает прямоугольник шириной в один дюйм.

    Когда вы имеете дело с принтерами, координаты GDI функции Rectangle() (или других GDI-функций для рисования) представляют собой координаты точек, в точности так же, как они являются координатами пикселей для окна. Другими словами, для принтера как с разрешением 120 dpi, так и с разрешением 300 dpi прямоугольник будет иметь ширину 300 точек, но из-за того, что точечный матричный принтер имеет большие точки, изображаемый им прямоугольник будет больше.

    Когда вы запустите программу из шага 30 и распечатаете изображение окна на вашем принтере, объект TPrinter нарисует прямоугольник на вашем принтере. Размер прямоугольника зависит от типа принтера, используемого в вашей системе. Чтобы сделать изображение на печатной странице пропорциональным изображению в рабочей области окна, вы должны выполнить масштабирование. Следующий пример показывает, как это делается.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\printer.h>
#include <owl\dc.h>
#include <owl\editfile.rh>
#include "pr32_1.rc"

//Класс приложения
class TApp: public TApplication
{
  public:
	 TApp():TApplication(){}
	 void InitMainWindow();
};

//Класс основного окна
class TWndw : public TFrameWindow
{
  public:
        TWndw(TWindow *parent, const char far *title);
        ~TWndw();
  protected:
        TPrinter *printer;
        void Paint(TDC &dc, BOOL, TRect&);
        void CmFilePrint();

        DECLARE_RESPONSE_TABLE(TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
      EV_COMMAND(CM_FILEPRINT, CmFilePrint), 
END_RESPONSE_TABLE;

// Класс печати.
class TWndwPrintout : public TPrintout
{
  protected:
       TWindow *window;
  public:
       TWndwPrintout (const char *title, TWindow *w);
       void PrintPage (int, TRect &rect, unsigned);
};

TWndw::TWndw(TWindow *parent, const char far *title):
		  TFrameWindow(parent,title, 0, FALSE)
{
	// Добавить меню к главному окну.
	AssignMenu("MENU_1");
	// Определить расположение и размеры окна.
	Attr.X=50;
	Attr.Y=50;
	Attr.W=400;
	Attr.H=200;
        // Создать объект окна TPrinter. 
        printer = new TPrinter;
}

// TWndw::~TWndw()
// Это деструктор главного окна.
TWndw::~TWndw() 
{
    delete printer;
}

//TWndw::CmFilePrint()
// Эта функция реагирует на команду Print меню File.
void  TWndw::CmFilePrint()
{
  // Если объект принтера существует...
  if (printer)
  {
     // Создать экземпляр объекта печати. 
     TWndwPrintout printout("Тест принтера", this);
     // Напечатать объект печати. 
     printer->Print(this, printout, TRUE);
  }
}

//TWndw::Paint()
// Эта функция переопределяет функцию Paint() из
// TWindow и реагирует на сообщения WM_PAINT, которые
// Windows посылает окну, если оно должно быть
// перерисовано. Однако, вызывая эту функцию с
// контекстом устройства TPrinter в качестве
// первого параметра, можно ее использовать для
// распечатки содержимого окна на принтере.
void TWndw::Paint(TDC &dc, BOOL, TRect&)
{
  // Нарисовать прямоугольник.
  dc.Rectangle (10,10,100,100);
}

//TWndwPrintout::TWndwPrintout()
// Это конструктор объекта  печати.
TWndwPrintout::TWndwPrintout(const char *title, TWindow *w) :
     TPrintout(title) 
{
  // Сохранить указатель окна для последующего использования.
  window = w;
}

// TWndwPrintout::PrintPage()
// Эта функция вызывается для каждой страницы
// документа. Именно в этом фрагменте программа
// создает изображение, которое появится на принтере.
void TWndwPrintout::PrintPage (int, TRect &rect, unsigned) 
{
  TSize oldVpExt, oldWndExt;
  // Установить произвольный режим отображения.
  int prevMode = DC->SetMapMode(MM_ISOTROPIC) ;
  // Получить размер рабочей области окна.
  TRect wndSize = window->GetClientRect();
  // Установить window в соотвествии с размерами
  // рабочей области окна.
  DC->SetWindowExt(wndSize.Size(),&oldWndExt);
  // Установить область просмотра в соотвествии
  // с размером страницы.
  DC->SetViewportExt(PageSize, &oldVpExt);
  // Вызвать функцию главного окна Paint(),
  // передавая контекст устройства объекта TPrinter,
  // а не контекст устройства окна.
  window->Paint(*DC, FALSE, rect);
  // Восстановить контекст устройства к начальным значениям.
  DC->SetWindowExt(oldWndExt);
  DC->SetViewportExt(oldVpExt);
  DC->SetMapMode(prevMode);
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw= new TWndw(0,"Использование принтера 2");
  SetMainWindow(wndw);
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif

#include <owl\printer.rh>

#define CM_EXIT        24310
#define CM_FILEPRINTT  24337

#ifdef RC_INVOKED

#include <owl\printer.rc>
#include <owl\except.rc>


MENU_1 MENU
{
 POPUP "&File"
 {
  MENUITEM "&Print...", CM_FILEPRINTT
  MENUITEM SEPARATOR
  MENUITEM "E&xit", CM_EXIT
 }
}
#endif
Текст этого приложения можно взять здесь.

    После запуска этой программы вы увидите такое же изображение, как в примере из шага 30. Но в этот раз при выборе команды Print из меню File вы получите вывод на печать, пропорциональный окну. Если вы возьмете окно меньшего размера, чтобы прямоугольник заполнял большую часть рабочей области окна, распечатываемый на странице прямоугольник будет больше, так как относительно окна его размеры не изменились. Сделайте окно больше, чтобы прямоугольник занял меньшую часть окна, и распечатываемый прямоугольник будет меньше. В этой программе рабочая область окна масштабируется таким образом, что все окно точно соответствует странице.

    Если вы рассмотрите объявления классов в программе, вы, возможно, подумаете, что они совпадают с аналогичными объявлениями из примера шага 30. Однако здесь функция PrintPage() объекта печати выполняет намного больше действий:

void TWndwPrintout::PrintPage (int, TRect &rect, unsigned) 
{
  TSize oldVpExt, oldWndExt;
  // Установить произвольный режим отображения.
  int prevMode = DC->SetMapMode(MM_ISOTROPIC) ;
  // Получить размер рабочей области окна.
  TRect wndSize = window->GetClientRect();
  // Установить window в соотвествии с размерами
  // рабочей области окна.
  DC->SetWindowExt(wndSize.Size(),&oldWndExt);
  // Установить область просмотра в соотвествии
  // с размером страницы.
  DC->SetViewportExt(PageSize, &oldVpExt);
  // Вызвать функцию главного окна Paint(),
  // передавая контекст устройства объекта TPrinter,
  // а не контекст устройства окна.
  window->Paint(*DC, FALSE, rect);
  // Восстановить контекст устройства к начальным значениям.
  DC->SetWindowExt(oldWndExt);
  DC->SetViewportExt(oldVpExt);
  DC->SetMapMode(prevMode);
}

    В этой функции происходит масштабирование. Сначала PrintPage() вызывает функцию контекста устройства принтера SetMapMode(), изменяя текущий режим отображения на MM_ISOTROPIC и сохраняя старый режим в prevMode. По умолчанию контекст устройства находится в режиме отображения ММ_ТЕХТ. Это означает, что логические единицы (в данном случае пиксели в рабочей области окна) являются такими же единицами, как и физические, или единицы устройства (в данном случае точки на принтере).

    В примере шага 30 режим отображения остается в MM_TEXT, т.е. одна точка в рабочей области окна преобразуется в одну точку на принтере. Так как окно и принтер имеют, вероятно, различное число точек на дюйм, результирующий вывод на печать не будет пропорционален окну. В режиме отображения MM_ISOTROPIC программа может задать свои собственные единицы вместо единиц измерения, связанных с другими режимами масштабирования.


    Замечание. Windows использует различные режимы отображения для преобразования логических координат в физические. Задаваемым по умолчанию режимом отображения является ММ_ТЕХТ - режим, в котором логические и физические координаты одни и те же. Режим отображения MM_TEXT определяет также, что значения х увеличиваются при движении вправо, а значения у увеличиваются при движении вниз. Вероятно, этот тип системы координат вы использовали при работе. Другие имеющиеся в вашем распоряжении режимы отображения включают: Для углубления знаний о режимах отображения воспользуйтесь руководствами по Borland C++ или руководством по программированию в среде Windows.

    Во второй строке текста программы PrintPage() вызывает GetClientRect() для восстановления рабочей области окна. Результаты обращения к этой функции сохраняются в wndSize, объекте TRect. Этот объект используется затем при вызове SetWindowExt(), которая устанавливает положение и размер подлежащей масштабированию прямоугольной области. В данном случае это вся пользовательская область окна. Старые расширения хранятся в oldWndExt. Вызов SetWindowExt() говорит: "Вот положение и размер области, которую я хочу использовать", т. е. вот "окно", которое будет преобразовано в выводимую прямоугольную область. Выводимая прямоугольная область называется областью просмотра (viewport), и в данной программе область просмотра представляет собой страницу, которая появится на печати.

    Отметим, что слово "окно" взято в кавычки. Это было сделано потому, что окно в данном контексте не означает экранного оконного объекта, а только какую-то любую прямоугольную графическую область. Для ясности будем ссылаться на такое окно, как на графическое окно. Хотя в программе размер рабочей области экранного окна и размер графического окна, установленный SetWindowExt(), одинаковы, это вовсе не обязательно для других случаев. В программе это сделано, чтобы графическое окно представляло полную пользовательскую область экранного окна.

    Вернемся к программе. PrintPage() вызывает SetViewportExt(), чтобы установить размеры области просмотра по размеру страницы. Эти значения хранятся в поле PageSize объекта TPrintout, который является объектом TSize. PageSize представляет собой вертикальное и горизонтальное разрешения доступной для печати области страницы. Величины этих разрешений изменяются от принтера к принтеру, в зависимости от разрешающей способности принтера и доступной принтеру области страницы. На струйном принтере с 300 точками на дюйм, к примеру, размер страницы составит 2400 на 3000. Эти значения показаны на рисунке 1. Вызов SetViewpsrtExt() также возвращает старые значения в oldVpExt.


Рис.1. Экранное окно, графическое окно и область просмотра

    Рисунок 1 иллюстрирует это странное соотношение между графическим и демонстрационным окнами. Отметим, что рисунок выполнен не в масштабе. Заметим также, что изображение прямоугольника в действительности никогда не рисуется в графическом окне, как показано на этом рисунке. Изображение прямоугольника содержится там потому, что графическое окно составляет часть экранного окна, с которым работает программа в настоящий момент, и эта часть содержит прямоугольное изображение. Другими словами, существует только два изображения прямоугольника, одно из них находится в экранном окне, другое - на печатаемой странице.

    Рисунок 2 показывает результат установки меньших размеров для графического окна до сравнению с рабочей областью основного окна.


Рис.2. Графическое окно не должно обязательно иметь те же размеры, что и рабочая область экранного окна

    Вы можете воспроизвести этот эффект, используя текст приведенного примера, уменьшив размер экранного окна, как указано на рисунке 3.


Рис.3. Уменьшенные размеры экранного окна

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

    Запомните: функция SetViewportExt() устанавливает положение и размер прямоугольной графической области, которую вы будете отображать в область просмотра, а не размер самого окна. Область просмотра - это прямоугольная область, относительно которой вы хотите промасштабировать графическое окно. После обращения к SetWindowExt() и SetViewportExt(), Windows преобразует координаты графического окна в координаты области просмотра. Иначе говоря, эти две функции устанавливают соотношение между размерами графического окна и размерами демонстрационного окна, разрешая масштабирование окна относительно области просмотра.

    После обращения к SetWindowExt() и SetViewportExt() Windows знает размер (в логических координатах) графического окна, которое требуется отобразить в области просмотра (измеренное в физических координатах или координатах устройства). Все, что программа должна делать в этом месте, так это вызвать функцию Paint() главного окна с контекстом устройства принтера для рисования изображения на принтере. Windows имеет всю информацию, необходимую для создания пропорционального изображения.

    Например, на рисунке 1 горизонтальное разрешение графического окна равно 200, а горизонтальное разрешение области просмотра составляет 2400, т.е. в 12 раз больше. Поэтому Windows знает, что для каждой точки графического окна необходимо нарисовать 12 точек в области просмотра. Таким образом, если прямоугольник в окне составляет половину ширины окна, то прямоугольник на странице займет половину ширины страницы.

    Вы наверняка удивитесь, почему изображение на странице не вытягивается по вертикали, занимая всю страницу. Это происходит потому, что режим отображения MM_ISOTROPIC гарантирует сохранение изображения пропорционально оригиналу. При выводе графического окна в область просмотра изображение увеличивается до самого большого, сохраняя правильные пропорции. Так как горизонтальное разрешение страницы меньше, чем вертикальное, можно использовать полную ширину страницы, в то время как часть высоты страницы не используется.

    В приведенном примере после вызова функции главного окна Print(), PrintPage() снова обращается к SetWindowExt(), SetViewportExt() и SetMapMode() для восстановления исходных установок контекста устройства.


    Замечание. В режиме отображения MM_ISOTROPIC вы должны вызвать SetWindowExt() до обращения к SetViewportExt(). Невыполнение этого условия может привести к непредсказуемому соотношению между окном и областью просмотра.

    На следующем шаге мы рассмотрим использование стандартных диалоговых окон печати.




Предыдущий шаг Содержание Следующий шаг