One class of image digital filters is described by a rectangular matrix of real coefficients called kernel convoluted in a sliding window of image pixels. Usually the kernel is square 




K

k
l




{\displaystyle K_{kl}}

, where k, l are in the range -R,-R+1,..,R-1,R. W=2R+1 is the kernel width. The filter determines the new value of a monochromatic image pixel Pij as a convolution of the image pixels in the window centered in i, j and the kernel values:







P

i
j


=


∑

k
=
−
R


R



∑

l
=
−
R


R



P

i
+
k
 
j
+
l



K

k
l





{\displaystyle P_{ij}=\displaystyle \sum _{k=-R}^{R}\sum _{l=-R}^{R}P_{i+k\ j+l}K_{kl}}




Color images are usually split into the channels which are filtered independently. A color model can be changed as well, i.e. filtration is performed not necessarily in RGB. Common kernels sizes are 3x3 and 5x5. The complexity of filtrating grows quadratically (O(n2)) with the kernel width.

Task: Write a generic convolution 3x3 kernel filter. Optionally show some end user filters that use this generic one.

(You can use, to test the functions below, these input and output solutions.)

--
-- demo\rosetta\Image_convolution.exw
-- ==================================
--
without js -- imImage, im_width, im_height, im_pixel, IupImageRGB, allocate,
            -- imFileImageLoadBitmap, peekNS, wait_key, IupImageFromImImage
include pGUI.e

constant filters = {-- Emboss
                    {{-2.0, -1.0, 0.0},
                     {-1.0,  1.0, 1.0},
                     { 0.0,  1.0, 2.0}},
                    -- Sharpen
                    {{-1.0, -1.0, -1.0},
                     {-1.0,  9.0, -1.0},
                     {-1.0, -1.0, -1.0}},
                    -- Sobel_emboss
                    {{-1.0, -2.0, -1.0},
                     { 0.0,  0.0,  0.0},
                     { 1.0,  2.0,  1.0}},
                    -- Box_blur
                    {{ 1.0, 1.0, 1.0},
                     { 1.0, 1.0, 1.0},
                     { 1.0, 1.0, 1.0}},
                    -- Gaussian_blur
                    {{1,  4,  7,  4, 1},
                     {4, 16, 26, 16, 4},
                     {7, 26, 41, 26, 7},
                     {4, 16, 26, 16, 4},
                     {1,  4,  7,  4, 1}}}

function convolute(imImage img, integer fdx)
    integer width = im_width(img),
            height = im_height(img)
    sequence original = repeat(repeat(0,width),height),
             new_image,
             filterfdx = filters[fdx]
    integer fh = length(filterfdx), hh=(fh-1)/2,
            fw = length(filterfdx[1]), hw=(fw-1)/2,
            divisor = max(sum(filterfdx),1)

    for y=height-1 to 0 by -1 do
        for x=0 to width-1 do
            original[height-y,x+1] = im_pixel(img, x, y)
        end for
    end for
    new_image = original

    for y=hh+1 to height-hh-1 do
        for x=hw+1 to width-hw-1 do
            sequence newrgb = {0,0,0}
            for i=-hh to +hh do
                for j=-hw to +hw do
                    newrgb = sq_add(newrgb,sq_mul(filterfdx[i+hh+1,j+hw+1],original[y+i,x+j]))
                end for
            end for
            new_image[y,x] = sq_max(sq_min(sq_floor_div(newrgb,divisor),255),0)
        end for
    end for

    new_image = flatten(new_image) -- (as needed by IupImageRGB)
    Ihandle new_img = IupImageRGB(width, height, new_image) 
    return new_img
end function

IupOpen()

constant w = machine_word(),
         TITLE = "Image convolution"
atom pError = allocate(w)
--imImage im1 = imFileImageLoadBitmap("Lenna50.jpg",0,pError)
--imImage im1 = imFileImageLoadBitmap("Lenna100.jpg",0,pError)
--imImage im1 = imFileImageLoadBitmap("Lena.ppm",0,pError)
imImage im1 = imFileImageLoadBitmap("Quantum_frog.png",0,pError)
--imImage im1 = imFileImageLoadBitmap("Quantum_frog.512.png",0,pError)

if im1=NULL then
    ?{"error opening image",peekNS(pError,w,1)}
    {} = wait_key()
    abort(0)
end if
--(see also Color_quantization.exw if not an IM_RGB image)

Ihandle dlg,
        flt = IupList("DROPDOWN=YES, VALUE=1")

Ihandln image1 = IupImageFromImImage(im1),
        image2 = convolute(im1,1),
        label1 = IupLabel(),
        label2 = IupLabel()
IupSetAttributeHandle(label1, "IMAGE", image1)
IupSetAttributeHandle(label2, "IMAGE", image2)

function valuechanged_cb(Ihandle /*flt*/)
    IupSetAttribute(dlg,"TITLE","Working...")
--  IupSetAttributeHandle(label2, "IMAGE", NULL)
    image2 = IupDestroy(image2)
    image2 = convolute(im1,IupGetInt(flt,"VALUE"))
    IupSetAttributeHandle(label2, "IMAGE", image2)
    IupSetAttribute(dlg,"TITLE",TITLE)
    IupRefresh(dlg)
    return IUP_DEFAULT
end function
IupSetCallback(flt,"VALUECHANGED_CB",Icallback("valuechanged_cb"))

IupSetAttributes(flt,`1=Emboss, 2=Sharpen, 3="Sobel emboss", 4="Box_blur", 5=Gaussian_blur`)
IupSetAttributes(flt,"VISIBLEITEMS=6") -- (still dunno why this trick works)
dlg = IupDialog(IupVbox({flt,
                         IupFill(),
                         IupHbox({IupFill(),label1, label2,IupFill()}),
                         IupFill()}))
IupSetAttribute(dlg, "TITLE", TITLE)
IupShow(dlg)
if platform()!=JS then  -- (no chance...)
    IupMainLoop()
    IupClose()
end if
