snibgo's ImageMagick pages

Crop to detail

A method for automatic cropping.

This page arose from a discussion on the IM forum: Smart crop function in IM?.

Cropping is a fundamental process for editing images, especially photographs. It is simple and quick, and needs a deep understanding of image content, aesthetic principles, visual psychology and gestalt theory to be most effective. The best tool is human intelligence.

On the other hand, we can automagickally crop photographs based entirely on the level of detail, with a fairly complex but fast process (4 seconds for a 35 million pixel image, with default parameters). There is no content analysis, no face detection. It might be called "semi-smart" or "slightly intelligent".

The process can be used on simple images containing a sharp subject on a blurred or out-of-focus background. That problem is quite straightforward. But it also works, and is surprisingly effective, on more complex images.

The scripts calls other published scripts. They do not use process modules.

CAUTION: This page, commands and scripts are still in development during July 2017, not even at alpha testing, are probably wrong, and are highly likely to change.

As always, use at your own peril.

References

The following are relevant to this page, though my method doesn't directly follow any of them.

Many papers explore computational aesthetics, for example:

The basic script

The script cropToDetailSml.bat needs an input file and an output file, and possibly a required width and height. It writes a cropped version of the input where the crop contains the maximal detail.

Parameter Description
%1 Input image file.
This is madatory. All other parameters are optional.
%2 Output image file.
Use "null:" if no output is required.
%3 Required dimensions in pixels, eg "250x150", "0x150", "250x0", "0x0", where "0" means "don't care".
Each dimension can be suffixed % or c or p.
Default: "0x0" don't care about either dimension.
%4 Required threshold, a number typically 50 to 99.99.
If supplied, the required width and height are ignored.
Default: don't care.

The dimensions should be in the form WxH, the required width and height, with an x between them. Either of the numbers may be suffixed with % or c or p. A suffix applies only to the dimension for which it is given. For example, "20x30%" means 20 pixels wide, and 30% of the input image height. For either dimension, "0" means "don't care". A blank or "." for the parameter is the same as "0x0".

The script caps both required dimensions to the input image dimensions, so giving a large value such as "999999" is equivalent to giving the input image width or height.

The script will calculate a reasonable value for any missing dimension, and crop to the rectangle of those dimensions that contains the most deail.

When neither dimension is given, the script will then crop off very roughly X% of the pixels, where X is a percentage threshold. (The parameter should be just a number, with no "%" percent sign.) This defaults to 85% or 90%, which is roughly the same as cropping to one-third horizontally and one-third vertically. If you want a tighter crop, use a higher number. For a looser crop, use a lower number.

Thresholding methods create an image where lightness represents detail, and threshold it, then take the bounding rectangle of either the largest white component, or all the white components. This rectangle is used either to crop the input image, or as the dimensions for a recursive search.

The script also uses values from some variables, as "expert settings".

Variable Description
ctdBLR_SIG sigma for blur of slope magnitude [default 0 = no blur if trimming to only largest component, otherwise 20 * diagonal_factor].
ctdUSE_CONN_COMP whether to use only largest connected component [default 1].
ctdDO_RECURSE whether to recursively call [default 1].
ctdDBG_IMG if not blank, writes a debug image to this file [default no image].

Sample inputs

We will show the script operating on these images:

insect.jpg

insect.jpg

white_cat1.jpg

white_cat1.jpg

car_lady_dog1.jpg

car_lady_dog1.jpg

sofa_cat_small.jpg

sofa_cat_small.jpg

toes.png

toes.pngjpg

The toes.png image is my copyright; the others are not.

Of the first four images, the average diagonal is 664 pixels.

Examples

First, we supply no parameters, so the script chooses both width and height, and the crop offsets:

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_in0.jpg
c2d_in0.jpg
call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_wc0.jpg
c2d_wc0.jpg
call %PICTBAT%cropToDetailSml ^
  car_lady_dog1.jpg c2d_cld0.jpg
c2d_cld0.jpg
call %PICTBAT%cropToDetailSml ^
  sofa_cat_small.jpg c2d_scs0.jpg
c2d_scs0.jpg
call %PICTBAT%cropToDetailSml ^
  toes.png c2d_t0.jpg
c2d_t0.jpg

Specify width only, so the script chooses the height, and the crop offsets:

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_inw.jpg 200x0
c2d_inw.jpg
call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_wcw.jpg 200x0
c2d_wcw.jpg
call %PICTBAT%cropToDetailSml ^
  car_lady_dog1.jpg c2d_cldw.jpg 200x0
c2d_cldw.jpg
call %PICTBAT%cropToDetailSml ^
  sofa_cat_small.jpg c2d_scsw.jpg 200x0
c2d_scsw.jpg
call %PICTBAT%cropToDetailSml ^
  toes.png c2d_tw.jpg 200
c2d_tw.jpg

Specify height only, so the script chooses the width, and the crop offsets:

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_inh.jpg 0x200
c2d_inh.jpg
call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_wch.jpg 0x200
c2d_wch.jpg
call %PICTBAT%cropToDetailSml ^
  car_lady_dog1.jpg c2d_cldh.jpg 0x200
c2d_cldh.jpg
call %PICTBAT%cropToDetailSml ^
  sofa_cat_small.jpg c2d_scsh.jpg 0x200
c2d_scsh.jpg
call %PICTBAT%cropToDetailSml ^
  toes.png c2d_th.jpg 0x200
c2d_th.jpg

Specify both width and height, so the script chooses just the crop offsets:

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_inwh.jpg 200x200
c2d_inwh.jpg
call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_wcwh.jpg 200x200
c2d_wcwh.jpg
call %PICTBAT%cropToDetailSml ^
  car_lady_dog1.jpg c2d_cldwh.jpg 200x200
c2d_cldwh.jpg
call %PICTBAT%cropToDetailSml ^
  sofa_cat_small.jpg c2d_scswh.jpg 200x200
c2d_scswh.jpg
call %PICTBAT%cropToDetailSml ^
  toes.png c2d_twh.jpg 200x200
c2d_twh.jpg

We can explicitly set the output dimensions to a percentage of the input dimensions. (The script will accept % or c for percent, or p for proportion.)

call %PICTBAT%cropToDetailSml ^
  toes.png c2d_tpc.jpg 40cx40c
c2d_tpc.jpg

