views:

270

answers:

1

How to create Photoshop Stamp filter analog in any pixel shading language? (I need at least filter algorithm...)

A: 

I've look at the Stamp Filter in Photoshop and it looks like a Threshold and a strong Blur added together.

I've used the HorizontalGaussianBlur kernel found Pixel Bender Basics Article on Devnet and just added a cheap threshold. Here is how my kernel looks like:

<languageVersion : 1.0;>

kernel stamp
<   namespace : "toMaterial";
    vendor : "George Profenza";
    version : 1;
    description : "Attempt to simulate Photoshop Stamp Filter. Original blur code: http://www.adobe.com/devnet/flash/articles/pixel_bender_basics_05.html";
>
{
    input image4 src;
    output pixel4 dst;

    parameter float threshold<
        minValue: 0.0;
        defaultValue:0.5;
        maxValue:1.0;
    >;
    parameter int radius
    <
        minValue : 1;
        maxValue : 6;
        defaultValue : 6;
    >;

    void
    evaluatePixel()
    {
        pixel4 center, band1, band2, band3, band4, band5, band6;
        float2 pos = outCoord();

        //Sample image in bands
        if(radius > 5) {
            pixel4 left  = dst;
            pixel4 right = dst;
            left.x -= 6.0;
            right.x += 6.0;
            band6 = left+right;
        }
        if(radius > 4) {
            pixel4 left  = dst;
            pixel4 right = dst;
            left.x -= 5.0;
            right.x += 5.0;
            band5 = left+right;
        }
        if(radius > 3) {
            pixel4 left  = dst;
            pixel4 right = dst;
            left.x -= 4.0;
            right.x += 4.0;
            band4 = left+right;
        }
        if(radius > 2) {
            pixel4 left  = dst;
            pixel4 right = dst;
            left.x -= 3.0;
            right.x += 3.0;
            band3 = left+right;
        }
        if(radius > 1) {
            pixel4 left  = dst;
            pixel4 right = dst;
            left.x -= 2.0;
            right.x += 2.0;
            band2 = left+right;
        }
        pixel4 left  = dst;
        pixel4 right = dst;
        left.x -= 1.0;
        right.x += 1.0;
        band1 = left+right;

        dst = sampleNearest(src,pos);
        //quick'n'dirty grayscale
        dst.rgb = float3(dst.r + dst.g + dst.b) * 0.333333333;
        //threshold
        if(dst.r < threshold) dst.r = 0.0; else dst.r = 1.0;
        if(dst.g < threshold) dst.g = 0.0; else dst.g = 1.0;
        if(dst.b < threshold) dst.b = 0.0; else dst.b = 1.0;

        center = dst;

        //Apply weights and compute resulting pixel
       if( radius == 6 )
       {
            dst = (band6 + (band5 * 12.0) + (band4 * 66.0) + (band3 * 220.0) + (band2 * 495.0) + (band1 * 792.0) + (center * 924.0)) * 0.000244140625;//4096.0;
       }
       if( radius == 5 )
       {
            dst = (band5 + (band4 * 10.0) + (band3 * 45.0) + (band2 * 120.0) + (band1 * 210.0) + (center * 252.0)) * 0.0009765625;//1024.0;
       }       
       if( radius == 4 )
       {
            dst = (band4 + (band3 * 8.0) + (band2 * 28.0) + (band1 * 56.0) + (center * 70.0)) * 0.00390625;//256.0;
       }
       if( radius == 3 )
       {
            dst = (band3 + (band2 * 6.0) + (band1 * 15.0) + (center * 20.0)) * 0.015625;//64.0;
       }
       if( radius == 2 )
       {
            dst = (band2 + (band1 * 4.0) + (center * 6.0)) * 0.0625;//16.0
       }
       if( radius == 1 )
       {
            dst = (band1 + (center * 2.0)) * 0.25;//4.0
       }

    }
}

This is far from perfect, it is what I could hack quickly at this hour. This should give you some ideas though.

UPDATE:

Here is an updated version using proper grayscale and the Gaussian Blur code is mostly from the Pixel Bender Guide.

<languageVersion : 1.0;>

