Перевод книги Julian’а Smart’а – Глава X – Работа с Изображениями – Часть 2

Читать первую часть главы

Программирование с wxImage

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

Основные функции wxImage в Таблице 10-6.

Таблица 10-6. wxImage Функции

  • wxImage – Изображение может быть создано с заданной шириной и высотой, из другого изображения, XPM данных, битовых данных (char[]) и опционально данных об альфа канале, из файла, су указанием типа файла, или из потока ввода.
  • ConvertAlphaToMask – Конвертирует альфа канал (если есть) в маску.
  • ConvertToMono – Конвертирует в новое монохромное изображение.
  • Copy – Возвращает идентичную копию, не используя подсчета ссылок.
  • Create – Создает изображение заданного размера, опционально инициализирует его из данных.
  • Destroy – Удаляет внутренние данные, если никакой другой объект их не использует.
  • GetData, SetData – Возвращают или задают указатель на внутренние данные (unsigned char*).
  • GetImageCount – Возвращает количество изображений в файле или потоке.
  • GetOption, GetOptionInt, SetOption, HasOption – Возвращают, задают, или проверяют объект на наличие опции.
  • GetSubImage – Возвращает область изображения, как новое изображение.
  • GetWidth, GetHeight – Возвращают размеры изображения.
  • Getred, GetGreen, GetBlue, SetRGB, GetAlpha, SetAlpha – Возвращают, или задают значения красного, синего, зеленого, и альфа значения для пикселя.
  • HasMask, GetMaskRed, GetMaskGreen, GetMaskBlue, SetMaskColour – Функции для проверки наличия маски, установки и взятия цвета маски.
  • LoadFile, SaveFile – Загрузка из файла и сохранение изображения в файл.
  • Mirror – Зеркально отображает изображение в заданном направлении, возвращает новое изображение.
  • Ok – Возвращает true если изображение инициализировано.
  • Paste – Вставляет изображение в текущее изображение, в заданной точке.
  • Rotate, Rotate90 – Вращает изображение, возвращает новое изображение.
  • SetMaskFromImage – Устанавливает маску, определяет изображение или цвет, который нужно использовать для прозрачности.
  • Scale, Rescale – Изменяют размеры изображения, возвращают новое изображение, или сохраняют изменения в текущем.

Загрузка и Сохранение Изображений

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

В Таблице 10-7 указаны все типы обработчиков изображений, на платформах поддерживаемых wxWidgets. По-умолчанию всегда установлен wxBMPHandler обработчик. Для того, что бы использовать другие форматы, установите нужный обработчик с помощью wxImage::AddHandler или wxInitAllImageHandlers.

Если Вы используете определенный формат, у Вас должен быть вот такой код в вашей wxApp::OnInit функции:

#include "wx/image.h"

wxImage::AddHandler( new wxPNGHandler );
wxImage::AddHandler( new wxJPEGHandler );
wxImage::AddHandler( new wxGIFHandler );
wxImage::AddHandler( new wxXPMHandler );

Или же можно просто вызвать:

wxInitAllImageHandlers();

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

// Загружаем изображение, используя конструктор, с указание типа файла
wxImage image(wxT("image.png"), wxBITMAP_TYPE_PNG);
if (image.Ok())
{
    ...
}

// Оставляем wxImage самому определить тип файла
wxImage image(wxT("image.png"));

// Загрузка в два шага
wxImage image;
if (image.LoadFile(wxT("image.png")))
{
    ...
}

// Загрузка в два шага, с использованием индекса изображения, для
// форматов, с несколькими изображениями в файле:
// загружаем изображение номер два, если оно есть
wxImage image;
int imageCount = wxImage::GetImageCount(wxT("image.tif"));
if (imageCount > 2)
    image.LoadFile(wxT("image.tif"), wxBITMAP_TYPE_TIFF, 2);