Instead of specifying dimensions, we can specify a threshold, so the script chooses width, height and offsets (like the first set of examples, but removing only about 75% of the image):

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_int.jpg . 75
c2d_int.jpg
call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_wct.jpg . 75
c2d_wct.jpg
call %PICTBAT%cropToDetailSml ^
  car_lady_dog1.jpg c2d_cldt.jpg . 75
c2d_cldt.jpg
call %PICTBAT%cropToDetailSml ^
  sofa_cat_small.jpg c2d_scst.jpg . 75
c2d_scst.jpg
call %PICTBAT%cropToDetailSml ^
  toes.png c2d_tt.jpg . 75
c2d_tt.jpg

We can see the effect of varying the threshold by appending examples with the script c2dVaryThreshold.bat. For each input image, we show eight appended images with ctdUSE_CONN_COMP=0, then eight appended images with ctdUSE_CONN_COMP=1. The values of the eight thresholds are: 99, 97, 95, 90, 85, 80, 75 and 70.

In each group of eight, we often find that each image is a subimage of the next size large, but this is not always true.

call %PICTBAT%c2dVaryThreshold ^
  insect.jpg c2d_invt_0.png c2d_invt_1.png
c2d_invt_0.pngjpg c2d_invt_1.pngjpg
call %PICTBAT%c2dVaryThreshold ^
  white_cat1.jpg c2d_wcvt_0.png c2d_wcvt_1.png
c2d_wcvt_0.pngjpg c2d_wcvt_1.pngjpg
call %PICTBAT%c2dVaryThreshold ^
  car_lady_dog1.jpg c2d_cldvt_0.png c2d_cldvt_1.png
c2d_cldvt_0.pngjpg c2d_cldvt_1.pngjpg
call %PICTBAT%c2dVaryThreshold ^
  sofa_cat_small.jpg c2d_scvt_0.png c2d_scvt_1.png
c2d_scvt_0.pngjpg c2d_scvt_1.pngjpg
call %PICTBAT%c2dVaryThreshold ^
  toes.png c2d_tvt_0.png c2d_tvt_1.png
c2d_tvt_0.pngjpg c2d_tvt_1.pngjpg

How does it work?

One of three methods is used, depending on whether the user has specified (1) both required dimensions or (2) only one required dimension or (3) a forced threshold but no dimensions.

In all cases, the script first creates a grayscale image that is black where there is no detail, and lighter (up to pure white) where there is most detail. In the literature, this is called a saliency map.

In method (1) where the script knows both required dimensions, the script creates a white image of those dimensions, and searches for this (RMSE metric) within an increased contrast version of the saliency map. (FIXME: This contrast adjustment is currently a hack. How do we determine how much we need?) The search is performed by srchImg.bat (see Searching an image), which does a multi-scale (aka "pyramid") search, so it is quite fast. Hence the result has the smallest RMSE from pure white. The calculated offset is used to crop the input image.

Method (3) crops according to a specified threshold. The saliency map is possibly blurred, then equalized and thresholded. Depending on ctdUSE_CONN_COMP, we find the bounding rectangle of just the largest connected white component (using the script connCompCropWhite.bat), or of all white pixels. The found trim parameters are used either to directly crop the input image, or to recursively call the script with just the found width and height so method (1) will be used. The variable ctdDO_RECURSE determines whether we use recursion. Using recursion is slower, but seems to give better results.

In method (2) where the user has supplied only one required dimension, the process has two phases. The first phase uses an iterative process to find the threshold that gives the closest match to the required dimension. (Iteration works, but inevitably takes time. There may be a quicker method.) Now we have found the trim parameters, we proceed as for method (3).

Methods (2) and (3) use a threshold of the blurred and equalized saliency map, either directly (because the user has supplied it, or we have a default threshold) or indirectly through iteration. This thresholded image will contain one or more white connected components. (It is equalized, so there is at least one white component provided the input image contained some details, ie it was not a flat colour. Mutiple components occur when multiple disconnected areas exceed this sharpness threshold.)

ASIDE: With large images, -connected-components breaks with the message too many components. To prevent this (I hope) we use -define connected-components:area-threshold=d*d where d is the image diagonal_factor (the diagonal / 664).

The iteration of method (2) is by trial and error (binary chop). The script finds the threshold that makes the result the required dimension, and hence finds the other dimension. A smaller value for the threshold will usually increase the trimmed dimensions or keep them the same, and increasing the threshold will usually decrease the trimmed dimensions or keep them the same. When we are trimmimg to all the connected components, a small change in threshold changes the trimmed dimensions by a small amount, so some threshold will give the required dimension, and the algorithm will find it.

When we are trimming to only the largest component, the situation is more complex. We might have two components, with the largest shaped like an "8" with a thin portion. If we then increase the threshold slightly, the thin portion can break, so the large piece becomes two smaller pieces, so the largest piece is now what was the smallest. This causes a sudden jump in the trimmed dimensions and offset, so there is no threshold that gives the target dimension, and the algorithm fails (it has "bust"). When this happens, we use dimensions and offset from whichever iteration gave us the closest to the target dimension, but force the required dimension to whatever was supplied.

ASIDE: The first phase finds the other dimension, but also the x and y offsets, which is all we need for a crop. So why bother with the recursive call? Because the method used in the first phase delivers a different offset to the "both dimensions" method, and it is generally worse, and some applications will need consistency.

The difference in offset can be great. For example, toes.png has the greatest detail around the big toe but little detail in the skin, so if the bounding rectangle is large a recursive search may prefer the more widespread detail at the base of the image.

There is an option to skip the second phase: set ctdDO_RECURSE to 0.

I might add an option: if the recursed rectangle doesn't overlap the original rectangle, we reject it.

Calculating the saliency map

For a saliency map, the script uses the slope magnitude.

There are many possible definitions of slope magnitude, and thus many methods of calculating it. See Details, details.

Currently, the script calls slopeXYblMag.bat, with a sigma around 1.0 for web-sized images. Because the output pixel depends only on input pixels in the same row and column, this doesn't scale well to large sigma (eg 13).

Processing the saliency map

To understand the process, it may be helpful to show the temporary images. We also use the ctdDBG_IMG option.

set ctdDBG_IMG=c2d_wc_dbg.jpg

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg NULL: 0x200

set ctdDBG_IMG=

The slope magnitude.

%IM%convert ^
  \temp\ctd_sm.miff ^
  c2d_sm_ex.png
c2d_sm_ex.pngjpg

