Extract an RGB channel of an image

22

7

Given an image, either as input (possibly in RGB triplets) or with the filename as input (you may assume the image has a specific filename, possibly without an extension), output an image representing a single color channel of the image.

You will also take another input, representing which channel to output. The input can be one of any 3 distinct symbols. However, the symbols must be either a string or a number. You cannot take a matrice to apply to the array as input however. (such as {1, 0, 0}, or {0, 1, 0}).

You will output the <input> channel of the input image. You may either save it to a file or output a set of RGB pairs.

Your program should have no limits on the size of the image (in px), and must support either .png, .jpg/.jpeg/.JPG, or RGB triplets as image formats. (it can support as many as you want however)

Test case:

violet original

Red channel:

violet red

Green channel:

violet green

Blue channel:

violet blue

And another test case, entirely of red. Original photo, red, green, and blue. (warning: the plain and red channel hurt to look at for too long)

2 more test cases:

Original, red, green, blue.

Original, red, green, blue.

The latter two test cases are from Images with all colors.

Rɪᴋᴇʀ

Posted 2017-03-29T16:57:24.483

Reputation: 7 410

This is good inspiration for a golfing language that transpiles/compiles to OpenCV operations. – Reinstate Monica - ζ-- – 2017-03-30T23:54:09.273

Answers

2

APL (Dyalog), 7 bytes

I/O: array of RGB triplets

⎕×⊂⎕=⍳3

Try it online!

⍳3 first three integers

⎕= compare numeric input (the chosen channel) to that

 enclose (so each image triplet pairs up this entire triplet)

⎕× multiply numeric input (the array of triplets) with that

Adám

Posted 2017-03-29T16:57:24.483

Reputation: 37 779

12

JavaScript (ES6), 29 bytes

a=>n=>a.map(b=>b.map(c=>c&n))

Input is a 2D array of 24-bit integers (e.g. [[0x0000ff,0x00ff00],[0xff0000,0xffffff]]) and 16711680 for red, 65280 for green, 255 for blue. If this isn't valid, try this instead:

JavaScript (ES6), 48 bytes

a=>n=>a.map(b=>b.map(c=>c.map((d,i)=>i==n?d:0)))

Input is a 3D array of color values and 0 for red, 1 for green, 2 for blue.

ETHproductions

Posted 2017-03-29T16:57:24.483

Reputation: 47 880

1Cheeky input format haha – Conor O'Brien – 2017-03-29T17:24:42.083

10

Mathematica, 13 bytes

ImageMultiply

This should be the legitimate version of JungHwan Min's answer. This function takes the image and one of Red, Green, Blue as input. For example:

enter image description here

As a fun-fact, there's also ColorSeparate which gives you individual channels, but it returns them as single-channel/greyscale images, so you'd need to multiply them by the colour afterwards anyway.

Martin Ender

Posted 2017-03-29T16:57:24.483

Reputation: 184 808

Is there anything Mathematica doesn't have a built-in for? – Brian Minton – 2017-04-06T12:41:27.227

8

Bash + ImageMagick, 35 32 27 bytes

mogrify -channel $1 -fx 0 i

Assumes the image is in the file i and the script takes one of RG, BG, BR for blue, red, and green respectively; outputs to the file i.

betseg

Posted 2017-03-29T16:57:24.483

Reputation: 8 493

beat me to it. I posted a different bash+IM answer below, with an optimization you can steal – Sparr – 2017-03-29T17:20:04.530

@Sparr I made a different golfing – betseg – 2017-03-29T17:29:26.160

you could still save two bytes with mogrify and no ' o'? – Sparr – 2017-03-29T19:37:13.603

7

Spectrum, noncompeting, 1 byte

(Non-competing because this challenge inspired this language.) In reality, Spectrum is an npm library with a language interface for the commands.

I

Takes input as:

<filename>
channel

Call the program as:

cat input.txt | node spectrum.js "I"

Alternatively, you may supply information to the prompts:

λ node spectrum.js "I"
input> Ov3Gm.png
input> r
[ Image { 512x512 } ]

This leaves the image on the stack. To view it, add O at the end, like so:

λ node spectrum.js "IO"
input> Ov3Gm.png
input> r
[]

For some extra fun, try echo filename | node spectrum.js "wO". It performs all three channel isolations at once:

warhol