// Загрузка из потока
wxFileInputStream stream(wxT("image.tif"));
wxImage image;
image.LoadFile(stream, wxBITMAP_TYPE_TIF);

// Сохранение в файл
image.SaveFile(wxT("image.png")), wxBITMAP_TYPE_PNG);

// Сохранение в поток
wxFileOutputStream stream(wxT("image.tif"));
image.SaveFile(stream, wxBITMAP_TYPE_TIF);

Изображения сохраняются в файл в 24-битном формате, за исключением XPM и PCX форматов, чьи обработчики подсчитывают количество цветов и сохраняют с нужной глубиной. В JPEG формате есть опция качества, которую можно установить перед сохранением. Значение является целым числом между 0 и 100, где 0 это наихудшее качество с высоким уровнем сжатия, и 100 наивысшее качество с плохим сжатием.

// Сохраняем с оптимальным сжатием и компрессией
image.SetOption(wxIMAGE_OPTION_QUALITY, 80);
image.SaveFile(wxT("picture.jpg"), wxBITMAP_TYPE_JPEG);

Вам может понадобиться использовать wxImage::SetOption когда Вы сохраняете XPM в поток, так как Вы не указываете при сохранении имя файла, и обработчик не будет знать какое значение присвоить переменной C, которая является частью данных XPM . Например:

// Сохраняем XPM в поток
image.SetOption(wxIMAGE_OPTION_FILENAME, wxT("myimage"));
image.SaveFile(stream, wxBITMAP_TYPE_XPM);

Учтите, обработчик добавит суффикс _xpm к имени файла, которое Вы укажете.

Прозрачность

Есть два способа использования прозрачности в wxImage: маска и альфа канал. Один цвет изображения может быть указан как цвет маски, что приведет к автоматическому созданию объекта wxMask при конвертировании в wxBitmap.
wxImage также поддерживает альфа канал. В дополнение к каждому байту в пикселе для красного, зеленого, и синего компонентов цвета, класс содержит байт, который указывает значение непрозрачности (opacity) для данного пикселя. Значение альфа 0 соответствует прозрачному пикселю (нулевая непрозрачность), и значение 255 указывает на то, что пиксель 100% непрозрачный.
Не у всех изображений есть альфа канал, и перед использованием GetAlpha, Вы должны проверить наличие альфа канал с помощью HasAlpha. В данный момент, альфа канал поддерживают только изображения, загруженные из PNG файлов, или изображения с назначенным альфа каналом функцией SetAlpha. Сохранение изображений с альфа каналом пока не поддерживается. Рисовать изображения с альфа каналом можно сконвертировав изображение в wxBitmap и вызвав wxDC::DrawBitmap или wxDC::Blit.
В нижеприведенном коде показывается, как создать wxImage с маской. Изображение будет синим, и будет содержать прозрачный прямоугольник.

// Создаем изображение с маской
// Сначала рисуем в wxBitmap
wxBitmap bitmap(400, 400);
wxMemoryDC dc;
dc.SelectObject(bitmap);
dc.SetBackground(*wxBLUE_BRUSH);
dc.Clear();
dc.SetPen(*wxRED_PEN);
dc.SetBrush(*wxRED_BRUSH);
dc.DrawRectangle(50, 50, 200, 200);
dc.SelectObject(wxNullBitmap);

// Конвертируем растровое изображение (bitmap) в картинку (image)
wxImage image = bitmap.ConvertToImage();

// Устанавливаем красный как цвет маски 
image.SetMaskColour(255, 0, 0);

Другой способ сделать маску – это создать ее из другого изображения. В следующем примере, image.bmp содержит основное изображение, и mask.bmp содержит черные пиксели, которые будут указывать на прозрачные области.

// Загрузим изображение и его маску
wxImage image(wxT("image.bmp"), wxBITMAP_TYPE_BMP);
wxImage maskImage(wxT("mask.bmp"), wxBITMAP_TYPE_BMP);

