Ensure scaling factors are calculated independently #452

Fixes bug introduced in v0.15.0 where, if the shrink operation
rounded up along one dimension, it could then also round up the
reduce operation on the same axis, creating a small stretch effect.
This commit is contained in:
Lovell Fuller 2016-06-13 23:03:41 +01:00
parent 61038888c4
commit 7d261a147d
4 changed files with 43 additions and 33 deletions

View File

@ -18,6 +18,10 @@ Requires libvips v8.3.1
[#443](https://github.com/lovell/sharp/pull/443) [#443](https://github.com/lovell/sharp/pull/443)
[@lemnisk8](https://github.com/lemnisk8) [@lemnisk8](https://github.com/lemnisk8)
* Ensure scaling factors are calculated independently to prevent rounding errors.
[#452](https://github.com/lovell/sharp/issues/452)
[@puzrin](https://github.com/puzrin)
* Add --sharp-cxx11 flag to compile with gcc's new C++11 ABI. * Add --sharp-cxx11 flag to compile with gcc's new C++11 ABI.
[#456](https://github.com/lovell/sharp/pull/456) [#456](https://github.com/lovell/sharp/pull/456)
[@kapouer](https://github.com/kapouer) [@kapouer](https://github.com/kapouer)

View File

@ -219,34 +219,46 @@ class PipelineWorker : public AsyncWorker {
// Scaling calculations // Scaling calculations
double xfactor = 1.0; double xfactor = 1.0;
double yfactor = 1.0; double yfactor = 1.0;
int targetResizeWidth = baton->width;
int targetResizeHeight = baton->height;
if (baton->width > 0 && baton->height > 0) { if (baton->width > 0 && baton->height > 0) {
// Fixed width and height // Fixed width and height
xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1); xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1); yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
switch (baton->canvas) { switch (baton->canvas) {
case Canvas::CROP: case Canvas::CROP:
xfactor = std::min(xfactor, yfactor); if (xfactor < yfactor) {
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor; yfactor = xfactor;
} else {
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break; break;
case Canvas::EMBED: case Canvas::EMBED:
xfactor = std::max(xfactor, yfactor); if (xfactor > yfactor) {
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor; yfactor = xfactor;
} else {
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break; break;
case Canvas::MAX: case Canvas::MAX:
if (xfactor > yfactor) { if (xfactor > yfactor) {
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor)); targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor; yfactor = xfactor;
} else { } else {
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor)); targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor; xfactor = yfactor;
} }
break; break;
case Canvas::MIN: case Canvas::MIN:
if (xfactor < yfactor) { if (xfactor < yfactor) {
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor)); targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor; yfactor = xfactor;
} else { } else {
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor)); targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor; xfactor = yfactor;
} }
break; break;
@ -259,23 +271,23 @@ class PipelineWorker : public AsyncWorker {
} }
} else if (baton->width > 0) { } else if (baton->width > 0) {
// Fixed width // Fixed width
xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1); xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
if (baton->canvas == Canvas::IGNORE_ASPECT) { if (baton->canvas == Canvas::IGNORE_ASPECT) {
baton->height = inputHeight; targetResizeHeight = baton->height = inputHeight;
} else { } else {
// Auto height // Auto height
yfactor = xfactor; yfactor = xfactor;
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor)); targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
} }
} else if (baton->height > 0) { } else if (baton->height > 0) {
// Fixed height // Fixed height
yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1); yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
if (baton->canvas == Canvas::IGNORE_ASPECT) { if (baton->canvas == Canvas::IGNORE_ASPECT) {
baton->width = inputWidth; targetResizeWidth = baton->width = inputWidth;
} else { } else {
// Auto width // Auto width
xfactor = yfactor; xfactor = yfactor;
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor)); targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
} }
} else { } else {
// Identity transform // Identity transform
@ -429,20 +441,14 @@ class PipelineWorker : public AsyncWorker {
// Swap input output width and height when rotating by 90 or 270 degrees // Swap input output width and height when rotating by 90 or 270 degrees
std::swap(shrunkWidth, shrunkHeight); std::swap(shrunkWidth, shrunkHeight);
} }
xresidual = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth); xresidual = static_cast<double>(targetResizeWidth) / static_cast<double>(shrunkWidth);
yresidual = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight); yresidual = static_cast<double>(targetResizeHeight) / static_cast<double>(shrunkHeight);
if (baton->canvas == Canvas::EMBED) { if (
xresidual = std::min(xresidual, yresidual); !baton->rotateBeforePreExtract &&
yresidual = xresidual; (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)
} else if (baton->canvas == Canvas::IGNORE_ASPECT) { ) {
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
std::swap(xresidual, yresidual); std::swap(xresidual, yresidual);
} }
} else {
xresidual = std::max(xresidual, yresidual);
yresidual = xresidual;
}
} }
// Ensure image has an alpha channel when there is an overlay // Ensure image has an alpha channel when there is an overlay
@ -673,10 +679,10 @@ class PipelineWorker : public AsyncWorker {
// use gravity in ovelay // use gravity in ovelay
if(overlayImageWidth <= baton->width) { if(overlayImageWidth <= baton->width) {
across = static_cast<int>(ceil(static_cast<double>(baton->width) / overlayImageWidth)); across = static_cast<int>(ceil(static_cast<double>(image.width()) / overlayImageWidth));
} }
if(overlayImageHeight <= baton->height) { if(overlayImageHeight <= baton->height) {
down = static_cast<int>(ceil(static_cast<double>(baton->height) / overlayImageHeight)); down = static_cast<int>(ceil(static_cast<double>(image.height()) / overlayImageHeight));
} }
if(across != 0 || down != 0) { if(across != 0 || down != 0) {
int left; int left;
@ -684,10 +690,10 @@ class PipelineWorker : public AsyncWorker {
overlayImage = overlayImage.replicate(across, down); overlayImage = overlayImage.replicate(across, down);
// the overlayGravity will now be used to CalculateCrop for extract_area // the overlayGravity will now be used to CalculateCrop for extract_area
std::tie(left, top) = CalculateCrop( std::tie(left, top) = CalculateCrop(
overlayImage.width(), overlayImage.height(), baton->width, baton->height, baton->overlayGravity overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity
); );
overlayImage = overlayImage.extract_area( overlayImage = overlayImage.extract_area(
left, top, baton->width, baton->height left, top, image.width(), image.height()
); );
} }
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0 // the overlayGravity was used for extract_area, therefore set it back to its default value of 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

After

Width:  |  Height:  |  Size: 789 B