Skip to content

Commit

Permalink
Merge d72f4bc into add4c79
Browse files Browse the repository at this point in the history
  • Loading branch information
kleisauke committed Nov 29, 2021
2 parents add4c79 + d72f4bc commit 43e1cfc
Show file tree
Hide file tree
Showing 29 changed files with 619 additions and 334 deletions.
26 changes: 8 additions & 18 deletions lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,8 @@ function png (options) {
* @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
* @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
Expand Down Expand Up @@ -527,20 +526,16 @@ function webp (options) {
* width: 128,
* height: 128 * pages
* })
* .gif({
* pageHeight: 128,
* dither: 0
* })
* .gif({ dither: 0 })
* .toBuffer();
*
* @param {Object} [options] - output options
* @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
* @param {number} [options.colors=256] - alternative spelling of `options.colours`
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
* @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
Expand Down Expand Up @@ -657,20 +652,12 @@ function jp2 (options) {
* @private
*
* @param {Object} [source] - output options
* @param {number} [source.pageHeight] - page height for animated output
* @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
* @param {Object} [target] - target object for valid options
* @throws {Error} Invalid options
*/
function trySetAnimationOptions (source, target) {
if (is.object(source) && is.defined(source.pageHeight)) {
if (is.integer(source.pageHeight) && source.pageHeight > 0) {
target.pageHeight = source.pageHeight;
} else {
throw is.invalidParameterError('pageHeight', 'integer larger than 0', source.pageHeight);
}
}
if (is.object(source) && is.defined(source.loop)) {
if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
target.loop = source.loop;
Expand All @@ -679,13 +666,16 @@ function trySetAnimationOptions (source, target) {
}
}
if (is.object(source) && is.defined(source.delay)) {
if (
// We allow singular values as well
if (is.integer(source.delay) && is.inRange(source.delay, 0, 65535)) {
target.delay = [source.delay];
} else if (
Array.isArray(source.delay) &&
source.delay.every(is.integer) &&
source.delay.every(v => is.inRange(v, 0, 65535))) {
target.delay = source.delay;
} else {
throw is.invalidParameterError('delay', 'array of integers between 0 and 65535', source.delay);
throw is.invalidParameterError('delay', 'integer or an array of integers between 0 and 65535', source.delay);
}
}
}
Expand Down
106 changes: 89 additions & 17 deletions src/common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -490,30 +490,24 @@ namespace sharp {

/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = delay.size() != 1 || delay.front() != -1;
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = !delay.empty();

if (pageHeight == 0 && image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
}

if (!hasDelay && image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
delay = image.get_array_int("delay");
hasDelay = true;
}
// Avoid a copy if none of the animation properties are needed.
if (nPages == 1 && !hasDelay && loop == -1) return image;

if (loop == -1 && image.get_typeof("loop") == G_TYPE_INT) {
loop = image.get_int("loop");
if (delay.size() == 1) {
// We have just one delay, repeat that value for all frames.
delay.insert(delay.end(), nPages - 1, delay[0]);
}

if (pageHeight == 0) return image;

// It is necessary to create the copy as otherwise, pageHeight will be ignored!
// Attaching metadata, need to copy the image.
VImage copy = image.copy();

copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
// Only set page-height if we have more than one page, or this could
// accidentally turn into an animated image later.
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
if (hasDelay) copy.set("delay", delay);
if (loop != -1) copy.set("loop", loop);

Expand Down Expand Up @@ -556,6 +550,14 @@ namespace sharp {
return copy;
}

/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image) {
return vips_image_get_page_height(image.get_image());
}

/*
Check the proposed format supports the current dimensions.
*/
Expand Down Expand Up @@ -882,4 +884,74 @@ namespace sharp {
return image;
}

std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement) {
if (swap) {
// Swap input width and height when requested.
std::swap(width, height);
}

double hshrink = 1.0;
double vshrink = 1.0;

if (targetWidth > 0 && targetHeight > 0) {
// Fixed width and height
hshrink = static_cast<double>(width) / targetWidth;
vshrink = static_cast<double>(height) / targetHeight;

switch (canvas) {
case Canvas::CROP:
case Canvas::MIN:
if (hshrink < vshrink) {
vshrink = hshrink;
} else {
hshrink = vshrink;
}
break;
case Canvas::EMBED:
case Canvas::MAX:
if (hshrink > vshrink) {
vshrink = hshrink;
} else {
hshrink = vshrink;
}
break;
case Canvas::IGNORE_ASPECT:
if (swap) {
std::swap(hshrink, vshrink);
}
break;
}
} else if (targetWidth > 0) {
// Fixed width
hshrink = static_cast<double>(width) / targetWidth;

if (canvas != Canvas::IGNORE_ASPECT) {
// Auto height
vshrink = hshrink;
}
} else if (targetHeight > 0) {
// Fixed height
vshrink = static_cast<double>(height) / targetHeight;

if (canvas != Canvas::IGNORE_ASPECT) {
// Auto width
hshrink = vshrink;
}
}

// We should not enlarge (oversample) the output image,
// if withoutEnlargement is specified.
if (withoutEnlargement) {
hshrink = std::max(1.0, hshrink);
vshrink = std::max(1.0, vshrink);
}

// We don't want to shrink so much that we send an axis to 0
hshrink = std::min(hshrink, static_cast<double>(width));
vshrink = std::min(vshrink, static_cast<double>(height));

return std::make_pair(hshrink, vshrink);
}

} // namespace sharp
26 changes: 24 additions & 2 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ namespace sharp {
MISSING
};

enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};

