@revizly/sharp 0.33.2-revizly3
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +191 -0
- package/README.md +118 -0
- package/install/check.js +41 -0
- package/lib/channel.js +174 -0
- package/lib/colour.js +182 -0
- package/lib/composite.js +210 -0
- package/lib/constructor.js +444 -0
- package/lib/index.d.ts +1723 -0
- package/lib/index.js +16 -0
- package/lib/input.js +657 -0
- package/lib/is.js +169 -0
- package/lib/libvips.js +195 -0
- package/lib/operation.js +921 -0
- package/lib/output.js +1572 -0
- package/lib/resize.js +582 -0
- package/lib/sharp.js +115 -0
- package/lib/utility.js +286 -0
- package/package.json +218 -0
- package/src/binding.gyp +280 -0
- package/src/common.cc +1090 -0
- package/src/common.h +393 -0
- package/src/metadata.cc +287 -0
- package/src/metadata.h +82 -0
- package/src/operations.cc +471 -0
- package/src/operations.h +125 -0
- package/src/pipeline.cc +1741 -0
- package/src/pipeline.h +385 -0
- package/src/sharp.cc +40 -0
- package/src/stats.cc +183 -0
- package/src/stats.h +59 -0
- package/src/utilities.cc +269 -0
- package/src/utilities.h +19 -0
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
|
package/src/operations.h
ADDED
@@ -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_
|