// // https://github.com/spite/Wagner/blob/master/fragment-shaders/toon-fs.glsl // package com.ruesga.android.wallpapers.photophase.effects; import android.media.effect.EffectContext; import android.opengl.GLES20; import com.ruesga.android.wallpapers.photophase.utils.GLESUtil; /** * A cartoon effect<br/> * <table> * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> * </table> */ public class ToonEffect extends PhotoPhaseEffect { private static final String FRAGMENT_SHADER = "precision mediump float;\n" + "uniform float w;\n" + "uniform float h;\n" + "uniform sampler2D tex_sampler;\n" + "varying vec2 v_texcoord;\n" + "\n" + "#define HueLevCount 6\n" + "#define SatLevCount 7\n" + "#define ValLevCount 4\n" + "float HueLevels[HueLevCount];\n" + "float SatLevels[SatLevCount];\n" + "float ValLevels[ValLevCount];\n" + "\n" + "vec3 RGBtoHSV(float r, float g, float b) {\n" + " float minv, maxv, delta;\n" + " vec3 res;\n" + "\n" + " minv = min(min(r, g), b);\n" + " maxv = max(max(r, g), b);\n" + " res.z = maxv;\n" + "\n" + " delta = maxv - minv;\n" + "\n" + " if( maxv != 0.0 )\n" + " res.y = delta / maxv;\n" + " else {\n" + " res.y = 0.0;\n" + " res.x = -1.0;\n" + " return res;\n" + " }\n" + "\n" + " if (r == maxv)\n" + " res.x = ( g - b ) / delta;\n" + " else if (g == maxv)\n" + " res.x = 2.0 + (b - r) / delta;\n" + " else\n" + " res.x = 4.0 + (r - g) / delta;\n" + "\n" + " res.x = res.x * 60.0;\n" + " if (res.x < 0.0)\n" + " res.x = res.x + 360.0;\n" + "\n" + " return res;\n" + "}\n" + "\n" + "vec3 HSVtoRGB(float h, float s, float v) {\n" + " int i;\n" + " float f, p, q, t;\n" + " vec3 res;\n" + "\n" + " if (s == 0.0) {\n" + " // achromatic (grey)\n" + " res.x = v;\n" + " res.y = v;\n" + " res.z = v;\n" + " return res;\n" + " }\n" + "\n" + " h /= 60.0;\n" + " i = int(floor(h));\n" + " f = h - float(i);\n" + " p = v * ( 1.0 - s );\n" + " q = v * ( 1.0 - s * f );\n" + " t = v * ( 1.0 - s * ( 1.0 - f ) );\n" + "\n" + " if (i==0) {\n" + " res.x = v;\n" + " res.y = t;\n" + " res.z = p;\n" + " } else if (i==1) {\n" + " res.x = q;\n" + " res.y = v;\n" + " res.z = p;\n" + " } else if (i==2) {\n" + " res.x = p;\n" + " res.y = v;\n" + " res.z = t;\n" + " } else if (i==3) {\n" + " res.x = p;\n" + " res.y = q;\n" + " res.z = v;\n" + " } else if (i==4) {\n" + " res.x = t;\n" + " res.y = p;\n" + " res.z = v;\n" + " } else if (i==5) {\n" + " res.x = v;\n" + " res.y = p;\n" + " res.z = q;\n" + " }\n" + "\n" + " return res;\n" + "}\n" + "\n" + "float nearestLevel(float col, int mode) {\n" + " if (mode==0) {\n" + " for (int i =0; i<HueLevCount-1; i++ ) {\n" + " if (col >= HueLevels[i] && col <= HueLevels[i+1]) {\n" + " return HueLevels[i+1];\n" + " }\n" + " }\n" + " }\n" + "\n" + " if (mode==1) {\n" + " for (int i =0; i<SatLevCount-1; i++ ) {\n" + " if (col >= SatLevels[i] && col <= SatLevels[i+1]) {\n" + " return SatLevels[i+1];\n" + " }\n" + " }\n" + " }\n" + "\n" + "\n" + " if (mode==2) {\n" + " for (int i =0; i<ValLevCount-1; i++ ) {\n" + " if (col >= ValLevels[i] && col <= ValLevels[i+1]) {\n" + " return ValLevels[i+1];\n" + " }\n" + " }\n" + " }\n" + "}\n" + "\n" + "float avg_intensity(vec4 pix) {\n" + " return (pix.r + pix.g + pix.b) / 3.;\n" + "}\n" + "\n" + "vec4 get_pixel(vec2 coords, float dx, float dy) {\n" + " return texture2D(tex_sampler, coords + vec2(dx, dy));\n" + "}\n" + "\n" + "float IsEdge(in vec2 coords){\n" + " float dxtex = 1.0 / w;\n" + " float dytex = 1.0 / h;\n" + "\n" + " float pix[9];\n" + "\n" + " int k = -1;\n" + " float delta;\n" + "\n" + " // read neighboring pixel intensities\n" + " float pix0 = avg_intensity(get_pixel(coords, -1.0 * dxtex, -1.0 * dytex));\n" + " float pix1 = avg_intensity(get_pixel(coords, -1.0 * dxtex, 0.0 * dytex));\n" + " float pix2 = avg_intensity(get_pixel(coords, -1.0 * dxtex, 1.0 * dytex));\n" + " float pix3 = avg_intensity(get_pixel(coords, 0.0 * dxtex, -1.0 * dytex));\n" + " float pix4 = avg_intensity(get_pixel(coords, 0.0 * dxtex, 0.0 * dytex));\n" + " float pix5 = avg_intensity(get_pixel(coords, 0.0 * dxtex, 1.0 * dytex));\n" + " float pix6 = avg_intensity(get_pixel(coords, 1.0 * dxtex, -1.0 * dytex));\n" + " float pix7 = avg_intensity(get_pixel(coords, 1.0 * dxtex, 0.0 * dytex));\n" + " float pix8 = avg_intensity(get_pixel(coords, 1.0 * dxtex, 1.0 * dytex));\n" + " delta = (abs(pix1 - pix7) + abs(pix5 - pix3) + abs(pix0 - pix8) + abs(pix2 - pix6)) / 4.;\n" + "\n" + " return clamp(5.5*delta,0.0,1.0);\n" + "}\n" + "\n" + "void main(void)\n" + "{\n" + " HueLevels[0] = 0.0;\n" + " HueLevels[1] = 80.0;\n" + " HueLevels[2] = 160.0;\n" + " HueLevels[3] = 240.0;\n" + " HueLevels[4] = 320.0;\n" + " HueLevels[5] = 360.0;\n" + "\n" + " SatLevels[0] = 0.0;\n" + " SatLevels[1] = 0.1;\n" + " SatLevels[2] = 0.3;\n" + " SatLevels[3] = 0.5;\n" + " SatLevels[4] = 0.6;\n" + " SatLevels[5] = 0.8;\n" + " SatLevels[6] = 1.0;\n" + "\n" + " ValLevels[0] = 0.0;\n" + " ValLevels[1] = 0.3;\n" + " ValLevels[2] = 0.6;\n" + " ValLevels[3] = 1.0;\n" + "\n" + " vec4 colorOrg = texture2D(tex_sampler, v_texcoord);\n" + " vec3 vHSV = RGBtoHSV(colorOrg.r, colorOrg.g, colorOrg.b);\n" + " vHSV.x = nearestLevel(vHSV.x, 0);\n" + " vHSV.y = nearestLevel(vHSV.y, 1);\n" + " vHSV.z = nearestLevel(vHSV.z, 2);\n" + " float edg = IsEdge(v_texcoord);\n" + " vec3 vRGB = (edg >= 0.3)? vec3(0.0, 0.0, 0.0) : HSVtoRGB(vHSV.x, vHSV.y, vHSV.z);\n" + " gl_FragColor = vec4(vRGB.x, vRGB.y, vRGB.z, 1.0);\n" + "}\n"; private final int mWidthHandle; private final int mHeightHandle; /** * Constructor of <code>ToonEffect</code>. * * @param ctx The effect context * @param name The effect name */ public ToonEffect(EffectContext ctx, String name) { super(ctx, ToonEffect.class.getName()); init(VERTEX_SHADER, FRAGMENT_SHADER); // Parameters mWidthHandle = GLES20.glGetUniformLocation(mProgram[0], "w"); GLESUtil.glesCheckError("glGetUniformLocation"); mHeightHandle = GLES20.glGetUniformLocation(mProgram[0], "h"); GLESUtil.glesCheckError("glGetUniformLocation"); } /** * {@inheritDoc} */ @Override void applyParameters(int width, int height) { // Set parameters GLES20.glUniform1f(mWidthHandle, (float) width); GLESUtil.glesCheckError("glUniform1f"); GLES20.glUniform1f(mHeightHandle, (float) height); GLESUtil.glesCheckError("glUniform1f"); } }