Overview
Given an image in plain PPM (P3) format as input, for each pixel p
in the image, replace each of the following 4 pixels' red, green, and blue with the floored average value of the respective channels of all 4 pixels:
p
itselfThe pixel located at
p
's location when the image is flipped verticallyThe pixel located at
p
's location when the image is flipped horizontallyThe pixel located at
p
's location when the image is flipped both vertically and horizontally
Output the resulting image in plain PPM (P3) format.
For further explanation, consider this 8x8 image, magnified to 128x128:
Let p
be the red pixel. To calculate the new value for p
(and the 3 blue pixels), the values of p
and the 3 blue pixels will be averaged together:
p1 = (255, 0, 0)
p2 = (0, 0, 255)
p3 = (0, 0, 255)
p4 = (0, 0, 255)
p_result = (63, 0, 191)
Examples
Reference Implementation
#!/usr/bin/python
import sys
from itertools import *
def grouper(iterable, n, fillvalue=None):
args = [iter(iterable)] * n
return list(izip_longest(*args, fillvalue=fillvalue))
def flatten(lst):
return sum(([x] if not isinstance(x, list) else flatten(x) for x in lst), [])
def pnm_to_bin(p):
w,h = map(int,p[1].split(' '))
data = map(int, ' '.join(p[3:]).replace('\n', ' ').split())
bin = []
lines = grouper(data, w*3)
for line in lines:
data = []
for rgb in grouper(line, 3):
data.append(list(rgb))
bin.append(data)
return bin
def bin_to_pnm(b):
pnm = 'P3 {} {} 255 '.format(len(b[0]), len(b))
b = flatten(b)
pnm += ' '.join(map(str, b))
return pnm
def imageblender(img):
h = len(img)
w = len(img[0])
for y in range(w):
for x in range(h):
for i in range(3):
val = (img[x][y][i] + img[x][~y][i] + img[~x][y][i] + img[~x][~y][i])//4
img[x][y][i],img[x][~y][i],img[~x][y][i],img[~x][~y][i] = (val,)*4
return img
def main(fname):
bin = pnm_to_bin(open(fname).read().split('\n'))
bin = imageblender(bin)
return bin_to_pnm(bin)
if __name__ == '__main__':
print main(sys.argv[1])
This program takes a single filename as input, formatted like the output of pngtopnm <pngfile> -plain
, and outputs a single line of PPM data separated by spaces.
A Brief Description of the P3 Format
A PPM plaintext file generated from pngtopnm <pngfile> -plain
will look like this:
P3
<width in pixels> <height in pixels>
<maximum value as defined by the bit depth, always 255 for our purposes>
<leftmost 24 pixels of row 1, in RGB triples, space-separated; like (0 0 0 1 1 1 ...)>
<next 24 pixels of row 1>
<...>
<rightmost (up to) 24 pixels of row 1>
<leftmost 24 pixels of row 2>
<next 24 pixels of row 2>
<...>
<rightmost (up to) 24 pixels of row 2>
<...>
This is the format that the example input and output files use. However, PNM is very loose about its formatting - any whitespace may separate values. You could replace all newlines in the above file with a single space each, and still have a valid file. For example, this file and this file are both valid, and represent the same image. The only other requirements are that the file must end with a trailing newline, and there must be width*height
RGB triplets following the 255
.
Rules
- This is code-golf, so the shortest valid solution wins.
- You may input and output PPM data formatted in any convenient and consistent manner, so long as it is valid according to the PPM format described above. The only exception is that you must use the plain (P3) format, and not the binary (P6) format.
- You must provide verification that your solution outputs the correct images for the above test images.
- All images will have a bit-depth of 8 bits.
Extra reading: Netpbm format wikipedia page
Testing Snippet (thanks to Calvin's Hobbies for this)
function loadImage(t){if(t.files&&t.files[0]){var i=new FileReader;i.onload=function(t){function i(t){t.attr("width",img.width),t.width=img.width,t.attr("height",img.height),t.height=img.height;var i=t[0].getContext("2d");return i}img=$("<img>").attr("src",t.target.result)[0],ctxIn=i($("#input")),ctxIn.drawImage(img,0,0),ctxOut=i($("#output")),go()},i.readAsDataURL(t.files[0])}}function getPixel(t,i){return ctxIn.getImageData(t,i,1,1).data}function setPixel(t,i,e){ctxOut.fillStyle="rgb("+e[0]+","+e[1]+","+e[2]+")",ctxOut.fillRect(t,i,1,1)}function go(){if(void 0!==ctxOut)for(var t=0;t<img.width;t++)for(var i=0;i<img.height;i++){for(var e=new Array(3),g=getPixel(t,i),a=getPixel(img.width-t-1,i),r=getPixel(t,img.height-i-1),n=getPixel(img.width-t-1,img.height-i-1),h=0;h<e.length;h++)e[h]=Math.floor((g[h]+a[h]+r[h]+n[h])/4);setPixel(t,i,e)}}var img,ctxIn,ctxOut;
* {
font-size: 100%;
font-family: Arial, sans-serif;
}
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></script>
Warning - runs very slowly on large images! 300x300 pixel maximum advised.<br><br>
<canvas id='input'>Your browser doesn't support the HTML5 canvas tag.</canvas><br><br>
Load an image: <input type='file' onchange='loadImage(this)'><br><br>
<canvas id='output'>Your browser doesn't support the HTML5 canvas tag.</canvas>