Chop image into 4*4 tiles with Imagemagick - image-processing

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.

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".

Compose lots of images at once in imagemagick Ruby

I have the following code which takes a PDF file and composes it into a single jpg image which has a horizontal black line between each PDF page image, stacking the PDF pages.
image = MiniMagick::Image.open(pdf_file)
# create a new blank file which we will use to build a composite image
# containing all of our pages
MiniMagick::Tool::Convert.new do |i|
i.size "#{image.width}x#{image.layers.size * image.height}"
i.stroke "black"
image.layers.count.times.each do |ilc|
next if ilc.zero?
top = ilc * (image.height + 1)
i.draw "line 0,#{top}, #{image.width},#{top}"
end
i.xc "white"
i << image_file_name
end
composite_image = MiniMagick::Image.open(image_file_name)
# For each pdf page, add it to our composite image. We add one so that we
# don't put the image over the 1px black line that was added to separate
# pages.
image.layers.count.times do |i|
composite_image = composite_image.composite(image.layers[i]) do |c|
c.compose "Over" # OverCompositeOp
c.geometry "+0+#{i * (image.height + 1)}"
end
end
composite_image.format(format)
composite_image.quality(85)
composite_image.write(image_file_name)
It works perfectly, except a 20 page PDF file takes three minutes. I'm looking for a better way to do this. I suspect one of these two options will work:
Compose all of the PDF page images at once, although I haven't figured out how to do that.
Use vips, thanks to its pipeline implementation.
I would rather stay with imagemagick, but I am open to either way. I'm looking for pointers how to achieve what I am looking for.
I had a stab at a ruby-vips version:
require 'vips'
# n: is the number of pages to load, -1 means all pages in tall, thin image
image = Vips::Image.pdfload ARGV[0], n: -1
# we can get the number of pages and the height of each page from the metadata
n_pages = image.get 'pdf-n_pages'
page_height = image.get 'page-height'
# loop down the image cutting it into an array of separate pages
pages = (0 ... n_pages).map do |page_number|
image.crop(0, page_number * page_height, image.width, page_height)
end
# make a 50-pixel-high black strip to separate each page
strip = Vips::Image.black image.width, 50
# and join the pages again
image = pages.inject do |acc, page|
acc.join(strip, 'vertical').join(page, 'vertical')
end
image.write_to_file ARGV[1]
On this desktop with this 58 page PDF I see:
$ /usr/bin/time -f %M:%e ruby ./pages.rb nipguide.pdf x.jpg
152984:1.08
$ vipsheader x.jpg
x.jpg: 595x50737 uchar, 3 bands, srgb, jpegload
So it makes a 50,000 pixel high jpg in about 1.1 seconds and needs a peak of 150 mb of memory.
I tried fmw42's clever imagemagick line:
$ /usr/bin/time -f %M:%e convert nipguide.pdf -background black -gravity south -splice 0x50 -append x.jpg
492244:5.16
so 500 mb of memory and 5.2s. It makes an image almost exactly the same size.
The speed difference is mostly the PDF rendering library, of course: IM shells out to ghostscript, whereas ruby-vips calls poppler or PDFium directly. libvips is able to stream this program, so during evaluation it never has more than one page in memory at once.
JPG has a limit of 65535 pixels in any axis, so you won't be able to get much larger than this. For shorter documents, you could add dpi: 300 to the PDF load to get more detail. The default is 72 dpi.
You should get nice text quality without having to render at high resolution. For example, for the PDF linked above, if I run:
$ vips pdfload nipguide.pdf x.png --page 12
To render page 12 at the default 72 dpi, I get:
I am not sure this is what you want, but it seems to me from your description, you want to append the images.
I created a 3-page PDF from 3 jpg images just for testing. I then add black border (in this case 10 pixels to show it better) at the bottom of each page and then append all the pages.
This was done with Imagemagick 6.9.10.12 Q16, but I suspect Python Wand or minimagick has similar functionality.
convert test.pdf -background black -gravity south -splice 0x10 -append test.jpg
If necessary, you could chop off the black line at the bottom of the last page after the append using -chop 0x10.

ImageMagick resize: Do really nothing for the "Only Shrink Larger" case

The original image:
http://www.tiaoyue.com/img/_test/original.jpg
(2,457 bytes)
Try to get a thumbnail by ImageMagick:
convert \
http://www.tiaoyue.com/img/_test/original.jpg \
-thumbnail 200x200\> \
SecondaryCompression.jpg
Or in Windows:
convert ^
http://www.tiaoyue.com/img/_test/original.jpg ^
-thumbnail 200x200^> ^
SecondaryCompression.jpg
Get the file:
SecondaryCompression.jpg
(2,452 bytes)
Can I get the target file (SecondaryCompression.jpg) without secondary compression, only copy of original image? (2,457 bytes of the image)
Reference:
http://www.imagemagick.org/Usage/resize/#shrink
The real problem with your 'convert' command is not that the file undergoes a 'secondary compression' as you called it.
The real problem is that some of the pixels get their color values changes very slightly (which in turn does allow a better or, maybe even worse, compression result for the total file).
So you should investigate how you can prevent the color changes first!
To document + verify the color changes for each single pixel, run these commands:
convert http://www.tiaoyue.com/img/_test/original.jpg original.txt
convert SecondaryCompression.jpg SecondaryCompression.txt
sdiff -sbB SecondaryCompression.txt original.txt
Hint: The TXT output format of convert is a textual representation of the coordinate position of each pixel and its respective color values (these values are given in 3 different ways: decimal RGB (or CMYK) values, hex RGB (or CMYK) values, human readable color names (when possible). If you see the format once, you'll understand it immediately.
One can establish that in total 1415 pixels have changed color values, out of a total of 7500 pixels. That's 18.86% of pixels changed.
To create a visual representation for the pixel differences, run:
compare original.jpg SecondaryCompression.jpg delta1.jpg
compare original.jpg SecondaryCompression.jpg -compose src delta2.jpg
The first image (delta1.jpg, far left) paints those pixels in red which have different color values, using the original.jpg as a light-gray background image.
The second image (delta2.jpg, second from left) paints only pixels in red which have different colors, and paints identical color values as white pixels.
The third image (second from right) is your original JPEG. The fourth one (far right) is your 'unaltered' thumbnail (in reality with some subtle changes for some pixels).
I've no time right now to investigate the reason for the slight color changes (and can't give a reason out of the top of my head), but will maybe return later to this topic.

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