#!/bin/ksh
# rotate_jpeg - Adjust JPEG image orientation
# Author: Perette Barella
# Copyright 2018 Devious Fish.  All rights reserved.
VERSION='$Id: rotate_jpeg 94 2023-11-07 17:07:38Z perette $'

arg0=$(basename "$0")
if ! [[ $(getopts '[-][12:abc]' flag --abc; print -- 0$flag) == "012" ]]
then
	print -- "$arg0: Outdated Korn shell." 1>&2
	exit 1
fi

USAGE=$'
[-1?'$VERSION$']
[+NAME?rotate_jpeg - Rotate jpeg image]
[+DESCRIPTION?\b'$arg0$'\b rotates a jpeg image by modifying the EXIF
Orientation field.  The image encoding itself is unchanged, so no loss
is incurred by the reorientation.]
[+?Note that not all software respects
the orientation field for display--notably, most web browsers ignore it when
rendering an image as part of a page.]
[+?\aAction\a may be one of:]
{
    [+disable?Set rotation to natural orientation of image.]
    [+cw?Rotate clockwise.]
    [+ccw?Rotate counterclockwise.]
    [+acw?Rotate anticlockwise.]
    [+180?Rotate 180 degrees.]
    [+mirror?Mirror image/flip left to right.]
    [+flip?Flip image top-to-bottom.]
}
[n:dry-run?Perform a trial run, without changing the image file.]
[+EXIT STATUS?0 on success, non-0 on error.]
[+SEE ALSO?\bsanesize\b(1), \bexiv2\b(1), \bdisplay\b(1) \awith attention on\a \v-auto-orient\v \aoption\a]

action image-file ...

[-author?Perette Barella <perette@deviousfish.com>]
'

# Korn shell/zsh require
# Author: Perette Barella
# Copyright 2018 Devious Fish.  All rights reserved.
# $Id: require 61 2021-08-09 18:49:07Z perette $

function require {
	typeset requirement
	integer result=0
	for requirement
	do
		if ! whence -p "${requirement%:*}" >/dev/null 2>&1
		then
			print -- "$arg0${arg0+: }${requirement#*:} not found; please install." 1>&2
			result=1
		fi
	done
	(( $result != 0 )) && exit $result
	return 0
}


function orientation_to_parameters {
	typeset -l orientation="$1"
	integer rotation
	typeset mirrored
	case "${orientation}" in
	    1)		rotation=0; mirrored=no ;;
	    2)		rotation=0; mirrored=yes;;
	    3)		rotation=180; mirrored=no ;;
	    4)		rotation=180; mirrored=yes ;;
	    5)		rotation=90 ; mirrored=yes ;;
	    6)		rotation=90; mirrored=no ;;
	    8)		rotation=270; mirrored=no ;;
	    7)		rotation=270; mirrored=yes ;;
	    *)		return 1 ;;
	esac
	print $rotation $mirrored
	return 0
}

function parameters_to_orientation {
	integer angle="$1"
	typeset mirror="$2" orientation
	case "$angle$mirror" in
	    0no)	orientation=1 ;;
	    90no)	orientation=6 ;;
	    180no)	orientation=3 ;;
	    270no)	orientation=8 ;;

	    0yes)	orientation=2 ;;
	    90yes)	orientation=7 ;;
	    180yes)	orientation=4 ;;
	    270yes)	orientation=5 ;;
	    *)		return 1 ;;
	esac
	print "$orientation"
	return 0
}

function change_orientation {
	typeset orientation="$1" action="$2"
	integer angle
	typeset mirroring
	orientation_to_parameters "$orientation" |
		read angle mirroring || return 1
	case "$action" in
		disable) let "angle=0"
			mirroring="no"
			;;
		cw) 	let "angle=(angle + 90) % 360" ;;
		acw|ccw) 	
			let "angle=(angle + 270) % 360" ;;
		180)	let "angle=(angle + 180) % 360" ;;
		mirror)	if [ "$mirroring" = "yes" ]
			then
				mirroring="no"
			else
				mirroring="yes"
			fi ;;
		flip)	change_orientation "$(change_orientation "$(change_orientation "$orientation" ccw)" mirror)" cw
			return $?
			;;
		*)	print "$action: Invalid action."
			exit 1 ;;
	esac
	parameters_to_orientation $angle "$mirroring"
	return $?
}

function get_image_orientation {
	typeset file="$1" value
	value=$(exiv2 -K Exif.Image.Orientation -Pv "$file") || return 1
	orientation_to_parameters "$value" >/dev/null &&
		print -- "$value" && return 0
	print "$file: Invalid orientation: $value" 1>&2
	return 1
}

function update_image_orientation {
	typeset file="$1" orientation="$2" ori
	typeset temp=$(dirname "$file")/$arg0.$$.tmp
	exiv2 -M"set Exif.Image.Orientation $orientation" -M"set Exif.Thumbnail.Orientation $orientation" modify "$file"
	return $?
}

TRIAL=false

while getopts -a "$arg0" "$USAGE" option
do
	case "$option" in
	    n)
		TRIAL=true
		;;
	esac
done
shift $((OPTIND - 1))

if (( $# < 2 ))
then
	OPTIND=0
	getopts -a "$arg0" "$USAGE" option --short
	exit 1
fi

action="$1"
shift

require exif

status=0
for file
do
	if [ ! -f "$file" ]
	then
		print -- "$file: Not a file." 1>&2
		status=1
	elif orientation=$(get_image_orientation "$file")
	then
		if orientation=$(change_orientation "$orientation" "$action")
		then
			$TRIAL && print -- "$file: $orientation"
			$TRIAL || update_image_orientation "$file" "$orientation"
		else
			print -- "$file: Unable to calculate new orientation." 1>&2
			status=1
		fi
	else
		print -- "$file: Unable to retrieve orientation." 1>&2
		status=1
	fi
done

exit $status
