Встраивание графики в веб-страницу

180

Применение метода Image.Save() для записи изображения в поток ответа ведет к перезаписи любой информации, которую среда ASP.NET использовала бы в противном случае. К счастью, существует более простое решение. Можно воспользоваться HTML-дескриптором <img> или веб-элементом управления Image, но вместо указания статического изображения в качестве источника понадобится выполнить привязку к файлу .aspx, который генерирует динамическое изображение.

Например, рассмотрим любое графическое изображение, созданное средствами GDI+ в предыдущей статье. Оно хранится в файле SimpleDrawing.aspx и записывает динамически сгенерированное изображение в поток ответа (в приведенных примерах в файловый поток). Вывести динамическое изображение на другой странице можно было бы за счет добавления к ней веб-элемента управления Image и установки SimpleDrawing.aspx в качестве значения свойства ImageUrl (либо сгенерированного файла). Затем можно было бы добавить другие элементы управления или даже несколько элементов управления Image, которые устанавливают связь с этим же содержимым.

На рисунке ниже показан пример, в котором используются два дескриптора <img>, указывающие на файл SimpleDrawing.aspx, а также ряд дополнительных веб-элементов управления ASP.NET. расположенных между ними:

<body>
    <form id="form1" runat="server">
        <asp:Image ID="Image1" runat="server" ImageUrl="~/SimpleDrawing.aspx" />
        <br /><br />
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <br /><br />
        <asp:Label ID="Label1" runat="server" Text="Label" />
        <br /><br />
        <asp:Image ID="Image2" runat="server" ImageUrl="~/SimpleDrawing.aspx" />
    </form>
</body>
Смешивание динамически рисуемого содержимого и обычных веб-элементов управления

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

Использование формата PNG

PNG является универсальным форматом, который всегда обеспечивает высокое качество, сочетая в себе сжатие без потерь GIF-изображений с широкими возможностями цветовой поддержки JPEG. Некоторые браузеры (особенно более старые версии Internet Explorer) неправильно выводят на экран PNG-изображения при их динамическом возвращении из страницы. Вместо содержимого рисунка пользователь получает сообщение с предложением загрузить содержимое рисунка и открыть его в другой программе. Для решения этой проблемы можно применить ранее рассмотренный подход с дескриптором <img>.

Еще одна проблема, связанная с динамической генерацией PNG-изображений - невозможность использования метода Bitmap.Save(), рассмотренного в предшествующей статье. Response.OutputStream является линейным потоком, т.е. данные должны записываться последовательно с начала до конца. Чтобы создать PNG-файл, программные средства .NET должны иметь возможность перемещаться по файлу назад и вперед, что требует потока, который может обеспечивать переход в конкретные позиции.

Решение достаточно просто. Вместо того чтобы выполнять сохранение непосредственно в поток Response.OutputStream, нужно создать поток System.IO.MemoryStream, который представляет собой буфер данных в памяти. Вызовите Bitmap.Save(), чтобы сохранить изображение в поток MemoryStream, а затем запишите MemoryStream в поток Response.OutputStream.

Код, необходимый для реализации этого решения, при условии, что пространство имен System.IO было импортировано, имеет следующий вид:

protected void Page_Load(object sender, EventArgs e)
{
    using (Bitmap image = new Bitmap(450, 100))
    {
        using (Graphics graphic = Graphics.FromImage(image))
        {
            /*
             * Какой-то код, генерирующий изображение 
             */

            // Сохранить изображение в поток
            Response.ContentType = "image/png";
            
            // Создать PNG-изображение, хранящееся в памяти
            MemoryStream mem = new MemoryStream();
            image.Save(mem, System.Drawing.Imaging.ImageFormat.Png);
            
            // Записать данные MemoryStream в выходной поток
            mem.WriteTo(Response.OutputStream);
        }
    }
}

Передача информации динамическим изображениям

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

Привязанный к данным список эскизов

Эта страница должна состоять из двух частей: страницы, которая содержит элемент управления GridView, и страницы, которая динамически визуализирует одиночный эскиз. Для заполнения списка страница GridView будет многократно вызывать страницу эскиза (используя дескрипторы <img>).

Имеет смысл сначала разработать страницу, создающую эскиз. В этом примере страница названа ThumbailsViewer.aspx. Чтобы сделать этот компонент максимально универсальным, не следует жестко кодировать какую-либо информацию об используемом каталоге или размерах эскиза. Вместо этого данная информация будет получена с помощью трех строковых аргументов запроса.

using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

