Crop image based on coordinates from a "-trim +repage" of a different image?

Questions and postings pertaining to the usage of ImageMagick regardless of the interface. This includes the command-line utilities, as well as the C and C++ APIs. Usage questions are like "How do I use ImageMagick to create drop shadows?".
the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-08T19:53:22-07:00

Is it possible to reproduce roughly this same process, on Imagemagick?

Image
(My keyboard is missing the 5+1 key :P )

Assuming the layer with the green markings already exists as a file of the same dimensions.

Roughly speaking, it would be the same of "applying" the "-trim +repage" of an image to a second one (layers?), then discarding the first one.

The end goal is to use GIMP+Imagemagick to batch-crop multiple images, based on a composite of thumbnails (on which the user paints such rough limit markings, on a separate layer, saved independently as a PNG), with some cropping/undoing of composites, resizing, and, crucially, this process, if it's possible at all. I guess I can manage the rest myself, I'll share the whole process, and whatever helper script(s) necessary to make it handier. But this is the key part, and I've only come somewhat accidentally close with "composite", but I think that's not the way to go.

User avatar
fmw42
Posts: 22988
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by fmw42 » 2017-12-08T20:34:15-07:00

In imagemagick alpha channels cannot have color. They are grayscale. So you could use an alpha channel with white and black, but it would mean your image would be mostly transparent. (You could use gray levels 255 and 254 to make it easier to see your image). But you would be better off making a second layer using gif or tif. So this works in IM 6.

Your image has no second layer or transparency. So I need to make a second layer. The displayed image alternates the two layers.

Code: Select all

convert -delay 100 2l95px.gif \( +clone -alpha set +transparent "rgb(17,249,5)" -alpha extract \) -loop 0 2l95px_layered.gif
Image

Then to process:

Code: Select all

cropvals=`convert 2l95px_layered.gif[1] -format "%@" info:`
convert 2l95px_layered.gif[0] -crop $cropvals +repage 2l95px_crop.gif
Image

In IM 7, you can do

Code: Select all

magick 2l95px_layered.gif \( -clone 1 -set option:cropvals "%@" \) -crop "%[cropvals]" +repage -delete 1 2l95px_crop.gif
Cropping inside the green marks is harder. At the moment, the only thing that comes to mind is after the crop, further shave the image by 1 pixel all around repeatedly until no more white shows in the second layer. That would require scripting and you have not specified what OS you are using.

Please always provide your exact IM version and platform, since syntax and scripting vary.

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-08T21:35:48-07:00

Wow! Many, many thanks, I've got it now! I just did a brief test and it worked just as I hoped.

The marks don't need to be green, but apparently one can use just use whatever color seems more practical while in GIMP, and later the script converts it with threshold + flatten to achieve the pure black and white "layer" required by IM. The most important thing is not to use a soft brush, I suppose.

It's not really necessary to worry about cropping inside the marks, as the crop is made from a image that's preserved, so the result is just like the one I've posted. In fact, the idea is that you can just "scribble" more or less randomly over the areas of interest, that it would crop around it.

In a few days, maybe tomorrow if I get lucky, I'll post the script(s) thing and instructions on how to proceed. Just bash script(s) for while, for compositing, and posterior slicing, resizing, and application of the slices to the original images. I think it perhaps could be eventually added to work from inside GIMP, with python-fu/script-fu, but it will take me much longer to figure how to do it.

I think this method (even without a "smooth" incorporation within GIMP) will be considerably faster and easier than, say, using something like grop-gui on a directory or a list of files, or something else like Gwenview or XnView, as it often implies in alternating sequentially between making the selections and the file operations, sometimes with considerable manual input. The operation over a composite of thumbnails won't have pixel-perfect precision, but I think that's probably acceptable for many purposes.

Thanks again!

Bonzo
Posts: 2958
Joined: 2006-05-20T08:08:19-07:00
Location: Cambridge, England

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by Bonzo » 2017-12-09T02:40:14-07:00

You say in your title a different image but in your example you use the same image.

Had you thought about using javascipt to select the area?

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-09T08:18:52-07:00

The "different image" would be one with just the green markings. My example is just how it looks on GIMP while you have both images/layers visible at once. But at some point you export just the layer with the markings as a separate image, which would then be used to crop the other image. Or images, plural, since for a single image there's no reason at all to use this method.

