Fred's ImageMagick Tidbits


 



Image Processing



Enabling The Alpha Channel

Default Enable Full Transparency (Zero Opacity)

  • Requires a format for the output that supports transparency
  • PNG will support alpha varying continuously from fully opaque to fullytransparent
  • GIF will support, in general, only binary alpha, namely part fully opaque and part fully transparent
  • JPG does not support transparency

NOTE: Both -channel a and -channel o actually set transparency in later IM releases.
However, in early releases both -channel a and -channel o actually set opacity.

convert logo.jpg -channel rgba -alpha set logo.png

Specify 40% Transparency (60% Opacity)

  • Specify transparency amount
  • Specify image
  • clone, threshold to solid white, change white to graylevel at transparency level
  • then merge second image as alpha channel

transparency=40
convert logo.jpg \
\( +clone -threshold -1 -fill gray$transparency -opaque white \) \
-alpha off -compose copy_opacity -composite logo_transparency40.png

or

transparency=40
convert logo.jpg -alpha set -channel a -evaluate set $transparency% logo_transparency40.png

or

opacity=60
transparency=`convert xc: -format "%[fx:100-opacity]" info:`
convert logo.jpg -alpha set -channel a -evaluate set $transparency% logo_opacity60.png

top



Converting All Pixels Of A Given Color To Transparent

Opaque Image

Method 1:

convert logo.jpg -transparent white logo_transparent1.gif

Method 2:

convert logo.jpg -fuzz 5% -transparent white logo_transparent2.gif

top



Re-coloring Transparency

Transparent Image

Convert Transparent To Color (Wheat)

convert logo_transparent2.gif -channel rgba -fill wheat -opaque none logo_transparent_wheat.gif

or

convert logo_transparent2.gif -background wheat -flatten logo_transparent_wheat.gif

top



Re-coloring All Pixels With Same Color As At Some Location

Opaque Image

Change All Pixels With Color At Location 10,10 To Red

convert cyclops.gif -channel rgba -alpha set -fill red -draw 'color 10,10 replace' cyclops_red.gif

Change All Pixels With Color At Location 10,10 To Transparent

convert cyclops.gif -channel rgba -alpha set -fill none -draw 'color 10,10 replace' cyclops_transparent.gif

top



Re-coloring All Pixels Within Some Block

Opaque Image

Change Color Of All Pixels Within Block 5,5 to 25,15 To Red

convert cyclops.gif -fx "i>=5&&i<=25&&j>=5&&j<=15?red:u" cyclops_block_red.gif

or

convert cyclops.gif -draw "fill red rectangle 5,5 25,15" cyclops_block_red.gif

Change All Pixels With Color At Location 5,5 to 25,15 To Transparent

convert cyclops.gif -alpha set -channel RGBA -fx "i>=5&&i<=25&&j>=5&&j<=15?none:u" cyclops_block_transparent.gif

or (recolor area red, then floodfill to transparent)

convert cyclops.gif -draw "fill red rectangle 5,5 25,15 fill none matte 10,10 floodfill" cyclops_block_transparent.gif

  • Note: method can be used to change selected pixels, also

top



Inserting A Transparent Hole

Opaque Image

Method 1:

convert logo.jpg -size 25x25 xc:none -alpha set \
-geometry +25+75 -compose copy -composite logo_hole1.gif

Method 2:

convert logo.jpg -size 25x25 xc:white -alpha set \
-geometry +25+75 -compose dst_out -composite logo_hole2.gif

top



Preserve Multiple Colors And Change The Rest

Original Image

convert -size 50x50 xc:red xc:magenta xc:green1 xc:cyan xc:blue +append rmgcb.png

Preserve Red And Blue And Change The Rest To Yellow

convert rmgcb.png -alpha off \( -clone 0 -transparent red -transparent blue -alpha extract -fill yellow -opaque white -transparent black \) \
-compose over -composite rmgcb_red_yellow_blue.png

Preserve Red And Blue And Change The Rest To Transparent

convert rmgcb.png -alpha off \( -clone 0 -transparent red -transparent blue -alpha extract -negate \) \
-compose copy_opacity -composite rmgcb_red_none_blue.png

or (Improved By Anthony Thyssen)

convert rmgcb.png -alpha off -channel A -transparent red -transparent blue -negate +channel rmgcb_red_none_blue.png

top



Gradient Transparency

Opaque Image

Add Gradient Transparency

ww=`convert rainbow_square.jpg -format %w info:`
hh=`convert rainbow_square.jpg -format %h info:`
convert rainbow_square.jpg \( -size ${ww}x${hh} gradient: \) \
-alpha off -compose copy_opacity -composite rainbow_square_grad_trans.png

top



Converting Color Images To Grayscale

Method 1: Gray, Grayscale, Rec601Luma or Intensity (all the same)

gray = 0.299*red+0.587*green+0.114*blue

convert gradient.gif -colorspace gray gradient_gray.gif

or

convert gradient.gif -type grayscale gradient_grayscale.gif

or

convert gradient.gif -colorspace rec601luma gradient_rec601luma.gif

or

convert gradient.gif -fx "intensity" gradient_intensity.gif

   Histogram

compare -metric rmse gradient_gray.gif gradient_grayscale.gif null:

0 (0)

compare -metric rmse gradient_gray.gif gradient_rec601luma.gif null:

0 (0)

compare -metric rmse gradient_gray.gif gradient_intensity.gif null:

0 (0)

Method 2: Rec709Luma and Luminance (both the same)

gray: 0.2126*red + 0.7152*green + 0.0722*blue

convert gradient.gif -colorspace rec709luma gradient_rec709luma.gif

or

convert gradient.gif -fx "luminance" gradient_luminance.gif

   Histogram

compare -metric rmse gradient_rec709luma.gif gradient_luminance.gif null:

0 (0)

Method 3: OHTA

gray: 0.33333*red + 0.33334*green + 0.33333*blue

convert gradient.gif -colorspace ohta -channel red -separate gradient_ohta.gif

Method 4: Ligntness

gray: (max(red,green,blue) + min(red,green,blue))/2

convert gradient.gif -colorspace hsl -channel blue -separate gradient_lightness.gif

or

