Сайт разработчика Александр Климова

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Подгонка изображений под заданный размер

В этой статье мы рассмотрим пример изменения размеров изображений.

Мы пользуемся многими устройствами в повседневной жизни: смартфоны, коммуникаторы, Sony PSP, которые имеют разные размеры экранов. И, иногда, нужно закачать на эти устройства файлы с фотографиями, которые имеют гораздо больший размер. Но большие картинки занимают много места на диске и долго грузятся. Гораздо проще сделать копии нужных фотографий под требуемый размер экрана. Существуют различные графические редакторы, способные выполнить эту задачу. Но если у вас под рукой такой программы нет, то напишем свое приложение для этой цели.

Настройка места для хранения обработанных картинок

Для начала надо выбрать папку, где мы будем хранить обработанные картинки. Выбор места удобно сделать через диалоговое окно FolderBrowserDialog. Обратите внимание, что в случае необходимости пользователь может создать новую папку:


outputDirDialog = new System.Windows.Forms.FolderBrowserDialog();
outputDirDialog.Description = "Выберите папку";
outputDirDialog.ShowNewFolderButton = true;  // показать кнопку Создать папку
outputDirDialog.ShowDialog();

Если пользователь выберет кнопку Отмена, то нужно вывести какое-нибудь сообщение в нижней части формы:


if (outputDirDialog.SelectedPath.Length == 0)
{
    statusLabel.Text = "Папка не выбрана";
    return;
}

Изображения для обработки

После того, как мы определились с папкой, в которую будем перемещать измененные картинки, нам нужно выбрать сами файлы, содержащие изображения. Для этого будем использовать стандартное диалоговое окно OpenFileDialog, которое также позволяет делать множественное выделение файлов.


sourceFilesDialog = new OpenFileDialog();
sourceFilesDialog.Multiselect = true;
sourceFilesDialog.Title = "Выберите файлы для обработки";
Настроим также фильтр, чтобы диалоговое окно показывало только файлы с изображениями:
sourceFilesDialog.Filter = 
    "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*";

Элементы в фильтре разделяются вертикальными черточками. Каждый фильтр является выражением из пары элементов – описание и расширения файлов. В нашем случае используются две пары:


"Файлы изображений(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|" и "Все файлы (*.*)|*.*"

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

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

Обработка изображений

Диалоговое окно OpenFileDialog возвращает список имен файлов в виде массива строк. Теперь мы можем открыть каждый файл, загрузить изображение из него, изменить его размер и сохранить полученный результат в новом файле. Эти операции будем делать в методе processFiles.


processFiles(sourceFilesDialog.FileNames, outputDirDialog.SelectedPath);

Методу передается массив имен файлов и путь к папке для сохранения результатов. Далее метод обрабатывает каждый файл и сохраняет его.