The debug image.

c2d_wc_dbg.jpg

The cat's outline is high magnitude, but there is little detail within the outline. The highest magnitude is in the edge of the large flower at top-centre.

We are effectively doing a fuzzy trim on the equalized slope magnitude. However, when trimming to all white connected components, we want to ignore small areas of sharpness within a sea of blur. Hence the blur before the equalization.

The slope magnitude,
possibly blurred, and equalized.

%IM%convert ^
  \temp\ctd_sm_b.miff ^
  c2d_sm_b_ex.png
c2d_sm_b_ex.pngjpg

Equalization has two purposes. It makes "-threshold" operate on a percentage of pixels. For example, after equalization, "-threshold 90%" will makes 90% of pixels black and the remainder white. Secondly, it normalises the controls across a variety of inputs, so a certain percentage will have a similar effect on different images.

The previous, thresholded.

%IM%convert ^
  \temp\ctd_sm_c.miff ^
  c2d_sm_c_ex.png
c2d_sm_c_ex.pngjpg

We then use "-connected-components", to find the largest white component, or "-format %@" to find the trim to all white components.

If everything else was equal, then trimming to just one component would always give a smaller result than trimming to all components. But this choice also affects the default blur: trimming to one component defaults to no blur; trimming to all has a blur with sigma=(20 * diag_factor).

Don't limit the components; default blur.

set ctdUSE_CONN_COMP=0
set ctdBLR_SIG=

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc0.png
c2d_sm_ex_cc0.pngjpg

Do limit the components; default blur.

set ctdUSE_CONN_COMP=1
set ctdBLR_SIG=

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc1.png
c2d_sm_ex_cc1.pngjpg

The bounding rectangle that contains all the values above 90% will also contain some values at 85%, 80%, whatever. This effect might be counter-balanced when we eliminate smaller areas, but it shows why a threshold of 90% crops to approximately 10% of the pixels. Some images will have detail extending to all four image edges, so even a high threshold like 99.9% won't crop away any pixels.

The amount of blur has a major impact on the result. A smaller blur results in more fragmentation, which gives a larger result when we include all components, or a smaller result when we include just the largest component. A larger blur removes irregular edges in components, so the largest component becomes more circular, so its bounding rectangle becomes more square. Conversely, a smaller blur can create less-square results.

sigma=5 sigma=25

Don't limit the components.

set ctdUSE_CONN_COMP=0
set ctdBLR_SIG=5

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc0s.png

set ctdBLR_SIG=25

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc0sb.png
c2d_sm_ex_cc0s.pngjpg c2d_sm_ex_cc0sb.pngjpg

Do limit the components.

set ctdUSE_CONN_COMP=1
set ctdBLR_SIG=5

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc1s.png

set ctdBLR_SIG=25

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc1sb.png
c2d_sm_ex_cc1s.pngjpg c2d_sm_ex_cc1sb.pngjpg

We might have a very small blur, limit to one component, which would give a very small result so we lower the threshold to compensate.

Use a very smaller blur.
Do limit the components.
Use a smaller threshold.

set ctdBLR_SIG=1
set ctdUSE_CONN_COMP=1

call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_sm_ex_cc0s2.png ^
  . 80
c2d_sm_ex_cc0s2.pngjpg

Note that the threshold parameter is relevant only when neither required dimension is specified, method (3). The variables ctdBLR_SIG and ctdUSE_CONN_COMP and ctdDO_RECURSE are relevant to that method, and when one required dimension is specified, method (2).

Reset the expert settings:

set ctdBLR_SIG=
set ctdUSE_CONN_COMP=
set ctdDO_RECURSE=
set ctdDBG_IMG=

Large images

The time required is slightly worse than proportional to the image size in pixels. (Worse, because some operations involve kernels that are proportional to the image diagonal.) For any given threshold, cropToDetailSml.bat is fast enough for web-size images (eg up to one million pixels) but unusably slow for large images, eg 2 minutes for 35 million pixels.

Ideally, the script would give results that were independent of image size. When we have an image at different scales, applying cropToDetailSml.bat with the same threshold to each would give the same result, at different sizes. Then we could apply a multi-scale (aka pyramid) method.

Sadly, I haven't yet found a method that gives good results for all input images at all thresholds, with the same results at all images scales. For now, cropToDetailSml.bat uses a method that gives good results for all input images at all thresholds, but gives different results at different scales.


We can apply two tests:

  1. Run the script on a small image. Enlarge the image, and run the script on that, and shrink the result. The two results should be the same.
  2. Run the script on a large image, and shrink the result. Shrink the image, and run the script on that. The two results should be the same.

I have devised methods that satisfy these tests, more or less. However, although they give the same results, the individual results are not wonderful.

:lge

rem goto :eof
rem goto skipAga

set LGE_SRC=zp_sus_sat.tiff

%IM%identify -format %%wx%%h %LGE_SRC% 
4924x7378

Resize to web-size.

set WEB_SIZE=600X600

%IM%convert ^
  %LGE_SRC% ^
  -resize %WEB_SIZE% ^
  +write c2d_lge_sm.png ^
  -format %%wx%%h info: 
400x600
c2d_lge_sm.pngjpg

Crop the large image to a range of thresholds, and show the time taken.

call %PICTBAT%c2dVaryThreshold ^
  %LGE_SRC% . c2d_lge_1.png
0 01:23:05

Resize the result for the web

%IM%convert ^
  c2d_lge_1.png ^
  -resize %WEB_SIZE% ^
  c2d_lge_1_sm.png
c2d_lge_1_sm.png

Crop the small image to a range of thresholds, and show the time taken.

call %PICTBAT%c2dVaryThreshold ^
  c2d_lge_sm.png . c2d_lge_sm_1.png

:skipAga
0 00:00:52
c2d_lge_sm_1.png

If we crop a small image, then enlarge the image, crop that and shrink the results, ideally we would find the same areas have been cropped.

set LGE_SRC=white_cat1.jpg

call %PICTBAT%c2dVaryThreshold ^
  %LGE_SRC% a.png b.png

%IM%convert %LGE_SRC% -resize 400%% c2d_lge.png

call %PICTBAT%c2dVaryThreshold ^
  c2d_lge.png c2d_lge_0.png c2d_lge_1.png

%IM%convert c2d_lge_0.png -resize 25%% c2d_lge_0.png
%IM%convert c2d_lge_1.png -resize 25%% c2d_lge_1.png

