@revizly/sharp 0.33.2-revizly3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/metadata.h ADDED
@@ -0,0 +1,82 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ #ifndef SRC_METADATA_H_
5
+ #define SRC_METADATA_H_
6
+
7
+ #include <string>
8
+ #include <napi.h>
9
+
10
+ #include "./common.h"
11
+
12
+ struct MetadataBaton {
13
+ // Input
14
+ sharp::InputDescriptor *input;
15
+ // Output
16
+ std::string format;
17
+ int width;
18
+ int height;
19
+ std::string space;
20
+ int channels;
21
+ std::string depth;
22
+ int density;
23
+ std::string chromaSubsampling;
24
+ bool isProgressive;
25
+ int paletteBitDepth;
26
+ int pages;
27
+ int pageHeight;
28
+ int loop;
29
+ std::vector<int> delay;
30
+ int pagePrimary;
31
+ std::string compression;
32
+ std::string resolutionUnit;
33
+ std::string formatMagick;
34
+ std::vector<std::pair<int, int>> levels;
35
+ int subifds;
36
+ std::vector<double> background;
37
+ bool hasProfile;
38
+ bool hasAlpha;
39
+ int orientation;
40
+ char *exif;
41
+ size_t exifLength;
42
+ char *icc;
43
+ size_t iccLength;
44
+ char *iptc;
45
+ size_t iptcLength;
46
+ char *xmp;
47
+ size_t xmpLength;
48
+ char *tifftagPhotoshop;
49
+ size_t tifftagPhotoshopLength;
50
+ std::string err;
51
+
52
+ MetadataBaton():
53
+ input(nullptr),
54
+ width(0),
55
+ height(0),
56
+ channels(0),
57
+ density(0),
58
+ isProgressive(false),
59
+ paletteBitDepth(0),
60
+ pages(0),
61
+ pageHeight(0),
62
+ loop(-1),
63
+ pagePrimary(-1),
64
+ subifds(0),
65
+ hasProfile(false),
66
+ hasAlpha(false),
67
+ orientation(0),
68
+ exif(nullptr),
69
+ exifLength(0),
70
+ icc(nullptr),
71
+ iccLength(0),
72
+ iptc(nullptr),
73
+ iptcLength(0),
74
+ xmp(nullptr),
75
+ xmpLength(0),
76
+ tifftagPhotoshop(nullptr),
77
+ tifftagPhotoshopLength(0) {}
78
+ };
79
+
80
+ Napi::Value metadata(const Napi::CallbackInfo& info);
81
+
82
+ #endif // SRC_METADATA_H_
@@ -0,0 +1,471 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
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
+ * Tint an image using the provided RGB.
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
+ // LAB identity function
26
+ VImage identityLab = VImage::identity(VImage::option()->set("bands", 3))
27
+ .colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
28
+ // Scale luminance range, 0.0 to 1.0
29
+ VImage l = identityLab[0] / 100;
30
+ // Weighting functions
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
+ // Convert lookup table to sRGB
35
+ VImage lut = identityLab.colourspace(VIPS_INTERPRETATION_sRGB,
36
+ VImage::option()->set("source_space", VIPS_INTERPRETATION_LAB));
37
+ // Original colourspace
38
+ VipsInterpretation typeBeforeTint = image.interpretation();
39
+ if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
40
+ typeBeforeTint = VIPS_INTERPRETATION_sRGB;
41
+ }
42
+ // Apply lookup table
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
+ * Stretch luminance to cover full dynamic range.
61
+ */
62
+ VImage Normalise(VImage image, int const lower, int const upper) {
63
+ // Get original colourspace
64
+ VipsInterpretation typeBeforeNormalize = image.interpretation();
65
+ if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
66
+ typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
67
+ }
68
+ // Convert to LAB colourspace
69
+ VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
70
+ // Extract luminance
71
+ VImage luminance = lab[0];
72
+
73
+ // Find luminance range
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
+ // Extract chroma
79
+ VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
80
+ // Calculate multiplication factor and addition
81
+ double f = 100.0 / (max - min);
82
+ double a = -(min * f);
83
+ // Scale luminance, join to chroma, convert back to original colourspace
84
+ VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
85
+ // Attach original alpha channel, if any
86
+ if (HasAlpha(image)) {
87
+ // Extract original alpha channel
88
+ VImage alpha = image[image.bands() - 1];
89
+ // Join alpha channel to normalised image
90
+ return normalized.bandjoin(alpha);
91
+ } else {
92
+ return normalized;
93
+ }
94
+ }
95
+ return image;
96
+ }
97
+
98
+ /*
99
+ * Contrast limiting adapative histogram equalization (CLAHE)
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
+ * Gamma encoding/decoding
107
+ */
108
+ VImage Gamma(VImage image, double const exponent) {
109
+ if (HasAlpha(image)) {
110
+ // Separate alpha channel
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
+ * Flatten image to remove alpha channel
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
+ * Produce the "negative" of the image.
133
+ */
134
+ VImage Negate(VImage image, bool const negateAlpha) {
135
+ if (HasAlpha(image) && !negateAlpha) {
136
+ // Separate alpha channel
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
+ * Gaussian blur. Use sigma of -1.0 for fast blur.
146
+ */
147
+ VImage Blur(VImage image, double const sigma) {
148
+ if (sigma == -1.0) {
149
+ // Fast, mild blur - averages neighbouring pixels
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
+ // Slower, accurate Gaussian blur
158
+ return StaySequential(image, VIPS_ACCESS_SEQUENTIAL).gaussblur(sigma);
159
+ }
160
+ }
161
+
162
+ /*
163
+ * Convolution with a kernel.
164
+ */
165
+ VImage Convolve(VImage image, int const width, int const height,
166
+ double const scale, double const offset,
167
+ std::unique_ptr<double[]> const &kernel_v
168
+ ) {
169
+ VImage kernel = VImage::new_from_memory(
170
+ kernel_v.get(),
171
+ width * height * sizeof(double),
172
+ width,
173
+ height,
174
+ 1,
175
+ VIPS_FORMAT_DOUBLE);
176
+ kernel.set("scale", scale);
177
+ kernel.set("offset", offset);
178
+
179
+ return image.conv(kernel);
180
+ }
181
+
182
+ /*
183
+ * Recomb with a Matrix of the given bands/channel size.
184
+ * Eg. RGB will be a 3x3 matrix.
185
+ */
186
+ VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
187
+ double *m = matrix.get();
188
+ image = image.colourspace(VIPS_INTERPRETATION_sRGB);
189
+ return image
190
+ .recomb(image.bands() == 3
191
+ ? VImage::new_from_memory(
192
+ m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
193
+ )
194
+ : VImage::new_matrixv(4, 4,
195
+ m[0], m[1], m[2], 0.0,
196
+ m[3], m[4], m[5], 0.0,
197
+ m[6], m[7], m[8], 0.0,
198
+ 0.0, 0.0, 0.0, 1.0));
199
+ }
200
+
201
+ VImage Modulate(VImage image, double const brightness, double const saturation,
202
+ int const hue, double const lightness) {
203
+ VipsInterpretation colourspaceBeforeModulate = image.interpretation();
204
+ if (HasAlpha(image)) {
205
+ // Separate alpha channel
206
+ VImage alpha = image[image.bands() - 1];
207
+ return RemoveAlpha(image)
208
+ .colourspace(VIPS_INTERPRETATION_LCH)
209
+ .linear(
210
+ { brightness, saturation, 1},
211
+ { lightness, 0.0, static_cast<double>(hue) }
212
+ )
213
+ .colourspace(colourspaceBeforeModulate)
214
+ .bandjoin(alpha);
215
+ } else {
216
+ return image
217
+ .colourspace(VIPS_INTERPRETATION_LCH)
218
+ .linear(
219
+ { brightness, saturation, 1 },
220
+ { lightness, 0.0, static_cast<double>(hue) }
221
+ )
222
+ .colourspace(colourspaceBeforeModulate);
223
+ }
224
+ }
225
+
226
+ /*
227
+ * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
228
+ */
229
+ VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
230
+ double const x1, double const y2, double const y3) {
231
+ if (sigma == -1.0) {
232
+ // Fast, mild sharpen
233
+ VImage sharpen = VImage::new_matrixv(3, 3,
234
+ -1.0, -1.0, -1.0,
235
+ -1.0, 32.0, -1.0,
236
+ -1.0, -1.0, -1.0);
237
+ sharpen.set("scale", 24.0);
238
+ return image.conv(sharpen);
239
+ } else {
240
+ // Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
241
+ VipsInterpretation colourspaceBeforeSharpen = image.interpretation();
242
+ if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
243
+ colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
244
+ }
245
+ return image
246
+ .sharpen(VImage::option()
247
+ ->set("sigma", sigma)
248
+ ->set("m1", m1)
249
+ ->set("m2", m2)
250
+ ->set("x1", x1)
251
+ ->set("y2", y2)
252
+ ->set("y3", y3))
253
+ .colourspace(colourspaceBeforeSharpen);
254
+ }
255
+ }
256
+
257
+ VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
258
+ if (!thresholdGrayscale) {
259
+ return image >= threshold;
260
+ }
261
+ return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
262
+ }
263
+
264
+ /*
265
+ Perform boolean/bitwise operation on image color channels - results in one channel image
266
+ */
267
+ VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
268
+ image = image.bandbool(boolean);
269
+ return image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
270
+ }
271
+
272
+ /*
273
+ Perform bitwise boolean operation between images
274
+ */
275
+ VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean) {
276
+ return image.boolean(imageR, boolean);
277
+ }
278
+
279
+ /*
280
+ Trim an image
281
+ */
282
+ VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt) {
283
+ if (image.width() < 3 && image.height() < 3) {
284
+ throw VError("Image to trim must be at least 3x3 pixels");
285
+ }
286
+ if (background.size() == 0) {
287
+ // Top-left pixel provides the default background colour if none is given
288
+ background = image.extract_area(0, 0, 1, 1)(0, 0);
289
+ } else if (sharp::Is16Bit(image.interpretation())) {
290
+ for (size_t i = 0; i < background.size(); i++) {
291
+ background[i] *= 256.0;
292
+ }
293
+ threshold *= 256.0;
294
+ }
295
+ std::vector<double> backgroundAlpha({ background.back() });
296
+ if (HasAlpha(image)) {
297
+ background.pop_back();
298
+ } else {
299
+ background.resize(image.bands());
300
+ }
301
+ int left, top, width, height;
302
+ left = image.find_trim(&top, &width, &height, VImage::option()
303
+ ->set("background", background)
304
+ ->set("line_art", lineArt)
305
+ ->set("threshold", threshold));
306
+ if (HasAlpha(image)) {
307
+ // Search alpha channel (A)
308
+ int leftA, topA, widthA, heightA;
309
+ VImage alpha = image[image.bands() - 1];
310
+ leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
311
+ ->set("background", backgroundAlpha)
312
+ ->set("line_art", lineArt)
313
+ ->set("threshold", threshold));
314
+ if (widthA > 0 && heightA > 0) {
315
+ if (width > 0 && height > 0) {
316
+ // Combined bounding box (B)
317
+ int const leftB = std::min(left, leftA);
318
+ int const topB = std::min(top, topA);
319
+ int const widthB = std::max(left + width, leftA + widthA) - leftB;
320
+ int const heightB = std::max(top + height, topA + heightA) - topB;
321
+ return image.extract_area(leftB, topB, widthB, heightB);
322
+ } else {
323
+ // Use alpha only
324
+ return image.extract_area(leftA, topA, widthA, heightA);
325
+ }
326
+ }
327
+ }
328
+ if (width > 0 && height > 0) {
329
+ return image.extract_area(left, top, width, height);
330
+ }
331
+ return image;
332
+ }
333
+
334
+ /*
335
+ * Calculate (a * in + b)
336
+ */
337
+ VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
338
+ size_t const bands = static_cast<size_t>(image.bands());
339
+ if (a.size() > bands) {
340
+ throw VError("Band expansion using linear is unsupported");
341
+ }
342
+ bool const uchar = !Is16Bit(image.interpretation());
343
+ if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
344
+ // Separate alpha channel
345
+ VImage alpha = image[bands - 1];
346
+ return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
347
+ } else {
348
+ return image.linear(a, b, VImage::option()->set("uchar", uchar));
349
+ }
350
+ }
351
+
352
+ /*
353
+ * Unflatten
354
+ */
355
+ VImage Unflatten(VImage image) {
356
+ if (HasAlpha(image)) {
357
+ VImage alpha = image[image.bands() - 1];
358
+ VImage noAlpha = RemoveAlpha(image);
359
+ return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
360
+ } else {
361
+ return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
362
+ }
363
+ }
364
+
365
+ /*
366
+ * Ensure the image is in a given colourspace
367
+ */
368
+ VImage EnsureColourspace(VImage image, VipsInterpretation colourspace) {
369
+ if (colourspace != VIPS_INTERPRETATION_LAST && image.interpretation() != colourspace) {
370
+ image = image.colourspace(colourspace,
371
+ VImage::option()->set("source_space", image.interpretation()));
372
+ }
373
+ return image;
374
+ }
375
+
376
+ /*
377
+ * Split and crop each frame, reassemble, and update pageHeight.
378
+ */
379
+ VImage CropMultiPage(VImage image, int left, int top, int width, int height,
380
+ int nPages, int *pageHeight) {
381
+ if (top == 0 && height == *pageHeight) {
382
+ // Fast path; no need to adjust the height of the multi-page image
383
+ return image.extract_area(left, 0, width, image.height());
384
+ } else {
385
+ std::vector<VImage> pages;
386
+ pages.reserve(nPages);
387
+
388
+ // Split the image into cropped frames
389
+ image = StaySequential(image, VIPS_ACCESS_SEQUENTIAL);
390
+ for (int i = 0; i < nPages; i++) {
391
+ pages.push_back(
392
+ image.extract_area(left, *pageHeight * i + top, width, height));
393
+ }
394
+
395
+ // Reassemble the frames into a tall, thin image
396
+ VImage assembled = VImage::arrayjoin(pages,
397
+ VImage::option()->set("across", 1));
398
+
399
+ // Update the page height
400
+ *pageHeight = height;
401
+
402
+ return assembled;
403
+ }
404
+ }
405
+
406
+ /*
407
+ * Split into frames, embed each frame, reassemble, and update pageHeight.
408
+ */
409
+ VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
410
+ VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight) {
411
+ if (top == 0 && height == *pageHeight) {
412
+ // Fast path; no need to adjust the height of the multi-page image
413
+ return image.embed(left, 0, width, image.height(), VImage::option()
414
+ ->set("extend", extendWith)
415
+ ->set("background", background));
416
+ } else if (left == 0 && width == image.width()) {
417
+ // Fast path; no need to adjust the width of the multi-page image
418
+ std::vector<VImage> pages;
419
+ pages.reserve(nPages);
420
+
421
+ // Rearrange the tall image into a vertical grid
422
+ image = image.grid(*pageHeight, nPages, 1);
423
+
424
+ // Do the embed on the wide image
425
+ image = image.embed(0, top, image.width(), height, VImage::option()
426
+ ->set("extend", extendWith)
427
+ ->set("background", background));
428
+
429
+ // Split the wide image into frames
430
+ for (int i = 0; i < nPages; i++) {
431
+ pages.push_back(
432
+ image.extract_area(width * i, 0, width, height));
433
+ }
434
+
435
+ // Reassemble the frames into a tall, thin image
436
+ VImage assembled = VImage::arrayjoin(pages,
437
+ VImage::option()->set("across", 1));
438
+
439
+ // Update the page height
440
+ *pageHeight = height;
441
+
442
+ return assembled;
443
+ } else {
444
+ std::vector<VImage> pages;
445
+ pages.reserve(nPages);
446
+
447
+ // Split the image into frames
448
+ for (int i = 0; i < nPages; i++) {
449
+ pages.push_back(
450
+ image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
451
+ }
452
+
453
+ // Embed each frame in the target size
454
+ for (int i = 0; i < nPages; i++) {
455
+ pages[i] = pages[i].embed(left, top, width, height, VImage::option()
456
+ ->set("extend", extendWith)
457
+ ->set("background", background));
458
+ }
459
+
460
+ // Reassemble the frames into a tall, thin image
461
+ VImage assembled = VImage::arrayjoin(pages,
462
+ VImage::option()->set("across", 1));
463
+
464
+ // Update the page height
465
+ *pageHeight = height;
466
+
467
+ return assembled;
468
+ }
469
+ }
470
+
471
+ } // namespace sharp
@@ -0,0 +1,125 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ #ifndef SRC_OPERATIONS_H_
5
+ #define SRC_OPERATIONS_H_
6
+
7
+ #include <algorithm>
8
+ #include <functional>
9
+ #include <memory>
10
+ #include <tuple>
11
+ #include <vips/vips8>
12
+
13
+ using vips::VImage;
14
+
15
+ namespace sharp {
16
+
17
+ /*
18
+ * Tint an image using the provided RGB.
19
+ */
20
+ VImage Tint(VImage image, std::vector<double> const tint);
21
+
22
+ /*
23
+ * Stretch luminance to cover full dynamic range.
24
+ */
25
+ VImage Normalise(VImage image, int const lower, int const upper);
26
+
27
+ /*
28
+ * Contrast limiting adapative histogram equalization (CLAHE)
29
+ */
30
+ VImage Clahe(VImage image, int const width, int const height, int const maxSlope);
31
+
32
+ /*
33
+ * Gamma encoding/decoding
34
+ */
35
+ VImage Gamma(VImage image, double const exponent);
36
+
37
+ /*
38
+ * Flatten image to remove alpha channel
39
+ */
40
+ VImage Flatten(VImage image, std::vector<double> flattenBackground);
41
+
42
+ /*
43
+ * Produce the "negative" of the image.
44
+ */
45
+ VImage Negate(VImage image, bool const negateAlpha);
46
+
47
+ /*
48
+ * Gaussian blur. Use sigma of -1.0 for fast blur.
49
+ */
50
+ VImage Blur(VImage image, double const sigma);
51
+
52
+ /*
53
+ * Convolution with a kernel.
54
+ */
55
+ VImage Convolve(VImage image, int const width, int const height,
56
+ double const scale, double const offset, std::unique_ptr<double[]> const &kernel_v);
57
+
58
+ /*
59
+ * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
60
+ */
61
+ VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
62
+ double const x1, double const y2, double const y3);
63
+
64
+ /*
65
+ Threshold an image
66
+ */
67
+ VImage Threshold(VImage image, double const threshold, bool const thresholdColor);
68
+
69
+ /*
70
+ Perform boolean/bitwise operation on image color channels - results in one channel image
71
+ */
72
+ VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
73
+
74
+ /*
75
+ Perform bitwise boolean operation between images
76
+ */
77
+ VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean);
78
+
79
+ /*
80
+ Trim an image
81
+ */
82
+ VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt);
83
+
84
+ /*
85
+ * Linear adjustment (a * in + b)
86
+ */
87
+ VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);
88
+
89
+ /*
90
+ * Unflatten
91
+ */
92
+ VImage Unflatten(VImage image);
93
+
94
+ /*
95
+ * Recomb with a Matrix of the given bands/channel size.
96
+ * Eg. RGB will be a 3x3 matrix.
97
+ */
98
+ VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
99
+
100
+ /*
101
+ * Modulate brightness, saturation, hue and lightness
102
+ */
103
+ VImage Modulate(VImage image, double const brightness, double const saturation,
104
+ int const hue, double const lightness);
105
+
106
+ /*
107
+ * Ensure the image is in a given colourspace
108
+ */
109
+ VImage EnsureColourspace(VImage image, VipsInterpretation colourspace);
110
+
111
+ /*
112
+ * Split and crop each frame, reassemble, and update pageHeight.
113
+ */
114
+ VImage CropMultiPage(VImage image, int left, int top, int width, int height,
115
+ int nPages, int *pageHeight);
116
+
117
+ /*
118
+ * Split into frames, embed each frame, reassemble, and update pageHeight.
119
+ */
120
+ VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
121
+ VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight);
122
+
123
+ } // namespace sharp
124
+
125
+ #endif // SRC_OPERATIONS_H_