sharp 0.29.3 → 0.30.2
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 +5 -5
- package/install/libvips.js +39 -8
- package/lib/composite.js +0 -1
- package/lib/constructor.js +10 -3
- package/lib/input.js +21 -4
- package/lib/libvips.js +24 -5
- package/lib/operation.js +14 -1
- package/lib/output.js +129 -69
- package/lib/platform.js +1 -1
- package/lib/resize.js +19 -0
- package/lib/sharp.js +2 -1
- package/lib/utility.js +22 -4
- package/package.json +27 -12
- package/src/common.cc +102 -19
- package/src/common.h +29 -5
- package/src/libvips/cplusplus/VImage.cpp +34 -16
- package/src/libvips/cplusplus/vips-operators.cpp +29 -1
- package/src/metadata.cc +6 -0
- package/src/metadata.h +1 -0
- package/src/operations.cc +94 -0
- package/src/operations.h +12 -0
- package/src/pipeline.cc +296 -270
- package/src/pipeline.h +17 -19
package/src/pipeline.cc
CHANGED
|
@@ -69,15 +69,24 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
69
69
|
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
|
70
70
|
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
|
|
71
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);
|
|
82
|
+
|
|
72
83
|
// Calculate angle of rotation
|
|
73
84
|
VipsAngle rotation;
|
|
85
|
+
bool flip = FALSE;
|
|
86
|
+
bool flop = FALSE;
|
|
74
87
|
if (baton->useExifOrientation) {
|
|
75
88
|
// Rotate and flip image according to Exif orientation
|
|
76
|
-
bool flip;
|
|
77
|
-
bool flop;
|
|
78
89
|
std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
|
79
|
-
baton->flip = baton->flip || flip;
|
|
80
|
-
baton->flop = baton->flop || flop;
|
|
81
90
|
} else {
|
|
82
91
|
rotation = CalculateAngleRotation(baton->angle);
|
|
83
92
|
}
|
|
@@ -86,9 +95,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
86
95
|
if (baton->rotateBeforePreExtract) {
|
|
87
96
|
if (rotation != VIPS_ANGLE_D0) {
|
|
88
97
|
image = image.rot(rotation);
|
|
98
|
+
if (flip) {
|
|
99
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
100
|
+
flip = FALSE;
|
|
101
|
+
}
|
|
102
|
+
if (flop) {
|
|
103
|
+
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
104
|
+
flop = FALSE;
|
|
105
|
+
}
|
|
89
106
|
image = sharp::RemoveExifOrientation(image);
|
|
90
107
|
}
|
|
91
108
|
if (baton->rotationAngle != 0.0) {
|
|
109
|
+
MultiPageUnsupported(nPages, "Rotate");
|
|
92
110
|
std::vector<double> background;
|
|
93
111
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
|
94
112
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
@@ -97,6 +115,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
97
115
|
|
|
98
116
|
// Trim
|
|
99
117
|
if (baton->trimThreshold > 0.0) {
|
|
118
|
+
MultiPageUnsupported(nPages, "Trim");
|
|
100
119
|
image = sharp::Trim(image, baton->trimThreshold);
|
|
101
120
|
baton->trimOffsetLeft = image.xoffset();
|
|
102
121
|
baton->trimOffsetTop = image.yoffset();
|
|
@@ -104,194 +123,168 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
104
123
|
|
|
105
124
|
// Pre extraction
|
|
106
125
|
if (baton->topOffsetPre != -1) {
|
|
107
|
-
image =
|
|
126
|
+
image = nPages > 1
|
|
127
|
+
? sharp::CropMultiPage(image,
|
|
128
|
+
baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight)
|
|
129
|
+
: image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
// Get pre-resize image width and height
|
|
111
133
|
int inputWidth = image.width();
|
|
112
134
|
int inputHeight = image.height();
|
|
113
|
-
if (!baton->rotateBeforePreExtract &&
|
|
114
|
-
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
|
|
115
|
-
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
116
|
-
std::swap(inputWidth, inputHeight);
|
|
117
|
-
}
|
|
118
135
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (baton->width > inputWidth) {
|
|
123
|
-
baton->width = inputWidth;
|
|
124
|
-
}
|
|
125
|
-
if (baton->height > inputHeight) {
|
|
126
|
-
baton->height = inputHeight;
|
|
127
|
-
}
|
|
136
|
+
// Is there just one page? Shrink to inputHeight instead
|
|
137
|
+
if (nPages == 1) {
|
|
138
|
+
pageHeight = inputHeight;
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
// Scaling calculations
|
|
131
|
-
double
|
|
132
|
-
double
|
|
142
|
+
double hshrink;
|
|
143
|
+
double vshrink;
|
|
133
144
|
int targetResizeWidth = baton->width;
|
|
134
145
|
int targetResizeHeight = baton->height;
|
|
135
|
-
if (baton->width > 0 && baton->height > 0) {
|
|
136
|
-
// Fixed width and height
|
|
137
|
-
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
138
|
-
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
139
|
-
switch (baton->canvas) {
|
|
140
|
-
case Canvas::CROP:
|
|
141
|
-
if (xfactor < yfactor) {
|
|
142
|
-
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
143
|
-
yfactor = xfactor;
|
|
144
|
-
} else {
|
|
145
|
-
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
146
|
-
xfactor = yfactor;
|
|
147
|
-
}
|
|
148
|
-
break;
|
|
149
|
-
case Canvas::EMBED:
|
|
150
|
-
if (xfactor > yfactor) {
|
|
151
|
-
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
152
|
-
yfactor = xfactor;
|
|
153
|
-
} else {
|
|
154
|
-
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
155
|
-
xfactor = yfactor;
|
|
156
|
-
}
|
|
157
|
-
break;
|
|
158
|
-
case Canvas::MAX:
|
|
159
|
-
if (xfactor > yfactor) {
|
|
160
|
-
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
161
|
-
yfactor = xfactor;
|
|
162
|
-
} else {
|
|
163
|
-
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
164
|
-
xfactor = yfactor;
|
|
165
|
-
}
|
|
166
|
-
break;
|
|
167
|
-
case Canvas::MIN:
|
|
168
|
-
if (xfactor < yfactor) {
|
|
169
|
-
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
|
|
170
|
-
yfactor = xfactor;
|
|
171
|
-
} else {
|
|
172
|
-
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
|
|
173
|
-
xfactor = yfactor;
|
|
174
|
-
}
|
|
175
|
-
break;
|
|
176
|
-
case Canvas::IGNORE_ASPECT:
|
|
177
|
-
if (!baton->rotateBeforePreExtract &&
|
|
178
|
-
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
|
|
179
|
-
std::swap(xfactor, yfactor);
|
|
180
|
-
}
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
} else if (baton->width > 0) {
|
|
184
|
-
// Fixed width
|
|
185
|
-
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
186
|
-
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
|
187
|
-
targetResizeHeight = baton->height = inputHeight;
|
|
188
|
-
} else {
|
|
189
|
-
// Auto height
|
|
190
|
-
yfactor = xfactor;
|
|
191
|
-
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
|
|
192
|
-
}
|
|
193
|
-
} else if (baton->height > 0) {
|
|
194
|
-
// Fixed height
|
|
195
|
-
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
196
|
-
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
|
197
|
-
targetResizeWidth = baton->width = inputWidth;
|
|
198
|
-
} else {
|
|
199
|
-
// Auto width
|
|
200
|
-
xfactor = yfactor;
|
|
201
|
-
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
// Identity transform
|
|
205
|
-
baton->width = inputWidth;
|
|
206
|
-
baton->height = inputHeight;
|
|
207
|
-
}
|
|
208
146
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
|
|
147
|
+
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
148
|
+
bool swap = !baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270);
|
|
212
149
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
150
|
+
// Shrink to pageHeight, so we work for multi-page images
|
|
151
|
+
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
152
|
+
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
|
153
|
+
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
|
|
216
154
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
int shrink_on_load = 1;
|
|
155
|
+
// The jpeg preload shrink.
|
|
156
|
+
int jpegShrinkOnLoad = 1;
|
|
220
157
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
158
|
+
// WebP, PDF, SVG scale
|
|
159
|
+
double scale = 1.0;
|
|
160
|
+
|
|
161
|
+
// Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when:
|
|
162
|
+
// - the width or height parameters are specified;
|
|
163
|
+
// - gamma correction doesn't need to be applied;
|
|
164
|
+
// - trimming or pre-resize extract isn't required;
|
|
165
|
+
// - input colourspace is not specified;
|
|
166
|
+
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
|
230
167
|
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
|
|
231
|
-
baton->colourspaceInput == VIPS_INTERPRETATION_LAST
|
|
232
|
-
|
|
233
|
-
) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
168
|
+
baton->colourspaceInput == VIPS_INTERPRETATION_LAST;
|
|
169
|
+
|
|
170
|
+
if (shouldPreShrink) {
|
|
171
|
+
// The common part of the shrink: the bit by which both axes must be shrunk
|
|
172
|
+
double shrink = std::min(hshrink, vshrink);
|
|
173
|
+
|
|
174
|
+
if (inputImageType == sharp::ImageType::JPEG) {
|
|
175
|
+
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
|
|
176
|
+
// for more consistent results and to avoid extra sharpness to the image
|
|
177
|
+
int factor = baton->fastShrinkOnLoad ? 1 : 2;
|
|
178
|
+
if (shrink >= 8 * factor) {
|
|
179
|
+
jpegShrinkOnLoad = 8;
|
|
180
|
+
} else if (shrink >= 4 * factor) {
|
|
181
|
+
jpegShrinkOnLoad = 4;
|
|
182
|
+
} else if (shrink >= 2 * factor) {
|
|
183
|
+
jpegShrinkOnLoad = 2;
|
|
184
|
+
}
|
|
185
|
+
// Lower shrink-on-load for known libjpeg rounding errors
|
|
186
|
+
if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
|
|
187
|
+
jpegShrinkOnLoad /= 2;
|
|
188
|
+
}
|
|
189
|
+
} else if (inputImageType == sharp::ImageType::WEBP ||
|
|
190
|
+
inputImageType == sharp::ImageType::SVG ||
|
|
191
|
+
inputImageType == sharp::ImageType::PDF) {
|
|
192
|
+
scale = 1.0 / shrink;
|
|
246
193
|
}
|
|
247
194
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
if (shrink_on_load > 1) {
|
|
255
|
-
// Reload input using shrink-on-load
|
|
195
|
+
|
|
196
|
+
// Reload input using shrink-on-load, it'll be an integer shrink
|
|
197
|
+
// factor for jpegload*, a double scale factor for webpload*,
|
|
198
|
+
// pdfload* and svgload*
|
|
199
|
+
if (jpegShrinkOnLoad > 1) {
|
|
256
200
|
vips::VOption *option = VImage::option()
|
|
257
201
|
->set("access", baton->input->access)
|
|
258
|
-
->set("shrink",
|
|
202
|
+
->set("shrink", jpegShrinkOnLoad)
|
|
259
203
|
->set("fail", baton->input->failOnError);
|
|
260
204
|
if (baton->input->buffer != nullptr) {
|
|
205
|
+
// Reload JPEG buffer
|
|
261
206
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
262
|
-
|
|
263
|
-
// Reload JPEG buffer
|
|
264
|
-
image = VImage::jpegload_buffer(blob, option);
|
|
265
|
-
} else {
|
|
266
|
-
// Reload WebP buffer
|
|
267
|
-
image = VImage::webpload_buffer(blob, option);
|
|
268
|
-
}
|
|
207
|
+
image = VImage::jpegload_buffer(blob, option);
|
|
269
208
|
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
270
209
|
} else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
210
|
+
// Reload JPEG file
|
|
211
|
+
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
|
|
212
|
+
}
|
|
213
|
+
} else if (scale != 1.0) {
|
|
214
|
+
vips::VOption *option = VImage::option()
|
|
215
|
+
->set("access", baton->input->access)
|
|
216
|
+
->set("scale", scale)
|
|
217
|
+
->set("fail", baton->input->failOnError);
|
|
218
|
+
if (inputImageType == sharp::ImageType::WEBP) {
|
|
219
|
+
option->set("n", baton->input->pages);
|
|
220
|
+
option->set("page", baton->input->page);
|
|
221
|
+
|
|
222
|
+
if (baton->input->buffer != nullptr) {
|
|
223
|
+
// Reload WebP buffer
|
|
224
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
225
|
+
image = VImage::webpload_buffer(blob, option);
|
|
226
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
274
227
|
} else {
|
|
275
228
|
// Reload WebP file
|
|
276
229
|
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
|
|
277
230
|
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
231
|
+
} else if (inputImageType == sharp::ImageType::SVG) {
|
|
232
|
+
option->set("unlimited", baton->input->unlimited);
|
|
233
|
+
option->set("dpi", baton->input->density);
|
|
234
|
+
|
|
235
|
+
if (baton->input->buffer != nullptr) {
|
|
236
|
+
// Reload SVG buffer
|
|
237
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
238
|
+
image = VImage::svgload_buffer(blob, option);
|
|
239
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
240
|
+
} else {
|
|
241
|
+
// Reload SVG file
|
|
242
|
+
image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
sharp::SetDensity(image, baton->input->density);
|
|
246
|
+
} else if (inputImageType == sharp::ImageType::PDF) {
|
|
247
|
+
option->set("n", baton->input->pages);
|
|
248
|
+
option->set("page", baton->input->page);
|
|
249
|
+
option->set("dpi", baton->input->density);
|
|
250
|
+
|
|
251
|
+
if (baton->input->buffer != nullptr) {
|
|
252
|
+
// Reload PDF buffer
|
|
253
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
254
|
+
image = VImage::pdfload_buffer(blob, option);
|
|
255
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
256
|
+
} else {
|
|
257
|
+
// Reload PDF file
|
|
258
|
+
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
sharp::SetDensity(image, baton->input->density);
|
|
290
262
|
}
|
|
291
263
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
264
|
+
|
|
265
|
+
// Any pre-shrinking may already have been done
|
|
266
|
+
inputWidth = image.width();
|
|
267
|
+
inputHeight = image.height();
|
|
268
|
+
|
|
269
|
+
// After pre-shrink, but before the main shrink stage
|
|
270
|
+
// Reuse the initial pageHeight if we didn't pre-shrink
|
|
271
|
+
if (shouldPreShrink) {
|
|
272
|
+
pageHeight = sharp::GetPageHeight(image);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Shrink to pageHeight, so we work for multi-page images
|
|
276
|
+
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
277
|
+
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
|
278
|
+
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
|
|
279
|
+
|
|
280
|
+
int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
|
|
281
|
+
int targetPageHeight = targetHeight;
|
|
282
|
+
|
|
283
|
+
// In toilet-roll mode, we must adjust vshrink so that we exactly hit
|
|
284
|
+
// pageHeight or we'll have pixels straddling pixel boundaries
|
|
285
|
+
if (inputHeight > pageHeight) {
|
|
286
|
+
targetHeight *= nPages;
|
|
287
|
+
vshrink = static_cast<double>(inputHeight) / targetHeight;
|
|
295
288
|
}
|
|
296
289
|
|
|
297
290
|
// Ensure we're using a device-independent colour space
|
|
@@ -299,7 +292,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
299
292
|
if (
|
|
300
293
|
sharp::HasProfile(image) &&
|
|
301
294
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
302
|
-
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
|
295
|
+
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
|
296
|
+
image.interpretation() != VIPS_INTERPRETATION_B_W
|
|
303
297
|
) {
|
|
304
298
|
// Convert to sRGB/P3 using embedded profile
|
|
305
299
|
try {
|
|
@@ -345,7 +339,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
345
339
|
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
|
346
340
|
}
|
|
347
341
|
|
|
348
|
-
bool const shouldResize =
|
|
342
|
+
bool const shouldResize = hshrink != 1.0 || vshrink != 1.0;
|
|
349
343
|
bool const shouldBlur = baton->blurSigma != 0.0;
|
|
350
344
|
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
|
351
345
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
@@ -379,39 +373,33 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
379
373
|
) {
|
|
380
374
|
throw vips::VError("Unknown kernel");
|
|
381
375
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
xfactor = 2 * image.width();
|
|
385
|
-
if (baton->canvas != Canvas::EMBED) {
|
|
386
|
-
baton->width = 1;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (image.height() / yfactor < 0.5) {
|
|
390
|
-
yfactor = 2 * image.height();
|
|
391
|
-
if (baton->canvas != Canvas::EMBED) {
|
|
392
|
-
baton->height = 1;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
image = image.resize(1.0 / xfactor, VImage::option()
|
|
396
|
-
->set("vscale", 1.0 / yfactor)
|
|
376
|
+
image = image.resize(1.0 / hshrink, VImage::option()
|
|
377
|
+
->set("vscale", 1.0 / vshrink)
|
|
397
378
|
->set("kernel", kernel));
|
|
398
379
|
}
|
|
399
380
|
|
|
400
381
|
// Rotate post-extract 90-angle
|
|
401
|
-
if (!baton->rotateBeforePreExtract &&
|
|
402
|
-
|
|
403
|
-
|
|
382
|
+
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
|
383
|
+
image = image.rot(rotation);
|
|
384
|
+
if (flip) {
|
|
385
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
386
|
+
flip = FALSE;
|
|
387
|
+
}
|
|
388
|
+
if (flop) {
|
|
389
|
+
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
390
|
+
flop = FALSE;
|
|
391
|
+
}
|
|
392
|
+
image = sharp::RemoveExifOrientation(image);
|
|
404
393
|
}
|
|
405
394
|
|
|
406
|
-
|
|
407
395
|
// Flip (mirror about Y axis)
|
|
408
|
-
if (baton->flip) {
|
|
396
|
+
if (baton->flip || flip) {
|
|
409
397
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
410
398
|
image = sharp::RemoveExifOrientation(image);
|
|
411
399
|
}
|
|
412
400
|
|
|
413
401
|
// Flop (mirror about X axis)
|
|
414
|
-
if (baton->flop) {
|
|
402
|
+
if (baton->flop || flop) {
|
|
415
403
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
416
404
|
image = sharp::RemoveExifOrientation(image);
|
|
417
405
|
}
|
|
@@ -429,52 +417,68 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
429
417
|
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
|
|
430
418
|
}
|
|
431
419
|
|
|
420
|
+
inputWidth = image.width();
|
|
421
|
+
inputHeight = nPages > 1 ? targetPageHeight : image.height();
|
|
422
|
+
|
|
423
|
+
// Resolve dimensions
|
|
424
|
+
if (baton->width <= 0) {
|
|
425
|
+
baton->width = inputWidth;
|
|
426
|
+
}
|
|
427
|
+
if (baton->height <= 0) {
|
|
428
|
+
baton->height = inputHeight;
|
|
429
|
+
}
|
|
430
|
+
|
|
432
431
|
// Crop/embed
|
|
433
|
-
if (
|
|
434
|
-
if (baton->canvas == Canvas::EMBED) {
|
|
432
|
+
if (inputWidth != baton->width || inputHeight != baton->height) {
|
|
433
|
+
if (baton->canvas == sharp::Canvas::EMBED) {
|
|
435
434
|
std::vector<double> background;
|
|
436
435
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
|
437
436
|
|
|
438
437
|
// Embed
|
|
439
438
|
|
|
440
|
-
// Calculate where to position the
|
|
439
|
+
// Calculate where to position the embedded image if gravity specified, else center.
|
|
441
440
|
int left;
|
|
442
441
|
int top;
|
|
443
442
|
|
|
444
|
-
left = static_cast<int>(round((baton->width -
|
|
445
|
-
top = static_cast<int>(round((baton->height -
|
|
443
|
+
left = static_cast<int>(round((baton->width - inputWidth) / 2));
|
|
444
|
+
top = static_cast<int>(round((baton->height - inputHeight) / 2));
|
|
446
445
|
|
|
447
|
-
int width = std::max(
|
|
448
|
-
int height = std::max(
|
|
446
|
+
int width = std::max(inputWidth, baton->width);
|
|
447
|
+
int height = std::max(inputHeight, baton->height);
|
|
449
448
|
std::tie(left, top) = sharp::CalculateEmbedPosition(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
image =
|
|
453
|
-
|
|
454
|
-
|
|
449
|
+
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
450
|
+
|
|
451
|
+
image = nPages > 1
|
|
452
|
+
? sharp::EmbedMultiPage(image,
|
|
453
|
+
left, top, width, height, background, nPages, &targetPageHeight)
|
|
454
|
+
: image.embed(left, top, width, height, VImage::option()
|
|
455
|
+
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
456
|
+
->set("background", background));
|
|
457
|
+
} else if (baton->canvas == sharp::Canvas::CROP) {
|
|
458
|
+
if (baton->width > inputWidth) {
|
|
459
|
+
baton->width = inputWidth;
|
|
460
|
+
}
|
|
461
|
+
if (baton->height > inputHeight) {
|
|
462
|
+
baton->height = inputHeight;
|
|
463
|
+
}
|
|
455
464
|
|
|
456
|
-
|
|
457
|
-
baton->canvas != Canvas::IGNORE_ASPECT &&
|
|
458
|
-
(image.width() > baton->width || image.height() > baton->height)
|
|
459
|
-
) {
|
|
460
|
-
// Crop/max/min
|
|
465
|
+
// Crop
|
|
461
466
|
if (baton->position < 9) {
|
|
462
467
|
// Gravity-based crop
|
|
463
468
|
int left;
|
|
464
469
|
int top;
|
|
465
470
|
std::tie(left, top) = sharp::CalculateCrop(
|
|
466
|
-
|
|
467
|
-
int width = std::min(
|
|
468
|
-
int height = std::min(
|
|
469
|
-
|
|
471
|
+
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
472
|
+
int width = std::min(inputWidth, baton->width);
|
|
473
|
+
int height = std::min(inputHeight, baton->height);
|
|
474
|
+
|
|
475
|
+
image = nPages > 1
|
|
476
|
+
? sharp::CropMultiPage(image,
|
|
477
|
+
left, top, width, height, nPages, &targetPageHeight)
|
|
478
|
+
: image.extract_area(left, top, width, height);
|
|
470
479
|
} else {
|
|
471
480
|
// Attention-based or Entropy-based crop
|
|
472
|
-
|
|
473
|
-
baton->width = image.width();
|
|
474
|
-
}
|
|
475
|
-
if (baton->height > image.height()) {
|
|
476
|
-
baton->height = image.height();
|
|
477
|
-
}
|
|
481
|
+
MultiPageUnsupported(nPages, "Resize strategy");
|
|
478
482
|
image = image.tilecache(VImage::option()
|
|
479
483
|
->set("access", VIPS_ACCESS_RANDOM)
|
|
480
484
|
->set("threaded", TRUE));
|
|
@@ -489,6 +493,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
489
493
|
|
|
490
494
|
// Rotate post-extract non-90 angle
|
|
491
495
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
|
496
|
+
MultiPageUnsupported(nPages, "Rotate");
|
|
492
497
|
std::vector<double> background;
|
|
493
498
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
494
499
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
@@ -496,20 +501,32 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
496
501
|
|
|
497
502
|
// Post extraction
|
|
498
503
|
if (baton->topOffsetPost != -1) {
|
|
499
|
-
|
|
500
|
-
|
|
504
|
+
if (nPages > 1) {
|
|
505
|
+
image = sharp::CropMultiPage(image,
|
|
506
|
+
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
|
|
507
|
+
nPages, &targetPageHeight);
|
|
508
|
+
|
|
509
|
+
// heightPost is used in the info object, so update to reflect the number of pages
|
|
510
|
+
baton->heightPost *= nPages;
|
|
511
|
+
} else {
|
|
512
|
+
image = image.extract_area(
|
|
513
|
+
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
|
514
|
+
}
|
|
501
515
|
}
|
|
502
516
|
|
|
503
517
|
// Affine transform
|
|
504
518
|
if (baton->affineMatrix.size() > 0) {
|
|
519
|
+
MultiPageUnsupported(nPages, "Affine");
|
|
505
520
|
std::vector<double> background;
|
|
506
521
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
522
|
+
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
|
|
523
|
+
const_cast<char*>(baton->affineInterpolator.data()));
|
|
507
524
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
|
508
525
|
->set("idx", baton->affineIdx)
|
|
509
526
|
->set("idy", baton->affineIdy)
|
|
510
527
|
->set("odx", baton->affineOdx)
|
|
511
528
|
->set("ody", baton->affineOdy)
|
|
512
|
-
->set("interpolate",
|
|
529
|
+
->set("interpolate", interp));
|
|
513
530
|
}
|
|
514
531
|
|
|
515
532
|
// Extend edges
|
|
@@ -519,10 +536,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
519
536
|
|
|
520
537
|
// Embed
|
|
521
538
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
|
522
|
-
baton->height = image.height() + baton->extendTop + baton->extendBottom;
|
|
539
|
+
baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
|
|
523
540
|
|
|
524
|
-
image =
|
|
525
|
-
|
|
541
|
+
image = nPages > 1
|
|
542
|
+
? sharp::EmbedMultiPage(image,
|
|
543
|
+
baton->extendLeft, baton->extendTop, baton->width, baton->height, background, nPages, &targetPageHeight)
|
|
544
|
+
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
545
|
+
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
|
|
526
546
|
}
|
|
527
547
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
528
548
|
if (shouldApplyMedian) {
|
|
@@ -562,6 +582,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
562
582
|
|
|
563
583
|
// Composite
|
|
564
584
|
if (shouldComposite) {
|
|
585
|
+
std::vector<VImage> images = { image };
|
|
586
|
+
std::vector<int> modes, xs, ys;
|
|
565
587
|
for (Composite *composite : baton->composite) {
|
|
566
588
|
VImage compositeImage;
|
|
567
589
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
|
@@ -607,12 +629,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
607
629
|
// gravity was used for extract_area, set it back to its default value of 0
|
|
608
630
|
composite->gravity = 0;
|
|
609
631
|
}
|
|
610
|
-
// Ensure image to composite is sRGB with
|
|
632
|
+
// Ensure image to composite is sRGB with unpremultiplied alpha
|
|
611
633
|
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
612
634
|
if (!sharp::HasAlpha(compositeImage)) {
|
|
613
635
|
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
614
636
|
}
|
|
615
|
-
if (
|
|
637
|
+
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
|
616
638
|
// Calculate position
|
|
617
639
|
int left;
|
|
618
640
|
int top;
|
|
@@ -630,12 +652,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
630
652
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
631
653
|
compositeImage.width(), compositeImage.height(), composite->gravity);
|
|
632
654
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
->set("y", top));
|
|
655
|
+
images.push_back(compositeImage);
|
|
656
|
+
modes.push_back(composite->mode);
|
|
657
|
+
xs.push_back(left);
|
|
658
|
+
ys.push_back(top);
|
|
638
659
|
}
|
|
660
|
+
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
|
|
639
661
|
}
|
|
640
662
|
|
|
641
663
|
// Reverse premultiplication after all transformations:
|
|
@@ -762,14 +784,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
762
784
|
baton->width = image.width();
|
|
763
785
|
baton->height = image.height();
|
|
764
786
|
|
|
765
|
-
bool const supportsGifOutput = vips_type_find("VipsOperation", "magicksave") != 0 &&
|
|
766
|
-
vips_type_find("VipsOperation", "magicksave_buffer") != 0;
|
|
767
|
-
|
|
768
787
|
image = sharp::SetAnimationProperties(
|
|
769
|
-
image,
|
|
770
|
-
baton->pageHeight,
|
|
771
|
-
baton->delay,
|
|
772
|
-
baton->loop);
|
|
788
|
+
image, nPages, targetPageHeight, baton->delay, baton->loop);
|
|
773
789
|
|
|
774
790
|
// Output
|
|
775
791
|
sharp::SetTimeout(image, baton->timeoutSeconds);
|
|
@@ -817,8 +833,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
817
833
|
vips_area_unref(area);
|
|
818
834
|
baton->formatOut = "jp2";
|
|
819
835
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
|
820
|
-
(inputImageType == sharp::ImageType::PNG ||
|
|
821
|
-
inputImageType == sharp::ImageType::SVG))) {
|
|
836
|
+
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
|
822
837
|
// Write PNG to buffer
|
|
823
838
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
824
839
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
|
@@ -828,7 +843,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
828
843
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
829
844
|
->set("palette", baton->pngPalette)
|
|
830
845
|
->set("Q", baton->pngQuality)
|
|
831
|
-
->set("
|
|
846
|
+
->set("effort", baton->pngEffort)
|
|
847
|
+
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
|
|
832
848
|
->set("dither", baton->pngDither)));
|
|
833
849
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
834
850
|
baton->bufferOutLength = area->length;
|
|
@@ -845,7 +861,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
845
861
|
->set("lossless", baton->webpLossless)
|
|
846
862
|
->set("near_lossless", baton->webpNearLossless)
|
|
847
863
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
848
|
-
->set("
|
|
864
|
+
->set("effort", baton->webpEffort)
|
|
849
865
|
->set("alpha_q", baton->webpAlphaQuality)));
|
|
850
866
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
851
867
|
baton->bufferOutLength = area->length;
|
|
@@ -853,14 +869,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
853
869
|
vips_area_unref(area);
|
|
854
870
|
baton->formatOut = "webp";
|
|
855
871
|
} else if (baton->formatOut == "gif" ||
|
|
856
|
-
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF
|
|
872
|
+
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {
|
|
857
873
|
// Write GIF to buffer
|
|
858
874
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
859
|
-
VipsArea *area = reinterpret_cast<VipsArea*>(image.
|
|
875
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
|
860
876
|
->set("strip", !baton->withMetadata)
|
|
861
|
-
->set("
|
|
862
|
-
->set("
|
|
863
|
-
->set("
|
|
877
|
+
->set("bitdepth", baton->gifBitdepth)
|
|
878
|
+
->set("effort", baton->gifEffort)
|
|
879
|
+
->set("dither", baton->gifDither)));
|
|
864
880
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
865
881
|
baton->bufferOutLength = area->length;
|
|
866
882
|
area->free_fn = nullptr;
|
|
@@ -888,7 +904,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
888
904
|
->set("tile_height", baton->tiffTileHeight)
|
|
889
905
|
->set("tile_width", baton->tiffTileWidth)
|
|
890
906
|
->set("xres", baton->tiffXres)
|
|
891
|
-
->set("yres", baton->tiffYres)
|
|
907
|
+
->set("yres", baton->tiffYres)
|
|
908
|
+
->set("resunit", baton->tiffResolutionUnit)));
|
|
892
909
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
893
910
|
baton->bufferOutLength = area->length;
|
|
894
911
|
area->free_fn = nullptr;
|
|
@@ -902,7 +919,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
902
919
|
->set("strip", !baton->withMetadata)
|
|
903
920
|
->set("Q", baton->heifQuality)
|
|
904
921
|
->set("compression", baton->heifCompression)
|
|
905
|
-
->set("
|
|
922
|
+
->set("effort", baton->heifEffort)
|
|
906
923
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
907
924
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
908
925
|
->set("lossless", baton->heifLossless)));
|
|
@@ -987,8 +1004,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
987
1004
|
->set("tile_width", baton->jp2TileWidth));
|
|
988
1005
|
baton->formatOut = "jp2";
|
|
989
1006
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
|
990
|
-
(inputImageType == sharp::ImageType::PNG ||
|
|
991
|
-
inputImageType == sharp::ImageType::SVG))) {
|
|
1007
|
+
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
|
992
1008
|
// Write PNG to file
|
|
993
1009
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
994
1010
|
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
@@ -998,7 +1014,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
998
1014
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
999
1015
|
->set("palette", baton->pngPalette)
|
|
1000
1016
|
->set("Q", baton->pngQuality)
|
|
1001
|
-
->set("bitdepth", baton->pngBitdepth)
|
|
1017
|
+
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
|
|
1018
|
+
->set("effort", baton->pngEffort)
|
|
1002
1019
|
->set("dither", baton->pngDither));
|
|
1003
1020
|
baton->formatOut = "png";
|
|
1004
1021
|
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
|
@@ -1011,18 +1028,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1011
1028
|
->set("lossless", baton->webpLossless)
|
|
1012
1029
|
->set("near_lossless", baton->webpNearLossless)
|
|
1013
1030
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
1014
|
-
->set("
|
|
1031
|
+
->set("effort", baton->webpEffort)
|
|
1015
1032
|
->set("alpha_q", baton->webpAlphaQuality));
|
|
1016
1033
|
baton->formatOut = "webp";
|
|
1017
1034
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
|
1018
|
-
(willMatchInput && inputImageType == sharp::ImageType::GIF
|
|
1035
|
+
(willMatchInput && inputImageType == sharp::ImageType::GIF)) {
|
|
1019
1036
|
// Write GIF to file
|
|
1020
1037
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
1021
|
-
image.
|
|
1038
|
+
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1022
1039
|
->set("strip", !baton->withMetadata)
|
|
1023
|
-
->set("
|
|
1024
|
-
->set("
|
|
1025
|
-
->set("
|
|
1040
|
+
->set("bitdepth", baton->gifBitdepth)
|
|
1041
|
+
->set("effort", baton->gifEffort)
|
|
1042
|
+
->set("dither", baton->gifDither));
|
|
1026
1043
|
baton->formatOut = "gif";
|
|
1027
1044
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
|
1028
1045
|
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
|
|
@@ -1046,7 +1063,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1046
1063
|
->set("tile_height", baton->tiffTileHeight)
|
|
1047
1064
|
->set("tile_width", baton->tiffTileWidth)
|
|
1048
1065
|
->set("xres", baton->tiffXres)
|
|
1049
|
-
->set("yres", baton->tiffYres)
|
|
1066
|
+
->set("yres", baton->tiffYres)
|
|
1067
|
+
->set("resunit", baton->tiffResolutionUnit));
|
|
1050
1068
|
baton->formatOut = "tiff";
|
|
1051
1069
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
|
1052
1070
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
@@ -1056,7 +1074,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1056
1074
|
->set("strip", !baton->withMetadata)
|
|
1057
1075
|
->set("Q", baton->heifQuality)
|
|
1058
1076
|
->set("compression", baton->heifCompression)
|
|
1059
|
-
->set("
|
|
1077
|
+
->set("effort", baton->heifEffort)
|
|
1060
1078
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1061
1079
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1062
1080
|
->set("lossless", baton->heifLossless));
|
|
@@ -1081,7 +1099,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1081
1099
|
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
|
1082
1100
|
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
|
1083
1101
|
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
|
1084
|
-
{"
|
|
1102
|
+
{"effort", std::to_string(baton->webpEffort)}
|
|
1085
1103
|
};
|
|
1086
1104
|
suffix = AssembleSuffixString(".webp", options);
|
|
1087
1105
|
} else {
|
|
@@ -1230,6 +1248,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1230
1248
|
Napi::FunctionReference debuglog;
|
|
1231
1249
|
Napi::FunctionReference queueListener;
|
|
1232
1250
|
|
|
1251
|
+
void MultiPageUnsupported(int const pages, std::string op) {
|
|
1252
|
+
if (pages > 1) {
|
|
1253
|
+
throw vips::VError(op + " is not supported for multi-page images");
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1233
1257
|
/*
|
|
1234
1258
|
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1235
1259
|
By default, returns zero, i.e. no rotation.
|
|
@@ -1320,15 +1344,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1320
1344
|
// Canvas option
|
|
1321
1345
|
std::string canvas = sharp::AttrAsStr(options, "canvas");
|
|
1322
1346
|
if (canvas == "crop") {
|
|
1323
|
-
baton->canvas = Canvas::CROP;
|
|
1347
|
+
baton->canvas = sharp::Canvas::CROP;
|
|
1324
1348
|
} else if (canvas == "embed") {
|
|
1325
|
-
baton->canvas = Canvas::EMBED;
|
|
1349
|
+
baton->canvas = sharp::Canvas::EMBED;
|
|
1326
1350
|
} else if (canvas == "max") {
|
|
1327
|
-
baton->canvas = Canvas::MAX;
|
|
1351
|
+
baton->canvas = sharp::Canvas::MAX;
|
|
1328
1352
|
} else if (canvas == "min") {
|
|
1329
|
-
baton->canvas = Canvas::MIN;
|
|
1353
|
+
baton->canvas = sharp::Canvas::MIN;
|
|
1330
1354
|
} else if (canvas == "ignore_aspect") {
|
|
1331
|
-
baton->canvas = Canvas::IGNORE_ASPECT;
|
|
1355
|
+
baton->canvas = sharp::Canvas::IGNORE_ASPECT;
|
|
1332
1356
|
}
|
|
1333
1357
|
// Tint chroma
|
|
1334
1358
|
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
|
@@ -1351,6 +1375,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1351
1375
|
}
|
|
1352
1376
|
// Resize options
|
|
1353
1377
|
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
|
|
1378
|
+
baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
|
|
1354
1379
|
baton->position = sharp::AttrAsInt32(options, "position");
|
|
1355
1380
|
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
|
|
1356
1381
|
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
|
@@ -1408,8 +1433,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1408
1433
|
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
|
|
1409
1434
|
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
|
1410
1435
|
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
|
1411
|
-
baton->affineInterpolator =
|
|
1412
|
-
|
|
1436
|
+
baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator");
|
|
1413
1437
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
|
1414
1438
|
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
|
1415
1439
|
if (options.Has("boolean")) {
|
|
@@ -1475,6 +1499,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1475
1499
|
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
|
1476
1500
|
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
|
1477
1501
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
|
1502
|
+
baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort");
|
|
1478
1503
|
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
|
1479
1504
|
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
|
1480
1505
|
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
|
|
@@ -1487,7 +1512,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1487
1512
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
|
1488
1513
|
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
|
1489
1514
|
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
|
1490
|
-
baton->
|
|
1515
|
+
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
|
1516
|
+
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1517
|
+
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1518
|
+
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
1491
1519
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
|
1492
1520
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
|
1493
1521
|
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
|
@@ -1506,30 +1534,28 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1506
1534
|
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
|
1507
1535
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
|
1508
1536
|
sharp::AttrAsStr(options, "tiffPredictor").data()));
|
|
1537
|
+
baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
|
|
1538
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
|
|
1539
|
+
sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
|
|
1540
|
+
|
|
1509
1541
|
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
|
|
1510
1542
|
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
|
|
1511
1543
|
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
|
1512
1544
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
|
1513
1545
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
|
1514
|
-
baton->
|
|
1546
|
+
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1515
1547
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1516
|
-
|
|
1517
1548
|
// Raw output
|
|
1518
1549
|
baton->rawDepth = static_cast<VipsBandFormat>(
|
|
1519
1550
|
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
|
|
1520
1551
|
sharp::AttrAsStr(options, "rawDepth").data()));
|
|
1521
|
-
|
|
1522
|
-
// Animated output
|
|
1523
|
-
if (sharp::HasAttr(options, "pageHeight")) {
|
|
1524
|
-
baton->pageHeight = sharp::AttrAsUint32(options, "pageHeight");
|
|
1525
|
-
}
|
|
1552
|
+
// Animated output properties
|
|
1526
1553
|
if (sharp::HasAttr(options, "loop")) {
|
|
1527
1554
|
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
1528
1555
|
}
|
|
1529
1556
|
if (sharp::HasAttr(options, "delay")) {
|
|
1530
1557
|
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
|
1531
1558
|
}
|
|
1532
|
-
|
|
1533
1559
|
// Tile output
|
|
1534
1560
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
|
1535
1561
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|