Conor O'Brien

Posted 2017-03-29T16:57:24.483

Reputation: 36 228

1@ThisGuy Yes, it can do primality checking: n[d1=\[d1-P*][],.g!]#Pdd1-P~%-1=p` – Conor O'Brien – 2017-03-29T23:21:59.653

1@ConorO'Brien did you see my primality tester for ImageMagick's fx operator? – Sparr – 2017-03-30T05:05:33.890

@Sparr no, I haven't . Link? – Conor O'Brien – 2017-03-30T05:08:19.973

7

JavaScript (ES6), 29 bytes

a=>b=>a.map((v,i)=>i%3^b?0:v)

A lot like ETHproductions' answer but with more flexible input than the first method and smaller than the second one.

This defines a function which accepts the image as a 1-dimensional array of numerical colour intensities. This function then returns another function which accepts an integer value representing the desired colour channel.

Colours can be represented by any numerical range as long as 0 indicates a complete absence of colour. E.g. 0.0 - 1.0 or 0 - 255

Examples:

If image data is in the format RGB then calling the function with arguments (imageData)(0) will return the image with only the red channel.

If image data is in the format BGR then calling the function with arguments (imageData)(2) will also return the image with only the red channel.

PragmaticProgrammer

Posted 2017-03-29T16:57:24.483

Reputation: 91

I guess i%3==b&&v also works. – Neil – 2017-04-08T19:47:02.737

6

Python 2, 69 bytes

from cv2 import*
def f(s,i):k=imread(s);k[:,:,[i,i-1]]=0;imwrite(s,k)

Still golfing.

Takes input as filename, n (where n is 0, 1, or 2). Saves the new image over the old one. 0 is green, 1 is red, and 2 is blue. Thanks to @ovs, @Mikhail and @Rod for bytes off.

Rɪᴋᴇʀ

Posted 2017-03-29T16:57:24.483

Reputation: 7 410

Save some bytes by dropping x and replacing k[:,:,x[i][0]]=k[:,:,x[i][1]]=0 with k[:,:,[1,2,0][i]]=k[:,:,i]=0 – ovs – 2017-03-29T17:36:24.193

@ovs thanks!... I had actually just come up with the second suggestion myself, after messing with yours. You ninja'ed me to it though. – Rɪᴋᴇʀ – 2017-03-29T17:53:23.313

You can use from cv2 import* to save few bytes – Rod – 2017-03-29T18:03:22.057

Seems that you can replace k[:,:,i-1]=k[:,:,i]=0 with k[:,:,[i,i-1]]=0 – Mikhail V – 2017-05-28T00:34:06.473

5

CJam, 12 bytes

{3,f=f{f.*}}

An anonymous block that expects a 3D array of RGB triplets and a number between 0 and 2 inclusive on the stack. Leaves the extracted array on the stack afterward.

Red   -> 0
Green -> 1
Blue  -> 2

Could possibly be golfed by using a bizarre input format, but I feel like that's cheating a bit.

Try it online!

The test case is made up just to demonstrate, using the triplets from an actual image would be way too big.

Explanation

3,          e# The range [0 1 2]
  f=        e# Check each for equality with the RGB indicator, gives the indicator array:
            e#  [1 0 0] for 0, [0 1 0] for 1, [0 0 1] for 2
    f{      e# Map on each row of the array using the indicator array as an extra parameter
      f.*   e#  Perform vectorized multiplication of the indicator array with each triplet.
            e#   For red, multiplies red by 1, and green and blue by 0. Does similarly
            e#   for green and blue.
         }  e# (end of block)

Business Cat

Posted 2017-03-29T16:57:24.483

Reputation: 8 927

5

PowerShell, 282 bytes

$x,$y=$args;1..($a=New-Object System.Drawing.Bitmap $x).Height|%{$h=--$_;1..$a.Width|%{$i=("$($a.GetPixel(--$_,$h)|select R,G,B)"|iex)[$y];$r,$g,$b=(((0,0,$i),(0,$i,0))[$y-eq'G'],($i,0,0))[$y-eq'R'];$a.SetPixel($_,$h,[system.drawing.color]::fromargb($r,$g,$b))}};$a.save("$x$y.png")

None of this fancy "golfing the input" or "using built-ins." Phooey on that. ;-)