public partial class ThumbailsViewer : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if ((String.IsNullOrEmpty(Request.QueryString["X"])) ||
            (String.IsNullOrEmpty(Request.QueryString["Y"])) ||
            (String.IsNullOrEmpty(Request.QueryString["FilePath"])))
        {
            // Часть данных отсутствует, поэтому ничего не выводить на экран. 
            // Другие возможные варианты действий - выбор подходящих значений по умолчанию 
            // или возврат изображения со статическим текстом сообщения об ошибке
        }
        else
        {
            int x = Int32.Parse(Request.QueryString["X"]);
            int y = Int32.Parse(Request.QueryString["Y"]);
            string file = Server.UrlDecode(Request.QueryString["FilePath"]);

            // Создать хранящееся в памяти растровое изображение,
            // где будет выполняться рисование
            using (Bitmap image = new Bitmap(x, y))
            {
                using (Graphics graphic = Graphics.FromImage(image))
                {
                    // Загрузить данные из файла
                    System.Drawing.Image thumbnail =
                        System.Drawing.Image.FromFile(file);

                    // Нарисовать эскиз
                    graphic.DrawImage(thumbnail, 0, 0, x, y);

                    // Сохранить изображение
                    image.Save(Response.OutputStream, ImageFormat.Jpeg);
                }
            }
        }
    }
}

При первой загрузке страницы необходимо проверить, что вся эта информация предоставлена. Как только основной набор данных получен, объекты Bitmap и Graphics можно создать обычным образом. В данном случае размеры объекта Bitmap должны соответствовать размеру эскиза, поскольку добавлять какое-то дополнительное содержимое не требуется. Создание эскиза не представляет особой сложности. Достаточно загрузить изображение (с использованием статического метода Image.FromFile), а затем вывести его на поверхность рисования. При рисовании изображения нужно указать начальную точку (0, 0), а также высоту и ширину. Высота и ширина соответствуют размерам объекта Bitmap. Класс Graphics автоматически масштабирует изображение в соответствии с этими размерами, применяя сглаживание для создания высококачественного эскиза.

Следующий необходимый шаг - применение этой страницы в странице, которая содержит элемент управления GridView. Основная идея, лежащая в основе базовой страницы, заключается в том, что пользователь будет вводить путь к каталогу и щелкать на кнопке отправки данных. На этом этапе код может выполнить определенную работу с классами пространства имен System.IO. Во-первых, понадобится создать объект DirectoryInfo, который представляет каталог, выбранный пользователем. Во-вторых, с помощью метода DirectoryInfo.GetFiles необходимо получить набор объектов FileInfo, которые представляют файлы в этом каталоге. И, наконец, код должен привязать массив объектов FileInfo к GridView, как показано в следующем примере:

protected void cmdShow_Click(object sender, EventArgs e)
{
        // Получить строковый массив со всеми файлами изображений
        DirectoryInfo dir = new DirectoryInfo(txtDir.Text);
        gridThumbs.DataSource = dir.GetFiles();

        // Построить GridView
        gridThumbs.DataBind();
}

Способ отображения связанных объектов FileInfo определяется шаблоном GridView. В этом примере требуется отобразить два элемента информации - краткое имя файла и соответствующий эскиз. Отображение краткого имени не представляет сложности. Достаточно выполнить привязку к свойству FileInfo.Name. Отображение эскиза требует использования дескриптора <img> для обращения к странице ThumbailsViewer.aspx. Однако построение правильного URL-адреса может оказаться не такой простой задачей, поэтому лучшее решение состоит в том, чтобы перепоручить эту работу методу GetImageUrl из класса веб-страницы.

Полное объявление GridView с применением шаблона имеет следующий вид:

<form id="form1" runat="server">
     <div>
          <asp:Label ID="Label1" runat="server" Text="Папка:"></asp:Label>
          <asp:TextBox ID="txtDir" runat="server"></asp:TextBox>
          <br /><br />
          <asp:Button ID="cmdShow" runat="server" OnClick="cmdShow_Click"
               Text="Показать эскизы" />
          <br />
          <asp:GridView ID="gridThumbs" runat="server" AutoGenerateColumns="False" Font-Names="Verdana"
                Font-Size="X-Small" GridLines="None">
                <Columns>
                    <asp:TemplateField>
                        <ItemTemplate>
                            <img src='<%# GetImageUrl(Eval("FullName")) %>' />
                            <%# Eval("Name") %>
                            <hr />
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
          </asp:GridView>
     </div>
</form>

Метод GetImageUrl() исследует полный путь к файлу, кодирует его и добавляет код в строку запроса, что позволяет ThumbailsViewer.aspx найти необходимый файл. Одновременно метод GetImageUrl() выбирает размер эскиза равный 50x50 пикселей. Обратите внимание, что путь к файлу является URL-закодированным в соответствии. Это связано с тем, что обычно имена файлов включают символы, которые не допускаются в URL-адресах, вроде пробелов:

protected string GetImageUrl(object path)
{
        return "ThumbailsViewer.aspx?x=50&y=50&FilePath=" +
          Server.UrlEncode((string)path);
}
Пройди тесты
Лучший чат для C# программистов