convert gradient.gif -fx "lightness" gradient_fx_lightness.gif

compare -metric rmse gradient_lightness.gif gradient_fx_lightness.gif null:

0 (0)

Method 5: Brightness

gray: max(red,green,blue)

convert gradient.gif -colorspace hsb -channel blue -separate gradient_brightness.gif

top



Stretch Colors To Full Range Of Values

Original Image

Get min and max stats for each channel

  • method requires IM 6.4.0-11 or higher due to use of fx escape image.minima and image.maxima
  • as of IM 6.4.2-6 image.minima and image.maxima may be shortened to just minima and maxima
  • alternate is to use grayscale methods on each channel

convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:

17219
19275
18247

convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:

53456
52428
49858

Method 1: -contrast-stretch: Histogram Stretch All Channels In Concert

  • limitation: groups data in bins
  • limitation: stretches based upon intensity of all channels mixed together
  • result: some data may be clipped in each channel even for channels that are stretched to black or white
  • as of IM 6.4.2-6 image.minima and image.maxima may be shortened to just minima and maxima

convert image26s.jpg -contrast-stretch 0 image26s_stretch1.png

Get min and max stats for each channel

convert image26s_stretch1.png -separate -format "%[fx:quantumrange*image.minima]" info:

0
1028
0

convert image26s_stretch1.png -separate -format "%[fx:quantumrange*image.maxima]" info:

65535
65535
60652

Method 2: -contrast-stretch: Histogram Stretch Each Channel Separately

  • limitation: groups data in bins
  • limitation: stretches each channel separately so color shift is possible
  • result: some data may be clipped in each channel due to use of histogram bins
  • as of IM 6.4.2-6 image.minima and image.maxima may be shortened to just minima and maxima

convert image26s.jpg -separate -contrast-stretch 0 -combine image26s_stretch2.png

Get min and max stats for each channel

convert image26s_stretch2.png -separate -format "%[fx:quantumrange*image.minima]" info:

0
0
0

convert image26s_stretch2.png -separate -format "%[fx:quantumrange*image.maxima]" info:

65535
65535
65535

Method 3: -level: Stretch All Channels In Concert Based Upon Largest Of Min Channel Values and Smallest Of Max Channel Values

  • result: some data may be clipped in any channel whose min is not the largest min and whose max is not the smallest max
  • as of IM 6.4.2-6 image.minima and image.maxima may be shortened to just minima and maxima

minr=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:) | cut -d\ -f1`
ming=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:) | cut -d\ -f2`
minb=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:) | cut -d\ -f3`
max_of_mins=`convert xc: -format "%[fx:max($minr,max($ming,$minb))]" info:`
maxr=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:) | cut -d\ -f1`
maxg=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:) | cut -d\ -f2`
maxb=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:) | cut -d\ -f3`
min_of_maxs=`convert xc: -format "%[fx:min($maxr,min($maxg,$maxb))]" info:`
convert image26s.jpg -level ${max_of_mins},${min_of_maxs} image26s_stretch3.png

Get min and max stats for each channel

convert image26s_stretch3.png -separate -format "%[fx:quantumrange*image.minima]" info:

0
0
0

convert image26s_stretch3.png -separate -format "%[fx:quantumrange*image.maxima]" info:

65535
65535
65535

Method 4: -level: Stretch All Channels In Concert Based Upon Smallest Of Min Channel Values and Largest Of Max Channel Values

  • result: some channels may not stretch to black and white, but no data will be clipped
  • as of IM 6.4.2-6 image.minima and image.maxima may be shortened to just minima and maxima

minr=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:) | cut -d\ -f1`
ming=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:) | cut -d\ -f2`
minb=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.minima]" info:) | cut -d\ -f3`
min_of_mins=`convert xc: -format "%[fx:min($minr,min($ming,$minb))]" info:`
maxr=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:) | cut -d\ -f1`
maxg=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:) | cut -d\ -f2`
maxb=`echo $(convert image26s.jpg -separate -format "%[fx:quantumrange*image.maxima]" info:) | cut -d\ -f3`
max_of_maxs=`convert xc: -format "%[fx:max($maxr,max($maxg,$maxb))]" info:`
convert image26s.jpg -level ${min_of_mins},${max_of_maxs} image26s_stretch4.png
convert image26s_stretch4.png -separate -format "%[fx:quantumrange*image.minima]" info:

Get min and max stats for each channel

convert image26s_stretch4.png -separate -format "%[fx:quantumrange*image.minima]" info:

0
3598
1799

convert image26s_stretch4.png -separate -format "%[fx:quantumrange*image.maxima]" info:

65535
63736
59110

Method 5: -level: Stretch All Channels Separately Based Upon Each Min And Max Channel Values

  • result: some color shift may occur, but no data will be clipped
  • requires IM 6.4.2.1 or higher due to use of -level-colors
  • as of IM 6.4.2-6 image.minima and image.maxima may be shortened to just minima and maxima

minr=`echo $(convert image26s.jpg -separate -format "%[fx:100*image.minima]%%" info:) | cut -d\ -f1`
ming=`echo $(convert image26s.jpg -separate -format "%[fx:100*image.minima]%%" info:) | cut -d\ -f2`
minb=`echo $(convert image26s.jpg -separate -format "%[fx:100*image.minima]%%" info:) | cut -d\ -f3`
maxr=`echo $(convert image26s.jpg -separate -format "%[fx:100*image.maxima]%%" info:) | cut -d\ -f1`
maxg=`echo $(convert image26s.jpg -separate -format "%[fx:100*image.maxima]%%" info:) | cut -d\ -f2`
maxb=`echo $(convert image26s.jpg -separate -format "%[fx:100*image.maxima]%%" info:) | cut -d\ -f3`
min="rgb($minr,$ming,$minb)"
max="rgb($maxr,$maxg,$maxb)"
convert image26s.jpg -level-colors ${min}-${max} image26s_stretch5.png

Get min and max stats for each channel

convert image26s_stretch5.png -separate -format "%[fx:quantumrange*image.minima]" info:

0
0
0

convert image26s_stretch5.png -separate -format "%[fx:quantumrange*image.maxima]" info:

65535
65535
65535

top



