# Proportionally filling a frame with an image in ConTeXt?

I have been trying to a achieve in ConTeXt what's called "Auto-Fit" in Adobe InDesign, specifically the "Fill Frame Proportionally" kind of Auto-Fit. And from what I gather from the description in ConTeXt Garden this should be possible. Unfortunately, however, I haven't been able to make it work, which is why I would like to ask for help.

Here's a MWE (btw, this resembles an older question discussed here):

\setuppapersize[A4][A4]
\setuplayout
[
backspace=11.67mm,        width=131.25mm,
topspace=21.21mm,         height=254.57mm,
footerdistance=13pt,      footer=13pt,
rightmargindistance=13pt, rightmargin=39.5mm,
]

\setuppagenumbering[alternative=doublesided, location=]

page=right,
continue=no,
style=\ssb\bf,
]

\starttext

\chapter{Testing Proportional Fitting}

\dorecurse{3}{\input{knuth}}

\placefigure[leftpage,none]{}
width=\textwidth,
height=\textheight,
]}

\PageSection{Here's a Section on a New Page}

\dorecurse{3}{\input{knuth}}

\stoptext


And this is what it looks like:

As you can see, the image is scaled to the size of the type area, which is almost what I want. I would like for the image to scale proportionally, though (and be cropped as necessary). So, I did some research and found the parameter factor: max here, which seems to be perfectly suited ("aspect ratio is kept, the image is scaled to the bigger (oversized) variant"). However, the result of adding the parameter...

\placefigure[leftpage,none]{}
width=\textwidth,
height=\textheight,
factor=max,
]}


is this:

Indeed, the image is scaling proportionally now, but it's not filling the frame anymore. So, I thought, I must have misunderstood the meaning of the factor and also tried factor: fit. Surprisingly, the result is exactly the same (i.e. no difference).

Can anybody offer any ideas as to how to achieve this? Am I on the right track trying the factor parameter? Is this a bug? Or are there other solutions?

Thanks in advance for any help.

• It sounds to me like you want to do something I once wanted to do, but I'm not sure. You can look at this old thread. I hope it helps. – mickep May 18 '17 at 11:29
• Oh, yes. Thanks for sharing that thread. I will have to try that, but after having read through all the solutions posted, I'm pretty sure that this is more or less exactly what I was hoping for. Please post your comment as an answer, so I can accept it. By the way, actually, I assumed that factor: max was supposed to do exactly that and thought that I had found a bug. Now, judging from the last paragraph in the thread ("We should convince Hans to add this functionality (...) with factor=clip") I am wrong. Do you happen to know, is this is being worked on? – Marcus C. May 18 '17 at 11:42
• I am glad if it helps. I am on travel, and cannot write long answers at the moment, so I invite you to write an answer on your own with this solution, and to accept it. Good luck with the typesetting! – mickep May 18 '17 at 11:50

Use \clip. You might want to play with the hoffset parameter to get the right section of the image.

I also noticed that there is a spurious vertical space of .5\baselineskip above the picture. I couldn't find out where it comes from but I asked on the mailing list. EDIT: Herbert got the answer, add high to the location.

\starttext

\input knuth

\startplacefigure
[location={leftpage,high,none}]

\useexternalfigure
[placeholder]

\clip
[width=\textwidth,height=\textheight]
{%
\externalfigure
[placeholder]
[height=\textheight]
}

\stopplacefigure

\stoptext


• Thanks for the suggestion. I hadn't known \clip, so that will definitely come in handy. However, I really would like to automatically fill the "frame", no matter which proportions the original image has (because the book will be published in different formats and I don't want to adjust each of the versions manually). So, I never know beforehand, if the image is in portrait or in landscape format. – Marcus C. May 18 '17 at 6:59

This is not an answer but a comment. The options to \externalfigure never clip the image, so you need to use \clip as in Henri Menke's answer.

I have always struggled to understand how the keywords given to factor work. Based on the code (grph-trf.mkiv), this is a high level explanation of what is going on (in pseudocode).

