Глубокое увеличение – база данных Silverlight

ОГЛАВЛЕНИЕ

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

•    Скачать исходники - 383.01 Кб

Введение

Данная статья показывает, как сохранить изображение с глубоким увеличением в базе данных, и как извлечь данные для отображения в приложении Silverlight. Для простоты используется база данных Microsoft Access, но код легко адаптируется к Microsoft SQL Server или Oracle. Требуется много усилий, чтобы заставить его работать. Статья рассматривает следующие основы:

•    Как создать изображение с глубоким увеличением из одного (большого) изображения. (Или, точнее, как создать мозаику, образующую изображение с глубоким увеличением.)
•    Как сохранить созданную мозаику в базе  данных.
•    Требуемая модель базы данных.
•    Как прочитать мозаику из базы данных и передать ее управляющему элементу MultiScaleImage.

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

Предыстория

Бесконечное увеличение в Google Earth завораживает. В Silverlight есть прекрасная возможность глубокого увеличения. Однако неудобно использовать отдельный инструмент (формирователь глубокого увеличения) для создания такого изображения. Генерируемые им в системе файлы непрактичны, однако  он сойдет, если надо развернуть одно-два изображения с глубоким увеличением на веб-сайте. Но что делать, если надо создать тысячу таких изображений?

Компания под названием Скоуп Солюшенс находится в Базеле, Швейцария. Она создает программное обеспечение для "исторических" архивов, управляющее метаданными об архивном хранилище, содержащем изображения, карты и планы зданий. Конечно, архивы сканируют свои интересные документы, чтобы сделать их общедоступными. Архивист сканирует старую карту в максимально возможном качестве, чтобы иметь резервную копию, как можно более совпадающую с оригиналом ( что если  вдруг оригинал повредится/потеряется?). Это ведет к очень большим файлам и растровым изображениям. Например, растровое изображение с 13722 x 9737 пикселями – не редкость. Такое изображение занимает, в зависимости от формата, до 400 Мб на диске. Неудивительно, что некоторые архивы имеют коллекции  с десятками тысяч карт или с более чем 250 тысячами изображений.

Было бы замечательно,  если бы архивисты-любители со всего мира могли просматривать "глубоко увеличиваемые" изображения старинных карт, не так ли? Для этой цели  использовать формирователь глубокого увеличения и систему на базе файлов не годится. Поэтому было найдено иное решение, как хранить изображения в базе данных.

Использование кода

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

Оба приложения входят в одно и то же решение. Для компиляции и запуска решения потребуется VS 2008 SP 1 (требуется для разработки на Silverlight) с .NET 3.5. (для простоты оба приложения упакованы в одно решение). Поэтому вам придется поменять проект запуска в зависимости от того, какое приложение вы хотите запустить.

Важно: Прежде чем вы сможете запустить решение на своем компьютере, вы должны изменить строку подключения в конфигурации, чтобы она соответствовала вашему расположению пути. Строку подключения для приложения Windows Forms можно изменить в файле настроек. Строку подключения для приложения ASP.NET можно изменить в файле web.config. (Пустой) образец базы данных расположен в каталоге App_Data приложения ASP.NET.

Внутри решения есть 4 следующих проекта:
•    DatabaseDeepZoom: Простое приложение Windows Forms, предоставляющее пользовательский интерфейс для создания мозаики изображения с глубоким увеличением.
•    DbDzComposer: Эта библиотека используется обоими проектами и делает большую часть работы. Она содержит класс для генерации мозаики и класс для сохранения и извлечения данных из базы данных.
•    DeepZoomSilverlightProject: Приложение Silverlight, отображающее изображения. Этот стандартный проект вы получаете при использовании формирователя глубокого увеличения. К нему был добавлен список для отображения эскизов правее изображения с глубоким увеличением.
•    DeepZoomSilverlightWeb: Приложение ASP.NET, в котором размещается приложение Silverlight. Это расширенная версия стандартного проекта, создаваемого формирователем глубокого увеличения. Были добавлены два HttpHandler, возвращающих мозаику и эскизы, которые требует приложение Silverlight, и веб-служба WCF для возврата данных об изображениях.

В данной статье опущено большинство комментариев к коду. Код, который вы можете скачать, лучше документирован.


Проект DatabaseDeepZoom

