diff --git a/channel/channel.go b/channel/channel.go index ad81439..6ffae34 100644 --- a/channel/channel.go +++ b/channel/channel.go @@ -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: diff --git a/channel/channel_test.go b/channel/channel_test.go index 81d4bde..baaf1c1 100644 --- a/channel/channel_test.go +++ b/channel/channel_test.go @@ -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