All components, small image:

a.png

All components, large image:

c2d_lge_0.png

One component, small image:

b.png

One component, large image:

c2d_lge_1.png

The script cropToDetailLge.bat is a drop-in replacement for cropToDetailSml.bat, and takes the same parameters. If the image has both dimensions less than or equal to a threshold, it simply calls cropToDetailSml.bat and exits. Otherwise, it makes a small version, and calls cropToDetailSml.bat, telling it not to make the output image, as we only want the crop parameters. It multiplies these parameters up for the full size, and uses them to crop the large image.

If we wanted greater precision, cropToDetailLge.bat could crop the large image plus a margin on all sides, and call cropToDetailSml.bat again. Would the results be better? I don't know. (We couldn't easily do this if we had called cropToDetailLge.bat with a threshold.)

Here are some results from source images with 35 million pixels, with all parameters at default values. The full-frame of the in-camera JPEG is shown, resized for the web with a simple "-resize". The crop is auto-levelled, sigmoid-adjusted if required to raise the standard deviation to 0.16667, and resized with resampHM.bat (see Resampling with halo minimization).

The full frame The cropped result
c2t_agacr_AGA_1089.jpg c2t_agacr_AGA_1089_cr.jpg
c2t_agacr_AGA_2138.jpg c2t_agacr_AGA_2138_cr.jpg
c2t_agacr_AGA_2156.jpg c2t_agacr_AGA_2156_cr.jpg
c2t_agacr_AGA_2185.jpg c2t_agacr_AGA_2185_cr.jpg
c2t_agacr_AGA_2198.jpg c2t_agacr_AGA_2198_cr.jpg
c2t_agacr_AGA_2434.jpg c2t_agacr_AGA_2434_cr.jpg
c2t_agacr_AGA_2235.jpg c2t_agacr_AGA_2235_cr.jpg
c2t_agacr_AGA_2363.jpg c2t_agacr_AGA_2363_cr.jpg
c2t_agacr_AGA_2526.jpg c2t_agacr_AGA_2526_cr.jpg
c2t_agacr_AGA_2744.jpg c2t_agacr_AGA_2744_cr.jpg
c2t_agacr_AGA_2794.jpg c2t_agacr_AGA_2794_cr.jpg
c2t_agacr_AGA_2802.jpg c2t_agacr_AGA_2802_cr.jpg
c2t_agacr_AGA_2905.jpg c2t_agacr_AGA_2905_cr.jpg
c2t_agacr_AGA_2968.jpg c2t_agacr_AGA_2968_cr.jpg
c2t_agacr_AGA_2969.jpg c2t_agacr_AGA_2969_cr.jpg
c2t_agacr_AGA_1020.jpg c2t_agacr_AGA_1020_cr.jpg
c2t_agacr_AGA_1346.jpg c2t_agacr_AGA_1346_cr.jpg

Dimension hints

As can be seen above, the script can make very high aspect-ratio crops. If this is undesirable, a wrapper script can increase the small dimension and crop with that, or call cropToDetailSml.bat again with revised dimensions.

The script has no preference for portrait or landscape format, and will happily make a portrait or landscape output from a landscape or portrait input.

An application might need to know whether a portrait or landscape format gives a better result. cropToDetailSml.bat (or cropToDetailLge.bat) can be run twice, eg with dimensions 600x300 and 300x600, and the scores compared. Scores are in the range 0.0 to 1.0, typically around 0.98, with lower scores indicating more detail. Scores come from the "white rectangle" process, so are available when both dimensions are specified, or when recursion is used. A score of "999999" means no score is available.

Crop horizontally.

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_inscr_h.jpg 200x100

echo ctdSCORE=%ctdSCORE% 
ctdSCORE=0.948164 
c2d_inscr_h.jpg

Crop vertically.

call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_inscr_v.jpg 100x200

echo ctdSCORE=%ctdSCORE% 
ctdSCORE=0.968503 
c2d_inscr_v.jpg

The horizontal crop has the lower score, so it contains more detail.

Performance hints

cropToDetailSml.bat takes up to about three seconds for each thresholded example shown on this page. For the highest speed (which may give horrible results):

These settings use all the white components, without blurring first, so have many outliers, creating a large result (not much cropping). To compensate, we use a higher threshold. For example:

:skipToPerf

set ctdBLR_SIG=0
set ctdUSE_CONN_COMP=0
set ctdDO_RECURSE=0

set THRESH=99.9
call %PICTBAT%cropToDetailSml ^
  insect.jpg c2d_i_fst.jpg ^
  . %THRESH%
c2d_i_fst.jpg
call %PICTBAT%cropToDetailSml ^
  white_cat1.jpg c2d_wc_fst.jpg ^
  . %THRESH%
c2d_wc_fst.jpg
call %PICTBAT%cropToDetailSml ^
  car_lady_dog1.jpg c2d_cld_fst.jpg ^
  . %THRESH%
c2d_cld_fst.jpg
call %PICTBAT%cropToDetailSml ^
  sofa_cat_small.jpg c2d_sc_fst.jpg ^
  . %THRESH%
c2d_sc_fst.jpg
call %PICTBAT%cropToDetailSml ^
  toes.png c2d_t_fst.jpg ^
  . %THRESH%
c2d_t_fst.jpg

The total time to process these five images was:

0 00:00:25

This is fast, but the results are not as good as the default settings. In the white-cat and car-lady-dog images detail has been cropped off, while leaving areas of apparent low detail that could have been removed.

Reset the expert settings:

set ctdBLR_SIG=
set ctdUSE_CONN_COMP=
set ctdDO_RECURSE=
set ctdDBG_IMG=

Future

The initial two guesses for thresholds should be variables.

A common requirement is to crop such that the output fits in a WxH box. The script would do this iteratively: find the smallest threshold such that each found dimension is less than or equal to the corresponding specified dimension. This will take longer, because probably no threshold can satisfy both dimensions exactly, so we will always iterate until bust.

How would we find the best crop for a given aspect ratio? We can readily find the dimensions of the largest rectangle of that aspect ratio, and use that WxH, but this would always use either the full width or height of the input. Beware: for a given aspect ratio, the most detailed image is probably very small, eg a single pixel.

Cropping to the exact bounding box means object boundaries often hit the image edges. A facility to slightly relax the crop would be useful.

