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