1 |
|
2 |
|
3 |
|
4 | #include <algorithm>
|
5 | #include <functional>
|
6 | #include <memory>
|
7 | #include <tuple>
|
8 | #include <vector>
|
9 | #include <vips/vips8>
|
10 |
|
11 | #include "common.h"
|
12 | #include "operations.h"
|
13 |
|
14 | using vips::VImage;
|
15 | using vips::VError;
|
16 |
|
17 | namespace sharp {
|
18 | |
19 |
|
20 |
|
21 | VImage Tint(VImage image, double const a, double const b) {
|
22 |
|
23 | VipsInterpretation typeBeforeTint = image.interpretation();
|
24 | if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
25 | typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
26 | }
|
27 |
|
28 | VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
|
29 |
|
30 | std::vector<double> chroma {a, b};
|
31 | VImage tinted = luminance
|
32 | .bandjoin(chroma)
|
33 | .copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LAB))
|
34 | .colourspace(typeBeforeTint);
|
35 |
|
36 | if (HasAlpha(image)) {
|
37 |
|
38 | VImage alpha = image[image.bands() - 1];
|
39 |
|
40 | tinted = tinted.bandjoin(alpha);
|
41 | }
|
42 | return tinted;
|
43 | }
|
44 |
|
45 | |
46 |
|
47 |
|
48 | VImage Normalise(VImage image, int const lower, int const upper) {
|
49 |
|
50 | VipsInterpretation typeBeforeNormalize = image.interpretation();
|
51 | if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
|
52 | typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
|
53 | }
|
54 |
|
55 | VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
|
56 |
|
57 | VImage luminance = lab[0];
|
58 |
|
59 |
|
60 | int const min = lower == 0 ? luminance.min() : luminance.percent(lower);
|
61 | int const max = upper == 100 ? luminance.max() : luminance.percent(upper);
|
62 |
|
63 | if (std::abs(max - min) > 1) {
|
64 |
|
65 | VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
|
66 |
|
67 | double f = 100.0 / (max - min);
|
68 | double a = -(min * f);
|
69 |
|
70 | VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
|
71 |
|
72 | if (HasAlpha(image)) {
|
73 |
|
74 | VImage alpha = image[image.bands() - 1];
|
75 |
|
76 | return normalized.bandjoin(alpha);
|
77 | } else {
|
78 | return normalized;
|
79 | }
|
80 | }
|
81 | return image;
|
82 | }
|
83 |
|
84 | |
85 |
|
86 |
|
87 | VImage Clahe(VImage image, int const width, int const height, int const maxSlope) {
|
88 | return image.hist_local(width, height, VImage::option()->set("max_slope", maxSlope));
|
89 | }
|
90 |
|
91 | |
92 |
|
93 |
|
94 | VImage Gamma(VImage image, double const exponent) {
|
95 | if (HasAlpha(image)) {
|
96 |
|
97 | VImage alpha = image[image.bands() - 1];
|
98 | return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
|
99 | } else {
|
100 | return image.gamma(VImage::option()->set("exponent", exponent));
|
101 | }
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 | VImage Flatten(VImage image, std::vector<double> flattenBackground) {
|
108 | double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
109 | std::vector<double> background {
|
110 | flattenBackground[0] * multiplier,
|
111 | flattenBackground[1] * multiplier,
|
112 | flattenBackground[2] * multiplier
|
113 | };
|
114 | return image.flatten(VImage::option()->set("background", background));
|
115 | }
|
116 |
|
117 | |
118 |
|
119 |
|
120 | VImage Negate(VImage image, bool const negateAlpha) {
|
121 | if (HasAlpha(image) && !negateAlpha) {
|
122 |
|
123 | VImage alpha = image[image.bands() - 1];
|
124 | return RemoveAlpha(image).invert().bandjoin(alpha);
|
125 | } else {
|
126 | return image.invert();
|
127 | }
|
128 | }
|
129 |
|
130 | |
131 |
|
132 |
|
133 | VImage Blur(VImage image, double const sigma) {
|
134 | if (sigma == -1.0) {
|
135 |
|
136 | VImage blur = VImage::new_matrixv(3, 3,
|
137 | 1.0, 1.0, 1.0,
|
138 | 1.0, 1.0, 1.0,
|
139 | 1.0, 1.0, 1.0);
|
140 | blur.set("scale", 9.0);
|
141 | return image.conv(blur);
|
142 | } else {
|
143 |
|
144 | return image.gaussblur(sigma);
|
145 | }
|
146 | }
|
147 |
|
148 | |
149 |
|
150 |
|
151 | VImage Convolve(VImage image, int const width, int const height,
|
152 | double const scale, double const offset,
|
153 | std::unique_ptr<double[]> const &kernel_v
|
154 | ) {
|
155 | VImage kernel = VImage::new_from_memory(
|
156 | kernel_v.get(),
|
157 | width * height * sizeof(double),
|
158 | width,
|
159 | height,
|
160 | 1,
|
161 | VIPS_FORMAT_DOUBLE);
|
162 | kernel.set("scale", scale);
|
163 | kernel.set("offset", offset);
|
164 |
|
165 | return image.conv(kernel);
|
166 | }
|
167 |
|
168 | |
169 |
|
170 |
|
171 |
|
172 | VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
|
173 | double *m = matrix.get();
|
174 | image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
175 | return image
|
176 | .recomb(image.bands() == 3
|
177 | ? VImage::new_from_memory(
|
178 | m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
|
179 | )
|
180 | : VImage::new_matrixv(4, 4,
|
181 | m[0], m[1], m[2], 0.0,
|
182 | m[3], m[4], m[5], 0.0,
|
183 | m[6], m[7], m[8], 0.0,
|
184 | 0.0, 0.0, 0.0, 1.0));
|
185 | }
|
186 |
|
187 | VImage Modulate(VImage image, double const brightness, double const saturation,
|
188 | int const hue, double const lightness) {
|
189 | VipsInterpretation colourspaceBeforeModulate = image.interpretation();
|
190 | if (HasAlpha(image)) {
|
191 |
|
192 | VImage alpha = image[image.bands() - 1];
|
193 | return RemoveAlpha(image)
|
194 | .colourspace(VIPS_INTERPRETATION_LCH)
|
195 | .linear(
|
196 | { brightness, saturation, 1},
|
197 | { lightness, 0.0, static_cast<double>(hue) }
|
198 | )
|
199 | .colourspace(colourspaceBeforeModulate)
|
200 | .bandjoin(alpha);
|
201 | } else {
|
202 | return image
|
203 | .colourspace(VIPS_INTERPRETATION_LCH)
|
204 | .linear(
|
205 | { brightness, saturation, 1 },
|
206 | { lightness, 0.0, static_cast<double>(hue) }
|
207 | )
|
208 | .colourspace(colourspaceBeforeModulate);
|
209 | }
|
210 | }
|
211 |
|
212 | |
213 |
|
214 |
|
215 | VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
|
216 | double const x1, double const y2, double const y3) {
|
217 | if (sigma == -1.0) {
|
218 |
|
219 | VImage sharpen = VImage::new_matrixv(3, 3,
|
220 | -1.0, -1.0, -1.0,
|
221 | -1.0, 32.0, -1.0,
|
222 | -1.0, -1.0, -1.0);
|
223 | sharpen.set("scale", 24.0);
|
224 | return image.conv(sharpen);
|
225 | } else {
|
226 |
|
227 | VipsInterpretation colourspaceBeforeSharpen = image.interpretation();
|
228 | if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
|
229 | colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
|
230 | }
|
231 | return image
|
232 | .sharpen(VImage::option()
|
233 | ->set("sigma", sigma)
|
234 | ->set("m1", m1)
|
235 | ->set("m2", m2)
|
236 | ->set("x1", x1)
|
237 | ->set("y2", y2)
|
238 | ->set("y3", y3))
|
239 | .colourspace(colourspaceBeforeSharpen);
|
240 | }
|
241 | }
|
242 |
|
243 | VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
|
244 | if (!thresholdGrayscale) {
|
245 | return image >= threshold;
|
246 | }
|
247 | return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
|
248 | }
|
249 |
|
250 | |
251 |
|
252 |
|
253 | VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
|
254 | image = image.bandbool(boolean);
|
255 | return image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
|
256 | }
|
257 |
|
258 | |
259 |
|
260 |
|
261 | VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean) {
|
262 | return image.boolean(imageR, boolean);
|
263 | }
|
264 |
|
265 | |
266 |
|
267 |
|
268 | VImage Trim(VImage image, std::vector<double> background, double threshold) {
|
269 | if (image.width() < 3 && image.height() < 3) {
|
270 | throw VError("Image to trim must be at least 3x3 pixels");
|
271 | }
|
272 | if (background.size() == 0) {
|
273 |
|
274 | background = image.extract_area(0, 0, 1, 1)(0, 0);
|
275 | } else if (sharp::Is16Bit(image.interpretation())) {
|
276 | for (size_t i = 0; i < background.size(); i++) {
|
277 | background[i] *= 256.0;
|
278 | }
|
279 | threshold *= 256.0;
|
280 | }
|
281 | std::vector<double> backgroundAlpha({ background.back() });
|
282 | if (HasAlpha(image)) {
|
283 | background.pop_back();
|
284 | } else {
|
285 | background.resize(image.bands());
|
286 | }
|
287 | int left, top, width, height;
|
288 | left = image.find_trim(&top, &width, &height, VImage::option()
|
289 | ->set("background", background)
|
290 | ->set("threshold", threshold));
|
291 | if (HasAlpha(image)) {
|
292 |
|
293 | int leftA, topA, widthA, heightA;
|
294 | VImage alpha = image[image.bands() - 1];
|
295 | leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
|
296 | ->set("background", backgroundAlpha)
|
297 | ->set("threshold", threshold));
|
298 | if (widthA > 0 && heightA > 0) {
|
299 | if (width > 0 && height > 0) {
|
300 |
|
301 | int const leftB = std::min(left, leftA);
|
302 | int const topB = std::min(top, topA);
|
303 | int const widthB = std::max(left + width, leftA + widthA) - leftB;
|
304 | int const heightB = std::max(top + height, topA + heightA) - topB;
|
305 | return image.extract_area(leftB, topB, widthB, heightB);
|
306 | } else {
|
307 |
|
308 | return image.extract_area(leftA, topA, widthA, heightA);
|
309 | }
|
310 | }
|
311 | }
|
312 | if (width > 0 && height > 0) {
|
313 | return image.extract_area(left, top, width, height);
|
314 | }
|
315 | return image;
|
316 | }
|
317 |
|
318 | |
319 |
|
320 |
|
321 | VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
|
322 | size_t const bands = static_cast<size_t>(image.bands());
|
323 | if (a.size() > bands) {
|
324 | throw VError("Band expansion using linear is unsupported");
|
325 | }
|
326 | bool const uchar = !Is16Bit(image.interpretation());
|
327 | if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
|
328 |
|
329 | VImage alpha = image[bands - 1];
|
330 | return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
|
331 | } else {
|
332 | return image.linear(a, b, VImage::option()->set("uchar", uchar));
|
333 | }
|
334 | }
|
335 |
|
336 | |
337 |
|
338 |
|
339 | VImage Unflatten(VImage image) {
|
340 | if (HasAlpha(image)) {
|
341 | VImage alpha = image[image.bands() - 1];
|
342 | VImage noAlpha = RemoveAlpha(image);
|
343 | return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
|
344 | } else {
|
345 | return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
|
346 | }
|
347 | }
|
348 |
|
349 | |
350 |
|
351 |
|
352 | VImage EnsureColourspace(VImage image, VipsInterpretation colourspace) {
|
353 | if (colourspace != VIPS_INTERPRETATION_LAST && image.interpretation() != colourspace) {
|
354 | image = image.colourspace(colourspace,
|
355 | VImage::option()->set("source_space", image.interpretation()));
|
356 | }
|
357 | return image;
|
358 | }
|
359 |
|
360 | |
361 |
|
362 |
|
363 | VImage CropMultiPage(VImage image, int left, int top, int width, int height,
|
364 | int nPages, int *pageHeight) {
|
365 | if (top == 0 && height == *pageHeight) {
|
366 |
|
367 | return image.extract_area(left, 0, width, image.height());
|
368 | } else {
|
369 | std::vector<VImage> pages;
|
370 | pages.reserve(nPages);
|
371 |
|
372 |
|
373 | for (int i = 0; i < nPages; i++) {
|
374 | pages.push_back(
|
375 | image.extract_area(left, *pageHeight * i + top, width, height));
|
376 | }
|
377 |
|
378 |
|
379 | VImage assembled = VImage::arrayjoin(pages,
|
380 | VImage::option()->set("across", 1));
|
381 |
|
382 |
|
383 | *pageHeight = height;
|
384 |
|
385 | return assembled;
|
386 | }
|
387 | }
|
388 |
|
389 | |
390 |
|
391 |
|
392 | VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
|
393 | VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight) {
|
394 | if (top == 0 && height == *pageHeight) {
|
395 |
|
396 | return image.embed(left, 0, width, image.height(), VImage::option()
|
397 | ->set("extend", extendWith)
|
398 | ->set("background", background));
|
399 | } else if (left == 0 && width == image.width()) {
|
400 |
|
401 | std::vector<VImage> pages;
|
402 | pages.reserve(nPages);
|
403 |
|
404 |
|
405 | image = image.grid(*pageHeight, nPages, 1);
|
406 |
|
407 |
|
408 | image = image.embed(0, top, image.width(), height, VImage::option()
|
409 | ->set("extend", extendWith)
|
410 | ->set("background", background));
|
411 |
|
412 |
|
413 | for (int i = 0; i < nPages; i++) {
|
414 | pages.push_back(
|
415 | image.extract_area(width * i, 0, width, height));
|
416 | }
|
417 |
|
418 |
|
419 | VImage assembled = VImage::arrayjoin(pages,
|
420 | VImage::option()->set("across", 1));
|
421 |
|
422 |
|
423 | *pageHeight = height;
|
424 |
|
425 | return assembled;
|
426 | } else {
|
427 | std::vector<VImage> pages;
|
428 | pages.reserve(nPages);
|
429 |
|
430 |
|
431 | for (int i = 0; i < nPages; i++) {
|
432 | pages.push_back(
|
433 | image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
|
434 | }
|
435 |
|
436 |
|
437 | for (int i = 0; i < nPages; i++) {
|
438 | pages[i] = pages[i].embed(left, top, width, height, VImage::option()
|
439 | ->set("extend", extendWith)
|
440 | ->set("background", background));
|
441 | }
|
442 |
|
443 |
|
444 | VImage assembled = VImage::arrayjoin(pages,
|
445 | VImage::option()->set("across", 1));
|
446 |
|
447 |
|
448 | *pageHeight = height;
|
449 |
|
450 | return assembled;
|
451 | }
|
452 | }
|
453 |
|
454 | }
|