-
-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathThumbnailPipeline.cs
229 lines (194 loc) · 8.41 KB
/
ThumbnailPipeline.cs
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
223
224
225
226
227
228
229
namespace NetVips.Samples
{
using System;
using System.IO;
public class ThumbnailPipeline : ISample
{
public string Name => "Thumbnail (configurable pipeline)";
public string Category => "Resample";
// Source: https://storage.googleapis.com/downloads.webmproject.org/webp/images/dancing-banana.gif
public const string Filename = "images/dancing-banana.gif";
// Maximum value for a coordinate.
private const int VipsMaxCoord = 10000000;
// = 71 megapixels
private const int MaxImageSize = 71000000;
// Halt processing and raise an error when loading invalid images.
// Set this flag to Enums.FailOn.None if you'd rather apply a "best effort" to decode
// images, even if the data is corrupt or invalid.
// See: CVE-2019-6976
// https://blog.silentsignal.eu/2019/04/18/drop-by-drop-bleeding-through-libvips/
private const Enums.FailOn FailOn = Enums.FailOn.Error;
// The name libvips uses to attach an ICC profile.
private const string VipsMetaIccName = "icc-profile-data";
/// <summary>
/// Does this loader support multiple pages?
/// </summary>
/// <param name="loader">The name of the load operation.</param>
/// <returns>A bool indicating if this loader support multiple pages.</returns>
public bool LoaderSupportPage(string loader)
{
return loader.StartsWith("VipsForeignLoadPdf") ||
loader.StartsWith("VipsForeignLoadGif") ||
loader.StartsWith("VipsForeignLoadTiff") ||
loader.StartsWith("VipsForeignLoadWebp") ||
loader.StartsWith("VipsForeignLoadHeif") ||
loader.StartsWith("VipsForeignLoadMagick");
}
#pragma warning disable CS0162 // Unreachable code detected
public void Execute(string[] args)
{
// If you set a number to zero (0), it will resize on the other specified axis.
var width = 200;
var height = 0;
// Enums.Size.Both - for both up and down.
// Enums.Size.Up - only upsize.
// Enums.Size.Down - only downsize.
// Enums.Size.Force - force size, that is, break aspect ratio.
const Enums.Size size = Enums.Size.Both;
// Just for example.
var buffer = File.ReadAllBytes(Filename);
// Find the name of the load operation vips will use to load a buffer
// so that we can work out what options to pass to NewFromBuffer().
var loader = Image.FindLoadBuffer(buffer);
if (loader == null)
{
// No known loader is found, stop further processing.
throw new Exception("Invalid or unsupported image format. Is it a valid image?");
}
var loadOptions = new VOption
{
{"access", Enums.Access.Sequential},
{"fail_on", FailOn}
};
var stringOptions = "";
if (LoaderSupportPage(loader))
{
// -1 means "until the end of the document", handy for animated images.
loadOptions.Add("n", -1);
stringOptions = "[n=-1]";
}
int inputWidth;
int inputHeight;
int pageHeight;
bool isCmyk;
bool isLabs;
bool embeddedProfile;
Image image = null;
try
{
image = (Image)Operation.Call(loader, loadOptions, buffer);
// Or:
// image = Image.NewFromBuffer(buffer, kwargs: loadOptions);
// (but the loader is already found, so the above will be a little faster).
inputWidth = image.Width;
inputHeight = image.Height;
// Use 64-bit unsigned type, to handle PNG decompression bombs.
if ((ulong)(inputWidth * inputHeight) > MaxImageSize)
{
throw new Exception(
"Image is too large for processing. Width x height should be less than 71 megapixels.");
}
pageHeight = image.PageHeight;
isCmyk = image.Interpretation == Enums.Interpretation.Cmyk;
isLabs = image.Interpretation == Enums.Interpretation.Labs;
embeddedProfile = image.Contains(VipsMetaIccName);
}
catch (VipsException e)
{
throw new Exception("Image has a corrupt header.", e);
}
finally
{
// We're done with the image; dispose early
image?.Dispose();
}
string importProfile = null;
string exportProfile = null;
Enums.Intent? intent = null;
// Ensure we're using a device-independent color space
if ((embeddedProfile || isCmyk) && !isLabs)
{
// Embedded profile; fallback in case the profile embedded in the image
// is broken. No embedded profile; import using default CMYK profile.
importProfile = isCmyk ? "cmyk" : "srgb";
// Convert to sRGB using embedded or import profile.
exportProfile = "srgb";
// Use "perceptual" intent to better match imagemagick.
intent = Enums.Intent.Perceptual;
}
// Scaling calculations
var thumbnailWidth = width;
var thumbnailHeight = height;
if (width > 0 && height > 0) // Fixed width and height
{
var xFactor = (double)inputWidth / width;
var yFactor = (double)pageHeight / height;
if (xFactor > yFactor) // Or: if (xFactor < yFactor)
{
thumbnailHeight = (int)Math.Round(pageHeight / xFactor);
}
else
{
thumbnailWidth = (int)Math.Round(inputWidth / yFactor);
}
}
else if (width > 0) // Fixed width
{
if (size == Enums.Size.Force)
{
thumbnailHeight = pageHeight;
height = pageHeight;
}
else
{
// Auto height
var yFactor = (double)inputWidth / width;
height = (int)Math.Round(pageHeight / yFactor);
// Height is missing, replace with a huuuge value to prevent
// reduction or enlargement in that axis
thumbnailHeight = VipsMaxCoord;
}
}
else if (height > 0) // Fixed height
{
if (size == Enums.Size.Force)
{
thumbnailWidth = inputWidth;
width = inputWidth;
}
else
{
// Auto width
var xFactor = (double)pageHeight / height;
width = (int)Math.Round(inputWidth / xFactor);
// Width is missing, replace with a huuuge value to prevent
// reduction or enlargement in that axis
thumbnailWidth = VipsMaxCoord;
}
}
else // Identity transform
{
thumbnailWidth = inputWidth;
width = inputWidth;
thumbnailHeight = pageHeight;
height = pageHeight;
}
// Note: don't use "image.ThumbnailImage". Otherwise, none of the very fast
// shrink-on-load tricks are possible. This can make thumbnailing of large
// images extremely slow.
using var thumb = Image.ThumbnailBuffer(buffer, thumbnailWidth, stringOptions, thumbnailHeight, size,
false, importProfile: importProfile, exportProfile: exportProfile, intent: intent);
thumb.WriteToFile("thumbnail.webp", new VOption
{
{"strip", true}
});
// Or:
/*buffer = thumb.WriteToBuffer(".webp", new VOption
{
{"strip", true}
});*/
Console.WriteLine("See thumbnail.webp");
}
}
#pragma warning restore CS0162 // Unreachable code detected
}