2using System.ComponentModel;
7 public partial class ImageView
9 private readonly PictureBox overviewPanel =
new PictureBox();
10 private BioImage overviewSource;
11 private BioImage previewRequestImage;
12 private (
int Z,
int C,
int T) previewRequestCoordinate;
13 private int previewVersion;
14 private bool overviewDragging;
15 private bool showOverview =
true;
17 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
24 UpdateOverviewVisibility();
28 private void InitializeOverview()
30 overviewPanel.Name =
"overviewPanel";
31 overviewPanel.BackColor =
Drawing.Color.Black;
32 overviewPanel.Cursor = Cursors.Cross;
33 overviewPanel.Visible =
false;
34 overviewPanel.ContextMenuStrip = contextMenuStrip;
35 overviewPanel.Paint += OverviewPaint;
36 overviewPanel.MouseDown += OverviewMouseDown;
37 overviewPanel.MouseMove += OverviewMouseMove;
38 overviewPanel.MouseUp += OverviewMouseUp;
39 overviewPanel.MouseWheel += ImageView_MouseWheel;
40 overviewPanel.MouseCaptureChanged += (_, _) =>
42 if (!overviewPanel.Capture)
43 overviewDragging =
false;
45 viewport.Controls.Add(overviewPanel);
54 private BioImage OverviewSelectedImage =>
57 private void UpdateOverviewVisibility()
59 if (overviewPanel.IsDisposed)
62 overviewSource == OverviewSelectedImage;
63 if (overviewPanel.Visible)
64 overviewPanel.BringToFront();
67 private void SetOverviewPreview(
Drawing.Image source, BioImage image)
69 int width = image.isPyramidal ? image.Resolutions[0].SizeX : source.Width;
70 int height = image.isPyramidal ? image.Resolutions[0].SizeY : source.Height;
71 double ratio = 160.0 / Math.Max(width, height);
72 int previewWidth = Math.Max(1, (
int)Math.Round(width * ratio));
73 int previewHeight = Math.Max(1, (
int)Math.Round(height * ratio));
74 var thumbnail =
new Drawing.Bitmap(previewWidth, previewHeight);
75 using (var graphics =
Drawing.Graphics.FromImage(thumbnail))
77 graphics.InterpolationMode =
Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
78 graphics.DrawImage(source, 0, 0, previewWidth, previewHeight);
82 overviewSource = image;
83 overview =
new AForge.Rectangle(8, 8, previewWidth, previewHeight);
84 overviewPanel.SetBounds(overview.X, overview.Y, overview.Width, overview.Height);
85 UpdateOverviewVisibility();
86 overviewPanel.Invalidate();
89 private void UpdateRegularOverview()
91 BioImage image = OverviewSelectedImage;
92 if (image ==
null || image.isPyramidal ||
SelectedIndex >= Bitmaps.Count)
95 previewRequestImage =
null;
99 private async
void InitPreview()
101 BioImage image = OverviewSelectedImage;
102 if (image ==
null || !image.isPyramidal)
104 var coordinate = (image.Coordinate.Z, image.Coordinate.C, image.Coordinate.T);
105 if (previewRequestImage == image && previewRequestCoordinate == coordinate)
107 previewRequestImage = image;
108 previewRequestCoordinate = coordinate;
109 int version = ++previewVersion;
110 UpdateOverviewVisibility();
113 if (image.OpenSlideBase !=
null)
117 var slide = image.OpenSlideBase;
118 var levels = slide.Schema.Resolutions.OrderBy(r => r.Value.UnitsPerPixel).ToArray();
119 var levelInfo = levels.Where(r =>
120 Math.Max(slide.Schema.Extent.Width, slide.Schema.Extent.Height) /
121 r.Value.UnitsPerPixel >= 160).DefaultIfEmpty(levels[0]).Last();
122 int width = (int)Math.Ceiling(slide.Schema.Extent.Width / levelInfo.Value.UnitsPerPixel);
123 int height = (int)Math.Ceiling(slide.Schema.Extent.Height / levelInfo.Value.UnitsPerPixel);
124 if ((
long)width * height > 16_000_000)
126 using Drawing.Bitmap slidePreview = SlideOverviewBuilder.Build(width, height,
127 levelInfo.Value.TileWidth, levelInfo.Value.TileHeight,
128 (column, row) => slide.GetTile(
new BruTile.TileInfo
130 Index = new BruTile.TileIndex(column, row, levelInfo.Key)
132 if (version == previewVersion && !IsDisposed && !Disposing && image == OverviewSelectedImage)
133 SetOverviewPreview(slidePreview, image);
137 int level = image.Resolutions.Count - 1;
138 var resolution = image.Resolutions[level];
139 if ((
long)resolution.SizeX * resolution.SizeY > 16_000_000)
141 int frame = image.GetFrameIndex(coordinate.Z, coordinate.C, coordinate.T);
142 using Bitmap tile = await image.GetTile(frame, level, 0, 0, resolution.SizeX, resolution.SizeY);
143 if (tile ==
null || version != previewVersion || IsDisposed || Disposing ||
144 image != OverviewSelectedImage)
146 using Drawing.Bitmap display = ToSysBitmap(tile);
147 SetOverviewPreview(display, image);
151 System.Diagnostics.Trace.WriteLine($
"Could not load slide overview: {ex.Message}");
155 private RectangleD OverviewWorldBounds(BioImage image)
157 if (image.isPyramidal)
161 var extent = image.OpenSlideBase?.Schema.Extent ?? image.SlideBase?.Schema.Extent;
162 if (extent is { } schemaExtent)
163 return new RectangleD(schemaExtent.MinX, -schemaExtent.MaxY, schemaExtent.Width, schemaExtent.Height);
164 double units = image.GetUnitPerPixel(0);
165 return new RectangleD(0, 0, image.Resolutions[0].SizeX * units,
166 image.Resolutions[0].SizeY * units);
168 return new RectangleD(image.Volume.Location.X, image.Volume.Location.Y,
169 image.Volume.Width, image.Volume.Height);
172 private void OverviewPaint(
object sender, PaintEventArgs e)
174 BioImage image = OverviewSelectedImage;
175 if (
overviewImage ==
null || image ==
null || overviewSource != image)
178 RectangleD bounds = OverviewWorldBounds(image);
179 if (bounds.W <= 0 || bounds.H <= 0)
182 PointD topLeft = image.isPyramidal
183 ?
new PointD(image.PyramidalOrigin.X * image.Resolution, image.PyramidalOrigin.Y * image.Resolution)
188 double left = Math.Clamp((topLeft.X - bounds.X) / bounds.W, 0, 1);
189 double top = Math.Clamp((topLeft.Y - bounds.Y) / bounds.H, 0, 1);
190 double right = Math.Clamp((topLeft.X + viewWidth - bounds.X) / bounds.W, 0, 1);
191 double bottom = Math.Clamp((topLeft.Y + viewHeight - bounds.Y) / bounds.H, 0, 1);
192 if (right > left && bottom > top)
195 e.Graphics.DrawRectangle(pen, (
float)(left * (overview.Width - 1)),
196 (
float)(top * (overview.Height - 1)), (
float)((right - left) * (overview.Width - 1)),
197 (
float)((bottom - top) * (overview.Height - 1)));
199 e.Graphics.DrawRectangle(
Drawing.Pens.White, 0, 0, overview.Width - 1, overview.Height - 1);
202 private void NavigateOverview(
int x,
int y)
204 BioImage image = OverviewSelectedImage;
205 if (image ==
null ||
overviewImage ==
null || overviewSource != image)
208 RectangleD bounds = OverviewWorldBounds(image);
209 double targetX = bounds.X + Math.Clamp(x / (
double)overview.Width, 0, 1) * bounds.W;
210 double targetY = bounds.Y + Math.Clamp(y / (
double)overview.Height, 0, 1) * bounds.H;
211 if (image.isPyramidal)
218 Origin =
new PointD(-targetX, -targetY);
222 private MouseEventArgs OverviewCanvasEvent(MouseEventArgs e) =>
223 new MouseEventArgs(e.Button, e.Clicks, e.X + overview.X, e.Y + overview.Y, e.Delta);
225 private void OverviewMouseDown(
object sender, MouseEventArgs e)
227 if (e.Button == MouseButtons.Left)
229 overviewDragging =
true;
230 overviewPanel.Capture =
true;
231 NavigateOverview(e.X, e.Y);
233 else if (e.Button == MouseButtons.Middle)
234 pictureBox_MouseDown(sender, OverviewCanvasEvent(e));
237 private void OverviewMouseMove(
object sender, MouseEventArgs e)
239 if (overviewDragging && e.Button.HasFlag(MouseButtons.Left))
240 NavigateOverview(e.X, e.Y);
241 else if (e.Button == MouseButtons.Middle)
242 rgbPictureBox_MouseMove(sender, OverviewCanvasEvent(e));
245 private void OverviewMouseUp(
object sender, MouseEventArgs e)
247 if (e.Button == MouseButtons.Left)
249 overviewDragging =
false;
250 overviewPanel.Capture =
false;
252 else if (e.Button == MouseButtons.Middle)
253 pictureBox_MouseUp(sender, OverviewCanvasEvent(e));
AForge.RectangleD RectangleD
System.Drawing.Bitmap overviewImage
new AForge.SizeF Scale
Scale when opening image stacks.
PointD PyramidalOrigin
Setting the value of the pyramidalOrigin variable. */.
double PxWmicron
Only used when opening stacks.
double Resolution
A property of the class ImageViewer. It is a getter and setter for the resolution of the image.
PointD Origin
A property of the class PointD. */.
async void UpdateImages(bool updatePyramidal=true)
double PxHmicron
Only used when opening stacks.
int SelectedIndex
A property that is used to set the selected index of the image. */.