Pad Or Crop To Square Dimensions

Image

Pad:

convert monet3.jpg -virtual-pixel background -background white -set option:distort:viewport \
"%[fx:max(w,h)]x%[fx:max(w,h)]-%[fx:max((h-w)/2,0)]-%[fx:max((w-h)/2,0)]" \
-filter point -distort SRT 0 +repage monet3_pad.jpg

Crop:

convert monet3.jpg -virtual-pixel edge -set option:distort:viewport \
"%[fx:min(w,h)]x%[fx:min(w,h)]+%[fx:max((w-h)/2,0)]+%[fx:max((h-w)/2,0)]" \
-filter point -distort SRT 0 +repage monet3_crop.jpg

top



Resizing An Image To Square Shape

Pad To Square The Size Of The Larger Dimension

max=`convert $infile -format "%[fx:max(w,h)]" info:`
convert monet.jpg -background white -gravity center -extent ${max}x${max} monet_padout_max.jpg

Crop To Square The Size Of The Smaller Dimension

min=`convert $infile -format "%[fx:min(w,h)]" info:`
convert monet.jpg -background white -gravity center -extent ${min}x${min} monet_cropout_min.jpg

Resize Larger Dimension And Pad Smaller Dimension To 200x200 Square

convert monet.jpg -resize 200x200 -background white -gravity center -extent 200x200 monet_padout_200.jpg

Resize Smaller Dimension And Crop Larger Dimension To 200x200 Square

convert monet.jpg -resize '200x200^' -background white -gravity center -extent 200x200 monet_cropout_200.jpg

Resize Larger Dimension And Pad Smaller Dimension To 200x200 Square Only If Image Larger Than 200x200

convert monet.jpg -resize '200x200>' -background white -gravity center -extent 200x200 monet_padout2_200.jpg

Resize Smaller Dimension And Crop Larger Dimension To 200x200 Square Only If Image Larger Than 200x200

convert monet.jpg -resize '200x200>^' -background white -gravity center -extent 200x200 monet_cropout2_200.jpg

top



Resize One Image To Match The Size Of Another Image

Resize logo image to match size of cyclops image

convert logo.jpg -resize `identify -ping -format "%wx%h\!" cyclops.jpg` logo_resize_cyclops.jpg

or

convert cyclops.jpg logo.jpg -fx "v.p{i*(v.w/u.w),j*(v.h/u.h)}" logo_resize_cyclops.jpg

top



Crop Image Into 2 Non-Overlapping Equal Sized Parts

Crop Vertically

convert monet.jpg -crop 100%x50% +repage monet_vertical_%d.jpg



Crop Horizontally

convert monet.jpg -crop 50%x100% +repage monet_horizontal_%d.jpg

top



Crop Image Into 4 Non-Overlapping Quadrants

Quadrant Crop

convert mandril_256x256.jpg -crop 128x128 +repage mandril_128x128_%d.jpg





top



Horizontally Appending Images: Top Aligned, Bottom Aligned And Center Aligned

Top Aligned

convert monet.jpg cyclops.jpg -background none +append append_top.png

As Of IM 6.4.7-1, Append Is Gravity Sensitive. Thus The Following Is Equivalent To The Above

convert monet.jpg cyclops.jpg -background none -gravity north +append append_top.png

Bottom Aligned

  • Flip both images
  • Append horizontally
  • Flip result

convert monet.jpg cyclops.jpg -flip -background none +append -flip append_bottom.jpg

As Of IM 6.4.7-1, Append Is Gravity Sensitive. Thus The Following Is Equivalent To The Above

convert monet.jpg cyclops.jpg -background none -gravity south +append append_top.png

Center Aligned
(but can also do top or bottom aligned)
May Be Limited To IM 6.4.6-6 Or Higher Due To -Extent Bug

  • Find Height Of Tallest Image
  • Use -extent In Vertical Direction To Fill Out Height Of Images
  • Append Images

convert monet.jpg cyclops.jpg -format "%h" info:
161
100

convert monet.jpg cyclops.jpg -gravity center -background none -extent x161 +append append_center.png

As Of IM 6.4.7-1, Append Is Gravity Sensitive. Thus The Following Is Equivalent To The Above

convert monet.jpg cyclops.jpg -background none -gravity center +append append_top.png

top



Trimming Background Color On Two Sides Only

Original Image

convert rose: -bordercolor white -border 20 rose_white20.jpg

Trim Only On The Top And Bottom

convert \( rose_white20.jpg -bordercolor white -border 1x0 \) \
-size 1x1 xc:black -gravity west -composite \
-size 1x1 xc:black -gravity east -composite \
-fuzz 10% -trim +repage -bordercolor white -shave 1x0 \
rose_white20_trim_vertical.jpg

Trim Only On The Left And Right

convert \( rose_white20.png -bordercolor white -border 0x1 \) \
-size 1x1 xc:black -gravity north -composite \
-size 1x1 xc:black -gravity south -composite \
-fuzz 10% -trim +repage -bordercolor white -shave 0x1 \
rose_white20_trim_horizontal.jpg

top



Skewing (Shearing) An Image

Image

Prior to IM 6.4.2-7, -affine -transform did not respect the virtual-pixel setting.
Thus, if the output image format supported an alpha channel, then the background
pad due to the skew would be transparent, irrespective of the background color;
otherwise it would be background color. As of IM 6.4.2-7, this has been fixed
along with an artifact that could show when skewing the bottom to the left,
expecially when the background was transparent.

Skew Bottom Edge To Right (SkewX=20 deg)

ang=20
sinang=`convert xc: -format "%[fx:sin($ang*pi/180)]" info:`
convert cyclops.png -affine 1,0,$sinang,1,0,0 -channel rgba -alpha set \
-virtual-pixel transparent -background white -transform \
cyclops_20_degrees_b2r.gif

convert cyclops.png -affine 1,0,$sinang,1,0,0 \
-virtual-pixel white -background white \
-transform cyclops_20_degrees_b2r_white.gif



Skew Bottom Edge To Left (SkewX=-20 deg)

ang=-20
sinang=`convert xc: -format "%[fx:sin($ang*pi/180)]" info:`
convert cyclops.png -affine 1,0,$sinang,1,0,0 -channel rgba -alpha set \
-virtual-pixel transparent -background white \
-transform cyclops_20_degrees_b2l.gif