kernel Stamp
<   namespace : "gp";
    vendor : "George Profenza";
    version : 1;
    description : "Attempt to do a filter similar to Photoshop's Stamp Filter, blur code mostly from the guide: http://www.adobe.com/go/pixelbender_devguide";
>
{
    input image4 source;
    output pixel4 result;

    parameter int blur<
        minValue:1;
        defaultValue:3;
        maxValue:8;
    >;
    parameter float threshold<
        minValue:0.0;
        maxValue:1.0;
        defaultValue:0.5;
    >;

    void
    evaluatePixel()
    {
        //blur
        const float sigma = 2.0;
        float c = 1.0 / ( sqrt(2.0 * 3.1415926535 ) * sigma );
        float ec = 2.0 * sigma * sigma;
        float weight0 = exp( -( 0.0 * 0.0 ) / ec ) * c;
        float weight1 = exp( -( 1.0 * 1.0 ) / ec ) * c;
        if(blur > 1) float weight2 = exp( -( 2.0 * 2.0 ) / ec ) * c;
        if(blur > 2) float weight3 = exp( -( 3.0 * 3.0 ) / ec ) * c;
        if(blur > 3) float weight4 = exp( -( 4.0 * 4.0 ) / ec ) * c;
        if(blur > 4) float weight5 = exp( -( 5.0 * 5.0 ) / ec ) * c;
        if(blur > 5) float weight6 = exp( -( 6.0 * 6.0 ) / ec ) * c;
        if(blur > 6) float weight7 = exp( -( 7.0 * 7.0 ) / ec ) * c;
        if(blur > 7) float weight8 = exp( -( 8.0 * 7.0 ) / ec ) * c;

        float4 acc = float4( 0.0 );

        acc += sampleNearest( source, outCoord() ) * weight0;
        acc += sampleNearest( source, outCoord() + float2( 1.0, 0.0 ) ) * weight1;
        acc += sampleNearest( source, outCoord() + float2( -1.0, 0.0 ) ) * weight1;
        if(blur > 1) {
            acc += sampleNearest( source, outCoord() + float2( 2.0, 0.0 ) ) * weight2;
            acc += sampleNearest( source, outCoord() + float2( -2.0, 0.0 ) ) * weight2;
        }
        if(blur > 2) {
            acc += sampleNearest( source, outCoord() + float2( 3.0, 0.0 ) ) * weight3;
            acc += sampleNearest( source, outCoord() + float2( -3.0, 0.0 ) ) * weight3;
        }
        if(blur > 3) {
            acc += sampleNearest( source, outCoord() + float2( 4.0, 0.0 ) ) * weight4;
            acc += sampleNearest( source, outCoord() + float2( -4.0, 0.0 ) ) * weight4;
        }
        if(blur > 4) {
            acc += sampleNearest( source, outCoord() + float2( 5.0, 0.0 ) ) * weight5;
            acc += sampleNearest( source, outCoord() + float2( -5.0, 0.0 ) ) * weight5;
        }
        if(blur > 5) {
            acc += sampleNearest( source, outCoord() + float2( 6.0, 0.0 ) ) * weight6;
            acc += sampleNearest( source, outCoord() + float2( -6.0, 0.0 ) ) * weight6;
        }
        if(blur > 6) {
            acc += sampleNearest( source, outCoord() + float2( 7.0, 0.0 ) ) * weight7;
            acc += sampleNearest( source, outCoord() + float2( -7.0, 0.0 ) ) * weight7;
        }
        if(blur > 7) {
            acc += sampleNearest( source, outCoord() + float2( 8.0, 0.0 ) ) * weight8;
            acc += sampleNearest( source, outCoord() + float2( -8.0, 0.0 ) ) * weight8;
        }
        //grayscale
        float luma = 0.299 * acc.r + 0.587 * acc.g + 0.114 * acc.b;
        acc = float4(luma,luma,luma,1.0);
        //threshold
        if(acc.r < threshold) acc.r = 0.0; else acc.r = 1.0;
        if(acc.g < threshold) acc.g = 0.0; else acc.g = 1.0;
        if(acc.b < threshold) acc.b = 0.0; else acc.b = 1.0;

        result = acc;
    }
}

Note: I applied the threshold after the blur, so the result is not exactly a Stamp Filter but close.

HTH, George

George Profenza