Combining images that are "cut off" in ImageMagick? - image-processing

I would like to combine 2 images, which are identical in width, but vary in height. They are identical on the bottom/top side, but it's unknown how much.
1) Identify identical parts
2) Combine the images so the identical parts match
Example:
Part 1: http://i.imgur.com/rZtAk2c.png
Part 2: http://i.imgur.com/CQaQbr8.png

1. Determine the image dimensions
Use identify to get width and height of each image:
identify \
http://i.imgur.com/rZtAk2c.png \
http://i.imgur.com/CQaQbr8.png
CQaQbr8.png PNG 701x974 720x994+10+0 8-bit sRGB 256c 33.9KB 0.000u 0:00.000
rZtAk2c.png PNG 701x723 720x773+10+46 8-bit sRGB 256c 25.6KB 0.000u 0:00.000
2. Interpret the results
The results from the above command are these:
Both images show 701 pixels wide rows.
One image shows 974 different rows.
The other image shows 723 different rows.
But both images use a different 'canvas' size.
The first image uses a 720x994 pixels canvas (offset of shown part is +10+0).
The second image uses a 720x773 pixels canvas (offset of shown part is +10+46).
3. Normalize the canvas to be identical with the shown pixels
We use the +repage image operator to normalize the canvas for both images:
convert CQaQbr8.png +repage img1.png
convert rZtAk2c.png +repage img2.png
4. Check both new images' dimensions again
identify img1.png img2.png
img1.png PNG 701x974 701x974+0+0 8-bit sRGB 256c 33.9KB 0.000u 0:00.000
img2.png PNG 701x723 701x723+0+0 8-bit sRGB 256c 25.5KB 0.000u 0:00.000
5. Learn, how to extract a single row from an image.
As an example, we extract row number 3 from img1.png (numbering starts with 0):
convert img1.png[701x1+0+3] +repage img1---row3.png
identify img---row3.png
img1---row3.png PNG 701x1 701x1+0+0 8-bit sRGB 256c 335B 0.000u 0:00.000
6. Learn, how to extract that same row in ImageMagick's 'txt' format:
convert img1.png[701x1+0+3] +repage img---row3.txt
If you are not familiar with the 'txt' format, here is an extract:
cat img---row3.txt
# ImageMagick pixel enumeration: 701,1,255,gray
0,0: (255,255,255) #FFFFFF gray(255)
1,0: (255,255,255) #FFFFFF gray(255)
2,0: (255,255,255) #FFFFFF gray(255)
3,0: (255,255,255) #FFFFFF gray(255)
4,0: (255,255,255) #FFFFFF gray(255)
5,0: (255,255,255) #FFFFFF gray(255)
6,0: (255,255,255) #FFFFFF gray(255)
7,0: (255,255,255) #FFFFFF gray(255)
8,0: (255,255,255) #FFFFFF gray(255)
9,0: (255,255,255) #FFFFFF gray(255)
[...skipping many lines...]
695,0: (255,255,255) #FFFFFF gray(255)
696,0: (255,255,255) #FFFFFF gray(255)
697,0: (255,255,255) #FFFFFF gray(255)
698,0: (255,255,255) #FFFFFF gray(255)
699,0: (255,255,255) #FFFFFF gray(255)
700,0: (255,255,255) #FFFFFF gray(255)
The 'txt' output file describes every pixel via a text line.
In each line the first column indicates the respective pixel's coordinates.
The second, third and fourth columns indicate the pixel's color in different ways (but they contain the same information each).
7. Convert each row into its 'txt' format and create its MD5 sum
This command also creates 'txt' output. But this time the 'target' file is given as txt:-. This means that the output is streamed to <stdout>.
for i in {0..973}; do \
convert img1.png[701x1+0+${i}] txt:- \
| md5sum > md5sum--img1--row${i}.md5 ; \
done
This command creates 974 different files containing the MD5 sum of the 'txt' representation for the respective rows.
We can also write all MD5 sums into a single file:
for i in {0..973}; do \
convert img1.png[701x1+0+${i}] txt:- \
| md5sum >> md5sum--img1--all-rows.md5 ; \
done
Now do the same thing for img2.png:
for i in {0..722}; do \
convert img2.png[701x1+0+${i}] txt:- \
| md5sum >> md5sum--img2--all-rows.md5 ; \
done
8. Use sdiff to determine which lines of the .md5 files match
We can use sdiff to compare the two .md5 files line by line and write the output to a log file. The nl -v 0 part of the following command automatically inserts the line number, starting with 0 into the result:
sdiff md5sum--img{1,2}--all-rows.md5 | nl -v 0 > md5sums.log
9. Check the md5sums.log for identical lines
cat md5sums.log
0 > 38c6cd70c39ffc853d1195a0da6474f8 -
1 > 85100351b390ace5a7caca11776666d5 -
2 > 66e2940dbb390e635eeba9a2944960dc -
3 > 8e93c1ed5c89aead8333f569cb768e4a -
4 > 8e93c1ed5c89aead8333f569cb768e4a -
[... skip many lines ...]
172 > f9fece874b60fa1af24516c4bcee7302 -
173 > edbe62592a3de60d18971dece07e3beb -
174 > 18a28776cc64ead860a99213644b0574 -
175 0d0753c587dc3c46078ac265895a3f6c - | 0d0753c587dc3c46078ac265895a3f6c -
176 5ecc2b5a61af4120151fed4cd2c3d305 - | 5ecc2b5a61af4120151fed4cd2c3d305 -
177 3f2857594fe410dc7fe42b4bef724a87 - | 3f2857594fe410dc7fe42b4bef724a87 -
178 2fade815d804b6af96550860602ec1ba - | 2fade815d804b6af96550860602ec1ba -
[... skip many lines ...]
719 127e6d52095db20f0bcb1fe6ff843da0 - | 127e6d52095db20f0bcb1fe6ff843da0 -
720 aef15dde4909e9c467f11a64198ba6d2 - | aef15dde4909e9c467f11a64198ba6d2 -
721 6320863dd7d747356f4b23fb7ba28a73 - | 6320863dd7d747356f4b23fb7ba28a73 -
722 2e32ceb7cc89d7bb038805e484dc7bc9 - | 2e32ceb7cc89d7bb038805e484dc7bc9 -
723 f9fece874b60fa1af24516c4bcee7302 - <
724 f9fece874b60fa1af24516c4bcee7302 - <
725 f9fece874b60fa1af24516c4bcee7302 - <
726 f9fece874b60fa1af24516c4bcee7302 - <
[... skip many lines ...]
1146 3e18a7db0aed8b6ac6a3467c6887b733 - <
1147 62866c8ef78cdcd88128b699794d93e6 - <
1148 7dbed48a0e083d03a6d731a6864d1172 - <
From this output we can conclude that rows 175 -- 722 in the sdiff-produced file all do match.
This means that there is a match in the following rows of the original images:
row 0 of img1.png matches row 175 of img2.png (begin of match).
img1.png has a total of 974 rows of pixels.
row 547 of img1.png matches row 722 of img2.png (end of match).
img2.png has a total of 723 rows of pixels.
(Remember, we used 0-based row numbering...)
10. Put it all together now
From above investigations we can conclude, that we need only the first 174 rows from img1.png and append the full img2.png below that in order to get the correct result:
convert img1.png[701x174+0+0] img2.png -append complete.png
NOTES:
There are many possible solutions (and methods to arrive there) to the problem posed by the OP. For example:
Instead of converting the rows to 'txt' format we could have used any other ImageMagick-supported format also (PNG, PPM, ...) and created the MD5 sums for comparison.
Instead of using -append to concatenate the two image parts, we could also have used -composite to superimpose them (with an appropriate offset, of course).
As #MarkSetchell says in his comment: instead of piping the 'pixel-rows' output to md5sum one could also use -format '%#' info:- to directly generate a hash value from the respective pixel-row. I had already forgotten about that option, because (years ago) I tried to use it for a similar purpose, and somehow it didn't work as I needed it. Which is why I became used to my 'piping to md5sum' approach...