If factor is set to max, fit, broad or auto then the following calculations are done (I am ignoring broad because it is somewhat complicated).

1. First, x_size and y_size are calculated. That depends on the scale parameter, but we can approximate it as being equal to the natural width and natural height of the box. In case of images, if neither height or width are specified, x_size and y_size equal to the natural size of the image; if one is specified, say width, then x_size equals width and y_size is scaled proportionally; if both are specified, then x_size equals width and y_size equals height.

2. Then, outer_v_size, h_size and v_size are calculated:

if maxheight is not set
outer_v_size = textheight
if inner or insidefloat or inpagebody
outer_v_size = \vsize
scrachdimen = \vsize
else
if \pagegoal < \maxdimen
if \pagetotal < \pagegoal
scratchdimen = \pagegoal - \pagetotal
else
scratchdimen = outer_v_size % \textheight
end
else
scratchdimen = outer_v_size % \textheight
end
else % maxheight is set
scratchdimen = maxheight
outer_v_size = scratchdimen
end
end

if height is empty
v_size = scratchdimen
else
v_size = height
end

if width is empty
h_size = \hsize
else
h_size = width
end

3. Then used_x_size and used_y_size are calculated.

function calculate_norm(used, factor, maxdim, size, _size)
switch(factor)
case max: used = size
case fit: used = _size
case auto: used = maxdim
end
end

if x_size > y_size
caclulate_norm(used_x_size, factor, maxwidth, hsize, h_size)
scale = used_x_size / x_size
used_y_size = scale * y_size
else
calculate_norm(used_y_size, factor, maxheight, outer_v_size, v_size)
scale = used_y_size / y_size
used_x_size = scale * x_size
end

4. Finally, the box or image is scaled to used_x_size and used_y_size.

Now, the key point to note is that the scaling depends on whether x_size > y_size. So, you can get the desired behaviour by setting the height and width of the image.

Here is an example to play with:

   \setupexternalfigures[location={local,default}]
\useexternalfigure
[placeholder]

\defineexternalfigure[tall][width=2\textheight, height=\textheight]
\defineexternalfigure[wide][width=\textwidth,  height=2\textwidth]

\setuppapersize[S4][S4]

\showframe
\startbuffer
\title{None - max}
\page
\externalfigure[placeholder][factor=max]
\page
\externalfigure[mill][factor=max]

\title{Tall - max}
\page
\externalfigure[placeholder][tall][factor=max]
\page
\externalfigure[mill][tall][factor=max]

\title{Wide - max}
\page
\externalfigure[placeholder][wide][factor=max]
\page
\externalfigure[mill][wide][factor=max]

\title{None - fit}
\page
\externalfigure[placeholder][factor=fit]
\page
\externalfigure[mill][factor=fit]

\title{Tall - fit}
\page
\externalfigure[placeholder][tall][factor=fit]
\page
\externalfigure[mill][tall][factor=fit]

\title{Wide - fit}
\page
\externalfigure[placeholder][wide][factor=fit]
\page
\externalfigure[mill][wide][factor=fit]
\page
\stopbuffer
\starttext

\getbuffer

\setuppapersize[A4][A4]
\getbuffer
\stoptext


Note that the image is never clipped. So, if you want to clip an image, then you will need to use \clip as in Henri Menke's answer.

• Thank you for the elaborate example. True, I struggled to understand what format is doing, as well, so thanks for the clarification. I will try the code as soon as possible (out of office right now), however the problem remains that I will still have to use two different definitions (i.e. the image will not automatically fill the "frame"). Actually, I thought that that's what the format parameter was supposed to achieve? – Marcus C. May 18 '17 at 7:04

Following mickep's suggestion (thank you very much!) I am going to post an answer to this question myself - just in case, anybody else is having the same problem.

So, originally, I thought that some setting for the factor parameter should achieve in ConTeXt what's called "Fill Frame Proportionally" in Adobe InDesign (i.e. scale the image proportionally until it completely fills the intended space, then clip it), but that doesn't seem to be the case. Indeed, there's even a suggestion for Hans to "add this functionality to the scale macro with factor=clip" here.

