ImageMagick create a composite grid from N images - imagemagick

How would I create a composite/grid(i'm not sure the legit terminology) from a group of differently sized images.
Is there a tried and true method for this?
For example:
1. [1,2]
2. [1,2,3]
3. [[1,2],[3,4]]
4. [[1,2,3], [4,5]] (here the images on the 2nd row would have to be wider than images on the first row.
5. [[1,2,3], [4,5,6]]
6. [[1,2,3],[4,5], [6,7]] here rows 2 and 3 would have to be wider than the top row.
# and so on...
is my expectation silly? is there another way to make a decent looking grid of N images?

ImageMagick's montage will do a pretty reasonable job with:
montage image1.png ... imageN.jpg -geometry +10+10 montage.gif
If you know you want 3 columns, you can add -tile 3x to force that. Likewise -tile x4 if you want 4 rows.
Beyond that, you can get image dimensions and lay them out any way you want if you have some aesthetics that appeal to your sense of order. I did one ages ago... here.

Related

Create fixed-size montage of images with missing files

Setting
Suppose we have a list of N elements of which an element can either be a path to an image (e.g. a.jpg) or NULL indicating that a file is missing.
Example (N = 6): a.jpg,NULL,c.jpg,NULL,NULL,f.jpg
All mentioned images (a.jpg, c.jpg, f.jpg) are guaranteed to have the same resolution.
Task
Create a fixed-width montage (e.g. out.jpg) in which NULL values are replaced with black images whose resolutions are consistent with the common resolution of a.jpg, c.jpg, f.jpg. I would like to abstain from creating an actual black.jpg and would prefer to create the image on-the-fly as needed.
Using ImageMagick's "montage" command, if your images are known dimensions so you can include that in the command, and if you can generate a text file "list.txt" of the image files and put "xc:black" on each line that has no image like this...
image00.png
image01.png
image02.png
image03.png
image04.png
xc:black
image06.png
image07.png
xc:black
xc:black
image10.png
image11.png
You can run the ImageMagick "montage" command something like this...
magick montage #list.txt -tile 3x4 -geometry 160x160+3+3! out.png
The "#" in front of the name of the text file tells IM to read the input images from there. The "-tile" describes how many columns and rows will be in the result. The "-geometry" setting is where you put the dimensions of the images and the spacing between columns and rows. The "xc:black" images are single black pixels, but the exclamation point forces them to the W and H dimensions in the "-geometry" argument.
That will create black images everywhere you have "xc:black" in the list. If you want to fill between the spaces with black also, add "-background black" to the command.
That works for me with IMv7 and "magick montage ..." For IMv6 you just use "montage". I'm pretty sure everything else about the command would work the same way.

Convert sprite sheet to gif animation

The frames go in the order left to right, top to bottom, the animations go sequentially, all frames are of the same size.
1234
5612
345
I need a command that would take frame size, coordinates of the first frame and frame count as input and give an animated gif as output. Preferably without generating intermediate files.
I could do this using a programming language, but isn't there a way to do it easier with a command line tool like ImageMagick or GraphicsMagick? It feels to me like it should be a common task, yet I've found only questions about how to convert gif to sprite sheet, not the other way around.
With ImageMagick you would extract each frame sub-image with -crop WxH +adjoin +repage, and then animate the frames together.
For example, given a sprite of 300x289 sub-images like the following...
convert sprite.png -crop 300x289 +adjoin +repage -adjoin -loop 0 -delay 1 output.gif
See Animation Basics, and Animation Modifications for other examples.
If you set variables in your shell for the width and height of an individual sprite, the X and Y offsets for the starting sprite, and the number of sprites to use, an ImageMagick command like this will extract the requested sprites from the sheet and turn them into an animated GIF.
This is in Windows CMD syntax...
set WIDE=100
set HIGH=100
set XCOORD=100
set YCOORD=300
set FRAMES=5
convert spritesheet.png ^
-set option:distort:viewport %[fx:%FRAMES%*%WIDE%]x%HIGH% ^
-set option:slider %[fx:%YCOORD%*(w/%WIDE%)+%XCOORD%] ^
-crop %WIDE%x%HIGH% +append +repage ^
-distort affine "%[slider],0 0,0" ^
-crop %WIDE%x%HIGH% +repage ^
-set delay 50 -loop 0 result.gif
The variables %WIDE% and %HIGH% are the dimensions of an individual sprite.
The variables %XCOORD% and %YCOORD% are the offsets of the first sprite you need from left and top of the sheet.
The variable %FRAMES% is the total number of sprites to extract.
The command starts by reading the input sheet. It uses the input image dimensions and your provided variables to define some settings for IM to use later. First is the dimensions of the viewport needed to isolate the requested number of sprites. Second, it calculates the offset where the first sprite will be after the sheet has been cropped into single sprites and appended into one horizontal row.
Next it "-crop"s the image into individual sprites and "+append"s them into a single horizontal row.
Then it uses "-distort affine" to slide the whole row of sprites the required distance – "%[slider]" – to the left, some amount out of the viewport if needed, and reduces the viewport to just show the proper number of sprites.
After that it crops that image into individual sprites again, sets a delay for the animation, and writes the output GIF.
For a Windows BAT script you'll need to double the percent signs "%%" on the IM variables and FX expressions, but not the shell variables like %WIDE%.
For a *nix shell or script you'll need to set those variables and access them differently. Also you'll need to replace the continued line carets "^" with backslashes "\".
For ImageMagick version 7 start the command with "magick" instead of "convert".
Before writing the output GIF you'll want to set your required dispose method, the delay, and probably "-loop 0".

Chop image into 4*4 tiles with Imagemagick

I am trying to figure out if it is possible to chop an image into 4*4 tiles equally (i.e so you create 16 images from every single image).
I know it is possible, but cannot figure out the command.
With ImageMagick you can use the "-crop" operator in several ways. To cut the image into 16 pieces, 4 tiles by 4 tiles, try this command...
convert input.png -crop 4x4# +repage output%02d.png
That will create 16 output images named "output00.png", "output01.png", etc. Keep in mind if the input image width or height is not evenly divisible by 4, the output images may not be identical dimensions. They will, however, always be plus or minus 1 pixel of the same.
To start numbering at 01 instead of 00, put "-scene 1" just before the name of the output.

Use ImageMagick to retrieve the original mask from two composited images

There once was an image, possibly with alpha transparency, overlaid onto both a white background and black background. I have access to the two resulting images, but not the original, and I want to retrieve the original.
I have some Ruby code written up to do this, but, I think simply by nature of being in Ruby, it's not as fast as it needs to be. Here's the basic logic, iterating pixel by pixel:
if pixel_on_black == pixel_on_white
# Matching pixels indicate 100% opacity in the original.
original_pixel = pixel_on_black
elsif color_on_black == BLACK && color_on_white == WHITE
# Black on black and white on white indicate 0% opacity in the original.
original_pixel = TRANSPARENT
else
# Since it's not one of the simple cases, then we'll do some math.
# Fancy algebra tells us the following. (MAX_VALUE is the largest value
# a channel can have. So, in most cases, 255.)
# First, find the alpha value. This equation follows from the equations
# for composing on black and composing on white.
alpha = pixel_on_black.red - pixel_on_white.red + MAX_VALUE
# Now that we know the alpha value, undo its multiplicative effect on the
# pixel on black. By dividing. Ta da.
alpha_ratio = alpha / MAX_VALUE
original_pixel = Pixel.new
original_pixel.red = pixel_on_black.red / alpha_ratio
original_pixel.green = pixel_on_black.green / alpha_ratio
original_pixel.blue = pixel_on_black.blue / alpha_ratio
original_pixel.alpha = alpha
end
So that's nice, and it works and all. However, this code needs to end up running blazing-fast, and iterating pixels in Ruby is not acceptable. It looks like, unless this function already exists somewhere, it would be in my best interest to come up with a series of ImageMagick options that would do the trick.
I'm researching ImageMagick's command line tool now, because it looks really, really powerful, and it looks like either -fx or a series of fancy -function arguments would do the same thing as my code above. I'll keep trying, too, but are there any ImageMagick pros out there who already know how to put all that together?
EDIT: I have an -fx version running now :)
convert image_on_black.png image_on_white.png -matte -channel alpha -fx "u.r + 1 - v.r" -channel RGB -fx "(u.a == 0) ? 1 : (u / u.a)" output.png
Almost an exact translation of the original code, broken into channels. Iterate over the alpha channel, and set the correct alpha values. Then iterate over the RGB channels, and dividing the channel by the alpha value (unless it's zero, in which case we can set it to anything, since dividing by zero throws an error—in this case, I chose 1 for white).
Now time to work on converting these into more explicit arguments, since the -fx expression is reevaluated for each pixel, which isn't great.
Mkay, I surprised myself here, and I think I found an answer. It's four ImageMagick commands, though maybe they could be worked into one somehow…though I doubt it.
convert input_on_white.png -channel RGB -negate /tmp/convert_negative.png && \
convert input_on_black.png /tmp/convert_negative.png -alpha Off -compose Plus -composite /tmp/convert_alpha.png && \
composite plasma2.png /tmp/convert_alpha.png -alpha Off -channel RGB -compose Divide /tmp/convert_division.png && \
convert /tmp/convert_division.png /tmp/convert_alpha.png -compose CopyOpacity -composite plasma_output.png
(Obviously, when done, clean up those temporary files. Also, use an actual tempfile system, rather than using hardcoded paths.)
The strategy is to first create a grayscale image that represents the alpha mask. We'll be emulating the line of code alpha = pixel_on_black.red - pixel_on_white.red + MAX_VALUE, which can be rewritten as alpha = pixel_on_black.red + (MAX_VALUE - pixel_on_white.red).
So, line 1: we create an image that represents the second term of that equation, a negated version of the RGB channels of image-on-white. We save it as a temporary file.
Then, line 2: we want to add that negative image to the image-on-black. Use ImageMagick's Plus composition, and save that as the temporary alpha mask. The result is a grayscale image where white represents areas that should have 100% opacity in the final image, and black represents areas that will later be fully transparent.
Then, line 3: bring the image-on-black back to the original RGB colors. Since the image-on-black was created by mutiplying the RGB channels by the alpha ratio, we divide by the alpha mask image to undo that effect.
Finally, line 4: take the color-corrected image from line 3 and apply the alpha mask from line 2, using ImageMagick's CopyOpacity composition function. Ta da!
My original strategy took anywhere from 5-10 seconds. This strategy takes less than a second. Much, much, much better.
Unsurprisingly, asking for help is what drove me to find the answer myself. Regardless, I'll leave this question open for 48 hours to see if anyone finds a slightly more optimal solution. Thanks!

Remove shapes from image with X number of pixels or less

If I have a image with, let's say squares. Is it possible to remove all shapes formed by 10 (non white) pixels or less and keep all shapes that is formed by 11 pixels or more? I want to do it programmatically or with a command line.
Thanks in advance!
Possibly an algorithm called Erosion may be useful. It works on boolean images, shrinking all areas of "true" removing one layer of their surface pixels. Apply a few times, and small areas disappear, bigger ones remain (though shrunken). De-shrink the survivors with the opposite algorithm, dilation (apply erosion to the logical complement of the image). Find a way to define a boolean images by testing if a pixel is inside an "object" however you define it, and find a way to apply the results to the original image to change the unwanted small objects to the background color.
To be more specific would require seeing examples.
Look up flood fill algorithms and alter them to count the pixels instead of filling. Then if the shape is small enough, fill it with white.
There are a couple of ways to approach this. What you are referring to is commonly called Despeckle in Document Imaging Applications. Document scanners often introduce a lot of dirt and noise into an image during scanning and so this must be removed removed to help improve OCR accuracy.
I assume you are processing B/W images here or can convert your image to B/W otherwise it becomes a lot more complex. Despeckle is done by analysing all the blobs on the page. Another way to decide on blob size is to decide on width, height and number of pixels combined.
Leptonica.com - Is an Open Source C based library that has the blob analysis functions you require. With some simple check and loops you can delete these smaller objects. Leptonica can also be compiled quite easily into a command line program. There are many example programs and that is the best way to learn Leptionica.
For testing, you may want to try ImageMagick. It has a command line option for despeckle but it has no further parameters.
http://www.imagemagick.org/script/command-line-options.php#despeckle
The other option is to look for "despeckle" algorithms in Google.
ImageMagick, starting from version 6.8.9-10, includes a -connected-components option which can be used to do what you want, however from the example provided in the official website, it is not immediately obvious how to actually obtain the original image minus the removed connected components.
I'm almost sure there is a simpler way, but I did it via a clunky script performing a series of steps:
First, I ran the command from the connected components example:
convert in.png \
-define connected-components:verbose=true \
-connected-components 8 out.png
This produces output in the following format:
Objects (id: bounding-box centroid area mean-color):
(...)
181: 9x9+1601+916 1605.2,920.2 44 gray(0)
185: 5x5+1266+923 1268.0,925.0 13 gray(0)
274: 5x5+2276+1661 2278.0,1663.0 13 gray(255)
Then, I used awk to filter only the lines containing an area (in pixels) of black components (mean-color gray(0) in my image) smaller than my threshold $min_cc_area. Note that connected-components has an option to filter components smaller than a given area, but I needed the opposite. The awk line is similar to the following:
{if ($4 < $min_cc_area && $5=="gray(0)") { print $2 }}
I then proceeded to create a command-line for ImageMagick where I drew white rectangles on top of these connected components. The -draw command expects coordinates in the form x1,y1 x2,y2, so I used awk again to compute the coordinates from the ones in the format [w]x[h]+x1+y1 given by -connected-components:
awk '{print "white fill rectangle " $3 "," $4 " " $3+$1-1 "," $4+$2-1 }'
Finally, I ran the created ImageMagick command-line to create a new image combining all the white rectangles on top of the original one.
In the end, I got the following script:
# usage: $0 infile min_cc_area outfile
infile=$1
min_cc_area=$2
outfile=$3
awk_exp="{if (\$4 < $min_cc_area && \$5==\"gray(0)\") { print \$2 }}"
draw_rects=""
draw_rects+=$(convert $infile -define connected-components:verbose=true \
-connected-components 8 null: | \
awk "$awk_exp" | tr 'x+' ' ' | \
awk '{print " rectangle " $3 "," $4 " " $3+$1-1 "," $4+$2-1 }')
convert $infile -draw "fill white $draw_rects" $outfile
Note that this solution may erase black pixels near the removed CC's, if they insersect the bounding rectangle of the removed component.
You want a connected components labeling algorithm. It will scan through the image and give every connected shape an id number, as well as assign every pixel an id number of what shape it belongs to.
After running a connected components filter, just count the pixels assigned to each object, find the objects that have less than 10 pixels, and replace the pixels in those objects with white.
If you can use openCV, this piece of code does what you want (i.e., despakle). You can play w/ parameters of Size(3,3) in the first line to get rid of bigger or smaller noisy artifacts.
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(3,3));
morphologyEx(image, image, MORPH_OPEN, element);
morphologyEx(image, image, MORPH_CLOSE, element);
You just want to figure out the area of each components. So an 8-direction tracking algorithm could help. I have an API solve this problem coded in C++. If you want, send me an email.

Resources