blob: 9ca4374a3769c762a984a93021dabb1d9401a335 [file] [log] [blame]
package main
import (
"image"
"image/color"
"math"
)
/*
* Given the adaptation luminance, this function returns the
* threshold of visibility in cd per m^2
* TVI means Threshold vs Intensity function
* This version comes from Ward Larson Siggraph 1997
*/
func VisibilityThreshold(adaptationLuminance float64) float64 {
var r float64
log_a := math.Log10(adaptationLuminance)
if log_a < -3.94 {
r = -2.86
} else if log_a < -1.44 {
r = math.Pow(0.405*log_a+1.6, 2.18) - 2.86
} else if log_a < -0.0184 {
r = log_a - 0.395
} else if log_a < 1.9 {
r = math.Pow(0.249*log_a+0.65, 2.7) - 0.72
} else {
r = log_a - 1.255
}
return math.Pow(10.0, r)
}
// computes the contrast sensitivity function (Barten SPIE 1989)
// given the cycles per degree (cpd) and luminance (lum)
func ContrastSensitivity(cpd, lum float64) float64 {
a := 440.0 * math.Pow(1.0+0.7/lum, -0.2)
b := 0.3 * math.Pow(1.0+100.0/lum, 0.15)
return a * cpd * math.Exp(-b*cpd) * math.Sqrt(1.0+0.06*math.Exp(b*cpd))
}
// Visual Masking Function
// from Daly 1883
func VisualMasking(contrast float64) float64 {
a := math.Pow(392.498*contrast, 0.7)
b := math.Pow(0.0153*a, 4.0)
return math.Pow(1.0+b, 0.25)
}
func Clone(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
dstBounds := srcBounds.Sub(srcBounds.Min)
dst := image.NewNRGBA(dstBounds)
dstMinX := dstBounds.Min.X
dstMinY := dstBounds.Min.Y
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
srcMaxX := srcBounds.Max.X
srcMaxY := srcBounds.Max.Y
switch src0 := img.(type) {
case *image.NRGBA:
rowSize := srcBounds.Dx() * 4
numRows := srcBounds.Dy()
i0 := dst.PixOffset(dstMinX, dstMinY)
j0 := src0.PixOffset(srcMinX, srcMinY)
di := dst.Stride
dj := src0.Stride
for row := 0; row < numRows; row++ {
copy(dst.Pix[i0:i0+rowSize], src0.Pix[j0:j0+rowSize])
i0 += di
j0 += dj
}
case *image.NRGBA64:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
j := src0.PixOffset(x, y)
dst.Pix[i+0] = src0.Pix[j+0]
dst.Pix[i+1] = src0.Pix[j+2]
dst.Pix[i+2] = src0.Pix[j+4]
dst.Pix[i+3] = src0.Pix[j+6]
}
}
case *image.RGBA:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
j := src0.PixOffset(x, y)
a := src0.Pix[j+3]
dst.Pix[i+3] = a
switch a {
case 0:
dst.Pix[i+0] = 0
dst.Pix[i+1] = 0
dst.Pix[i+2] = 0
case 0xff:
dst.Pix[i+0] = src0.Pix[j+0]
dst.Pix[i+1] = src0.Pix[j+1]
dst.Pix[i+2] = src0.Pix[j+2]
default:
dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
dst.Pix[i+1] = uint8(uint16(src0.Pix[j+1]) * 0xff / uint16(a))
dst.Pix[i+2] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
}
}
}
case *image.RGBA64:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
j := src0.PixOffset(x, y)
a := src0.Pix[j+6]
dst.Pix[i+3] = a
switch a {
case 0:
dst.Pix[i+0] = 0
dst.Pix[i+1] = 0
dst.Pix[i+2] = 0
case 0xff:
dst.Pix[i+0] = src0.Pix[j+0]
dst.Pix[i+1] = src0.Pix[j+2]
dst.Pix[i+2] = src0.Pix[j+4]
default:
dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
dst.Pix[i+1] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
dst.Pix[i+2] = uint8(uint16(src0.Pix[j+4]) * 0xff / uint16(a))
}
}
}
case *image.Gray:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
j := src0.PixOffset(x, y)
c := src0.Pix[j]
dst.Pix[i+0] = c
dst.Pix[i+1] = c
dst.Pix[i+2] = c
dst.Pix[i+3] = 0xff
}
}
case *image.Gray16:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
j := src0.PixOffset(x, y)
c := src0.Pix[j]
dst.Pix[i+0] = c
dst.Pix[i+1] = c
dst.Pix[i+2] = c
dst.Pix[i+3] = 0xff
}
}
case *image.YCbCr:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
yj := src0.YOffset(x, y)
cj := src0.COffset(x, y)
r, g, b := color.YCbCrToRGB(src0.Y[yj], src0.Cb[cj], src0.Cr[cj])
dst.Pix[i+0] = r
dst.Pix[i+1] = g
dst.Pix[i+2] = b
dst.Pix[i+3] = 0xff
}
}
case *image.Paletted:
plen := len(src0.Palette)
pnew := make([]color.NRGBA, plen)
for i := 0; i < plen; i++ {
pnew[i] = color.NRGBAModel.Convert(src0.Palette[i]).(color.NRGBA)
}
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
j := src0.PixOffset(x, y)
c := pnew[src0.Pix[j]]
dst.Pix[i+0] = c.R
dst.Pix[i+1] = c.G
dst.Pix[i+2] = c.B
dst.Pix[i+3] = c.A
}
}
default:
i0 := dst.PixOffset(dstMinX, dstMinY)
for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
dst.Pix[i+0] = c.R
dst.Pix[i+1] = c.G
dst.Pix[i+2] = c.B
dst.Pix[i+3] = c.A
}
}
}
return dst
}
func toNRGBA(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
if src0, ok := img.(*image.NRGBA); ok {
return src0
}
}
return Clone(img)
}
func clamp(v float64) uint8 {
return uint8(math.Min(math.Max(v, 0.0), 255.0) + 0.5)
}
func float_clamp(v float64) uint8 {
return clamp(v * 255)
}