Anyway, to solve my problem at hand, I copied the code suggested on this page and added it as a module (file t-scaleandclip.tex) in the right directory:

\unprotect

\newdimen\d_scaleandclip_actual_wd
\newdimen\d_scaleandclip_actual_ht

\newdimen\d_scaleandclip_requested_wd
\newdimen\d_scaleandclip_requested_ht

\newbox\scaleandclip_box

\installnamespace{scaleandclip}
\installcommandhandler \????scaleandclip {scaleandclip} \????scaleandclip

\setupscaleandclip
[width=\textwidth,
height=\textheight]

\unexpanded\def\scaleandclip{\dodoubleempty\doscaleandclip}

\def\doscaleandclip[#1][#2]%
{\bgroup
\ifsecondargument
\edef\currentscaleandclip{#1}%
\setupcurrentscaleandclip[#2]%
\else\iffirstargument
\doifassignmentelse{#1}
{\let\currentscaleandclip\empty
\setupcurrentscaleandclip[#1]}
{\edef\currentscaleandclip{#1}}
\else
\let\currentscaleandclip\empty
\fi\fi
\dowithnextboxcs\scaleandclip_finish\hbox}

\def\scaleandclip_finish
{%
\d_scaleandclip_requested_wd \dimexpr\scaleandclipparameter\c!width\relax
\d_scaleandclip_requested_ht \dimexpr\scaleandclipparameter\c!height\relax
%
\d_scaleandclip_actual_wd\wd\nextbox
\d_scaleandclip_actual_ht\dimexpr\ht\nextbox + \dp\nextbox\relax
%
\ifdim\dimexpr\d_scaleandclip_actual_wd*100/\d_scaleandclip_requested_wd <
\dimexpr\d_scaleandclip_actual_ht*100/\d_scaleandclip_requested_ht \relax
\setbox\scaleandclip_box\hbox
{\scale[\c!width=\d_scaleandclip_requested_wd]{\box\nextbox}}%
\scratchdimen=\the\dimexpr(\ht\scaleandclip_box - \d_scaleandclip_requested_ht)/2\relax
\clip
[
\c!voffset=\scratchdimen,
\c!height=\d_scaleandclip_requested_ht,
]{\box\scaleandclip_box}%
\else
\setbox\scaleandclip_box\hbox
{\scale[\c!height=\d_scaleandclip_requested_ht]{\box\nextbox}}%
\scratchdimen=\the\dimexpr(\wd\scaleandclip_box - \d_scaleandclip_requested_wd)/2\relax
\clip
[
\c!hoffset=\scratchdimen,
\c!width=\d_scaleandclip_requested_wd,
]{\box\scaleandclip_box}%
\fi
\egroup}

\protect

The usage is:

\scaleandclip[width=..., height=...]{ ... any box ...}

simimlar to the usage of \scale macro.

\starttext

\dontleavehmode
\scaleandclip[width=3cm, height=3cm]{\externalfigure[cow.pdf][width=3cm,frame=on]}
\scale[maxwidth=3cm, maxheight=3cm]{\externalfigure[cow.pdf][width=3cm,frame=on]}

\dontleavehmode
\scaleandclip[width=3cm, height=3cm]{\externalfigure[cow.pdf][width=3cm,height=10cm,frame=on]}
\scale[maxwidth=3cm, maxheight=3cm]{\externalfigure[cow.pdf][width=3cm,height=10cm,frame=on]}

\dontleavehmode
\scaleandclip[width=3cm, height=3cm]{\externalfigure[cow.pdf][width=10cm,height=3cm,frame=on]}
\scale[maxwidth=3cm, maxheight=3cm]{\externalfigure[cow.pdf][width=10cm,height=3cm,frame=on]}

\stoptext


Then, after loading the module with \usemodule[scaleandclip] I can use it like this:

\placefigure[leftpage,high,none]{}{
\scaleandclip[width=\textwidth, height=\textheight]

Indeed, I find that when combined with the \offset macro this even gives me an easier solution than the \bleed macro to achieve full page (bleeding) figures as discussed here.