sharp 0.29.0 → 0.30.0
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 +2 -2
- package/binding.gyp +2 -0
- package/install/libvips.js +39 -8
- package/lib/constructor.js +20 -5
- package/lib/input.js +21 -4
- package/lib/libvips.js +16 -2
- package/lib/operation.js +31 -3
- package/lib/output.js +250 -73
- package/lib/platform.js +1 -1
- package/lib/resize.js +19 -0
- package/lib/sharp.js +11 -3
- package/lib/utility.js +22 -3
- package/package.json +30 -13
- package/src/common.cc +156 -29
- package/src/common.h +49 -7
- 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 +98 -3
- package/src/operations.h +15 -2
- package/src/pipeline.cc +357 -268
- package/src/pipeline.h +31 -19
- package/src/sharp.cc +16 -0
- package/src/utilities.cc +1 -1
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,17 +95,27 @@ 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
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
111
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
|
94
112
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
95
113
|
}
|
|
96
114
|
}
|
|
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,201 +123,194 @@ 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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
|
|
136
|
+
// Is there just one page? Shrink to inputHeight instead
|
|
137
|
+
if (nPages == 1) {
|
|
138
|
+
pageHeight = inputHeight;
|
|
117
139
|
}
|
|
118
140
|
|
|
119
|
-
// If
|
|
120
|
-
// Override target width and height if
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
// If withoutReduction is specified,
|
|
142
|
+
// Override target width and height if less than respective value from input file
|
|
143
|
+
if (baton->withoutReduction) {
|
|
144
|
+
if (baton->width < inputWidth) {
|
|
145
|
+
baton->width = inputWidth;
|
|
146
|
+
}
|
|
147
|
+
if (baton->height < inputHeight) {
|
|
148
|
+
baton->height = inputHeight;
|
|
149
|
+
}
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
// Scaling calculations
|
|
131
|
-
double
|
|
132
|
-
double
|
|
153
|
+
double hshrink;
|
|
154
|
+
double vshrink;
|
|
133
155
|
int targetResizeWidth = baton->width;
|
|
134
156
|
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
157
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
|
|
158
|
+
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
159
|
+
bool swap = !baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270);
|
|
212
160
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
161
|
+
// Shrink to pageHeight, so we work for multi-page images
|
|
162
|
+
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
163
|
+
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
|
164
|
+
baton->canvas, swap, baton->withoutEnlargement);
|
|
216
165
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
int shrink_on_load = 1;
|
|
166
|
+
// The jpeg preload shrink.
|
|
167
|
+
int jpegShrinkOnLoad = 1;
|
|
220
168
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
169
|
+
// WebP, PDF, SVG scale
|
|
170
|
+
double scale = 1.0;
|
|
171
|
+
|
|
172
|
+
// Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when:
|
|
173
|
+
// - the width or height parameters are specified;
|
|
174
|
+
// - gamma correction doesn't need to be applied;
|
|
175
|
+
// - trimming or pre-resize extract isn't required;
|
|
176
|
+
// - input colourspace is not specified;
|
|
177
|
+
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
|
230
178
|
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
|
-
|
|
179
|
+
baton->colourspaceInput == VIPS_INTERPRETATION_LAST;
|
|
180
|
+
|
|
181
|
+
if (shouldPreShrink) {
|
|
182
|
+
// The common part of the shrink: the bit by which both axes must be shrunk
|
|
183
|
+
double shrink = std::min(hshrink, vshrink);
|
|
184
|
+
|
|
185
|
+
if (inputImageType == sharp::ImageType::JPEG) {
|
|
186
|
+
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
|
|
187
|
+
// for more consistent results and avoid occasional small image shifting
|
|
188
|
+
int factor = baton->fastShrinkOnLoad ? 1 : 2;
|
|
189
|
+
if (shrink >= 8 * factor) {
|
|
190
|
+
jpegShrinkOnLoad = 8;
|
|
191
|
+
} else if (shrink >= 4 * factor) {
|
|
192
|
+
jpegShrinkOnLoad = 4;
|
|
193
|
+
} else if (shrink >= 2 * factor) {
|
|
194
|
+
jpegShrinkOnLoad = 2;
|
|
195
|
+
}
|
|
196
|
+
} else if (inputImageType == sharp::ImageType::WEBP ||
|
|
197
|
+
inputImageType == sharp::ImageType::SVG ||
|
|
198
|
+
inputImageType == sharp::ImageType::PDF) {
|
|
199
|
+
scale = 1.0 / shrink;
|
|
246
200
|
}
|
|
247
201
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
if (shrink_on_load > 1) {
|
|
255
|
-
// Reload input using shrink-on-load
|
|
202
|
+
|
|
203
|
+
// Reload input using shrink-on-load, it'll be an integer shrink
|
|
204
|
+
// factor for jpegload*, a double scale factor for webpload*,
|
|
205
|
+
// pdfload* and svgload*
|
|
206
|
+
if (jpegShrinkOnLoad > 1) {
|
|
256
207
|
vips::VOption *option = VImage::option()
|
|
257
208
|
->set("access", baton->input->access)
|
|
258
|
-
->set("shrink",
|
|
209
|
+
->set("shrink", jpegShrinkOnLoad)
|
|
259
210
|
->set("fail", baton->input->failOnError);
|
|
260
211
|
if (baton->input->buffer != nullptr) {
|
|
212
|
+
// Reload JPEG buffer
|
|
261
213
|
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
|
-
}
|
|
214
|
+
image = VImage::jpegload_buffer(blob, option);
|
|
269
215
|
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
270
216
|
} else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
217
|
+
// Reload JPEG file
|
|
218
|
+
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
|
|
219
|
+
}
|
|
220
|
+
} else if (scale != 1.0) {
|
|
221
|
+
vips::VOption *option = VImage::option()
|
|
222
|
+
->set("access", baton->input->access)
|
|
223
|
+
->set("scale", scale)
|
|
224
|
+
->set("fail", baton->input->failOnError);
|
|
225
|
+
if (inputImageType == sharp::ImageType::WEBP) {
|
|
226
|
+
option->set("n", baton->input->pages);
|
|
227
|
+
option->set("page", baton->input->page);
|
|
228
|
+
|
|
229
|
+
if (baton->input->buffer != nullptr) {
|
|
230
|
+
// Reload WebP buffer
|
|
231
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
232
|
+
image = VImage::webpload_buffer(blob, option);
|
|
233
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
274
234
|
} else {
|
|
275
235
|
// Reload WebP file
|
|
276
236
|
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
|
|
277
237
|
}
|
|
238
|
+
} else if (inputImageType == sharp::ImageType::SVG) {
|
|
239
|
+
option->set("unlimited", baton->input->unlimited);
|
|
240
|
+
option->set("dpi", baton->input->density);
|
|
241
|
+
|
|
242
|
+
if (baton->input->buffer != nullptr) {
|
|
243
|
+
// Reload SVG buffer
|
|
244
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
245
|
+
image = VImage::svgload_buffer(blob, option);
|
|
246
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
247
|
+
} else {
|
|
248
|
+
// Reload SVG file
|
|
249
|
+
image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
sharp::SetDensity(image, baton->input->density);
|
|
253
|
+
} else if (inputImageType == sharp::ImageType::PDF) {
|
|
254
|
+
option->set("n", baton->input->pages);
|
|
255
|
+
option->set("page", baton->input->page);
|
|
256
|
+
option->set("dpi", baton->input->density);
|
|
257
|
+
|
|
258
|
+
if (baton->input->buffer != nullptr) {
|
|
259
|
+
// Reload PDF buffer
|
|
260
|
+
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
261
|
+
image = VImage::pdfload_buffer(blob, option);
|
|
262
|
+
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
|
|
263
|
+
} else {
|
|
264
|
+
// Reload PDF file
|
|
265
|
+
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
sharp::SetDensity(image, baton->input->density);
|
|
278
269
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Any pre-shrinking may already have been done
|
|
273
|
+
int thumbWidth = image.width();
|
|
274
|
+
int thumbHeight = image.height();
|
|
275
|
+
|
|
276
|
+
// After pre-shrink, but before the main shrink stage
|
|
277
|
+
// Reuse the initial pageHeight if we didn't pre-shrink
|
|
278
|
+
int preshrunkPageHeight = shouldPreShrink ? sharp::GetPageHeight(image) : pageHeight;
|
|
279
|
+
|
|
280
|
+
if (baton->fastShrinkOnLoad && jpegShrinkOnLoad > 1) {
|
|
281
|
+
// JPEG shrink-on-load rounds the output dimensions down, which
|
|
282
|
+
// may cause incorrect dimensions when fastShrinkOnLoad is enabled
|
|
283
|
+
// Just recalculate vshrink / hshrink on the main image instead of
|
|
284
|
+
// the pre-shrunk image when this is the case
|
|
285
|
+
hshrink = static_cast<double>(thumbWidth) / (static_cast<double>(inputWidth) / hshrink);
|
|
286
|
+
vshrink = static_cast<double>(preshrunkPageHeight) / (static_cast<double>(pageHeight) / vshrink);
|
|
287
|
+
} else {
|
|
288
|
+
// Shrink to preshrunkPageHeight, so we work for multi-page images
|
|
289
|
+
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
290
|
+
thumbWidth, preshrunkPageHeight, targetResizeWidth, targetResizeHeight,
|
|
291
|
+
baton->canvas, swap, baton->withoutEnlargement);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
int targetHeight = static_cast<int>(std::rint(static_cast<double>(preshrunkPageHeight) / vshrink));
|
|
295
|
+
int targetPageHeight = targetHeight;
|
|
296
|
+
|
|
297
|
+
// In toilet-roll mode, we must adjust vshrink so that we exactly hit
|
|
298
|
+
// preshrunkPageHeight or we'll have pixels straddling pixel boundaries
|
|
299
|
+
if (thumbHeight > preshrunkPageHeight) {
|
|
300
|
+
targetHeight *= nPages;
|
|
301
|
+
vshrink = static_cast<double>(thumbHeight) / targetHeight;
|
|
291
302
|
}
|
|
292
303
|
|
|
293
304
|
// Ensure we're using a device-independent colour space
|
|
305
|
+
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
|
294
306
|
if (
|
|
295
307
|
sharp::HasProfile(image) &&
|
|
296
308
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
297
309
|
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
|
298
310
|
) {
|
|
299
|
-
// Convert to sRGB using embedded profile
|
|
311
|
+
// Convert to sRGB/P3 using embedded profile
|
|
300
312
|
try {
|
|
301
|
-
image = image.icc_transform(
|
|
313
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
302
314
|
->set("embedded", TRUE)
|
|
303
315
|
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
|
|
304
316
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
@@ -306,7 +318,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
306
318
|
// Ignore failure of embedded profile
|
|
307
319
|
}
|
|
308
320
|
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
|
309
|
-
image = image.icc_transform(
|
|
321
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
310
322
|
->set("input_profile", "cmyk")
|
|
311
323
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
312
324
|
}
|
|
@@ -340,13 +352,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
340
352
|
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
|
341
353
|
}
|
|
342
354
|
|
|
343
|
-
bool const shouldResize =
|
|
355
|
+
bool const shouldResize = hshrink != 1.0 || vshrink != 1.0;
|
|
344
356
|
bool const shouldBlur = baton->blurSigma != 0.0;
|
|
345
357
|
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
|
346
358
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
347
359
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
|
348
360
|
bool const shouldComposite = !baton->composite.empty();
|
|
349
|
-
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
|
361
|
+
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
|
362
|
+
baton->hue != 0.0 || baton->lightness != 0.0;
|
|
350
363
|
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
|
351
364
|
|
|
352
365
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
|
@@ -373,35 +386,33 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
373
386
|
) {
|
|
374
387
|
throw vips::VError("Unknown kernel");
|
|
375
388
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
xfactor = 2 * image.width();
|
|
379
|
-
baton->width = 1;
|
|
380
|
-
}
|
|
381
|
-
if (image.height() / yfactor < 0.5) {
|
|
382
|
-
yfactor = 2 * image.height();
|
|
383
|
-
baton->height = 1;
|
|
384
|
-
}
|
|
385
|
-
image = image.resize(1.0 / xfactor, VImage::option()
|
|
386
|
-
->set("vscale", 1.0 / yfactor)
|
|
389
|
+
image = image.resize(1.0 / hshrink, VImage::option()
|
|
390
|
+
->set("vscale", 1.0 / vshrink)
|
|
387
391
|
->set("kernel", kernel));
|
|
388
392
|
}
|
|
389
393
|
|
|
390
394
|
// Rotate post-extract 90-angle
|
|
391
|
-
if (!baton->rotateBeforePreExtract &&
|
|
392
|
-
|
|
393
|
-
|
|
395
|
+
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
|
396
|
+
image = image.rot(rotation);
|
|
397
|
+
if (flip) {
|
|
398
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
399
|
+
flip = FALSE;
|
|
400
|
+
}
|
|
401
|
+
if (flop) {
|
|
402
|
+
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
403
|
+
flop = FALSE;
|
|
404
|
+
}
|
|
405
|
+
image = sharp::RemoveExifOrientation(image);
|
|
394
406
|
}
|
|
395
407
|
|
|
396
|
-
|
|
397
408
|
// Flip (mirror about Y axis)
|
|
398
|
-
if (baton->flip) {
|
|
409
|
+
if (baton->flip || flip) {
|
|
399
410
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
400
411
|
image = sharp::RemoveExifOrientation(image);
|
|
401
412
|
}
|
|
402
413
|
|
|
403
414
|
// Flop (mirror about X axis)
|
|
404
|
-
if (baton->flop) {
|
|
415
|
+
if (baton->flop || flop) {
|
|
405
416
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
406
417
|
image = sharp::RemoveExifOrientation(image);
|
|
407
418
|
}
|
|
@@ -419,52 +430,68 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
419
430
|
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
|
|
420
431
|
}
|
|
421
432
|
|
|
433
|
+
inputWidth = image.width();
|
|
434
|
+
inputHeight = nPages > 1 ? targetPageHeight : image.height();
|
|
435
|
+
|
|
436
|
+
// Resolve dimensions
|
|
437
|
+
if (baton->width <= 0) {
|
|
438
|
+
baton->width = inputWidth;
|
|
439
|
+
}
|
|
440
|
+
if (baton->height <= 0) {
|
|
441
|
+
baton->height = inputHeight;
|
|
442
|
+
}
|
|
443
|
+
|
|
422
444
|
// Crop/embed
|
|
423
|
-
if (
|
|
424
|
-
if (baton->canvas == Canvas::EMBED) {
|
|
445
|
+
if (inputWidth != baton->width || inputHeight != baton->height) {
|
|
446
|
+
if (baton->canvas == sharp::Canvas::EMBED) {
|
|
425
447
|
std::vector<double> background;
|
|
426
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground);
|
|
448
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
|
427
449
|
|
|
428
450
|
// Embed
|
|
429
451
|
|
|
430
|
-
// Calculate where to position the
|
|
452
|
+
// Calculate where to position the embedded image if gravity specified, else center.
|
|
431
453
|
int left;
|
|
432
454
|
int top;
|
|
433
455
|
|
|
434
|
-
left = static_cast<int>(round((baton->width -
|
|
435
|
-
top = static_cast<int>(round((baton->height -
|
|
456
|
+
left = static_cast<int>(round((baton->width - inputWidth) / 2));
|
|
457
|
+
top = static_cast<int>(round((baton->height - inputHeight) / 2));
|
|
436
458
|
|
|
437
|
-
int width = std::max(
|
|
438
|
-
int height = std::max(
|
|
459
|
+
int width = std::max(inputWidth, baton->width);
|
|
460
|
+
int height = std::max(inputHeight, baton->height);
|
|
439
461
|
std::tie(left, top) = sharp::CalculateEmbedPosition(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
image =
|
|
443
|
-
|
|
444
|
-
|
|
462
|
+
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
463
|
+
|
|
464
|
+
image = nPages > 1
|
|
465
|
+
? sharp::EmbedMultiPage(image,
|
|
466
|
+
left, top, width, height, background, nPages, &targetPageHeight)
|
|
467
|
+
: image.embed(left, top, width, height, VImage::option()
|
|
468
|
+
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
469
|
+
->set("background", background));
|
|
470
|
+
} else if (baton->canvas == sharp::Canvas::CROP) {
|
|
471
|
+
if (baton->width > inputWidth) {
|
|
472
|
+
baton->width = inputWidth;
|
|
473
|
+
}
|
|
474
|
+
if (baton->height > inputHeight) {
|
|
475
|
+
baton->height = inputHeight;
|
|
476
|
+
}
|
|
445
477
|
|
|
446
|
-
|
|
447
|
-
baton->canvas != Canvas::IGNORE_ASPECT &&
|
|
448
|
-
(image.width() > baton->width || image.height() > baton->height)
|
|
449
|
-
) {
|
|
450
|
-
// Crop/max/min
|
|
478
|
+
// Crop
|
|
451
479
|
if (baton->position < 9) {
|
|
452
480
|
// Gravity-based crop
|
|
453
481
|
int left;
|
|
454
482
|
int top;
|
|
455
483
|
std::tie(left, top) = sharp::CalculateCrop(
|
|
456
|
-
|
|
457
|
-
int width = std::min(
|
|
458
|
-
int height = std::min(
|
|
459
|
-
|
|
484
|
+
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
485
|
+
int width = std::min(inputWidth, baton->width);
|
|
486
|
+
int height = std::min(inputHeight, baton->height);
|
|
487
|
+
|
|
488
|
+
image = nPages > 1
|
|
489
|
+
? sharp::CropMultiPage(image,
|
|
490
|
+
left, top, width, height, nPages, &targetPageHeight)
|
|
491
|
+
: image.extract_area(left, top, width, height);
|
|
460
492
|
} else {
|
|
461
493
|
// Attention-based or Entropy-based crop
|
|
462
|
-
|
|
463
|
-
baton->width = image.width();
|
|
464
|
-
}
|
|
465
|
-
if (baton->height > image.height()) {
|
|
466
|
-
baton->height = image.height();
|
|
467
|
-
}
|
|
494
|
+
MultiPageUnsupported(nPages, "Resize strategy");
|
|
468
495
|
image = image.tilecache(VImage::option()
|
|
469
496
|
->set("access", VIPS_ACCESS_RANDOM)
|
|
470
497
|
->set("threaded", TRUE));
|
|
@@ -479,21 +506,32 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
479
506
|
|
|
480
507
|
// Rotate post-extract non-90 angle
|
|
481
508
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
|
509
|
+
MultiPageUnsupported(nPages, "Rotate");
|
|
482
510
|
std::vector<double> background;
|
|
483
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
511
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
484
512
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
485
513
|
}
|
|
486
514
|
|
|
487
515
|
// Post extraction
|
|
488
516
|
if (baton->topOffsetPost != -1) {
|
|
489
|
-
|
|
490
|
-
|
|
517
|
+
if (nPages > 1) {
|
|
518
|
+
image = sharp::CropMultiPage(image,
|
|
519
|
+
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
|
|
520
|
+
nPages, &targetPageHeight);
|
|
521
|
+
|
|
522
|
+
// heightPost is used in the info object, so update to reflect the number of pages
|
|
523
|
+
baton->heightPost *= nPages;
|
|
524
|
+
} else {
|
|
525
|
+
image = image.extract_area(
|
|
526
|
+
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
|
527
|
+
}
|
|
491
528
|
}
|
|
492
529
|
|
|
493
530
|
// Affine transform
|
|
494
531
|
if (baton->affineMatrix.size() > 0) {
|
|
532
|
+
MultiPageUnsupported(nPages, "Affine");
|
|
495
533
|
std::vector<double> background;
|
|
496
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
|
534
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
497
535
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
|
498
536
|
->set("idx", baton->affineIdx)
|
|
499
537
|
->set("idy", baton->affineIdy)
|
|
@@ -505,14 +543,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
505
543
|
// Extend edges
|
|
506
544
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
|
507
545
|
std::vector<double> background;
|
|
508
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground);
|
|
546
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
|
509
547
|
|
|
510
548
|
// Embed
|
|
511
549
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
|
512
|
-
baton->height = image.height() + baton->extendTop + baton->extendBottom;
|
|
550
|
+
baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
|
|
513
551
|
|
|
514
|
-
image =
|
|
515
|
-
|
|
552
|
+
image = nPages > 1
|
|
553
|
+
? sharp::EmbedMultiPage(image,
|
|
554
|
+
baton->extendLeft, baton->extendTop, baton->width, baton->height, background, nPages, &targetPageHeight)
|
|
555
|
+
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
556
|
+
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
|
|
516
557
|
}
|
|
517
558
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
518
559
|
if (shouldApplyMedian) {
|
|
@@ -542,7 +583,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
542
583
|
}
|
|
543
584
|
|
|
544
585
|
if (shouldModulate) {
|
|
545
|
-
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
|
586
|
+
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
546
587
|
}
|
|
547
588
|
|
|
548
589
|
// Sharpen
|
|
@@ -715,9 +756,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
715
756
|
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
|
716
757
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
|
717
758
|
// Transform colours from embedded profile to output profile
|
|
718
|
-
if (baton->withMetadata && sharp::HasProfile(image)) {
|
|
719
|
-
image = image.icc_transform(
|
|
720
|
-
|
|
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));
|
|
721
763
|
}
|
|
722
764
|
}
|
|
723
765
|
|
|
@@ -726,7 +768,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
726
768
|
image = image.icc_transform(
|
|
727
769
|
const_cast<char*>(baton->withMetadataIcc.data()),
|
|
728
770
|
VImage::option()
|
|
729
|
-
->set("input_profile",
|
|
771
|
+
->set("input_profile", processingProfile)
|
|
772
|
+
->set("embedded", TRUE)
|
|
730
773
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
731
774
|
}
|
|
732
775
|
// Override EXIF Orientation tag
|
|
@@ -750,16 +793,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
750
793
|
baton->width = image.width();
|
|
751
794
|
baton->height = image.height();
|
|
752
795
|
|
|
753
|
-
bool const supportsGifOutput = vips_type_find("VipsOperation", "magicksave") != 0 &&
|
|
754
|
-
vips_type_find("VipsOperation", "magicksave_buffer") != 0;
|
|
755
|
-
|
|
756
796
|
image = sharp::SetAnimationProperties(
|
|
757
|
-
image,
|
|
758
|
-
baton->pageHeight,
|
|
759
|
-
baton->delay,
|
|
760
|
-
baton->loop);
|
|
797
|
+
image, nPages, targetPageHeight, baton->delay, baton->loop);
|
|
761
798
|
|
|
762
799
|
// Output
|
|
800
|
+
sharp::SetTimeout(image, baton->timeoutSeconds);
|
|
763
801
|
if (baton->fileOut.empty()) {
|
|
764
802
|
// Buffer output
|
|
765
803
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -787,9 +825,24 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
787
825
|
} else {
|
|
788
826
|
baton->channels = std::min(baton->channels, 3);
|
|
789
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";
|
|
790
844
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
|
791
|
-
(inputImageType == sharp::ImageType::PNG ||
|
|
792
|
-
inputImageType == sharp::ImageType::SVG))) {
|
|
845
|
+
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
|
793
846
|
// Write PNG to buffer
|
|
794
847
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
795
848
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
|
@@ -799,7 +852,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
799
852
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
800
853
|
->set("palette", baton->pngPalette)
|
|
801
854
|
->set("Q", baton->pngQuality)
|
|
802
|
-
->set("
|
|
855
|
+
->set("effort", baton->pngEffort)
|
|
856
|
+
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
|
|
803
857
|
->set("dither", baton->pngDither)));
|
|
804
858
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
805
859
|
baton->bufferOutLength = area->length;
|
|
@@ -816,7 +870,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
816
870
|
->set("lossless", baton->webpLossless)
|
|
817
871
|
->set("near_lossless", baton->webpNearLossless)
|
|
818
872
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
819
|
-
->set("
|
|
873
|
+
->set("effort", baton->webpEffort)
|
|
820
874
|
->set("alpha_q", baton->webpAlphaQuality)));
|
|
821
875
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
822
876
|
baton->bufferOutLength = area->length;
|
|
@@ -824,14 +878,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
824
878
|
vips_area_unref(area);
|
|
825
879
|
baton->formatOut = "webp";
|
|
826
880
|
} else if (baton->formatOut == "gif" ||
|
|
827
|
-
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF
|
|
881
|
+
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {
|
|
828
882
|
// Write GIF to buffer
|
|
829
883
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
830
|
-
VipsArea *area = reinterpret_cast<VipsArea*>(image.
|
|
884
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
|
831
885
|
->set("strip", !baton->withMetadata)
|
|
832
|
-
->set("
|
|
833
|
-
->set("
|
|
834
|
-
->set("
|
|
886
|
+
->set("bitdepth", baton->gifBitdepth)
|
|
887
|
+
->set("effort", baton->gifEffort)
|
|
888
|
+
->set("dither", baton->gifDither)));
|
|
835
889
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
836
890
|
baton->bufferOutLength = area->length;
|
|
837
891
|
area->free_fn = nullptr;
|
|
@@ -859,7 +913,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
859
913
|
->set("tile_height", baton->tiffTileHeight)
|
|
860
914
|
->set("tile_width", baton->tiffTileWidth)
|
|
861
915
|
->set("xres", baton->tiffXres)
|
|
862
|
-
->set("yres", baton->tiffYres)
|
|
916
|
+
->set("yres", baton->tiffYres)
|
|
917
|
+
->set("resunit", baton->tiffResolutionUnit)));
|
|
863
918
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
864
919
|
baton->bufferOutLength = area->length;
|
|
865
920
|
area->free_fn = nullptr;
|
|
@@ -868,11 +923,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
868
923
|
} else if (baton->formatOut == "heif" ||
|
|
869
924
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
870
925
|
// Write HEIF to buffer
|
|
926
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
871
927
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
872
928
|
->set("strip", !baton->withMetadata)
|
|
873
929
|
->set("Q", baton->heifQuality)
|
|
874
930
|
->set("compression", baton->heifCompression)
|
|
875
|
-
->set("
|
|
931
|
+
->set("effort", baton->heifEffort)
|
|
876
932
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
877
933
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
878
934
|
->set("lossless", baton->heifLossless)));
|
|
@@ -917,13 +973,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
917
973
|
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
|
918
974
|
bool const isGif = sharp::IsGif(baton->fileOut);
|
|
919
975
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
|
976
|
+
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
|
920
977
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
|
921
978
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
|
922
979
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
|
923
980
|
bool const isV = sharp::IsV(baton->fileOut);
|
|
924
981
|
bool const mightMatchInput = baton->formatOut == "input";
|
|
925
982
|
bool const willMatchInput = mightMatchInput &&
|
|
926
|
-
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
|
983
|
+
!(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
|
|
927
984
|
|
|
928
985
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
|
929
986
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -943,9 +1000,20 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
943
1000
|
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
944
1001
|
baton->formatOut = "jpeg";
|
|
945
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";
|
|
946
1015
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
|
947
|
-
(inputImageType == sharp::ImageType::PNG ||
|
|
948
|
-
inputImageType == sharp::ImageType::SVG))) {
|
|
1016
|
+
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
|
949
1017
|
// Write PNG to file
|
|
950
1018
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
951
1019
|
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
@@ -955,7 +1023,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
955
1023
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
956
1024
|
->set("palette", baton->pngPalette)
|
|
957
1025
|
->set("Q", baton->pngQuality)
|
|
958
|
-
->set("
|
|
1026
|
+
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
|
|
1027
|
+
->set("effort", baton->pngEffort)
|
|
959
1028
|
->set("dither", baton->pngDither));
|
|
960
1029
|
baton->formatOut = "png";
|
|
961
1030
|
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
|
@@ -968,18 +1037,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
968
1037
|
->set("lossless", baton->webpLossless)
|
|
969
1038
|
->set("near_lossless", baton->webpNearLossless)
|
|
970
1039
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
971
|
-
->set("
|
|
1040
|
+
->set("effort", baton->webpEffort)
|
|
972
1041
|
->set("alpha_q", baton->webpAlphaQuality));
|
|
973
1042
|
baton->formatOut = "webp";
|
|
974
1043
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
|
975
|
-
(willMatchInput && inputImageType == sharp::ImageType::GIF
|
|
1044
|
+
(willMatchInput && inputImageType == sharp::ImageType::GIF)) {
|
|
976
1045
|
// Write GIF to file
|
|
977
1046
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
978
|
-
image.
|
|
1047
|
+
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
979
1048
|
->set("strip", !baton->withMetadata)
|
|
980
|
-
->set("
|
|
981
|
-
->set("
|
|
982
|
-
->set("
|
|
1049
|
+
->set("bitdepth", baton->gifBitdepth)
|
|
1050
|
+
->set("effort", baton->gifEffort)
|
|
1051
|
+
->set("dither", baton->gifDither));
|
|
983
1052
|
baton->formatOut = "gif";
|
|
984
1053
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
|
985
1054
|
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
|
|
@@ -1003,16 +1072,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1003
1072
|
->set("tile_height", baton->tiffTileHeight)
|
|
1004
1073
|
->set("tile_width", baton->tiffTileWidth)
|
|
1005
1074
|
->set("xres", baton->tiffXres)
|
|
1006
|
-
->set("yres", baton->tiffYres)
|
|
1075
|
+
->set("yres", baton->tiffYres)
|
|
1076
|
+
->set("resunit", baton->tiffResolutionUnit));
|
|
1007
1077
|
baton->formatOut = "tiff";
|
|
1008
1078
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
|
1009
1079
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
1010
1080
|
// Write HEIF to file
|
|
1081
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
1011
1082
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1012
1083
|
->set("strip", !baton->withMetadata)
|
|
1013
1084
|
->set("Q", baton->heifQuality)
|
|
1014
1085
|
->set("compression", baton->heifCompression)
|
|
1015
|
-
->set("
|
|
1086
|
+
->set("effort", baton->heifEffort)
|
|
1016
1087
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1017
1088
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1018
1089
|
->set("lossless", baton->heifLossless));
|
|
@@ -1037,7 +1108,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1037
1108
|
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
|
1038
1109
|
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
|
1039
1110
|
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
|
1040
|
-
{"
|
|
1111
|
+
{"effort", std::to_string(baton->webpEffort)}
|
|
1041
1112
|
};
|
|
1042
1113
|
suffix = AssembleSuffixString(".webp", options);
|
|
1043
1114
|
} else {
|
|
@@ -1186,6 +1257,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1186
1257
|
Napi::FunctionReference debuglog;
|
|
1187
1258
|
Napi::FunctionReference queueListener;
|
|
1188
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
|
+
|
|
1189
1266
|
/*
|
|
1190
1267
|
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1191
1268
|
By default, returns zero, i.e. no rotation.
|
|
@@ -1276,15 +1353,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1276
1353
|
// Canvas option
|
|
1277
1354
|
std::string canvas = sharp::AttrAsStr(options, "canvas");
|
|
1278
1355
|
if (canvas == "crop") {
|
|
1279
|
-
baton->canvas = Canvas::CROP;
|
|
1356
|
+
baton->canvas = sharp::Canvas::CROP;
|
|
1280
1357
|
} else if (canvas == "embed") {
|
|
1281
|
-
baton->canvas = Canvas::EMBED;
|
|
1358
|
+
baton->canvas = sharp::Canvas::EMBED;
|
|
1282
1359
|
} else if (canvas == "max") {
|
|
1283
|
-
baton->canvas = Canvas::MAX;
|
|
1360
|
+
baton->canvas = sharp::Canvas::MAX;
|
|
1284
1361
|
} else if (canvas == "min") {
|
|
1285
|
-
baton->canvas = Canvas::MIN;
|
|
1362
|
+
baton->canvas = sharp::Canvas::MIN;
|
|
1286
1363
|
} else if (canvas == "ignore_aspect") {
|
|
1287
|
-
baton->canvas = Canvas::IGNORE_ASPECT;
|
|
1364
|
+
baton->canvas = sharp::Canvas::IGNORE_ASPECT;
|
|
1288
1365
|
}
|
|
1289
1366
|
// Tint chroma
|
|
1290
1367
|
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
|
@@ -1307,6 +1384,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1307
1384
|
}
|
|
1308
1385
|
// Resize options
|
|
1309
1386
|
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
|
|
1387
|
+
baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
|
|
1310
1388
|
baton->position = sharp::AttrAsInt32(options, "position");
|
|
1311
1389
|
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
|
|
1312
1390
|
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
|
@@ -1328,6 +1406,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1328
1406
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
|
1329
1407
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
|
1330
1408
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
|
1409
|
+
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
|
1331
1410
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
|
1332
1411
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
|
1333
1412
|
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
|
@@ -1364,7 +1443,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1364
1443
|
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
|
1365
1444
|
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
|
1366
1445
|
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
|
|
1367
|
-
|
|
1368
1446
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
|
1369
1447
|
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
|
1370
1448
|
if (options.Has("boolean")) {
|
|
@@ -1415,6 +1493,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1415
1493
|
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
|
1416
1494
|
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
|
1417
1495
|
}
|
|
1496
|
+
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
|
1418
1497
|
// Format-specific
|
|
1419
1498
|
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
|
1420
1499
|
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
|
|
@@ -1429,14 +1508,23 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1429
1508
|
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
|
1430
1509
|
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
|
1431
1510
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
|
1432
|
-
baton->
|
|
1511
|
+
baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort");
|
|
1512
|
+
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
|
1433
1513
|
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
|
1514
|
+
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
|
|
1515
|
+
baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless");
|
|
1516
|
+
baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight");
|
|
1517
|
+
baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth");
|
|
1518
|
+
baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling");
|
|
1434
1519
|
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
|
1435
1520
|
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
|
1436
1521
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
|
1437
1522
|
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
|
1438
1523
|
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
|
1439
|
-
baton->
|
|
1524
|
+
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
|
1525
|
+
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1526
|
+
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1527
|
+
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
1440
1528
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
|
1441
1529
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
|
1442
1530
|
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
|
@@ -1445,6 +1533,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1445
1533
|
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
|
|
1446
1534
|
baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
|
|
1447
1535
|
baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
|
|
1536
|
+
if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
|
|
1537
|
+
baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
|
|
1538
|
+
}
|
|
1448
1539
|
// tiff compression options
|
|
1449
1540
|
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
|
|
1450
1541
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
|
|
@@ -1452,30 +1543,28 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1452
1543
|
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
|
1453
1544
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
|
1454
1545
|
sharp::AttrAsStr(options, "tiffPredictor").data()));
|
|
1546
|
+
baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
|
|
1547
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
|
|
1548
|
+
sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
|
|
1549
|
+
|
|
1455
1550
|
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
|
|
1456
1551
|
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
|
|
1457
1552
|
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
|
1458
1553
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
|
1459
1554
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
|
1460
|
-
baton->
|
|
1555
|
+
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1461
1556
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1462
|
-
|
|
1463
1557
|
// Raw output
|
|
1464
1558
|
baton->rawDepth = static_cast<VipsBandFormat>(
|
|
1465
1559
|
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
|
|
1466
1560
|
sharp::AttrAsStr(options, "rawDepth").data()));
|
|
1467
|
-
|
|
1468
|
-
// Animated output
|
|
1469
|
-
if (sharp::HasAttr(options, "pageHeight")) {
|
|
1470
|
-
baton->pageHeight = sharp::AttrAsUint32(options, "pageHeight");
|
|
1471
|
-
}
|
|
1561
|
+
// Animated output properties
|
|
1472
1562
|
if (sharp::HasAttr(options, "loop")) {
|
|
1473
1563
|
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
1474
1564
|
}
|
|
1475
1565
|
if (sharp::HasAttr(options, "delay")) {
|
|
1476
1566
|
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
|
1477
1567
|
}
|
|
1478
|
-
|
|
1479
1568
|
// Tile output
|
|
1480
1569
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
|
1481
1570
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|