// How many tasks are in the queue?
extern volatile int counterQueue;

Expand Down Expand Up @@ -208,9 +216,8 @@ namespace sharp {

/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop);

/*
Remove animation properties from image.
Expand All @@ -232,6 +239,12 @@ namespace sharp {
*/
VImage SetDensity(VImage image, const double density);

/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image);

/*
Check the proposed format supports the current dimensions.
*/
Expand Down Expand Up @@ -325,6 +338,15 @@ namespace sharp {
*/
VImage EnsureAlpha(VImage image, double const value);

/*
Calculate the shrink factor, taking into account auto-rotate, the canvas
mode, and so on. The hshrink/vshrink are the amount to shrink the input
image axes by in order for the output axes (ie. after rotation) to match
the required thumbnail width/height and canvas mode.
*/
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement);

} // namespace sharp

#endif // SRC_COMMON_H_
2 changes: 0 additions & 2 deletions src/metadata.cc
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,6 @@ class MetadataWorker : public Napi::AsyncWorker {
}
if (baton->pageHeight > 0) {
info.Set("pageHeight", baton->pageHeight);
} else if (baton->pages > 0) {
info.Set("pageHeight", baton->height);
}
if (baton->loop >= 0) {
info.Set("loop", baton->loop);
Expand Down
94 changes: 94 additions & 0 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,98 @@ namespace sharp {
return image;
}

/*
* Split and crop each frame, reassemble, and update pageHeight.
*/
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
int nPages, int *pageHeight) {
if (top == 0 && height == *pageHeight) {
// Fast path; no need to adjust the height of the multi-page image
return image.extract_area(left, 0, width, image.height());
} else {
std::vector<VImage> pages;
pages.reserve(nPages);

// Split the image into cropped frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(left, *pageHeight * i + top, width, height));
}

// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));

// Update the page height
*pageHeight = height;

return assembled;
}
}

/*
* Split into frames, embed each frame, reassemble, and update pageHeight.
*/
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
std::vector<double> background, int nPages, int *pageHeight) {
if (top == 0 && height == *pageHeight) {
// Fast path; no need to adjust the height of the multi-page image
return image.embed(left, 0, width, image.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
} else if (left == 0 && width == image.width()) {
// Fast path; no need to adjust the width of the multi-page image
std::vector<VImage> pages;
pages.reserve(nPages);

// Rearrange the tall image into a vertical grid
image = image.grid(*pageHeight, nPages, 1);

// Do the embed on the wide image
image = image.embed(0, top, image.width(), height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));

// Split the wide image into frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(width * i, 0, width, height));
}

// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));

// Update the page height
*pageHeight = height;

return assembled;
} else {
std::vector<VImage> pages;
pages.reserve(nPages);

// Split the image into frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
}

// Embed each frame in the target size
for (int i = 0; i < nPages; i++) {
pages[i] = pages[i].embed(left, top, width, height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
}

// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));

// Update the page height
*pageHeight = height;

return assembled;
}
}

} // namespace sharp
12 changes: 12 additions & 0 deletions src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ namespace sharp {
*/
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace);

/*
* Split and crop each frame, reassemble, and update pageHeight.
*/
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
int nPages, int *pageHeight);

/*
* Split into frames, embed each frame, reassemble, and update pageHeight.
*/
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
std::vector<double> background, int nPages, int *pageHeight);

} // namespace sharp

#endif // SRC_OPERATIONS_H_
Loading

0 comments on commit 43e1cfc

Please sign in to comment.