convert cyclops.png -affine 1,0,$sinang,1,0,0 \
-virtual-pixel white -background white -transform \
cyclops_20_degrees_b2l_white.gif



Skew Right Edge To Bottom (SkewY=20 deg)

ang=20
sinang=`convert xc: -format "%[fx:sin($ang*pi/180)]" info:`
convert cyclops.png -affine 1,$sinang,0,1,0,0 -channel rgba -alpha set \
-virtual-pixel transparent -background white -transform \
cyclops_20_degrees_r2b.gif

convert cyclops.png -affine 1,$sinang,0,1,0,0 \
-virtual-pixel white -background white -transform \
cyclops_20_degrees_r2b_white.gif



Skew Right Edge To Top (SkewY=20 deg)

ang=-20
sinang=`convert xc: -format "%[fx:sin($ang*pi/180)]" info:`
convert cyclops.png -affine 1,$sinang,0,1,0,0 -channel rgba -alpha set \
-virtual-pixel transparent -background white -transform \
cyclops_20_degrees_r2t.gif

convert cyclops.png -affine 1,$sinang,0,1,0,0 \
-virtual-pixel white -background white -transform \
cyclops_20_degrees_r2t_white.gif



top



Fuzzy Color Thresholding

  • define color (blue)
  • define fuzz amount
  • replace all colors not within fuzz range with black
  • replace all colors within fuzz range with white

color="rgb(2,90,164)"
fuzz=5
convert logo.gif -fuzz $fuzz% \
-fill black +opaque "$color" \
-fill white -opaque "$color" \
-colorspace gray logo_fuzzy_thresh.gif

top



Color Range Slicing

Slice Mid 50% Of Each Channel

By Minimum And Maximum Graylevel Percent

min="25"
max="75"
convert gradient.gif -separate \
-black-threshold $min% \
-white-threshold $max% \
-fill black -opaque white \
-combine \
gradient_slice1.gif

Or By Mid Graylevel And Range Percent

mid="50"
range="25"
convert grad_wedge.gif -separate \
-fuzz $range% \
-fill black +opaque "gray($mid%)" \
-combine \
gradient_slice1.gif

Slice Mid 50% Of Each Channel And Intensify

By Minimum And Maximum Graylevel Percent

min="25"
max="75"
convert gradient.gif -separate \
-black-threshold $min% \
-white-threshold $max% \
-fill black -opaque white \
-fill white +opaque black \
-combine \
gradient_slice2.gif

Or By Mid Graylevel And Range Percent

mid="50"
range="25"
convert grad_wedge.gif -separate \
-fuzz $range% \
-fill black +opaque "gray($mid%)" \
-fill white +opaque black \
-combine \
gradient_slice2.gif

Slice Mid 50% Of Each Channel, Intensify And Convert To Grayscale

By Minimum And Maximum Graylevel Percent

min="25"
max="75"
convert gradient.gif -separate \
-black-threshold $min% \
-white-threshold $max% \
-fill black -opaque white \
-fill white +opaque black \
-combine -colorspace gray \
gradient_slice3.gif

Or By Mid Graylevel And Range Percent

mid="50"
range="25"
convert grad_wedge.gif -separate \
-fuzz $range% \
-fill black +opaque "gray($mid%)" \
-fill white +opaque black \
-combine -colorspace gray \
gradient_slice3.gif

top



Grayscale Range Slicing And Thresholding

Slice Mid 50%

By Minimum And Maximum Graylevel Percent

min="25"
max="75"
convert grad.gif \
-black-threshold $min% \
-white-threshold $max% \
-fill black -opaque white \
grad_slice1.gif

Or By Mid Graylevel And Range Percent

mid="50"
range="25"
convert grad.gif \
-fuzz $range% \
-fill black +opaque "gray($mid%)" \
grad_slice1.gif

Threshold Mid 50%

By Minimum And Maximum Graylevel Percent

min="25"
max="75"
convert grad.gif \
-black-threshold $min% \
-white-threshold $max% \
-fill black -opaque white \
-fill white +opaque black \
grad_slice2.gif

Or By Mid Graylevel And Range Percent

mid="50"
range="25"
convert grad.gif \
-fuzz $range% \
-fill black +opaque "gray($mid%)" \
-fill white +opaque black \
grad_slice2.gif

top



Lower Right Offset Composite

convert \( -size 100x100 xc:white \) rose: -gravity southeast -geometry +10+10 rose_white.jpg

(note: +10+10 NOT -10-10)

top



Padding An Image To A Power Of 2 Size

convert rose: -format "%w x %h" info:
70 x 46

Rectangular

p2w=`convert rose: -format "%[fx:2^(ceil(log(w)/log(2)))]" info:`
p2h=`convert rose: -format "%[fx:2^(ceil(log(h)/log(2)))]" info:`

echo "$p2w x $p2h"
128 x 64

convert rose: -background white -gravity center -extent ${p2w}x${p2h} rose_padded.jpg

Square

p2=`convert rose: -format "%[fx:2^(ceil(log(max(w,h))/log(2)))]" info:`

echo "$p"
128

convert rose: -background white -gravity center -extent ${p2}x${p2} rose_padded_sq.jpg


Note: logtwo(x)=log2(x)=log(x)/log(2) added to -fx in IM 6.4.2-2

top



Mirror Framing Of An Image (Unfolding Border Region)

convert cyclops.jpg \( +clone -border 25x25 \) +swap -virtual-pixel mirror -fx "v.p{i-25,j-25}" cyclops_unfold.jpg

or

convert cyclops.jpg -virtual-pixel mirror -set option:distort:viewport 150x150-25-25 -distort SRT 0 +repage cyclops_unfold.jpg

top



Mirror Tiling

convert question.gif \( +clone -flop \) +append \( +clone -flip \) -append -write mpr:tile +delete \
-size 100x100 tile:mpr:tile question_mirror.gif

or

convert question.gif \( +clone -extent 100x100 \) +swap -virtual-pixel mirror -fx "v" question_mirror.gif

or

