A SIMPLE LIBRARY TO BUILD A DEEP ZOOM IMAGE
My current project requires a lot of work with Deep Zoom images. We recently received some very high-res photos, around 500M tiff files, some of which Deep Zoom Composer was unable to process. My first thought was to split them into smaller pieces and compose them together, but this led to visible join lines when processed, possibly due to rounding issues.
I’d already written some code to generate parts of a much larger deep zoom image, which was focused on building or rebuilding a small portion of much larger composition (over 2.5 million tiles in total) but it wasn’t quite ready to build a whole DZI.
So I took the bits of the code I needed and put them in a class by themselves. It’s very simple, and only designed to take a single source image and create a deep zoom tileset which can then be used by Silverlight.
It’s fairly unsophisticated in how it works. It loads in the source image (so you need enough RAM to hold that image at least) then renders out the top level tiles. Then it dumps the source image and renders the next levels down, rendering each one using the tiles of the level above. This seems to give a good result and was a technique I used previously when I was stitching together many source images and finding that DZC would leave ugly join lines at certain zoom levels.
The code feels a bit hacky, since most of the calculations were rough guesses or trial and error, but it definitely works for a single image, and it might be useful to someone, maybe as a server-side component to build deep zoom images on the fly. It’s biggest drawback is that it takes a long time, but if you’re using it in a WPF app you can always run it using a BackgroundWorker.
Note that this is WPF code, not Silverlight.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Media.Imaging;
using Path = System.IO.Path;
using System.Windows;
using System.Windows.Media;
using System.Xml.Linq; namespace DeepZoomBuilder
{
public enum ImageType
{
Png,
Jpeg
} public class DeepZoomCreator
{
/// <summary>
/// Default public constructor
/// </summary>
public DeepZoomCreator() { } /// <summary>
/// Create a deep zoom image from a single source image
/// </summary>
///
<param name="sourceImage">Source image path</param>
///
<param name="destinationImage">Destination path (must be .dzi or .xml)</param>
public void CreateSingleComposition(string sourceImage, string destinationImage, ImageType type)
{
imageType = type;
string source = sourceImage;
string destDirectory = Path.GetDirectoryName(destinationImage);
string leafname = Path.GetFileNameWithoutExtension(destinationImage);
string root = Path.Combine(destDirectory, leafname); ;
string filesdir = root + "_files"; Directory.CreateDirectory(filesdir);
BitmapImage img = new BitmapImage(new Uri(source));
double dWidth = img.PixelWidth;
double dHeight = img.PixelHeight;
double AspectRatio = dWidth / dHeight; // The Maximum level for the pyramid of images is
// Log2(maxdimension) double maxdimension = Math.Max(dWidth, dHeight);
double logvalue = Math.Log(maxdimension, );
int MaxLevel = (int)Math.Ceiling(logvalue);
string topleveldir = Path.Combine(filesdir, MaxLevel.ToString()); // Create the directory for the top level tiles
Directory.CreateDirectory(topleveldir); // Calculate how many tiles across and down
int maxcols = img.PixelWidth / ;
int maxrows = img.PixelHeight / ; // Get the bounding rectangle of the source image, for clipping
Rect MainRect = new Rect(, , img.PixelWidth, img.PixelHeight);
for (int j = ; j <= maxrows; j++)
{
for (int i = ; i <= maxcols; i++)
{
// Calculate the bounds of the tile
// including a 1 pixel overlap each side
Rect smallrect = new Rect((double)(i * ) - , (double)(j * ) - , 258.0, 258.0); // Adjust for the rectangles at the edges by intersecting
smallrect.Intersect(MainRect); // We want a RenderTargetBitmap to render this tile into
// Create one with the dimensions of this tile
RenderTargetBitmap outbmp = new RenderTargetBitmap((int)smallrect.Width, (int)smallrect.Height, , , PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
DrawingContext context = visual.RenderOpen(); // Set the offset of the source image into the destination bitmap
// and render it
Rect rect = new Rect(-smallrect.Left, -smallrect.Top, img.PixelWidth, img.PixelHeight);
context.DrawImage(img, rect);
context.Close();
outbmp.Render(visual); // Save the bitmap tile
string destination = Path.Combine(topleveldir, string.Format("{0}_{1}", i, j));
EncodeBitmap(outbmp, destination); // null out everything we've used so the Garbage Collector
// knows they're free. This could easily be voodoo since they'll go
// out of scope, but it can't hurt.
outbmp = null;
context = null;
visual = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
} // clear the source image since we don't need it anymore
img = null;
GC.Collect();
GC.WaitForPendingFinalizers(); // Now render the lower levels by rendering the tiles from the level
// above to the next level down
for (int level = MaxLevel - ; level >= ; level--)
{
RenderSubtiles(filesdir, dWidth, dHeight, MaxLevel, level);
} // Now generate the .dzi file string format = "png";
if (imageType == ImageType.Jpeg)
{
format = "jpg";
} XElement dzi = new XElement("Image",
new XAttribute("TileSize", ),
new XAttribute("Overlap", ),
new XAttribute("Format", format), // xmlns="http://schemas.microsoft.com/deepzoom/2008">
new XElement("Size",
new XAttribute("Width", dWidth),
new XAttribute("Height", dHeight)),
new XElement("DisplayRects",
new XElement("DisplayRect",
new XAttribute("MinLevel", ),
new XAttribute("MaxLevel", MaxLevel),
new XElement("Rect",
new XAttribute("X", ),
new XAttribute("Y", ),
new XAttribute("Width", dWidth),
new XAttribute("Height", dHeight)))));
dzi.Save(destinationImage); } /// <summary>
/// Save the output bitmap as either Png or Jpeg
/// </summary>
///
<param name="outbmp">Bitmap to save</param>
///
<param name="destination">Path to save to, without the file extension</param>
private void EncodeBitmap(RenderTargetBitmap outbmp, string destination)
{
if (imageType == ImageType.Png)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(outbmp));
FileStream fs = new FileStream(destination + ".png", FileMode.Create);
encoder.Save(fs);
fs.Close();
}
else
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = ;
encoder.Frames.Add(BitmapFrame.Create(outbmp));
FileStream fs = new FileStream(destination + ".jpg", FileMode.Create);
encoder.Save(fs);
fs.Close();
}
} /// <summary>
/// Specifies the output filetype
/// </summary>
ImageType imageType = ImageType.Jpeg; /// <summary>
/// Render the subtiles given a fully rendered top-level
/// </summary>
///
<param name="subfiles">Path to the xxx_files directory</param>
///
<param name="imageWidth">Width of the source image</param>
///
<param name="imageHeight">Height of the source image</param>
///
<param name="maxlevel">Top level of the tileset</param>
///
<param name="desiredlevel">Level we want to render. Note it requires
/// that the level above this has already been rendered.</param>
private void RenderSubtiles(string subfiles, double imageWidth, double imageHeight, int maxlevel, int desiredlevel)
{
string formatextension = ".png";
if (imageType == ImageType.Jpeg)
{
formatextension = ".jpg";
}
int uponelevel = desiredlevel + ;
double desiredfactor = Math.Pow(, maxlevel - desiredlevel);
double higherfactor = Math.Pow(, maxlevel - (desiredlevel + ));
string renderlevel = Path.Combine(subfiles, desiredlevel.ToString());
Directory.CreateDirectory(renderlevel);
string upperlevel = Path.Combine(subfiles, (desiredlevel + ).ToString()); // Calculate the tiles we want to translate down
Rect MainBounds = new Rect(, , imageWidth, imageHeight);
Rect OriginalRect = new Rect(, , imageWidth, imageHeight); // Scale down this rectangle to the scale factor of the level we want
MainBounds.X = Math.Ceiling(MainBounds.X / desiredfactor);
MainBounds.Y = Math.Ceiling(MainBounds.Y / desiredfactor);
MainBounds.Width = Math.Ceiling(MainBounds.Width / desiredfactor);
MainBounds.Height = Math.Ceiling(MainBounds.Height / desiredfactor); int lowx = (int)Math.Floor(MainBounds.X / );
int lowy = (int)Math.Floor(MainBounds.Y / );
int highx = (int)Math.Floor(MainBounds.Right / );
int highy = (int)Math.Floor(MainBounds.Bottom / ); for (int x = lowx; x <= highx; x++)
{
for (int y = lowy; y <= highy; y++)
{
Rect smallrect = new Rect((double)(x * ) - , (double)(y * ) - , 258.0, 258.0);
smallrect.Intersect(MainBounds);
RenderTargetBitmap outbmp = new RenderTargetBitmap((int)smallrect.Width, (int)smallrect.Height, , , PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
DrawingContext context = visual.RenderOpen(); // Calculate the bounds of this tile Rect rect = smallrect;
// This is the rect of this tile. Now render any appropriate tiles onto it
// The upper level tiles are twice as big, so they have to be shrunk down Rect scaledRect = new Rect(rect.X * , rect.Y * , rect.Width * , rect.Height * );
for (int tx = lowx * ; tx <= highx * + ; tx++)
{
for (int ty = lowy * ; ty <= highy * + ; ty++)
{
// See if this tile overlaps
Rect subrect = GetTileRectangle(tx, ty);
if (scaledRect.IntersectsWith(subrect))
{
subrect.X -= scaledRect.X;
subrect.Y -= scaledRect.Y;
RenderTile(context, Path.Combine(upperlevel, tx.ToString() + "_" + ty.ToString() + formatextension), subrect);
}
}
}
context.Close();
outbmp.Render(visual); // Render the completed tile and clear all resources used
string destination = Path.Combine(renderlevel, string.Format(@"{0}_{1}", x, y));
EncodeBitmap(outbmp, destination);
outbmp = null;
visual = null;
context = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
} } /// <summary>
/// Get the bounds of the given tile rectangle
/// </summary>
///
<param name="x">x index of the tile</param>
///
<param name="y">y index of the tile</param>
/// <returns>Bounding rectangle for the tile at the given indices</returns>
private static Rect GetTileRectangle(int x, int y)
{
Rect rect = new Rect( * x - , * y - , , );
if (x == )
{
rect.X = ;
rect.Width = rect.Width - ;
}
if (y == )
{
rect.Y = ;
rect.Width = rect.Width - ;
} return rect;
} /// <summary>
/// Render the given tile rectangle, shrunk down by half to fit the next
/// lower level
/// </summary>
///
<param name="context">DrawingContext for the DrawingVisual to render into</param>
///
<param name="path">path to the tile we're rendering</param>
///
<param name="rect">Rectangle to render this tile.</param>
private void RenderTile(DrawingContext context, string path, Rect rect)
{
if (File.Exists(path))
{
BitmapImage img = new BitmapImage(new Uri(path));
rect = new Rect(rect.X / 2.0, rect.Y / 2.0, ((double)img.PixelWidth) / 2.0, ((double)img.PixelHeight) / 2.0);
context.DrawImage(img, rect);
}
} }
}
If you do use this for anything, please let me know as I’d love to see it used elsewhere.
Update: This code is now a little redundant, now that the Deep Zoom Composer team have released a DLL to do the same thing.
From:http://jimlynn.wordpress.com/2008/11/12/a-simple-library-to-build-a-deep-zoom-image/
A SIMPLE LIBRARY TO BUILD A DEEP ZOOM IMAGE的更多相关文章
- [WPF系列]-Deep Zoom
参考 Deep Zoom in Silverlight
- openseadragon.js与deep zoom java实现艺术品图片展示
openseadragon.js 是一款用来做图像缩放的插件,它可以用来做图片展示,做展示的插件很多,也很优秀,但大多数都解决不了图片尺寸过大的问题. 艺术品图像展示就是最简单的例子,展示此类图片一般 ...
- 零元学Expression Blend 4 - Chapter 23 Deep Zoom Composer与Deep Zoom功能
原文:零元学Expression Blend 4 - Chapter 23 Deep Zoom Composer与Deep Zoom功能 最近有机会在工作上用到Deep Zoom这个功能,我就顺便介绍 ...
- Simple Library Management System HDU - 1497(图书管理系统)
Problem Description After AC all the hardest problems in the world , the ACboy 8006 now has nothing ...
- 为什么angular library的build不能将assets静态资源打包进去(转)
Versions Angular CLI: 6.0.7 Node: 9.3.0 OS: darwin x64 Angular: 6.0.3 ... animations, common, compil ...
- 【HDOJ】1497 Simple Library Management System
链表. #include <cstdio> #include <cstring> #include <cstdlib> #define MAXM 1001 #def ...
- How to add “Maven Managed Dependencies” library in build path eclipse
If you have m2e installed and the project already is a maven project but the maven dependencies are ...
- XStream -- a simple library to serialize objects to XML and back again
Link :http://xstream.codehaus.org/index.html http://www.cnblogs.com/hoojo/archive/2011/04/22/2025197 ...
- R Customizing graphics
Customizing graphics GraphicsLaTeXLattice (Treillis) plots In this chapter (it tends to be overly co ...
随机推荐
- [hihoCoder] 第四十八周: 拓扑排序·二
题目1 : 拓扑排序·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho所在学校的校园网被黑客入侵并投放了病毒.这事在校内BBS上立刻引起了大家的讨论,当 ...
- tomcat启动窗口中的时间与系统时间不一致
比我的系统时间慢8个小时,应该如何设置? 产生原因是因为Tomcat中的时区设置与操作系统的时区设置不一致,通过修改Tomcat根目录下的bin文件夹中的catalina.bat文件,增加以下配置解决 ...
- Sql Server删除数据表中重复记录 三种方法
本文介绍了Sql Server数据库中删除数据表中重复记录的方法. [项目]数据库中users表,包含u_name,u_pwd两个字段,其中u_name存在重复项,现在要实现把重复的项删除![分析]1 ...
- Java经典问题:传值与传引用?
转自:http://developer.51cto.com/art/201104/254715.htm Java到底是传值还是传引用?相信很少有人能完全回答正确.通常的说法是:对于基本数据类型(整型. ...
- R语言之——字符串处理函数
nchar 取字符数量的函数 length与nchar不同,length是取向量的长度 # nchar表示字符串中的字符的个数 nchar("abcd") [1] 4 # leng ...
- 基于ASP.NET MVC的ABP框架入门学习教程
为什么使用ABP 我们近几年陆续开发了一些Web应用和桌面应用,需求或简单或复杂,实现或优雅或丑陋.一个基本的事实是:我们只是积累了一些经验或提高了对,NET的熟悉程度. 随着软件开发经验的不断增加, ...
- HTML5学习笔记(二十五):事件
在浏览器或文档某个元素发生某个特定情况的瞬间,会作为一个事件进行广播,我们可以对其添加监听来处理特定的事件. 事件流 事件流描述了页面中接收事件的顺序. 整个事件流包含了三个阶段:事件捕获阶段.事件目 ...
- HTML5学习笔记(八):CSS定位
CSS 定位 (Positioning) 属性允许你对元素进行定位. 定位和浮动 CSS 为定位和浮动提供了一些属性,利用这些属性,可以建立列式布局,将布局的一部分与另一部分重叠.定位的基本思想很简单 ...
- C++11 override和final
30多年来,C++一直没有继承控制关键字.最起码这是不容易的,禁止一个类的进一步衍生是可能的但也很棘手.为避免用户在派生类中重载一个虚函数,你不得不向后考虑. C++ 11添加了两个继承控制关键字:o ...
- zsh与oh-my-zsh
在开始今天的 MacTalk 之前,先问两个问题吧: 1.相对于其他系统,Mac 的主要优势是什么?2.你们平时用哪种 Shell?…… 第一个童靴可以坐下了,Mac 的最大优势是 GUI 和命令行的 ...