// Укажем черный, как цвет маски
image.SetMaskFromImage(maskImage, 0, 0, 0);

Если Вы загрузили изображение с диска, Вы можете проверить его на прозрачность, и получить цвет маски:

// Загружаем изображение с прозрачностью
wxImage image(wxT("image.png"), wxBITMAP_TYPE_PNG);

// Получаем маску
if (image.HasMask())
{
    wxColour maskColour(image.GetMaskRed(),
    image.GetMaskGreen(),
    image.GetMaskBlue());
}

Преобразования

wxImage поддерживает масштабирование, вращение, и зеркальное отражение картинки. Вот несколько примеров:

// Масштабировать изображение как 200x200
// и назначить результат другому объекту.
// image1 не модифицируется.
wxImage image2 = image1.Scale(200, 200);

// Изменяем размер на 200x200
image1.Rescale(200, 200);

// Вращаем на указаное число радиан.
// image1 не модифицируется.
wxImage image2 = image1.Rotate(0.5);

// Вращаем на 90 градусов по часовой стрелке.
// image1 не модифицируется.
wxImage image2 = image1.Rotate90(true);

// Отражаем изображение по горизонтали.
// image1 не модифицируется.
wxImage image2 = image1.Mirror(true);

Уменьшение Цветности
Если Вам нужно уменьшить количество цветов в изображении, Вы можете использовать статические функции класса wxQuantize. Нужная нам функция Quantize, в качестве аргументов принимает исходное изображение, возвращаемое изображение, выборочно палитру wxPalette** для сокращенных из изображения цветов, и желаемое количество цветов. Так же можно указать переменную типа unsigned char**, для получения 8-битного представления возвращаемого изображения и стиль, для лучшего управления возвращаемыми данными; для более детальной информации, смотрите справочное руководство.
Данный пример показывает, как уменьшить цветность изображения до 256 цветов:

#include "wx/image.h"
#include "wx/quantize.h"

wxImage image(wxT("image.png"));

int maxColorCount = 256;
int colors = image.CountColours();
wxPalette* palette = NULL;
if (colors > maxColorCount )
{
    wxImage reducedImage;
    if (wxQuantize::Quantize(image, reducedImage,
                               & palette, maxColorCount))
    {
        colors = reducedImage.CountColours();
        image = reducedImage;
    }
}

С изображением может идти палитра, связанная с ним wxPalette, например когда изображение загружено из GIF файла. Тем не менее, изображение хранится в RGB формате, а палитра просто указывает значение индексов в RGB формате. Для wxImage также можно назначить палитру wxPalette, и в результате SaveFile может сохранить данное изображение с ограниченным количеством цветов. Например, если Windows BMP обработчик обнаруживает, что в опциях изображения указан wxBMP_8BPP_PALETTE формат, он сохраняет изображение с палитрой изображения; если указан формат wxBMP_8BPP, обработчик создает палитру из данных изображения. Некоторые обработчики сами производят уменьшение цветности, например PCX, пока не подберут достаточное количество уникальных цветов.

Больше информации по wxPalette, смотрите в разделе “wxPalette” в Главе 5.

Манипуляции Непосредственно с Данными wxImage

Вы можете получить доступ непосредственно к данным изображения функцией GetData , для более быстрой манипуляции данными, чем через GetRed, GetBlue, GetGreen, и SetRGB. Ниже показан пример преобразования цветного изображения в черно-белое:

void wxImage::ConvertToGrayScale(wxImage& image)
{
    double red2Gray   = 0.297;
    double green2Gray = 0.589;
    double blue2Gray  = 0.114;
    int w = image.GetWidth(), h = image.GetHeight();
    unsigned char *data = image.GetData();

    int x,y;
    for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
        {
            long pos = (y * w + x) * 3;

            char g = (char) (data[pos]*red2Gray +
                              data[pos+1]*green2Gray +
                              data[pos+2]*blue2Gray);
            data[pos] = data[pos+1] = data[pos+2] = g;
        }
}
[/sourcecode]