convert question.gif -virtual-pixel mirror -set option:distort:viewport 100x100 -distort SRT 0 question_mirror.gif

top



Weighted Average Of Multiple Images

Examples Use The Same Image To Verify That They Reproduce The Original

Unequal Weight Of Two Images

composite 33%x67% gradient.gif gradient.gif

33%   + 67% 100%

 

Equal Weighting Of More Than Two Images

convert gradient.gif gradient.gif gradient.gif -average gradient_equal_ave.gif

1/3 + 1/3 + 1/3 100%

 

Unequal Weighting Of More Than Two Images

convert \
\( gradient.gif -evaluate multiply .2 \) \
\( gradient.gif -evaluate multiply .5 \) \
\( gradient.gif -evaluate multiply .3 \) \
-background black -compose plus -flatten gradient_unequal_ave.gif

20% + 50% + 30% 100%

top



Max Or Min Composite Of Two Images

Create Horizontal Mirror Of Gradient

convert gradient.gif -flop gradient_flop.gif

Max Of Gradient And Its Mirror Image

convert gradient.gif gradient_flop.gif -compose lighten -composite gradient_max.gif

max of with  

 

Min Of Gradient And Its Mirror Image

convert gradient.gif gradient_flop.gif -compose darken -composite gradient_min.gif

min of with  

top



Graph To 1D (LUT) Image: Image Intensity Transformation

  • Create transformation as break point pairs in range 0-100 (percent) where x is the input value and y is the output value.
    Here we use "0,20 100,80" which will lower the contrast by 20%. But any number of break points can be supplied
  • Augment the point pairs with 0,0 at start and 100,0 at end so that we can form a complete filled polygon
  • Use gradient.gif image as image to be transformed by 1D Look Up Table image that will be created
  • Optionally draw polyline in red to show user supplied two-point linear transformation and write out a flipped image for display
  • Optionally draw polyline in red to show augmented break point polyline and write out a flipped image for display
  • Draw white filled polygon on black background, crop out the first which represents values at zero so they do not contribute
    (and optionally write out flipped image for display)
  • Do a column average to one row for use as the LUT using -scale (and optionally write it out for display)
  • Apply the LUT to the gradient.gif image using -clut
  • (Note: -clut requires IM 6.3.5.7 or higher; the alternate is to use -fx 'v.p{u*v.w,0}')

plist_user="0,20 100,80"
plist_augmented="0,0 $plist_user 100,0"
convert gradient.gif \
\( -size 101x101 xc:white -fill white -draw "stroke red polyline $plist_user" -flip -write gradient_graph1.gif \) +delete \
\( -size 101x101 xc:white -fill white -draw "stroke red polyline $plist_augmented" -flip -write gradient_graph2.gif \) +delete \
\( -size 101x101 xc:black -fill white -draw "polygon $plist_augmented" \
-crop 101x100+0+1 +repage -flip -write gradient_graph3.gif \
-scale 101x1! -write gradient_lut.png \) \
-clut gradient_low_contrast.gif

LUT Creation: ( )
  linear
transform
 
augmented
polyline
 
filled
polygon
 
LUT
(expanded
to 20 rows)


Apply LUT To Image:    + 

top



1D (Row) Image To Graph: Row Profile

  • Create 100x1 grayscale gradient and apply -gamma 2 transformation to it to create the single row image.
    (note: any row of an image can be used)

convert -size 1x101 gradient: -rotate 90 -gamma 2 gradient_gamma2.gif

  • Define a scaling fraction to convert 8-bit data to range 0 to 100
  • Convert the row of pixels into list of text values using NetPBM PGM ASCII image format, setting 8-bit depth,
    scaling the data to the range 0 to 100 and removing the first 3 header lines of the pgm data
  • Define empty point-pair list
  • Initialize point-pair x values to 0
  • Use "for loop" to create list of x,y point-pairs, one for each pixel in the 101x1 row image
  • Draw graph using list of point-pairs
  • (note: for floating point accuracy, remove -evaluate multipy $frac and insert
    y=`echo "scale=1; "100*$y/255" | bc` into the beginning of the "for loop")

frac=`convert xc: -format "%[fx:100/255]" info:`
pixvals=`convert gradient_gamma2.gif -depth 8 -evaluate multiply $frac -compress none pgm:- | sed '1,3d'`
plist=""
x=0
for y in $pixvals; do
plist="$plist $x,$y"
x=`expr $x + 1`
done
convert -size 101x101 xc:white -fill white -draw "stroke red polyline $plist" -flip gradient_gamma2_graph.gif

Note: LUT expanded to 20 rows for display

top



1D And 2D Equal Weight (Averaging) Convolutions

Create Test Image

convert -size 100x100 xc:white -bordercolor black -border 28 white_in_black.jpg

2D Uniform Weight 11x11 Convolution Using -convolve:

  • uniform weight convolution produces linear ramp centered on edge of white area

ave11="
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1
"
ave11=`echo $ave11 | sed -n 's/ //'pg` #removes spaces introduced by new lines
convert white_in_black.jpg -convolve $ave11 white_in_black_ave11.jpg


2D Gaussian Weight 11x11 Convolution Using -blur radius,sigma:

  • -blur radius,sigma -- produces gaussian weight filter and gaussian curved ramped sides
  • if radius set to 0, then radius will be set approximately to 3xsigma
  • thus use radius=0 and sigma=11/3=3.7

convert white_in_black.jpg -blur 0,3.7 white_in_black_blur0x3p7.jpg


2D Equal Weight 11x11 Convolution Using -blur radius,sigma:

  • -blur radius,sigma -- produces gaussian weight filter
  • if sigma>>radius, then the filter will be uniform weight and sides of result linearly ramped
  • thus use radius=(floor(11/2))=5 and sigma=65535 to achieve 11x11 equal weight filter

convert white_in_black.jpg -blur 5,65535 white_in_black_blur5x65535.jpg


1D (Horizontal Only) Equal Weight 11x1 Convolution Using -motion-blur radius,sigma:

  • -blur radius,sigma -- produces gaussian weight filter
  • if sigma>>radius, then the filter will be uniform weight and sides of result linearly ramped
  • but motion-blur is asymmetric -- thus it is not centered