Currently, the script thresholds on a given percentage of pixels. I may provide an option that instead thresholds on the blurred and auto-levelled (but not equalized) slope magnitude. The user could then eliminate areas that were (roughly speaking) blurrier than 25% of the sharpest areas.

There could be a weighting, eg to bias the results towards the centre on the input. This could be done before the script, as a pre-process: blur the image near the edges. Computationally it would be more efficient to selectively darken the magnitude image.

In principle, we could also "auto-rotate" if this would increase the score for a given crop.

To find the whitest area, srchImg.bat is used. Perhaps srchImgAg.bat, which is much faster, could be used.

Perhaps we can detect the "bust" state sooner.

Many operations can be combined to reduce disk I/O.

As always, the script could be written in a compiled language. This would improve performance mostly in the iterative process when only one dimension is known.

Conclusion

The scripts have a very simple interface, and they work. The user specifies the required width, or height, or both, or neither, and the script will automatically choose a crop.

Of course, the scripts don't always choose the same crop that I would, because I use more criteria than mere sharpness. But I have now tested this with thousands of images, and it has never made a "bad" crop with the default settings. Sometimes it makes interesting crops that hadn't occurred to me.

Scripts

cropToDetailSml.bat

rem From %1 an ordinary image,
rem makes %2 cropped to detail of %1.
rem %3 is required dimensions, WxH.
rem %4 force the use of this threshold value.
@rem If width and height are both given, the job is easy.
@rem If neither width nor height are given, we use a threshold.
@rem If only one is given, it iterates to a solution.
@rem   This may not be exactly the specified dimension.
@rem   If not, recursively called with that specified dimension
@rem   and calculated dimension.
@rem
@rem Also uses:
@rem   ctdBLR_SIG: sigma for blur of slope magnitude [0 or 20]
@rem   ctdUSE_CONN_COMP 0 or 1:
@rem     whether to use only largest connected component. [1]
@rem   ctdDO_RECURSE 0 or 1: whether to recursively call. [1]
@rem   ctdDBG_IMG is not blank, writes a debug image to this.
@rem
@rem   ctdREUSE_SM if 1, use existing slope magnitude image (don't make a new one).
@rem     (This is used internally, at recursive calls.)
@rem
@rem Reference: http://im.snibgo.com/crop2det.htm
@rem
@rem Last updated:
@rem   9-July-2017 Use connCompCropWhite.bat; %3 format WxH.
@rem   12-July-2017 Numerous.
@rem


@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

echo %0: %1 %2 %3 %4
echo %0: ctdBLR_SIG=%ctdBLR_SIG% ctdUSE_CONN_COMP=%ctdUSE_CONN_COMP% ctdDO_RECURSE=%ctdDO_RECURSE%

call %PICTBAT%setInOut %1 ctd

if not "%2"=="" if not "%2"=="." set OUTFILE=%2

set DIMS=%3
if "%DIMS%"=="." set DIMS=
if "%DIMS%"=="" set DIMS=0x0

set FORCE_TH=%4
if "%FORCE_TH%"=="." set FORCE_TH=

if "%ctdUSE_CONN_COMP%"=="" set ctdUSE_CONN_COMP=1

if "%ctdDO_RECURSE%"=="" set ctdDO_RECURSE=1

rem   Ensure required dims don't exceed image dims.

set WW=
for /F "usebackq" %%L in (`%IM%identify ^
  -format "WW=%%w\nHH=%%h\nDIAG_FACT=%%[fx:hypot(w,h)/664]" ^
  %INFILE%`) do set %%L
if "%WW%"=="" exit /B 1

call parseXxY2i %WW% %HH% DIMS %DIMS%

set R_WI=%DIMS_X%
set R_HT=%DIMS_Y%

if not "%R_WI%"=="0" (
  if %R_WI% GTR %WW% set R_WI=%WW%
)

if not "%R_HT%"=="0" (
  if %R_HT% GTR %HH% set R_HT=%HH%
)

set HAS_BOTH=0
if not "%R_WI%"=="0" if not "%R_HT%"=="0" if "%FORCE_TH%"=="" set HAS_BOTH=1

if "%R_WI%"=="0" if "%R_HT%"=="0" if "%FORCE_TH%"=="" (
  set FORCE_TH=85
  if "%ctdUSE_CONN_COMP%"=="0" set FORCE_TH=90
)

if "%ctdBLR_SIG%"=="" (
  set ctdBLR_SIG=0
  if %ctdUSE_CONN_COMP%==0 set ctdBLR_SIG=20

  for /F "usebackq" %%L in (`%IM%identify ^
    -format "ctdBLR_SIG=%%[fx:!ctdBLR_SIG!*%DIAG_FACT%]\n" ^
    xc:`) do set %%L
)

echo %0: WW=%WW% HH=%HH% req:%R_WI%x%R_HT% ctdBLR_SIG=%ctdBLR_SIG% ctdUSE_CONN_COMP=%ctdUSE_CONN_COMP% ctdDO_RECURSE=%ctdDO_RECURSE% DIAG_FACT=%DIAG_FACT% FORCE_TH=%FORCE_TH%

rem For method 1, COMET_BLR=1*DIAG_FACT.
rem for method 11, try 2*...

for /F "usebackq" %%L in (`%IM%identify ^
  -format "COMET_BLR=%%[fx:0.5*%DIAG_FACT%]\ncccwAREA_TH=%%[fx:int(%DIAG_FACT%*%DIAG_FACT%*2+1)]\n" ^
  xc:`) do set %%L

set TMPDIR=\temp\
set SM_IMG=%TMPDIR%ctd_sm.miff
set SM_IMG_B=%TMPDIR%ctd_sm_b.miff
set SM_IMG_C=%TMPDIR%ctd_sm_c.miff
set SM_IMG_D=%TMPDIR%ctd_sm_d.miff
set WHT_IMG=%TMPDIR%ctd_white.miff

rem FIXME: make SM_METH a variable
rem FIXME: make one script to do any method?

set SCORE=999999

if "%ctdREUSE_SM%"=="1" goto made_sm

set SM_METH=11

if %SM_METH%==0 (
  call %PICTBAT%slopeMagF %INFILE% %SM_IMG%
) else if %SM_METH%==1 (
  echo %0: COMET_BLR=%COMET_BLR%

  call %PICTBAT%slopeXYblMag ^
    %INFILE% %SM_IMG% %COMET_BLR% ^
    "-grayscale Brightness -auto-level"

  if ERRORLEVEL 1 exit /B 1

) else if %SM_METH%==11 (
  echo %0: gsm BLR=%COMET_BLR%

  call %PICTBAT%gaussSlpMag ^
    %INFILE% %SM_IMG% %COMET_BLR% ^
    "-grayscale Brightness -auto-level"

  if ERRORLEVEL 1 exit /B 1

) else if %SM_METH%==2 (

  call %PICTBAT%gaussStdDev ^
    %INFILE% ^
    %SM_IMG% ^
    "-blur 0x3" ^
    "-separate -evaluate-sequence Max -auto-level"

) else if %SM_METH%==3 (

  for /F "usebackq" %%L in (`%IM%identify ^
    -format "METH_SIG=%%[fx:%DIAG_FACT%]\n" ^
    xc:`) do set %%L

  echo METH_SIG=!METH_SIG!

  call %PICTBAT%gaussStdDev ^
    %INFILE% ^
    %SM_IMG% ^
    "-blur 0x!METH_SIG!" ^
    "-separate -evaluate-sequence Max"

) else if %SM_METH%==3XX (

  for /F "usebackq" %%L in (`%IM%identify ^
    -format "METH__SIG=%%[fx:2*%DIAG_FACT%]\n" ^
    xc:`) do set %%L

  echo METH_SIG=!METH_SIG!

  call %PICTBAT%gaussStdDev ^
    %INFILE% ^
    %SM_IMG% ^
    "-blur 0x!METH_SIG!" ^
    "-separate -evaluate-sequence Max"


  rem call %PICTBAT%gaussStdDev ^
    %INFILE% ^
    x2.miff ^
    "-blur 0x8" ^
    "-separate -evaluate-sequence Max"

  rem %IM%convert ^
    x1.miff +write mpr:X1 ^
    x2.miff ^
    -compose MinusSrc -composite ^
    mpr:X1 ^
    -compose Multiply -composite ^
    -auto-level ^
    NULL:


) else if %SM_METH%==4 (

  call %PICTBAT%slopeXY ^
    %INFILE% ^
    s.miff

  call %PICTBAT%slopeXYdiv ^
    s.miff ^
    %SM_IMG%

  %IM32f%convert ^
    %SM_IMG% ^
    -separate -evaluate-sequence Max ^
    %SM_IMG%
)
if ERRORLEVEL 1 exit /B 1

:made_sm

if %HAS_BOTH%==1 (
  %IM%convert -size %R_WI%x%R_HT% xc:white %WHT_IMG%
  if ERRORLEVEL 1 exit /B 1

  rem Next for method 1:
  rem %IM%convert %SM_IMG% -sigmoidal-contrast 20,50%% ctd_hack.miff

  rem Next for method 111:
  %IM%convert %SM_IMG% -sigmoidal-contrast 20,50%% ctd_hack.miff

  call %PICTBAT%srchImg ctd_hack.miff %WHT_IMG%
  if ERRORLEVEL 1 exit /B 1

  echo %0: crop is !siCOMP_CROP! score is !siCOMP_FLT!

  if /I "%OUTFILE%" NEQ "null:" (
    %IM%convert ^
      %INFILE% ^
      -crop !siCOMP_CROP! +repage ^
      %OUTFILE%
    if ERRORLEVEL 1 exit /B 1
  )

  set SCORE=!siCOMP_FLT!
  set CROP4=!siCOMP_CROP!

  goto end
)

if "%R_WI%"=="0" (
  set REQ_DIM=%R_HT%
) else (
  set REQ_DIM=%R_WI%
)

if "%ctdBLR_SIG%"=="0" (
  set sBLUR=
) else (
  set sBLUR=-blur 0x%ctdBLR_SIG%
)

echo %0: %sBLUR% equalize

%IM%convert ^
  %SM_IMG% ^
  %sBLUR% ^
  -equalize ^
  %SM_IMG_B%

if not "%FORCE_TH%"=="" (
  set BUST=0
  call :calcCrop %FORCE_TH%
  if ERRORLEVEL 1 exit /B 1

  goto foundCrop
)

set FINISHED=0
set T0=10
set T2=99.99
set T2=100

echo %0: T0=%T0% T2=%T2%

call :calcCrop %T0%
if ERRORLEVEL 1 exit /B 1
set D0=%CALC_DIM%

call :calcCrop %T2%
if ERRORLEVEL 1 set CALC_DIM=0
set D2=%CALC_DIM%

if %D0% LSS %REQ_DIM% (
  echo Bust: REQ_DIM=%REQ_DIM% D0=%D0% 
  exit /B 1
)

if %D2% GTR %REQ_DIM% (
  echo Bust: REQ_DIM=%REQ_DIM% D1=%D1% 
  exit /B 1
)

set BUST=0
set PREV_DIFF=999999
:loop

for /F "usebackq" %%L in (`%IM%identify ^
  -precision 9 ^
  -format "T1=%%[fx:sqrt(%T0%*%T2%)]"
  xc:`) do set %%L

call :calcCrop %T1%
if ERRORLEVEL 1 exit /B 1
set D1=%CALC_DIM%

set /A DIFF=%CALC_DIM%-%REQ_DIM%
if %DIFF% LSS 0 set /A DIFF=-%DIFF%

if %DIFF% LSS %PREV_DIFF% (
  set PREV_DIFF=%DIFF%
  set BW=%CW%
  set BH=%CH%
  set BX=%CX%
  set BY=%CY%
)

if %T0%==%T1% set BUST=1
if %T2%==%T1% set BUST=1

if %BUST%==1 set FINISHED=1

if %FINISHED%==0 (
  if %D1%==%REQ_DIM% (
    echo %0: Found T1=%T1% D1=%D1%
    set FINISHED=1
  ) else if %D1% GTR %REQ_DIM% (
    rem Solution is between 1 and 2
    set T0=%T1%
    set D0=%D1%
  ) else (
    rem Solution is between 0 and 1
    set T2=%T1%
    set D2=%D1%
  )
  goto loop
)


:foundCrop

echo %0: last crop is %CW%x%CH%+%CX%+%CY%

if %BUST%==1 (
  echo %0: ** BUST!!
  echo %0: best crop is %BW%x%BH%+%BX%+%BY%

  set CW=%BW%
  set CH=%BH%
  set CX=%BX%
  set CY=%BY%

  if "%R_WI%"=="0" (
    set /A CW=!CW!*%R_HT%/!CH!

    set CH=%R_HT%
  ) else (
    set /A CH=!CH!*%R_WI%/!CW!

    set CW=%R_WI%
  )
  rem FIXME: Should we also change the other dimension, in proportion?
)

rem Now we know both required dimensions, so maybe recursive call.

if %ctdDO_RECURSE%==0 (
  set CROP4=%CW%x%CH%+%CX%+%CY%
  if /I "%OUTFILE%" NEQ "null:" (
    %IM%convert ^
      %INFILE% ^
      -crop !CROP4! +repage ^
      %OUTFILE%
    if ERRORLEVEL 1 exit /B 1
  )
) else (
  set ctdREUSE_SM=1
  call %PICTBAT%cropToDetailSml %INFILE% %OUTFILE% %CW%x%CH%
  if ERRORLEVEL 1 exit /B 1
  set ctdREUSE_SM=

  set CROP4=!ctdCROP!
  set SCORE=!ctdSCORE!
)

rem FIXME: possibly make a debug image.

if not "%ctdDBG_IMG%"=="" %IM%convert ^
  %SM_IMG% ^
  -region %CROP4% ^
  +level 33,67%% ^
  %ctdDBG_IMG%

:end

echo %0: OUTFILE=%OUTFILE% ctdSCORE=%SCORE%

call echoRestore

endlocal & set ctdOUTFILE=%OUTFILE%& set ctdSCORE=%SCORE%& set ctdCROP=%CROP4%

@exit /B 0


::--------------------------------------------------
:: Subroutines.

:: calcCrop: Given a threshold %1 percent, find the crop params.
:: Returns just the dimension that was required in CALC_DIM.

:calcCrop

set CW=
if %ctdUSE_CONN_COMP%==1 (

  %IM%convert ^
    %SM_IMG_B% ^
    -threshold %1%% ^
    %SM_IMG_C%

  call %PICTBAT%connCompCropWhite %SM_IMG_C%

  if ERRORLEVEL 1 exit /B 1

  set /A AREA=!CW!*!CH!
) else (

  for /F "usebackq tokens=1-4 delims=+x" %%A in (`%IM%convert ^
    %SM_IMG_B% ^
    -threshold %1%% ^
    -bordercolor Black -border 1 ^
    -format %%@\n ^
    info:`) do (
    set CW=%%A
    set CH=%%B
    set /A CX=%%C-1
    set /A CY=%%D-1
    set /A AREA=%%A*%%B
  )
)

if "%CW%"=="" exit /B 1

set CALC_DIM=%CW%
if "%R_WI%"=="0" set CALC_DIM=%CH%

echo %0: thresh=%1  %CW%x%CH%+%CX%+%CY%  REQ_DIM=%REQ_DIM% CALC_DIM=%CALC_DIM%

if %AREA%==0 exit /B 1

exit /B 0

slopeXYblMag.bat

rem From colour image %1,
rem find magnitude of slope of RGB channels, by comet blur method.
rem Output %2 is single image.
rem %3 is blur sigma. (Should be at least about 0.12)
rem %4 is post-processing.
@rem
@rem See also slopeXYbl.bat.

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 sxybm

if not "%2"=="" set OUTFILE=%2

set BLR_SIG=%3
if "%BLR_SIG%"=="." set BLR_SIG=
if "%BLR_SIG%"=="" set BLR_SIG=1

set POST_PROC=%~4
if "%POST_PROC%"=="" set POST_PROC=

if "%IM32f%"=="" call %PICTBAT%setIm8

%IM32f%convert ^
  %INFILE% ^
  -alpha off ^
  -virtual-pixel Edge ^
  -define compose:clamp=off ^
  -define convolve:scale="^!" ^
  ( -clone 0 ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG% ) ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG%,180 ) ^
    -delete 0 ^
    -compose MinusDst -composite ^
  ) ^
  ( -clone 0 ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG%,90 ) ^
    ( -clone 0 -morphology Convolve Comet:0x%BLR_SIG%,270 ) ^
    -delete 0 ^
    -compose MinusDst -composite ^
  ) ^
  -delete 0 ^
  -channel RGB ^
  -evaluate Pow 2 ^
  -compose plus -composite ^
  -evaluate Divide 2 ^
  -evaluate Pow 0.5 ^
  %POST_PROC% ^
  +depth ^
  -depth 32 ^
  -define "quantum:format=floating-point" ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set sxybmOUTFILE=%OUTFILE%

