Copy link to clipboard
Copied
Hello. Please help me solve the problem.
I have a lot of studio photos taken without a tripod, so the horizon is often tilted at different angles.
There is a clear contrast between the wall and the floor.
I need to automatically straighten the horizon in these photos, no change in perspective, no cropping. Just the angle of the slope.
Lightroom, camera raw are not up to the task.
The manual method, using a ruler, is not for me either.
Perhaps there is some Script that can help me solve my problem?
*Sample photo added.
I do not know how to work with java script, but I have some ideas.
If there are people here who understand this, tell me if it is realistic to implement, or am I mistaken)
By @Andrew Bold
Your ideas sound reasonable, but don't forget that we are limited by the runtime environment. Photoshop's scripting environment severely limits our ability to analyze images.
There are several ways to solve your problem, but all of them have some disadvantages and depend heavily on how the rest of the imag
...Hi! Unfortunately, such scripts are not universal and it works well only for solving a specific problem.
Try this one. It works well on the image posted to you, but there may be issues with other images.
I've modified the script so that it tries to find an area in the bottom half of the frame with a big difference in brightness, assuming that's where the floor boundary is. For large images, you can increase the size of the BATCH_SIZE constant (this is the number of consecutive pixels within which
Copy link to clipboard
Copied
Copy link to clipboard
Copied
The Upright feature in Camera Raw may be your best bet for two reasons: It can quickly correct multiple selected images in one click with no need for a script or action, and it can analyze each image and apply a different correct solution for each image. You need the second feature because the error is different in each picture.
And the error variations are challenging enough that only one Upright option I tried could fix both pictures with no manual intervention: The Full option. For some reason, the Level and Vertical options did something wrong on at least one of the images. But with the Full option, both images were corrected in one click, as shown in the demo below. Hopefully this will work as well on many more images than just these two. Then you could select any number of images and correct them all in one click.
The images were opened into Camera Raw from Adobe Bridge. Of course this works only if JPEG files are enabled for Camera Raw. (In Camera Raw preferences, File Handling, JPEG, HEIC, and TIFF Handling.) Camera Raw is also available as a filter in Photoshop, but using the filter version is not recommended because it works only for a layer in the current document, so you can’t load multiple images into the Camera Raw filter.
The Upright feature works the same way in Lightroom Classic, and you can batch-apply it using its Sync or Auto Sync features.
Copy link to clipboard
Copied
Hi. Thanks for the answers, but I wrote in the description that this method doesn't work for me.
I will explain why. There are several reasons:
I'm even fine with leaving transparent areas on the picture that appear when I tilt the image.
Copy link to clipboard
Copied
I'm even fine with leaving transparent areas on the picture that appear when I tilt the image.
By @Andrew Bold
Oh, OK. Originally I thought that would be a big problem because in the picture of the female with no margin at the bottom of the frame, any rotation-only solution would either cut off the feet (to avoid transparent areas) or leave a transparent area (to avoid cutting off the feet). But if you are OK with transparent areas, then a rotation-only solution could work. And maybe the transparent area could be filled in with Content-Aware Fill, if it’s just the floor.
But the challenge now is in automating all of that so you don’t have to do much manually. I am not good with scripting so unless someone like Stephen knows how, your solution might be outside Adobe software, such as the examples you found on the Internet.
Copy link to clipboard
Copied
Yes, that's why transparent parts are not a problem. Just with Content-Aware Fill I made an action that removes empty areas🙂
Thank you for your answers!
Copy link to clipboard
Copied
@Andrew Bold – I thought that due to the volume of images, you were looking for an automated method, however, it appears that you are OK with doing this manually on each image.
Sorry to ask again, but you didn't reply as to why the ruler + straighten option doesn't work for you.
You can make the Background image into a floating layer and expand the canvas a little if needed. Then run the ruler tool over the horizon. Then run the Straighten.jsx script from the ruler toolbar button. Next use Image/Trim to remove transparent pixels. Then content aware fill etc. All of this can be recorded into an action to semi-automate the task.
Copy link to clipboard
Copied
Sorry, I answered that question in another post.
The ruler tool really doesn't work for me, as I need full automation.
I get hundreds of photos a day, and even semi-automation is very time consuming.
I want to use my straightening as one step in full automation. That's why I don't want an option that crops out parts of the original image, changes perspective, or alters the original image in any way.
The only thing that works for me is to change the angle of the picture without losing any of its parts.
My algorithm would look like this:
- Changing the angle of the image (without any cropping)
- Transparent holes are restored with Content-Aware Fill.
- Automatic cropping of the model, with the required distances on all sides of the canvas. Model centering.
- some automatic manipulations with color.
- saving the finished picture with the desired name, format, size, to the desired location.
This is all in one action that I will apply to batch processing hundreds of photos every day.
I already have all of these steps implemented except one. Except for that first step with alignment!🤯
That's why I got so caught up in this topic)
Copy link to clipboard
Copied
There are limited options. The automated methods are not acceptable to you. The manual method is not acceptable due to the volume of images. So I'm out of ideas. Good luck!
Copy link to clipboard
Copied
Hopefully if you get hundreds of these photos a day, the quality is better for most of them. I didn't touch on this because its off-topic but the easiest process is to get it right in camera.
Maybe its time to go back to whomever is sending you these photos and begin rejecting the ones that are not up to spec. There is no automated method to do what you want, period.
Copy link to clipboard
Copied
I agree. I've never had success with automated straightening. Most often, it's a fiddly manual process.
Copy link to clipboard
Copied
Lightroom, camera raw are not up to the task.
By @Andrew Bold
Lr, ACR and the CR Filter in Photoshop was going to be my first and only suggestion (Geometry/Upright/Level).
Why isn't it up to the task?
The manual method, using a ruler, is not for me either.
Why?
Perhaps there is some Script that can help me solve my problem?
Scripts can't perform magic, they need to leverage underlying features to correct geometry. For example, it is possible to script the Geometry/Upright/Vertical in the CR Filter in Photoshop. This would not use static values, it would be adaptive/variable for each image.
Copy link to clipboard
Copied
I do not know how to work with java script, but I have some ideas.
If there are people here who understand this, tell me if it is realistic to implement, or am I mistaken)
Here's my guess:
1. To align the image, we need to find 2 points, connect them with a line, and find out what is the angle between this line and the horizon. This value will be the angle by which we need to rotate the original image.
2. A similar method, we find 2 points and compare the distance from them to the edge of the canvas. The scrip should find the difference in distance and rotate the original image by half of this difference? (L1-L2)\2.
"I'm very bad at geometry too :), so don't judge me harshly."
3. I found the third method on the Internet.
It uses several methods, such as:
- Canny edge detector (https://en.wikipedia.org/wiki/Canny_edge_detector)
- Hough transform (https://en.wikipedia.org/wiki/Hough_transform)
In the original this article is in another language, so I tried to translate it and saved it in a pdf.
It describes step by step how this algorithm is implemented.
https://drive.google.com/drive/folders/14KnwoBWpd7lpVNgUBb-KTHGLJpKOTY3u?usp=sharing
Copy link to clipboard
Copied
I think there are many ways in which we can find the extremes of "A" and "B."
For example:
1. Cut out the image inside except for a few pixels on the sides.
2. Using channels and levels to achieve a mask. Remove everything except the floor parts. And with the help of selection, make a stroke around these areas. As a result, we will have vector points, the coordinates of which can be used to calculate everything we need (?).
That's like the first option I had in my head to find vector coordinates, maybe I'm wrong about something.
Copy link to clipboard
Copied
Greetings @jazz-y !
Could you help me a bit with modifying this script?
I'm not very successful in modifying it.....
I'm trying to change this script for other photos (Different background, where there is no difference in brightness between wall and floor)
But in those photos, there is a clear black bar between the wall and the floor.
I thought it would be logical to include one "Threshold"(up 12 percent) correction step in the action. To get a clear black line.
After that, to modify the script so that the script detects the black line and not the absolute difference in brightness "findFloor".
But due to my poor knowledge of scripting I was not able to get a working result.
I will be very grateful if you have an opportunity to help me
Copy link to clipboard
Copied
Hi! Unfortunately, such scripts are not universal and it works well only for solving a specific problem.
Try this one. It works well on the image posted to you, but there may be issues with other images.
I've modified the script so that it tries to find an area in the bottom half of the frame with a big difference in brightness, assuming that's where the floor boundary is. For large images, you can increase the size of the BATCH_SIZE constant (this is the number of consecutive pixels within which the script tries to find the maximum brightness difference)
var apl = new AM('application'),
doc = new AM('document'),
lr = new AM('layer'),
floorY = [];
const STRIPE_SIZE = 25,
MAX_DE = 5;
try {
if (apl.getProperty('numberOfDocuments')) {
var docRes = doc.getProperty('resolution'),
docW = doc.getProperty('width') * docRes / 72,
docH = doc.getProperty('height') * docRes / 72;
activeDocument.suspendHistory('Find floor line', 'function() {}')
activeDocument.suspendHistory('Get left stripe', 'measureDocument(floorY)')
doc.stepBack();
activeDocument.suspendHistory('Get right stripe', 'measureDocument(floorY)')
doc.stepBack();
activeDocument.suspendHistory('Rotate Document', 'rotateDocument()')
}
} catch (e) { alert('A lot of things can go wrong in this script. :(\n\n' + e) }
function measureDocument(floor) {
doc.flatten()
doc.convertToGrayscale();
floor.length ? doc.selectStrip(docH * 0.5, 0, docH, 1) : doc.selectStrip(docH * 0.5, docW - 1, docH, docW);
doc.crop();
var f = new File(Folder.temp + '/colors.raw');
doc.saveToRAW(f)
floor.push(findFloor(f));
}
function rotateDocument() {
var title = lr.getProperty('name'),
id = lr.getProperty('layerID')
lr.duplicate(title)
lr.deleteLayer(id);
lr.rotate(Math.atan2(floorY[1] - floorY[0], docW) * 180 / Math.PI)
}
function findFloor(f) {
var content = '';
if (f.exists) {
f.open('r');
f.encoding = "BINARY";
content = f.read();
f.close();
f.remove();
var colors = function (s) {
var m = 0, c = [];
for (var i = 0; i < s.length; i++) {
var k = s.charCodeAt(i); m += k; c.push(k)
};
return c
}(content),
dE = function (a, b) {
return Math.round(Math.sqrt(Math.pow(a - b, 2)));
},
cur = 0,
colorDiff = [];
do {
var stripe = [],
median = 0;
for (cur; cur < colors.length; cur++) {
median += colors[cur]
stripe.push(colors[cur])
if (stripe.length >= STRIPE_SIZE) { cur++; break };
}
median = median / stripe.length
for (var i = 0; i < stripe.length; i++) {
colorDiff.push(dE(stripe[i], median))
}
if (cur == colors.length) break;
} while (true)
var max = 0,
height = 0;
for (var i = 0; i < colorDiff.length; i++) {
if (colorDiff[i] >= max) { max = colorDiff[i]; height = i };
}
for (var i = height; i >= 0; i--) {
if (dE(colors[height], colors[i]) > MAX_DE) return i + 1;
}
}
return height;
}
function AM(target) {
var s2t = stringIDToTypeID,
t2s = typeIDToStringID;
target = target ? s2t(target) : null;
this.getProperty = function (property, id, idxMode) {
property = s2t(property);
(r = new ActionReference()).putProperty(s2t('property'), property);
id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
return getDescValue(executeActionGet(r), property)
}
this.hasProperty = function (property, id, idxMode) {
property = s2t(property);
(r = new ActionReference()).putProperty(s2t('property'), property);
id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
: r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
return executeActionGet(r).hasKey(property)
}
this.convertToGrayscale = function () {
(d = new ActionDescriptor()).putClass(s2t("to"), s2t("grayscaleMode"));
executeAction(s2t("convertMode"), d, DialogModes.NO);
}
this.selectStrip = function (top, left, bottom, right) {
(r = new ActionReference()).putProperty(s2t("channel"), s2t("selection"));
(d = new ActionDescriptor()).putReference(s2t("null"), r);
(d1 = new ActionDescriptor()).putUnitDouble(s2t("top"), s2t("pixelsUnit"), top);
d1.putUnitDouble(s2t("left"), s2t("pixelsUnit"), left);
d1.putUnitDouble(s2t("bottom"), s2t("pixelsUnit"), bottom);
d1.putUnitDouble(s2t("right"), s2t("pixelsUnit"), right);
d.putObject(s2t("to"), s2t("rectangle"), d1);
executeAction(s2t("set"), d, DialogModes.NO);
}
this.flatten = function () {
executeAction(s2t("flattenImage"), new ActionDescriptor(), DialogModes.NO);
}
this.crop = function () {
(d = new ActionDescriptor()).putBoolean(s2t("delete"), true);
executeAction(s2t("crop"), d, DialogModes.NO);
}
this.saveToRAW = function (f) {
(d = new ActionDescriptor()).putBoolean(s2t('copy'), true);
(d1 = new ActionDescriptor()).putObject(s2t("as"), s2t("rawFormat"), d);
d1.putPath(s2t("in"), f);
executeAction(s2t("save"), d1, DialogModes.NO);
}
this.stepBack = function () {
(r = new ActionReference()).putEnumerated(charIDToTypeID('HstS'), s2t('ordinal'), s2t('previous'));
(d = new ActionDescriptor()).putReference(s2t('null'), r);
executeAction(s2t('select'), d, DialogModes.NO);
}
this.duplicate = function (title) {
(r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
(d = new ActionDescriptor()).putReference(s2t("null"), r);
d.putString(s2t("name"), title);
executeAction(s2t("duplicate"), d, DialogModes.NO);
}
this.deleteLayer = function (id) {
(r = new ActionReference()).putIdentifier(s2t("layer"), id);
(d = new ActionDescriptor()).putReference(s2t("null"), r);
executeAction(s2t("delete"), d, DialogModes.NO);
}
this.rotate = function (angle) {
(r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
(d = new ActionDescriptor()).putReference(s2t("null"), r);
d.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage"));
d.putUnitDouble(s2t("angle"), s2t("angleUnit"), angle);
d.putEnumerated(s2t("interfaceIconFrameDimmed"), s2t("interpolationType"), s2t("bicubic"));
executeAction(s2t("transform"), d, DialogModes.NO);
}
function getDescValue(d, p) {
switch (d.getType(p)) {
case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
case DescValueType.LISTTYPE: return d.getList(p);
case DescValueType.REFERENCETYPE: return d.getReference(p);
case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
case DescValueType.STRINGTYPE: return d.getString(p);
case DescValueType.INTEGERTYPE: return d.getInteger(p);
case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
case DescValueType.DOUBLETYPE: return d.getDouble(p);
case DescValueType.ALIASTYPE: return d.getPath(p);
case DescValueType.CLASSTYPE: return d.getClass(p);
case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
default: break;
};
}
}
Copy link to clipboard
Copied
You're a genius! As always, very helpful. And the script works perfectly!
Thanks again for your help!
Copy link to clipboard
Copied
I have the same idea when editing my portraits. I think if I use straight strokes from both eyes it will be more accurate
Copy link to clipboard
Copied
I do not know how to work with java script, but I have some ideas.
If there are people here who understand this, tell me if it is realistic to implement, or am I mistaken)
By @Andrew Bold
Your ideas sound reasonable, but don't forget that we are limited by the runtime environment. Photoshop's scripting environment severely limits our ability to analyze images.
There are several ways to solve your problem, but all of them have some disadvantages and depend heavily on how the rest of the images will be similar to the samples you provided.
I follow this logic:
The script is not perfect, but it works well with the examples above.
var apl = new AM('application'),
doc = new AM('document'),
lr = new AM('layer'),
floorY = [];
const ERR_RATE = 5;
try {
if (apl.getProperty('numberOfDocuments')) {
var docRes = doc.getProperty('resolution'),
docW = doc.getProperty('width') * docRes / 72,
docH = doc.getProperty('height') * docRes / 72;
activeDocument.suspendHistory('Find floor line', 'function() {}')
activeDocument.suspendHistory('Get left stripe', 'measureDocument(floorY)')
doc.stepBack();
activeDocument.suspendHistory('Get right stripe', 'measureDocument(floorY)')
doc.stepBack();
activeDocument.suspendHistory('Rotate Document', 'rotateDocument()')
}
} catch (e) { alert('A lot of things can go wrong in this script. :(\n\n' + e) }
function measureDocument(floor) {
doc.flatten()
doc.convertToGrayscale();
floor.length ? doc.selectStrip(0, docW - 1, docH, docW) : doc.selectStrip(0, 0, docH, 1)
doc.crop()
var f = new File(Folder.temp + '/colors.raw');
doc.saveToRAW(f)
floor.push(findFloor(f));
}
function rotateDocument() {
var title = lr.getProperty('name'),
id = lr.getProperty('layerID')
lr.duplicate(title)
lr.deleteLayer(id)
lr.rotate(Math.atan2(floorY[1] - floorY[0], docW) * 180 / Math.PI)
}
function findFloor(f) {
var content = '';
if (f.exists) {
f.open('r');
f.encoding = "BINARY";
content = f.read();
f.close();
f.remove();
var colors = function (s) {
var m = 0, c = [];
for (var i = 0; i < s.length; i++) {
var k = s.charCodeAt(i); m += k; c.push(k)
};
return { colors: c, median: m / s.length }
}(content),
dE = function (c) {
var m = 0; d = [];
for (var i = 0; i < c.colors.length; i++) {
var k = Math.sqrt(Math.pow(c.median - c.colors[i], 2)); d.push(k); m += k
};
return { dE: d, median: m / c.colors.length }
}(colors),
threshold = function (dE) {
var t = [];
for (var i = 0; i < dE.dE.length; i++)
t.push(dE.dE[i] > dE.median ? 1 : 0);
return t.reverse();
}(dE);
var height = 0,
err = 0;
for (var i = 0; i < threshold.length; i++) {
height++
if (threshold[i] == 1) {
err = 0
} else {
err++
if (err >= ERR_RATE) {
height -= err;
break;
}
}
}
}
return height;
}
function AM(target) {
var s2t = stringIDToTypeID,
t2s = typeIDToStringID;
target = target ? s2t(target) : null;
this.getProperty = function (property, id, idxMode) {
property = s2t(property);
(r = new ActionReference()).putProperty(s2t('property'), property);
id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
return getDescValue(executeActionGet(r), property)
}
this.hasProperty = function (property, id, idxMode) {
property = s2t(property);
(r = new ActionReference()).putProperty(s2t('property'), property);
id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
: r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
return executeActionGet(r).hasKey(property)
}
this.convertToGrayscale = function () {
(d = new ActionDescriptor()).putClass(s2t("to"), s2t("grayscaleMode"));
executeAction(s2t("convertMode"), d, DialogModes.NO);
}
this.selectStrip = function (top, left, bottom, right) {
(r = new ActionReference()).putProperty(s2t("channel"), s2t("selection"));
(d = new ActionDescriptor()).putReference(s2t("null"), r);
(d1 = new ActionDescriptor()).putUnitDouble(s2t("top"), s2t("pixelsUnit"), top);
d1.putUnitDouble(s2t("left"), s2t("pixelsUnit"), left);
d1.putUnitDouble(s2t("bottom"), s2t("pixelsUnit"), bottom);
d1.putUnitDouble(s2t("right"), s2t("pixelsUnit"), right);
d.putObject(s2t("to"), s2t("rectangle"), d1);
executeAction(s2t("set"), d, DialogModes.NO);
}
this.flatten = function () {
executeAction(s2t("flattenImage"), new ActionDescriptor(), DialogModes.NO);
}
this.crop = function () {
(d = new ActionDescriptor()).putBoolean(s2t("delete"), true);
executeAction(s2t("crop"), d, DialogModes.NO);
}
this.saveToRAW = function (f) {
(d = new ActionDescriptor()).putBoolean(s2t('copy'), true);
(d1 = new ActionDescriptor()).putObject(s2t("as"), s2t("rawFormat"), d);
d1.putPath(s2t("in"), f);
executeAction(s2t("save"), d1, DialogModes.NO);
}
this.stepBack = function () {
(r = new ActionReference()).putEnumerated(charIDToTypeID('HstS'), s2t('ordinal'), s2t('previous'));
(d = new ActionDescriptor()).putReference(s2t('null'), r);
executeAction(s2t('select'), d, DialogModes.NO);
}
this.duplicate = function (title) {
(r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
(d = new ActionDescriptor()).putReference(s2t("null"), r);
d.putString(s2t("name"), title);
executeAction(s2t("duplicate"), d, DialogModes.NO);
}
this.deleteLayer = function (id) {
(r = new ActionReference()).putIdentifier(s2t("layer"), id);
(d = new ActionDescriptor()).putReference(s2t("null"), r);
executeAction(s2t("delete"), d, DialogModes.NO);
}
this.rotate = function (angle) {
(r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
(d = new ActionDescriptor()).putReference(s2t("null"), r);
d.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage"));
d.putUnitDouble(s2t("angle"), s2t("angleUnit"), angle);
d.putEnumerated(s2t("interfaceIconFrameDimmed"), s2t("interpolationType"), s2t("bicubic"));
executeAction(s2t("transform"), d, DialogModes.NO);
}
function getDescValue(d, p) {
switch (d.getType(p)) {
case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
case DescValueType.LISTTYPE: return d.getList(p);
case DescValueType.REFERENCETYPE: return d.getReference(p);
case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
case DescValueType.STRINGTYPE: return d.getString(p);
case DescValueType.INTEGERTYPE: return d.getInteger(p);
case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
case DescValueType.DOUBLETYPE: return d.getDouble(p);
case DescValueType.ALIASTYPE: return d.getPath(p);
case DescValueType.CLASSTYPE: return d.getClass(p);
case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
default: break;
};
}
}
Copy link to clipboard
Copied
Sir, this is awesome! 🤩
I tested the script on 10 photos and it worked 100% of the time. It works even with a different background (There's a wooden floor and a gray wall).
I admire your skilfulness! It works much better for me than the standard Photoshop and Lightroom methods.
I am very grateful to you! 🤗
I really regret that I don't have the knowledge to do such useful things.
As soon as I will have time, I will test this script on a lot of pictures, then I will be able to describe my impression and tell about errors (if any).
Copy link to clipboard
Copied
There will certainly be errors. The algorithm will fail if the contrast between the wall or floor is low or if there are large objects on the wall or floor that are very different from the general background, however I hope that my code will allow you to correct most of the photos.
Copy link to clipboard
Copied
Well, you can't. Either something gets chopped off or there is distortion. You may be able to manually adjust and have transparent areas on the corners, that would require manual fill. Or you can manually use Perspective/Puppet Warp, those are powerful but can be tricky and slow.
Next time hold your camera straight and give plenty of room around the subject for cropping if needed.
Copy link to clipboard
Copied
I chose an unfortunate example (the picture with the girl). Don't take it as a rule.
99.9% of photos don't have cropped legs 🙂
In this case, you don't need to pay attention to it.
The transparent parts I can easily remove with action.
The main thing I'm interested in now is just to rotate the picture, without cropping, without deformation and distortion 😞
Copy link to clipboard
Copied
Next time hold your camera straight and give plenty of room around the subject for cropping if needed.
By @Lumigraphics
These photos are not taken by me and I have no influence on their quality, unfortunately