This here takes the full path of an input PNG (though, strictly speaking, it doesn't need to be a PNG, since JPG, BMP, etc. are supported by .NET, but I've only tried it on PNGs), and one of (R, G, B) for the color channel, then stores those into $x and $y. We then create a New-Object of type System.Drawing.Bitmap of $x and store that into $a.

Then we do a double loop over all pixels. First from 1 up to $a's .Height, setting $h each iteration (it's zero-indexed, so that's why we --$_, which saves bytes over ( .height-1). Inside, we loop from 1 up to $a's .Width.

Each iteration, we're performing a .GetPixel on the particular w,h coordinates, which returns a System.Drawing.Color object. We select out the R G B values thereof. The iex here is a neat trick that turns this into a hashtable (e.g., something like @{R=34; G=177; B=76}) so we can index into that directly with the desired color channel [$y]. That value is stored into $i.

Next, we set three values $r, $g, $b to be the result of some pseudo-ternary operators based on the letter value of $y. So, for example, if $y is R, then the result here is $r=$i, $g=0, $b=0.

Then, we do a .SetPixel back onto that particular w,h coordinate, constructing a new System.Drawing.Color using the static FromARGB (which assumes the alpha to be fully opaque). Once we've finished looping, we then simply .Save the PNG to a new file.

Note: This does take a long while, since it's completely independently looping through every pixel and performing a bunch of calculations and calls each iteration.

Tests:
AdmBorkBork Red AdmBorkBork Green AdmBorkBork Blue

AdmBorkBork

Posted 2017-03-29T16:57:24.483

Reputation: 41 581

3

Bash + ImageMagick, 41 bytes

C="RGB"
mogrify -channel ${C//$1} -fx 0 x

Input is a file named x, command line parameter one of R or G or B

Output overwrites the input file

Differs from betseg's answer in taking more natural single-letter channel parameter. Also overwriting instead of outputting a new file which betseg is free to steal :)

Sparr

Posted 2017-03-29T16:57:24.483

Reputation: 5 758

the $1 needs quotation marks, file names can have spaces – betseg – 2017-03-29T17:31:44.167

@betseg not really, I said you can assume it has a specific filename. If he wants, he can even change it to read from a file name x for 1 byte off. – Rɪᴋᴇʀ – 2017-03-29T17:41:34.183

@Riker oh, I didnt notice that, that helps me too :D – betseg – 2017-03-29T17:42:34.117

3

Chip, 78 bytes

 ~Z~vS
f/F,^-.
e/E|,-Z.
d/D|>zz^.
a/AMx-x-].
b/BMx-]v-'
c/CM]v-'
 >--v'
G/gh/H

Try it online!

Chip is a 2D language that operates on the component bits of each byte in a byte stream.

Overview

This particular solution expects the first byte of input to be the control character--to define which channel(s) to keep--and following is the bytes of image data. The image data must be RGB triplets, one byte per channel, 24 bits per pixel:

|  1  |  2  |  3  |  4  |  5  |  6  |  7  | ... | 3n-2 |  3n  | 3n+1 |
|     |   First Pixel   |   Second Pixel  | ... |      nth Pixel     |
| Ctl | Red | Grn | Blu | Red | Grn | Blu | ... |  Red |  Grn |  Blu |

The control character is interpreted as a bit field, though only the three lowest bits have meaning:

0x1  keep Red channel
0x2  keep Green channel
0x4  keep Blue channel

This means we can use ASCII characters to select which channel we want:

1 means red
2 means blue
4 means green

Or do something fancier:

5 keep red and blue, but not green
7 keep all channels (image is unchanged)
0 drop all channels (image is black)

How it works

This solution is rather jumbled up due to golfing, but here goes:

  1. Read the three low bits of the first byte with C, B, and A, and store those bits in their corresponding Memory cells. Also, Suppress output for the first cycle.
  2. Loop over those three bits repeatedly. If the current bit it is on, close the / switches to output the current input byte, if it is off, open the switches so that we output a zero byte.
  3. Continue until input is exhausted.

Viewing the results

Sure, you could use hexdump or something boring, but it turns out this is (almost) an actual valid image format: binary Portable PixMap.

Just plop the image data from above (minus the control byte, of course) into the file below, adjust the width/height to be valid, and you can view the image in a proper viewer like IrfanView, though simpler ones (like most browser built-ins) can't handle it.