convert white_in_black.jpg -motion-blur 5,65535 white_in_black_motion_blur_5x65535.jpg


  • repeat but shift back so properly centered using -roll

convert white_in_black.jpg -motion-blur 5,65535 -roll +5+0 white_in_black_motion_blur_5x65535_roll5.jpg


top



Mean And Standard Deviation Filtering

Method 1: Use -convolve For 3x3 Equal Weight Average As Mean

Mean (Average): Blurs

convert cyclops.png -convolve "1,1,1,1,1,1,1,1,1" cyclops_mean1.png

 

Standard Deviation = sqrt( average of squared image - square of averaged image ): Extracts Edges

  • multiply mean by itself
  • multiply image by itself then take mean
  • subtract and take square root, then stretch to full range of values
  • note: -evaluate pow is only available since IM 6.4.1.9; alternate is -gamma 2
  • convert \
    \( cyclops_mean1.png cyclops_mean1.png -compose multiply -composite \) \
    \( cyclops.png cyclops.png -compose multiply -composite -convolve "1,1,1,1,1,1,1,1,1" \) \
    -compose minus -composite -evaluate pow 0.5 -contrast-stretch 0 cyclops_std1.png

    with  

Method 2: Use -blur 0xsigma (sigma=1) For Approximately The Same But Weighted Average For Mean

(note: sigma can be increased/decreased to make more/less blurring and broader/narrower edges)

Mean (Average): Blurs

convert cyclops.png -blur 0x1 cyclops_mean2.png

 

Standard Deviation = sqrt( average of squared image - square of averaged image ): Extracts Edges

  • multiply mean by itself
  • multiply image by itself then take mean
  • subtract and take square root, then stretch to full range of values
  • note: -evaluate pow is only available since IM 6.4.1.9; alternate is -gamma 2
  • convert \
    \( cyclops_mean2.png cyclops_mean2.png -compose multiply -composite \) \
    \( cyclops.png cyclops.png -compose multiply -composite -blur 0x1 \) \
    -compose minus -composite -evaluate pow 0.5 -contrast-stretch 0 cyclops_std2.png

    with  

top



Antialiased Image Composite Along Image Boundary

Method 1: Linear Ramp Blending

  • Define fraction of image to ramp inward from each edge of the image
  • Specify overlay image
  • Create a grayscale mask ramped from black at the left edge to white at the given fraction inward and save in memory
  • Horizontally mirror the left edge mask to make the right edge mask and save in memory
  • Create a grayscale mask ramped from black at the top edge to white at the given fraction inward and save in memory
  • Horizontally mirror the left edge mask to make the right edge mask and save in memory
  • Composite the first and second ramped (in memory) masks
  • Composite the previous result with the third ramped mask
  • Composite the previous result with the fourth ramped mask to make the final ramped mask image \
    (and optionally save for display and generating a profile)
  • Add the mask as an alpha channel to the overlay image
  • Specify the background image and composite with the modified overlay image

ff=.3
convert flower_yellow.jpg \
\( -clone 0 -monitor -fx "min(1,(i/($ff*(w-1))))" -write mpr:tmp0 +delete \) \
\( mpr:tmp0 -flop -write mpr:tmp1 \) \
\( -clone 0 -monitor -fx "min(1,(j/($ff*(h-1))))" -write mpr:tmp2 +delete \) \
\( mpr:tmp2 -flip -write mpr:tmp3 \) \
\( mpr:tmp0 mpr:tmp1 -compose multiply -composite \
mpr:tmp2 -compose multiply -composite \
mpr:tmp3 -compose multiply -composite -write flower_linear_mask.jpg \) \
-compose copy_opacity -composite \
flower_pink.jpg +swap -gravity center -composite flowers_linear.jpg

background image

overlay
image

mask image

( )

profile of center
row of mask


composited image

Method 2: Partial Cosine Ramp Blending

  • Define fraction of image to ramp inward from each edge of the image
  • Specify overlay image
  • Create a grayscale mask ramped from black at the left edge to white at the given fraction inward and save in memory
  • Horizontally mirror the left edge mask to make the right edge mask and save in memory
  • Create a grayscale mask ramped from black at the top edge to white at the given fraction inward and save in memory
  • Horizontally mirror the left edge mask to make the right edge mask and save in memory
  • Composite the first and second ramped (in memory) masks
  • Composite the previous result with the third ramped mask
  • Composite the previous result with the fourth ramped mask to make the final ramped mask image \
    (and optionally save for display and generating a profile)
  • Add the mask as an alpha channel to the overlay image
  • Specify the background image and composite with the modified overlay image

ff=.3
convert flower_yellow.jpg \
\( -clone 0 -monitor -fx "cos((pi/2)*max(0,(1-i/($ff*(w-1)))))" -write mpr:tmp0 +delete \) \
\( mpr:tmp0 -flop -write mpr:tmp1 \) \
\( -clone 0 -monitor -fx "cos((pi/2)*max(0,(1-j/($ff*(h-1)))))" -write mpr:tmp2 +delete \) \
\( mpr:tmp2 -flip -write mpr:tmp3 \) \
\( mpr:tmp0 mpr:tmp1 -compose multiply -composite \
mpr:tmp2 -compose multiply -composite \
mpr:tmp3 -compose multiply -composite -write flower_cosine1_mask.jpg \) \
-compose copy_opacity -composite \
flower_pink.jpg +swap -gravity center -composite flowers_cosine1.jpg

background image

overlay
image

mask image

( )

profile of center
row of mask


composited image

Method 3: Full Cosine Ramp Blending

  • Define fraction of image to ramp inward from each edge of the image
  • Specify overlay image
  • Create a grayscale mask ramped from black at the left edge to white at the given fraction inward and save in memory
  • Horizontally mirror the left edge mask to make the right edge mask and save in memory
  • Create a grayscale mask ramped from black at the top edge to white at the given fraction inward and save in memory
  • Horizontally mirror the left edge mask to make the right edge mask and save in memory
  • Composite the first and second ramped (in memory) masks
  • Composite the previous result with the third ramped mask
  • Composite the previous result with the fourth ramped mask to make the final ramped mask image \
    (and optionally save for display and generating a profile)
  • Add the mask as an alpha channel to the overlay image
  • Specify the background image and composite with the modified overlay image

