Many image processing algorithms are defined for grayscale (or else monochromatic) images.



Task

Extend the data storage type defined on this page to support grayscale images.

Define two operations, one to convert a color image to a grayscale image and one for the backward conversion.

To get luminance of a color use the formula recommended by CIE:

 L  =  0.2126 × R   +   0.7152 × G   +   0.0722 × B 

When using floating-point arithmetic make sure that rounding errors would not cause run-time problems or else distorted results when calculated luminance is stored as an unsigned integer.




package raster
 
import (
    "math"
    "math/rand"
)
 
// Grmap parallels Bitmap, but with an element type of uint16
// in place of Pixel.
type Grmap struct {
    Comments   []string
    rows, cols int
    px         []uint16
    pxRow      [][]uint16
}
 
// NewGrmap constructor.
func NewGrmap(x, y int) (b *Grmap) {
    g := &Grmap{
        Comments: []string{creator}, // creator a const in bitmap source file
        rows:     y,
        cols:     x,
        px:       make([]uint16, x*y),
        pxRow:    make([][]uint16, y),
    }
    x0, x1 := 0, x
    for i := range g.pxRow {
        g.pxRow[i] = g.px[x0:x1]
        x0, x1 = x1, x1+x
    }
    return g
}
 
func (b *Grmap) Extent() (cols, rows int) {
    return b.cols, b.rows
}
 
func (g *Grmap) Fill(c uint16) {
    for i := range g.px {
        g.px[i] = c
    }
}
 
func (g *Grmap) SetPx(x, y int, c uint16) bool {
    defer func() { recover() }()
    g.pxRow[y][x] = c
    return true
}
 
func (g *Grmap) GetPx(x, y int) (uint16, bool) {
    defer func() { recover() }()
    return g.pxRow[y][x], true
}
 
// Grmap method of Bitmap, converts (color) Bitmap to (grayscale) Grmap
func (b *Bitmap) Grmap() *Grmap {
    g := NewGrmap(b.cols, b.rows)
    g.Comments = append([]string{}, b.Comments...)
    for i, p := range b.px {
        g.px[i] = uint16((int64(p.R)*2126 + int64(p.G)*7152 + int64(p.B)*722) *
            math.MaxUint16 / (math.MaxUint8 * 10000))
    }
    return g
}
 
// Bitmap method Grmap, converts Grmap to Bitmap.  All pixels in the resulting
// color Bitmap will be (very nearly) shades of gray.
func (g *Grmap) Bitmap() *Bitmap {
    b := NewBitmap(g.cols, g.rows)
    b.Comments = append([]string{}, g.Comments...)
    for i, p := range g.px {
        roundedSum := int(p) * 3 * math.MaxUint8 / math.MaxUint16
        rounded := uint8(roundedSum / 3)
        remainder := roundedSum % 3
        b.px[i].R = rounded
        b.px[i].G = rounded
        b.px[i].B = rounded
        if remainder > 0 {
            odd := rand.Intn(3)
            switch odd + (remainder * 3) {
            case 3:
                b.px[i].R++
            case 4:
                b.px[i].G++
            case 5:
                b.px[i].B++
            case 6:
                b.px[i].G++
                b.px[i].B++
            case 7:
                b.px[i].R++
                b.px[i].B++
            case 8:
                b.px[i].R++
                b.px[i].G++
            }
        }
    }
    return b
}