P6
{width as ASCII} {height as ASCII}
255
{binary image data here}

For example (using escapes for the raw data):

P6
3 1
255
\xff\x00\x00\x00\xff\x00\x00\x00\xff

Phlarx

Posted 2017-03-29T16:57:24.483

Reputation: 1 366

3

MATL, 21 13 bytes

Yi3:iml&!*3YG

The color channel should be specified using the following mapping: 1:red, 2:green, 3:blue

The Online interpreters are unable to use imread to read images so here is a slightly modified version where I have hard-coded a random image into the source.

Explanation

        % Implicitly grab first input as a string (filename)
Yi      % Read the image in from a filename or URL
3:      % Push the string literal 'rgb' to the stack
i       % Grab the second input as a number
m       % Check for membership. So for example 2 will yield [false, true, false]
l&!     % Permute the dimensions to turn this 1 x 3 array into a 1 x 1 x 3 array
*       % Multiply this with the input RGB image. It will maintain the channel which
        % corresponds with the TRUE values and zero-out the channels that corresponded to
        % the FALSE values
3YG     % Display the image

Suever

Posted 2017-03-29T16:57:24.483

Reputation: 10 257

2

05AB1E, 16 bytes

vyvy2ô²è}})¹0ègô

Try it online!

Uses hex colors in a 2D array format and [0,1,2] for each channel.

Magic Octopus Urn

Posted 2017-03-29T16:57:24.483

Reputation: 19 422

2

Clojure, 421 332 bytes

-89 bytes by aggressively inlining everything, changing from my ridiculous old method of stripping the channels, and getting rid of an unnecessary accidental import.