c2dVaryThreshold.bat

rem From image %1,
rem writes %2 and %3,
rem appending outputs from cropToDetail.bat at various thresholds.

@setlocal

set INFILE=%1
set OUTFILE0=%2
set OUTFILE1=%3

if "%OUTFILE0%"=="." set OUTFILE0=
if "%OUTFILE1%"=="." set OUTFILE1=

set PREF=%TEMP%/c2d_th


if not "%OUTFILE0%"=="" (
  set ctdUSE_CONN_COMP=0

  call :doAppend %OUTFILE0%
)

if not "%OUTFILE1%"=="" (
  set ctdUSE_CONN_COMP=1

  call :doAppend %OUTFILE1%
)

set ctdUSE_CONN_COMP=

exit /B 0

::---------------------------------------------
:: Subroutine

:doAppend
call %PICTBAT%cropToDetailSml %INFILE% %PREF%99.miff . 99
call %PICTBAT%cropToDetailSml %INFILE% %PREF%97.miff . 97
call %PICTBAT%cropToDetailSml %INFILE% %PREF%95.miff . 95
call %PICTBAT%cropToDetailSml %INFILE% %PREF%90.miff . 90
call %PICTBAT%cropToDetailSml %INFILE% %PREF%85.miff . 85
call %PICTBAT%cropToDetailSml %INFILE% %PREF%80.miff . 80
call %PICTBAT%cropToDetailSml %INFILE% %PREF%75.miff . 75
call %PICTBAT%cropToDetailSml %INFILE% %PREF%70.miff . 70

