? Основы работа с графикой в Web приложениях

В начало раздела

Глава 2 Основы работа с графикой в Web приложениях

Аннотация: В продолжение к сказанному в аннотации начала главы (см. "Основы работа с графикой на С# в Visual Studio 2005/2008") - данный материал является обобщением того, что по крупицам собиралось и продолжает собираться из различных источников (книги, сеть, собственные решения при выполнении конкретных задач). После проверки найденного материала, добавления недосказанного и недописанного, уточнения некоторых аспектов - материал попадает в мой справочник, некоторыми из его параграфов автор решил поделиться с Вами.


В начало

Параграф 1 Два подхода к отображению графической информации в ASP.NET

Как известно, графика в HTML отображаются через запрос в URL (src), с прямым указанием имя и расширения файла. Однако - если графический образ хранится где-то, например, в БД, или формируется динамически, то мы должны запросить адрес страницы, где формируется изображение и, далее, как-то превратить этот запрос в принятую форму вывода изображения. Тоесть, нельзя вернуть запрошенный графический объект как некую последовательность байт, а необходимо его интерпретировать или, иначе, преобразовать в картинку, а запрос трактовать как URL к графическому объекту.

Для этой цели служит класс HttpResponse, который предназначен для формирования данных ответа на запрос HTTP. Cвойство класса HttpResponse.OutputStream - позволяет сформировать вывод двоичных данных в теле исходящего содержимого ответа на НТТР запрос. Оба, рассматриваемых ниже метода, используют OutputStream класса HttpResponse и отличаются лишь тем, что в первом случае создается собственный обработчик конкретного запроса, а во втором случае используется отдельная Web страница для формирования двоичного потока данных, соответствующая графическому образу.


В начало

1.1 Отображение графической информации с использованием HttpHandler

ASP.NET обрабатывает запросы HTTP с помощью обработчика HttpHandler, который доступен по умолчанию для всех запрашиваемых страниц c расширением *.aspx и служб (*.asmx). HttpHandlers - это классы, реализующие интерфейсы IHttpHandler и IHttpAsyncHandler и, по существу, служат ISAPI фильтром, обработки http запросов. Запросы могут обрабатываться как синхронно (интерфейс System.Web.IHttpHandler) - HttpHandler возвращает управление по завершению обработки запроса или асинхронно (интерфейс System.Web.IHttpAsyncHandler) - путем запуска процессов обработки и возврата управления. Иначе - HttpHandlerы могут передавать выполнение запроса другим классам или же сами обрабатывают запрос и возвращают результат клиенту.

Важной особенностью ASP.NET является то, что HttpHandler может быть создан разработчиком для выполнения своих конкретных задач. Далее, мы рассмотрим создание своего собственного HttpHandler для отображения графической информации.

Создадим сайт с именем ws1. Пусть на основной странице Default.aspx необходимо отображать графическую информацию, которая будет создаваться некоторым классом MakeGraphiks, вызов которого будет обеспечивать обработчик HttpHandler. HTML код страницы будет примерно таким:

<%@ Page Language="C#" AutoEventWireup="true"  
  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Image id="Image1"  runat="server" Height="900px" 
      Width="800px" ImageAlign="Middle"></asp:Image>
    </form>
</body>
</html>

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

Замечание: Можно также создавать экземпляр обработчика HttpHandler с помощью класса, реализующего интерфейс IHttpHandlerFactory.

Интерфейсы System.Web.IHttpAsyncHandler и System.Web.IHttpHandler должны включать методы ProcessRequest (обработчик запроса), свойства IsReusable (поддержика организация пулов). Для интерфейса System.Web.IHttpAsyncHandler требуются дополнительно методы BeginProcessRequest и EndProcessRequest(инициализация и завершение асинхронных вызовов).

Добавим к проекту класс с именем MyHandlerGraph и включим в него необходимые методы и свойства:

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;

public class MyHandlerGraph : IHttpHandler
{	
 #region IHttpHandler Members
 public bool IsReusable
 {
     get { return true; }
 }
 public void ProcessRequest(HttpContext context)
 {
  HttpRequest Request = context.Request;
  HttpResponse Response = context.Response;
   //Здесь код обработки запроса
 }
 #endregion
}

Если при вызове HttpHandler используются сессии, то необходимо дополнить код методом GetHandler:

using System.Web.SessionState;
public virtual IHttpHandler GetHandler(HttpContext context, 
                  string requestType, string url, string path)
{
 return PageParser.GetCompiledPageInstance(url,
       context.Server.MapPath("~/default.aspx"), context);
}

HttpHandler создан. Далее необходимо подключить его к приложению. Для чего в файле Web.config в секции <httpHandlers> добавим:

<httpHandlers>
 .......
 <add verb="GET" path="getgraph.aspx" type="MyHandlerGraph" />
</httpHandlers>

Буквально это означает, что при вызове страници getgraph.aspx необходимо запрос перенаправить HttpHandlerу MyHandlerGraph. Файл getgraph.aspx в сборке приложения реально не существует, однако для вызова HttpHandler достаточно либо непосредственно задать вызов, например:

<Image1 src="getgraph.aspx">

Либо воспользоваться следующим вызовам в Default.aspx.cs:

private void Page_Load(object sender, System.EventArgs e)
{
 Image1.Attributes.Add("src","getgraph.aspx");
}