(import '(java.awt.image BufferedImage)'(clojure.lang Keyword)'(javax.imageio ImageIO)'(java.io File)'(java.awt Color))(fn[p i](doseq[y(range(.getHeight p))x(range(.getWidth p))](.setRGB p x y(.getRGB(apply #(Color. % %2 %3)(#(let[c(Color. %)](assoc [0 0 0]i(([(.getRed c)(.getGreen c)(.getBlue c)]%)i)))(.getRGB p x y))))))(ImageIO/write p"jpg"(File."./o.jpg")))

Smacked this out half an hour before my shift started with no idea what I was doing. I have little experience with BufferedImage, so there may be a better way of going about this. I'm also abusing Color to convert back-and-forth between integer and individual channel representations of colors.

This is comically huge compared to the other answers for a couple reasons (besides the obvious that Clojure isn't a golfing language):

  • Instead of just manipulating a byte array, this actually takes as input a picture, alters it, and outputs it.

  • I'm using Java-interop, which while handy can be quite verbose at times. The imports alone are larger than many answers.

See the code below for a break down.

(ns bits.restrict-channel
  (:import (java.awt.image BufferedImage)
           (clojure.lang Keyword)
           (javax.imageio ImageIO)
           (java.io File)
           (java.awt Color)))

(defn restrict-channel
  "Accepts a BufferedImage and a index between 0 and 2 (inclusive).
  Removes color from all channels EXCEPT the channel indicated by color-i.
  color-i of 0 = keep red
  color-i of 1 = keep green
  color-i of 2 = keep blue"
  [^BufferedImage image ^long color-i]
  (let [; Turn a number representing RGB into a triplet representing [R G B]
        channels #(let [c (Color. %)]
                    [(.getRed c) (.getGreen c) (.getBlue c)])

        ; Create a new triplet that only contains color in the color-i channel
        zero-channels #(assoc [0 0 0] color-i ((channels %) color-i))]

    ; Loop over each pixel
    (doseq [y (range (.getHeight image))
            x (range (.getWidth image))]

      ; Grab the current color...
      (let [cur-color (.getRGB image x y)]

        ; ... setting it to stripped color triplet returned by zero-channels
        (.setRGB image x y

                          ; This "apply" part is just applying the stripped triplet to the Color constructor
                         ;  This is needed to convert the separate channels into the integer representation that `BufferedImage` uses. 

                 (.getRGB (apply #(Color. % %2 %3) (zero-channels cur-color))))))

    ; Save the result to file
    (ImageIO/write image "jpg" (File. "./o.jpg"))))

Carcigenicate

Posted 2017-03-29T16:57:24.483

Reputation: 3 295

This is either the most glorious or horrific thing I've ever seen. I'm going with the former. – Rɪᴋᴇʀ – 2017-03-29T20:34:24.053

@Riker Definitely the former. I started writing this half an hour before my shift started with no idea how I was going to do it. But it works! – Carcigenicate – 2017-03-29T21:09:18.007

>

  • I actually meant the latter, but glorious messes are a thing too.
  • < – Carcigenicate – 2017-03-29T22:14:01.403

    1

    Lua (love2d Framework), 498 bytes

    I did this more as a exercise for myself, so it's not as short as it could be (though I did try to golf), but I wanted to add it to here cause I think I did good. Even if I'm too late.

    Here the golfed code, under it is the explained and untangled version.

    l=love g,p,rm,bm,gm,s=l.graphics,0,1,1,1,[[uniform float m[4];vec4 effect(vec4 co,Image t,vec2 c,vec2 s){vec4 p=Texel(t,c);p.r=p.r*m[0];p.b=p.b*m[1];p.g=p.g*m[2];return p;}]]t=g.setShader h=g.newShader(s)function l.draw()h:send("m",rm,gm,bm)if p~=0 then t(h)g.draw(p)t()end end function l.filedropped(f)a=f:getFilename()p=g.newImage(f)end function l.keypressed(k)if k=="0"then rm,gm,bm=1,1,1 end if k=="1"then rm,gm,bm=1,0,0 end if k=="2"then rm,gm,bm=0,1,0 end if k=="3"then rm,gm,bm=0,0,1 end end
    

    Here is the code which has to get a *.jpg file dropped into it. After the image was inserted you can press the number buttons for red(1) green(2) or blue(3) channels. Also to see the default picture again press 0. Actually it just shows the picture in the window.

    l=love
    g=l.graphics
    p=0
    rm,bm,gm=1,1,1
    s = [[uniform float m[4];
    vec4 effect(vec4 co,Image t,vec2 c,vec2 s){vec4 p=Texel(t,c);p.r = p.r * m[0];p.b = p.b * m[1]; p.g = p.g * m[2]; return p;}
    ]]
    sh=g.newShader(s)
    
    function l.draw()
      sh:send("m",rm,gm,bm)
      if p~=0 then
        g.setShader(sh)
        g.draw(p)
        g.setShader()
      end
    end
    
    function l.filedropped(f)
      a=f:getFilename()
      p=g.newImage(f)
    end
    
    function l.keypressed(k)
      if k=="0"then rm,gm,bm=1,1,1 end
      if k=="1"then rm,gm,bm=1,0,0 end
      if k=="2"then rm,gm,bm=0,1,0 end
      if k=="3"then rm,gm,bm=0,0,1 end
    end
    

    The important part which does all the work is the shader which is the small string declaration at the beginning or untangled:

    uniform float m[4];
    vec4 effect(vec4 co,Image t,vec2 c,vec2 s)
    {
        vec4 p=Texel(t,c);
        p.r = p.r * m[0];
        p.b = p.b * m[1];
        p.g = p.g * m[2]; 
        return p;
    }
    

    which gets the actual pixel of the image and just shows the channels as needed.

    My test image and the different outputs for the channels (also for sure the other ones) default image and channel images merged

    Lycea

    Posted 2017-03-29T16:57:24.483

    Reputation: 141

    Thank you for your hint, I fixed it, next time I know :) – Lycea – 2017-04-11T15:28:33.787

    No problem! Welcome to ppcg, hope you stick around! – Rɪᴋᴇʀ – 2017-04-11T15:55:11.980

    Also, you can still golf this. You don't need to include the buttons and such. – Rɪᴋᴇʀ – 2017-04-11T15:55:57.687

    0

    C, 60 58 bytes

    main(a,b)char**b;{while(scanf(b[1],&a)>0)printf("%d ",a);}
    

    Image input is a list of numbers (in decimal) between 0 and 255 on stdin, e.g.

    255 0 0  192 192 192  31 41 59 ...
    

    The channel is specified as the first argument to the program, and is one of

    red   -> "%d%*d%*d"
    green -> "%*d%d%*d"
    blue  -> "%*d%*d%d"
    

    Example:

    $ echo "255 0 0" | ./extract "%d%*d%*d"
    255 
    

    nneonneo

    Posted 2017-03-29T16:57:24.483

    Reputation: 11 445