ff=.3
convert flower_yellow.jpg \
\( -clone 0 -monitor -fx "0.5*cos(pi*max(0,(1-i/($ff*(w-1)))))+0.5" -write mpr:tmp0 +delete \) \
\( mpr:tmp0 -flop -write mpr:tmp1 \) \
\( -clone 0 -monitor -fx "0.5*cos(pi*max(0,(1-j/($ff*(h-1)))))+0.5" -write mpr:tmp2 +delete \) \
\( mpr:tmp2 -flip -write mpr:tmp3 \) \
\( mpr:tmp0 mpr:tmp1 -compose multiply -composite \
mpr:tmp2 -compose multiply -composite \
mpr:tmp3 -compose multiply -composite -write flower_cosine1_mask.jpg \) \
-compose copy_opacity -composite \
flower_pink.jpg +swap -gravity center -composite flowers_cosine1.jpg

background image

overlay
image

mask image

( )

profile of center
row of mask


composited image

Method 4: Linear Radial Ramp Blending

  • Define fraction of image to ramp inward from each edge of the image
  • Specify overlay image
  • Create a grayscale mask radial ramped from black at the radius, min(w/2,h/2), to white at the given fraction inward
  • Optionally save the mask for display and generating a profile
  • Add the mask as an alpha channel to the overlay image
  • Specify the background image and composite with the modified overlay image

ff=.3
convert flower_yellow.jpg \
\( +clone -monitor -fx "rm=min(w/2,h/2); rr=hypot(i-w/2,j-h/2); min(1,max(0,(rm-rr))/($ff*rm))" \
-write flower_radial_linear_mask.jpg \) \
-compose copy_opacity -composite \
flower_pink.jpg +swap -gravity center -composite flowers_radial_linear.jpg

background image

overlay
image

mask image

( )

profile of center
row of mask


composited image

top



Antialiased Image Composite Along Arbitrary Boundary

Composite Background, Overlay And Binary Mask

  • provide background and overlay images
  • provide mask
  • composite
  • (note jaggies around border of cyclops)

convert cyclops_backgray.jpg cyclops.jpg \
cyclops_mask.png \
-composite cyclops_composite1.jpg



Composite Background, Overlay And Linearly Ramped Blurred Binary Mask

  • provide background and overlay images
  • provide mask and blur it -- note blurring will be centered along cyclops boundary,
    so that it will extend outward as well as inward.
    (optionally save blurred mask for display)
  • composite
  • (note blurring causes halo effect)

convert cyclops_backgray.jpg cyclops.jpg \
\( cyclops_mask.jpg -blur 1x65535 -write cyclops_mask_blur.jpg \) \
-composite cyclops_composite2.jpg



Composite Background, Overlay And Linearly Ramped Blurred Binary Mask Limited To Inside Cyclops Boundary

  • provide background and overlay images
  • provide mask and blur it, then apply -level to make linear ramp only inside boundary
    (optionally save blurred mask for display)
  • composite
  • (note jaggies are removed and no halo, but some thinning of narrow features occurs)

convert cyclops_backgray.jpg cyclops.jpg \
\( cyclops_mask.jpg -blur 1x65535 -level 50%,100% -write cyclops_mask_blur_level.jpg \) \
-composite cyclops_composite2.jpg

top



Mean Of A Region In An Image

Original Image

Extract Mask Of Shape

convert shape_ellipse.gif -threshold 0 shape_ellipse_mask.gif

Compute Mean As quantumrange*(mean of image)/(mean of mask)

mean_image=`convert shape_ellipse.gif -format "%[mean]" info:`

echo "mean_image = $mean_image"
mean_image = 8537.95

mean_mask=`convert shape_ellipse_mask.gif -format "%[mean]" info:`

echo "mean_mask = $mean_mask"
mean_mask = 17517.5

mean_shape=`convert xc: -format "%[fx:quantumrange*$mean_image/$mean_mask]" info:`

echo "mean_shape = $mean_shape"
mean_shape = 31941.5

# convert to 8-bit mean if desired
mean_shape8=`convert xc: -format "%[fx:255*$mean_shape/quantumrange]" info:`

echo "mean_shape8 = $mean_shape8"
mean_shape8 = 124.286

Better Method Suggested By Anthony Thyssen:
Scaling to 1 pixel will ignore any transparent values
and give the equivalent of averaging the non-transparent pixels.

mean=`convert shape_ellipse.gif \( -clone 0 -threshold 0 \) \
-alpha off -compose copy_opacity -composite \
-scale 1x1! -format "%[fx:u*quantumrange]" info:`

echo "mean = $mean"
mean = 31941

# convert to 8-bit mean if desired
mean8=`convert shape_ellipse.gif \( -clone 0 -threshold 0 \) \
-alpha off -compose copy_opacity -composite \
-scale 1x1! -format "%[fx:u*255]" info:`

echo "mean8 = $mean8"
mean8 = 124.284

top



Montage By Columns

convert logo3.jpg lena2.jpg hatching.jpg zelda3.jpg -transpose miff:- |\
montage - -geometry +2+2 -tile 2x2 miff:- |\
convert - -transpose montage_columns.jpg

top



Partially Overlapping Blend

White Background

convert -background "rgba(255,255,255,0)" \
-page -64-64 zelda3.png -page +0+0 lena2.png \
-compose blend -define compose:args=50,50 \
-layers merge -alpha off zelda_lena_blend50.jpg

top



Rounded Corners With Shadow

Original Image

Prior To IM 6.4.3-7

convert thumbnail.gif \( +clone -channel a -separate +channel -negate \
\( -size 15x15 xc:black -draw 'fill white circle 15,15 15,0' -write mpr:arc +delete \) \
\( mpr:arc \) -gravity northwest -composite \
\( mpr:arc -flip \) -gravity southwest -composite \
\( mpr:arc -flop \) -gravity northeast -composite \
\( mpr:arc -rotate 180 \) -gravity southeast -composite \) \
-alpha off -compose CopyOpacity -composite -compose over \
\( +clone -background black -shadow 80x3+5+5 \) \
+swap -background none -mosaic thumbnail_rounded_shade1.png

