Silverlight数据库深度缩放






4.96/5 (36投票s)
本文描述了如何创建深度缩放图像并将图块存储在数据库中,以及如何从数据库中读取图像并在浏览器中显示它。

致谢
此代码源于另外两个项目/文章:Berend Engelbrecht 的 DecosDeepZoom 和 Mike Ormond 关于 MultiTileSource 的文章。我使用了 Berend Engelbrecht 的代码来生成图块。
引言
本文展示了如何将深度缩放图像存储在数据库中,以及如何检索数据以在 Silverlight 应用程序中显示。为简单起见,我使用 Microsoft Access 数据库,但代码可以轻松适应 Microsoft SQL Server 或 Oracle。尽管听起来很简单,但要使其工作需要做很多工作。本文涵盖以下内容:
- 如何从单个(大)图像创建深度缩放图像。(或者更确切地说,如何创建构成深度缩放图像的图块。)
- 如何将创建的图块存储在数据库中。
- 所需的数据库模型。
- 如何从数据库中读取图块并将其传递给
MultiScaleImage
控件。
在此过程中,您还将看到一些其他有用的东西,例如,如何将位图存储在 Access 数据库中,或者如何使用 LINQ 将 DataSet
转换为可作为 Silverlight 控件数据源的对象列表。
背景
我一直着迷于 Google Earth 中可以无限缩放的方式。当 Silverlight 推出深度缩放功能时,我非常兴奋。然而,我发现必须使用单独的工具(Deep Zoom Composer)来创建此类图像很不方便。我还发现它在文件系统上生成的文件不实用。如果您必须在网站上部署一两个深度缩放图像,这当然有效。但是,如果您必须创建数千个,该怎么办?
我受雇于一家名为 Scope Solutions 的公司,位于瑞士巴塞尔。我们为“历史”档案开发软件,这意味着我们主要管理档案保存的元数据,其中包括图像、地图和建筑平面图。当然,档案馆会扫描他们的有趣物品以供公众查阅。而且,如果档案管理员要扫描一张旧地图,他会以尽可能高的质量进行扫描,这样他就可以拥有一个尽可能接近原始地图的“备份”。以防原件损坏/丢失。这可能导致非常大的文件和位图。例如,13722 x 9737 像素的位图并不少见。根据格式的不同,这样的位图在磁盘上占用多达 400 MB。说到这里,听到一些档案馆的地图收藏有数万张地图或超过二十五万张图像,也就不足为奇了。
如果世界各地的业余档案管理员都能查看这些历史地图的“可深度缩放”图像,那不是很酷吗?使用 Deep Zoom Composer 和基于文件的系统肯定不是正确的做法。这就是我进行调查并想找出如何将图像存储在数据库中的原因。
Using the Code
这里展示的演示包含两个应用程序:
- 一个 Windows Forms 应用程序,用于生成深度缩放图像的图块并将其存储在 Microsoft Access 数据库中。
- 一个 Silverlight Web 应用程序,用于显示数据库中的图像。
这两个应用程序包含在同一个解决方案中。要编译和运行解决方案,您需要带有 .NET 3.5 的 VS 2008 SP 1(Silverlight 开发所需)。(为简单起见,我决定将两个应用程序封装在一个解决方案中)。因此,您必须根据要运行的应用程序更改启动项目。
重要提示:在您的机器上运行解决方案之前,您必须更改配置中的连接字符串以匹配您的路径位置。Windows Forms 应用程序的连接字符串可以在设置文件中更改。ASP.NET 应用程序的连接字符串可以在 web.config 文件中更改。(空)示例数据库位于 ASP.NET 应用程序的 App_Data 目录中。
解决方案中包含的四个项目是:
- DatabaseDeepZoom:一个简单的 Windows Forms 应用程序,提供用于创建深度缩放图像图块的用户界面。
- DbDzComposer:此库由两个项目使用,并完成大部分工作。它包含一个用于生成图块的类,以及一个用于从数据库存储和检索数据的类。
- DeepZoomSilverlightProject:显示图像的 Silverlight 应用程序。这主要是使用 Deep Zoom Composer 时获得的默认项目。它已通过一个列表进行了扩展,用于在深度缩放图像的右侧显示缩略图。
- DeepZoomSilverlightWeb:托管 Silverlight 应用程序的 ASP.NET 应用程序。它是 Deep Zoom Composer 创建的默认项目的扩展版本。添加了两个 HttpHandler,它们返回 Silverlight 应用程序所需的图块和缩略图,以及一个用于返回图像数据的 WCF Web 服务。
在本文中,我省略了大部分代码注释。您可以下载的代码有更好的文档。
DatabaseDeepZoom 项目
这个简单的应用程序为深度缩放图像生成图块。用户提供一个文件名和一个图像名称。代码调用 DeepZoomComposer
类中的 GenerateFromFile
函数,该类是 DbDzComposer
库的一部分。DeepZoomGenerator
类使用默认构造函数实例化,该构造函数指示类创建 256 x 256 像素的图块,并将其保存为质量为 90 的 24 位彩色 JPEG 图块。您可以使用第二个构造函数或通过设置属性来更改此行为。
最重要的一点是,我们还实例化了数据库访问类。该类继承自定义基本接口的 abstract
基类。使用该接口,可以轻松编写一个额外的提供程序,将数据存储在 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;
}
代码的其余部分只是处理打开图像文件,以及其他一些与 UI 相关的东西,如进度条。
DbDzComposer 项目
此库由 Windows Forms 应用程序和 ASP.NET 应用程序使用。它包含用于创建深度缩放图像的类以及用于将生成的图块存储在 Microsoft Access 数据库中的类。DzDbAccess
类继承自一个 abstract
接口,该接口定义了从数据库存储和检索图像的方法。我选择创建此接口,以便我可以轻松地为不同的数据库系统添加另一个数据库存储提供程序。从代码中可以看出,该接口非常简单。它提供了一个保存图像信息的方法和一个保存单个图块的方法。然后,它具有返回数据库中存储图像信息的列表的方法,以及将缩略图预览和单个图块返回给调用者的方法。当然,它还需要一个包含数据库连接信息的属性。
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
类基于 Berend Engelbrecht 的工作,是一个精简版本,因为我的目标是仅为单个图像创建图块。此类接收一个大图像,并创建创建深度缩放图像所需的图块。但是,它不支持 Deep Zoom 技术提供的更高级功能,例如 Berend 在其原始类中提供的图像集合。
DeepZoomSilverlight 项目
此 Silverlight 项目由单个页面组成,该页面显示深度缩放图像并在列表框中显示数据库中的可用图像。问题当然是,MultiScaleImage
控件如何显示存储在数据库中的图块?通常,我们会将该控件的 Source
属性分配给 XML 文件的 URI,例如,类似 msi.Source="../GeneratedImages/dzc_output.xml"
。但是,此属性实际上是 MultiScaleTileSource
类型。
因此,我们需要做的是创建一个继承自 MultiScaleTileSource
的类并实现其接口。该类的主要方法名为 GetTileLayers
,由 MultiScaleImage
控件为每个所需的图块调用。在该方法内部,您将每个图块添加到列表中,但不是以位图的形式,而是以 URI 的形式。URI 本身必须流回图像,这就是我们在 Web 项目中使用 HttpHandler 的原因。
/// <summary>
/// Gets a collection of the tiles that comprise the Deep Zoom image.
/// </summary>
/// <param name="tileLevel">Level of the tile.</param>
/// <param name="tilePositionX">X-coordinate position of the tile.</param>
/// <param name="tilePositionY">Y-coordinate position of the tile.</param>
/// <param name="tileImageLayerSources">Source of the tile image layer.</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 项目,而是属于托管 Silverlight 应用程序的 ASP.NET 应用程序。需要这种架构,因为 Silverlight 本身无法访问数据库,因为它在安全的沙盒环境中运行。
DeepZoomSilverlight Web 项目
这是托管 Silverlight 控件的 ASP.NET 应用程序。此应用程序还为 Silverlight 控件提供数据。此应用程序中有两个 HttpHandler,一个返回位图图块,另一个返回特定图像的缩略图。除此之外,还定义了一个 WCF 服务,Silverlight 应用程序使用该服务检索图像信息。
public void ProcessRequest(HttpContext context)
{
if (context.Request.QueryString.HasKeys())
{
// Being lazy...
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 中详细描述的内容。
数据库设计
我必须回答的一个问题是存储图像图块所需的数据库结构。事实证明,这比我最初查看 Deep Zoom Composer 生成的所有文件后想象的要容易。数据库设计由两个表组成。一个表用于存储图像信息,最重要的是宽度和高度、图块大小和重叠,因为这些信息对于正确初始化 MultiScaleTileSource
是必需的。另一个表存储生成的图块,以及它们在哪一层和哪个坐标上使用。

在 Microsoft Access 数据库中存储位图
尽管我经常进行数据库编程,但我并不是每天都将位图存储在 BLOB 字段中。要实现此目标,您必须使用参数化命令对象。位图的参数必须是 OleDbType.LongVarBinary
类型,并且您将一个字节数组传递给此参数。要将位图转换为字节数组,您将使用 BinaryFormatter
将位图序列化到内存流中。
public override void SaveImageTile(int imageId, int level,
int x, int y, Bitmap bitmap)
{
// Create the SQL
string sql = "INSERT INTO DeepZoomTile(imageId, [level], x, y,
bitmap) values (?, ?, ?, ?, ?)";
var cmd = new OleDbCommand(sql, dbConnection);
//serialise the image
byte[] bytes = BitmapToByteArray(bitmap);
// Add the parameters
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;
// Execute it
cmd.ExecuteNonQuery();
}
/// <summary>
/// Serializes a bitmap to an byte array
/// </summary>
/// <param name="bitmap">The 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 列表框
Web 前端在深度缩放图像的右侧显示一个列表框。此列表框显示来自数据库的数据。由于 Silverlight 无法处理 DataTable
或 DataSet
,我们无法像通常那样传递其中一个对象。因此,数据库访问类返回一个 ImageInfo
对象列表。在这里,LINQ 在简化开发人员工作方面做得很好,一旦您理解了它所需的语法。每当您需要从 DataSet
返回对象列表时,您都可以使用此示例。此方法的一个特殊之处在于传递的参数 fromUri
,它用于返回 ThumbnailUrl
。由于 Silverlight 应用程序可以部署在网络的任何地方,因此返回缩略图图像的 HttpHandler 的路径会更改,我从调用页面传入 URI。这样,我就可以确保 ThumbnailUrl
的地址始终正确。
/// <summary>
/// Gets a list with the information about the images.
/// </summary>
/// <param name="fromUri">The Url from which the call is made.</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
对象列表随后通过 WCF 服务传递给 Silverlight 应用程序。以下是 ASP.NET 应用程序中定义的服务的外观:
namespace DeepZoomSilverlightWeb
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
public class ImageListService
{
/// <summary>
/// Gets a list of images from the database.
/// </summary>
/// <returns>A list of ImageInfo objects</returns>
[OperationContract]
public List<ImageInfo> GetImageList()
{
DbDzComposer.DzDbAccess accessDb = Helper.GetAccessDb();
return accessDb.GetImageInfo(HttpContext.Current.Request.Url);
}
}
}
在消耗返回列表的 Silverlight 应用程序中,我们从页面的构造函数中调用 GetImageList
方法。由于这是一个异步调用,我们将其绑定到该方法的完成事件处理程序中。
首先,初始化 imageListClient
...
public Page()
{
InitializeComponent();
// Setting the datasource for the list
var imageListClient = ServiceClientFactory.GetImageListServiceClient();
imageListClient.GetImageListCompleted +=
new EventHandler<DeepZoomSilverlightProject.
ImageListClient.GetImageListCompletedEventArgs>(
imageListCliet_GetImageListCompleted);
imageListClient.GetImageListAsync();
将结果绑定到 imageList
并选择列表中的第一项...
/// <summary>
/// Handles the GetImageListCompleted event of the imageListCliet control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="DeepZoomSilverlight
/// Project.ImageListClient.GetImageListCompletedEventArgs"/>
/// instance containing the event data.</param>
void imageListCliet_GetImageListCompleted(object sender,
DeepZoomSilverlightProject.ImageListClient.GetImageListCompletedEventArgs e)
{
// Set the datasource for the image list control
imageList.ItemsSource = e.Result;
if (imageList.Items.Count > 0)
imageList.SelectedIndex = 0;
}
在列表框的 SelectionChanged
事件中,我们向 MultiScaleImage
传递一个 MultiScaleTileSource
对象...
private void imageList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ImageInfo imageInfo = imageList.SelectedItem as ImageInfo;
if (imageInfo != null)
{
// Setting the source of the multi scale image
msi.Source = new DeepZoomTileSource(imageInfo.ImageId,
imageInfo.Width, imageInfo.Height, imageInfo.TileSize,
imageInfo.Overlap, imageInfo.MimeType);
}
}
就是这样。我希望您觉得代码有用且易于理解。如果您有任何意见或建议,请告诉我。
历史
- 2009 年 2 月 4 日 - 版本 1.0 创建
- 2009 年 3 月 25 日 - 文章更新