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} 

Scaling to area examples


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}

enter image description here


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 recognize cm, 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 of sp 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 in xintexpr and user must go via \dimexpr #1\relax; the xintexpr 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 the xintexpr solution I applied a transformation to fixed point value with 5 fractional digits. I don't know how one does that in xfp and whether it could be needed here in some cases. In the MWE above it works fine.

Tags:

Graphics