Списки Изображений и Пакеты Иконок Иногда удобно объединять несколько изображений в один объект. Списки wxImageList можно использовать непосредственно в Вашем приложении, или для иконок элементов управления wxWidgets, которые используют списки изображений (image lists). wxNotebook, wxTreeCtrl, и wxListCtrl используют wxImageList для иконок в своих элементах. Также можно просто нарисовать отдельное изображение из списка wxImageList в контекст устройства. wxImageList создается с указанием высоты и ширины каждого изображения, булевым значением, которое указывает на то будет ли использоваться маска, и начальным размером списка (для внутренней оптимизации). Потом нужно добавить несколько wxBitmap или wxIcon изображений. Для списков нельзя использовать wxImage объект, но можно передать его в конструктор wxBitmap. wxImageList::Add возвращает целочисельный индекс, который потом можно использовать как идентификатор изображения; после того, как Вы добавите изображение в список, Вы можете его уничтожить, потому что wxImageList делает его копию. Далее несколько примеров создания wxImageList и добавления в него изображений. // СозданиеImageList wxImageList *imageList = new wxImageList(16, 16, true, 1); // Добавление растрового изображения с прозрачностью из PNG wxBitmap bitmap1(wxT("image.png"), wxBITMAP_TYPE_PNG); imageList->Add(bitmap1); // Добавление изображения с прозрачностью из другого растрового изображения wxBitmap bitmap2(wxT("image.bmp"), wxBITMAP_TYPE_BMP); wxBitmap maskBitmap(wxT("mask.bmp"), wxBITMAP_TYPE_BMP); imageList->Add(bitmap2, maskBitmap); // Добавление растрового изображения с маской, указанной цветом маски wxBitmap bitmap3(wxT("image.bmp"), wxBITMAP_TYPE_BMP); imageList->Add(bitmap3, *wxRED); // Добавление иконки #include "folder.xpm" wxIcon icon(folder_xpm); imageList->Add(icon);

Можно нарисовать изображение непосредственно в контекст устройства, передав флаг, который указывает, как будет нарисовано изображение. wxIMAGELIST_DRAW_TRANSPARENT, например, указывает, что изображение рисуется с использованием прозрачности, есть еще три формата рисования: wxIMAGELIST_DRAW_NORMAL, wxIMAGELIST_DRAW_SELECTED, или wxIMAGELIST_DRAW_FOCUSED.

// Рисуем все изображения в списке
wxClientDC dc(window);
size_t i;
for (i = 0; i < imageList->GetImageCount(); i++)
{
    imageList->Draw(i, dc, i*16, 0, wxIMAGELIST_DRAW_NORMAL|
                                    wxIMAGELIST_DRAW_TRANSPARENT);
}

Для того, что бы назначить иконки вкладкам блокнота (notebook), нужно создать список изображений с иконками 16x16 , и вызвать wxNotebook::SetImageList или wxNotebook::AssignImageList. Если Вы будете использовать первый вариант, блокнот не будет удалять список, когда будет уничтожатся; во втором варианте, блокнот принимает на себя управление списком и Вам не нужно волноваться из-за удаления списка изображений. Теперь, когда Вы добавляете страницы в блокнот, Вы можете указать индекс иконки, которая будет показываться возле текста на вкладке (или вместо него, если на вкладке нет надписи). Ниже показан пример кода, где добавляется две страницы с иконками на вкладках.

// Создадим wxImageList
wxImageList *imageList = new wxImageList(16, 16, true, 1);

// Добавим несколько иконок
wxBitmap bitmap1(wxT("folder.png"), wxBITMAP_TYPE_PNG);
wxBitmap bitmap2(wxT("file.png"), wxBITMAP_TYPE_PNG);
int folderIndex = imageList->Add(bitmap1);
int fileIndex = imageList->Add(bitmap2);