На данном этапе у нас есть зарегестрированный Handler - осталось заставить его работать по назначению.

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

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
public class MakeGraphiks
{
 private Bitmap objBitmap;
 private Graphics objGraphics;
 private Pen objPen; 
 public MakeGraphiks()
 {		
 }
 public Bitmap Create()
 {
  //Холст
  objPen=new Pen(Color.Blue);
  objBitmap = new Bitmap(800, 900);
  //Графическое представление objBitmap
  objGraphics = Graphics.FromImage(objBitmap);
  objGraphics.Clear(Color.GhostWhite);
  //Далее можем рисовать на холсте все,
  //что нам хочется, задавая параметры пера,
  //линий, кистей
  objPen.DashStyle = DashStyle.Dot;
  objPen.Width = 1;
  objGraphics.DrawLine(objPen, 40, 0, 400, 0);
  SolidBrush myBrush = new SolidBrush(Color.Silver);
  objGraphics.FillRectangle(myBrush, 2, 2, 45, 78);
  //И т.д и т.п
  //в заключкнии возвращаем рисунок Handlerу
  return objBitmap; 
 }//public Bitmap Create()
} 

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

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

void IHttpHandler.ProcessRequest(HttpContext context)
{
 HttpRequest Request = context.Request;
 HttpResponse Response = context.Response;
 using(MakeGraphiks mkgr=new MakeGraphiks())
 {
  Bitmap mygraph=mkgr.Create();
  using(MemoryStream ms=new MemoryStream())
  {
   mygraph.Save(ms,ImageFormat.Png);
   Response.Clear();
   Response.ContentType = "image/png";
   ms.WriteTo(Response.OutputStream);
  }
 }
}

Теперь при вызове странички Default.aspx будет отображаться графическая информация, созданная в классе MakeGraphiks.


В начало

Параграф 2 Отображение графической информации с использованием дополнительной страницы

В данном способе для формирования графического изображения требуется отдельная .ASPX страница. Страница не имеет HTML содержания, ее основная задача - извлечение параметров из URL и использование их для формирования картинки.

В данном примере показано использование класса Page для создания страницы ASP.NET фонового кода и его использования для вывода графической информации.

Добавим к проекту новую Web форму MakeGraph.aspx и оставим в ней только первую строчку HTML кода:

<%@ Page Language="C#" AutoEventWireup="true" 
  CodeFile="MakeGraph.aspx.aspx.cs" Inherits="MakeGraph.aspx" %>

Как и ранее определим необходимые объекты, и код формирования изображения:

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

public partial class MakeGraph : System.Web.UI.Page
{
 private Bitmap objBitmap;
 private Graphics objGraphics;
 private Pen objPen;
 private void Page_Load(object sender, System.EventArgs e)
 {
  objBitmap = new Bitmap(800, 900);
  //Графическое представление objBitmap
  objGraphics = Graphics.FromImage(objBitmap);
  objGraphics.Clear(Color.GhostWhite);
  //Далее можем рисовать на холсте все,
  //что нам хочется, задавая параметры пера,
  //линий, кистей
  objPen = new Pen(Color.Blue);
  objPen.DashStyle = DashStyle.Dot;        
  objPen.Width = 1;
  objGraphics.DrawLine(objPen, 40, 0, 400, 0);
  SolidBrush myBrush = new SolidBrush(Color.Silver);
  objGraphics.FillRectangle(myBrush, 2, 2, 45, 78);
  //И т.д и т.п

  //Отображаем Bitmap с помощью OutputStream
  objBitmap.Save(Response.OutputStream, ImageFormat.Gif);
 }//private void Page_Load
}

Обратим внимание, что class MakeGraph мы определили как наследника Page - класса, который представляет файл .aspx, называемый также страницей веб-форм, запрашиваемый с сервера, где выполняется веб-приложение ASP.NET.

Этот класс целесообразно наследовать, когда необходимо создать страницу веб-форм, используя метод фонового кода. (Visual Studio .NET, автоматически используют эту модель для создания страниц веб-форм, так как объект Page является именованным контейнером для всех серверных элементов управления на странице.).

Класс Page связан с файлами, имеющими расширение .aspx. Эти файлы компилируются во время выполнения как объекты Page и кэшируются в памяти сервера.

Пространства имен System.Web.UI, System.Web.UI.WebControls, System.HTML.UI.WebControls, как раз и обеспечивают возможности класса Page.

Теперь, для использования данного кода, в вызывающей странице достаточно поместить тег:

<IMG src="MakeGraph.aspx">

Либо воспользоваться следующим вызовам в Default.aspx.cs:

private void Page_Load(object sender, System.EventArgs e)
{
 Image1.Attributes.Add("src","getgraph.aspx");
}

Для вывода файлов можно использовать следующий код файла MakeGraph.aspx:

protected void Page_Load(object sender, EventArgs e)
{
 //Картинка хранится в Files/Images/ там где установлено приложение
 string sFilename=Server.MapPath(@"Files/Images/Имя_Картинки");
 Response.ContentType = "image/png";
 Response.Write(sFilename);
}

Вызов остался тотже. Используя вызов с параметрами, можно выводить различные картинки.

Адаптировано к VS 2008 28.02.2008г.


В начало

Параграф 2 О возможностях преобразований графических файлов при их отображении на сайте


В начало

2.1 Как повторить примеры, приведенные ниже

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

Для использования кода в MakeGraph, в вызывающей странице (файл Default.aspx.cs) объявлена переменная
public string sTegImage = string.Empty;, а в файл Default.aspx тэг, через который происходит вызов страницы MakeGraph.aspx:

<%=sTegImage%>

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

 
sTegImage = "<IMG src = \"MakeGraph.aspx?fn=" + sFileName + "\">";