Это простое приложение генерирует мозаику для изображения с глубоким увеличением. Пользователь указывает имя файла и имя для изображения. Код вызывает функцию GenerateFromFile из класса DeepZoomComposer, являющегося частью библиотеки DbDzComposer. Экземпляр класса DeepZoomGenerator создается по умолчанию с помощью конструктора, приказывающего классу создать элементы мозаики 256 x 256 пикселей и сохранить их как элементы мозаики с 24-разрядной глубиной представления цвета с качеством 90. Такое поведение можно изменить с помощью второго конструктора или путем установки свойств.

Наиболее важно то, что также создается экземпляр класса доступа к базе данных. Этот класс наследует интерфейс от абстрактного базового класса, определяющего стандартный интерфейс. С помощью данного интерфейса легко написать дополнительный поставщик, сохраняющий данные в базе данных Oracle или SQL Server, вместо используемого здесь класса Access.

public MainForm()
{
    InitializeComponent();

    dz = new DeepZoomGenerator {DatabasePersister = GetAccessDb()};
    dz.CreationProgress += dz_CreationProgress;
}

private DzDbAccess GetAccessDb()
{
    string connectString = Settings.Default.ConnectionString;
    var cn = new OleDbConnection(connectString);
    cn.Open();

    var accessPersistence = new DzDbAccess();
    accessPersistence.DbConnection = cn;
    return accessPersistence;
}

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

Проект DbDzComposer

Эта библиотека используется приложением Windows Forms и приложением ASP.NET. Она содержит классы для создания изображения с глубоким увеличением и класс для хранения сгенерированной мозаики в базе данных Microsoft Access. Класс DzDbAccess наследуется от абстрактного интерфейса, определяющего методы для сохранения и извлечения изображений из базы данных. Данный интерфейс был создан, чтобы можно было легко добавить другой поставщик сохранения в базу данных для другой системы баз данных. Как видно из кода, интерфейс весьма простой. Он предоставляет метод для сохранения данных об изображении и метод для сохранения отдельных элементов мозаики. Также он имеет методы для возврата списка с информацией об изображениях, хранящихся в базе данных, и методы для возврата просмотра эскиза и отдельных элементов мозаики вызывающей функции. И, конечно, ему нужно свойство с информацией о подключении к базе данных.

namespace DbDzComposer
{
    public abstract class IDzDbPersistance
    {
        public abstract int SaveImageInfo(string imageName, int width, int height,
            int tileSize, int overlap, string mimeType, Bitmap thumbnail);
        public abstract List<ImageInfo> GetImageInfo(Uri fromUri);
        public abstract void SaveImageTile(int imageId, int level, int x, int y,
            Bitmap bitmap);
        public abstract Bitmap GetImageTile(int imageId, int level, int x, int y);
        public abstract Bitmap GetThumbnail(int imageId);
        public abstract IDbConnection DbConnection { get; set; }
    }