%IM%convert ^
  -background gray ^
  ( %PREF%99.miff %PREF%97.miff %PREF%95.miff %PREF%90.miff %PREF%85.miff ^
    -bordercolor gray -border 5 +append ^
  ) ^
  ( %PREF%80.miff %PREF%75.miff %PREF%70.miff ^
    -bordercolor gray -border 5 +append ^
  ) ^
  -append ^
  %1

exit /B 0

connCompCropWhite.bat

rem Given image %1,
rem writes environment variable cccwCROP
rem as the bounding box WxH+X+Y of the
rem largest white connected component.
rem Also sets CW, CH, CX and CY.

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 cccw

if "%cccwAREA_TH%"=="" set cccwAREA_TH=2

echo %0: cccwAREA_TH=%cccwAREA_TH%

set CROP=
set CW=

for /F "usebackq skip=1 tokens=2,5" %%A in (`%IM%convert ^
  %INFILE% ^
  -define connected-components:verbose^=true ^
  -define connected-components:area-threshold^=%cccwAREA_TH% ^
  -connected-components 8 ^
  NULL:`) do (
  if "!CROP!"=="" (
    set IsWhite=0
    if "%%B"=="srgb(100%%,100%%,100%%)" set IsWhite=1
    if "%%B"=="srgb(255,255,255)" set IsWhite=1
    if !IsWhite!==1 (
      set CROP=%%A
    )
  )
)

