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