Calculate required image and frame sizes
* Refactor simpleprocess.cc to be more clear on resize dimensions * Implement image and frame sizing calculations * Resizes the image based on adjusted framing calculations * Missing functionality to draw border around image after resizing
This commit is contained in:
parent
b88ad569c2
commit
a96dc4cdde
@ -213,12 +213,31 @@ enum class BlurType {
|
|||||||
void sharpening(LabImage* lab, const procparams::SharpeningParams &sharpenParam, bool showMask = false);
|
void sharpening(LabImage* lab, const procparams::SharpeningParams &sharpenParam, bool showMask = false);
|
||||||
void sharpeningcam(CieImage* ncie, float** buffer, bool showMask = false);
|
void sharpeningcam(CieImage* ncie, float** buffer, bool showMask = false);
|
||||||
void transform(Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const FramesMetaData *metadata, int rawRotationDeg, bool fullImage, bool useOriginalBuffer = false);
|
void transform(Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const FramesMetaData *metadata, int rawRotationDeg, bool fullImage, bool useOriginalBuffer = false);
|
||||||
float resizeScale(const procparams::ProcParams* params, int fw, int fh, int &imw, int &imh);
|
|
||||||
void lab2monitorRgb(LabImage* lab, Image8* image);
|
void lab2monitorRgb(LabImage* lab, Image8* image);
|
||||||
|
|
||||||
|
double resizeScale(const procparams::ProcParams* params, int fw, int fh, int &imw, int &imh);
|
||||||
void resize(Imagefloat* src, Imagefloat* dst, float dScale);
|
void resize(Imagefloat* src, Imagefloat* dst, float dScale);
|
||||||
void Lanczos(const LabImage* src, LabImage* dst, float scale);
|
void Lanczos(const LabImage* src, LabImage* dst, float scale);
|
||||||
void Lanczos(const Imagefloat* src, Imagefloat* dst, float scale);
|
void Lanczos(const Imagefloat* src, Imagefloat* dst, float scale);
|
||||||
|
|
||||||
|
struct FramingArgs {
|
||||||
|
const procparams::ProcParams* params = nullptr;
|
||||||
|
int cropWidth = 0;
|
||||||
|
int cropHeight = 0;
|
||||||
|
int resizeWidth = 0;
|
||||||
|
int resizeHeight = 0;
|
||||||
|
double resizeScale = 1.0f;
|
||||||
|
};
|
||||||
|
struct FramingData {
|
||||||
|
bool enabled = false;
|
||||||
|
int imgWidth = 0;
|
||||||
|
int imgHeight = 0;
|
||||||
|
double scale = 1.0;
|
||||||
|
int framedWidth = 0;
|
||||||
|
int framedHeight = 0;
|
||||||
|
};
|
||||||
|
FramingData framing(const FramingArgs& args) const;
|
||||||
|
|
||||||
void deconvsharpening(float** luminance, float** buffer, const float* const * blend, int W, int H, const procparams::SharpeningParams &sharpenParam, double Scale);
|
void deconvsharpening(float** luminance, float** buffer, const float* const * blend, int W, int H, const procparams::SharpeningParams &sharpenParam, double Scale);
|
||||||
void deconvsharpeningloc(float** luminance, float** buffer, int W, int H, float** loctemp, int damp, double radi, int ite, int amo, int contrast, double blurrad, int sk);
|
void deconvsharpeningloc(float** luminance, float** buffer, int W, int H, float** loctemp, int damp, double radi, int ite, int amo, int contrast, double blurrad, int sk);
|
||||||
|
|
||||||
|
@ -33,6 +33,582 @@
|
|||||||
# include <iostream>
|
# include <iostream>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
using ProcParams = rtengine::procparams::ProcParams;
|
||||||
|
using FramingParams = rtengine::procparams::FramingParams;
|
||||||
|
|
||||||
|
using Basis = FramingParams::Basis;
|
||||||
|
using BorderSizing = FramingParams::BorderSizing;
|
||||||
|
using FramingMethod = FramingParams::FramingMethod;
|
||||||
|
|
||||||
|
enum class Orientation { LANDSCAPE, PORTRAIT };
|
||||||
|
enum class Side { WIDTH, HEIGHT };
|
||||||
|
|
||||||
|
struct Dimensions {
|
||||||
|
double width;
|
||||||
|
double height;
|
||||||
|
|
||||||
|
Dimensions() : width(0), height(0) {}
|
||||||
|
Dimensions(double w, double h) : width(w), height(h) {}
|
||||||
|
|
||||||
|
bool isDegenerate() const { return width <= 0.0 || height <= 0.0; }
|
||||||
|
|
||||||
|
double aspectRatio() const {
|
||||||
|
if (isDegenerate()) return 1.0;
|
||||||
|
else return static_cast<double>(width) / static_cast<double>(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Orientation orient() const {
|
||||||
|
return width >= height ? Orientation::LANDSCAPE : Orientation::PORTRAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inside(const Dimensions& other) const {
|
||||||
|
return width <= other.width && height <= other.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotate(Orientation newOrient) {
|
||||||
|
if (newOrient != orient()) {
|
||||||
|
std::swap(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Dimensions& other) const {
|
||||||
|
return width == other.width && height == other.height;
|
||||||
|
}
|
||||||
|
bool operator!=(const Dimensions& other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
Dimensions intersect(const Dimensions& other) const {
|
||||||
|
return Dimensions(std::min(width, other.width), std::min(height, other.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug(const char* prefix) const {
|
||||||
|
printf("%s w=%f h=%f ar=%f\n", prefix, width, height, aspectRatio());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResizeArgs {
|
||||||
|
Dimensions size;
|
||||||
|
double scale = 1.0;
|
||||||
|
|
||||||
|
ResizeArgs(const Dimensions& aSize, double aScale) : size(aSize), scale(aScale) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Framing {
|
||||||
|
public:
|
||||||
|
Framing(const ProcParams& params, int fullWidth, int fullHeight);
|
||||||
|
|
||||||
|
ResizeArgs adjustResizeForFraming(const ResizeArgs& resize) const;
|
||||||
|
Dimensions computeFramedSize(const Dimensions& imgSize) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Dimensions clampResize(const Dimensions& imgSize, const Dimensions& bounds) const;
|
||||||
|
ResizeArgs adjustResize(const ResizeArgs& resize, const Dimensions& newSize) const;
|
||||||
|
Dimensions computeRelativeImageBBoxInFrame(const Dimensions& imgSize,
|
||||||
|
const Dimensions& framedSize) const;
|
||||||
|
ResizeArgs resizeForFixedFrame(const ResizeArgs& resize) const;
|
||||||
|
ResizeArgs resizeForBBox(const ResizeArgs& resize) const;
|
||||||
|
Dimensions computeSizeWithBorders(const Dimensions& imgSize) const;
|
||||||
|
|
||||||
|
const ProcParams& allParams;
|
||||||
|
const FramingParams framing; // Make a copy to sanitize inputs
|
||||||
|
const Dimensions postCropImageSize;
|
||||||
|
const Dimensions maxUpscalingBBox;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Downscaling limit is 32x32px
|
||||||
|
constexpr double MIN_DOWNSCALE_PX = 32.0;
|
||||||
|
// Upscaling limit is 16x image size
|
||||||
|
constexpr double MAX_UPSCALE_FACTOR = 16.0;
|
||||||
|
|
||||||
|
int computeSize(int dim, double scale)
|
||||||
|
{
|
||||||
|
return static_cast<int>(static_cast<double>(dim) * scale + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
Orientation orient(const FramingParams& params, const Dimensions& imgSize) {
|
||||||
|
switch (params.orientation) {
|
||||||
|
case FramingParams::Orientation::LANDSCAPE:
|
||||||
|
return Orientation::LANDSCAPE;
|
||||||
|
case FramingParams::Orientation::PORTRAIT:
|
||||||
|
return Orientation::PORTRAIT;
|
||||||
|
case FramingParams::Orientation::AS_IMAGE:
|
||||||
|
default:
|
||||||
|
return imgSize.orient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double flipAspectRatioByOrientation(double aspectRatio, Orientation orient)
|
||||||
|
{
|
||||||
|
switch (orient) {
|
||||||
|
case Orientation::LANDSCAPE:
|
||||||
|
return aspectRatio >= 1.0 ? aspectRatio : 1.0 / aspectRatio;
|
||||||
|
case Orientation::PORTRAIT:
|
||||||
|
return aspectRatio <= 1.0 ? aspectRatio : 1.0 / aspectRatio;
|
||||||
|
default:
|
||||||
|
return aspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Side autoPickBasis(const FramingParams& params, const Dimensions& imgSize)
|
||||||
|
{
|
||||||
|
if (imgSize.isDegenerate()) {
|
||||||
|
if (imgSize.width <= 0) return Side::HEIGHT;
|
||||||
|
else return Side::WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
Orientation imgOrient = imgSize.orient();
|
||||||
|
double imgAspectRatio = imgSize.aspectRatio();
|
||||||
|
Orientation frameOrient = orient(params, imgSize);
|
||||||
|
double frameAspectRatio = flipAspectRatioByOrientation(params.aspectRatio, frameOrient);
|
||||||
|
|
||||||
|
if (frameOrient == imgOrient) {
|
||||||
|
// Pick the more constrained side (i.e. hits 0 border width first)
|
||||||
|
return imgAspectRatio >= frameAspectRatio ? Side::WIDTH : Side::HEIGHT;
|
||||||
|
} else if (imgOrient == Orientation::LANDSCAPE) {
|
||||||
|
// Image in landscape, frame in portrait
|
||||||
|
return Side::WIDTH;
|
||||||
|
} else {
|
||||||
|
// Image in portrait, frame in landscape
|
||||||
|
return Side::HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Side pickReferenceSide(const FramingParams& params, const Dimensions& imgSize)
|
||||||
|
{
|
||||||
|
switch (params.basis) {
|
||||||
|
case Basis::WIDTH:
|
||||||
|
return Side::WIDTH;
|
||||||
|
case Basis::HEIGHT:
|
||||||
|
return Side::HEIGHT;
|
||||||
|
case Basis::LONG:
|
||||||
|
return imgSize.width >= imgSize.height ? Side::WIDTH : Side::HEIGHT;
|
||||||
|
case Basis::SHORT:
|
||||||
|
return imgSize.width <= imgSize.height ? Side::WIDTH : Side::HEIGHT;
|
||||||
|
case Basis::AUTO:
|
||||||
|
default:
|
||||||
|
return autoPickBasis(params, imgSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool INSIDE_BBOX = true;
|
||||||
|
constexpr bool OUTSIDE_BBOX = false;
|
||||||
|
|
||||||
|
Dimensions clampToBBox(const Dimensions& img, const Dimensions& bbox, bool clampInside) {
|
||||||
|
double widthScale = 1.0;
|
||||||
|
double heightScale = 1.0;
|
||||||
|
if (bbox.width > 0) {
|
||||||
|
widthScale = img.width / bbox.width;
|
||||||
|
}
|
||||||
|
if (bbox.height > 0) {
|
||||||
|
heightScale = img.height / bbox.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions newSize = img;
|
||||||
|
|
||||||
|
if (clampInside) {
|
||||||
|
// If a side exceeds the bbox, scale down to bbox
|
||||||
|
double scale = std::max(widthScale, heightScale);
|
||||||
|
if (scale > 1.0) {
|
||||||
|
if (widthScale >= heightScale) {
|
||||||
|
newSize.width = bbox.width;
|
||||||
|
newSize.height = img.height / widthScale;
|
||||||
|
} else {
|
||||||
|
newSize.width = img.width / heightScale;
|
||||||
|
newSize.height = bbox.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If a side is within the bbox, scale up to bbox
|
||||||
|
double scale = std::min(widthScale, heightScale);
|
||||||
|
if (scale < 1.0) {
|
||||||
|
if (widthScale <= heightScale) {
|
||||||
|
newSize.width = bbox.width;
|
||||||
|
newSize.height = img.height / widthScale;
|
||||||
|
} else {
|
||||||
|
newSize.width = img.width / heightScale;
|
||||||
|
newSize.height = bbox.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions downscaleToTouchBBox(const Dimensions& img, const Dimensions& bbox) {
|
||||||
|
if (bbox.isDegenerate()) return Dimensions(0, 0);
|
||||||
|
if (!bbox.inside(img)) return img;
|
||||||
|
|
||||||
|
double widthScale = img.width / bbox.width;
|
||||||
|
double heightScale = img.height / bbox.height;
|
||||||
|
|
||||||
|
Dimensions downscaled;
|
||||||
|
if (widthScale <= heightScale) {
|
||||||
|
downscaled.width = bbox.width;
|
||||||
|
downscaled.height = img.height / widthScale;
|
||||||
|
} else {
|
||||||
|
downscaled.height = bbox.height;
|
||||||
|
downscaled.width = img.width / heightScale;
|
||||||
|
}
|
||||||
|
return downscaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions upscaleToBBox(const Dimensions& img, const Dimensions& bbox) {
|
||||||
|
if (bbox.isDegenerate()) return Dimensions(0, 0);
|
||||||
|
if (!img.inside(bbox)) return img;
|
||||||
|
|
||||||
|
double widthScale = img.width / bbox.width;
|
||||||
|
double heightScale = img.height / bbox.height;
|
||||||
|
|
||||||
|
Dimensions upscaled;
|
||||||
|
if (widthScale >= heightScale) {
|
||||||
|
upscaled.width = bbox.width;
|
||||||
|
upscaled.height = img.height / widthScale;
|
||||||
|
} else {
|
||||||
|
upscaled.height = bbox.height;
|
||||||
|
upscaled.width = img.width / heightScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return upscaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
double orientAspectRatio(const FramingParams& framing, const Dimensions& imgSize)
|
||||||
|
{
|
||||||
|
double aspectRatio = framing.aspectRatio;
|
||||||
|
Orientation borderOrient = orient(framing, imgSize);
|
||||||
|
if ((borderOrient == Orientation::PORTRAIT && aspectRatio > 1.0) ||
|
||||||
|
(borderOrient == Orientation::LANDSCAPE && aspectRatio < 1.0)) {
|
||||||
|
aspectRatio = 1.0 / aspectRatio;
|
||||||
|
}
|
||||||
|
return aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions fromAspectRatio(const Dimensions& size, double aspectRatio)
|
||||||
|
{
|
||||||
|
Dimensions result;
|
||||||
|
if (aspectRatio >= 1.0) {
|
||||||
|
result.height = size.height;
|
||||||
|
result.width = result.height * aspectRatio;
|
||||||
|
} else {
|
||||||
|
result.width = size.width;
|
||||||
|
result.height = result.width / aspectRatio;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramingParams sanitize(const FramingParams& dirty)
|
||||||
|
{
|
||||||
|
FramingParams framing = dirty;
|
||||||
|
framing.framedWidth = std::max(static_cast<int>(MIN_DOWNSCALE_PX), framing.framedWidth);
|
||||||
|
framing.framedHeight = std::max(static_cast<int>(MIN_DOWNSCALE_PX), framing.framedHeight);
|
||||||
|
framing.relativeBorderSize = std::max(0.0, std::min(1.0, framing.relativeBorderSize));
|
||||||
|
framing.minWidth = std::max(0, framing.minWidth);
|
||||||
|
framing.minHeight = std::max(0, framing.minHeight);
|
||||||
|
framing.absWidth = std::max(0, framing.absWidth);
|
||||||
|
framing.absHeight = std::max(0, framing.absHeight);
|
||||||
|
return framing;
|
||||||
|
}
|
||||||
|
|
||||||
|
Framing::Framing(const ProcParams& params, int fullWidth, int fullHeight) :
|
||||||
|
allParams(params),
|
||||||
|
framing(sanitize(params.framing)),
|
||||||
|
postCropImageSize(params.crop.enabled ?
|
||||||
|
Dimensions(params.crop.w, params.crop.h) :
|
||||||
|
Dimensions(fullWidth, fullHeight)),
|
||||||
|
maxUpscalingBBox(Dimensions(
|
||||||
|
computeSize(postCropImageSize.width, MAX_UPSCALE_FACTOR),
|
||||||
|
computeSize(postCropImageSize.height, MAX_UPSCALE_FACTOR)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions Framing::clampResize(const Dimensions& imgSize, const Dimensions& bounds) const
|
||||||
|
{
|
||||||
|
// Don't adjust above upscaling limit.
|
||||||
|
//
|
||||||
|
// If the upscaling limit is contained inside the target bounds, scale
|
||||||
|
// down the bounds to outside the upscaling limit. This is needed since
|
||||||
|
// scaling the bounds to fit inside the upscaling bbox may artificially
|
||||||
|
// reduce the upscaling limit due to aspect ratio differences.
|
||||||
|
Dimensions clampedBounds = maxUpscalingBBox.inside(bounds) ?
|
||||||
|
downscaleToTouchBBox(bounds, maxUpscalingBBox) :
|
||||||
|
clampToBBox(bounds, maxUpscalingBBox, INSIDE_BBOX);
|
||||||
|
|
||||||
|
if (!imgSize.inside(clampedBounds)) {
|
||||||
|
// Downscale large images to fit inside bounds (only if above limit)
|
||||||
|
|
||||||
|
Dimensions minSize(MIN_DOWNSCALE_PX, MIN_DOWNSCALE_PX);
|
||||||
|
if (!minSize.inside(imgSize)) {
|
||||||
|
// Skip images below downscaling limit
|
||||||
|
return imgSize;
|
||||||
|
} else if (!minSize.inside(clampedBounds)) {
|
||||||
|
// Go as small as possible without exceeding downscaling limit
|
||||||
|
return downscaleToTouchBBox(imgSize, minSize);
|
||||||
|
} else {
|
||||||
|
// Downscale large images to fit inside bounds
|
||||||
|
return clampToBBox(imgSize, clampedBounds, INSIDE_BBOX);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Consider upscaling...
|
||||||
|
if (!framing.allowUpscaling ||
|
||||||
|
imgSize.width == clampedBounds.width ||
|
||||||
|
imgSize.height == clampedBounds.height) {
|
||||||
|
return imgSize;
|
||||||
|
} else {
|
||||||
|
return upscaleToBBox(imgSize, clampedBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeArgs Framing::adjustResize(const ResizeArgs& resize, const Dimensions& bbox) const
|
||||||
|
{
|
||||||
|
Dimensions newSize = clampResize(resize.size, bbox);
|
||||||
|
double newScale = newSize.width / resize.size.width;
|
||||||
|
return ResizeArgs(newSize, newScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions Framing::computeRelativeImageBBoxInFrame(const Dimensions& imgSize,
|
||||||
|
const Dimensions& framedSize) const
|
||||||
|
{
|
||||||
|
if (imgSize.isDegenerate() || framedSize.isDegenerate()) {
|
||||||
|
return Dimensions(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double imgAspectRatio = imgSize.aspectRatio();
|
||||||
|
// Compute the width:height ratio of the border size for the requested
|
||||||
|
// image size and framed size.
|
||||||
|
//
|
||||||
|
// We do this by creating a dummy image. Then, scale the framed size to be
|
||||||
|
// larger than the dummy image such that there is a non-zero difference for
|
||||||
|
// widths and heights.
|
||||||
|
double borderAspectRatio = [&]()
|
||||||
|
{
|
||||||
|
Dimensions fakeImage = fromAspectRatio(framedSize, imgAspectRatio);
|
||||||
|
Dimensions bigFrame = clampToBBox(framedSize, fakeImage, OUTSIDE_BBOX);
|
||||||
|
bigFrame.width *= 2.0;
|
||||||
|
bigFrame.height *= 2.0;
|
||||||
|
|
||||||
|
Dimensions diff(bigFrame.width - fakeImage.width, bigFrame.height - fakeImage.height);
|
||||||
|
return diff.aspectRatio();
|
||||||
|
}();
|
||||||
|
|
||||||
|
Side side = pickReferenceSide(framing, imgSize);
|
||||||
|
double scale = framing.relativeBorderSize;
|
||||||
|
|
||||||
|
// Compute image and border lengths on basis side
|
||||||
|
double frameBasis = side == Side::WIDTH ? framedSize.width : framedSize.height;
|
||||||
|
double frameOther = side == Side::WIDTH ? framedSize.height : framedSize.width;
|
||||||
|
// frame_len = img_len + 2 * scale * img_len = (1 + 2 * scale) * img_len
|
||||||
|
double imgFrameScale = (1.0 + 2.0 * scale);
|
||||||
|
double imgBasis = frameBasis / imgFrameScale;
|
||||||
|
// border_len = (scale * img_len)
|
||||||
|
// = frame_len / (1 / scale + 2)
|
||||||
|
// = frame_len * scale / (1 + 2 * scale)
|
||||||
|
double borderBasis = frameBasis * scale / imgFrameScale;
|
||||||
|
|
||||||
|
// Compute image and border lengths for the non-basis side.
|
||||||
|
double imgBasisToOther = side == Side::WIDTH ? 1.0 / imgAspectRatio : imgAspectRatio;
|
||||||
|
double borderBasisToOther = side == Side::WIDTH ? 1.0 / borderAspectRatio : borderAspectRatio;
|
||||||
|
double imgOther = imgBasis * imgBasisToOther;
|
||||||
|
double borderOther = borderBasis * borderBasisToOther;
|
||||||
|
|
||||||
|
double maxImageBasis = frameBasis;
|
||||||
|
double maxImageOther = frameOther;
|
||||||
|
if (framing.minSizeEnabled) {
|
||||||
|
double minBorderBasis = static_cast<double>(
|
||||||
|
side == Side::WIDTH ? framing.minWidth : framing.minHeight);
|
||||||
|
double minBorderOther = static_cast<double>(
|
||||||
|
side == Side::WIDTH ? framing.minHeight : framing.minWidth);
|
||||||
|
|
||||||
|
if (borderOther < minBorderOther) {
|
||||||
|
maxImageOther = std::floor(frameOther - 2.0 * minBorderOther);
|
||||||
|
}
|
||||||
|
if (borderBasis < minBorderBasis) {
|
||||||
|
maxImageBasis = std::floor(frameBasis - 2.0 * minBorderBasis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imgOther > maxImageOther) {
|
||||||
|
imgOther = maxImageOther;
|
||||||
|
imgBasis = imgOther / imgBasisToOther;
|
||||||
|
}
|
||||||
|
if (imgBasis > maxImageBasis) {
|
||||||
|
imgBasis = maxImageBasis;
|
||||||
|
imgOther = imgBasis * imgBasisToOther;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (side == Side::WIDTH) {
|
||||||
|
return Dimensions(imgBasis, imgOther);
|
||||||
|
} else {
|
||||||
|
return Dimensions(imgOther, imgBasis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeArgs Framing::adjustResizeForFraming(const ResizeArgs& resize) const
|
||||||
|
{
|
||||||
|
if (!framing.enabled) return resize;
|
||||||
|
|
||||||
|
switch (framing.framingMethod) {
|
||||||
|
case FramingMethod::BBOX:
|
||||||
|
return resizeForBBox(resize);
|
||||||
|
case FramingMethod::FIXED_SIZE:
|
||||||
|
return resizeForFixedFrame(resize);
|
||||||
|
case FramingMethod::STANDARD:
|
||||||
|
default:
|
||||||
|
// No limits on framed size so do nothing
|
||||||
|
return resize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeArgs Framing::resizeForFixedFrame(const ResizeArgs& args) const
|
||||||
|
{
|
||||||
|
double framedWidth = framing.framedWidth;
|
||||||
|
double framedHeight = framing.framedHeight;
|
||||||
|
Dimensions frameSize(framedWidth, framedHeight);
|
||||||
|
|
||||||
|
Dimensions bbox;
|
||||||
|
if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) {
|
||||||
|
auto length = [](double frame, double border) {
|
||||||
|
return std::max(0.0, frame - 2.0 * border);
|
||||||
|
};
|
||||||
|
|
||||||
|
bbox = {
|
||||||
|
length(framedWidth, framing.absWidth),
|
||||||
|
length(framedHeight, framing.absHeight)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
bbox = computeRelativeImageBBoxInFrame(args.size, frameSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustResize(args, bbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeArgs Framing::resizeForBBox(const ResizeArgs& args) const
|
||||||
|
{
|
||||||
|
Dimensions boundary(framing.framedWidth, framing.framedHeight);
|
||||||
|
|
||||||
|
Dimensions bbox;
|
||||||
|
if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) {
|
||||||
|
auto length = [](double frame, double border) {
|
||||||
|
return std::max(0.0, frame - 2.0 * border);
|
||||||
|
};
|
||||||
|
|
||||||
|
bbox = {
|
||||||
|
length(boundary.width, framing.absWidth),
|
||||||
|
length(boundary.height, framing.absHeight)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// For the requested aspect ratio, it must fit inside the requested
|
||||||
|
// bounding box
|
||||||
|
double aspectRatio = orientAspectRatio(framing, args.size);
|
||||||
|
Dimensions ratioBBox = fromAspectRatio(boundary, aspectRatio);
|
||||||
|
ratioBBox = clampToBBox(ratioBBox, boundary, INSIDE_BBOX);
|
||||||
|
|
||||||
|
// Now we have the true max bounds for the framed image. Determine how the
|
||||||
|
// original image fits inside these bounds. This process is the same as
|
||||||
|
// in the fixed frame mode.
|
||||||
|
bbox = computeRelativeImageBBoxInFrame(args.size, ratioBBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustResize(args, bbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions Framing::computeFramedSize(const Dimensions& imgSize) const
|
||||||
|
{
|
||||||
|
if (!framing.enabled) return imgSize;
|
||||||
|
|
||||||
|
// For constrained frame sizes, check if the image size (without frame)
|
||||||
|
// exceeds the constrained size. This indicates that a combination of
|
||||||
|
// parameters caused the downscaling limit to be hit. In which case,
|
||||||
|
// just return the original image size (i.e. don't insert border).
|
||||||
|
//
|
||||||
|
// If the image fits the constrained size, assume that previous resize
|
||||||
|
// calculations were correct and trim off any excess borders. The excess
|
||||||
|
// may be from rounding errors or hitting some downscaling limit.
|
||||||
|
switch (framing.framingMethod) {
|
||||||
|
case FramingMethod::BBOX:
|
||||||
|
{
|
||||||
|
Dimensions fixed(framing.framedWidth, framing.framedHeight);
|
||||||
|
if (imgSize.inside(fixed)) {
|
||||||
|
Dimensions framedSize = computeSizeWithBorders(imgSize);
|
||||||
|
return clampToBBox(framedSize, fixed, INSIDE_BBOX);
|
||||||
|
} else {
|
||||||
|
return imgSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case FramingMethod::FIXED_SIZE:
|
||||||
|
{
|
||||||
|
Dimensions fixed(framing.framedWidth, framing.framedHeight);
|
||||||
|
return imgSize.inside(fixed) ? fixed : imgSize;
|
||||||
|
}
|
||||||
|
case FramingMethod::STANDARD:
|
||||||
|
default:
|
||||||
|
return computeSizeWithBorders(imgSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions Framing::computeSizeWithBorders(const Dimensions& imgSize) const
|
||||||
|
{
|
||||||
|
auto length = [](double img, double border) {
|
||||||
|
return img + 2.0 * border;
|
||||||
|
};
|
||||||
|
auto evalBorder = [](double side, double scale, double img) {
|
||||||
|
double otherSide = side * scale;
|
||||||
|
double totalBorder = otherSide - img;
|
||||||
|
return std::max(totalBorder, 0.0) * 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (framing.borderSizingMethod == BorderSizing::FIXED_SIZE) {
|
||||||
|
return Dimensions(length(imgSize.width, framing.absWidth),
|
||||||
|
length(imgSize.height, framing.absHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
Side side = pickReferenceSide(framing, imgSize);
|
||||||
|
double aspectRatio = orientAspectRatio(framing, imgSize);
|
||||||
|
|
||||||
|
// Compute the size with borders given the requested reference side length
|
||||||
|
// and aspect ratio
|
||||||
|
Dimensions borderSize;
|
||||||
|
double scale = framing.relativeBorderSize;
|
||||||
|
if (side == Side::WIDTH) {
|
||||||
|
borderSize.width = imgSize.width * scale;
|
||||||
|
double totalWidth = length(imgSize.width, borderSize.width);
|
||||||
|
borderSize.height = evalBorder(totalWidth, 1.0 / aspectRatio, imgSize.height);
|
||||||
|
} else {
|
||||||
|
borderSize.height = imgSize.height * scale;
|
||||||
|
double totalHeight = length(imgSize.height, borderSize.height);
|
||||||
|
borderSize.width = evalBorder(totalHeight, aspectRatio, imgSize.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framing.minSizeEnabled) {
|
||||||
|
Dimensions minSize(static_cast<double>(framing.minWidth),
|
||||||
|
static_cast<double>(framing.minHeight));
|
||||||
|
borderSize = clampToBBox(borderSize, minSize, OUTSIDE_BBOX);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimensions framedSize(length(imgSize.width, borderSize.width),
|
||||||
|
length(imgSize.height, borderSize.height));
|
||||||
|
|
||||||
|
// Check if the computed frame size satsifies the requested aspect ratio
|
||||||
|
// without cutting off the original image. If the image is cut off, use
|
||||||
|
// the smallest frame that preserves the original image and still
|
||||||
|
// satisfies the requested aspect ratio.
|
||||||
|
Dimensions minFramedSize = fromAspectRatio(imgSize, aspectRatio);
|
||||||
|
if (framing.minSizeEnabled) {
|
||||||
|
Dimensions limit = imgSize;
|
||||||
|
limit.width += 2.0 * framing.minWidth;
|
||||||
|
limit.height += 2.0 * framing.minHeight;
|
||||||
|
minFramedSize = clampToBBox(minFramedSize, limit, OUTSIDE_BBOX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minFramedSize.inside(framedSize)) {
|
||||||
|
return framedSize;
|
||||||
|
} else {
|
||||||
|
return minFramedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace rtengine
|
namespace rtengine
|
||||||
{
|
{
|
||||||
@ -333,7 +909,7 @@ void ImProcFunctions::Lanczos (const LabImage* src, LabImage* dst, float scale)
|
|||||||
delete[] wwh;
|
delete[] wwh;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh)
|
double ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh)
|
||||||
{
|
{
|
||||||
imw = fw;
|
imw = fw;
|
||||||
imh = fh;
|
imh = fh;
|
||||||
@ -406,10 +982,6 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fabs (dScale - 1.0) <= 1e-5) {
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params->crop.enabled && params->resize.appliesTo == "Full image") {
|
if (params->crop.enabled && params->resize.appliesTo == "Full image") {
|
||||||
imw = params->crop.w;
|
imw = params->crop.w;
|
||||||
imh = params->crop.h;
|
imh = params->crop.h;
|
||||||
@ -418,9 +990,13 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in
|
|||||||
imh = refh;
|
imh = refh;
|
||||||
}
|
}
|
||||||
|
|
||||||
imw = (int) ( (double)imw * dScale + 0.5 );
|
if (fabs (dScale - 1.0) <= 1e-5) {
|
||||||
imh = (int) ( (double)imh * dScale + 0.5 );
|
return 1.0;
|
||||||
return (float)dScale;
|
} else {
|
||||||
|
imw = computeSize(imw, dScale);
|
||||||
|
imh = computeSize(imh, dScale);
|
||||||
|
return dScale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale)
|
void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale)
|
||||||
@ -458,4 +1034,35 @@ void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImProcFunctions::FramingData ImProcFunctions::framing(const FramingArgs& args) const
|
||||||
|
{
|
||||||
|
FramingData result;
|
||||||
|
result.enabled = false;
|
||||||
|
result.imgWidth = args.resizeWidth;
|
||||||
|
result.imgHeight = args.resizeHeight;
|
||||||
|
result.scale = args.resizeScale;
|
||||||
|
result.framedWidth = args.resizeWidth;
|
||||||
|
result.framedHeight = args.resizeHeight;
|
||||||
|
|
||||||
|
if (!args.params || !args.params->resize.enabled) return result;
|
||||||
|
if (!args.params->framing.enabled) return result;
|
||||||
|
|
||||||
|
// For these calculations, try to keep everything as doubles to minimize
|
||||||
|
// rounding errors from intermediate steps!
|
||||||
|
|
||||||
|
Framing util(*params, args.cropWidth, args.cropHeight);
|
||||||
|
ResizeArgs resize(Dimensions(args.resizeWidth, args.resizeHeight), args.resizeScale);
|
||||||
|
ResizeArgs adjusted = util.adjustResizeForFraming(resize);
|
||||||
|
Dimensions framedSize = util.computeFramedSize(adjusted.size);
|
||||||
|
|
||||||
|
result.enabled = true;
|
||||||
|
result.imgWidth = std::round(adjusted.size.width);
|
||||||
|
result.imgHeight = std::round(adjusted.size.height);
|
||||||
|
result.scale = adjusted.scale;
|
||||||
|
result.framedWidth = std::round(framedSize.width);
|
||||||
|
result.framedHeight = std::round(framedSize.height);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace rtengine
|
@ -1893,20 +1893,50 @@ private:
|
|||||||
pl->setProgress(0.60);
|
pl->setProgress(0.60);
|
||||||
}
|
}
|
||||||
|
|
||||||
int imw, imh;
|
|
||||||
double tmpScale = ipf.resizeScale(¶ms, fw, fh, imw, imh);
|
|
||||||
bool labResize = params.resize.enabled && params.resize.method != "Nearest" && (tmpScale != 1.0 || params.prsharpening.enabled);
|
|
||||||
LabImage *tmplab;
|
|
||||||
|
|
||||||
// crop and convert to rgb16
|
// crop and convert to rgb16
|
||||||
int cx = 0, cy = 0, cw = labView->W, ch = labView->H;
|
int cx = 0, cy = 0, cw = labView->W, ch = labView->H;
|
||||||
|
|
||||||
if (params.crop.enabled) {
|
if (params.crop.enabled) {
|
||||||
cx = params.crop.x;
|
cx = params.crop.x;
|
||||||
cy = params.crop.y;
|
cy = params.crop.y;
|
||||||
cw = params.crop.w;
|
cw = params.crop.w;
|
||||||
ch = params.crop.h;
|
ch = params.crop.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImProcFunctions::FramingArgs framingArgs;
|
||||||
|
framingArgs.params = ¶ms;
|
||||||
|
framingArgs.cropWidth = cw;
|
||||||
|
framingArgs.cropHeight = ch;
|
||||||
|
{
|
||||||
|
int imw, imh;
|
||||||
|
double tmpScale = ipf.resizeScale(¶ms, fw, fh, imw, imh);
|
||||||
|
framingArgs.resizeWidth = imw;
|
||||||
|
framingArgs.resizeHeight = imh;
|
||||||
|
framingArgs.resizeScale = tmpScale;
|
||||||
|
|
||||||
|
// If upscaling is not permitted, keep original sizing
|
||||||
|
if ((cw < imw || ch < imh) && !params.resize.allowUpscaling) {
|
||||||
|
framingArgs.resizeWidth = cw;
|
||||||
|
framingArgs.resizeHeight = ch;
|
||||||
|
framingArgs.resizeScale = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If framing is not enabled, resize values simply pass through to output
|
||||||
|
ImProcFunctions::FramingData framingData = ipf.framing(framingArgs);
|
||||||
|
printf("Framing Parameters (enabled=%s)\n", framingData.enabled ? "yes" : "no");
|
||||||
|
printf(" Crop: w=%d h=%d\n", cw, ch);
|
||||||
|
printf(" Original resize: w=%d h=%d s=%f\n",
|
||||||
|
framingArgs.resizeWidth, framingArgs.resizeHeight, framingArgs.resizeScale);
|
||||||
|
printf(" Framed image size: w=%d h=%d s=%f\n",
|
||||||
|
framingData.imgWidth, framingData.imgHeight, framingData.scale);
|
||||||
|
printf(" Total size: w=%d h=%d\n",
|
||||||
|
framingData.framedWidth, framingData.framedHeight);
|
||||||
|
|
||||||
|
bool labResize = params.resize.enabled && params.resize.method != "Nearest" &&
|
||||||
|
(framingData.scale != 1.0 || params.prsharpening.enabled || framingData.enabled);
|
||||||
|
|
||||||
|
LabImage *tmplab = nullptr;
|
||||||
|
if (params.crop.enabled) {
|
||||||
if (labResize) { // crop lab data
|
if (labResize) { // crop lab data
|
||||||
tmplab = new LabImage(cw, ch);
|
tmplab = new LabImage(cw, ch);
|
||||||
|
|
||||||
@ -1926,11 +1956,12 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (labResize) { // resize lab data
|
if (labResize) { // resize lab data
|
||||||
if ((labView->W != imw || labView->H != imh) &&
|
int imw = framingData.imgWidth;
|
||||||
(params.resize.allowUpscaling || (labView->W >= imw && labView->H >= imh))) {
|
int imh = framingData.imgHeight;
|
||||||
|
if (labView->W != imw || labView->H != imh) {
|
||||||
// resize image
|
// resize image
|
||||||
tmplab = new LabImage(imw, imh);
|
tmplab = new LabImage(imw, imh);
|
||||||
ipf.Lanczos(labView, tmplab, tmpScale);
|
ipf.Lanczos(labView, tmplab, framingData.scale);
|
||||||
delete labView;
|
delete labView;
|
||||||
labView = tmplab;
|
labView = tmplab;
|
||||||
}
|
}
|
||||||
@ -1984,13 +2015,19 @@ private:
|
|||||||
pl->setProgress(0.70);
|
pl->setProgress(0.70);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tmpScale != 1.0 && params.resize.method == "Nearest" &&
|
if (framingData.scale != 1.0 && params.resize.method == "Nearest") {
|
||||||
(params.resize.allowUpscaling || (readyImg->getWidth() >= imw && readyImg->getHeight() >= imh))) { // resize rgb data (gamma applied)
|
int imw = framingData.imgWidth;
|
||||||
|
int imh = framingData.imgHeight;
|
||||||
|
if (readyImg->getWidth() != imw || readyImg->getHeight() != imh) {
|
||||||
|
// resize rgb data (gamma applied)
|
||||||
Imagefloat* tempImage = new Imagefloat(imw, imh);
|
Imagefloat* tempImage = new Imagefloat(imw, imh);
|
||||||
ipf.resize(readyImg, tempImage, tmpScale);
|
ipf.resize(readyImg, tempImage, framingData.scale);
|
||||||
delete readyImg;
|
delete readyImg;
|
||||||
readyImg = tempImage;
|
readyImg = tempImage;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Blit framing border + image
|
||||||
|
|
||||||
Exiv2Metadata info(imgsrc->getFileName());
|
Exiv2Metadata info(imgsrc->getFileName());
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user