289 lines
9.5 KiB
Lua
289 lines
9.5 KiB
Lua
--[[
|
|
|
|
auto_straighten.lua - straighten an image in darkroom using image metadata
|
|
|
|
Copyright (C) 2025 Bill Ferguson <wpferguson@gmail.com>.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
]]
|
|
--[[
|
|
auto_straighten - straighten an image in darkroom using image metadata
|
|
|
|
auto_straighten reads the roll and pitch angle imformation captured when
|
|
an image is captured. A correction is computed and applied to correct the
|
|
roll to the nearest vertical or horizontal axis using the rotate and
|
|
perspective module.
|
|
|
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
|
exiftool - https://exiftool.org
|
|
|
|
USAGE
|
|
* add it sowewhere in your scripts directory
|
|
* ensure exiftool is installed and accessible
|
|
* enable it using script manager
|
|
|
|
BUGS, COMMENTS, SUGGESTIONS
|
|
Bill Ferguson <wpferguson@gmail.com>
|
|
|
|
CHANGES
|
|
|
|
LIMITATIONS
|
|
* Roll and pitch angles are taken when an image is captured. If you shoot
|
|
in burst mode, the roll and pitch angles of the first image are used in
|
|
the rest of the burst (at least for Canon).
|
|
* If you are running multiple scripts that trigger on an image being opened
|
|
in darkroom then the auto crop may not work. There is a shortcut included
|
|
that can have a key sequence assigned so that the auto crop will be reapplied.
|
|
* Currently oply roll angle is corrected
|
|
|
|
LIEENSE
|
|
GPL V2
|
|
]]
|
|
|
|
local dt = require "darktable"
|
|
local du = require "lib/dtutils"
|
|
-- local da = require "lib/dtutils.action"
|
|
local df = require "lib/dtutils.file"
|
|
-- local ds = require "lib/dtutils.string"
|
|
-- local dtsys = require "lib/dtutils.system"
|
|
local log = require "lib/dtutils.log"
|
|
-- local debug = require "darktable.debug"
|
|
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- C O N S T A N T S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local MODULE <const> = "auto_straighten"
|
|
local DEFAULT_LOG_LEVEL <const> = log.info
|
|
local TMP_DIR <const> = dt.configuration.tmp_dir
|
|
|
|
-- path separator
|
|
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
|
|
|
|
-- command separator
|
|
local CS <const> = dt.configuration.running_os == "windows" and "&" or ";"
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- A P I C H E C K
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need
|
|
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- I 1 8 N
|
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local gettext = dt.gettext.gettext
|
|
|
|
local function _(msgid)
|
|
return gettext(msgid)
|
|
end
|
|
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local script_data = {}
|
|
|
|
script_data.destroy = nil -- function to destory the script
|
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
|
|
|
script_data.metadata = {
|
|
name = _("auto_straighten"), -- visible name of script
|
|
purpose = _("straighten an image in darkroom using image metadata"), -- purpose of script
|
|
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
|
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/auto_straighten/" -- URL to help/documentation
|
|
}
|
|
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- L O G L E V E L
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
log.log_level(DEFAULT_LOG_LEVEL)
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- N A M E S P A C E
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local auto_straighten = {}
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- G L O B A L V A R I A B L E S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
auto_straighten.pixelpipe_busy = false
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- P R E F E R E N C E S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
dt.preferences.register(MODULE, "crop_area", "enum",
|
|
_("automatic_cropping"),
|
|
_("what automatic cropping to perform"),
|
|
_("original format"),
|
|
_("off"), _("largest area"), _("original format"))
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- A L I A S E S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local namespace = auto_straighten
|
|
local as = auto_straighten
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- F U N C T I O N S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local function determine_correction(roll_angle)
|
|
|
|
--[[
|
|
Canon camera roll angle is as foillows
|
|
* landscape orientation camera upright = 0
|
|
* camera rolled to the left (shutter button up) portrait = -90
|
|
* camera inverted landscape = +/- 180
|
|
* camera rolled to the right (shutter button down) portrait = 90
|
|
|
|
rotate and perspective corrections
|
|
* rotate the image to the right is a negative correction
|
|
* rotate the image left is a positive correction
|
|
]]
|
|
|
|
if roll_angle == 0 or -- landscape
|
|
roll_angle == -90 or -- roll left portrait
|
|
math.abs(roll_angle) == 180 or -- inverted landscape
|
|
roll_angle == 90 then -- roll right portrait
|
|
dt.print_log("on axis, no correction required")
|
|
return 0
|
|
elseif roll_angle > 0 and roll_angle <= 45 then
|
|
return (roll_angle * -1.0)
|
|
elseif roll_angle > 45 and roll_angle < 90 then
|
|
return (90 - roll_angle)
|
|
elseif roll_angle > 90 and roll_angle <= 135 then
|
|
return (roll_angle - 90) * -1.0
|
|
elseif roll_angle > 135 and roll_angle < 180 then
|
|
return (180 - roll_angle)
|
|
elseif roll_angle > -180 and roll_angle <= -135 then
|
|
return (-180 - roll_angle)
|
|
elseif roll_angle > -135 and roll_angle <= -90 then
|
|
return (math.abs(roll_angle) - 90)
|
|
elseif roll_angle > -90 and roll_angle <= -45 then
|
|
return (-90 - roll_angle)
|
|
else
|
|
return roll_angle * -1.0
|
|
end
|
|
end
|
|
|
|
local function toggle_auto_crop()
|
|
local crop = dt.preferences.read(MODULE, "crop_area", "enum")
|
|
dt.gui.action("iop/ashift/automatic cropping", "selection", "item:off", 1.000)
|
|
dt.control.sleep(100)
|
|
dt.gui.action("iop/ashift/automatic cropping", "selection", "item:" .. crop, 1.000)
|
|
end
|
|
|
|
local function straighten_image(image)
|
|
|
|
local roll = nil
|
|
local pitch = nil
|
|
|
|
-- Roll Angle 0 is landscape, -90 is rolled to the left in portrait, +- 180 is landscape inverted, 90 is portrait rolled to the right
|
|
|
|
local crop = dt.preferences.read(MODULE, "crop_area", "enum")
|
|
|
|
local pipe = io.popen(as.exiftool .. " -RollAngle -PitchAngle " .. image.path .. PS .. image.filename)
|
|
if pipe then
|
|
for line in pipe:lines() do
|
|
local pr, val = string.match(line, "(.).+: (.+)")
|
|
if pr == "R" then
|
|
local sign = 1.0
|
|
dt.print_log("roll angle is " .. val)
|
|
log.msg(log.debug, "roll angle is " .. val)
|
|
-- check for Nidon and change the sign
|
|
if string.match(string.upper(image.exif_maker), "NIKON") then
|
|
sign = -1.0
|
|
end
|
|
roll = determine_correction(tonumber(val) * sign)
|
|
dt.print_log("corrected roll angle is " .. roll)
|
|
log.msg(log.debug, "corrected roll angle is " .. roll)
|
|
elseif pr == "P" then
|
|
pitch = tonumber(val)
|
|
end
|
|
end
|
|
pipe:close()
|
|
end
|
|
|
|
if roll and dt.gui.action("iop/ashift", "enable", "", "") == 0.0 then
|
|
dt.print_log("straightening image")
|
|
dt.gui.action("iop/ashift/rotation", "value", "set", roll)
|
|
-- dt.gui.action("iop/ashift/automatic cropping", "selection", "item:" .. crop, 1.000)
|
|
-- da.wait_until_pixelpipe_complete()
|
|
toggle_auto_crop()
|
|
end
|
|
|
|
end
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- M A I N P R O G R A M
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
as.exiftool = df.check_if_bin_exists("exiftool")
|
|
|
|
if not as.exiftool then
|
|
log.msg(log.error, "exiftool not found, exiting...")
|
|
log.msg(log.screen, _("exiftool not found, exiting..."))
|
|
return
|
|
end
|
|
|
|
-- da.register_events(MODULE, straighten_image)
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- U S E R I N T E R F A C E
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- D A R K T A B L E I N T E G R A T I O N
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local function destroy()
|
|
dt.destroy_event(MODULE, "darkroom-image-loaded")
|
|
end
|
|
|
|
script_data.destroy = destroy
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- E V E N T S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
---[[
|
|
dt.register_event(MODULE, "darkroom-image-loaded",
|
|
function(event, clean, image)
|
|
if clean then
|
|
straighten_image(image)
|
|
end
|
|
end
|
|
)
|
|
--]]
|
|
|
|
dt.register_event(MODULE, "shortcut",
|
|
function(event, shortcut)
|
|
toggle_auto_crop()
|
|
end, "toggle auto crop off and on"
|
|
)
|
|
|
|
return script_data
|