private void processFiles(string[] FileNames, string outputPath)
{
    Bitmap dest = new Bitmap(size.width, size.height);
    foreach (string filename in FileNames)
    {
        Bitmap image;
        try
        {
            image = new Bitmap(filename);
        }
        catch
        {
            MessageBox.Show("Error loading bitmap : " + filename);
            continue;
        }
        scaleBitmap(dest,image);
        string destFilename = outputPath + @"\" + 
            System.IO.Path.GetFileNameWithoutExtension(filename) + ".jpg";
        try
        {
            dest.Save(destFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
        }
        catch
        {
            MessageBox.Show("Error saving bitmap : " + destFilename);
            return;
        }
    }
}

Метод создает изображение, вызывает метод scaleBitmap для изменения размеров и сохраняет результат обратно на диск.

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

Изменение размеров

Изменить размеры изображения очень просто. Имеются различные графические методы для рисования прямоугольника из одного изображения в другое. Меняя размеры источника и приемника, мы можем получить нужный результат. Сложность состоит в сохранении пропорций картинки. Наш метод должен отобразить картинку во весь экран просматриваемого устройства и добавить пространство вокруг него в случае необходимости:


private Rectangle srcRect = new Rectangle();
private Rectangle destRect = new Rectangle();

private void scaleBitmap ( Bitmap dest, Bitmap src )
{
    destRect.Width = dest.Width;
    destRect.Height = dest.Height;
    using (Graphics g = Graphics.FromImage(dest))
    {
        Brush b = new SolidBrush(backgroundColor);
        g.FillRectangle(b, destRect);
        srcRect.Width = src.Width;
        srcRect.Height = src.Height;
        float sourceAspect = (float)src.Width / (float)src.Height;
        float destAspect = (float)dest.Width / (float)dest.Height;
        if (sourceAspect > destAspect)
        {
            // wider than high heep the width and scale the height
            destRect.Width = dest.Width;
            destRect.Height = (int)((float)dest.Width / sourceAspect);
            destRect.X = 0;
            destRect.Y = (dest.Height - destRect.Height) / 2;
        }
        else
        {
            // higher than wide – keep the height and scale the width
            destRect.Height = dest.Height;
            destRect.Width = (int)((float)dest.Height * sourceAspect);
            destRect.X = (dest.Width - destRect.Width) / 2;
            destRect.Y = 0;
        }
        g.DrawImage(src, destRect, srcRect, System.Drawing.GraphicsUnit.Pixel);
    }
}

Метод DrawImage позволяет легко изменять размеры изображения и играет главную роль в методе scaleBitmap.

Управление размерами

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

Создадим отдельный класс, который будет содержать информацию о размерах.


public class OutputSize
{
    public int width;
    public int height;
    string name;

    public override string ToString()
    {
        return name + "  " + width.ToString() + " x " + height.ToString();
    }

    public OutputSize(string inName, int inWidth, int inHeight)
    {
        name = inName;
        width = inWidth;
        height = inHeight;
    }
}

Тип OutputSize содержит свойство name для идентификации устройства и размера его экран. Переопределяемый метод ToString служит для вывода текста в ComboBox. Свойства width и height являются открытыми и могут быть использованы любым методом в разных классах. Наконец, конструктор позволит установить сразу необходимые значения. Мы можем создать массив экземпляров класса и заполнить ComboBox:


private OutputSize[] resolutionSettings = new OutputSize[] {
    new OutputSize ( "Pocket PC", 640, 480 ),
    new OutputSize ( "QVGA", 320, 240 ),
    new OutputSize ( "PSP", 480, 272 ),
    new OutputSize ( "Smartphone", 176, 180)
};

Если вы хотите добавить другие типы устройств в программу, то просто добавьте описание в массив и оно появится в комбинированном окне автоматически. Добавить все элементы массива в ComboBox очень просто:


resolutionComboBox.DataSource = resolutionSettings;

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


OutputSize size = resolutionComboBox.SelectedItem as OutputSize;

Теперь у нас есть необходимая информация для изменения изображения: свойства width, height экземпляра size.

Установка фона

Можно также дать возможность пользователю выбрать цвет фона для изображения, если оно не помещается полностью в заданный размер:


private Color backgroundColor = Color.White;

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


backColorDialog = new ColorDialog();
backColorDialog.SolidColorOnly = true;
backColorDialog.Color = backgroundColor;
backColorDialog.ShowDialog();

После выбора цвета для фона, мы можем использовать его в методе scaleBitmap.

Предварительный просмотр

Для предварительного просмотра используется элемент PictureBox. Мы устанавливаем задний фон равным выбранному фону, чтобы на предварительной картинке могли увидеть готовый вариант, который получится во время изменения размеров.


previewPictureBox.Image = dest;

Для PictureBox свойство SizeMode нужно установить в Zoom, чтобы видеть точное изображение.

Индикатор прогресса

Если пользователь выберет сразу много файлов для изменения размеров, то этот процесс может растянуться надолго. Мы можем подсчитать величину этого процесса, разделив число обработанных файлов на общее число выбранных файлов и умножив результат на 100, чтобы получить значение в процентах.


loadProgressBar.Value = (int)(100 * ((float)fileCount / (float)noOfFiles));

Обработка ошибок

В принципе, программа готова. Но, при слишком долгой работе по изменению размеров Visual Studio может решить, что программа зависла. Чтобы отключить исключение по этой причине, кидаемое средой разработки, нужно найти в меню "Debug" пункт "Exceptions" и снять галочку для "Thrown" у исключения ContextSwitchDeadlock:

Теперь программа будет нормально работать даже при большом количестве файлов. В принципе программа готова. Но ее можно улучшить.

Drag-and-Drop

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

Альбомный/Портретный режим

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

Скачать пример

Перевод: Василий Котов

Реклама