    public class ImageInfo
    {
        public int ImageId { get; set; }
        public string ImageName { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public int TileSize { get; set; }
        public int Overlap { get; set; }
        public string MimeType { get; set; }
        public string ThumbnailUrl { get; set; }
    }
}

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


Проект DeepZoomSilverlight

Этот проект Silverlight состоит из одной страницы, отображающей изображение с глубоким увеличением и отображающей доступные изображения из базы данных в окне списка. Спрашивается, как управляющий элемент MultiScaleImage отображает мозаику, хранящуюся в базе данных? Обычно свойству Source этого управляющего элемента присваивается универсальный код ресурса (URI) для файла XML, например, нечто вроде msi.Source="../GeneratedImages/dzc_output.xml". На(о?) это свойство на самом деле имеет тип MultiScaleTileSource.

Создается класс, унаследованный от MultiScaleTileSource и реализующий его интерфейс. Главный метод этого класса называется GetTileLayers и вызывается из управляющего элемента MultiScaleImage для каждого требуемого элемента мозаики. Внутри этого метода каждый элемент мозаики добавляется в список, но не в виде растрового изображения, а в виде URI. Затем сам URI должен передать изображение обратно, и поэтому в веб-проекте используется HttpHandler.

/// <summary>
/// Получает набор элементов мозаики, образующий изображение с глубоким увеличением.
/// </summary>
/// <param name="tileLevel">Уровень заголовка.</param>
/// <param name="tilePositionX">X-координата позиции заголовка.</param>
/// <param name="tilePositionY">Y-координата позиции заголовка.</param>
/// <param name="tileImageLayerSources">Источник изображения заголовка.</param>
protected override void GetTileLayers(int tileLevel, int tilePositionX,
                                      int tilePositionY,
                                      IList<object> tileImageLayerSources)
{
    string source =
        string.Format(
                      "TileHandler.ashx?tileLevel={0}&tilePositionX={1}&" +
                      "tilePositionY={2}&imageId={3}&contentType={4}",
                      tileLevel, tilePositionX, tilePositionY, imageId, mimeType);

    var uri = new Uri(HtmlPage.Document.DocumentUri, source);

    tileImageLayerSources.Add(uri);
}

HttpHandler не входит в проект Silverlight, а является частью приложения ASP.NET, в котором размещается приложение Silverlight. Необходим такой вид архитектуры, потому что сам Silverlight не способен подключиться к базе данных, поскольку он работает в безопасной среде-песочнице.

Веб-проект DeepZoomSilverlight

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

public void ProcessRequest(HttpContext context)
{
    if (context.Request.QueryString.HasKeys())
    {
        // Лень...
        int imageId = int.Parse(context.Request.QueryString.Get("imageId"));
        int tileLevel = int.Parse(context.Request.QueryString.Get("tileLevel"));
        int tilePosX = int.Parse(context.Request.QueryString.Get("tilePositionX"));
        int tilePosY = int.Parse(context.Request.QueryString.Get("tilePositionY"));
        string contentType = context.Request.QueryString.Get("contentType");

        context.Response.ContentType = contentType;
        System.Drawing.Bitmap bitmap = GetTile(imageId, tileLevel, tilePosX,
            tilePosY);

        if (bitmap != null)
        {
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            if (contentType.Contains("png"))
                bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            else
                bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
            ms.WriteTo(context.Response.OutputStream);
        }
    }
}

private Bitmap GetTile(int imageId, int level, int x, int y)
{
    try
    {
        DzDbAccess accessDb = DeepZoomSilverlightWeb.Helper.GetAccessDb();
        Bitmap bmp = accessDb.GetImageTile(imageId, level, x, y);
        accessDb.DbConnection.Close();
        return bmp;
    }
    catch (Exception)
    {
        return null;
    }
}

Интересные особенности

После обзора разных проектов, входящих в решение, и объяснения самых важных моментов дается дополнительная ценная информация о коде.

Формат файла с глубоким увеличением

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

Структура базы данных

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


Хранение растровых изображений в базе данных Microsoft Access

Не каждый день приходится сохранять растровые изображения в поле BLOB (большой двоичный объект). Для достижения этой цели используется объект параметризованной команды. Параметр для растрового изображения должен иметь тип OleDbType.LongVarBinary, и этому параметру передается массив байтов. Чтобы превратить растровое изображение в массив байтов, растровое изображение сериализуется в поток памяти с помощью BinaryFormatter.

public override void SaveImageTile(int imageId, int level, 
                     int x, int y, Bitmap bitmap)
{
    // Создается SQL

    string sql = "INSERT INTO DeepZoomTile(imageId, [level], x, y,
        bitmap) values (?, ?, ?, ?, ?)";
    var cmd = new OleDbCommand(sql, dbConnection);

    //Сериализация изображения
    byte[] bytes = BitmapToByteArray(bitmap);

    // Добавление параметров
    cmd.Parameters.Add("@imageid", OleDbType.Integer).Value = imageId;
    cmd.Parameters.Add("@level", OleDbType.Integer).Value = level;
    cmd.Parameters.Add("@x", OleDbType.Integer).Value = x;
    cmd.Parameters.Add("@y", OleDbType.Integer).Value = y;
    cmd.Parameters.Add("@bitmap", OleDbType.LongVarBinary,
        bytes.Length).Value = bytes;

    // Исполнение
    cmd.ExecuteNonQuery();
}

/// <summary>
/// Сериализация бинарного изображения в байтовый массив
/// </summary>
/// <param name="bitmap">Бинарное изображение.</param>
/// <returns></returns>
private static byte[] BitmapToByteArray(Bitmap bitmap)
{
    var memStream = new MemoryStream();
    var formatter = new BinaryFormatter();
    formatter.Serialize(memStream, bitmap);

    byte[] bytes = memStream.GetBuffer();
    memStream.Close();
    return bytes;
}

Возврат информации об изображениях из базы данных в окно списка Silverlight