Related

imagemagick: Remove all occurrences of a stray pixel surrounded by transparency

Hello! I'd like to remove all occurrences of stray pixels from a transparent image.
Below is an example image, enlarged for convenience:
Following is that same image but how I desire it to look after processing:
The best way I can think to describe what I'm looking to achieve is that every pixel whose surrounding pixels are fully transparent should be removed. Think of the selector as a 3x3 grid, with the middle of the grid being the pixel operated on.
I took a look at Morphology in the IM manual, but it doesn't appear to provide a fine enough method for this.
Is this possible with ImageMagick? Is there any other command line software that could achieve this if not?
In Imagemagick, you can use -connected-components to remove those isolated blocks of pixels. They seem to be 5x5 pixels on a side. So we threshold the area to keep at 26. We remove those blocks in the alpha channel and then replace that in the image. (Note we need to use 8-connected vs 4-connected region detection to preserve your other regions).
Since you say the your image was enlarged, so I presume your isolated regions were 1x1 pixels. So change the area-threshold to 2, to remove single pixel regions.
Input:
Unix Syntax:
convert img.png \
\( -clone 0 -alpha extract -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:area-threshold=26 \
-connected-components 8 \) \
-alpha off -compose copy_opacity -composite \
result.png
Windows Syntax:
convert img.png ^
( -clone 0 -alpha extract -type bilevel ^
-define connected-components:mean-color=true ^
-define connected-components:area-threshold=26 ^
-connected-components 8 ) ^
-alpha off -compose copy_opacity -composite ^
result.png
See -connected-components
ADDITION:
If you only want to remove the small isolated color pixels and not any transparent pixels inside the color ones, then there is no trivial way to do that. That is an enhancement I would like to have. However, it can be done.
Here is your image modified so that the top left red block has a single transparent center pixel. I added a red line to its right to be sure it was larger than 25 pixels when the center was turned transparent and so that you could see which pixel has the transparent center. You will have to download and zoom in on this image to see the missing pixel.
4x Zoom:
The method is to find all white regions in the alpha channel and then make a list of all regions that are less than 26 pixels. Then reprocess the image to remove those regions by ID.
Get ID List
id_list=""
OLDIFS=$IFS
IFS=$'\n'
arr=(`convert img2.png -alpha extract -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:verbose=true \
-connected-components 8 null: | grep "gray(255)" | sed 's/^[ ]*//'`)
echo "${arr[*]}"
num=${#arr[*]}
IFS=$OLDIFS
for ((i=0; i<num; i++)); do
id=`echo "${arr[$i]}" | cut -d' ' -f1 | sed 's/[:]*$//'`
count=`echo "${arr[$i]}" | cut -d' ' -f4`
if [ $count -lt 26 ]; then
id_list="$id_list $id"
fi
done
echo "$id_list"
Here is what is printed
12: 5x5+120+70 122.0,72.0 25 gray(255)
14: 5x5+30+85 32.0,87.0 25 gray(255)
15: 5x5+110+85 112.0,87.0 25 gray(255)
16: 5x5+75+90 77.0,92.0 25 gray(255)
17: 5x5+40+100 42.0,102.0 25 gray(255)
18: 5x5+110+110 112.0,112.0 25 gray(255)
19: 5x5+140+110 142.0,112.0 25 gray(255)
21: 5x5+15+130 17.0,132.0 25 gray(255)
22: 5x5+40+140 42.0,142.0 25 gray(255)
23: 5x5+85+140 87.0,142.0 25 gray(255)
24: 5x5+120+140 122.0,142.0 25 gray(255)
2: 5x5+55+5 57.0,7.0 25 gray(255)
5: 5x5+100+20 102.0,22.0 25 gray(255)
7: 5x5+65+30 67.0,32.0 25 gray(255)
8: 5x5+125+30 127.0,32.0 25 gray(255)
9: 5x5+105+50 107.0,52.0 25 gray(255)
11: 5x5+25+65 27.0,67.0 25 gray(255)
12 14 15 16 17 18 19 21 22 23 24 2 5 7 8 9 11
Reprocess to remove regions by ID
convert img2.png \
\( -clone 0 -alpha extract -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:remove="$id_list" \
-connected-components 8 -background black -flatten +write tmp.png \) \
-alpha off -compose copy_opacity -composite \
result2.png
4x Zoom:
fmw42's excellent answer uses connected regions, but I think it is possible with just a morphology. Use:
0 0 0
0 1 0
0 0 0
As the structuring element with erode and it'll detect 8-way connected isolated pixels. Now EOR that with your alpha and it'll make those pixels fully transparent (ie. remove them).
I don't know IM well enough to make you a command to do this :-( But with the libvips command-line it would be this to make a test image:
size=256
# sparse speckles for the alpha
vips gaussnoise t1.v $size $size
vips relational_const t1.v a.v more 200
# RGB noise
vips gaussnoise r.v $size $size
vips gaussnoise g.v $size $size
vips gaussnoise b.v $size $size
# assemble and save
vips bandjoin "r.v g.v b.v a.v" x.png
Then to remove your stray pixels:
# make mask.mor, a file containing our structuring element
cat >mask.mor <<EOF
3 3
0 0 0
0 255 0
0 0 0
EOF
# pull out the alpha channel
vips extract_band x.png a.v 3
# find isolated pixels
vips morph a.v t1.v mask.mor erode
# EOR with alpha to zap those pixels
vips boolean t1.v a.v t2.v eor
# extract rgb from original, then attach our modified alpha
vips extract_band x.png rgb.v 0 --n 3
vips bandjoin "rgb.v t2.v" x2.png
Here's before and after:
The libvips CLI is fast but a bit clumsy. It's neater if you use something like Python to script it.

Get only hex values from imagemagick

I can get the 10 most frequent colors from an image with this command
convert MYIMAG.JPG +dither -colors 10 -unique-colors
The output is the following:
# ImageMagick pixel enumeration: 10,1,65535,srgb
0,0: (17797.7,15058.3,10214.1) #453B28 srgb(69,59,40)
1,0: (26745.1,24530.8,20814.7) #685F51 srgb(104,95,81)
2,0: (35510.4,30224.2,23717.1) #8A765C srgb(138,118,92)
3,0: (33428.3,32608.7,27562.4) #827F6B srgb(130,127,107)
4,0: (42221,36875.3,29255.8) #A48F72 srgb(164,143,114)
5,0: (53896.7,44085.9,24988.3) #D2AC61 srgb(210,172,97)
6,0: (45384.3,42509,38801.6) #B1A597 srgb(177,165,151)
7,0: (54519.7,46803.7,37705.7) #D4B693 srgb(212,182,147)
8,0: (56368.6,48645.3,40350) #DBBD9D srgb(219,189,157)
9,0: (58605,50733.4,41256.9) #E4C5A1 srgb(228,197,161)
Now I would like to either convert that or simply just get it in the following format:
#453B28
#685F51
#8A765C
#827F6B
#A48F72
#D2AC61
#B1A597
#D4B693
#DBBD9D
#E4C5A1
Is there a way to just get the hex values without the Rest ?
Thank you
convert ... | tail -n +2 | awk '{ print $3 }'
Use tail to skip the undesired first line of output.
Then use a simple awk program to keep just the 3rd column.
If you only have 10 colours. you may be prepared to put up with this awkwardness:
convert -size 10x1 gradient: -depth 8 -format "%[hex:p{0,0}]\n%[hex:p{1,0}]\n%[hex:p{2,0}]\n%[hex:p{3,0}]\n%[hex:p{4,0}]\n%[hex:p{5,0}]\n%[hex:p{6,0}]\n%[hex:p{7,0}]\n%[hex:p{8,0}]\n%[hex:p{9,0}]\n" info:
FFFFFF
E3E3E3
C6C6C6
AAAAAA
8E8E8E
717171
555555
393939
1C1C1C
000000
Other than that, I don't know of a native way to do as you ask, and Jonathon's method is as good as any. Another option may be like this, where I create a little image with a red, green, blue and white pixel then dump it as RGB and use xxd to format it in columns of 3 for R,G,B:
convert xc:red xc:lime xc:blue xc:white +append -depth 8 rgb: | xxd -p -c3
ff0000 # red
00ff00 # green
0000ff # blue
ffffff # white
I added the colour names for clarification - they don't actually come out of the process.

Find nearest point horizontal in imagemagick?

I'm trying to find the nearest point to the point (red in this case) in this image. In this image this output find the first line point from right
how I could do this
output
Please help me.
This looks fun! Let's dump the image to text using ImageMagick:
convert image.png txt:
# ImageMagick pixel enumeration: 337,218,65535,srgb
0,0: (65535,65535,65535) #FFFFFF white
1,0: (65535,65535,65535) #FFFFFF white
2,0: (65535,65535,65535) #FFFFFF white
3,0: (65535,65535,65535) #FFFFFF white
4,0: (65535,65535,65535) #FFFFFF white
...
...
221,79: (0,0,0) #000000 black
221,80: (0,0,0) #000000 black
221,81: (0,0,0) #000000 black
221,82: (0,0,0) #000000 black
...
...
Ok, now let's use awk to find all black pixels and print their (x,y) coordinates:
convert image.png txt: | awk -F'[,:]' '/black/{x=$1;y=$2;print x,y}'
221 79
221 80
221 81
221 82
221 83
221 84
...
...
Ok, now let's tell awk where the red pixel is by passing in rx (red x-coordinate) and ry (red y-coordinate). Then also, calculate the sum of the squares of the x-distance and y-distance from red to each black pixel. When it is less (i.e. nearer) than any seen so far, save the location. Print the nearest location at the end.
convert image.png txt: | awk -F'[,:]' -v rx=318 -v ry=127 '
BEGIN{m=999999}
/black/{
x=$1; y=$2; d2=(rx-x)*(rx-x)+(ry-y)*(ry-y)
if(d2<m){m=d2;xm=x;ym=y}
}
END{print xm,ym}'
277 127
So, that is the answer... (277,127). Let's check it by drawing a cyan circle there:
convert image.png -fill cyan -draw "circle 277,127 277,132" check.png
On re-reading the question, I note that you actually only want the horizontally closest point whereas my solution above caters for the general case in any direction. If you just want horizontal offset, and you know the horizontal line is at y-coordinate 127, you can just extract that specific row from the image and simplify things like this:
convert image.png -crop x1+0+127 txt: | awk -F'[,:]' -v rx=318 '
BEGIN{m=999999} /black/{x=$1;d=(rx-x)*(rx-x);if(d<m){m=d;xm=x}} END{print xm}'
277
If you don't like awk, you can just do it by eyeball...
convert image.png -crop x1+0+127 txt: | grep -E "black|red"
221,0: (0,0,0) #000000 black
277,0: (0,0,0) #000000 black <--- nearest black to red
314,0: (65535,0,0) #FF0000 red
315,0: (65535,0,0) #FF0000 red
316,0: (65535,0,0) #FF0000 red
317,0: (65535,0,0) #FF0000 red
318,0: (65535,0,0) #FF0000 red
319,0: (65535,0,0) #FF0000 red
320,0: (65535,0,0) #FF0000 red
How did I find the coordinates of the red pixel? I used ImageMagick's sub-image search looking for a red pixel like this:
compare -metric rmse -subimage-search -dissimilarity-threshold 1 image.png \( -size 1x1 xc:red \) null:
0 (0) # 317,121
Notes:
I just used the sum of the squares rather than the square root of the sum of the squares because it is computationally faster and the results are the same because it holds that if a>b, then a * a > b * b in this case.
I used slightly different rx and ry from those generated by the sub-image search because OP says he had the coordinates and the ones found by sub-image search don't find the exact centre of the rather large red blob, but instead the top-leftmost edge of the red blob.

Find the first black pixel in every row with ImageMagick

For every row in an image, I would like to find the first black (or first non-white) pixel in that row. For example, for an image like this:
I would expect output like:
0
1
0
Or something close to that that I can parse. I think there might be a way to do this with subimage-search but I don't quite know how. Any pointers?
You do NOT need subimage-search to achieve your goal. The problem can be reduced to text parsing.
1. Basics
Consider this: you can tell ImageMagick to convert any image to a textual representation, which holds the exact color information for each individual pixel. Example:
convert wizard: textwizard.txt
(wizard: is a builtin image available for all ImageMagick installations for testing purposes.)
Yes, it is that easy! This image "format" is requested by just adding a .txt suffix. Results:
# ImageMagick pixel enumeration: 480,640,255,srgb
0,0: (255,255,255) #FFFFFF white
1,0: (255,255,255) #FFFFFF white
2,0: (255,255,255) #FFFFFF white
[....]
47,638: (246,247,249) #F6F7F9 srgb(246,247,249)
48,638: (246,247,249) #F6F7F9 srgb(246,247,249)
47,639: (236,235,236) #ECEBEC srgb(236,235,236)
48,639: (230,228,218) #E6E4DA srgb(230,228,218)
[....]
476,639: (255,255,255) #FFFFFF white
477,639: (255,255,255) #FFFFFF white
478,639: (255,255,255) #FFFFFF white
479,639: (255,255,255) #FFFFFF white
If you look at the first line of the output, you'll notice that ImageMagick uses it to detail some special info about the image here:
# ImageMagick pixel enumeration: 480,640,255,srgb
It means:
the image is 480 pixels wide,
the image is 640 pixels high,
the image uses a range of 0-255 for color info per channel (that is equivalent to an 8-bit color depth),
the image is build in the sRGB color space
The other lines consist of 4 columns:
the first column in format (N,M) indicates the exact position of the respective pixels as (row_number,column_number). (The index for row and column numbers is zero-based -- row no. 1 is indicated as 0, no. 2 as 1.)
the other three columns, redundantly, each hold the exact same information, each in a different notation: the exact color value for the pixel given in column 1. (The last column will use a human-readable name if ImageMagick knows one for that color value...)
As a side note: you can use such a textual representation of the original image (with or without some extra modifications) to re-create a real image:
convert textwizard.txt wizard.jpg
2. Select a specific row
You should be aware that you can select a specific region of an image with the following syntax:
image.png[WIDTHxHEIGHT+X_OFFSET+Y_OFFSET]
So to select a specific row only, you can set HEIGHT as 1. To get any row completely, set X-OFFSET as 0. To get a specific row, set the Y-OFFSET accordingly.
In order to get the values (for the builtin wizard: image used above) for the row with index 47, we can do:
convert wizard:[640x1+0+47] row47.txt
cat row47.txt
# ImageMagick pixel enumeration: 480,1,255,srgb
0,0: (255,255,255) #FFFFFF white
1,0: (255,255,255) #FFFFFF white
2,0: (255,255,255) #FFFFFF white
[....]
428,0: (82,77,74) #524D4A srgb(82,77,74)
429,0: (169,167,168) #A9A7A8 srgb(169,167,168)
430,0: (232,231,228) #E8E7E4 srgb(232,231,228)
432,0: (246,247,249) #F6F7F9 srgb(246,247,249)
[....]
476,0: (255,255,255) #FFFFFF white
477,0: (255,255,255) #FFFFFF white
478,0: (255,255,255) #FFFFFF white
479,0: (255,255,255) #FFFFFF white
If you do not want the textual output in a file, but printed on the standard output channel, you can do this:
convert wizard:[480x1+0+47] txt:-
3. Stitching it all together
Based on above snippets of information, the approach that can be taken with this task is clear:
Loop through all pixel rows of the image.
Output each pixel's color value as text.
Look for the first non-white pixel and keep its location information.
4. Possible script (OS X, Linux, Unix)
Here is a main part of a Bash script that could be used:
# Define some image specific variables (width, height, ...)
image=${1}
number_of_columns=$(identify -format '%W' ${image})
width=${number_of_columns} # just an alias
number_of_rows=$(identify -format '%H' ${image})
height=${number_of_rows} # just an alias
max_of_indices=$(( ${height} -1 ))
# Loop through all rows and grep for first non-white pixel
for i in $(seq 0 ${max_of_indices}); do
echo -n "Row ${i} : " ;
convert ${image}[${width}x1+0+${i}] txt:- \
| grep -v enumeration \
| grep -v '#FFFFFF' -m 1 \
|| echo "All WHITE pixels in row!"
done
The -v white will de-select all lines which contain the string white.
The -m 1 parameter will return the maximum of 1 matches (i.e. the first match).
It will be slow, but it will work.
I would go with something like this using the built-in checkerboard pattern:
convert -size 100x100 pattern:checkerboard -auto-level board.png
#!/bin/bash
convert wizard: txt: | awk -F'[,: ]' '
/^#/ || /#FFFFFF/ {next}
!($2 in fb) {fb[$2]=$1}
END {r=$2;for(i=0;i<=r;i++){if(i in fb)print i,fb[i]; else print i,"-1"}}'
The -F[,: ] tells awk to split the words on the line by commas, colons or spaces - this helps me get at the row and column at the start of each line. The line with /^#/ skips the comment in the first line of ImageMagick text output and all lines that contain white or #FFFFFF.
Then, I have an array fb[] , indexed by image row, that holds the column of the first black pixel on each row. Each time I find a line with a row not in my array fb[], I save it in the array.
At the end, inside END{}, I run through fb[] printing all rows and indices of first black pixels in those rows. Note that I output -1 in place of any undefined elements (i.e. those with no non-white pixels) - thanks to #KurtPfeifle for the hint.

How to create PNG image with color_type=3 and bit_depth=1

The source is an RGBA PNG image (color_type=6 and bit_depth=8).
I need an image with indexed color and 2 palette items (color_type=3, bit_depth=1).
I tried with ImageMagick, but was able to reach only 1bit grayscale image (color_type=0, bit_depth=1) or 2bit indexed color image (color_type=3, bit_depth=2) in which only 2 colors are in use.
According to the PNG specification such image is possible, but how to create it?
Here is an image I am trying to convert:
The result of "convert input.png -type palette -depth 1 output.png":
The result of "convert input.png -type palette -depth 1 -colors 2 output.png"
Both results have bit_depth=2, but the second one uses only 2 colors of 4 possible.
Use one of the following 3, ordered from least to most ancillary tags in the output file:
convert -colors 2 -define png:include-chunk=none -verbose input.png output.png
 
convert -colors 2 -define png:exclude-chunk=bkgd -verbose input.png output.png
 
convert -colors 2 -background #000000 -verbose input.png output.png
The root of this problem is that ImageMagick by default tags the image with a white background color (bKGD) PNG chunk (unnecessarily I'd say), and then adds this color to the palette if it's not already there, even if the image has no pixels of that color. Your particular image doesn't have white after converting to 2-color, so the unneeded background color tag becomes a 3rd color and it can no longer be saved as a 1-bit indexed color image. See also this from the author of IM.
The reason others have failed to reproduce the problem is probably that they've tested with images where one of the 2 colors happened to be white.
The 1st option with -define png:include-chunk=none avoids the problem by not outputting any ancillary PNG chunks at all (e.g. bKGD, gAMA, cHRM, tEXt, tIME). Like pngcrush -rem alla. I'd prefer this for a less cluttered file (without it IM will add a few of these even if they weren't in the input file). Note: There's also the simple -strip option which should avoid most of these, but as of v6.9.3 it won't cut bKGD due to a bug.
The 2nd with -define png:exclude-chunk=bkgd removes only the offending background chunk.
The 3rd option with -background #000000 retains all ancillary PNG chunks including bKGD but sets it black, one of the 2 colors present in your image, so it can still be 1-bit. Adjust the color for an image with neither white nor black in it.
Note that for all these I'm including the -verbose switch. Not overly verbose; just goes from zero to two lines of status output where you'll notice a "2c" if the image stayed 2-color or a "3c" if not. It will also tell you if the palette reduction was lossy or not. It also outputs "8-bit sRGB" for most images, even paletted ones with fewer than 8 bits-per-pixel; that's not a bug as in your comment to another answer #johnfound, this refers not to the bits-per-pixel but to the bits-per-color component. It can be more than 8-bit for (rare) deep color images.
convert input.png -background white -type palette -depth 1 -colors 2 output.png
works for me.
(Why -depth=1 is not enough? No idea.)
BTW, the tweakpng tool is useful for checking this kind of things.
Update: if the images have transparency, you might play safer by removing it explicitly:
convert input.png -background white -flatten -type palette -depth 1 -colors 2 output.png
(You can replace white for your preference)
I think the command you need is this:
convert input.png -type palette -depth 1 output.png
If not, please post your input image and say how/where you find the color_type and bit_depth fields you refer to.
Exiftool tells me this:
ExifTool Version Number : 9.76
File Name : output.png
Directory : .
File Size : 2.3 kB
File Modification Date/Time : 2015:01:10 19:20:33+00:00
File Access Date/Time : 2015:01:10 19:21:46+00:00
File Inode Change Date/Time : 2015:01:10 19:20:33+00:00
File Permissions : rw-r--r--
File Type : PNG
MIME Type : image/png
Image Width : 640
Image Height : 427
Bit Depth : 1
Color Type : Grayscale
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Gamma : 2.2
Background Color : 1
Datecreate : 2015-01-10T10:21:30+00:00
Datemodify : 2015-01-10T10:21:30+00:00
Image Size : 640x427
ImageMagick identify tells me this:
Image: output.png
Format: PNG (Portable Network Graphics)
Mime type: image/png
Class: PseudoClass
Geometry: 640x427+0+0
Units: Undefined
Type: Bilevel
Base type: Bilevel
Endianess: Undefined
Colorspace: Gray
Depth: 8/1-bit
Channel depth:
gray: 1-bit
Channel statistics:
Pixels: 273280
Gray:
min: 0 (0)
max: 255 (1)
mean: 1.90168 (0.00745755)
standard deviation: 21.9388 (0.0860345)
kurtosis: 129.1
skewness: 11.4499
Colors: 2
Histogram:
271242: ( 0, 0, 0) #000000 gray(0)
2038: (255,255,255) #FFFFFF gray(255)
Colormap entries: 2
Colormap:
0: ( 0, 0, 0) #000000 gray(0)
1: (255,255,255) #FFFFFF gray(255)
Rendering intent: Undefined
Gamma: 0.45455
Background color: gray(255)
Border color: gray(223)
Matte color: gray(189)
Transparent color: gray(0)
Interlace: None
Intensity: Undefined
Compose: Over
Page geometry: 640x427+0+0
Dispose: Undefined
Iterations: 0
Compression: Zip
Orientation: Undefined
Properties:
date:create: 2015-01-10T19:20:33+00:00
date:modify: 2015-01-10T19:20:33+00:00
png:bKGD: chunk was found (see Background color, above)
png:gAMA: gamma=0.45455 (See Gamma, above)
png:IHDR.bit-depth-orig: 1
png:IHDR.bit_depth: 1
png:IHDR.color-type-orig: 0
png:IHDR.color_type: 0 (Grayscale)
png:IHDR.interlace_method: 0 (Not interlaced)
png:IHDR.width,height: 640, 427
png:text: 2 tEXt/zTXt/iTXt chunks were found
signature: 3e08d7fea7bc7aeb0659ac2e2696084083d35ce30b0e3075dc561ad94259eaec
Artifacts:
filename: output.png
verbose: true
Tainted: True
Filesize: 2.33KB
Number pixels: 273K
Pixels per second: 27.33MB
User time: 0.000u
Elapsed time: 0:01.009
Version: ImageMagick 6.8.9-8 Q16 x86_64 2014-11-10 http://www.imagemagick.org

Resources