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 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);
|
||||
float resizeScale(const procparams::ProcParams* params, int fw, int fh, int &imw, int &imh);
|
||||
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 Lanczos(const LabImage* src, LabImage* 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 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>
|
||||
#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
|
||||
{
|
||||
@ -333,7 +909,7 @@ void ImProcFunctions::Lanczos (const LabImage* src, LabImage* dst, float scale)
|
||||
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;
|
||||
imh = fh;
|
||||
@ -406,10 +982,6 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in
|
||||
break;
|
||||
}
|
||||
|
||||
if (fabs (dScale - 1.0) <= 1e-5) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
if (params->crop.enabled && params->resize.appliesTo == "Full image") {
|
||||
imw = params->crop.w;
|
||||
imh = params->crop.h;
|
||||
@ -418,9 +990,13 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in
|
||||
imh = refh;
|
||||
}
|
||||
|
||||
imw = (int) ( (double)imw * dScale + 0.5 );
|
||||
imh = (int) ( (double)imh * dScale + 0.5 );
|
||||
return (float)dScale;
|
||||
if (fabs (dScale - 1.0) <= 1e-5) {
|
||||
return 1.0;
|
||||
} else {
|
||||
imw = computeSize(imw, dScale);
|
||||
imh = computeSize(imh, dScale);
|
||||
return dScale;
|
||||
}
|
||||
}
|
||||
|
||||
void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale)
|
||||
@ -458,4 +1034,35 @@ void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale)
|
||||
#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);
|
||||
}
|
||||
|
||||
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
|
||||
int cx = 0, cy = 0, cw = labView->W, ch = labView->H;
|
||||
|
||||
if (params.crop.enabled) {
|
||||
cx = params.crop.x;
|
||||
cy = params.crop.y;
|
||||
cw = params.crop.w;
|
||||
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
|
||||
tmplab = new LabImage(cw, ch);
|
||||
|
||||
@ -1926,11 +1956,12 @@ private:
|
||||
}
|
||||
|
||||
if (labResize) { // resize lab data
|
||||
if ((labView->W != imw || labView->H != imh) &&
|
||||
(params.resize.allowUpscaling || (labView->W >= imw && labView->H >= imh))) {
|
||||
int imw = framingData.imgWidth;
|
||||
int imh = framingData.imgHeight;
|
||||
if (labView->W != imw || labView->H != imh) {
|
||||
// resize image
|
||||
tmplab = new LabImage(imw, imh);
|
||||
ipf.Lanczos(labView, tmplab, tmpScale);
|
||||
ipf.Lanczos(labView, tmplab, framingData.scale);
|
||||
delete labView;
|
||||
labView = tmplab;
|
||||
}
|
||||
@ -1984,14 +2015,20 @@ private:
|
||||
pl->setProgress(0.70);
|
||||
}
|
||||
|
||||
if (tmpScale != 1.0 && params.resize.method == "Nearest" &&
|
||||
(params.resize.allowUpscaling || (readyImg->getWidth() >= imw && readyImg->getHeight() >= imh))) { // resize rgb data (gamma applied)
|
||||
Imagefloat* tempImage = new Imagefloat(imw, imh);
|
||||
ipf.resize(readyImg, tempImage, tmpScale);
|
||||
delete readyImg;
|
||||
readyImg = tempImage;
|
||||
if (framingData.scale != 1.0 && params.resize.method == "Nearest") {
|
||||
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);
|
||||
ipf.resize(readyImg, tempImage, framingData.scale);
|
||||
delete readyImg;
|
||||
readyImg = tempImage;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Blit framing border + image
|
||||
|
||||
Exiv2Metadata info(imgsrc->getFileName());
|
||||
|
||||
switch (params.metadata.mode) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user