// Создадим блокнот с двумя страницами
wxNotebook* notebook = new wxNotebook(parent, wxID_ANY);
wxPanel* page1 = new wxPanel(notebook, wxID_ANY);
wxPanel* page2 = new wxPanel(notebook, wxID_ANY);

// Назначим список изображений
notebook->AssignImageList(imageList);

// Добавим страницы с иконками
notebook->AddPage(page1, wxT("Folder options"), true, folderIndex);
notebook->AddPage(page2, wxT("File options"), false, fileIndex);

В wxTreeCtrl и wxListCtrl используется тот же принцип с указанием списка изображений, или назначением его элементу (когда элемент управления берет на себя удаление списка).

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

#include "wx/hashmap.h"

WX_DECLARE_STRING_HASH_MAP(int, IconNameToIndexHashMap);

// Класс для обращения к индексам через имя
class IconNameToIndex
{
public:
    IconNameToIndex() {}

    // Добавление изображения с именем в список изображений
    void Add(wxImageList* list, const wxBitmap& bitmap,
        const wxString& name) {
        m_hashMap[name] = list->Add(bitmap);
    }

    // Добавление иконки с именем в список изображений
    void Add(wxImageList* list, const wxIcon& icon,
        const wxString& name) {
        m_hashMap[name] = list->Add(icon);
    }

    // Поиск индекса по имени
    int Find(const wxString& name) { return m_hashMap[name]; }

private:
    IconNameToIndexHashMap m_hashMap;
};

wxIconBundle это класс для набора изображений, но он предназначен для хранения разных разрешений одного изображения, а не множества картинок. Этот класс помогает подобрать системе картинку нужного размера. Например, иконка из заголовка окна может быть меньше, чем та же иконка файла, или иконка в менеджере задач. Ниже показан пример использования пакета иконок (icon bundle).

// Создаем пакет с одной иконкой
#include "file16x16.xpm"
wxIconBundle iconBundle(wxIcon(file16x16_xpm));

// Добавляем иконку из файла
iconBundle.Add(wxIcon(wxT("file32x32.png"), wxBITMAP_TYPE_PNG));

// Создаем пакет иконок из нескольких иконок в одном фале
wxIconBundle iconBundle2(wxT("multi-icons.tif"), wxBITMAP_TYPE_TIF);

// Получает иконку с заданным размером, или если не найдена, берет иконку
// с размерами wxSYS_ICON_X, wxSYS_ICON_Y
wxIcon icon = iconBundle.GetIcon(wxSize(16,16));

// Назначает пакет иконок приложению
wxFrame* frame = new wxFrame(parent, wxID_ANY);
frame->SetIcons(iconBundle);

В Windows, SetIcons извлекает иконки размером 16x16 и 32x32 из пакета.

Настройка Графики в wxWidgets

wxArtProvider класс – это класс, который позволяет настраивать встроенную графику ("art") в wxWidgets приложениях. Например, Вы хотите заменить стандартные иконки в wxWidgets HTML справке или иконки в общих диалогах, таких как просмотрщик логов.
В wxWidgets есть стандартный объект wxArtProvider, и когда вашей программе нужна графическая информация, вызывается wxArtProvider::GetBitmap и wxArtProvider::GetIcon для получения иконок.

У графических объектов есть два идентификатора: графический идентификатор (wxArtID) и пользовательский идентификатор (wxArtClient). Пользовательский идентификатор применяется, если разным окнам нужно использовать разные изображения для одних графических идентификаторов. Как пример, окно справки wxHTML использует следующий код, для изображения кнопки "назад" на панели инструментов:

wxBitmap bmp = wxArtProvider::GetBitmap(wxART_GO_BACK,wxART_TOOLBAR);

