-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathUtilities.cs
More file actions
222 lines (208 loc) · 10 KB
/
Utilities.cs
File metadata and controls
222 lines (208 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
using BruTile;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
namespace OpenSlideSharp.BruTile
{
public class ImageUtil
{
/// <summary>
/// BGR/BGRA convert to jpeg
/// </summary>
/// <param name="bgraBytes">BGR/BGRA</param>
/// <param name="bytesPerPixel">bytes per pixel</param>
/// <param name="bytesPerLine">bytes per line</param>
/// <param name="width">width</param>
/// <param name="height">height</param>
/// <param name="dstWidth">dst width</param>
/// <param name="dstHeight">dst height</param>
/// <param name="quality">jpeg quality</param>
/// <param name="background">background color for transparent if <paramref name="bytesPerPixel"/> == 4</param>
/// <returns></returns>
public static unsafe byte[] GetJpeg(byte[] bgraBytes, int bytesPerPixel, int bytesPerLine, int width, int height, int dstWidth = 0, int dstHeight = 0, int? quality = null, uint background = 0xFFFFFFFF)
{
if (bgraBytes == null) return null;
if (bytesPerPixel != 3 && bytesPerPixel != 4) throw new ArgumentException(nameof(bytesPerPixel));
var prms = quality != null ? new int[] { (int)ImwriteFlags.JpegQuality, quality.Value } : null;
var pixel = MatType.CV_8UC(bytesPerPixel);
fixed (byte* scan0 = bgraBytes)
{
using (var src = new Mat(height, width, pixel, (IntPtr)scan0, bytesPerLine))
{
// black transparent to background
if (pixel.Channels == 4)
{
unchecked
{
src.ForEachAsInt32((_i, _p) =>
{
if ((*_i & 0xFF000000) != 0xFF000000) *_i = (Int32)(background); // alpha is not 0xff
});
}
}
if ((dstWidth <= 0 && dstHeight <= 0) || (dstWidth == width && dstHeight == height))
{
return src.ToBytes(".jpg", prms);
}
else // fill
{
var scalar = new Scalar((int)(background >> 24 & 0xFF), (int)(background >> 16 & 0xFF), (int)(background >> 8 & 0xFF), (int)(background & 0xFF));
using (var dst = new Mat(dstHeight, dstWidth, pixel, scalar))
{
DrawImage(src, dst);
return dst.ToBytes(".jpg", prms);
}
}
}
}
}
/// <summary>
/// Fill the source image with adaptive scaling to the target image
/// </summary>
/// <param name="src"></param>
/// <param name="dst"></param>
private static void DrawImage(Mat src, Mat dst)
{
var fx = (double)dst.Width / src.Width;
var fy = (double)dst.Height / src.Height;
var fmin = Math.Min(fx, fy);
if (fmin < 1) // src > dst
{
using (var srcResized = src.Resize(new Size(src.Width * fmin, src.Height * fmin)))
{
using (var sub = new Mat(dst, new Rect(0, 0, srcResized.Width, srcResized.Height)))
{
srcResized.CopyTo(sub);
}
}
}
else // src <= dst
{
using (var sub = new Mat(dst, new Rect(0, 0, src.Width, src.Height)))
{
src.CopyTo(sub);
}
}
}
/// <summary>
/// Join by <paramref name="srcPixelTiles"/> and cut by <paramref name="srcPixelExtent"/> then scale to <paramref name="dstPixelExtent"/>(only height an width is useful).
/// </summary>
/// <param name="srcPixelTiles">tile with tile extent collection</param>
/// <param name="srcPixelExtent">canvas extent</param>
/// <param name="dstPixelExtent">jpeg output size</param>
/// <param name="dstQuality">jpeg output quality</param>
/// <param name="backgroundBGRA">background hex code,default is white</param>
/// <returns></returns>
public static byte[] Join(IEnumerable<Tuple<Extent, Mat>> srcPixelTiles, Extent srcPixelExtent, Extent dstPixelExtent, int? dstQuality = 85, uint backgroundBGRA = 0xFFFFFFFF)
{
if (srcPixelTiles == null || !srcPixelTiles.Any()) return null;
try
{
srcPixelExtent = srcPixelExtent.ToIntegerExtent();
dstPixelExtent = dstPixelExtent.ToIntegerExtent();
var canvasWidth = (int)srcPixelExtent.Width;
var canvasHeight = (int)srcPixelExtent.Height;
var dstWidth = (int)dstPixelExtent.Width;
var dstHeight = (int)dstPixelExtent.Height;
var bytesPerPixel = 3;
var pixelFormat = MatType.CV_8UC(bytesPerPixel);
using (var canvas = new Mat(canvasHeight, canvasWidth, pixelFormat, new Scalar((int)(backgroundBGRA >> 24 & 0xFF), (int)(backgroundBGRA >> 16 & 0xFF), (int)(backgroundBGRA >> 8 & 0xFF), (int)(backgroundBGRA & 0xFF))))
{
foreach (var tile in srcPixelTiles)
{
var tileExtent = tile.Item1.ToIntegerExtent();
var tileRawData = tile.Item2;
var intersect = srcPixelExtent.Intersect(tileExtent);
var tileOffsetPixelX = (int)(intersect.MinX - tileExtent.MinX);
var tileOffsetPixelY = (int)(intersect.MinY - tileExtent.MinY);
var canvasOffsetPixelX = (int)(intersect.MinX - srcPixelExtent.MinX);
var canvasOffsetPixelY = (int)(intersect.MinY - srcPixelExtent.MinY);
using (var tileMat = new Mat((int)tileExtent.Height, (int)tileExtent.Width, pixelFormat, tileRawData.Data, tileRawData.Step()))
{
var tileRegion = new Mat(tileMat, new Rect(tileOffsetPixelX, tileOffsetPixelY, (int)intersect.Width, (int)intersect.Height));
var canvasRegion = new Mat(canvas, new Rect(canvasOffsetPixelX, canvasOffsetPixelY, (int)intersect.Width, (int)intersect.Height));
tileRegion.CopyTo(canvasRegion);
}
}
var prms = dstQuality != null ? new int[] { (int)ImwriteFlags.JpegQuality, dstQuality.Value } : null;
if (dstWidth != canvasWidth || dstHeight != canvasHeight)
{
using (var output = new Mat())
{
Cv2.Resize(canvas, output, new OpenCvSharp.Size(dstWidth, dstHeight));
Cv2.ImEncode(".jpg", output, out var buf0, prms);
return buf0;
}
}
Cv2.ImEncode(".jpg", canvas, out var buf1, prms);
return buf1;
}
}
catch (Exception)
{
return null;
}
}
}
public class TileUtil
{
/// <summary>
/// To ensure image quality, try to use high-resolution level downsampling to low-resolution level
/// </summary>
/// <param name="resolutions"></param>
/// <param name="unitsPerPixel"></param>
/// <param name="sampleMode"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="Exception"></exception>
public static int GetLevel(IDictionary<int, Resolution> resolutions, double unitsPerPixel, SampleMode sampleMode = SampleMode.Nearest)
{
if (resolutions.Count == 0)
{
throw new ArgumentException("No tile resolutions");
}
IOrderedEnumerable<KeyValuePair<int, Resolution>> orderedEnumerable = resolutions.OrderByDescending((KeyValuePair<int, Resolution> r) => r.Value.UnitsPerPixel);
if (orderedEnumerable.Last().Value.UnitsPerPixel > unitsPerPixel)
{
return orderedEnumerable.Last().Key;
}
if (orderedEnumerable.First().Value.UnitsPerPixel < unitsPerPixel)
{
return orderedEnumerable.First().Key;
}
switch (sampleMode)
{
case SampleMode.Nearest:
{
int id = -1;
double num = double.MaxValue;
foreach (KeyValuePair<int, Resolution> item in orderedEnumerable)
{
double num2 = Math.Abs(item.Value.UnitsPerPixel - unitsPerPixel);
if (num2 < num)
{
id = item.Key;
num = num2;
}
}
if (id == -1)
{
throw new Exception("Unexpected error when calculating nearest level");
}
return id;
}
case SampleMode.NearestUp:
return orderedEnumerable.Last(_ => _.Value.UnitsPerPixel >= unitsPerPixel).Key;
case SampleMode.NearestDwon:
return orderedEnumerable.First(_ => _.Value.UnitsPerPixel <= unitsPerPixel).Key;
case SampleMode.Top:
return orderedEnumerable.First().Key;
case SampleMode.Bottom:
return orderedEnumerable.Last().Key;
default:
throw new Exception($"Unexpected error {nameof(sampleMode)}");
}
}
}
}