Веб-интерфейс показывает окно списка правее изображения с глубоким увеличением. Это окно списка отображает данные из базы данных. Поскольку Silverlight не может работать с DataTable или DataSet, нельзя передать один из этих объектов как обычно. Поэтому класс доступа к базе данных возвращает список объектов ImageInfo. Здесь LINQ превосходно упрощает работу разработчика, если вы поняли требуемый им синтаксис. Вы можете использовать данный пример всегда, когда понадобится вернуть список объектов из DataSet. Переданный параметр fromUri метода необходим для возврата ThumbnailUrl. Так как приложение Silverlight может быть развернуто где угодно в сети, а значит и путь к HttpHandler, возвращающему эскиз изображения, меняется, URI передается с вызывающей страницы. При этом адрес для ThumbnailUrl всегда верен.

/// <summary>
/// Получает список с информацией об изображениях.
/// </summary>
/// <param name="fromUri">Url, из которого делается вызов.</param>
/// <returns></returns>
public override List<ImageInfo> GetImageInfo(Uri fromUri)
{
    string sql = "Select * from DeepZoomImage order by imageName";
    var dataAdapter = new OleDbDataAdapter(sql, dbConnection);
    var ds = new DataSet();

    dataAdapter.Fill(ds, "ImageInfo");

    var imageInfos = from image in ds.Tables["ImageInfo"].AsEnumerable()
                     select new ImageInfo
                    {
                        ImageId = image.Field<int>("ImageId"),
                        ImageName = image.Field<string>("ImageName"),
                        Height = image.Field<int>("Height"),
                        Width = image.Field<int>("Width"),
                        TileSize = image.Field<int>("TileSize"),
                        Overlap = image.Field<int>("Overlap"),
                        MimeType = image.Field<string>("MimeType"),
                        ThumbnailUrl = new Uri(fromUri,
                               "ThumbnailHandler.ashx?ImageId=" +
                               image.Field<int>("ImageId")).ToString()
                    };

    return imageInfos.ToList();
}

Возвращенный список объектов ImageInfo затем передается приложению Silverlight с помощью службы WCF. Служба, определенная в приложении ASP.NET, выглядит так:

namespace DeepZoomSilverlightWeb
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode =
        AspNetCompatibilityRequirementsMode.Allowed)]
    public class ImageListService
    {
        /// <summary>
        /// Получает список изображений из базы данных.
        /// </summary>
        /// <returns>Список объектов ImageInfo</returns>
        [OperationContract]
        public List<ImageInfo> GetImageList()
        {
            DbDzComposer.DzDbAccess accessDb = Helper.GetAccessDb();
            return accessDb.GetImageInfo(HttpContext.Current.Request.Url);
        }
    }
}

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

Сначала инициализируется imageListClient...

public Page()
{
    InitializeComponent();

    // Установка источника данных для списка
     var imageListClient = ServiceClientFactory.GetImageListServiceClient();
    imageListClient.GetImageListCompleted +=
      new EventHandler<DeepZoomSilverlightProject.
          ImageListClient.GetImageListCompletedEventArgs>(
    imageListCliet_GetImageListCompleted);
    imageListClient.GetImageListAsync();

Результат связывается с imageList, и выбирается первый элемент в списке...

/// <summary>
/// Обрабатывает событие GetImageListCompleted управляющего элемента imageListCliet.
/// </summary>
/// <param name="sender">Источник события.</param>
/// <param name="e">The <see cref="DeepZoomSilverlight
///                 Project.ImageListClient.GetImageListCompletedEventArgs"/>
/// экземпляр, содержащий данные события.</param>
void imageListCliet_GetImageListCompleted(object sender,
    DeepZoomSilverlightProject.ImageListClient.GetImageListCompletedEventArgs e)
{
    // Установка источника данных для списка изображений
    imageList.ItemsSource = e.Result;

    if (imageList.Items.Count > 0)
        imageList.SelectedIndex = 0;
}

В событии SelectionChanged окна списка передается объект MultiScaleTileSource в MultiScaleImage.
private void imageList_SelectionChanged(object sender, SelectionChangedEventArgs e)

{
    ImageInfo imageInfo = imageList.SelectedItem as ImageInfo;

    if (imageInfo != null)
    {
        // Установка источника изображения
        msi.Source = new DeepZoomTileSource(imageInfo.ImageId,
            imageInfo.Width, imageInfo.Height, imageInfo.TileSize,
            imageInfo.Overlap, imageInfo.MimeType);
    }
}

Вот и все! Надеемся код вам поможет в ваших проектах.