Вы можете посмотреть идентификаторы и графику, встроенную в wxWidgets, скомпилировав и запустив проект samples/artprov в wxWidgets директории. На Изображении 10-1 показано окно браузера графики.

Для того, что бы заменить графические элементы wxWidgets, создайте производный класс из wxArtProvider, перегрузите CreateBitmap, и вызовите wxArtProvider::PushProvider в OnInit функции для того, что бы wxWidgets использовала ваш класс. Ниже показан пример замени большинства графики окна справки wxHTML.

// XPM’ы с графикой
#include "bitmaps/helpbook.xpm"
#include "bitmaps/helppage.xpm"
#include "bitmaps/helpback.xpm"
#include "bitmaps/helpdown.xpm"
#include "bitmaps/helpforward.xpm"
#include "bitmaps/helpoptions.xpm"
#include "bitmaps/helpsidepanel.xpm"
#include "bitmaps/helpup.xpm"
#include "bitmaps/helpuplevel.xpm"
#include "bitmaps/helpicon.xpm"

#include "wx/artprov.h"

// Класс графики
class MyArtProvider : public wxArtProvider
{
protected:
    virtual wxBitmap CreateBitmap(const wxArtID& id,
                                  const wxArtClient& client,
                                  const wxSize& size);
};

// CreateBitmap функция
wxBitmap MyArtProvider::CreateBitmap(const wxArtID& id,
                                     const wxArtClient& client,
                                     const wxSize& size)
{
    if (id == wxART_HELP_SIDE_PANEL)
        return wxBitmap(helpsidepanel_xpm);
    if (id == wxART_HELP_SETTINGS)
        return wxBitmap(helpoptions_xpm);
    if (id == wxART_HELP_BOOK)
        return wxBitmap(helpbook_xpm);
    if (id == wxART_HELP_FOLDER)
        return wxBitmap(helpbook_xpm);
    if (id == wxART_HELP_PAGE)
        return wxBitmap(helppage_xpm);
    if (id == wxART_GO_BACK)
        return wxBitmap(helpback_xpm);
    if (id == wxART_GO_FORWARD)
        return wxBitmap(helpforward_xpm);
    if (id == wxART_GO_UP)
        return wxBitmap(helpup_xpm);
    if (id == wxART_GO_DOWN)
        return wxBitmap(helpdown_xpm);
    if (id == wxART_GO_TO_PARENT)
        return wxBitmap(helpuplevel_xpm);
    if (id == wxART_FRAME_ICON)
        return wxBitmap(helpicon_xpm);
    if (id == wxART_HELP)
        return wxBitmap(helpicon_xpm);

    // Любые иконки wxWidgets, которые здесь не реализованы,
    // будут поставлены «родным» классом графики.
    return wxNullBitmap; 
}

// Инициализация
bool MyApp::OnInit()
{
    ...

    wxArtProvider::PushProvider(new MyArtProvider);

    ...
    return true;
}

Итоги

В этой главе мы рассмотрели использование четырех основных классов изображений wxBitmap, wxIcon, wxCursor, и wxImage и двух классов для объединения изображений wxImageList и wxIconBundle. Также мы посмотрели, как можно заменить иконки и изображения wxWidgets своими собственными. Для примеров использования классов изображений, смотрите samples/image, samples/listctrl, и samples/dragimag в wxWidgets директории.
В следующей главе мы рассмотрим классы, которые позволяют реализовать перенос данных с помощью буфера обмена и drag’n’drop.

3 comments

  1. Pingback: Перевод книги Julian’а Smart’а - Глава X - Работа с Изображениями - Часть 1 | Cross-Platform Programming with wxWidgets

  2. Syber   •  

    больше всего заинтересовала wxQuantize…
    Судя по всему ее можно использовать чтобы сделать темнее\ярче изображение…
    Нужно проверить.

  3. T-Rex   •     Author

    Эээ.. если не лень, про результаты проверки потом отпиши plz? 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

Please leave these two fields as-is: