Median filter: Difference between revisions

m
→‎{{header|Wren}}: Changed to Wren S/H
m (image_stb -> stb_image)
m (→‎{{header|Wren}}: Changed to Wren S/H)
 
(15 intermediate revisions by 7 users not shown)
Line 4:
 
''(to test the function below, you can use these [[Read_ppm_file|input]] and [[Write_ppm_file|output]] solutions)''
 
=={{header|Action!}}==
{{libheader|Action! Bitmap tools}}
{{libheader|Action! Tool Kit}}
<syntaxhighlight lang="action!">INCLUDE "H6:LOADPPM5.ACT"
INCLUDE "D2:SORT.ACT" ;from the Action! Tool Kit
 
DEFINE HISTSIZE="256"
 
PROC PutBigPixel(INT x,y BYTE col)
IF x>=0 AND x<=79 AND y>=0 AND y<=47 THEN
y==LSH 2
col==RSH 4
IF col<0 THEN col=0
ELSEIF col>15 THEN col=15 FI
Color=col
Plot(x,y)
DrawTo(x,y+3)
FI
RETURN
 
PROC DrawImage(GrayImage POINTER image INT x,y)
INT i,j
BYTE c
 
FOR j=0 TO image.gh-1
DO
FOR i=0 TO image.gw-1
DO
c=GetGrayPixel(image,i,j)
PutBigPixel(x+i,y+j,c)
OD
OD
RETURN
 
INT FUNC Clamp(INT x,min,max)
IF x<min THEN
RETURN (min)
ELSEIF x>max THEN
RETURN (max)
FI
RETURN (x)
 
BYTE FUNC Median(BYTE ARRAY a BYTE len)
SortB(a,len,0)
len==RSH 1
RETURN (a(len))
 
PROC Median3x3(GrayImage POINTER src,dst)
INT x,y,i,j,ii,jj,index,sum
BYTE ARRAY arr(9)
BYTE c
 
FOR j=0 TO src.gh-1
DO
FOR i=0 TO src.gw-1
DO
sum=0 index=0
FOR jj=-1 TO 1
DO
y=Clamp(j+jj,0,src.gh-1)
FOR ii=-1 TO 1
DO
x=Clamp(i+ii,0,src.gw-1)
c=GetGrayPixel(src,x,y)
arr(index)=c
index==+1
OD
OD
c=Median(arr,9)
SetGrayPixel(dst,i,j,c)
OD
OD
RETURN
 
PROC Main()
BYTE CH=$02FC ;Internal hardware value for last key pressed
BYTE ARRAY dataIn(900),dataOut(900)
GrayImage in,out
INT size=[30],x,y
 
Put(125) PutE() ;clear the screen
 
InitGrayImage(in,size,size,dataIn)
InitGrayImage(out,size,size,dataOut)
PrintE("Loading source image...")
LoadPPM5(in,"H6:LENA30G.PPM")
PrintE("Median filter...")
Median3x3(in,out)
 
Graphics(9)
x=(40-size)/2
y=(48-size)/2
DrawImage(in,x,y)
DrawImage(out,x+40,y)
 
DO UNTIL CH#$FF OD
CH=$FF
RETURN</syntaxhighlight>
{{out}}
[https://gitlab.com/amarok8bit/action-rosetta-code/-/raw/master/images/Median_filter.png Screenshot from Atari 8-bit computer]
 
=={{header|Ada}}==
<langsyntaxhighlight lang="ada">function Median (Picture : Image; Radius : Positive) return Image is
type Extended_Luminance is range 0..10_000_000;
type VRGB is record
Line 65 ⟶ 166:
end loop;
return Result;
end Median;</langsyntaxhighlight>
The implementation works with an arbitrary window width. The window is specified by its radius ''R''>0. The resulting width is 2''R''+1. The filter uses the original pixels of the image from the median of the window sorted according to the luminance. The image edges are extrapolated using the nearest pixel on the border. Sorting uses binary search. (For practical use, note that median filter is extremely slow.)
 
The following sample code illustrates use:
<langsyntaxhighlight lang="ada"> F1, F2 : File_Type;
begin
Open (F1, In_File, "city.ppm");
Line 75 ⟶ 176:
Put_PPM (F2, Median (Get_PPM (F1), 1)); -- Window 3x3
Close (F1);
Close (F2);</langsyntaxhighlight>
 
=={{header|BBC BASIC}}==
Line 82 ⟶ 183:
[[Image:greyscale_bbc.jpg|right]]
[[Image:median_bbc.jpg|right]]
<langsyntaxhighlight lang="bbcbasic"> INSTALL @lib$+"SORTLIB"
Sort% = FN_sortinit(0,0)
Line 122 ⟶ 223:
REPEAT
WAIT 1
UNTIL FALSE</langsyntaxhighlight>
 
=={{header|C}}==
O(n) filter with histogram.
<langsyntaxhighlight lang="c">#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
Line 296 ⟶ 397:
 
return 0;
}</langsyntaxhighlight>
 
=={{header|D}}==
Line 306 ⟶ 407:
 
Currently this code works only on greyscale images.
<langsyntaxhighlight lang="d">import grayscale_image;
 
Image!Color medianFilter(uint radius=10, Color)(in Image!Color img)
Line 381 ⟶ 482:
medianFilter!10
.savePGM("lena_median_r10.pgm");
}</langsyntaxhighlight>
Compile with -version=median_filter_main to run the demo.
 
=={{header|Delphi}}==
{{works with|Delphi|6.0}}
{{libheader|SysUtils,StdCtrls}}
[[File:DelphiMedianFilter.png|frame|none]]
 
<syntaxhighlight lang="Delphi">
 
{-------------------------------------------------------------------------------}
 
 
 
type THistogram = record
Bins: array [0..255] of integer;
Colors: array [0..255] of TRGBTriple;
end;
 
 
procedure MedianFilter(Src,Dest: TBitmap; WindowX, WindowY: integer);
var x, y, X1, Y1, med, md, dl, delta_l, WX2, WY2: integer;
var I, MedSum, XStart,XEnd, YStart,YEnd, MedInx: integer;
var middle: integer;
var Histogram: THistogram;
var u: byte;
var Color: TRGBTriple;
var SrcRows,DestRows: TRGBTripleRowArray;
begin
WindowX:=WindowX * 2 -1;
WindowY:=WindowY * 2 -1;
 
Src.PixelFormat:=pf24Bit;
Dest.PixelFormat:=pf24Bit;
 
Dest.Width:=Src.Width;
Dest.Height:=Src.Height;
SetLength(SrcRows,Src.Height);
SetLength(DestRows,Dest.Height);
 
{Capture scan lines of both source and destiantion bitmaps}
for Y:=0 to Src.Height-1 do SrcRows[Y]:=Src.ScanLine[Y];
for Y:=0 to Dest.Height-1 do DestRows[Y]:=Dest.ScanLine[Y];
 
 
WX2 := WindowX div 2;
WY2 := WindowY div 2;
 
middle := (WindowX * WindowY-1) div 2;
 
for y := 0 to SRC.Height-1 do
begin
{ Determine the histogram and median for the first element of each row}
YStart:=Y - WY2;
YEnd:=Y + WY2;
 
{ histogram reset }
for I := 0 to 255 do Histogram.Bins[I] := 0;
 
{recalculation of the histogram for the start element row=y, col=0 }
for Y1 := YStart to YEnd do
for X1 := -WX2 to WX2 do
begin
{It is the first pixel on the row, so don't worry about right edge}
if (Y1>=0) and (Y1<SRC.Height) and (X1>=0) then Color:=SrcRows[Y1][X1] else Color:=MakeRBGTriple(0,0,0); // Color:=SrcRows[y][0];
U:=RGBToGray(Color);
inc(Histogram.Bins[U]);
Histogram.Colors[U]:=Color;
end;
 
{ now determine the median }
MedSum := 0;
for MedInx := 0 to 255 do
begin
inc(MedSum,Histogram.Bins[MedInx]);
if MedSum > middle then break;
end;
med := MedInx;
 
delta_l := MedSum - Histogram.Bins[MedInx];
DestRows[Y][0]:=Histogram.Colors[MedInx];
 
{ Loop through each column in this row}
for x := 1 to Src.Width-1 do
begin
XStart := x-wx2-1;
XEnd := x+wx2;
{ go to next column }
for Y1 := YStart to YEnd do
begin
if (XStart >= 0) and (Y1 >= 0) and (Y1 < SRC.Height) then Color:=SrcRows[Y1][XStart] else Color:=MakeRBGTriple(0,0,0); // Color:=SrcRows[Y][X];
U:=RGBToGray(Color);
if Histogram.Bins[u]>0 then dec(Histogram.Bins[u]);
if u < med then dec(delta_l);
if (XEnd < Src.Width) and (Y1 >= 0) and (Y1 < SRC.Height) then Color:=SrcRows[Y1][XEnd] else Color:=MakeRBGTriple(0,0,0); // Color:=SrcRows[Y][X];
U:=RGBToGray(Color);
inc(Histogram.Bins[u]);
Histogram.Colors[U]:=Color;
if u < med then inc(delta_l);
end;
 
{ update new median }
dl := delta_l;
md := med;
if dl > middle then
begin
while dl > middle do
begin
dec(md);
if Histogram.Bins[md] > 0 then
dec(dl,Histogram.Bins[md]);
end;
end
else
begin
while dl + Histogram.Bins[md] <= middle do
begin
if Histogram.Bins[md] > 0 then inc(dl,Histogram.Bins[md]);
inc(md);
end;
end;
delta_l := dl;
med := md;
DestRows[Y][X]:= Histogram.Colors[med];
end; { x loop}
end; { y loop}
end;
 
 
 
</syntaxhighlight>
{{out}}
<pre>
 
Elapsed Time: 110.287 ms.
 
</pre>
 
=={{header|GDL}}==
GDL has no inbuilt median filter function, which is native in IDL. This example is based on pseudocode here: http://en.wikipedia.org/wiki/Median_filter#2D_median_filter_pseudo_code, however, it works with 1D arrays only. It does not process boundaries.
<syntaxhighlight lang="gdl">
<lang GDL>
FUNCTION MEDIANF,ARRAY,WINDOW
RET=fltarr(N_ELEMENTS(ARRAY),1)
Line 401 ⟶ 637:
RETURN, RET
END
</syntaxhighlight>
</lang>
Usage:
<langsyntaxhighlight GDLlang="gdl">Result = MEDIANF(ARRAY, WINDOW)</langsyntaxhighlight>
 
=={{header|Go}}==
Implemented with existing GetPx/SetPx functions at Grayscale image task. It could be sped up by putting code in the raster package, but if you're concerned about speed, you should implement one of the O(n) algorithms available.
<langsyntaxhighlight lang="go">package main
 
// Files required to build supporting package raster are found in:
Line 496 ⟶ 732:
m := n / 2
return (kc[m-1] + kc[m]) / 2
}</langsyntaxhighlight>
 
=={{header|J}}==
Line 503 ⟶ 739:
The following verbs are used to work with bitmaps:
 
<syntaxhighlight lang="j">
<lang J>
makeRGB=: 0&$: : (($,)~ ,&3)
toGray=: <. @: (+/) @: (0.2126 0.7152 0.0722 & *)"1
</syntaxhighlight>
</lang>
 
We'll determine the window as a square zone around each pixel, with the given pixel in the center of the zone. Such a window always have odd height and width. We'll say the window radius is 0 if the window contain only the given pixel - in this case the resulting picture will be identical to the input. The radius is 1 if the window is 3x3 pixels, with given pixel in the center. Radius is 2 if the window is 5x5 pixels, with given pixel in the center, etc.
Line 512 ⟶ 748:
To get all pixels in the window, first calculate coordinates - or indexes - of those pixels. For the pixels on the edges of the input bitmap, include only those indexes which correspond to actually existing pixels - no negative indexes and no indexes outside of the bitmap boundaries.
 
<syntaxhighlight lang="j">
<lang J>
median_filter =: dyad define
win =. y -~ i. >: +: y
Line 522 ⟶ 758:
medians =. ({~ <. @ -: @ {. @ $) @ ({~ /: @: toGray) @ (,/) @ > sets
)
</syntaxhighlight>
</lang>
 
Example:
<syntaxhighlight lang="j">
<lang J>
] bmp =. ?. 256 + makeRGB 4 5
34 39 168
Line 574 ⟶ 810:
142 156 125
116 139 217
</syntaxhighlight>
</lang>
 
=={{header|Java}}==
The class in the [[Bitmap]] task is reused for this task with an additional method to filter the image using the Wikipedia pseudo-code.
 
The program is tested with the left half of the sample image file, Medianfilterp.png, in the Wikipedia article.
<syntaxhighlight lang="java">
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
import javax.imageio.ImageIO;
 
public final class MedianFilter {
 
public static void main(String[] aArgs) {
try {
BufferedImage image = ImageIO.read( new File("beforeFilter.png") );
BasicBitmapStorage bitmap = new BasicBitmapStorage(image.getWidth(null), image.getHeight(null));
for ( int y = 0; y < image.getHeight(null); y++ ) {
for ( int x = 0; x < image.getWidth(null); x++ ) {
bitmap.setPixel(x, y, new Color(image.getRGB(x, y), true));
}
}
bitmap.medianFilter(3, 3);
File fileAfterFilter = new File("afterFilter.png");
ImageIO.write((RenderedImage) bitmap.getImage(), "png", fileAfterFilter);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
 
final class BasicBitmapStorage {
 
public BasicBitmapStorage(int aWidth, int aHeight) {
image = new BufferedImage(aWidth, aHeight, BufferedImage.TYPE_INT_RGB);
}
 
public void fill(Color aColor) {
Graphics graphics = image.getGraphics();
graphics.setColor(aColor);
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
}
 
public Color getPixel(int aX, int aY) {
return new Color(image.getRGB(aX, aY));
}
public void setPixel(int aX, int aY, Color aColor) {
image.setRGB(aX, aY, aColor.getRGB());
}
public Image getImage() {
return image;
}
public void medianFilter(int aWindowWidth, int aWindowHeight) {
List<Color> window = Stream.generate( () -> Color.BLACK )
.limit(aWindowWidth * aWindowHeight).collect(Collectors.toList());
final int edgeX = aWindowWidth / 2;
final int edgeY = aWindowHeight / 2;
Comparator<Color> luminanceComparator = (one, two) -> Double.compare(luminance(one), luminance(two));
for ( int x = edgeX; x < image.getWidth() - edgeX; x++ ) {
for ( int y = edgeY; y < image.getHeight() - edgeY; y++ ) {
int i = 0;
for ( int fx = 0; fx < aWindowWidth; fx++ ) {
for ( int fy = 0; fy < aWindowHeight; fy++ ) {
window.set(i, getPixel(x + fx - edgeX, y + fy - edgeY));
i += 1;
}
}
Collections.sort(window, luminanceComparator);
setPixel(x, y, window.get(aWindowWidth * aWindowHeight / 2));
}
}
}
private double luminance(Color aColor) {
return 0.2126 * aColor.getRed() + 0.7152 * aColor.getGreen() + 0.0722 * aColor.getBlue();
}
private final BufferedImage image;
}
</syntaxhighlight>
{{ out }}
[[Media:beforeFilter.png]] & [[Media:afterFilter.png]]
 
=={{header|Julia}}==
{{works with|Julia|0.6}}
 
<langsyntaxhighlight lang="julia">using Images, ImageFiltering, FileIO
Base.isless(a::RGB{T}, b::RGB{T}) where T =
red(a) < red(b) || green(a) < green(b) || blue(a) < blue(b)
Line 585 ⟶ 922:
 
img = load("data/lenna100.jpg")
mapwindow(median!, img, (3, 3))</langsyntaxhighlight>
 
=={{header|Kotlin}}==
Line 591 ⟶ 928:
 
To test the function we use the left half of the sample image file (Medianfilterp.png) in the Wikipedia article and see if we can get close to the right half.
<langsyntaxhighlight lang="scala">// Version 1.2.41
import java.awt.Color
import java.awt.Graphics
Line 647 ⟶ 984:
ImageIO.write(image, "png", mfFile)
}
}</langsyntaxhighlight>
 
{{output}}
Line 654 ⟶ 991:
</pre>
 
=={{header|Mathematica}}/{{header|Wolfram Language}}==
<syntaxhighlight lang="mathematica">MedianFilter[img,n]</syntaxhighlight>
 
<lang Mathematica>
MedianFilter[img,n]
</lang>
 
=={{header|Nim}}==
{{trans|Kotlin}}
{{libheader|imageman}}
{{libheader|stb_image-Nim}}
<br>
Compile with command <code>nim c -d:imagemanlibpng=false -d:imagemanlibjpeg=false median_filter.nim</code> to
Line 669 ⟶ 1,003:
procedure has some difficulties to open PNG files using a palette.
 
<langsyntaxhighlight Nimlang="nim">import algorithm
import imageman
 
Line 696 ⟶ 1,030:
var image = fullImage[0..<(fullImage.width div 2), 0..<fullImage.height]
image.applyMedianFilter(3, 3)
savePNG(image, "Medianfilterp_3x3.png")</langsyntaxhighlight>
 
=={{header|OCaml}}==
 
<langsyntaxhighlight lang="ocaml">let color_add (r1,g1,b1) (r2,g2,b2) =
( (r1 + r2),
(g1 + g2),
Line 763 ⟶ 1,097:
done;
done;
(res)</langsyntaxhighlight>
 
an alternate version of the function <tt>median_value</tt> using arrays instead of lists:
<langsyntaxhighlight lang="ocaml">let median_value img radius =
let samples = (radius*2+1) * (radius*2+1) in
let sample = Array.make samples (0,0,0) in
Line 785 ⟶ 1,119:
then sample.(mid+1)
else (color_div (color_add sample.(mid)
sample.(mid+1)) 2)</langsyntaxhighlight>
 
=={{header|Perl}}==
<langsyntaxhighlight lang="perl">use strict 'vars';
use warnings;
 
Line 796 ⟶ 1,130:
my $image = rpic 'plasma.png';
my $smoothed = med2d $image, ones(3,3), {Boundary => Truncate};
wpic $smoothed, 'plasma_median.png';</langsyntaxhighlight>
Compare offsite images: [https://github.com/SqrtNegInf/Rosettacode-Perl5-Smoke/blob/master/ref/plasma.png plasma.png] vs.
[https://github.com/SqrtNegInf/Rosettacode-Perl5-Smoke/blob/master/ref/plasma_median.png plasma_median.png]
Line 804 ⟶ 1,138:
Requires read_ppm() from [[Bitmap/Read_a_PPM_file#Phix|Read_a_PPM_file]], write_ppm() from [[Bitmap/Write_a_PPM_file#Phix|Write_a_PPM_file]],
which are both now part of demo\rosetta\ppm.e. Results may be verified with demo\rosetta\viewppm.exw
<langsyntaxhighlight Phixlang="phix">-- demo\rosetta\Bitmap_Median_filter.exw
include ppm.e
 
Line 854 ⟶ 1,188:
sequence img = read_ppm("Lena.ppm")
img = median(img)
write_ppm("LenaMedian.ppm",img)</langsyntaxhighlight>
 
=={{header|PicoLisp}}==
<langsyntaxhighlight PicoLisplang="picolisp">(de ppmMedianFilter (Radius Ppm)
(let Len (inc (* 2 Radius))
(make
Line 887 ⟶ 1,221:
X ) )
(inc Radius) ) ) )
(map pop X) ) ) ) ) ) ) )</langsyntaxhighlight>
Test using 'ppmRead' from [[Bitmap/Read a PPM file#PicoLisp]] and 'ppmWrite'
from [[Bitmap/Write a PPM file#PicoLisp]]:
Line 896 ⟶ 1,230:
{{libheader|PIL}}
 
<langsyntaxhighlight lang="python">import Image, ImageFilter
im = Image.open('image.ppm')
 
median = im.filter(ImageFilter.MedianFilter(3))
median.save('image2.ppm')</langsyntaxhighlight>
 
=={{header|Racket}}==
Due to the use of flomaps the solution below works for all types of images.
<langsyntaxhighlight lang="racket">
#lang racket
(require images/flomap math)
Line 926 ⟶ 1,260:
(f (+ x 1) y)
(f (+ x 1) (+ y 1)))))))
</syntaxhighlight>
</lang>
 
=={{header|Raku}}==
(formerly Perl 6)
Clone of Perl 5, for now.
<syntaxhighlight lang="raku" perl6line>use PDL:from<Perl5>;
use PDL::Image2D:from<Perl5>;
 
my $image = rpic 'plasma.png';
my $smoothed = med2d($image, ones(3,3), {Boundary => 'Truncate'});
wpic $smoothed, 'plasma_median.png';</langsyntaxhighlight>
Compare offsite images: [https://github.com/SqrtNegInf/Rosettacode-Perl6-Smoke/blob/master/ref/plasma-perl6.png plasma.png] vs.
[https://github.com/SqrtNegInf/Rosettacode-Perl6-Smoke/blob/master/ref/plasma_median.png plasma_median.png]
Line 942 ⟶ 1,276:
=={{header|Ruby}}==
{{trans|Tcl}}
<langsyntaxhighlight lang="ruby">class Pixmap
def median_filter(radius=3)
radius += 1 if radius.even?
Line 1,008 ⟶ 1,342:
 
bitmap = Pixmap.open('file')
filtered = bitmap.median_filter</langsyntaxhighlight>
 
=={{header|Tcl}}==
{{works with|Tcl|8.5}}
{{libheader|Tk}}
<langsyntaxhighlight lang="tcl">package require Tk
 
# Set the color of a pixel
Line 1,059 ⟶ 1,393:
update
pack [labelframe .dst -text Median] -side left
pack [label .dst.l -image [medianFilter teapot]]</langsyntaxhighlight>
 
=={{header|Wren}}==
{{libheader|DOME}}
This follows the Wikipedia pseudo-code for the median filter, sorting the colors by their luminance, and displays the 'before' and 'after' images side by side on the canvas. Results are as expected though remaining corruption seems more prominent than on Wikipedia image.
<syntaxhighlight lang="wren">import "graphics" for Canvas, ImageData, Color
import "dome" for Window
 
class MedianFilter {
construct new(filename, filename2, windowWidth, windowHeight) {
Window.title = "Median filter"
var image = ImageData.loadFromFile(filename)
Window.resize(image.width, image.height)
Canvas.resize(image.width, image.height)
_ww = windowWidth
_wh = windowHeight
// split off the left half
_image = ImageData.create(filename2, image.width/2, image.height)
_name = filename2
for (x in 0...image.width/2) {
for (y in 0...image.height) _image.pset(x, y, image.pget(x, y))
}
// display it on the left before filtering
_image.draw(0, 0)
}
 
luminance(c) { 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b }
 
medianFilter(windowWidth, windowHeight) {
var window = List.filled(windowWidth * windowHeight, Color.black)
var edgeX = (windowWidth / 2).floor
var edgeY = (windowHeight / 2).floor
var comparer = Fn.new { |a, b| luminance(a) < luminance(b) }
for (x in edgeX..._image.width - edgeX) {
for (y in edgeY..._image.height - edgeY) {
var i = 0
for (fx in 0...windowWidth) {
for (fy in 0...windowHeight) {
window[i] = _image.pget(x + fx - edgeX, y + fy - edgeY)
i = i + 1
}
}
window.sort(comparer)
_image.pset(x, y, window[((windowWidth * windowHeight)/2).floor])
}
}
}
 
init() {
medianFilter(_ww, _wh)
// display it on the right after filtering
_image.draw(_image.width, 0)
// save it to a file
_image.saveToFile(_name)
}
 
update() {}
 
draw(alpha) {}
}
 
var Game = MedianFilter.new("Medianfilterp.png", "Medianfilterp2.png", 3, 3)</syntaxhighlight>
 
=={{header|zkl}}==
Uses Image Magick and the PPM class from http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#zkl
 
<langsyntaxhighlight lang="zkl">fcn medianFilter(img){ //-->new image
var [const] window=[-2..2].walk(), edge=(window.len()/2); // 5x5 window
 
Line 1,077 ⟶ 1,472:
}
new
}</langsyntaxhighlight>
<langsyntaxhighlight lang="zkl">filtered:=medianFilter(PPM.readJPGFile("lena.jpg"));
filtered.writeJPGFile("lenaMedianFiltered.zkl.jpg");</langsyntaxhighlight>
See the [http://www.zenkinetic.com/Images/RosettaCode/lenaMedianFiltered.zkl.jpg filtered image]
and the [http://www.zenkinetic.com/Images/RosettaCode/lena.jpg orginal].
9,476

edits