Image Smoothie!

5

1

Input into your program a black and white image. (png, jpg, etc)

Convert the image to a 2D matrix of pixels, where each index is the grayscale value of the image.

Take each index of the matrix and average the 8 pixels around it. Then replace that index with the average.

For example if you had:

120 80  60
20  10  30
40  100 05

If you were finding the average for 10, the result would be 56.875, which is (120,80,60,30,05,100,40,20)/8.

Round to up to the nearest integer (57) and replace the index. The result of this iteration would be

120 80  60
20  57  30
40  100 05

If you are finding the average for a pixel on an edge then only take values that are valid. In the example above if you were finding the average for 20 You would only consider 120, 80, 57, 100, and 40 which is 79.4 rounded to 80.

You may start at any index of the graph but it must be in order. For example you could do

[0][0], [0][1], ... [0][n], then [1][0], [1][1],... [1][n]

Once the averaging is complete, convert the matrix back into a new image.

Display the new image as the output of the program. (Or save it and add it in your solution). The result of your code should be a blurry looking image but much smoother than the original.

Example Input
enter image description here
Example Output
enter image description here

Example Input two
enter image description here
Example Output two
enter image description here

Please provide your code and start and end image results.

Q/A
1Q. If one of the 8 neighbors has already been changed, should the average be calculated based on this new value, or based on the original values? That is, does the order of calculation affect the end result?

1A Yes, You should change each index with new values from any potential indices done before. That is why you need to go left-right, top-bottom etc. In order.

2Q. Could you clarify what is meant by "must be in order"? Does this mean that only raster order/reading order is acceptable? Or does it mean that the order must be the same each time the code is run? Would a spiral be valid? Or a shuffled order that is always the same?

2A. You should go left to right, right to left, top to bottom, or bottom to top. I.E. Do all of the 0th row, do all of the 1st row,... do all of the nth row

jacksonecac

Posted 2016-10-17T13:20:08.220

Reputation: 2 584

1If one of the 8 neighbours has already been changed, should the average be calculated based on this new value, or based on the original values? That is, does the order of calculation affect the end result? – trichoplax – 2016-10-17T13:29:38.363

Yes each value is based on the previous ones. That is why you need to change them going from one direction to another. and not hop around. – jacksonecac – 2016-10-17T13:34:56.647

Could you clarify what is meant by "must be in order"? Does this mean that only raster order/reading order is acceptable? Or does it mean that the order must be the same each time the code is run? Would a spiral be valid? Or a shuffled order that is always the same? – trichoplax – 2016-10-17T13:35:38.667

You should go left to right, right to left, top to bottom, or bottom to top. I.E. Do all of the 0th row, do all of the 1st row,... do all of the nth row – jacksonecac – 2016-10-17T13:36:44.277

Are we allowed to take the image as an array of integers? – miles – 2016-10-17T16:00:56.593

Yes, that will work – jacksonecac – 2016-10-17T16:01:34.170

"You may start at any index of the graph but it must be in order" seems to conflict with "2A [...] Do all of the 0th row, do all of the 1st row,... do all of the nth row" – Luis Mendo – 2016-10-18T15:35:05.933

what about edges? should the array indices wrap or should the average be taken from fewer pixels? – Aaron – 2016-10-20T19:53:05.617

@Aaron that case is specified in the description. "If you are finding the average for a pixel on an edge then only take values that are valid. In the example above if you were finding the average for 20 You would only consider 120, 80, 57, 100, and 40 which is 79.4 rounded to 80." – jacksonecac – 2016-10-21T11:11:00.037

Answers

5

J, 90 82 bytes

[:((=i.@$){"0 1],"+3 3((+/>.@%#)@(_-.~(<<<4){,);._3)_,.~_,._,~_,])&.>/(#<@-#\)@,,<

This iterates over pixels from left-to-right, then top-to-bottom.

You can see the progression of values here.

   |. ((=i.@$){"0 1],"+3 3((+/>.@%#)@(_-.~(<<<4){,);._3)_,.~_,._,~_,])&.>/\. ((#<@-#\)@,,<) 120 80 60 , 20 10 30 ,: 40 100 5
┌──────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬────────┐
│120  80 60│37  80 60│37  32 60│37  32 24│37  32 24│37  32 24│37  32 24│37  32 24│37 32 24│37 32 24│
│ 20  10 30│20  10 30│20  10 30│20  10 30│44  10 30│44  39 30│44  39 40│44  39 40│44 39 40│44 39 40│
│ 40 100  5│40 100  5│40 100  5│40 100  5│40 100  5│40 100  5│40 100  5│61 100  5│61 38  5│61 38 39│
└──────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴────────┘

The output for the first sample image is (converting from an array of pixels to a bmp)

sample

miles

Posted 2016-10-17T13:20:08.220

Reputation: 15 654

Here's code in groovy to change the image into a 2D array (85 bytes, 1 more than you already, jesus.......), {i->(0..i.getWidth()-1).collect{x->(0..i.getHeight()-1).collect{y->i.getRGB(x,y)}}}​...... Ha. I concede, you win before I even start. – Magic Octopus Urn – 2016-10-17T17:08:54.003

Can you provide the image before and after? – jacksonecac – 2016-10-17T17:22:17.390

@jacksonecac Sure, also could you clarify what order (directions to iterate over the pixels) you used for the sample images? – miles – 2016-10-17T20:07:10.623

1@JonathanAllan Thanks, I've added a simple fix using a permutation that shifts the center to the front so it can be dropped – miles – 2016-10-18T13:28:44.033

4

PHP, 321 bytes

$i=imagecreatefromjpeg($f="96512.jpg");list($w,$h)=getimagesize($f);for($y=$h;$y--;)for($x=$w;$x--;$s=$n=0){for($a=-2;$a++<1;)for($b=-2;$b++<1;)if(false!==($d=imagecolorat($i,$x+$a,$y+$b))){$n++;$s+=imagecolorsforindex($i,$d)[red];}imagesetpixel($i,$x,$y,imagecolorallocate($i,$r=ceil($s/$n),$r,$r));}imagejpeg($i,"b$f");

does the blurring from bottom to top, from right to left.

-2 bytes for a gif or png; could save even more with a shorter file name
and -6 if rounding down was allowed. pity that image processing functions have so long names.
but I´m fine. pretty byte count.

blurred example image

Titus

Posted 2016-10-17T13:20:08.220

Reputation: 13 814

Nice job looks great – jacksonecac – 2016-10-18T11:08:27.880

2

PHP, 70 bytes

imagefilter($i=imagecreatefromjpeg($f="i.jpg"),10,0);imagejpeg($i,$f);

Overwrites the original image file. Run with -r.
-2 bytes if you use a gif or png.


I thought PHP might have a built-in for this, and indeed:
10 stands for IMG_FILTER_SMOOTH; and 0 is the weight parameter.

See the GD library source code: This calls gdImageSmooth which calls gdImageConvolution with the requested matrix. I am not sure what it does at the edges; but overall it does the smoothing from top to bottom, from left to right.

enter image description here

Titus

Posted 2016-10-17T13:20:08.220

Reputation: 13 814

1

Racket 346 bytes

(let((s1 sub1)(a1 add1)(lr list-ref)(ln length))(define(g r c)(if(or(> r(s1(ln l)))(> c(s1(ln(lr l 0))))(< r 0)(< c 0))
#f(lr(lr l r)c)))(define(ng r c)(define h'(-1 0 1))(define k(filter(λ(x)x)(for*/list((i h)(j h)#:unless(= 0 i j))
(g (+ r i)(+ c j)))))(round(/(apply + k)(ln k))))(for/list((i(ln l)))(for/list((j(length (lr l 0))))(ng i j))))

Ungolfed (comments follow ';'):

(define (f l)
  (define (get r c)                       ; to get value at a row,col            
    (if (or (> r (sub1 (length l))) 
            (> c (sub1 (length (list-ref l 0))))
            (< r 0)
            (< c 0))                      ; if row/col number is invalid
        #f (list-ref (list-ref l r) c)))  ; return false else return value
  (define (neighbors r c)                 ; finds average of neighboring values
    (define h '(-1 0 1))
    (define k (filter                     ; filter out false from list
               (lambda(x) x)
               (for*/list ((i h)(j h) #:unless (= 0 i j))
                 (get (+ r i) (+ c j)))))
    (round(/(apply + k)(length k))))      ; find average of values (sum/length of list)
  (for/list ((i (length l)))              ; create a list of lists of average values
    (for/list ((j (length (list-ref l 0))))
      (neighbors i j))
    ))

Testing:

(f (list
    '(120 80 60)
    '(20 10 30)
    '(40 100 5)))

Output:

'((37 48 40) (70 57 51) (43 21 47))

rnso

Posted 2016-10-17T13:20:08.220

Reputation: 1 635

Nice Job! Please provide the image result as well. – jacksonecac – 2016-10-18T17:09:20.353

1

Ruby 2.3.1, 338 bytes

->w{d,k=16843008,ChunkyPNG::Canvas.from_file(w);(0..k.height-1).each do |y|;(0..k.width-1).each do |x|;t,i,a=0,0,[[x-1,y-1],[x,y-1],[x+1,y-1],[x-1,y],[x+1,y],[x,y+1],[x,y+1],[x+1,y+1]];a.each do |c|; next unless k.include_xy?(c[0],c[1]);t=t+k.[](c[0],c[1]);i=i+1;end;n=t/i;while(n%d)>0 do;n=n+1;end;k.[]=(x,y,n-1);end;end;k.save("w")}
  • Uses oily_png/chunky_png for image handling.
  • Goes left to right, top to bottom doing the averaging.
  • Since chunky uses integer color values, I'm doing a really, really slow up rounding method to get averaged color values to an actual grayscale value.

Example output(that took a very, very long time):

enter image description here

Ungolfed, slightly modded, and commented

require 'oily_png'
l = -> img {
  time_begin = Time.new
  d = 16843008
  k = ChunkyPNG::Canvas.from_file(img)
  # go line by line, pixel by pixel
  (0..k.height-1).each do |y|
    (0..k.width-1).each do |x|
      total, i = 0, 0
      p "Starting color is #{curr} or #{curr.to_s(16)}"
      # set the coords around the pixel we want
      around = [[x-1,y-1], [x, y-1], [x+1, y-1], [x-1,y], [x+1, y], [x,y+1], [x, y+1], [x+1, y+1]]
      # grab the 8 colors around the current pixel
      around.each do |coords|
        # ignore values that are not in bounds
        next unless k.include_xy?(coords[0], coords[1])
        # get a total to average later
        total = total + k.[](coords[0], coords[1])
        # increment
        i = i+1
      end
      # get the average value for the new color
      new_color = total/i
      # since new color will likely not be a grayscale value,
      # round up to the nearest grayscale value.
      # This is unfortunately really slow.
      while (new_color%d) > 0 do
        new_color = new_color+1
      end
      p "New color is now #{new_color-1} or #{(new_color-1).to_s(16)}"
      # set the color and
      # remove one from the color value to get full alpha instead of none.
      k.[]=(x, y, new_color-1)
    end
  end
  # save the complete image
  k.save("#{Time.new}.png")
}
l.($*[0])

Could certainly do things a bit more efficiently, unfortunately I'm not quite how to. There's got to be a more clever way of handling the coordinate management for the 8 surrounding locations.

metropolis

Posted 2016-10-17T13:20:08.220

Reputation: 111