As Of IM 6.4.3-7

convert thumbnail.gif \( +clone -alpha extract \
\( -size 15x15 xc:black -draw 'fill white circle 15,15 15,0' -write mpr:arc +delete \) \
\( mpr:arc \) -gravity northwest -composite \
\( mpr:arc -flip \) -gravity southwest -composite \
\( mpr:arc -flop \) -gravity northeast -composite \
\( mpr:arc -rotate 180 \) -gravity southeast -composite \) \
-alpha off -compose CopyOpacity -composite -compose over \
\( +clone -background black -shadow 80x3+5+5 \) \
+swap -background none -layers merge thumbnail_rounded_shade2.png

top



Filling Holes

Binary Image

Method 1:

convert 1thres_15_small.png -bordercolor white -border 1 \
-fuzz 10% -fill red -floodfill +0+0 white \
-fill black +opaque red \
-fill white -opaque red \
-shave 1x1 1thres_15_small_filled.png

Method 2:

convert 1thres_15_small.png -bordercolor white -border 1 \
-fill red -draw "color 0,0 floodfill" \
-fill black +opaque red \
-fill white -opaque red \
-shave 1x1 1thres_15_small_filled.png

Method 3:

convert 1thres_15_small.png \
\( +clone -bordercolor white -border 1 \
-fill black -draw "color 0,0 floodfill" \
-negate -shave 1x1 -transparent white \) \
-compose over -composite \
1thres_15_small_filled.png

Result:

top



Adding Grid Lines

Image

Method 1: Overlaying Grid Lines -- Images Stays Same Size

infile="lena.jpg"
hcolor="white"
vcolor="black"
thickness=3
xspace=24
yspace=24
ww=`convert $infile -format "%w" info:`
hh=`convert $infile -format "%h" info:`
inname=`convert $infile -format "%t" info:`
convert $infile \
\( -size ${xspace}x${yspace} xc:none -background none -fill none -strokewidth $thickness \
-draw "stroke $vcolor line 0,0 0,$((yspace-1)) stroke $hcolor line 0,0 $((xspace-1)),0" -write mpr:grid +delete \
-size $((ww))x$((hh)) tile:mpr:grid \) -compose over -composite ${inname}_grid.jpg

Method 2: Inserting Grid Lines -- Image Is Enlarged

infile="lena.jpg"
hcolor="white"
vcolor="black"
thickness=3
xspace=24
yspace=24
ww=`convert $infile -format "%w" info:`
hh=`convert $infile -format "%h" info:`
nx=`convert xc: -format "%[fx:round($ww/$xspace)]" info:`
ny=`convert xc: -format "%[fx:round($hh/$yspace)]" info:`
inname=`convert $infile -format "%t" info:`
convert $infile -crop ${xspace}x${yspace} \
-background $vcolor -gravity west -splice ${thickness}x0 \
-background $hcolor -gravity north -splice 0x${thickness} \
miff:- | montage - -geometry +0+0 -tile ${nx}x${ny} ${inname}_grid_expand.jpg

Method 2: Inserting Grid Lines -- Image Is Enlarged
(Method Provided By Anthony Thyssen)

infile="lena.jpg"
hcolor="white"
vcolor="black"
thickness=3
xspace=24
yspace=24
convert $infile \
-crop ${xspace}x0 +repage -background $vcolor -splice ${thickness}x0 +append \
-crop 0x${yspace} +repage -background $hcolor -splice 0x${thickness} -append \
${inname}_grid_expand.jpg

top



Converting An Image To An Overlay

Opaque Color Or Grayscale Image

Convert Image To 8-bit Transparent Overlay

convert sample-gray.jpeg -set colorspace gray -negate \
-background black -alpha shape -background black -alpha background \
PNG32:sample_gray_correct2.png

top



Creating A Labeled Animation

Images

Create Labeled Animation

convert -label "%f" \
zelda3.jpg lena2.jpg checks.jpg miff:- |\
montage - -background skyblue -geometry +0+0 -tile 1x1 miff:- |\
convert -dispose background -delay 150 - -loop 0 animation_labeled.gif

top



Fast Gaussian Blur

Input Image

Fast Gaussian Blur

For blur sigmas greater than or equal to 20, resize by 1/8,
apply gaussian blur, then resize back to original size. For
smaller blur sigmas, resize in proportion to the sigma value.

infile="logo2.png"
ww=`convert $infile -format "%[fx:w]" info:`
hh=`convert $infile -format "%[fx:h]" info:`
for ((sigma=50; sigma>0; sigma=sigma-10)); do
factor=`convert xc: -format "%[fx: max(min($sigma/2.5,8),1)]" info:`
sigma2=`convert xc: -format "%[fx:$sigma/$factor]" info:`
pct=`convert xc: -format "%[fx:100/$factor]" info:`
echo "sigma=$sigma; factor=$factor; sigma2=$sigma2; pct=$pct;"

time convert $infile -virtual-pixel edge -resize ${pct}x${pct}% -gaussian-blur 0x$sigma2 \
-resize ${ww}x${hh}! logo_resize_gaussblur_${sigma}_${factor}.jpg

time convert $infile -monitor -gaussian-blur 0x$sigma +monitor logo_gaussblur_$sigma.jpg

done

Gaussian Blur Fast Gaussian Blur
Sigma=50; Time=32.3 s Sigma=50; Time=0.07 s; Resize=1/8 (12.5%)

Sigma=40; Time=26.1 s Sigma=40; Time=0.06 s; Resize=1/8 (12.5%)

Sigma=30; Time=18.8 s Sigma=30; Time=0.05 s; Resize=1/8 (12.5%)

Sigma=20; Time=11.0 s Sigma=20; Time=0.05 s; Resize=1/8 (12.5%)

Sigma=10; Time=3.9 s Sigma=10; Time=0.07 s; Resize=1/4 (25%)

top



Color Blurring

Two-Color Image

Poor Way (Simple Blur):

convert redgreen.png -blur 0x10 redgreen_blur_bad.png

Better Way (Blur In Linear Colorspace):

convert redgreen.png -colorspace RGB -blur 0x10 -colorspace sRGB redgreen_blur_good.png

top