Skip to content

Commit

Permalink
Add extract multiple channels
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonynsimon committed Jul 30, 2019
1 parent eee9188 commit 6defcc6
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
53 changes: 53 additions & 0 deletions channel/channel.go
Expand Up @@ -20,6 +20,59 @@ const (
Alpha
)

var (
allChannels = []Channel{Red, Green, Blue, Alpha}
)

// ExtractMultiple returns a RGBA image containing the values of the selected channels.
//
// Usage example:
//
// result := channel.ExtractMultiple(img, channel.Blue, channel.Alpha)
//
func ExtractMultiple(img image.Image, channels ...Channel) *image.RGBA {
for _, c := range channels {
if c < 0 || 3 < c {
panic(fmt.Sprintf("channel index '%v' out of bounds. Red: 0, Green: 1, Blue: 2, Alpha: 3", c))
}
}

dst := clone.AsRGBA(img)
bounds := dst.Bounds()
dstW, dstH := bounds.Dx(), bounds.Dy()

if bounds.Empty() {
return &image.RGBA{}
}

channelsToRemove := []Channel{}
for _, channel := range allChannels {
shouldRemove := true
for _, enabled := range channels {
if enabled == channel {
shouldRemove = false
break
}
}
if shouldRemove {
channelsToRemove = append(channelsToRemove, channel)
}
}

parallel.Line(dstH, func(start, end int) {
for y := start; y < end; y++ {
for x := 0; x < dstW; x++ {
pos := y*dst.Stride + x*4
for _, c := range channelsToRemove {
dst.Pix[pos+int(c)] = 0x00
}
}
}
})

return dst
}

// Extract returns a grayscale image containing the values of the selected channel.
//
// Usage example:
Expand Down
118 changes: 118 additions & 0 deletions channel/channel_test.go
Expand Up @@ -7,6 +7,124 @@ import (
"github.com/anthonynsimon/bild/util"
)

func TestExtractMultiple(t *testing.T) {
cases := []struct {
description string
channels []Channel
img image.Image
expected *image.RGBA
}{
{
description: "red empty image",
channels: []Channel{Red},
img: &image.RGBA{},
expected: &image.RGBA{},
},
{
description: "red empty pix",
channels: []Channel{Red},
img: &image.RGBA{
Rect: image.Rect(0, 0, 0, 0),
Stride: 0 * 4,
Pix: []uint8{},
},
expected: &image.RGBA{},
},
{
description: "red single pixel",
channels: []Channel{Red},
img: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{
0x20, 0x60, 0x90, 0xFF,
},
},
expected: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1,
Pix: []uint8{
0x20, 0x00, 0x00, 0x00,
}},
},
{
description: "green single pixel",
channels: []Channel{Green},
img: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{
0x20, 0x60, 0x90, 0xFF,
},
},
expected: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1,
Pix: []uint8{
0x00, 0x60, 0x00, 0x00,
}},
},
{
description: "blue single pixel",
channels: []Channel{Blue},
img: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{
0x20, 0x60, 0x90, 0xFF,
},
},
expected: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1,
Pix: []uint8{
0x00, 0x00, 0x90, 0x00,
}},
},
{
description: "alpha single pixel",
channels: []Channel{Alpha},
img: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{
0x20, 0x60, 0x90, 0xFF,
},
},
expected: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1,
Pix: []uint8{
0x00, 0x00, 0x00, 0xFF,
}},
},
{
description: "multiple single pixel",
channels: []Channel{Red, Alpha},
img: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{
0x20, 0x60, 0x90, 0xFF,
},
},
expected: &image.RGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1,
Pix: []uint8{
0x20, 0x00, 0x00, 0xFF,
}},
},
}

for _, c := range cases {
actual := ExtractMultiple(c.img, c.channels...)
if !util.RGBAImageEqual(actual, c.expected) {
t.Errorf("%s: expected: %#v, actual %#v", "Extract "+c.description, c.expected, actual)
}
}
}

func TestExtract(t *testing.T) {
cases := []struct {
description string
Expand Down

0 comments on commit 6defcc6

Please sign in to comment.