Size image by area
Scaling to an area is an unusual request: normally one knows the target height or width. The following takes the image and scales such that the area is that given in the optional argument, interpreted in square centimetres:
\documentclass{article}
\usepackage{graphicx}
\usepackage{xfp}
\makeatletter
\define@key{Garea}{area}{\def\Garea@area{#1}}
\define@key{Garea}{areaunit}{\def\Garea@unit{#1}}
\define@key{Gin}{area}{} % So we can pass through easily
\define@key{Gin}{areaunit}{}
\newcommand*{\Garea@area}{}
\newcommand*{\Garea@unit}{cm}
\newcommand{\includegraphicsbyarea}[2][]{%
\setkeys{Garea}{#1}%
\ifx\Garea@area\@empty
\gdef\Garea@scale{scale = 1}%
\else
\begingroup
\setbox0=\hbox{\includegraphics[#1]{#2}}%
\xdef\Garea@scale{scale =
\fpeval{sqrt((\Garea@area * 1 \Garea@unit * 1 \Garea@unit)
/(\the\ht0 * \the\wd0))}}%
\endgroup
\fi
\expandafter\includegraphics\expandafter[\Garea@scale,#1]{#2}%
}
\makeatother
\begin{document}
\includegraphicsbyarea[area=100]{example-image-a}
\includegraphicsbyarea[area=100,areaunit=pt]{example-image-a}
\end{document}
It still applies any options to the graphic: I've ordered such that the area scale is first, but one might want to put it last.
An alternative interface uses the square root of the area of the image: conveniently this has units we can work with directly
\documentclass{article}
\usepackage{graphicx}
\usepackage{xfp}
\makeatletter
\define@key{Garea}{sqrtarea}{\def\Garea@sqrtarea{#1}}
\define@key{Gin}{sqrtarea}{}
\newcommand*{\Garea@sqrtarea}{}
\newcommand{\includegraphicsbyarea}[2][]{%
\setkeys{Garea}{#1}%
\ifx\Garea@diag\@empty
\gdef\Garea@scale{scale = 1}%
\else
\begingroup
\setbox0=\hbox{\includegraphics[#1]{#2}}%
\xdef\Garea@scale{scale =
\fpeval{\Garea@sqrtarea /(sqrt(\the\ht0 * \the\wd0))}}%
\endgroup
\fi
\expandafter\includegraphics\expandafter[\Garea@scale,#1]{#2}%
}
\makeatother
\begin{document}
\includegraphicsbyarea[sqrtarea = 5cm]{example-image-a}
\includegraphicsbyarea[sqrtarea = 10cm]{example-image-a}
\end{document}
(See the answer by mmj for an alternative name for the key.)
Update 3
This is the last revision, I promise.
The only change is the use of pgfmath.sty
in place of fp-eval.sty
which, in turn, required a little special handling to avoid dimension too large
errors. As well, the use of pgfmath.sty
allowed for considerable simplification. The new output is identical to the old.
%===8><-----%
I was unhappy with the way that I left the \scaletoarea macro. It has been altered to be, hopefully, a little more user-friendly. I have made it possible to use the options to \includegraphics
for all of the macros. Take a look at the examples.
%===8><---%
I take it that you want all of the graphics to have the same area regardless of the aspect ratio. The following code does that. There are two approaches outlined here.
The first scales subsequent graphics based on the size of the first graphic Use \fpic
for the first graphic; using \spic
for the subsequent graphics will size them to have the same area as the first graphic. Use the options to \fpic
(the same as those to \includegraphics
) to size the first graphic accordingly.
Second, if you know the target area, you can use \scaletoarea{<unitless area>}{<linear unit of area>}{<name of graphic>}
. For example, if you were to scale a graphic, foo
, so that it is 5 square inches in area, you would write: \scaletoarea{5}{in}{foo}
. Any of the units recognized by TeX can be used.
\documentclass{article}
\usepackage{graphicx,calc,pgfmath}
\usepackage[margin=0.5in]{geometry}
\newlength\fpicw
\newlength\fpich
\newlength\spicw
\newlength\spich
\newsavebox{\firstpic}
\newsavebox{\nextpics}
\newlength{\targetarea}
%% \fpic[<options to \includegraphics>]{<name of graphic>}
\newcommand{\fpic}[2][]{% First picture, use \includegraphics, and options
\sbox{\firstpic}{\includegraphics[#1]{#2}}%
\settoheight{\fpich}{\usebox{\firstpic}}%
\settowidth{\fpicw}{\usebox{\firstpic}}%
\usebox{\firstpic}%
}
%% \spic[<options to \includegraphics>]{<graphic name>}
\newcommand{\spic}[2][]{% Second and succeeding pictures
\sbox{\nextpics}{\includegraphics[#1]{#2}}%
\settoheight{\spich}{\usebox{\nextpics}}%
\settowidth{\spicw}{\usebox{\nextpics}}%
\pgfmathsetmacro{\scaling}{sqrt((\fpicw/\spicw)*(\fpich/\spich))}%
\scalebox{\scaling}{\usebox{\nextpics}}%
\typeout{**The scaling (#2) = \scaling}% Comment-out if not needed
}
%% \scaletoarea[<options to \includegraphics>]{<unitless area>}{<unit of area (linear)>}{<graphic name>}
\newcommand{\scaletoarea}[4][]{%
\pgfmathsetmacro{\mytmp}{sqrt(#2)}%
\setlength{\targetarea}{\mytmp #3}%
\sbox{\nextpics}{\includegraphics[#1]{#4}}%
\settoheight{\spich}{\usebox{\nextpics}}%
\settowidth{\spicw}{\usebox{\nextpics}}%
\pgfmathsetmacro{\scaling}{sqrt((\targetarea/\spicw)*(\targetarea/\spich))}%
\scalebox{\scaling}{\usebox{\nextpics}}%
\typeout{++The scaling (#4) = \scaling}% Comment-out if not needed
}
%% \scaletoareab[<options to \includegraphics>]{<square root of desired area>}{<name of graphic>}
\newcommand{\scaletoareab}[3][]{%
\setlength{\targetarea}{#2}%
\sbox{\nextpics}{\includegraphics[#1]{#3}}%
\settoheight{\spich}{\usebox{\nextpics}}%
\settowidth{\spicw}{\usebox{\nextpics}}%
\pgfmathsetmacro{\scaling}{sqrt((\targetarea/\spicw)*(\targetarea/\spich))}%
\scalebox{\scaling}{\usebox{\nextpics}}%
\message{++The scaling (#3) = \number\scaling}%
}
\begin{document}
\fpic[width=1.25in]{Peppers}\spic{Pasta}\spic{OldImage}\spic{BethanyDrawing}
\scaletoarea{3}{in}{Peppers}\scaletoarea{12}{cm}{Pasta}\scaletoarea{2}{in}{OldImage}\scaletoarea{61}{pc}{BethanyDrawing}
\scaletoarea[width=0.5in,height=3in,keepaspectratio=false]{3}{in}{Peppers}
\scaletoareab[width=0.5in,height=3in,keepaspectratio=false]{1.732050807568877in}{Peppers}
\end{document}
Here is an approach not redefining \includegraphics
. However compared to scale
option, I cut a branch because I don't have details of graphicx.sty
in head, so possibly there would be a way to delegate to driver the final rescaling, which I am losing here. Ping @DavidCarlisle.
(I use xintexpr
but xfp
of course would do it as well as in Joseph's answer; also, up to some more cumbersome notations one could use only the macros of xintfrac
, giving a tiny speed-up as expression parsing is skipped).
\documentclass{article}
\usepackage{graphicx}
\usepackage{xintexpr}
\makeatletter
\define@key{Gin}{sqrtofarea}{%
\def\Gin@req@sizes{%
\edef\Gin@scalex{\xinttheiexpr[5]% round fixed point to 5
% fractional digits
\dimexpr#1\relax/
sqrt(\Gin@nat@height*\Gin@nat@width)
\relax}%
\let\Gin@scaley\Gin@exclamation
\Gin@req@height\Gin@scalex\Gin@nat@height
\Gin@req@width\Gin@scalex\Gin@nat@width
}%
\@tempswatrue}
\makeatother
\begin{document}
\includegraphics[sqrtofarea=2cm]{example-image-a}
\includegraphics[sqrtofarea=3cm]{example-image-a}
\includegraphics[sqrtofarea=4cm]{example-image-a}
\includegraphics[sqrtofarea=5cm]{example-image-a}
\newbox\mybox
Equality only expected up to 5 digits of precision due to intrinsic
limitations of graphicx computations.
\setbox\mybox\hbox{\includegraphics[sqrtofarea=2cm]{example-image-a}}%
\xinttheiiexpr\ht\mybox*\wd\mybox\relax
?=
\xinttheiiexpr\dimexpr2cm\relax*\dimexpr2cm\relax\relax\ (4cm$^2$)
\setbox\mybox\hbox{\includegraphics[sqrtofarea=5cm]{example-image-a}}%
\xinttheiiexpr\ht\mybox*\wd\mybox\relax
?=
\xinttheiiexpr\dimexpr5cm\relax*\dimexpr5cm\relax\relax\ (25cm$^2$)
\end{document}
The sentence at bottom of image must be amended: in TeX all dimensions are integer multiples of 1sp. When we set the area square root as key, we automatically limit the achievable precision of the area. For example 5cm
internally in TeX gives 9323399sp, hence a square equal to 86925768913201 as in image above. The previous square is 86925750266404 and the next one is 86925787560000, so they diverge in the 7th digit already and we can never overcome that possible imprecision when comparing a square with a produce height times width. Above we observe discrepancy already in 5th digit so the sentence is probably not completely wrong, but I felt I needed to add this mathematical precision.
Here is same with xfp
(basically copied from Joseph's way of using it):
\documentclass{article}
\usepackage{graphicx}
\usepackage{xfp}
\makeatletter
\define@key{Gin}{sqrtofarea}{%
\def\Gin@req@sizes{%
\edef\Gin@scalex{\fpeval{#1/sqrt(\Gin@nat@height*\Gin@nat@width)}}%
\let\Gin@scaley\Gin@exclamation
\Gin@req@height\Gin@scalex\Gin@nat@height
\Gin@req@width\Gin@scalex\Gin@nat@width
}%
\@tempswatrue}
\makeatother
\begin{document}
\includegraphics[sqrtofarea=2cm]{example-image-a}
\includegraphics[sqrtofarea=3cm]{example-image-a}
\includegraphics[sqrtofarea=4cm]{example-image-a}
\includegraphics[sqrtofarea=5cm]{example-image-a}
\end{document}
About this:
no wrapping of
#1
in\dimexpr #1\relax
needed here;xintexpr
could easily be extended to recognizecm
,in
,pt
, etc ... units so that e.g.2cm
is understood automatically, but the problem is that it would then do an exact conversion to a fractional number ofsp
units, whereas TeX process is more complex than simply using a proportionality factor and proceeds with rounding and truncating in various directions at various stages; so using exact conversion factor means not doing same operations as TeX itself. For this reason, no such units are defined yet inxintexpr
and user must go via\dimexpr #1\relax
; thexintexpr
parser will apply\number
to this, triggering TeX's own way to convert dimensional units.I am not expert in
xfp
so I don't know if sometimes such computation could result in a scientific notation which would break TeX later; in thexintexpr
solution I applied a transformation to fixed point value with 5 fractional digits. I don't know how one does that inxfp
and whether it could be needed here in some cases. In the MWE above it works fine.