public Layer bumpSpecular(Channel bumpmap, float lx, float ly, float lz, float shadow, float light_r, float light_g, float light_b, int specular)
{
if(!(bumpmap.getWidth() == width && bumpmap.getHeight() == height))
throw new Exception("bumpmap size does not match layer size");
float lnorm = (float)Math.Sqrt(lx*lx + ly*ly + lz*lz);
float nz = 4*(1f/Math.Min(width, height));
float nzlz = nz*lz;
float nz2 = nz*nz;
int power = 2<<specular;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float nx = bumpmap.getPixelWrap(x + 1, y) - bumpmap.getPixelWrap(x - 1, y);
float ny = bumpmap.getPixelWrap(x, y + 1) - bumpmap.getPixelWrap(x, y - 1);
float brightness = nx*lx + ny*ly;
float costheta = (brightness + nzlz)/((float)Math.Sqrt(nx*nx + ny*ny + nz2)*lnorm);
float highlight;
if (costheta > 0) {
highlight = (float)Math.Pow(costheta, power);
} else {
highlight = 0;
}
putPixelClip(x, y,
(r.getPixel(x, y) + highlight*light_r)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow),
(g.getPixel(x, y) + highlight*light_g)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow),
(b.getPixel(x, y) + highlight*light_b)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow));
}
}
return this;
}