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) }