How is the approach to plot complex drawings with Direct2D (Sharpdx)?
Actually I am using a WindowsRenderTarget, connecting it with a Direct2D1.Factory and drawing to a RenderControl.
Factory2D = new SharpDX.Direct2D1.Factory(FactoryType.MultiThreaded);
FactoryWrite = new SharpDX.DirectWrite.Factory();
var properties = new HwndRenderTargetProperties();
properties.Hwnd = this.Handle;
properties.PixelSize = new Size2(this.ClientSize.Width, this.ClientSize.Height);
properties.PresentOptions = PresentOptions.RetainContents;
RenderTarget2D = new WindowRenderTarget(Factory2D, new RenderTargetProperties(new PixelFormat(Format.Unknown, AlphaMode.Premultiplied)), properties);
RenderTarget2D.AntialiasMode = AntialiasMode.PerPrimitive;
The drawing is done in the Paint Event of the form:
RenderTarget2D.BeginDraw();
RenderTarget2D.Clear(Color4.Black);
drawProgress(); // Doing Paintings like DrawLine, Multiple PathGeometrys, DrawEllipse and DrawText
RenderTarget2d.EndDraw();
In the MouseMove/MouseWheel event the drawing will be recalculated (for scaling or calculation of the elements that will be displayed). This process need about 8-10ms.
The next step is actually
this.Refresh();
Here, I guess, is the problem, this progress needs up to 140ms.
So the scaling/moving of the plot has about 7fps.
Also the program occupies more and more memory when refreshing the Control
////Edit
Painting of lines:
private void drawLines(Pen pen, PointF[] drawElements)
{
SolidColorBrush tempBrush = new SolidColorBrush(RenderTarget2D, SharpDX.Color.FromRgba(pen.Color.ToArgb()));
int countDrawing = (drawElements.Length / 2) + drawElements.Length % 2;
for (int i = 0; i < countDrawing; i++)
{
drawLine(new Vector2(drawElements[i].X, drawElements[i].Y), new Vector2(drawElements[i + 1].X, drawElements[i + 1].Y), brushWhite);
}
}
Painting geometrys:
RenderTarget2D.DrawGeometry(graphicPathToPathGeometry(p), penToSolidColorBrush(pen));
private PathGeometry graphicPathToPathGeometry(GraphicsPath path)
{
geometry = new PathGeometry(Factory2D);
sink = geometry.Open();
if (path.PointCount > 0)
{
sink.BeginFigure(new Vector2(path.PathPoints[path.PointCount - 1].X, path.PathPoints[path.PointCount - 1].Y), FigureBegin.Hollow);
sink.AddLines(pointFToVector2(path.PathPoints));
sink.EndFigure(new FigureEnd());
sink.Close();
}
return geometry;
}
In mouse move the drawing will be recalculated by just building differences between Cursor.Position.X/Y old and Cursor.Position.X/Y new. So the the lines will be recalculated really often :)
The main bottleneck is your graphicPathToPathGeometry() function. You are creating and "filling" a PathGeometry in a render loop. As I mentioned above, a core principle is that you have to create your resources at once and then just reuse them in your drawing routine(s).
About your memory leak... your code samples don't provide enough information, but most probably you are not freeing the resources that you are creating (ie PathGeometry, SolidColorBrush and the ones we don't see).
The simplest advise is - use your render loop only for rendering/drawing and reuse resources instead of recreating them.
Improving the performance of Direct2D apps
One part of the problem is:
SolidColorBrush tempBrush = new SolidColorBrush(RenderTarget2D, SharpDX.Color.FromRgba(pen.Color.ToArgb()));
Creating objects of any kind inside the renderloop creates a big memory lack in the application. Drawing existing values is the way to go.
I guess the performance issue will also be based on this problem.
Related
I will start with my situation right now.
I downloaded the raycast project from: https://github.com/ChrisSerpico/raycasting
This is based on the tutorial from here: https://lodev.org/cgtutor/raycasting.html
After I got the project to work, played around a bit and modified some things, I'm currently stuck with adding multiple layers (based on one map per layer). I read a lot of things around the Internet but had no luck implementing that feature.
In this project: https://github.com/Owlzy/OwlRaycastEngine
there are multiple layers added, but that is done with slices and I can't figure out how to implement this in the Serpico project (took this because the floor/ceiling drawing works a lot better there). Textures are saved like this:
Texture2D canvas; // used to convert the buffer to a single texture to be drawn
Color[] buffer; // screen buffer with raw color data to be drawn
Color[][] rawData; // raw data of the individual external textures
// initialize graphics rendering objects
canvas = new Texture2D(GraphicsDevice, SCREEN_WIDTH, SCREEN_HEIGHT);
buffer = new Color[SCREEN_WIDTH * SCREEN_HEIGHT];
rawData = new Color[NUM_TEXTURES][]; //number of Textures
for (int i = 0; i < NUM_TEXTURES; i++)
{
rawData[i] = new Color[TEXTURE_WIDTH * TEXTURE_HEIGHT];
}
The buffer gets filles this way in the Wallcasting loop:
if (TEXTURE_WIDTH * texY + texX <= rawData[texNum].Length - 1)
{
buffer[SCREEN_WIDTH * y + x] = rawData[texNum][TEXTURE_WIDTH * texY + texX];
}
else //avoid crash when running into walls
{
buffer[SCREEN_WIDTH * y + x] = rawData[texNum][rawData[texNum].Length - 1];
}
and finally drawn this way:
canvas.SetData<Color>(buffer);
b.Draw(canvas, new Rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), Color.White);
The code is straight from the lodev tutorial. I tried around with the variables lineHeight , texY and so on but no result. The textures just get stretched, cut off or the screen gets drawn with terrible effects.
Could someone help please? I really dispair...
Thanks a lot!
The problem is in the call in Draw() canvas.SetData<Color>(buffer);
Move this line to Update() and it will "mostly" work. Texture memory is shared between the CPU and GPU. By the time Draw() is called it is expected the textures already exist in GPU memory. Transferring data during draws causes random tearing.
The mostly comes in the nondeterministic delays between Update() and Draw() and the PCIE memory transfer.
I am making a small application that shows a live webcam feed in a windows form, and also stores watermarked images to drive at a specified interval (Creating a timelapse video is the end goal).
I am using the AForge library for image and video processing.
I have problems were there seems to be a memory leak, even though i try to make sure to use "using" statements at every location where image processing occurs.
Below is the code were the image processing takes place (The NewFrame event)
private void Video_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
try
{
if (ImageProcessing) // If the previous frame is not done processing, let this one go
return;
else
ImageProcessing = true;
using (Bitmap frame = (Bitmap)eventArgs.Frame)
{
// Update the GUI picturebox to show live webcam feed
Invoke((Action)(() =>
{
webcam_PictureBox.Image = (Bitmap)frame.Clone();
}));
// During tests, store images to drive at a certain interval
if (ImageStoreTimer.Elapsed.TotalSeconds > ImageStoreTime)
{
DateTime dt = DateTime.Now;
using (Graphics graphics = Graphics.FromImage(frame))
{
PointF firstLocation = new PointF(frame.Width / 2, frame.Height / 72);
PointF secondLocation = new PointF(frame.Width / 2, frame.Height / 15);
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Center;
using (Font arialFont = new Font("Arial", 15))
{
graphics.DrawString(dt.ToString(), arialFont, Brushes.Red, firstLocation, drawFormat);
graphics.DrawString(Pressure.ToString("F0") + " mbar", arialFont, Brushes.Red, secondLocation, drawFormat);
}
}
// Place images in a folder with the same name as the test
string filePath = Application.StartupPath + "\\" + TestName + "\\";
// Name images by number 1....N
string fileName = (Directory.GetFiles(filePath).Length + 1).ToString() + ".jpeg";
frame.Save(filePath + fileName, ImageFormat.Jpeg);
ImageStoreTimer.Restart();
}
}
//GC.Collect(); <----- I dont want this
}
catch
{
if (ProgramClosing == true){}
// Empty catch for exceptions caused by the program being closed incorrectly
else
throw;
}
finally
{
ImageProcessing = false;
}
}
Now, when running the program, i see memory usage going up and down, usually it gets to about 900MB before dropping. But occasionally it will rise to 2GB. Occasionally, i even get an out of memory exception at this line:
Graphics graphics = Graphics.FromImage(frame)
So after spending an hour or so to trying to reshape the code and looking for my memory leak, i at last tried the GC.Collect line that is commented out in the code (Shame). After that, my memory usage stays constant, at less than 60MB. And i can run the program for 24 hours without any problems.
So i read a bit about GC.Collect, and how bad it is, for example that it could take a lot of processing power to do it to often in a program. But when i compare the CPU power used by my program, it does not really change regardless if i comment the line out or leave it. But the memory problem is gone if i collect at the end of the new frame event.
I would like to find a solution to my problem that does not involve the GC.collect function, as i know it is bad programming practice and i should instead find the underlying problem source.
Thank you all in advance!
I'm not good with win forms but I think that this line:
webcam_PictureBox.Image = (Bitmap)frame.Clone();
Will leave previous image undisposed, which leaks memory (unmanaged memory hold by Bitmap). Since Bitmap has finalizer - it will be reclaimed by GC at some future time (or when you call GC.Collect), but as you already understand - it's not a good practice to rely on GC in such case. So try to do it like this instead:
if (webcam_PictureBox.Image != null)
webcam_PictureBox.Image.Dispose();
webcam_PictureBox.Image = (Bitmap)frame.Clone();
Reasonable comment by Larse: it might be better to not dispose image while it's still being assigned to PictureBox.Image, because who knows, maybe PictureBox control does anything with old image when you are assigning a new one. So alternative is then:
var oldImage = webcam_PictureBox.Image;
webcam_PictureBox.Image = (Bitmap)frame.Clone();
if (oldImage != null)
oldImage.Dispose();
This worked well for us, https://stackoverflow.com/a/70914235/10876657
before that, we tried using statements and all sorts but one solution might work on one machine, but not on the other, but by referencing the image currently set in picturebox and disposing of it afterwords has worked well, not entirely sure why picture.image is not disposed of automatically when a new image is set (frustrating!) but hey, at least this simple workaround exists
When I am drawing line and then iterate in for loop it cause high memory leak then after several line it crashed.
private Pen ReDrawFromGrid(Pen pen)
{
for (int parseDgV = 0; parseDgV < dataGridView1.Rows.Count; parseDgV++)
{
float redrawX1;
float redrawY1;
float redrawX2;
float redrawY2;
float.TryParse(dataGridView1.Rows[parseDgV].Cells[0].Value.ToString(), out redrawX1);
float.TryParse(dataGridView1.Rows[parseDgV].Cells[1].Value.ToString(), out redrawY1);
float.TryParse(dataGridView1.Rows[parseDgV].Cells[2].Value.ToString(), out redrawX2);
float.TryParse(dataGridView1.Rows[parseDgV].Cells[3].Value.ToString(), out redrawY2);
if (dataGridView1.Rows[parseDgV].Cells[5].Value.ToString() == "Solid")
{
dashRedraw = new float[1] { 10 };
}
else if (dataGridView1.Rows[parseDgV].Cells[5].Value.ToString() == "Dash")
{
dashRedraw = new float[2] { 10, 10 };
}
else if (dataGridView1.Rows[parseDgV].Cells[5].Value.ToString() == "Dot")
{
dashRedraw = new float[2] { 3, 5 };
}
else
{
dashRedraw = new float[1] { 10 };
}
pen = new Pen(dataGridView1.Rows[parseDgV].Cells[4].Style.BackColor);
pen.DashPattern = dashRedraw;
g.DrawLine(pen, redrawX1, redrawY1, redrawX2, redrawY2);
this.Refresh();
this.axDNVideoX1.Invalidate();
}
GC.Collect();
return pen;
}
I tried to put dispose before and after in this for loop but it resulted no line drawn. How can I iterate draw line without causing memory leak?
Thanks for your help!
Here's a quick re-write of the basic drawing code with some minor tweaks:
static float[] patSolid = new float[] { 10 };
static float[] patDash = new float[] { 10, 10 };
static float[] patDot = new float[] { 3, 5 };
private void RedrawFromGrid(Graphics g)
{
float X1, Y1, X2, Y2;
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
var cells = dataGridView1.Rows[i].Cells;
if (!float.TryParse(cells[0].Value.ToString(), out X1) ||
!float.TryParse(cells[1].Value.ToString(), out Y1) ||
!float.TryParse(cells[2].Value.ToString(), out X2) ||
!float.TryParse(cells[2].Value.ToString(), out Y2)
)
continue;
var style = cells[5].Value.ToString();
float[] pattern = patSolid;
if (style == "Dash")
pattern = patDash;
else if (style == "Dot")
pattern = patDot;
using (var pen = new Pen(cells[4].Style.BackColor))
{
pen.DashPattern = pattern;
g.DrawLine(pen, X1, Y1, X2, Y2);
}
}
}
It's a bit dirty still, since I wanted to keep the basic functionality close to where you had it.
A few refinements:
The only thing that needs to be allocated inside the loop is the pen object, and you could get rid of that too by caching the pens and reusing them. But since we're doing allocation each time, the pen needs to be disposed of when you're done with it. The using statement does this for you. You should be doing the same for the Graphics object in the method that calls this.
Second: don't allocate the same arrays over and over, allocate them once and reuse them. You already know ahead of time what patterns you are going to be using, so pre-allocating the pattern arrays is a good idea. Especially if this is getting called 30 times per second by your video code or similar.
And finally, this method should have as few side effects as possible. The method that calls it should take care of the side effects - calling this.Refresh() or this.axDNVideoX1.Invalidate() after disposing of the Graphics object when all the drawing is done.
In general a drawing operation should be as fast - and have as few side effects - as it is possible to make it. Allocating arrays, calling Invalidate or Refresh, etc. are for the caller to do. Don't let your drawing method do anything but draw.
edit:
Incidentally, calling GC.Collect() will not solve a memory leak. In fact it's quite likely that it's not going to do anything at all in this context, since none of the allocations happening inside this loop will have reached termination anyway. When you find yourself directly invoking the garbage collector it is almost always a mistake.
First, the parameter "pen" does not have sense to me. You are passing a Pen to your method and then in your method you are creating a new instance of the Pen and then you are returning a Pen. I would cache all required Pen instances and reuse them. Once you don't need a particular Pen any more, Dispose it.
The only 'Memory leak' worrying part of the code here that I see is a 'axDNVideoX1' which is it seems an ActiveX control and it has to do something with Video. Try to comment out that part of the code and see if you still have a memory leak.
I would not say that the 'DrawLine' causes a memory leak.
Keep your eye on the count of the 'GDI objects' created and destroyed. You can see this value in the Task Manager.
You are creating a Pen per loop iteration. You need to dispose those objects properly. The pen you pass to the function is lost, the pens you create in the loop are lost except for the last which is returned.
using blocks are your friend. Use them.
I am trying to render text on a bitmap in a Windows Phone 7 application.
Code that looks more or less like the following would work fine when it's running on the main thread:
public ImageSource RenderText(string text, double x, double y)
{
var canvas = new Canvas();
var textBlock = new TextBlock { Text = text };
canvas.Children.Add(textBloxk);
Canvas.SetLeft(textBlock, x);
Canvas.SetTop(textBlock, y);
var bitmap = new WriteableBitmap(400, 400);
bitmap.Render(canvas, null);
bitmap.Invalidate();
return bitmap;
}
Now, since I have to render several images with more complex stuff, I would like to render the bitmap on a background thread to avoid an unresponsive UI.
When I use a BackgroundWorker to do so, the constructor for TextBlock throws an UnauthorizedAccessException claiming that this is an invalid cross-thread access.
My question is: how can I render text on a bitmap without blocking the UI?
Please don't suggest using a web service to do the rendering. I need to render a large number of images and the bandwidth cost is not acceptable for my needs, and the ability to work offline is a major requirement.
The solution doesn't necessarily has to use WriteableBitmap or UIElements, if there is another way to render text.
EDIT
Another thought: does anyone know if it should be possible to run a UI message loop in another thread, and then have that thread do the work? (instead of using a BackgroundWorker)?
EDIT 2
To consider alternatives to WriteableBitmap, the features I need are:
Draw a background image.
Measure the width and height of a 1-line string, given a font familiy and size (and preferably style). No need for word wrapping.
Draw a 1-line string, with given font family, size, style, at a given coordinate.
Text rendering should support a transparent background. I.e. you should see the background image between the characters.
This method copies the letters from an pre-made image instead of using TextBlock, it's based on my answer to this question. The main limitation is requiring a different image for each font and size needed. A size 20 Font needed about 150kb.
Using SpriteFont2 export the font and the xml metrics file in the sizes you require. The code assumes they're named "FontName FontSize".png and "FontName FontSize".xml add them to your project and set the build action to content. The code also requires WriteableBitmapEx.
public static class BitmapFont
{
private class FontInfo
{
public FontInfo(WriteableBitmap image, Dictionary<char, Rect> metrics, int size)
{
this.Image = image;
this.Metrics = metrics;
this.Size = size;
}
public WriteableBitmap Image { get; private set; }
public Dictionary<char, Rect> Metrics { get; private set; }
public int Size { get; private set; }
}
private static Dictionary<string, List<FontInfo>> fonts = new Dictionary<string, List<FontInfo>>();
public static void RegisterFont(string name,params int[] sizes)
{
foreach (var size in sizes)
{
string fontFile = name + " " + size + ".png";
string fontMetricsFile = name + " " + size + ".xml";
BitmapImage image = new BitmapImage();
image.SetSource(App.GetResourceStream(new Uri(fontFile, UriKind.Relative)).Stream);
var metrics = XDocument.Load(fontMetricsFile);
var dict = (from c in metrics.Root.Elements()
let key = (char) ((int) c.Attribute("key"))
let rect = new Rect((int) c.Element("x"), (int) c.Element("y"), (int) c.Element("width"), (int) c.Element("height"))
select new {Char = key, Metrics = rect}).ToDictionary(x => x.Char, x => x.Metrics);
var fontInfo = new FontInfo(new WriteableBitmap(image), dict, size);
if(fonts.ContainsKey(name))
fonts[name].Add(fontInfo);
else
fonts.Add(name, new List<FontInfo> {fontInfo});
}
}
private static FontInfo GetNearestFont(string fontName,int size)
{
return fonts[fontName].OrderBy(x => Math.Abs(x.Size - size)).First();
}
public static Size MeasureString(string text,string fontName,int size)
{
var font = GetNearestFont(fontName, size);
double scale = (double) size / font.Size;
var letters = text.Select(x => font.Metrics[x]).ToArray();
return new Size(letters.Sum(x => x.Width * scale),letters.Max(x => x.Height * scale));
}
public static void DrawString(this WriteableBitmap bmp,string text,int x,int y, string fontName,int size,Color color)
{
var font = GetNearestFont(fontName, size);
var letters = text.Select(f => font.Metrics[f]).ToArray();
double scale = (double)size / font.Size;
double destX = x;
foreach (var letter in letters)
{
var destRect = new Rect(destX,y,letter.Width * scale,letter.Height * scale);
bmp.Blit(destRect, font.Image, letter, color, WriteableBitmapExtensions.BlendMode.Alpha);
destX += destRect.Width;
}
}
}
You need to call RegisterFont once to load the files then you call DrawString. It uses WriteableBitmapEx.Blit so if your font file has white text and a transparent background alpha is handled correctly and you can recolour it. The code does scale the text if you draw at a size you didn't load but the results aren't good, a better interpolation method could be used.
I tried drawing from a different thread and this worked in the emulator, you still need to create the WriteableBitmap on the main thread. My understanding of your scenario is that you want to scroll through tiles similar to how mapping apps work, if this is the case reuse the old WriteableBitmaps instead of recreating them. If not the code could be changed to work with arrays instead.
I'm not sure if this will fully resolve your issues, but there are 2 tools that I use in my comic book reader (I won't shamelessly plug it here, but I'm tempted.. a hint if you are searching for it.. it is "Amazing"). There are times where I need to stitch together a bunch of images. I use Rene Schulte's (and a bunch of other contributors) WriteableBitmapExtensions (http://writeablebitmapex.codeplex.com/). I have been able to offload rendering/stitching of an image to a background thread and then set the resulting WriteableBitmap as the source of some image on the UI thread.
Another up and comer in this space is the .NET Image Tools (http://imagetools.codeplex.com/). They have a bunch of utilities for saving/reading various image formats. They also have a few of the low levels, and I wish there were an easy way to use both (but there isn't).
All of the above work in WP7.
I guess the major difference is with these tools you won't be using XAML you will be writing directly to your image (so you may need to do size detection of your text and stuff like that).
The very nature of UI elements requires interaction with them on the UI thread. Even if you could create them on a background thread, when you came to try to render them into the WriteableBitmap you'd get a similar exception, and even then if it allowed you to do that, the elements wouldn't actually have a visual representation until they were added into the visual tree. You might need to use a generic image manipulation library instead of using UI elements.
Perhaps you could describe your scenario on a wider basis, we might have a better solution for you :)
First off, are you sure about rendering this as a bitmap? How about generating a Canvas with an image and TextBlock?
I need to render a large number of images
I have a feeling that this generating will kill phone performance. Generally, for bitmap mainupulation, the best way is to use XNA. Some parts of the XNA framework do a great job Silverlight projects. (BTW the refreshed Windows Phone Developer Tools will allow Silverlight and XNA coexist in the same project)
I would step back and think about this feature. Developing something like this for a week and then end up with unacceptable performance would make me a sad panda.
EDIT
As far I understand you need some kind of popup with image as a background and the message.
Make a Canvas with TextBlock but hide it.
<Canvas x:Name="userInfoCanvas" Height="200" Width="200" Visibility="Collapsed">
<Image x:Name="backgroundImage"> </Image>
<TextBlock x:Name="messageTextBlock" Canvas.ZIndex="3> </TextBlock> <!--ZIndex set the order of elements -->
</Canvas>
When you got the new message, show the Canvas to the user (a opacity animation would be nice), when you finish rendering on the background thread.
messageTextBlock.Text = message;
backgroundImage.Source = new BitmapImage(renderedImage);
Obviouslly, here is a problem with update. UIelements can be updated only form the UI Thread, hence update must be queue with Dispatcher
Dispatcher.BeginInvoke(DispatcherPriority.Background, messageUpdate); //messageUpdate is an Action or anthing that can be infered to Delegate
PS. didn't compile, this is more pseudocode.
You can draw on WriteableBitmap in thread, but You have to
create WriteableBitmap in main UI thread
do draw work in background thread
assign BitmapSource in main UI thread
i'll agree with Derek's answer: you're trying to use UI controls without a UI.
If you want to render a bitmap you need to stick to classes for drawing text on bitmaps.
i presume Windows Phone 7 has the .NET Compact Framework.
psudeo-code:
public Bitmap RenderText(string text, double x, double y)
{
Bitmap bitmap = new Bitmap(400, 400);
using (Graphics g = new Graphics(bitmap))
{
using (Font font = SystemFonts....)
{
using (Brush brush = new SolidColorBrush(...))
{
g.DrawString(text, font, brush, new Point(x, y));
}
}
}
return bitmap;
}
In my project, I'm using (uncompressed 16-bit grayscale) gigapixel images which come from a high resolution scanner for measurement purposes. Since these bitmaps can not be loaded in memory (mainly due to memory fragmentation) I'm using tiles (and tiled TIFF on disc). (see StackOverflow topic on this)
I need to implement panning/zooming in a way like Google Maps or DeepZoom. I have to apply image processing on the fly before presenting it on screen, so I can not use a precooked library which directly accesses an image file. For zooming I intend to keep a multi-resolution image in my file (pyramid storage). The most useful steps seem to be +200%, 50% and show all.
My code base is currently C# and .NET 3.5. Currently I assume Forms type, unless WPF gives me great advantage in this area. I have got a method which can return any (processed) part of the underlying image.
Specific issues:
hints or references on how to implement this pan/zoom with on-demand generation of image parts
any code which could be used as a basis (preferably commercial or LGPL/BSD like licenses)
can DeepZoom be used for this (i.e. is there a way that I can provide a function to provide a tile at the right resulution for the current zoom level?) ( I need to have pixel accurate addressing still)
This CodeProject article: Generate...DeepZoom Image Collection might be a useful read since it talks about generating a DeepZoom image source.
This MSDN article has a section Dynamic Deep Zoom: Supplying Image Pixels at Run Time and links to this Mandelbrot Explorer which 'kinda' sounds similar to what you're trying to do (ie. he is generating specific parts of the mandelbrot set on-demand; you want to retrieve specific parts of your gigapixel image on-demand).
I think the answer to "can DeepZoom be used for this?" is probably "Yes", however as it is only available in Silverlight you will have to do some tricks with an embedded web browser control if you need a WinForms/WPF client app.
Sorry I can't provide more specific answers - hope those links help.
p.s. I'm not sure if Silverlight supports TIFF images - that might be an issue unless you convert to another format.
I decided to try something myself. I came up with a straightforward GDI+ code, which uses the tiles I've already got. I just filter out the parts which are relevant for current clipping region. It works like magic! Please find my code below.
(Form settings double buffering for the best results)
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics dc = e.Graphics;
dc.ScaleTransform(1.0F, 1.0F);
Size scrollOffset = new Size(AutoScrollPosition);
int start_x = Math.Min(matrix_x_size,
(e.ClipRectangle.Left - scrollOffset.Width) / 256);
int start_y = Math.Min(matrix_y_size,
(e.ClipRectangle.Top - scrollOffset.Height) / 256);
int end_x = Math.Min(matrix_x_size,
(e.ClipRectangle.Right - scrollOffset.Width + 255) / 256);
int end_y = Math.Min(matrix_y_size,
(e.ClipRectangle.Bottom - scrollOffset.Height + 255) / 256);
// start * contain the first and last tile x/y which are on screen
// and which need to be redrawn.
// now iterate trough all tiles which need an update
for (int y = start_y; y < end_y; y++)
for (int x = start_x; x < end_x; x++)
{ // draw bitmap with gdi+ at calculated position.
dc.DrawImage(BmpMatrix[y, x],
new Point(x * 256 + scrollOffset.Width,
y * 256 + scrollOffset.Height));
}
}
To test it, I've created a matrix of 80x80 of 256 tiles (420 MPixel). Of course I'll have to add some deferred loading in real life. I can leave tiles out (empty) if they are not yet loaded. In fact, I've asked my client to stick 8 GByte in his machine so I don't have to bother about performance too much. Once loaded tiles can stay in memory.
public partial class Form1 : Form
{
bool dragging = false;
float Zoom = 1.0F;
Point lastMouse;
PointF viewPortCenter;
private readonly Brush solidYellowBrush = new SolidBrush(Color.Yellow);
private readonly Brush solidBlueBrush = new SolidBrush(Color.LightBlue);
const int matrix_x_size = 80;
const int matrix_y_size = 80;
private Bitmap[,] BmpMatrix = new Bitmap[matrix_x_size, matrix_y_size];
public Form1()
{
InitializeComponent();
Font font = new Font("Times New Roman", 10, FontStyle.Regular);
StringFormat strFormat = new StringFormat();
strFormat.Alignment = StringAlignment.Center;
strFormat.LineAlignment = StringAlignment.Center;
for (int y = 0; y < matrix_y_size; y++)
for (int x = 0; x < matrix_x_size; x++)
{
BmpMatrix[y, x] = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
// BmpMatrix[y, x].Palette.Entries[0] = (x+y)%1==0?Color.Blue:Color.White;
using (Graphics g = Graphics.FromImage(BmpMatrix[y, x]))
{
g.FillRectangle(((x + y) % 2 == 0) ? solidBlueBrush : solidYellowBrush, new Rectangle(new Point(0, 0), new Size(256, 256)));
g.DrawString("hello world\n[" + x.ToString() + "," + y.ToString() + "]", new Font("Tahoma", 8), Brushes.Black,
new RectangleF(0, 0, 256, 256), strFormat);
g.DrawImage(BmpMatrix[y, x], Point.Empty);
}
}
BackColor = Color.White;
Size = new Size(300, 300);
Text = "Scroll Shapes Correct";
AutoScrollMinSize = new Size(256 * matrix_x_size, 256 * matrix_y_size);
}
Turned out this was the easy part. Getting async multithreaded i/o done in the background was a lot harder to acchieve. Still, I've got it working in the way described here. The issues to resolve were more .NET/Form multithreading related than to this topic.
In pseudo code it works like this:
after onPaint (and on Tick)
check if tiles on display need to be retrieved from disc
if so: post them to an async io queue
if not: check if tiles close to display area are already loaded
if not: post them to an async io/queue
check if bitmaps have arrived from io thread
if so: updat them on screen, and force repaint if visible
Result: I now have my own Custom control which uses roughly 50 MByte for very fast access to arbitrary size (tiled) TIFF files.
I guess you can address this issue following the steps below:
Image generation:
segment your image in multiple subimages (tiles) of a small resolution, for instace, 500x500. These images are depth 0
combine a series of tiles with depth 0 (4x4 or 6x6), resize the combination generating a new tile with 500x500 pixels in depth 1.
continue with this approach until get the entire image using only a few tiles.
Image visualization
Start from the highest depth
When user drags the image, load the tiles dynamically
When the user zoom a region of the image, decrease the depth, loading the tiles for that region in a higher resolution.
The final result is similar to Google Maps.