I haven't thought of using javascript, I only have some sense of bash scripting, and that's all.

snibgo
Posts: 10031
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by snibgo » 2017-12-09T08:36:36-07:00

One way of doing this task would be to have a script that takes an input image as the argument:

1. Copy the input to another file.

2. Open gimp with that other file.

3. The user changes the foreground colour to pure green (assuming the input contains no pure green), and selects the pencil (to avoid anti-aliasing).

4. The user makes green marks.

5. The user tells Gimp to "overwrite" its input, and closes Gimp.

6. The script converts non-green to black, and green to white, then proceeds as Fred has said.

You might automate steps (3) and (5) in Gimp, but I don't know how.
snibgo's IM pages: im.snibgo.com

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-09T11:36:32-07:00

Here's what I have at the moment, not fully automated yet:

These are the original images to be cropped in batch, all jpegs, all in the same folder:
Image

The next step is to create a montage (still done manually):

montage -define jpeg:size=220x -geometry 220x220 *.jpg montage1.png

And then it's edited on GIMP: gimp montage1.png

On GIMP, you create a new, transparent layer over the montage, and make those squiggles over the points of interest. Here's how it looks:
Image

You can tweak the transparency of the squiggle-mask layer if that's more convenient, but it's possibly ideal to restore full opacity after finishing. But maybe the "threshold 90%" manages to deal with it regardless, I haven't yet tested.