Таким образом, для повторения примеров создайте проект, показанный на Рис.1 (или возмите с сайта - см. выше):

graph001.gif

Рис.1 Проект Web сайта

Естественно, что данный проект демонстрационный, и Вы не будете таскать на сервер файлы картинок с тем, чтобы потом их тащить назад и отобразить в Browser. Файлы картинок хранятся обычно на сервере или в таблицах баз данных (о том, как их оттуда достать, речь пойдет позже). На данном этапе надо сосредоточиться на том, как выдать картинки клиенту.

Рассмотрим отображение картинок. Код вызывающей страницы будет примерно таким:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
public partial class _Default : System.Web.UI.Page
{
 public string sTegImage = string.Empty;  
 #region Page_Load
 protected void Page_Load(object sender, EventArgs e)
 {
  Label1.Text = "";       
 }
 #endregion
 #region Загрузка картинок и эксперименты
 protected void Button1_Click(object sender, EventArgs e)
 {
  string sPath = @"c:\temp\";
  string sFileName = string.Empty;
  if (FileUpload1.HasFile)
  {
      sFileName = sPath + FileUpload1.FileName;
      FileUpload1.SaveAs(sFileName);
  }
  else
  {
      Label1.ForeColor = Color.Red;
      Label1.Text = "Выберите файл для проведения эксперимента";
      return;
  }
  sTegImage = "<IMG src = \"MakeGraph.aspx?fn=" + sFileName + "\">"; 
  //В случае, когда необходимо разделение вызова по расширению
  //string s = sFileName.Substring(sFileName.LastIndexOf(".")+1);
  //if (s.ToUpper() == "BMP")
  //{
  // .........
  
 }
 #endregion
}

Напомним, что Web Form MakeGraph состоит из файла MakeGraph.aspx с одной единственной строкой:

<%@ Page Language="C#" AutoEventWireup="true" 
 CodeFile="MakeGraph.aspx.cs" Inherits="MakeGraph" %> 

Код MakeGraph.aspx.cs имеет одну функцию Page_Load:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
public partial class MakeGraph : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {


    }
}

Все приводимые ниже примеры связаны с наполнением содержимым функции Page_Load класса MakeGraph.


В начало

2.2 Вывод рисунков на Web сайт

Приведенный ниже код позволяет выводить картинки на сайт, масштабировать картинки файлов форматов jpeg, bmp, png, gif, wmf, emf,tiff, ico. Анимированные файлы gif выводятся как не анимированные (о том, как вывести анимированные Gif - см. в конце этого параграфа). При использовании данного кода файлы формата tiff не загружаются на страничку в формате tiff. Правда, и обычным образом, они не могут быть выведены на html страницу (по крайней мере, в IE 7 вызов <img src="Путь\Имя_файла.tiff" ALT="Имя_файла.tiff"> не выводит рисунок на страничку), но преобразование к png формату позволяет выполнить их просмотр (то есть, приведенный ниже код отображает и tiff файлы).

Отметим, что мы можем сохранить формат вывода таким, как в исходном файле (для этого достаточно передать тип выбранного файла в параметрах вызова MakeGraph.aspx - его можно получить через свойство FileUpload1.PostedFile.ContentType). Однако целесообразнее передавать файлы на страничку в png, gif или jpeg форматах, что значительно сокращает сетевой трафик по сравнению с другими типами файлов.

В файле Default.aspx.cs пишем:

#region Загрузка картинок и эксперименты
protected void Button1_Click(object sender, EventArgs e)
{
 string sPath = @"c:\temp\";
 string sFileName = string.Empty;
 if (FileUpload1.HasFile)
 {
  sFileName = sPath + FileUpload1.FileName;
  //Переносим файл на сервер 
  FileUpload1.SaveAs(sFileName);
 }
 else
 {
  Label1.ForeColor = Color.Red;
  Label1.Text = "Выберите файл для проведения эксперимента";
  return;
 }
 //Формируем тэг IMG для странички Default.aspx.cs
 sTegImage = "<IMG src = \"MakeGraph.aspx?fn=" + sFileName + "\">";
}

Код MakeGraph.aspx.cs:

protected void Page_Load(object sender, EventArgs e)
{
 string sFileName = string.Empty;
 try
 {
  sFileName = Request.QueryString.Get(0);
 }
 catch (Exception)
 {
  Response.End();
  return;
 }
 //Загружаем картинку любого из перечисленных форматов в класс Bitmap 
 Bitmap bitmap = new Bitmap(sFileName);
 //Если необходимо уменьшение размера картинки, например в 2 раза, используем 
 //метод GetThumbnailImage с параметрами  bitmap.Width / 2, bitmap.Height / 2
 //(размер может быть и увеличен 3*bitmap.Width / 2,3* bitmap.Height / 2)
 Bitmap bitmapchange = (Bitmap)bitmap.GetThumbnailImage(bitmap.Width / 2, bitmap.Height / 2, null, 
                                                                           IntPtr.Zero);
 MemoryStream memorystream=null;
 try
 {
  memorystream = new MemoryStream();
  //Передаем в массив в формате Png
  bitmapchange.Save(memorystream, ImageFormat.Png);
  byte[] b = memorystream.GetBuffer();
  //В каком формате будем возвращать картинку
  Response.ContentType = "image/png";
  //Возвращаем картинку
  Response.BinaryWrite(b);
 }
 catch (Exception ex)
 {
  Response.End();
 }
 bitmap.Dispose();
 bitmapchange.Dispose();
 memorystream.Dispose();

Если использовать вывод на страничку в формате jpeg, то код можно упростить (отказавшись от MemoryStream):

Response.ContentType = "image/jpeg";
bitmapchange.Save(Response.OutputStream, ImageFormat.Jpeg);
bitmap.Dispose();
bitmapchange.Dispose();

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

string sFileName = Request.QueryString.Get(0);
System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
MemoryStream memorystream = new MemoryStream();
image.Save(memorystream, ImageFormat.Gif);
Response.ContentType = "image/gif";
byte[] b = memorystream.GetBuffer();
Response.BinaryWrite(b);
memorystream.Dispose();
image.Dispose();
//Или так
string sFileName = Request.QueryString.Get(0);            
System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
MemoryStream memorystream = new MemoryStream();
image.Save(memorystream, ImageFormat.Gif);
Response.ContentType = "image/gif";
Response.BinaryWrite(memorystream.ToArray());
memorystream.Dispose();
image.Dispose();
//Или так
string sFileName = Request.QueryString.Get(0);            
System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
MemoryStream memorystream = new MemoryStream();           
image.Save(memorystream, ImageFormat.Gif);
string sImage = Convert.ToBase64String(memorystream.ToArray());
memorystream.Flush();
memorystream = new MemoryStream(Convert.FromBase64String(sImage));          
Response.BinaryWrite(mem.ToArray());       
memorystream.Dispose();
image.Dispose();

P.S. Как ни странно, но анимация работает и при значении Response.ContentType не равному "image/gif", но ImageFormat обязательно должен быть Gif.


В начало

2.3 Возможности преобразования форматов графических файлов с использованием класса Bitmap

В предыдущем параграфе мы загружали файлы большинства известных графических форматов в класс Bitmap. Причем на странице мы их отображали уже в другом формате (Png). Это говорит о том, что класс может быть использован и для непосредственного преобразования форматов файлов.

Вместо физических путей во всех примерах, приводимых далее, можно использовать относительный путь, используя Server.MapPath()

Возможность преобразования демонстрирует следующий код (в нашем проекте его можно поместить как в файл Default.aspx.cs, так и в MakeGraph.aspx.cs):

//Загружаем в любом из форматов jpeg, bmp, ico, gif, wmf, png, emf, gif, tiff
Bitmap bitmap = new Bitmap(sFileName);            
MemoryStream memorystream = new MemoryStream();
//mageFormat.Gif - в каком формате хотим получить файл
bitmap.Save(memorystream, ImageFormat.Gif);
//Куда сохранить результат
FileStream filestream = new FileStream(@"Путь\имя_файла.gif", FileMode.OpenOrCreate,
   FileAccess.ReadWrite, FileShare.ReadWrite);
filestream.Write(memorystream.GetBuffer(), 0, (int)memorystream.Length);
//Сохраняем
filestream.Flush();
//Освобождаем сохраненный файл для других
filestream.Close();
bitmap.Dispose();
//==============================
//Или проще
Bitmap bitmap = new Bitmap(sFileName);
FileStream filestream = new FileStream(@"Путь\имя_файла.tiff", FileMode.OpenOrCreate,
                                       FileAccess.ReadWrite, FileShare.ReadWrite);
bitmap.Save(filestream, ImageFormat.Tiff);
filestream.Flush();
filestream.Close();
bitmap.Dispose();

Таблица 1 Возможности преобразований форматов c использованием класса Bitmap и метода Save
  *.bmp *.jpeg *.gif *.png *.wmf/*.emf *.tiff *.ico
*.bmp + + + + - + -
*.jpeg + + + + - + -
*.gif + + + + - + -
*.png + + + + - + -
*.wmf/*.emf + + + + - + -
*.tiff + + + + - + -
*.ico + + + + - + -

Из таблицы видно, что можно легко конвертировать WMF, EMF или ICO к другим форматам, но не наоборот. Microsoft Windows Metafile Format (WMF) используется для хранения векторных и растровых изображений и графических данных. WMF - это базовый 16-битный формат. Формат EMF является 32-битной дополненной переработкой формата WMF. EMF расширил функциональное назначение WMF, включая цветную палитру и полную поддержку для всех 32-битовых команд GDI.

В сети можно встретить простой код, для преобразования форматов, в том числе (по утверждению авторов), подходящего для конвертации в wmf и emf:

System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
image.Save(@"Путь\имя_файла.emf", ImageFormat.Emf);

Но, к сожалению, хотя данный код и формирует файл с расширением emf, однако содержимое файла окажется в формате png и, например, такой Viewer как Irvan View, тут же предложит Вам сменить расширение имени файла на png.

Причины этому - GDI + компоненты или, так называемые кодеки (encoders). NET имеет встроенные в кодирующие и декодирующие устройства кодеки, которые поддерживают чтение и запись bmp, gif, jpeg, png, tiff файлов и дополнительные встроенные кодеки, поддерживающие чтение файлов типа wmf, emf и ico. (О том, как программно посмотреть какие кодеки есть в системе, будем говорить в следующем параграфе).

Поэтому, для преобразования файлов в формат emf (формат wmf, скорее всего, разработчики Visual Studio посчитали устаревшим, и обошли его стороной), необходимо использовать класс Metafile. Следующий код демонстрирует пример, позволяющий преобразовать в emf формат любой из приведенных в таблице форматов:

 
Bitmap bitmap = new Bitmap(sFileName);            
Graphics graphics = Graphics.FromHwnd(IntPtr.Zero);
//Получаем контекст устройства
IntPtr graphicshandle = graphics.GetHdc();
Metafile metafile = new Metafile(@"Путь\имя_файла.emf",graphicshandle);
//Освобождаем контекст
graphics.ReleaseHdc(graphicshandle);
graphics.Dispose();
//Загружаем пустой метафайл
graphics = Graphics.FromImage(metafile);
graphics.DrawImage(metafile, 0, 0);
//Рисуем картинку на graphics
//Можно писать, рисовать, вставлять комментарии и т.д. и т.п.
graphics.DrawImage(bitmap, 0, 0);            
metafile.Dispose();
graphics.Dispose();
bitmap.Dispose();

И последнее замечание данного параграфа - для преобразования файлов всех форматов в *.ico файлы подходит следующий код (однако, не забывайте о размерах иконок):

Bitmap bitmap = new Bitmap(sFileName);
IntPtr iconhandle = bitmap.GetHicon();
Icon icon = Icon.FromHandle(iconhandle);
using (FileStream filestream = new FileStream(@"Путь\имя_файла.ico", 
    FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
{
 icon.Save(filestream);
 filestream.Flush();
 filestream.Close();
}


В начало

2.4 Возможности сжатия и трансформации графических файлов, преобразованных в формат jpg

Когда мы говорим о сжатии изображений, то понимаем под этим, что речь идет, прежде всего, о формате jpg. JPEG (Joint Photographic Experts Group) создан именно как метод сжатия фотоизображений. Алгоритм JPEG является алгоритмом сжатия с потерей качества. При сохранении JPEG-файла можно указать степень сжатия, которую задают в некоторых условных единицах (в PhotoShop 1-12, в Net 1-100..). Большее число соответствует лучшему качеству, но при этом увеличивается размер файла. Когда речь идет о сжатии других форматов, то это, скорее всего, речь об удалении лишней (служебной) информации из файла (об этих аспектах сжатия мы говорить не будем). Для bmp файлов - это речь о числе бит на пиксель (2*0, 2*4, 2*8,2*24, 2*32) . Методы, применяемые для сжатия битовых матриц, связаны с изменением числа бит на пиксель (оптимизация палитры или "огрубление" цветов). Эти методы дают некоторый эффект и при работе с другими типами файлов - об этом будем говорить в следующем параграфе.


В начало

2.4.1Сжатие формата jpg

Следующий код, помещенный в MakeGraph.aspx.cs, позволяет сначала преобразовать изображение в jpg формат, а затем выполнить сжатие файла с потерей качества и вывод рисунка на web страничку.

public partial class MakeGraph : System.Web.UI.Page
{
 protected void Page_Load(object sender, EventArgs e)
 {
   string sFileName = string.Empty;
   try
   {
       sFileName = Request.QueryString.Get(0);
   }
   catch (Exception)
   {
       Response.End();
       return;
   } 
   //Ищем кодек для jpeg
   string sMimeType = "image/jpeg";                
   ImageCodecInfo[] imagecodecinfo = ImageCodecInfo.GetImageEncoders();
   ImageCodecInfo jpimagecodecinfo = null;        
   //Таким образом можно не только найти, но и посмотреть все кодеки
   for (int i = 0; i < imagecodecinfo.Length; i++)
   {
       if (imagecodecinfo[i].MimeType == sMimeType)
       {
           jpimagecodecinfo = imagecodecinfo[i];
           break;
       }
   }
   if (jpimagecodecinfo == null)
   {
       Response.End();
       return;
   }
   //Создаем параметр 1, в котором определяем степень сжатия для jpeg 25%
   EncoderParameter encoderparameter = new EncoderParameter(Encoder.Quality, (long)25);
   EncoderParameters encoderparameters = new EncoderParameters(1);
   encoderparameters.Param[0] = encoderparameter;
   //Загружаем файл любого формата и преобразуем его в Jpeg
   MemoryStream memorystream = new MemoryStream();
   System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
   image.Save(memorystream, ImageFormat.Jpeg);
   image.Dispose();
   //Помещаем в привычный Bitmap
   Bitmap bitmap = new Bitmap(memorystream);
   memorystream.Flush();
   memorystream = new MemoryStream();
   //Вносим сжатие 25%
   bitmap.Save(memorystream, jpimagecodecinfo, encoderparameters);
   //Выводим сжатый файл на страничку в формате jpg
   Response.ContentType = sMimeType; 
   byte[] b = memorystream.GetBuffer();            
   Response.BinaryWrite(b);      
   bitmap.Dispose();   
   memorystream.Dispose();
 }
}

Результат преобразования файла из формата bmp в код в формате jpg и сжатия на 25% показан на Рис.2 справа (слева исходный файл).

graph002.gif

Рис.2 Сжатие изображения с потерей качества


В начало

2.4.2 Трансформация файла формата jpg

Код, обеспечивающий вывод трансформированных графических файлов формата jpg на сайт, практически полностью аналогичен приведенному выше и, поэтому, приводится здесь с минимальными комментариями:

string sFileName = string.Empty;
sFileName = Request.QueryString.Get(0);
//Ищем кодек для jpeg
string sMimeType = "image/jpeg";
ImageCodecInfo[] imagecodecinfo = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo jpimagecodecinfo = null;
for (int i = 0; i < imagecodecinfo.Length; i++)
{
    if (imagecodecinfo[i].MimeType == sMimeType)
    {
        jpimagecodecinfo = imagecodecinfo[i];
        break;
    }
}
if (jpimagecodecinfo == null)
{
    Response.End();
    return;
}
EncoderParameters encoderparameters = new EncoderParameters(1);
//Здесь задаются параметры трансформации (EncoderValue)
EncoderParameter encoderparameter = 
  new EncoderParameter(System.Drawing.Imaging.Encoder.Transformation,
    (long)EncoderValue.TransformRotate90);
encoderparameters.Param[0] = encoderparameter;
System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
MemoryStream memorystream = new MemoryStream();
//Изображение обязательно переводим в jpg
image.Save(memorystream, ImageFormat.Jpeg);
image.Dispose();
//Помещаем в привычный Bitmap
Bitmap bitmap = new Bitmap(memorystream);
memorystream.Flush();
memorystream = new MemoryStream();
//Вносим трансформацию
bitmap.Save(memorystream, jpimagecodecinfo, encoderparameters);
//Выводим на сайт
Response.ContentType = sMimeType;
byte[] b = memorystream.GetBuffer();
Response.BinaryWrite(b);
bitmap.Dispose();
memorystream.Dispose();


В начало

2.5 О возможности сжатия графических файлов изменением числа бит на пиксель и их трансформации

Как мы отметили выше, методы, применяемые для сжатия bmp файлов, связанные с изменением числа бит на пиксель (оптимизация палитры или "огрубление" цветов), дают эффект и при работе с другими типами файлов (минимальный для jpeg и не дают эффекта для gif, emf, wmf, ico). Для jpg, gif, emf, wmf и ico файлов, при использовании приведенных ниже примеров, можно говорить только о трансформации (поворотах).

string sFileName = Request.QueryString.Get(0);
Bitmap bitmap = new Bitmap(sFileName);
//Поддерживает PixelFormat Format16bppRgb555 Format16bppRgb556 
//Format24bppRgb Format32bppRgb 
Bitmap bitmap1 = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format16bppRgb565);            
Graphics g = Graphics.FromImage(bitmap1);
g.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
g.Save();
//Можно перевернуть изображение
bitmap1.RotateFlip(RotateFlipType.Rotate90FlipY);
//Сохраняем в файле для контроля размера
bitmap1.Save(@"Путь\имя_файла.bmp", ImageFormat.Bmp);
//Выводим на сайт в Png
MemoryStream memorystream = new MemoryStream();            
bitmap1.Save(memorystream, ImageFormat.Png);
byte[] b = memorystream.GetBuffer();           
Response.ContentType = "image/png";            
Response.BinaryWrite(b);

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

string sFileName = Request.QueryString.Get(0);
MemoryStream memorystream = new MemoryStream();
System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
image.Save(memorystream, ImageFormat.Bmp);
image.Dispose();
//Помещаем в привычный Bitmap
Bitmap bitmap = new Bitmap(memorystream);
memorystream.Flush();
Bitmap bitmap1 = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(bitmap1);
g.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
g.Save();
//Можно перевернуть изображение
bitmap1.RotateFlip(RotateFlipType.Rotate90FlipY);
//Сохраняем в файле для контроля размера
bitmap1.Save(@"Папка\Имя_файла.bmp", ImageFormat.Bmp);
//Выводим на сайт в Png
memorystream = new MemoryStream();
bitmap1.Save(memorystream, ImageFormat.Png);
byte[] b = memorystream.GetBuffer();
Response.ContentType = "image/png";
Response.BinaryWrite(b);


В начало

2.6 Для тех, кто хочет работать с пикселями

Идея уменьшения изображения за счет выбрасывания каждого второго или n-го пикселя используется во многих графических редакторах при сжатии битовых матриц (и не только). В принципе, этот метод почти всегда дает неплохие результаты при малых значениях n и больших размерах картинок, и потеря качества при n=2, например, практически незаметна при размерах изображения 100*100pix. Впервые, приведенную ниже функцию вращения и сжатия изображения я написал в среде Borland C++ Builder и использовал в программе Bricks "Программы для семейного отдыха". Переведенная на C# она также может быть полезной при работе с графикой. Доступ непосредственно к байтам позволяет выполнять трансформации картинок, их сжатие с любым коэффициентом, а также может быть использована при замене части одного изображения другим и т.д. и т.п.

Приведенный ниже код справляется с вращением и масштабированием файлов всех форматов, рассматриваемых нами (единственный недостаток - время вращения больших картинок).

protected void Page_Load(object sender, EventArgs e)
{
 try
 {
     string sFileName = Request.QueryString.Get(0);
     MemoryStream memorystream = new MemoryStream();
     //Повернуть вокруг вертикальной оси, сжать по осям в 2 раза, 
     //сделать черно белой
     vRotate(sFileName, 2, 2,true,out memorystream);
     byte[] b = memorystream.GetBuffer();
     Response.BinaryWrite(b);
     memorystream.Flush();
     memorystream.Dispose();
 }
 catch (Exception ex)
 {
     Response.End();
     return;
 }
}
private void vRotate(string sFileName, 
     int i, int k, bool gray, out MemoryStream ms)
{
 //i=0 - без поворотов
 //i=1 - поворот вокруг горизонтальной оси
 //i=2 - поворот вокруг вертикальной  оси
 //i=3 - поворот на 90 градусов  вправо
 //i=4 - поворот на 180 градусов вправо
 //i=5 - поворот на 90 градусов влево        
 //k - коэффециэнт сжатия по Х и У
 //gray = true - сделать черно белой
 Bitmap bmp1 = new Bitmap(sFileName);        
 int X = 0;
 int Y = 0;
 Bitmap bmp2 = null;        
 switch (i)
 {
  case 0:
  case 2:
  case 4:
    bmp2 = new Bitmap(bmp1.Width / k, bmp1.Height / k);
    X = bmp1.Width;
    Y = bmp1.Height;
    break;
  case 3:
  case 5:
    bmp2 = new Bitmap(bmp1.Height / k, bmp1.Width / k);
    X = bmp1.Height;
    Y = bmp1.Width;
    break;
 }
 Color color;
 int r=0;
 int g=0;
 int b=0;
 for (int y = Y - 1; y >= 0; y -= k)
 {
  for (int x = 0; x < X; x += k)
  {
   try
   {
    switch (i)
    {
     case 0:
      r = bmp1.GetPixel(x, y).R;
      g = bmp1.GetPixel(x, y).G;
      b = bmp1.GetPixel(x, y).B;
      break;
     case 1:
      r = bmp1.GetPixel(bmp1.Width - x - 1, y).R;
      g = bmp1.GetPixel(bmp1.Width - x - 1, y).G;
      b = bmp1.GetPixel(bmp1.Width - x - 1, y).B;
      break;
     case 2:
      r = bmp1.GetPixel(x, bmp1.Height - y - 1).R;
      g = bmp1.GetPixel(x, bmp1.Height - y - 1).G;
      b = bmp1.GetPixel(x, bmp1.Height - y - 1).B;                           
      break;
     case 3:
      r = bmp1.GetPixel(y, bmp1.Height - x - 1).R;
      g = bmp1.GetPixel(y, bmp1.Height - x - 1).G;
      b = bmp1.GetPixel(y, bmp1.Height - x - 1).B;                            
      break;
     case 4:
      r = bmp1.GetPixel(bmp1.Width - x - 1, bmp1.Height - y - 1).R;
      g = bmp1.GetPixel(bmp1.Width - x - 1, bmp1.Height - y - 1).G;
      b = bmp1.GetPixel(bmp1.Width - x - 1, bmp1.Height - y - 1).B;                            
      break;
     case 5:
      r = bmp1.GetPixel(bmp1.Width - y - 1, x).R;
      g = bmp1.GetPixel(bmp1.Width - y - 1, x).G;
      b = bmp1.GetPixel(bmp1.Width - y - 1, x).B;                            
      break;
    }
    if (gray)
    {
     r = (r + g + b) / 3;
     color = Color.FromArgb(r, r, r);
     bmp2.SetPixel(x / k, y / k, color);
    }
    else
    {
     color = Color.FromArgb(r, g, b);
     bmp2.SetPixel(x / k, y / k, color);
    }
   }catch(Exception)
   {
   }
  }
 }
 ms = new MemoryStream();
 //Для контроля размера файла - можем сохранить в файл
 //bmp2.Save(@"C:\Ab\a01.bmp", ImageFormat.Bmp);
 bmp2.Save(ms, ImageFormat.Bmp);
 ms.Flush();
 ms.Dispose();
 bmp1.Dispose();
 bmp2.Dispose();
}


В начало

2.7 Использование метода DrawImage для работы с графическими файлами

Метод DrawImage класса Graphics позволяет отрисовывать изображения с их геометрическими преобразованиями. Мы его уже использовали в примерах, приведенных выше, для отрисовки изображений. Метод имеет 30 перегружаемых конструкторов и позволяет гораздо больше, нежели просто отрисовка. Некоторые из возможностей, подходящие к цели данного материала, мы и рассмотрим далее.

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

Рассмотрим использование ColorMatrix. ColorMatrix это массив 5*5 чисел с плавающей запятой (Табл.2). Каждое число определяет то, каким образом будет изменена соответствующая составляющая цвета картинки (R -красный, G - зеленый, B - синий, A - альфа канал или степень прозрачности, и последний столбец - служебный), которая будет отрисовываться методом DrawImage. Там, где в матрице стоят 0 и 1 - это предопределенные числа, которые являются константами матрицы. Там, где в матрице д.б. цифра, которая не обязаны быть ни 0 ни 1, то там может быть положительное число с плавающей точкой. Цвет пикселя будет соответствовать значению соответствующей цветовой компоненты пикселя исходного изображения умноженной на значение числа в ячейке. В пятой строке матрицы числа в диапазоне [-1 +1] и определяют, на сколько должно измениться значение компонента по отношению к цветовым составляющим пикселя исходного изображения, RGBA составляющие которого считаются равными 1. Первыми вычисляются значения цвета пикселей по первым четырем строкам, а затем применяется пятая строка.

Таблица 2.  Матрица ColorMatrix 
Параметр R - красный G - зеленый B - синий A - альфа канал Доп столбец
Строка 1 Цифра 0 0 0 0
Строка 2 0 Цифра 0 0 0
Строка 3 0 0 Цифра 0 0
Строка 4 0 0 0 Цифра 0
Строка 5 Цифра Цифра Цифра Цифра 1


В начало

2.7.1 Трансформация и прозрачность изображений

Код, приведенный ниже, позволяет создать ColorMatrix, которая будучи включенной в качестве атрибута в ImageAttributes, позволит выполнить отрисовку изображения со степенью прозрачности 0.3. Кроме того, код демонстрирует применение метода RotateTransform при отрисовке изображений на Web сайт.

protected void Page_Load(object sender, EventArgs e)
{
 try
 {
  string sFileName = Request.QueryString.Get(0);
  System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
  Bitmap bitmap = new Bitmap(image.Width*3, image.Height*3, PixelFormat.Format24bppRgb);
  Graphics graphics = Graphics.FromImage(bitmap);
  graphics.Clear(Color.WhiteSmoke);
  image.RotateFlip(RotateFlipType.Rotate90FlipX);
  graphics.DrawImage(image, 0, 0);
  graphics.RotateTransform(15, MatrixOrder.Append);
  graphics.DrawImage(image, image.Width, 0);
  graphics.RotateTransform(30, MatrixOrder.Append);
  ImageAttributes imageattributes = new ImageAttributes();
  ColorMatrix colormatrix = new ColorMatrix();
  imageattributes.SetColorMatrix(new ColorMatrix(new float[][]{ 
  new float[] {1, 0, 0, 0, 0},
  new float[] {0, 1, 0, 0, 0},
  new float[] {0, 0, 1, 0, 0},
  new float[] {0, 0, 0, 0.3f, 0},
  new float[] {0, 0, 0, 0, 1}}));
  graphics.DrawImage(image, new Rectangle (300,50,image.Width, image.Height),
     0,0,image.Width, image.Height,GraphicsUnit.Pixel, imageattributes);            
  Response.ContentType = "image/jpeg";
  bitmap.Save(Response.OutputStream, ImageFormat.Jpeg);
  Response.End();           
  bitmap.Dispose();
  image.Dispose();
 }
 catch (Exception ex)
 {
  Response.End();           
  return;
 }
}

Данный код работает с bmp, png, jpeg, gif, ico файлами без дополнительных преобразований.

graph001.jpg

Рис.3 Использование метода DrawImage


В начало

2.7.2 Имитация прозрачного фона и вывод рисунков с прозрачным фоном

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

graphics.Clear(Color.White);

В данном случае метод будет рисовать изображение по белому фону, а при задание цветов, которые должны быть прозрачными, они будут заменены белым. Это хорошо, когда страничка, на которую выводится изображение, белая - создается эффект прозрачности фона. А как быть в том случае, если на страничке используется background-image или, как на данной страничке, которую Вы читаете?

В таких случаях я использую поэтапный вывод, который заключается в том, что сначала картинка отрисовывается методом DrawImage на холсте с каким-нибудь редко встречающимся в рисунках цветом, а затем методами HTML передается на сайт с указанием сделать цвет холста прозрачным.

Для выполнения данных шагов и наглядности, выполним следующее:

  • Изменим фон странички, для чего в файле Default.aspx изменим тэг body:

    <body style="background-color : Teal">
    
  • В файле Default.aspx.cs изменим вывод картинки на сайт, как картинки с прозрачным цветом:

    sTegImage = "<IMG  style=\"filter:Chroma(Color='#010101')\" 
    		   src = \"MakeGraph.aspx?cs=" + sFileName +"\">";
    
  • В файле MakeGraph.aspx.cs напишем нижеприведенный код:

    string sFileName = Request.QueryString.Get(0);
    System.Drawing.Image image = System.Drawing.Image.FromFile(sFileName);
    //Картинку будем выводить уменьшенную в 2 раза
    Bitmap bitmap = new Bitmap(image.Width/2, image.Height/2, PixelFormat.Format24bppRgb);
    Graphics graphics = Graphics.FromImage(bitmap);
    //Цветом прозрачности выбираем цвет пикселя изображения [0,0]
    Bitmap bitmap1 = new Bitmap(image);            
    Color transparentcolor = bitmap1.GetPixel(0, 0);
    ImageAttributes imageattributes = new ImageAttributes();
    imageattributes.SetColorKey(transparentcolor, transparentcolor);
    //Можно задать дипазон прозрачных цветов 
    //attr.SetColorKey(Color.FromArgb(0, 0, 250), Color.FromArgb(0, 0, 255));
    //Делаем фон отрисовки почти черным 
    //Можно было задать белый, тогда при выводе на страничку с белам цветом
    //будет эффект прозрачности фона
    graphics.Clear(Color.FromArgb(1, 1,1));
    graphics.DrawImage(image, new Rectangle(0, 0, image.Width/2, image.Height/2),
        0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageattributes);
    MemoryStream memorystream = new MemoryStream();
    bitmap.Save(memorystream, ImageFormat.Png);
    byte[] b = memorystream.GetBuffer();
    Response.ContentType = "image/png";
    Response.BinaryWrite(b);
    

Результат работы показан на Рис.4.

graph004.gpg

Рис. 4 Использование прозрачного фона


В начало

Литература

Дино Эспозито. Microsoft ASP.NET 2.0 Углубленное изучение. Русская редакция Питер. 2007г.

Kumar Gaurav Khanna. Image Format Conversion in .NET

ООО "БизХост" Манипуляции с цветом и некоторые другие возможности GDI+

Молчанов Владислав 21.09.2005г.-14.02.2007г.

Еcли Вы пришли с поискового сервера - посетите мою главную страничку

На главной странице Вы найдете программы комплекса Veles - программы для автолюбителей, программы из раздела графика - программы для работы с фото, сделанными цифровым фотоаппаратом, программу Bricks - игрушку для детей и взрослых, программу записную книжку, программу TellMe - говорящий Русско-Английский разговорник - программу для тех, кто собирается погостить за бугром или повысить свои знания в английском, теоретический материал по программированию в среде Borland C++ Builder, C# (ASP.Net).

В начало страницы и главы

В начало раздела

В начало книги

На главную страницу


В начало


Сайт управляется системой uCoz