if "%CROP%"=="" (
  echo %0: No white components found in %INFILE%.
  exit /B 1
)

for /F "tokens=1-4 delims=+x" %%A in ("%CROP%") do (
  set CW=%%A
  set CH=%%B
  set CX=%%C
  set CY=%%D
  set AREA=%%A*%%B
)

echo %0: CROP=%CROP%  %CW% %CH% %CX% %CY%

call echoRestore

@endlocal & set cccwCROP=CROP& set CW=%CW%& set CH=%CH%& set CX=%CX%& set CY=%CY%

cropToDetailLge.bat

rem Like cropToDetailSml.bat, but for large images.

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

echo %0: %1 %2 %3 %4 %5
echo %0: ctdBLR_SIG=%ctdBLR_SIG% ctdUSE_CONN_COMP=%ctdUSE_CONN_COMP% ctdDO_RECURSE=%ctdDO_RECURSE%

call %PICTBAT%setInOut %1 ctdl

if not "%2"=="" if not "%2"=="." set OUTFILE=%2

set DIMS=%3
if "%DIMS%"=="." set DIMS=
if "%DIMS%"=="" set DIMS=0x0

set FORCE_TH=%4
if "%FORCE_TH%"=="." set FORCE_TH=


set LIM_W=600
set LIM_H=600

set WW=
for /F "usebackq" %%L in (`%IM%identify ^
  -format "WW=%%w\nHH=%%h\n" ^
  %INFILE%`) do set %%L
if "%WW%"=="" exit /B 1

call parseXxY2i %WW% %HH% DIMS %DIMS%

set R_WI=%DIMS_X%
set R_HT=%DIMS_Y%

if not "%R_WI%"=="0" (
  if %R_WI% GTR %WW% set R_WI=%WW%
)

if not "%R_HT%"=="0" (
  if %R_HT% GTR %HH% set R_HT=%HH%
)

echo %0: WW=%WW% HH=%HH% req:%R_WI%x%R_HT% ctdBLR_SIG=%ctdBLR_SIG% ctdUSE_CONN_COMP=%ctdUSE_CONN_COMP% ctdDO_RECURSE=%ctdDO_RECURSE% DIAG_FACT=%DIAG_FACT% FORCE_TH=%FORCE_TH%

if %WW% LEQ %LIM_W% if %HH% LEQ %LIM_H% (
  call %PICTBAT%cropToDetailSml %INFILE% %OUTFILE% %DIMS% %FORCE_TH%
  if ERRORLEVEL 1 exit /B 1

  set CROP4=!ctdCROP!
  set SCORE=!ctdSCORE!

  goto end
)


set TMPDIR=\temp\
set SML_IMG=%TMPDIR%ctdl_sml.miff

set sFMT=^
MULT_W=%%[fx:%WW%/w]\n^
MULT_H=%%[fx:%HH%/h]\n^
RW=%%[fx:int(%R_HT%*w/%WW%+0.5)]\n^
RH=%%[fx:int(%R_HT%*h/%HH%+0.5)]\n

set RW=
for /F "usebackq" %%L in (`%IM%convert ^
  %INFILE% ^
  -resize %LIM_W%x%LIM_H% ^
  +write %SML_IMG% ^
  -precision 9 ^
  -format "%sFMT%" ^
  info:`) do set %%L
if "%RW%"=="" exit /B 1


call %PICTBAT%cropToDetailSml %SML_IMG% null: %RW%x%RH% %FORCE_TH%
if ERRORLEVEL 1 exit /B 1

echo %0: ctdCROP=%ctdCROP%
set SCORE=!ctdSCORE!

set sFMT=
for /F "tokens=1-4 delims=+x" %%A in ("%ctdCROP%") do set sFMT=^
FW=%%[fx:int(%%A*%MULT_W%+0.5)]\n^
FH=%%[fx:int(%%B*%MULT_H%+0.5)]\n^
FX=%%[fx:int(%%C*%MULT_W%+0.5)]\n^
FY=%%[fx:int(%%D*%MULT_H%+0.5)]\n

if "%sFMT%"=="" exit /B 1

set FW=
for /F "usebackq" %%L in (`%IM%identify ^
  -precision 9 ^
  -format "%sFMT%" ^
  xc:`) do set %%L
if "%FW%"=="" exit /B 1

if %FW%==0 set FW=1
if %FH%==0 set FH=1

rem If this script was called with explicit W and H, use those.
rem
if not "%R_WI%"=="0" set FW=%R_WI%
if not "%R_HT%"=="0" set FH=%R_HT%

set CROP4=%FW%x%FH%+%FX%+%FY%

echo %0: CROP4 = %CROP4%

if /I "%OUTFILE%" NEQ "null:" %IM%convert ^
  %INFILE% ^
  -crop %CROP4% +repage ^
  %OUTFILE%

:end

@call echoRestore

@endlocal & set ctdlOUTFILE=%OUTFILE%& set ctdlSCORE=%SCORE%& set ctdlCROP=%CROP4%

@exit /B 0

All images on this page were created by the commands shown, using:

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG to JPG.

Source file for this web page is crop2det.h1. To re-create this web page, run "procH1 crop2det".


This page, including the images except where shown otherwise, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.

Anyone is permitted to link to this page, including for commercial use.


Page version v2.0 12-July-2017.

Page created 28-Jul-2017 19:58:43.

Copyright © 2017 Alan Gibson.