sharp 0.27.1 → 0.30.5
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/README.md +19 -18
- package/binding.gyp +15 -14
- package/install/can-compile.js +11 -0
- package/install/dll-copy.js +10 -10
- package/install/libvips.js +128 -47
- package/lib/agent.js +1 -1
- package/lib/channel.js +38 -14
- package/lib/colour.js +51 -1
- package/lib/composite.js +19 -2
- package/lib/constructor.js +47 -46
- package/lib/input.js +112 -18
- package/lib/is.js +19 -5
- package/lib/libvips.js +64 -29
- package/lib/operation.js +239 -35
- package/lib/output.js +398 -116
- package/lib/platform.js +5 -3
- package/lib/resize.js +61 -15
- package/lib/sharp.js +32 -0
- package/lib/utility.js +58 -19
- package/package.json +46 -24
- package/src/common.cc +202 -52
- package/src/common.h +59 -10
- package/src/libvips/cplusplus/VConnection.cpp +0 -26
- package/src/libvips/cplusplus/VImage.cpp +88 -32
- package/src/libvips/cplusplus/VInterpolate.cpp +0 -13
- package/src/libvips/cplusplus/vips-operators.cpp +213 -1
- package/src/metadata.cc +26 -0
- package/src/metadata.h +4 -0
- package/src/operations.cc +141 -7
- package/src/operations.h +32 -3
- package/src/pipeline.cc +454 -306
- package/src/pipeline.h +64 -29
- package/src/sharp.cc +1 -0
- package/src/utilities.cc +17 -1
- package/src/utilities.h +1 -0
package/src/pipeline.cc
CHANGED
|
@@ -67,16 +67,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
67
67
|
vips::VImage image;
|
|
68
68
|
sharp::ImageType inputImageType;
|
|
69
69
|
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
|
70
|
+
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
|
|
71
|
+
|
|
72
|
+
int nPages = baton->input->pages;
|
|
73
|
+
if (nPages == -1) {
|
|
74
|
+
// Resolve the number of pages if we need to render until the end of the document
|
|
75
|
+
nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
|
|
76
|
+
? image.get_int(VIPS_META_N_PAGES) - baton->input->page
|
|
77
|
+
: 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Get pre-resize page height
|
|
81
|
+
int pageHeight = sharp::GetPageHeight(image);
|
|
70
82
|
|
|
71
83
|
// Calculate angle of rotation
|
|
72
84
|
VipsAngle rotation;
|
|
85
|
+
bool flip = FALSE;
|
|
86
|
+
bool flop = FALSE;
|
|
73
87
|
if (baton->useExifOrientation) {
|
|
74
88
|
// Rotate and flip image according to Exif orientation
|
|
75
|
-
bool flip;
|
|
76
|
-
bool flop;
|
|
77
89
|
std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
|
78
|
-
baton->flip = baton->flip || flip;
|
|
79
|
-
baton->flop = baton->flop || flop;
|
|
80
90
|
} else {
|
|
81
91
|
rotation = CalculateAngleRotation(baton->angle);
|
|
82
92
|
}
|
|
@@ -85,17 +95,29 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
85
95
|
if (baton->rotateBeforePreExtract) {
|
|
86
96
|
if (rotation != VIPS_ANGLE_D0) {
|
|
87
97
|
image = image.rot(rotation);
|
|
98
|
+
}
|
|
99
|
+
if (flip) {
|
|
100
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
101
|
+
}
|
|
102
|
+
if (flop) {
|
|
103
|
+
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
104
|
+
}
|
|
105
|
+
if (rotation != VIPS_ANGLE_D0 || flip || flop) {
|
|
88
106
|
image = sharp::RemoveExifOrientation(image);
|
|
89
107
|
}
|
|
108
|
+
flop = FALSE;
|
|
109
|
+
flip = FALSE;
|
|
90
110
|
if (baton->rotationAngle != 0.0) {
|
|
111
|
+
MultiPageUnsupported(nPages, "Rotate");
|
|
91
112
|
std::vector<double> background;
|
|
92
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
113
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
|
93
114
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
94
115
|
}
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
// Trim
|
|
98
119
|
if (baton->trimThreshold > 0.0) {
|
|
120
|
+
MultiPageUnsupported(nPages, "Trim");
|
|
99
121
|
image = sharp::Trim(image, baton->trimThreshold);
|
|
100
122
|
baton->trimOffsetLeft = image.xoffset();
|
|
101
123
|
baton->trimOffsetTop = image.yoffset();
|
|
@@ -103,199 +125,187 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
103
125
|
|
|
104
126
|
// Pre extraction
|
|
105
127
|
if (baton->topOffsetPre != -1) {
|
|
106
|
-
image =
|
|
128
|
+
image = nPages > 1
|
|
129
|
+
? sharp::CropMultiPage(image,
|
|
130
|
+
baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight)
|
|
131
|
+
: image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
// Get pre-resize image width and height
|
|
110
135
|
int inputWidth = image.width();
|
|
111
136
|
int inputHeight = image.height();
|
|
112
|
-
if (!baton->rotateBeforePreExtract &&
|
|
113
|
-
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
|
|
114
|
-
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
115
|
-
std::swap(inputWidth, inputHeight);
|
|
116
|
-
}
|
|
117
137
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (baton->width > inputWidth) {
|
|
122
|
-
baton->width = inputWidth;
|
|
123
|
-
}
|
|
124
|
-
if (baton->height > inputHeight) {
|
|
125
|
-
baton->height = inputHeight;
|
|
126
|
-
}
|
|
138
|
+
// Is there just one page? Shrink to inputHeight instead
|
|
139
|
+
if (nPages == 1) {
|
|
140
|
+
pageHeight = inputHeight;
|
|
127
141
|
}
|
|
128
142
|
|
|
129
143
|
// Scaling calculations
|
|
130
|
-
double
|
|
131
|
-
double
|
|
144
|
+
double hshrink;
|
|
145
|
+
double vshrink;
|
|
132
146
|
int targetResizeWidth = baton->width;
|
|
133
147
|
int targetResizeHeight = baton->height;
|
|
134
|
-
if (baton->width > 0 && baton->height > 0) {
|
|
135
|
-
// Fixed width and height
|
|
136
|
-
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
137
|
-
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
138
|
-
switch (baton->canvas) {
|
|
139
|
-
case Canvas::CROP:
|
|
140
|
-
if (xfactor < yfactor) {
|
|
141
|
-
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
142
|
-
yfactor = xfactor;
|
|
143
|
-
} else {
|
|
144
|
-
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
145
|
-
xfactor = yfactor;
|
|
146
|
-
}
|
|
147
|
-
break;
|
|
148
|
-
case Canvas::EMBED:
|
|
149
|
-
if (xfactor > yfactor) {
|
|
150
|
-
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
151
|
-
yfactor = xfactor;
|
|
152
|
-
} else {
|
|
153
|
-
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
154
|
-
xfactor = yfactor;
|
|
155
|
-
}
|
|
156
|
-
break;
|
|
157
|
-
case Canvas::MAX:
|
|
158
|
-
if (xfactor > yfactor) {
|
|
159
|
-
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
160
|
-
yfactor = xfactor;
|
|
161
|
-
} else {
|
|
162
|
-
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
163
|
-
xfactor = yfactor;
|
|
164
|
-
}
|
|
165
|
-
break;
|
|
166
|
-
case Canvas::MIN:
|
|
167
|
-
if (xfactor < yfactor) {
|
|
168
|
-
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
169
|
-
yfactor = xfactor;
|
|
170
|
-
} else {
|
|
171
|
-
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
172
|
-
xfactor = yfactor;
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case Canvas::IGNORE_ASPECT:
|
|
176
|
-
if (!baton->rotateBeforePreExtract &&
|
|
177
|
-
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
|
|
178
|
-
std::swap(xfactor, yfactor);
|
|
179
|
-
}
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
} else if (baton->width > 0) {
|
|
183
|
-
// Fixed width
|
|
184
|
-
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
185
|
-
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
|
186
|
-
targetResizeHeight = baton->height = inputHeight;
|
|
187
|
-
} else {
|
|
188
|
-
// Auto height
|
|
189
|
-
yfactor = xfactor;
|
|
190
|
-
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
|
|
191
|
-
}
|
|
192
|
-
} else if (baton->height > 0) {
|
|
193
|
-
// Fixed height
|
|
194
|
-
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
195
|
-
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
|
196
|
-
targetResizeWidth = baton->width = inputWidth;
|
|
197
|
-
} else {
|
|
198
|
-
// Auto width
|
|
199
|
-
xfactor = yfactor;
|
|
200
|
-
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
// Identity transform
|
|
204
|
-
baton->width = inputWidth;
|
|
205
|
-
baton->height = inputHeight;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Calculate integral box shrink
|
|
209
|
-
int xshrink = std::max(1, static_cast<int>(floor(xfactor)));
|
|
210
|
-
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
|
|
211
|
-
|
|
212
|
-
// Calculate residual float affine transformation
|
|
213
|
-
double xresidual = static_cast<double>(xshrink) / xfactor;
|
|
214
|
-
double yresidual = static_cast<double>(yshrink) / yfactor;
|
|
215
148
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
149
|
+
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
150
|
+
bool swap = !baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270);
|
|
151
|
+
|
|
152
|
+
// Shrink to pageHeight, so we work for multi-page images
|
|
153
|
+
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
154
|
+
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
|
155
|
+
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
|
|
156
|
+
|
|
157
|
+
// The jpeg preload shrink.
|
|
158
|
+
int jpegShrinkOnLoad = 1;
|
|
159
|
+
|
|
160
|
+
// WebP, PDF, SVG scale
|
|
161
|
+
double scale = 1.0;
|
|
162
|
+
|
|
163
|
+
// Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when:
|
|
164
|
+
// - the width or height parameters are specified;
|
|
165
|
+
// - gamma correction doesn't need to be applied;
|
|
166
|
+
// - trimming or pre-resize extract isn't required;
|
|
167
|
+
// - input colourspace is not specified;
|
|
168
|
+
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
|
169
|
+
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
|
|
170
|
+
baton->colourspaceInput == VIPS_INTERPRETATION_LAST;
|
|
171
|
+
|
|
172
|
+
if (shouldPreShrink) {
|
|
173
|
+
// The common part of the shrink: the bit by which both axes must be shrunk
|
|
174
|
+
double shrink = std::min(hshrink, vshrink);
|
|
175
|
+
|
|
176
|
+
if (inputImageType == sharp::ImageType::JPEG) {
|
|
177
|
+
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
|
|
178
|
+
// for more consistent results and to avoid extra sharpness to the image
|
|
179
|
+
int factor = baton->fastShrinkOnLoad ? 1 : 2;
|
|
180
|
+
if (shrink >= 8 * factor) {
|
|
181
|
+
jpegShrinkOnLoad = 8;
|
|
182
|
+
} else if (shrink >= 4 * factor) {
|
|
183
|
+
jpegShrinkOnLoad = 4;
|
|
184
|
+
} else if (shrink >= 2 * factor) {
|
|
185
|
+
jpegShrinkOnLoad = 2;
|
|
186
|
+
}
|
|
187
|
+
// Lower shrink-on-load for known libjpeg rounding errors
|
|
188
|
+
if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
|
|
189
|
+
jpegShrinkOnLoad /= 2;
|
|
190
|
+
}
|
|
191
|
+
} else if (inputImageType == sharp::ImageType::WEBP ||
|
|
192
|
+
inputImageType == sharp::ImageType::SVG ||
|
|
193
|
+
inputImageType == sharp::ImageType::PDF) {
|
|
194
|
+
scale = 1.0 / shrink;
|
|
243
195
|
}
|
|
244
196
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
if (shrink_on_load > 1) {
|
|
252
|
-
// Reload input using shrink-on-load
|
|
197
|
+
|
|
198
|
+
// Reload input using shrink-on-load, it'll be an integer shrink
|
|
199
|
+
// factor for jpegload*, a double scale factor for webpload*,
|
|
200
|
+
// pdfload* and svgload*
|
|
201
|
+
if (jpegShrinkOnLoad > 1) {
|
|
253
202
|
vips::VOption *option = VImage::option()
|
|
254
203
|
->set("access", baton->input->access)
|
|
255
|
-
->set("shrink",
|
|
256
|
-
->set("
|
|
204
|
+
->set("shrink", jpegShrinkOnLoad)
|
|
205
|
+
->set("fail_on", baton->input->failOn);
|
|
257
206
|
if (baton->input->buffer != nullptr) {
|
|
207
|
+
// Reload JPEG buffer
|
|
258
208
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
259
|
-
|
|
260
|
-
// Reload JPEG buffer
|
|
261
|
-
image = VImage::jpegload_buffer(blob, option);
|
|
262
|
-
} else {
|
|
263
|
-
// Reload WebP buffer
|
|
264
|
-
image = VImage::webpload_buffer(blob, option);
|
|
265
|
-
}
|
|
209
|
+
image = VImage::jpegload_buffer(blob, option);
|
|
266
210
|
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
267
211
|
} else {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
212
|
+
// Reload JPEG file
|
|
213
|
+
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
|
|
214
|
+
}
|
|
215
|
+
} else if (scale != 1.0) {
|
|
216
|
+
vips::VOption *option = VImage::option()
|
|
217
|
+
->set("access", baton->input->access)
|
|
218
|
+
->set("scale", scale)
|
|
219
|
+
->set("fail_on", baton->input->failOn);
|
|
220
|
+
if (inputImageType == sharp::ImageType::WEBP) {
|
|
221
|
+
option->set("n", baton->input->pages);
|
|
222
|
+
option->set("page", baton->input->page);
|
|
223
|
+
|
|
224
|
+
if (baton->input->buffer != nullptr) {
|
|
225
|
+
// Reload WebP buffer
|
|
226
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
227
|
+
image = VImage::webpload_buffer(blob, option);
|
|
228
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
271
229
|
} else {
|
|
272
230
|
// Reload WebP file
|
|
273
231
|
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
|
|
274
232
|
}
|
|
233
|
+
} else if (inputImageType == sharp::ImageType::SVG) {
|
|
234
|
+
option->set("unlimited", baton->input->unlimited);
|
|
235
|
+
option->set("dpi", baton->input->density);
|
|
236
|
+
|
|
237
|
+
if (baton->input->buffer != nullptr) {
|
|
238
|
+
// Reload SVG buffer
|
|
239
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
240
|
+
image = VImage::svgload_buffer(blob, option);
|
|
241
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
242
|
+
} else {
|
|
243
|
+
// Reload SVG file
|
|
244
|
+
image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
|
|
245
|
+
}
|
|
246
|
+
sharp::SetDensity(image, baton->input->density);
|
|
247
|
+
if (image.width() > 32767 || image.height() > 32767) {
|
|
248
|
+
throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
|
|
249
|
+
}
|
|
250
|
+
} else if (inputImageType == sharp::ImageType::PDF) {
|
|
251
|
+
option->set("n", baton->input->pages);
|
|
252
|
+
option->set("page", baton->input->page);
|
|
253
|
+
option->set("dpi", baton->input->density);
|
|
254
|
+
|
|
255
|
+
if (baton->input->buffer != nullptr) {
|
|
256
|
+
// Reload PDF buffer
|
|
257
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
258
|
+
image = VImage::pdfload_buffer(blob, option);
|
|
259
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
260
|
+
} else {
|
|
261
|
+
// Reload PDF file
|
|
262
|
+
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
sharp::SetDensity(image, baton->input->density);
|
|
275
266
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!baton->rotateBeforePreExtract &&
|
|
280
|
-
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
|
|
281
|
-
// Swap when rotating by 90 or 270 degrees
|
|
282
|
-
xfactor = static_cast<double>(shrunkOnLoadWidth) / static_cast<double>(targetResizeHeight);
|
|
283
|
-
yfactor = static_cast<double>(shrunkOnLoadHeight) / static_cast<double>(targetResizeWidth);
|
|
284
|
-
} else {
|
|
285
|
-
xfactor = static_cast<double>(shrunkOnLoadWidth) / static_cast<double>(targetResizeWidth);
|
|
286
|
-
yfactor = static_cast<double>(shrunkOnLoadHeight) / static_cast<double>(targetResizeHeight);
|
|
267
|
+
} else {
|
|
268
|
+
if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
|
|
269
|
+
throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit");
|
|
287
270
|
}
|
|
288
271
|
}
|
|
289
272
|
|
|
273
|
+
// Any pre-shrinking may already have been done
|
|
274
|
+
inputWidth = image.width();
|
|
275
|
+
inputHeight = image.height();
|
|
276
|
+
|
|
277
|
+
// After pre-shrink, but before the main shrink stage
|
|
278
|
+
// Reuse the initial pageHeight if we didn't pre-shrink
|
|
279
|
+
if (shouldPreShrink) {
|
|
280
|
+
pageHeight = sharp::GetPageHeight(image);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Shrink to pageHeight, so we work for multi-page images
|
|
284
|
+
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
285
|
+
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
|
286
|
+
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
|
|
287
|
+
|
|
288
|
+
int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
|
|
289
|
+
int targetPageHeight = targetHeight;
|
|
290
|
+
|
|
291
|
+
// In toilet-roll mode, we must adjust vshrink so that we exactly hit
|
|
292
|
+
// pageHeight or we'll have pixels straddling pixel boundaries
|
|
293
|
+
if (inputHeight > pageHeight) {
|
|
294
|
+
targetHeight *= nPages;
|
|
295
|
+
vshrink = static_cast<double>(inputHeight) / targetHeight;
|
|
296
|
+
}
|
|
297
|
+
|
|
290
298
|
// Ensure we're using a device-independent colour space
|
|
299
|
+
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
|
291
300
|
if (
|
|
292
301
|
sharp::HasProfile(image) &&
|
|
293
302
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
294
|
-
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
|
303
|
+
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
|
304
|
+
image.interpretation() != VIPS_INTERPRETATION_B_W
|
|
295
305
|
) {
|
|
296
|
-
// Convert to sRGB using embedded profile
|
|
306
|
+
// Convert to sRGB/P3 using embedded profile
|
|
297
307
|
try {
|
|
298
|
-
image = image.icc_transform(
|
|
308
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
299
309
|
->set("embedded", TRUE)
|
|
300
310
|
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
|
|
301
311
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
@@ -303,7 +313,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
303
313
|
// Ignore failure of embedded profile
|
|
304
314
|
}
|
|
305
315
|
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
|
306
|
-
image = image.icc_transform(
|
|
316
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
307
317
|
->set("input_profile", "cmyk")
|
|
308
318
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
309
319
|
}
|
|
@@ -324,7 +334,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
324
334
|
|
|
325
335
|
// Negate the colours in the image
|
|
326
336
|
if (baton->negate) {
|
|
327
|
-
image = image
|
|
337
|
+
image = sharp::Negate(image, baton->negateAlpha);
|
|
328
338
|
}
|
|
329
339
|
|
|
330
340
|
// Gamma encoding (darken)
|
|
@@ -337,20 +347,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
337
347
|
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
|
338
348
|
}
|
|
339
349
|
|
|
340
|
-
bool const shouldResize =
|
|
350
|
+
bool const shouldResize = hshrink != 1.0 || vshrink != 1.0;
|
|
341
351
|
bool const shouldBlur = baton->blurSigma != 0.0;
|
|
342
352
|
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
|
343
353
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
344
354
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
|
345
355
|
bool const shouldComposite = !baton->composite.empty();
|
|
346
|
-
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
|
356
|
+
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
|
357
|
+
baton->hue != 0.0 || baton->lightness != 0.0;
|
|
358
|
+
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
|
347
359
|
|
|
348
360
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
|
349
|
-
image = sharp::EnsureAlpha(image);
|
|
361
|
+
image = sharp::EnsureAlpha(image, 1);
|
|
350
362
|
}
|
|
351
363
|
|
|
352
364
|
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
|
|
353
|
-
(shouldResize || shouldBlur || shouldConv || shouldSharpen
|
|
365
|
+
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
|
|
354
366
|
|
|
355
367
|
// Premultiply image alpha channel before all transformations to avoid
|
|
356
368
|
// dark fringing around bright pixels
|
|
@@ -369,35 +381,33 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
369
381
|
) {
|
|
370
382
|
throw vips::VError("Unknown kernel");
|
|
371
383
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
xfactor = 2 * image.width();
|
|
375
|
-
baton->width = 1;
|
|
376
|
-
}
|
|
377
|
-
if (image.height() / yfactor < 0.5) {
|
|
378
|
-
yfactor = 2 * image.height();
|
|
379
|
-
baton->height = 1;
|
|
380
|
-
}
|
|
381
|
-
image = image.resize(1.0 / xfactor, VImage::option()
|
|
382
|
-
->set("vscale", 1.0 / yfactor)
|
|
384
|
+
image = image.resize(1.0 / hshrink, VImage::option()
|
|
385
|
+
->set("vscale", 1.0 / vshrink)
|
|
383
386
|
->set("kernel", kernel));
|
|
384
387
|
}
|
|
385
388
|
|
|
386
389
|
// Rotate post-extract 90-angle
|
|
387
|
-
if (!baton->rotateBeforePreExtract &&
|
|
388
|
-
|
|
389
|
-
|
|
390
|
+
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
|
391
|
+
image = image.rot(rotation);
|
|
392
|
+
if (flip) {
|
|
393
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
394
|
+
flip = FALSE;
|
|
395
|
+
}
|
|
396
|
+
if (flop) {
|
|
397
|
+
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
398
|
+
flop = FALSE;
|
|
399
|
+
}
|
|
400
|
+
image = sharp::RemoveExifOrientation(image);
|
|
390
401
|
}
|
|
391
402
|
|
|
392
|
-
|
|
393
403
|
// Flip (mirror about Y axis)
|
|
394
|
-
if (baton->flip) {
|
|
404
|
+
if (baton->flip || flip) {
|
|
395
405
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
396
406
|
image = sharp::RemoveExifOrientation(image);
|
|
397
407
|
}
|
|
398
408
|
|
|
399
409
|
// Flop (mirror about X axis)
|
|
400
|
-
if (baton->flop) {
|
|
410
|
+
if (baton->flop || flop) {
|
|
401
411
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
402
412
|
image = sharp::RemoveExifOrientation(image);
|
|
403
413
|
}
|
|
@@ -409,57 +419,74 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
409
419
|
|
|
410
420
|
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
|
|
411
421
|
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
|
|
422
|
+
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
|
|
412
423
|
image = image.bandjoin(joinImage);
|
|
413
424
|
}
|
|
414
425
|
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
|
|
415
426
|
}
|
|
416
427
|
|
|
428
|
+
inputWidth = image.width();
|
|
429
|
+
inputHeight = nPages > 1 ? targetPageHeight : image.height();
|
|
430
|
+
|
|
431
|
+
// Resolve dimensions
|
|
432
|
+
if (baton->width <= 0) {
|
|
433
|
+
baton->width = inputWidth;
|
|
434
|
+
}
|
|
435
|
+
if (baton->height <= 0) {
|
|
436
|
+
baton->height = inputHeight;
|
|
437
|
+
}
|
|
438
|
+
|
|
417
439
|
// Crop/embed
|
|
418
|
-
if (
|
|
419
|
-
if (baton->canvas == Canvas::EMBED) {
|
|
440
|
+
if (inputWidth != baton->width || inputHeight != baton->height) {
|
|
441
|
+
if (baton->canvas == sharp::Canvas::EMBED) {
|
|
420
442
|
std::vector<double> background;
|
|
421
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground);
|
|
443
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
|
422
444
|
|
|
423
445
|
// Embed
|
|
424
446
|
|
|
425
|
-
// Calculate where to position the
|
|
447
|
+
// Calculate where to position the embedded image if gravity specified, else center.
|
|
426
448
|
int left;
|
|
427
449
|
int top;
|
|
428
450
|
|
|
429
|
-
left = static_cast<int>(round((baton->width -
|
|
430
|
-
top = static_cast<int>(round((baton->height -
|
|
451
|
+
left = static_cast<int>(round((baton->width - inputWidth) / 2));
|
|
452
|
+
top = static_cast<int>(round((baton->height - inputHeight) / 2));
|
|
431
453
|
|
|
432
|
-
int width = std::max(
|
|
433
|
-
int height = std::max(
|
|
454
|
+
int width = std::max(inputWidth, baton->width);
|
|
455
|
+
int height = std::max(inputHeight, baton->height);
|
|
434
456
|
std::tie(left, top) = sharp::CalculateEmbedPosition(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
image =
|
|
438
|
-
|
|
439
|
-
|
|
457
|
+
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
458
|
+
|
|
459
|
+
image = nPages > 1
|
|
460
|
+
? sharp::EmbedMultiPage(image,
|
|
461
|
+
left, top, width, height, background, nPages, &targetPageHeight)
|
|
462
|
+
: image.embed(left, top, width, height, VImage::option()
|
|
463
|
+
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
464
|
+
->set("background", background));
|
|
465
|
+
} else if (baton->canvas == sharp::Canvas::CROP) {
|
|
466
|
+
if (baton->width > inputWidth) {
|
|
467
|
+
baton->width = inputWidth;
|
|
468
|
+
}
|
|
469
|
+
if (baton->height > inputHeight) {
|
|
470
|
+
baton->height = inputHeight;
|
|
471
|
+
}
|
|
440
472
|
|
|
441
|
-
|
|
442
|
-
baton->canvas != Canvas::IGNORE_ASPECT &&
|
|
443
|
-
(image.width() > baton->width || image.height() > baton->height)
|
|
444
|
-
) {
|
|
445
|
-
// Crop/max/min
|
|
473
|
+
// Crop
|
|
446
474
|
if (baton->position < 9) {
|
|
447
475
|
// Gravity-based crop
|
|
448
476
|
int left;
|
|
449
477
|
int top;
|
|
450
478
|
std::tie(left, top) = sharp::CalculateCrop(
|
|
451
|
-
|
|
452
|
-
int width = std::min(
|
|
453
|
-
int height = std::min(
|
|
454
|
-
|
|
479
|
+
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
480
|
+
int width = std::min(inputWidth, baton->width);
|
|
481
|
+
int height = std::min(inputHeight, baton->height);
|
|
482
|
+
|
|
483
|
+
image = nPages > 1
|
|
484
|
+
? sharp::CropMultiPage(image,
|
|
485
|
+
left, top, width, height, nPages, &targetPageHeight)
|
|
486
|
+
: image.extract_area(left, top, width, height);
|
|
455
487
|
} else {
|
|
456
488
|
// Attention-based or Entropy-based crop
|
|
457
|
-
|
|
458
|
-
baton->width = image.width();
|
|
459
|
-
}
|
|
460
|
-
if (baton->height > image.height()) {
|
|
461
|
-
baton->height = image.height();
|
|
462
|
-
}
|
|
489
|
+
MultiPageUnsupported(nPages, "Resize strategy");
|
|
463
490
|
image = image.tilecache(VImage::option()
|
|
464
491
|
->set("access", VIPS_ACCESS_RANDOM)
|
|
465
492
|
->set("threaded", TRUE));
|
|
@@ -474,40 +501,56 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
474
501
|
|
|
475
502
|
// Rotate post-extract non-90 angle
|
|
476
503
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
|
504
|
+
MultiPageUnsupported(nPages, "Rotate");
|
|
477
505
|
std::vector<double> background;
|
|
478
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
506
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
479
507
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
480
508
|
}
|
|
481
509
|
|
|
482
510
|
// Post extraction
|
|
483
511
|
if (baton->topOffsetPost != -1) {
|
|
484
|
-
|
|
485
|
-
|
|
512
|
+
if (nPages > 1) {
|
|
513
|
+
image = sharp::CropMultiPage(image,
|
|
514
|
+
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
|
|
515
|
+
nPages, &targetPageHeight);
|
|
516
|
+
|
|
517
|
+
// heightPost is used in the info object, so update to reflect the number of pages
|
|
518
|
+
baton->heightPost *= nPages;
|
|
519
|
+
} else {
|
|
520
|
+
image = image.extract_area(
|
|
521
|
+
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
|
522
|
+
}
|
|
486
523
|
}
|
|
487
524
|
|
|
488
525
|
// Affine transform
|
|
489
526
|
if (baton->affineMatrix.size() > 0) {
|
|
527
|
+
MultiPageUnsupported(nPages, "Affine");
|
|
490
528
|
std::vector<double> background;
|
|
491
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
|
529
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
530
|
+
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
|
|
531
|
+
const_cast<char*>(baton->affineInterpolator.data()));
|
|
492
532
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
|
493
533
|
->set("idx", baton->affineIdx)
|
|
494
534
|
->set("idy", baton->affineIdy)
|
|
495
535
|
->set("odx", baton->affineOdx)
|
|
496
536
|
->set("ody", baton->affineOdy)
|
|
497
|
-
->set("interpolate",
|
|
537
|
+
->set("interpolate", interp));
|
|
498
538
|
}
|
|
499
539
|
|
|
500
540
|
// Extend edges
|
|
501
541
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
|
502
542
|
std::vector<double> background;
|
|
503
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground);
|
|
543
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
|
504
544
|
|
|
505
545
|
// Embed
|
|
506
546
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
|
507
|
-
baton->height = image.height() + baton->extendTop + baton->extendBottom;
|
|
547
|
+
baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
|
|
508
548
|
|
|
509
|
-
image =
|
|
510
|
-
|
|
549
|
+
image = nPages > 1
|
|
550
|
+
? sharp::EmbedMultiPage(image,
|
|
551
|
+
baton->extendLeft, baton->extendTop, baton->width, baton->height, background, nPages, &targetPageHeight)
|
|
552
|
+
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
553
|
+
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
|
|
511
554
|
}
|
|
512
555
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
513
556
|
if (shouldApplyMedian) {
|
|
@@ -537,20 +580,24 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
537
580
|
}
|
|
538
581
|
|
|
539
582
|
if (shouldModulate) {
|
|
540
|
-
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
|
583
|
+
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
541
584
|
}
|
|
542
585
|
|
|
543
586
|
// Sharpen
|
|
544
587
|
if (shouldSharpen) {
|
|
545
|
-
image = sharp::Sharpen(image, baton->sharpenSigma, baton->
|
|
588
|
+
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
|
|
589
|
+
baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
|
|
546
590
|
}
|
|
547
591
|
|
|
548
592
|
// Composite
|
|
549
593
|
if (shouldComposite) {
|
|
594
|
+
std::vector<VImage> images = { image };
|
|
595
|
+
std::vector<int> modes, xs, ys;
|
|
550
596
|
for (Composite *composite : baton->composite) {
|
|
551
597
|
VImage compositeImage;
|
|
552
598
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
|
553
|
-
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
|
|
599
|
+
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
|
600
|
+
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
|
|
554
601
|
// Verify within current dimensions
|
|
555
602
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
|
556
603
|
throw vips::VError("Image to composite must have same dimensions or smaller");
|
|
@@ -562,9 +609,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
562
609
|
// Use gravity in overlay
|
|
563
610
|
if (compositeImage.width() <= baton->width) {
|
|
564
611
|
across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
|
|
612
|
+
// Ensure odd number of tiles across when gravity is centre, north or south
|
|
613
|
+
if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
|
|
614
|
+
across |= 1;
|
|
615
|
+
}
|
|
565
616
|
}
|
|
566
617
|
if (compositeImage.height() <= baton->height) {
|
|
567
618
|
down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
|
|
619
|
+
// Ensure odd number of tiles down when gravity is centre, east or west
|
|
620
|
+
if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
|
|
621
|
+
down |= 1;
|
|
622
|
+
}
|
|
568
623
|
}
|
|
569
624
|
if (across != 0 || down != 0) {
|
|
570
625
|
int left;
|
|
@@ -583,30 +638,35 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
583
638
|
// gravity was used for extract_area, set it back to its default value of 0
|
|
584
639
|
composite->gravity = 0;
|
|
585
640
|
}
|
|
586
|
-
// Ensure image to composite is sRGB with
|
|
641
|
+
// Ensure image to composite is sRGB with unpremultiplied alpha
|
|
587
642
|
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
588
643
|
if (!sharp::HasAlpha(compositeImage)) {
|
|
589
|
-
compositeImage = sharp::EnsureAlpha(compositeImage);
|
|
644
|
+
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
590
645
|
}
|
|
591
|
-
if (
|
|
646
|
+
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
|
592
647
|
// Calculate position
|
|
593
648
|
int left;
|
|
594
649
|
int top;
|
|
595
650
|
if (composite->hasOffset) {
|
|
596
651
|
// Composite image at given offsets
|
|
597
|
-
|
|
598
|
-
|
|
652
|
+
if (composite->tile) {
|
|
653
|
+
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
654
|
+
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
|
655
|
+
} else {
|
|
656
|
+
left = composite->left;
|
|
657
|
+
top = composite->top;
|
|
658
|
+
}
|
|
599
659
|
} else {
|
|
600
660
|
// Composite image with given gravity
|
|
601
661
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
602
662
|
compositeImage.width(), compositeImage.height(), composite->gravity);
|
|
603
663
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
->set("y", top));
|
|
664
|
+
images.push_back(compositeImage);
|
|
665
|
+
modes.push_back(composite->mode);
|
|
666
|
+
xs.push_back(left);
|
|
667
|
+
ys.push_back(top);
|
|
609
668
|
}
|
|
669
|
+
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
|
|
610
670
|
}
|
|
611
671
|
|
|
612
672
|
// Reverse premultiplication after all transformations:
|
|
@@ -636,11 +696,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
636
696
|
image = sharp::Normalise(image);
|
|
637
697
|
}
|
|
638
698
|
|
|
699
|
+
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
|
700
|
+
if (shouldApplyClahe) {
|
|
701
|
+
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
|
702
|
+
}
|
|
703
|
+
|
|
639
704
|
// Apply bitwise boolean operation between images
|
|
640
705
|
if (baton->boolean != nullptr) {
|
|
641
706
|
VImage booleanImage;
|
|
642
707
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
|
643
708
|
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
|
|
709
|
+
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
|
|
644
710
|
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
|
|
645
711
|
}
|
|
646
712
|
|
|
@@ -678,8 +744,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
678
744
|
}
|
|
679
745
|
|
|
680
746
|
// Ensure alpha channel, if missing
|
|
681
|
-
if (baton->ensureAlpha) {
|
|
682
|
-
image = sharp::EnsureAlpha(image);
|
|
747
|
+
if (baton->ensureAlpha != -1) {
|
|
748
|
+
image = sharp::EnsureAlpha(image, baton->ensureAlpha);
|
|
683
749
|
}
|
|
684
750
|
|
|
685
751
|
// Convert image to sRGB, if not already
|
|
@@ -690,9 +756,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
690
756
|
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
|
691
757
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
|
692
758
|
// Transform colours from embedded profile to output profile
|
|
693
|
-
if (baton->withMetadata && sharp::HasProfile(image)) {
|
|
694
|
-
image = image.icc_transform(
|
|
695
|
-
|
|
759
|
+
if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
|
|
760
|
+
image = image.icc_transform("srgb", VImage::option()
|
|
761
|
+
->set("embedded", TRUE)
|
|
762
|
+
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
696
763
|
}
|
|
697
764
|
}
|
|
698
765
|
|
|
@@ -701,30 +768,36 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
701
768
|
image = image.icc_transform(
|
|
702
769
|
const_cast<char*>(baton->withMetadataIcc.data()),
|
|
703
770
|
VImage::option()
|
|
704
|
-
->set("input_profile",
|
|
771
|
+
->set("input_profile", processingProfile)
|
|
772
|
+
->set("embedded", TRUE)
|
|
705
773
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
706
774
|
}
|
|
707
|
-
|
|
708
775
|
// Override EXIF Orientation tag
|
|
709
776
|
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
|
710
777
|
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
|
711
778
|
}
|
|
779
|
+
// Override pixel density
|
|
780
|
+
if (baton->withMetadataDensity > 0) {
|
|
781
|
+
image = sharp::SetDensity(image, baton->withMetadataDensity);
|
|
782
|
+
}
|
|
783
|
+
// Metadata key/value pairs, e.g. EXIF
|
|
784
|
+
if (!baton->withMetadataStrs.empty()) {
|
|
785
|
+
image = image.copy();
|
|
786
|
+
for (const auto& s : baton->withMetadataStrs) {
|
|
787
|
+
image.set(s.first.data(), s.second.data());
|
|
788
|
+
}
|
|
789
|
+
}
|
|
712
790
|
|
|
713
791
|
// Number of channels used in output image
|
|
714
792
|
baton->channels = image.bands();
|
|
715
793
|
baton->width = image.width();
|
|
716
794
|
baton->height = image.height();
|
|
717
795
|
|
|
718
|
-
bool const supportsGifOutput = vips_type_find("VipsOperation", "magicksave") != 0 &&
|
|
719
|
-
vips_type_find("VipsOperation", "magicksave_buffer") != 0;
|
|
720
|
-
|
|
721
796
|
image = sharp::SetAnimationProperties(
|
|
722
|
-
image,
|
|
723
|
-
baton->pageHeight,
|
|
724
|
-
baton->delay,
|
|
725
|
-
baton->loop);
|
|
797
|
+
image, nPages, targetPageHeight, baton->delay, baton->loop);
|
|
726
798
|
|
|
727
799
|
// Output
|
|
800
|
+
sharp::SetTimeout(image, baton->timeoutSeconds);
|
|
728
801
|
if (baton->fileOut.empty()) {
|
|
729
802
|
// Buffer output
|
|
730
803
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -735,8 +808,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
735
808
|
->set("Q", baton->jpegQuality)
|
|
736
809
|
->set("interlace", baton->jpegProgressive)
|
|
737
810
|
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
738
|
-
?
|
|
739
|
-
:
|
|
811
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
812
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
740
813
|
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
741
814
|
->set("quant_table", baton->jpegQuantisationTable)
|
|
742
815
|
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
@@ -752,9 +825,24 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
752
825
|
} else {
|
|
753
826
|
baton->channels = std::min(baton->channels, 3);
|
|
754
827
|
}
|
|
828
|
+
} else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
|
|
829
|
+
&& inputImageType == sharp::ImageType::JP2)) {
|
|
830
|
+
// Write JP2 to Buffer
|
|
831
|
+
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
|
832
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option()
|
|
833
|
+
->set("Q", baton->jp2Quality)
|
|
834
|
+
->set("lossless", baton->jp2Lossless)
|
|
835
|
+
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
|
836
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
837
|
+
->set("tile_height", baton->jp2TileHeight)
|
|
838
|
+
->set("tile_width", baton->jp2TileWidth)));
|
|
839
|
+
baton->bufferOut = static_cast<char*>(area->data);
|
|
840
|
+
baton->bufferOutLength = area->length;
|
|
841
|
+
area->free_fn = nullptr;
|
|
842
|
+
vips_area_unref(area);
|
|
843
|
+
baton->formatOut = "jp2";
|
|
755
844
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
|
756
|
-
(inputImageType == sharp::ImageType::PNG ||
|
|
757
|
-
inputImageType == sharp::ImageType::SVG))) {
|
|
845
|
+
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
|
758
846
|
// Write PNG to buffer
|
|
759
847
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
760
848
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
|
@@ -764,7 +852,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
764
852
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
765
853
|
->set("palette", baton->pngPalette)
|
|
766
854
|
->set("Q", baton->pngQuality)
|
|
767
|
-
->set("
|
|
855
|
+
->set("effort", baton->pngEffort)
|
|
856
|
+
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
|
|
768
857
|
->set("dither", baton->pngDither)));
|
|
769
858
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
770
859
|
baton->bufferOutLength = area->length;
|
|
@@ -781,7 +870,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
781
870
|
->set("lossless", baton->webpLossless)
|
|
782
871
|
->set("near_lossless", baton->webpNearLossless)
|
|
783
872
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
784
|
-
->set("
|
|
873
|
+
->set("effort", baton->webpEffort)
|
|
785
874
|
->set("alpha_q", baton->webpAlphaQuality)));
|
|
786
875
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
787
876
|
baton->bufferOutLength = area->length;
|
|
@@ -789,14 +878,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
789
878
|
vips_area_unref(area);
|
|
790
879
|
baton->formatOut = "webp";
|
|
791
880
|
} else if (baton->formatOut == "gif" ||
|
|
792
|
-
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF
|
|
881
|
+
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {
|
|
793
882
|
// Write GIF to buffer
|
|
794
883
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
795
|
-
VipsArea *area = reinterpret_cast<VipsArea*>(image.
|
|
884
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
|
796
885
|
->set("strip", !baton->withMetadata)
|
|
797
|
-
->set("
|
|
798
|
-
->set("
|
|
799
|
-
->set("
|
|
886
|
+
->set("bitdepth", baton->gifBitdepth)
|
|
887
|
+
->set("effort", baton->gifEffort)
|
|
888
|
+
->set("dither", baton->gifDither)));
|
|
800
889
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
801
890
|
baton->bufferOutLength = area->length;
|
|
802
891
|
area->free_fn = nullptr;
|
|
@@ -824,7 +913,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
824
913
|
->set("tile_height", baton->tiffTileHeight)
|
|
825
914
|
->set("tile_width", baton->tiffTileWidth)
|
|
826
915
|
->set("xres", baton->tiffXres)
|
|
827
|
-
->set("yres", baton->tiffYres)
|
|
916
|
+
->set("yres", baton->tiffYres)
|
|
917
|
+
->set("resunit", baton->tiffResolutionUnit)));
|
|
828
918
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
829
919
|
baton->bufferOutLength = area->length;
|
|
830
920
|
area->free_fn = nullptr;
|
|
@@ -833,15 +923,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
833
923
|
} else if (baton->formatOut == "heif" ||
|
|
834
924
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
835
925
|
// Write HEIF to buffer
|
|
926
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
836
927
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
837
928
|
->set("strip", !baton->withMetadata)
|
|
838
|
-
->set("compression", baton->heifCompression)
|
|
839
929
|
->set("Q", baton->heifQuality)
|
|
840
|
-
->set("
|
|
841
|
-
|
|
930
|
+
->set("compression", baton->heifCompression)
|
|
931
|
+
->set("effort", baton->heifEffort)
|
|
842
932
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
843
933
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
844
|
-
#endif
|
|
845
934
|
->set("lossless", baton->heifLossless)));
|
|
846
935
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
847
936
|
baton->bufferOutLength = area->length;
|
|
@@ -856,9 +945,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
856
945
|
image = image[0];
|
|
857
946
|
baton->channels = 1;
|
|
858
947
|
}
|
|
859
|
-
if (image.format() !=
|
|
860
|
-
// Cast pixels to
|
|
861
|
-
image = image.cast(
|
|
948
|
+
if (image.format() != baton->rawDepth) {
|
|
949
|
+
// Cast pixels to requested format
|
|
950
|
+
image = image.cast(baton->rawDepth);
|
|
862
951
|
}
|
|
863
952
|
// Get raw image data
|
|
864
953
|
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
|
|
@@ -884,13 +973,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
884
973
|
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
|
885
974
|
bool const isGif = sharp::IsGif(baton->fileOut);
|
|
886
975
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
|
976
|
+
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
|
887
977
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
|
888
978
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
|
889
979
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
|
890
980
|
bool const isV = sharp::IsV(baton->fileOut);
|
|
891
981
|
bool const mightMatchInput = baton->formatOut == "input";
|
|
892
982
|
bool const willMatchInput = mightMatchInput &&
|
|
893
|
-
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
|
983
|
+
!(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
|
|
894
984
|
|
|
895
985
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
|
896
986
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -901,8 +991,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
901
991
|
->set("Q", baton->jpegQuality)
|
|
902
992
|
->set("interlace", baton->jpegProgressive)
|
|
903
993
|
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
904
|
-
?
|
|
905
|
-
:
|
|
994
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
995
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
906
996
|
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
907
997
|
->set("quant_table", baton->jpegQuantisationTable)
|
|
908
998
|
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
@@ -910,9 +1000,20 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
910
1000
|
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
911
1001
|
baton->formatOut = "jpeg";
|
|
912
1002
|
baton->channels = std::min(baton->channels, 3);
|
|
1003
|
+
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
|
1004
|
+
(willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
|
|
1005
|
+
// Write JP2 to file
|
|
1006
|
+
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
|
1007
|
+
image.jp2ksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1008
|
+
->set("Q", baton->jp2Quality)
|
|
1009
|
+
->set("lossless", baton->jp2Lossless)
|
|
1010
|
+
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
|
1011
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1012
|
+
->set("tile_height", baton->jp2TileHeight)
|
|
1013
|
+
->set("tile_width", baton->jp2TileWidth));
|
|
1014
|
+
baton->formatOut = "jp2";
|
|
913
1015
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
|
914
|
-
(inputImageType == sharp::ImageType::PNG ||
|
|
915
|
-
inputImageType == sharp::ImageType::SVG))) {
|
|
1016
|
+
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
|
916
1017
|
// Write PNG to file
|
|
917
1018
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
918
1019
|
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
@@ -922,7 +1023,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
922
1023
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
923
1024
|
->set("palette", baton->pngPalette)
|
|
924
1025
|
->set("Q", baton->pngQuality)
|
|
925
|
-
->set("
|
|
1026
|
+
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
|
|
1027
|
+
->set("effort", baton->pngEffort)
|
|
926
1028
|
->set("dither", baton->pngDither));
|
|
927
1029
|
baton->formatOut = "png";
|
|
928
1030
|
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
|
@@ -935,18 +1037,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
935
1037
|
->set("lossless", baton->webpLossless)
|
|
936
1038
|
->set("near_lossless", baton->webpNearLossless)
|
|
937
1039
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
938
|
-
->set("
|
|
1040
|
+
->set("effort", baton->webpEffort)
|
|
939
1041
|
->set("alpha_q", baton->webpAlphaQuality));
|
|
940
1042
|
baton->formatOut = "webp";
|
|
941
1043
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
|
942
|
-
(willMatchInput && inputImageType == sharp::ImageType::GIF
|
|
1044
|
+
(willMatchInput && inputImageType == sharp::ImageType::GIF)) {
|
|
943
1045
|
// Write GIF to file
|
|
944
1046
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
945
|
-
image.
|
|
1047
|
+
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
946
1048
|
->set("strip", !baton->withMetadata)
|
|
947
|
-
->set("
|
|
948
|
-
->set("
|
|
949
|
-
->set("
|
|
1049
|
+
->set("bitdepth", baton->gifBitdepth)
|
|
1050
|
+
->set("effort", baton->gifEffort)
|
|
1051
|
+
->set("dither", baton->gifDither));
|
|
950
1052
|
baton->formatOut = "gif";
|
|
951
1053
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
|
952
1054
|
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
|
|
@@ -970,20 +1072,20 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
970
1072
|
->set("tile_height", baton->tiffTileHeight)
|
|
971
1073
|
->set("tile_width", baton->tiffTileWidth)
|
|
972
1074
|
->set("xres", baton->tiffXres)
|
|
973
|
-
->set("yres", baton->tiffYres)
|
|
1075
|
+
->set("yres", baton->tiffYres)
|
|
1076
|
+
->set("resunit", baton->tiffResolutionUnit));
|
|
974
1077
|
baton->formatOut = "tiff";
|
|
975
1078
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
|
976
1079
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
977
1080
|
// Write HEIF to file
|
|
1081
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
978
1082
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
979
1083
|
->set("strip", !baton->withMetadata)
|
|
980
1084
|
->set("Q", baton->heifQuality)
|
|
981
1085
|
->set("compression", baton->heifCompression)
|
|
982
|
-
->set("
|
|
983
|
-
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
|
|
1086
|
+
->set("effort", baton->heifEffort)
|
|
984
1087
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
985
1088
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
986
|
-
#endif
|
|
987
1089
|
->set("lossless", baton->heifLossless));
|
|
988
1090
|
baton->formatOut = "heif";
|
|
989
1091
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
|
@@ -1006,7 +1108,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1006
1108
|
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
|
1007
1109
|
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
|
1008
1110
|
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
|
1009
|
-
{"
|
|
1111
|
+
{"effort", std::to_string(baton->webpEffort)}
|
|
1010
1112
|
};
|
|
1011
1113
|
suffix = AssembleSuffixString(".webp", options);
|
|
1012
1114
|
} else {
|
|
@@ -1038,6 +1140,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1038
1140
|
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
|
1039
1141
|
->set("background", baton->tileBackground)
|
|
1040
1142
|
->set("centre", baton->tileCentre)
|
|
1143
|
+
->set("id", const_cast<char*>(baton->tileId.data()))
|
|
1041
1144
|
->set("skip_blanks", baton->tileSkipBlanks);
|
|
1042
1145
|
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
|
1043
1146
|
// not passing anything - libvips will handle choice
|
|
@@ -1099,6 +1202,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1099
1202
|
info.Set("width", static_cast<uint32_t>(width));
|
|
1100
1203
|
info.Set("height", static_cast<uint32_t>(height));
|
|
1101
1204
|
info.Set("channels", static_cast<uint32_t>(baton->channels));
|
|
1205
|
+
if (baton->formatOut == "raw") {
|
|
1206
|
+
info.Set("depth", vips_enum_nick(VIPS_TYPE_BAND_FORMAT, baton->rawDepth));
|
|
1207
|
+
}
|
|
1102
1208
|
info.Set("premultiplied", baton->premultiplied);
|
|
1103
1209
|
if (baton->hasCropOffset) {
|
|
1104
1210
|
info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
|
|
@@ -1151,6 +1257,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1151
1257
|
Napi::FunctionReference debuglog;
|
|
1152
1258
|
Napi::FunctionReference queueListener;
|
|
1153
1259
|
|
|
1260
|
+
void MultiPageUnsupported(int const pages, std::string op) {
|
|
1261
|
+
if (pages > 1) {
|
|
1262
|
+
throw vips::VError(op + " is not supported for multi-page images");
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1154
1266
|
/*
|
|
1155
1267
|
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1156
1268
|
By default, returns zero, i.e. no rotation.
|
|
@@ -1241,15 +1353,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1241
1353
|
// Canvas option
|
|
1242
1354
|
std::string canvas = sharp::AttrAsStr(options, "canvas");
|
|
1243
1355
|
if (canvas == "crop") {
|
|
1244
|
-
baton->canvas = Canvas::CROP;
|
|
1356
|
+
baton->canvas = sharp::Canvas::CROP;
|
|
1245
1357
|
} else if (canvas == "embed") {
|
|
1246
|
-
baton->canvas = Canvas::EMBED;
|
|
1358
|
+
baton->canvas = sharp::Canvas::EMBED;
|
|
1247
1359
|
} else if (canvas == "max") {
|
|
1248
|
-
baton->canvas = Canvas::MAX;
|
|
1360
|
+
baton->canvas = sharp::Canvas::MAX;
|
|
1249
1361
|
} else if (canvas == "min") {
|
|
1250
|
-
baton->canvas = Canvas::MIN;
|
|
1362
|
+
baton->canvas = sharp::Canvas::MIN;
|
|
1251
1363
|
} else if (canvas == "ignore_aspect") {
|
|
1252
|
-
baton->canvas = Canvas::IGNORE_ASPECT;
|
|
1364
|
+
baton->canvas = sharp::Canvas::IGNORE_ASPECT;
|
|
1253
1365
|
}
|
|
1254
1366
|
// Tint chroma
|
|
1255
1367
|
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
|
@@ -1272,6 +1384,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1272
1384
|
}
|
|
1273
1385
|
// Resize options
|
|
1274
1386
|
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
|
|
1387
|
+
baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
|
|
1275
1388
|
baton->position = sharp::AttrAsInt32(options, "position");
|
|
1276
1389
|
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
|
|
1277
1390
|
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
|
@@ -1288,14 +1401,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1288
1401
|
baton->flatten = sharp::AttrAsBool(options, "flatten");
|
|
1289
1402
|
baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
|
|
1290
1403
|
baton->negate = sharp::AttrAsBool(options, "negate");
|
|
1404
|
+
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
|
|
1291
1405
|
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
|
1292
1406
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
|
1293
1407
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
|
1294
1408
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
|
1409
|
+
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
|
1295
1410
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
|
1296
1411
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
|
1297
|
-
baton->
|
|
1298
|
-
baton->
|
|
1412
|
+
baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
|
|
1413
|
+
baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
|
|
1414
|
+
baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
|
|
1415
|
+
baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
|
|
1416
|
+
baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
|
|
1299
1417
|
baton->threshold = sharp::AttrAsInt32(options, "threshold");
|
|
1300
1418
|
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
|
|
1301
1419
|
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
|
@@ -1305,6 +1423,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1305
1423
|
baton->linearB = sharp::AttrAsDouble(options, "linearB");
|
|
1306
1424
|
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
|
|
1307
1425
|
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
|
1426
|
+
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
|
1427
|
+
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
|
1428
|
+
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
|
1308
1429
|
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
|
1309
1430
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
|
1310
1431
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
|
@@ -1324,10 +1445,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1324
1445
|
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
|
|
1325
1446
|
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
|
1326
1447
|
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
|
1327
|
-
baton->affineInterpolator =
|
|
1328
|
-
|
|
1448
|
+
baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator");
|
|
1329
1449
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
|
1330
|
-
baton->ensureAlpha = sharp::
|
|
1450
|
+
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
|
1331
1451
|
if (options.Has("boolean")) {
|
|
1332
1452
|
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
|
|
1333
1453
|
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
|
|
@@ -1355,6 +1475,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1355
1475
|
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
|
1356
1476
|
}
|
|
1357
1477
|
}
|
|
1478
|
+
baton->colourspaceInput = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspaceInput"));
|
|
1479
|
+
if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
|
|
1480
|
+
baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
|
|
1481
|
+
}
|
|
1358
1482
|
baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
|
|
1359
1483
|
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
|
|
1360
1484
|
baton->colourspace = VIPS_INTERPRETATION_sRGB;
|
|
@@ -1364,7 +1488,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1364
1488
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
|
1365
1489
|
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
|
|
1366
1490
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
|
1491
|
+
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
|
1367
1492
|
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
|
|
1493
|
+
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
|
|
1494
|
+
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
|
1495
|
+
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
|
|
1496
|
+
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
|
1497
|
+
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
|
1498
|
+
}
|
|
1499
|
+
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
|
1368
1500
|
// Format-specific
|
|
1369
1501
|
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
|
1370
1502
|
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
|
|
@@ -1379,14 +1511,23 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1379
1511
|
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
|
1380
1512
|
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
|
1381
1513
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
|
1382
|
-
baton->
|
|
1514
|
+
baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort");
|
|
1515
|
+
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
|
1383
1516
|
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
|
1517
|
+
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
|
|
1518
|
+
baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless");
|
|
1519
|
+
baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight");
|
|
1520
|
+
baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth");
|
|
1521
|
+
baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling");
|
|
1384
1522
|
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
|
1385
1523
|
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
|
1386
1524
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
|
1387
1525
|
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
|
1388
1526
|
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
|
1389
|
-
baton->
|
|
1527
|
+
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
|
1528
|
+
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1529
|
+
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1530
|
+
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
1390
1531
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
|
1391
1532
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
|
1392
1533
|
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
|
@@ -1395,6 +1536,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1395
1536
|
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
|
|
1396
1537
|
baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
|
|
1397
1538
|
baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
|
|
1539
|
+
if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
|
|
1540
|
+
baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
|
|
1541
|
+
}
|
|
1398
1542
|
// tiff compression options
|
|
1399
1543
|
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
|
|
1400
1544
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
|
|
@@ -1402,25 +1546,28 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1402
1546
|
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
|
1403
1547
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
|
1404
1548
|
sharp::AttrAsStr(options, "tiffPredictor").data()));
|
|
1549
|
+
baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
|
|
1550
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
|
|
1551
|
+
sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
|
|
1552
|
+
|
|
1405
1553
|
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
|
|
1406
1554
|
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
|
|
1407
1555
|
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
|
1408
1556
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
|
1409
1557
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
|
1410
|
-
baton->
|
|
1558
|
+
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1411
1559
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1560
|
+
// Raw output
|
|
1561
|
+
baton->rawDepth = static_cast<VipsBandFormat>(
|
|
1562
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
|
|
1563
|
+
sharp::AttrAsStr(options, "rawDepth").data()));
|
|
1564
|
+
// Animated output properties
|
|
1417
1565
|
if (sharp::HasAttr(options, "loop")) {
|
|
1418
1566
|
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
1419
1567
|
}
|
|
1420
1568
|
if (sharp::HasAttr(options, "delay")) {
|
|
1421
1569
|
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
|
1422
1570
|
}
|
|
1423
|
-
|
|
1424
1571
|
// Tile output
|
|
1425
1572
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
|
1426
1573
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
|
@@ -1438,6 +1585,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1438
1585
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
|
|
1439
1586
|
sharp::AttrAsStr(options, "tileDepth").data()));
|
|
1440
1587
|
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
|
1588
|
+
baton->tileId = sharp::AttrAsStr(options, "tileId");
|
|
1441
1589
|
|
|
1442
1590
|
// Force random access for certain operations
|
|
1443
1591
|
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|