Then you toggle the visibility of the montage itself, leaving you only with the squiggles over a transparent background (still "real transparency", rather than ImageMagick's black and white, the script takes care of that for simplicity), and you export them as "montage.png", on the same folder.

You run the script, wait a while (it's slower than I'd expected, I think I'm piping too much stuff*, possibly there are more adequate ImageMagick syntaxes to produce the same results, faster), and the result is:

Image


Here's the humiliatingly messy and altogether ridiculous working prototype code, if anyone is interested:

Code: Select all

#!/bin/bash 
 # This script is free to do whatever and whatnot, except patenting and making billions with it
 # No guarantees whatsoever
 
convert montage.png -crop 220x220 maskplain-%02d.gif

\ls -1 *.jpg > originals.txt
\ls -1 maskplain*.gif > masks.txt

#while read original <&3 && read mask <&4 ; do echo $original - $mask ; done 3<originals.txt 4<masks.txt  # expression to pair originals and masks, according to file order

while read original <&3 && read mask <&4 ; do 
echo $original $mask

res=($(identify -format  "%w %h" {$original}))

#res2=($(identify -format  "%wx%h" {$original}))

x=${res[0]}
y=${res[1]}

samplevar="${x}x"
((y>x)) && samplevar="x${y}"

expandvar="x${x}"
((y>x)) && expandvar="${y}x"
  

echo ${res[@]}
echo $x x $y   echo $expandvar
echo $samplevar 

convert <(convert $mask +repage  -colorspace gray -threshold 90% -background white -alpha remove gif:-) -sample ${samplevar} /dev/shm/masktmp-${original}.gif  ## GET LARGEST DIMENSION FROM ORIGINAL

cropv=`convert /dev/shm/masktmp-${original}.gif  -format "%@" info:` ; convert <(convert "${original}" -gravity center -extent $expandvar jpg:-) -crop $cropv +repage "${original/.jpg/-cropped.jpg}"

# rm /dev/shm/masktmp1-$$.gif

# cool #  "cool" is just a script I have that will implement an arbitrary sleep, according to CPU temperature



done 3<originals.txt 4<masks.txt
There's a lot to do in terms of organizing and cleaning temporary files and everything. Just the core logic is working.

Now it associates the original files with their crop-masks by sheer file order, which is possibly not the best solution, ideally the association would be more explicit. It requires that there are no extra jpegs added to the folder during the whole process, or between the creation of the main mask file and the execution of the script. Otherwise it will just put those new files somewhere on the line, "stealing" the crop masks of other files and making a whole mess.

I"ll gradually polish it and update the script, either here and/or on my blog at linuxquestions.org


And thanks again, fmw42!!!


* I'm particularly confused by the apparent necessity of this pipe:

cropv=`convert /dev/shm/masktmp-${original}.gif -format "%@" info:` ; convert <(convert "${original}" -gravity center -extent $expandvar jpg:-) -crop $cropv +repage "${original/.jpg/-cropped.jpg}"

I thought it should work just with:

cropv=`convert /dev/shm/masktmp-${original}.gif -format "%@" info:` ; convert "${original}" -gravity center -extent $expandvar -crop $cropv +repage "${original/.jpg/-cropped.jpg}"

But for some reason it doesn't.

User avatar
fmw42
Posts: 22988
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by fmw42 » 2017-12-09T11:58:29-07:00

try

Code: Select all

convert "${original}" -gravity center -extent $expandvar -crop $( convert /dev/shm/masktmp-${original}.gif -format "%@" info: ) +repage "${original/.jpg/-cropped.jpg}"
But you do not gain anything from just keeping two lines

Code: Select all

cropv=`convert /dev/shm/masktmp-${original}.gif -format "%@" info:`
convert "${original}" -gravity center -extent $expandvar -crop $cropv +repage "${original/.jpg/-cropped.jpg}"
But where do you define $original and does it have a suffix. If so, then why are you doing ${original/.jpg/-cropped.jpg}, which is confusing to me since you have a directory called .jpg. If $original does not have a suffix, then convert ${original} is not a properly suffixed file name.

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-09T12:36:02-07:00

For some reason, it does not have the same effect. It ends up mysteriously misaligning the crop mask and the image, and the cropped results are totally off.

"${original/.jpg/-cropped.jpg}" is a bash string manipulation/editing, there's no directory called jpg.

The string manipulation replaces ".jpg" by "-cropped.jpg" (it's like "newname=$(echo $originalname | sed 's/.jpg/-cropped.jpg/')" ), in order to have the cropped files on the same folder, and identified in their names as so.

Try file=file.ext ; echo ${file/.ext/-suffix.ext}

On the "vid" galleries there are examples of the result, "Waterhouse#.jpg.jpg" becomes "Waterhouse#-cropped.jpg.jpg".

(The double ".jpg" is just because one of Geeqie's renaming functions automatically adds the extension and I didn't notice)



""${original}" is being defined as one of the items that's being read in the loop:

while read original <&3 && read mask <&4 ; do

Which in turn is reading from a file called originals.txt, which was created previously by "\ls -1 *.jpg > originals.txt", being therefore a list of jpg file names on that folder.

It's really offensively messy, sorry. :shock:

User avatar
fmw42
Posts: 22988
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by fmw42 » 2017-12-09T12:39:50-07:00

What is the purpose of the -extent? If you extend before the crop, then your cropv values will be offset from the exenteded image if you are changing its size?

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-09T13:46:46-07:00

I just "learned" of it yesterday, I think the "extent" thing is expanding the canvas into a square, just like the squares of the montage reference.

My reasoning is that the squiggle-mask is made over a thumbnail that is fit into a square, regardless of the aspect of the original image. While doing with your original line worked fine for a squiggle done "directly" over the original image (as they have the same aspect ratio), but it didn't quite work when using the upsampled square-cropped masks to crop the original images (of varying aspect ratios), it ended up all misaligned.

I guessed that the original image should also be fit into a "virtual square", so the up-sampled squiggle (within a square) will remain aligned with the original image.

Or at least that's what I thought was going on, and somehow it coincidentally works this way.

But perhaps it's somehow redundant. I'll try to remove both things and see what happens.


Edit: It misaligns.
Last edited by the dsc on 2017-12-09T13:55:15-07:00, edited 2 times in total.

User avatar
fmw42
Posts: 22988
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by fmw42 » 2017-12-09T13:52:25-07:00

Perhaps the location of the square is not being offset properly when resizing to full resolution. But you always use -gravity center before extent. That may be the issue. If you make your square from the top left of the thumbnail and resize, then you should use -gravity northwest. But I really do not follow your process. If you need further help, please post a simple single image example without variables and exact values and image names, so we can follow and see what may be going wrong.

The other thing that I do not quite follow is whether you are processing images one at a time separately or from a montage as per your images above.

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-09T14:28:27-07:00

Trying

Code: Select all

cropv=`convert /dev/shm/masktmp-${original}.gif -format "%@" info:`
convert "${original}" -gravity northwest -extent $expandvar -crop $cropv +repage "${original/.jpg/-cropped.jpg}"
Still gives me misaligned cropping.


The script first crops/"undoes" a montage (the transparent set of "masks" drawn over the original montage), producing N "masks". (Probably "mask" is not the appropriate term here, it's definitely not the same thing as regular IM masks).

Then it loops through the masks and corresponding original images, one at a time (or "one pair" at a time, mask+original), and applies that command line you gave, with the apparently necessary subprocess tweak.

But just before doing it, the mask is upscaled according to the higher dimension of the original file it's supposed to crop, in order to restore the proportions from the thumbnailed montage pairing.



I'll eventually try to clean the code and make some sort of verbose/debug mode that leaves all temporary files, and maybe even extra files that would help showing what's going on. Next I'll compare the misaligned croppings withe the full image and parameters to try to see what's going wrong.

User avatar
fmw42
Posts: 22988
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by fmw42 » 2017-12-09T15:28:06-07:00

How do you crop your montage? Do you resent the virtual canvas with +repage?

Does this help?

cropv=`convert /dev/shm/masktmp-${original}.gif +repage -format "%@" info:`
convert "${original}" +repage -gravity northwest -extent $expandvar -crop $cropv +repage "${original/.jpg/-cropped.jpg}"

I am really just guessing here, since you have not provide a simple example with your input and intermediate cropped images and then the final crop image.

the dsc
Posts: 10
Joined: 2017-12-08T17:23:35-07:00
Authentication code: 1152

Re: Crop image based on coordinates from a "-trim +repage" of a different image?

Post by the dsc » 2017-12-10T18:55:07-07:00

I've managed to tweak things a bit so that there's no need for the "extent" on the original image to crop it. Instead it uses the process twice, but cropping the mask itself before (unless the image itself happens to be square).

I'll keep trying to strip down parts and get closer to your original expression, though.

I guess it's really probably something related with "repages" or lack thereof. In the first attempts of this "new version" I had it mysteriously working just for the first image of the whole batch. I was thinking there was somehow some variables remaining with values from the previous turn on the loop or something... but it was just a "repage" missing at the start.


Meanwhile, here's a more cleaned-up version of the script, commented almost line by line, "old lines" deleted, storing temporary files at a folder created in /dev/shm (defined once in the beginning of the script), and deleting it afterwards:

Code: Select all

#!/bin/bash

# this script does the cropping after the mask layer has already been saved

[[ -z "$1" ]] && echo -e "\nError: no montage-mask file given.\n\nUsage:\n $(basename ${0}) transparent-montage.png\n\nThe input file (transparent-montage.png, for example) should be a largely transparent png with opaque markings, drawn over a reference image created by running \"montage\" with the following parameters:\n\n$ montage -define jpeg:size=220x -geometry 220x220 *.jpg montage-reference.png\n\nThe resulting file is then opened in GIMP, where preservation limit marks or selections are drawn over the reference thumbnails, on a transparent layer, which is exported by itself (deleting or making invisible the reference layer), with a name such as montage.png, on the same folder where are the jpegs to be cropped.\n" && exit 1


tmpfolder=/dev/shm/cropper$$
convert="nice -19 convert" # defines imagemagick's priority to be the lowest possible




mkdir $tmpfolder


# slices the montage.png image with all the masks into N different "mask" files, one for each file to be cropped
$convert "$1" -crop 220x220 +repage ${tmpfolder}/maskplain-%02d.png 

# lists all jpgs (LIMITATION: not jpegs, not JPG, not JPEG), on the current folder, to be looped
\ls -1 "${PWD}"/*.jpg  > ${tmpfolder}/originals.txt 

# lists the masks to be looped. 
\ls -1 "${tmpfolder}"/maskplain*.png > ${tmpfolder}/masks.txt 

# This process is probably not ideal. Ideally the association between masks and files would be more explicit,
# rather than depending of "ls" file name sorting, which is widely said to be bad practice





# expression to loop in pairs originals and masks, according to file order, processing all the .jpg images
# on the folder, one at a time
while read original <&3 && read mask <&4 ; do 


# used to name temporary files with the original file name (basename, without folder) as a "prefix", 
# for easier visual debugging, if needed

originalprefix=$(basename "${original}")


# reads resolution of original image file into an array
res=($(identify -format  "%w %h" "${original}")) 


x=${res[0]} # defines x as the first element of the res array, for simplicity
y=${res[1]} # y as the second

# default assumption for for upsampling the mask, x is the largest dimension and therefore used to enlarge it
samplevar="${x}x" 

square="${x}x${x}" # a square of the largest dimensions. I think it's no longer being used


# if y is really the largest dimensions,redefine the previous two strings accordingly
((y>x)) && samplevar="x${y}" && square="${y}x${y}" 



# will be used next, but it's possibly better to unset for each loop, I guess
unset square 




# calculating the size of a rectangle proportional to the original image, with its largest dimension having 220px

# if the image is wider than it's tall...
if ((x>y)) ; then
ratio=$((x/220))
tny=$((y/ratio))
tnx=220


else 

# if the image is taller than it's wide
ratio=$((y/220))
tnx=$((x/ratio))
tny=220

fi

# tnx/tny stand for "ThumbNail's X/Y"



# if it's really a square
if ((x==y)) ; then

# "flag" string to skip the whole process of cropping the mask itself
square=1

fi


# if the "square" string/flag is still empty... do the whole cropping of the mask, in order to have it proportional
# to the original image, achieving proper alignment
if [[ -z "$square" ]] ; then

# this is probably a dumb way of creating a rectangle of the same proportions of the image centered within a 220x220 square. I couldn't grasp how to
# use the "draw" functionality of imagemagick, but I wanted to test the idea of cropping the mask itself rather than expanding the original

# It creates the mask's mask, hence the temporary filename "mask-mask"
nice -10 composite -gravity center <(convert -size ${tnx}x${tny} xc:black gif:-) <(convert -size 220x220 xc:white gif:-)  "${tmpfolder}/${originalprefix}-1-mask-mask.gif" 

# just for precaution...
unset cropv  
# defines the coordinates to crop the mask in proportion to the original image
cropv=`$convert "${tmpfolder}/${originalprefix}-1-mask-mask.gif" -format "%@" info:`   




########## cp $mask ${tmpfolder}/${originalprefix}-0-$mask.png


# renaming the files in a pattern I think makes easier to "debug" visually. The original file name is used as a prefix for the masks,
# and after the prefix, a sequential numbering according to the order the files were created

mv "${mask}" ${tmpfolder}/${originalprefix}-0-proto-mask.png


mask="${tmpfolder}/${originalprefix}-0-proto-mask.png"


# cropping the mask according to the coordinates previously attributed to "cropv"
$convert "${mask}" -crop $cropv +repage "${tmpfolder}/${originalprefix}-2-masktrimmed.gif"


else

# if the original image was square, there's no need for cropping the mask itself, therefore it's just converted

convert "${mask}" "${tmpfolder}/${originalprefix}-2-masktrimmed.gif"


# but let's just follow the same naming pattern for the temporary files
mv "${mask}" ${tmpfolder}/${originalprefix}-0-proto-mask.png

# no need to rename first and then redefine "mask" as the new name, it's ranemed just for "visual debug" purposes

fi


# up-sampling the trimmed (or square but correct) mask, to match the original image dimensions
# it's also being converted into pure black-and-white, in order to get the crop values for imagemagick

$convert <($convert "${tmpfolder}/${originalprefix}-2-masktrimmed.gif" +repage -colorspace gray -background white -alpha remove -threshold 99% png:-) -sample ${samplevar} "${tmpfolder}/${originalprefix}-3-masktmp.gif"


# possibly not needed, but doesn't hurt
unset cropv 

# getting the crop values from the up-sampled mask...
cropv=`$convert "${tmpfolder}/${originalprefix}-3-masktmp.gif"  -format "%@" info:` 


# and finally cropping the original image, suffixed replacing ".jpg" as "-cropped.jpg" on the original file name.
$convert  "${original}" -crop $cropv +repage "${original/.jpg/-cropped.jpg}"


# cool # just a script that adds an arbitrary sleep according to CPU temperature



done 3<${tmpfolder}/originals.txt 4<${tmpfolder}/masks.txt


# deleting all the temporary files. If something didn't work right, it may be useful to comment this line
# and see if it's possible to infer something from the temporary files.

rm -rf -- "${tmpfolder}"

echo "all done..."

exit 0

Post Reply