The Chroma Key to Success

23

3

The RGB color value #00FF00 is a rather important one: it is used to make movies, TV shows, weather announcements, and more. It is the famous "TV green" or "green screen" color.

The Challenge

Your task is to write a program that takes two input images, both in PNG format (or in your image library's image object type) and of the same dimensions. One image can be any old image. The other one is the image that will have a background of the color #00FF00. The output image will consist of the second image overlaid over the first, with no #00FF00 color present (except in the first image). Input and output may be done with files, a GUI, etc. You are allowed to take an array of RGB values as input, as seen here. You may assume that an image has only pixels of full opacity.

Basically...

Make a program that takes every #00FF00 pixel in one image and replace it with the corresponding pixel in the background image.

Test Cases

Generously provided by @dzaima: Background:
my profile image
Foreground:
dennis
Output:
output


Of course, standard loopholes are strictly forbidden. This includes using an online resource to do it for you.
This is , so may the shortest code win and the best programmer prosper...

ckjbgames

Posted 2017-07-16T19:14:55.030

Reputation: 1 287

2May we take an image object in the native format of the language/library as input, or do we have to read the image via filename? – notjagan – 2017-07-16T19:20:12.907

@notjagan You may take image objects as input. – ckjbgames – 2017-07-16T19:21:42.530

Can you add an example? – ovs – 2017-07-16T19:22:23.697

@ovs i need someone else to provide something – ckjbgames – 2017-07-16T19:23:15.833

3Is I/O of arrays of arrays of integers acceptable or are we actually restricted to some other set of image I/O? – Jonathan Allan – 2017-07-16T21:26:04.460

Can we take the chroma-key (0x00ff00) as a function arg? I'm assuming the constant has to be hard-coded into the function, but I could save bytes by having the caller put it in a register for me, making the function more flexible/generic. – Peter Cordes – 2017-07-17T16:58:33.953

1@PeterCordes I will allow that. – ckjbgames – 2017-07-17T19:27:26.383

@JonathanAllan Yes. Just explain the format used. – ckjbgames – 2017-07-17T19:27:43.073

@ckjbgames: Are you sure that's a good idea? Several existing answers could probably be smaller if they got the caller to put the colorkey into a variable in the form they want it (e.g. a list like [0,255,0]). I'd actually recommend that you don't change the rules at this point, from what people were assuming. Unless it's normal to allow that kind of thing for codegolf, and the other answers should have thought of that :P – Peter Cordes – 2017-07-17T19:33:40.077

1@PeterCordes ok – ckjbgames – 2017-07-17T19:35:12.487

@PeterCordes Done – ckjbgames – 2017-07-17T19:36:14.893

Oh, I think you were misunderstanding what I was asking. I was talking about the color value to match, not the array of image pixels. I was asking about replacing the 0xff00 32-bit constant with a one-byte reference to an argument passed by the caller (which on second thought I decided would be a bad change, because it's basically cheating by offloading part of the function to the caller, even if you could justify it in a chromakey function). Your edit to the question to allow taking images as arrays of pixels was good, but nothing to do with what I was talking about. – Peter Cordes – 2017-07-17T19:40:32.767

@PeterCordes Oh. Undo. – ckjbgames – 2017-07-17T19:44:52.297

Answers

14

x86-64 (and x86-32) machine code, 13 15 13 bytes

changelog:

  1. Bugfix: the first version was only checking G=0xff, not requiring R and B to be 0. I changed to modifying the background in place so I could use lodsd on the foreground to have fg pixels in eax for the short-form cmp eax, imm32 encoding (5 bytes), instead of cmp dh,0xff (3 bytes).

  2. Save 2 bytes: noticed that modifying the bg in place allowed using a memory operand for cmov, saving a 2-byte mov load (and saving a register, in case that matters).


This is a function following the x86-64 System V calling convention, callable directly from C or C++ (on x86-64 non-Windows systems) with this signature:

void chromakey_blend_RGB32(uint32_t *background /*rdi*/,
                     const uint32_t *foreground /*rsi*/,
                  int dummy, size_t pixel_count /*rcx*/);

The image format is RGB0 32bpp, with the green component at the 2nd lowest memory address within each pixel. The foreground background image is modified in-place. pixel_count is rows*columns. It doesn't care about rows/columns; it just chromekey blends however many dwords of memory you specify.

RGBA (with A required to be 0xFF) would require using a different constant, but no change in function size. Foreground DWORDs are compared for exact equality against an arbitrary 32-bit constant stored in 4 bytes, so any pixel-order or chroma-key colour can be easily supported.

The same machine code also works in 32-bit mode. To assemble as 32-bit, change rdi to edi in the source. All other registers that become 64-bit are implicit (lodsd/stosd, and loop), and the other explicit regs stay 32-bit. But note that you'll need a wrapper to call from 32-bit C, because none of the standard x86-32 calling conventions use the same regs as x86-64 SysV.

NASM listing (machine-code + source), commented for asm beginners with descriptions of what the more complex instructions do. (Duplicating the instruction reference manual is bad style in normal usage.)

 1                       ;; inputs:
 2                       ;; Background image pointed to by RDI, RGB0 format  (32bpp)
 3                       ;; Foreground image pointed to by RSI, RGBA or RGBx (32bpp)
 4          machine      ;; Pixel count in RCX
 5          code         global chromakey_blend_RGB32
 6          bytes        chromakey_blend_RGB32:
 7 address               .loop:                      ;do {
 8 00000000 AD               lodsd                   ; eax=[rsi], esi+=4. load fg++
 9 00000001 3D00FF0000       cmp    eax, 0x0000ff00  ; check for chromakey
10 00000006 0F4407           cmove  eax, [rdi]       ; eax = (fg==key) ? bg : fg
11 00000009 AB               stosd                   ; [rdi]=eax, edi+=4. store into bg++
12 0000000A E2F4             loop .loop              ;} while(--rcx)
13                       
14 0000000C C3               ret

##  next byte starts at 0x0D, function length is 0xD = 13 bytes

To get the original NASM source out of this listing, strip the leading 26 characters of each line with <chromakey.lst cut -b 26- > chromakey.asm. I generated this with
nasm -felf64 chromakey-blend.asm -l /dev/stdout | cut -b -28,$((28+12))- NASM listings leave more blank columns than I want between the machine-code and source. To build an object file you can link with C or C++, use nasm -felf64 chromakey.asm. (Or yasm -felf64 chromakey.asm).

untested, but I'm pretty confident that the basic idea of load / load / cmov / store is sound, because it's so simple.

I could save 3 bytes if I could require the caller to pass the chroma-key constant (0x00ff00) as an extra arg, instead of hard-coding the constant into the function. I don't think the usual rules allow writing a more generic function that has the caller set up constants for it. But if it did, the 3rd arg (currently dummy) is passed in edx in the x86-64 SysV ABI. Just change cmp eax, 0x0000ff00 (5B) to cmp eax, edx (2B).


With SSE4 or AVX, you might do this faster (but larger code size) with pcmpeqd and blendvps to do a 32-bit element size variable-blend controlled by the compare mask. (With pand, you could ignore the high byte). For packed RGB24, you might use pcmpeqb and then 2x pshufb+pand to get TRUE in bytes where all 3 components of that pixel match, then pblendvb.

(I know this is code-golf, but I did consider trying MMX before going with scalar integer.)

Peter Cordes

Posted 2017-07-16T19:14:55.030

Reputation: 2 810

Could you send me an executable made with this machine code? – ckjbgames – 2017-07-17T19:46:38.453

x86_32, please. – ckjbgames – 2017-07-17T19:46:46.427

@ckjbgames: I haven't written a caller that loads / saves images, just the modify-pixels-in-place part. I'd have to do that before it would make any sense to build an executable. But if I did, what kind of executable? Windows PE32? Linux ELF32? FreeBSD?? – Peter Cordes – 2017-07-17T19:48:18.933

ELF32, if you will. – ckjbgames – 2017-07-17T19:51:10.607

@ckjbgames: If I find time, I'll look for an image-loading library and write something. I added a paragraph about how to turn the listing back into code you can assemble with nasm -felf32. (For 32-bit, you'll also need a wrapper function to call from C, because it's still using the same registers as the x86-64 SysV ABI.) – Peter Cordes – 2017-07-17T20:16:36.203

Will you show me the wrapper function? – ckjbgames – 2017-07-18T17:27:39.283

@ckjbgames: I haven't written one, but it would be very similar to the wrapper I made for my adler32 answer. I put it on github, here's a link to that function. I'd really recommend that you just assemble it as 64-bit and call it directly from C, because my function obeys the x86-64 SysV calling convention.

– Peter Cordes – 2017-07-18T17:34:31.437

13

Mathematica 57 35 bytes

update: by default, a green background is removed using RemoveBackground. The first submission included the unnecessary second parameter, `{"Background",Green}".


#~ImageCompose~RemoveBackground@#2&

Removes the background of image 2 and composes the result with image 1.


Example

i1

The following, in prefix rather than infix form, shows more clearly how the code works.

i2

DavidC

Posted 2017-07-16T19:14:55.030

Reputation: 24 524

4Would this work for images where it's not the "background" that's green? (There seems to be a small patch of green left in your output) – DBS – 2017-07-17T17:29:39.247

If there were a green "island" in the picture, the additional parameter, `{"Background", Green}", would be required, which would raise the total to 57 bytes. That was my first submission. Because I see no green isolated in the foreground of the picture, that parameter was dropped. – DavidC – 2017-07-18T06:39:53.977

11

Python 3 + numpy, 59 bytes

lambda f,b:copyto(f,b,'no',f==[0,255,0])
from numpy import*

Try it online!

Input is given in the format of a numpy array, with integer triplets representing pixels (where #00FF00 in hex color code is equivalent to [0, 255, 0]). The input array is modified in place, which is allowed per meta.

Example Images

Input (from the question)

Background:

ckjbgames' profile picture

Foreground:

Dennis' profile picture

Foreground image after running the function:

Merged image with #00FF00 replaced with background pixels

Reference Implementation (uses opencv to read image files)

g = lambda f,b:copyto(f,b,'no',f==[0,255,0])
from numpy import*

import cv2

f = cv2.imread("fg.png")
b = cv2.imread("bg.png")

g(f, b)

cv2.imshow("Output", f)
cv2.imwrite("out.png", f)

Displays the image to the screen and writes it to an output file.

notjagan

Posted 2017-07-16T19:14:55.030

Reputation: 4 011

17What's with all the red dots on the resulting image? – Yytsi – 2017-07-16T20:56:51.370

1I've asked about I/O - this seems to comply with the current wording (i.e. "your library"), if so, does cv2 itself require the numpy import? If not you could do it in 54 by not using any numpy functions, and not importing numpy: lambda f,b:[x[list(x[0])==[0,255,0]]for x in zip(f,b)]. If list of lists of integers are actually acceptable too then you could do it in 48 with lambda f,b:[x[x[0]==[0,255,0]]for x in zip(f,b)] – Jonathan Allan – 2017-07-16T21:30:42.550

..in fact even if numpy is required for cv2 to perform the conversion I still think you could do the 54 byte version, since we don't need to import cv2 for the challenge. – Jonathan Allan – 2017-07-16T21:53:46.283

5If G == 255, the value gets replaced even if R and B are not zero which leads to the red dots. This also happens for the other bands even tough that's less visible. So it performs the logic checks independently of each other and swaps out single channels even if only one of the conditions is met. E.g. if a pixel is [0 255 37] the red and green bands will be replaced. – Leander Moesinger – 2017-07-17T07:34:45.147

2@LeanderMoesinger: Well spotted. I had that bug in mine, too >.<; IDK why I thought that only checking green=0xFF while ignoring R and B was correct! – Peter Cordes – 2017-07-17T20:18:35.190

9

Processing, 116 99 bytes

PImage f(PImage b,PImage f){int i=0;for(int c:f.pixels){if(c!=#00FF00)b.pixels[i]=c;i++;}return b;}

Unfortunately, processing doesn't support java 8 stuff, like lambdas.

Example implementation: (saves image as out.png and also draws it on screen)

PImage bg;
void settings() {
  bg = loadImage("bg.png");
  size(bg.width,bg.height);
}
void setup() {
  image(f(bg, loadImage("fg.png")), 0, 0);
  save("out.png");
}
PImage f(PImage b,PImage f){int i=0;for(int c:f.pixels){if(c!=#00FF00)b.pixels[i]=c;i++;}return b;}

dzaima

Posted 2017-07-16T19:14:55.030

Reputation: 19 048

You can get rid of the settings() and setup() functions and just run the code directly. – Kevin Workman – 2017-07-17T18:44:48.440

@KevinWorkman I have settings and setup there so it'd display the image on screen, which otherwise wouldn't be possible – dzaima – 2017-07-17T18:45:59.870

Is #ff00 or 0xff00 the same as #00ff00 in Processing? – Peter Cordes – 2017-07-18T17:36:18.357

@PeterCordes #FF00 gives syntax error, sadly, and #00FF00 == 0xFF00FF00, so 0xFF00 doesn't work as it checks for alpha value 0 – dzaima – 2017-07-18T17:38:21.130

@dzaima: Can you take your images in RGB0 format, so 0x0000FF00 is the bit-pattern you're looking for? – Peter Cordes – 2017-07-18T17:48:42.440

@PeterCordes I doubt that Processing would not modify it to be full opacity – dzaima – 2017-07-18T17:51:20.707

In RGB0, the 4th byte is padding, not opacity. It's just a padded RGB format without alpha. (If Processing doesn't support it natively, though, then you'd have to accept your images as arrays of pixel data, instead of PImage, so that would be a somewhat different answer.) – Peter Cordes – 2017-07-18T17:53:14.083

Can int i=0;for(int c:f.pixels){if(c!=#00FF00)b.pixels[i]=c;i++;} be golfed into int i=0;for(int c:f.pixels)if(c!=#00FF00)b.pixels[i++]=c;? – user41805 – 2017-12-23T17:36:38.100

@Cowsquack no, then i only gets incremented if c!=#00FF00, not always. – dzaima – 2017-12-23T17:52:46.647

6

Bash + ImageMagick, 45 bytes

convert $1 $2 -transparent lime -composite x:

Takes two images as arguments and displays the output on the screen. Change x: to $3 to write to a third file argument instead. The method is simple: read the "background" image; read the "foreground" imagek; reinterpret the color "lime" (#00ff00) as transparency in the second image; then composite the second image onto the first and output.

ImageMagick: 28 bytes?

I could have submitted this as an ImageMagick answer but it's not clear how to deal with the arguments. If you want to posit that ImageMagick is a stack-based language (which is kinda sorta not really true but almost... it's weird) then -transparent lime -composite is a function that expects two images on the stack and leaves one merged image on the stack... maybe that's good enough to count?

hobbs

Posted 2017-07-16T19:14:55.030

Reputation: 2 403

3

MATL, 40 37 31 bytes

,jYio255/]tFTF1&!-&3a*5M~b*+3YG

Example run with the offline interpreter. The images are input by their URL's (local filenames could also be provided).

enter image description here

Explanation

,        % Do this twice
  j      %   Input string with URL or filename
  Yi     %   Read image as an M×N×3 uint8 array
  o      %  Convert to double
  255/   %   Divide by 255
]        % End
t        % Duplicate the second image
FTF      % Push 1×3 vector [0 1 0]
1&!      % Permute dimensions to give a 1×1×3 vector
-        % Subtract from the second image (M×N×3 array), with broadcast
&3a      % "Any" along 3rd dim. This gives a M×N mask that contains
         % 0 for pure green and 1 for other colours
*        % Mulltiply. This sets green pixels to zero
5M       % Push mask M×N again
~        % Negate
b        % Bubble up the first image
*        % Multiply. This sets non-green pixels to zero
+        % Add the two images
3YG      % Show image in a window

Luis Mendo

Posted 2017-07-16T19:14:55.030

Reputation: 87 464

3

Pyth, 27 bytes

M?q(Z255Z)GHG.wmgVhded,V'E'

It takes quoted input. The input are the two paths of the image files. Output a file o.png Unfortunately that cannot be tested on the online interpreter for safety reason (' is disabled on it). You will need to get Pyth on your computer to test it.

Explanation

M?q(Z255Z)GHG                  # Define a function g which takes two tuples G and H and returns G if G != (0, 255, 0), H otherwise
                       V'E'    # Read the images. They are returned as lists of lists of colour tuples
                      ,        # Zip both images
               m  hded         # For each couple of lists in the zipped list...
                gV             # Zip the lists using the function g
             .w                # Write the resulting image to o.png

Jim

Posted 2017-07-16T19:14:55.030

Reputation: 1 442

The chroma-key blend function on its own is 13 bytes, same as my x86 machine-code answer. I didn't realize earlier that this was a complete program handing image I/O as well. – Peter Cordes – 2017-07-30T03:03:32.817

2

Matlab 2016b and Octave, 62 59 bytes

Input: A = MxNx3 unit8 foreground matrix, B = MxNx3 unit8 background matrix.

k=sum(A(:,:,2)-A(:,:,[1 3]),3)==510.*ones(1,1,3);A(k)=B(k);

Output: A = MxNx3 unit8 matrix

Sample use:

A = imread('foreground.png');
B = imread('backgroundimg.png');

k=sum(A(:,:,2)-A(:,:,[1 3]),3)==510.*ones(1,1,3);A(k)=B(k);

imshow(A)

Leander Moesinger

Posted 2017-07-16T19:14:55.030

Reputation: 300

1

C++, 339 bytes

This uses CImg, and it can take files in other formats, too. The result is displayed in a window.

#include<CImg.h>
using namespace cimg_library;
int main(int g,char** v){CImg<unsigned char> f(v[1]),b(v[2]);for(int c=0;c<f.width();c++){for(int r=0;r<f.height();r++){if((f(c,r)==0)&&(f(c,r,0,1)==255)&&(f(c,r,0,2)==0)){f(c,r)=b(c,r);f(c,r,0,1)=b(c,r,0,1);f(c,r,0,2) = b(c,r,0,2);}}}CImgDisplay dis(f);while(!dis.is_closed()){dis.wait();}}

Compile with g++ chromakey.cpp -g -L/usr/lib/i386-linux-gnu -lX11 -o chromakey -pthread.

ckjbgames

Posted 2017-07-16T19:14:55.030

Reputation: 1 287

1

R, 135 bytes

function(x,y,r=png::readPNG){a=r(x);m=apply(a,1:2,function(x)all(x==0:1));for(i in 1:4)a[,,i][m]=r(y)[,,i][m];png::writePNG(a,"a.png")}

Anonymous function, takes 2 png file paths as arguments and output a png picture called a.png.

Slightly ungolfed, with explanations:

function(x,y){
    library(png)
    # readPNG output a 3D array corresponding to RGBA values on a [0,1] scale:
    a = readPNG(x)
    # Logical mask, telling which pixel is equal to c(0, 1, 0, 1), 
    # i.e. #00FF00 with an alpha of 1:
    m = apply(a, 1:2, function(x) all(x==0:1))
    # For each RGB layer, replace that part with the equivalent part of 2nd png:
    for(i in 1:4) a[,,i][m] = readPNG(y)[,,i][m]
    writePNG(a,"a.png")
}

plannapus

Posted 2017-07-16T19:14:55.030

Reputation: 8 610

1

SmileBASIC, 90 bytes whats the key

DEF C I,J
DIM T[LEN(I)]ARYOP.,T,I,16711936ARYOP 2,T,T,T
ARYOP 6,T,T,0,1ARYOP 5,I,I,J,T
END

I is the foreground and output, J is the background. Both are integer arrays of pixels, in 32 bit ARGB format.

Ungolfed

DEF C IMAGE,BACKGROUND 'function
 DIM TEMP[LEN(IMAGE)]  'create array "temp"
 ARYOP #AOPADD,TEMP,IMAGE,-RGB(0,255,0)    'temp = image - RGB(0,255,0)
 ARYOP #AOPCLP,TEMP,TEMP,-1,1              'temp = clamp(temp, -1, 1)
 ARYOP #AOPMUL,TEMP,TEMP,TEMP              'temp = temp * temp
 ARYOP #AOPLIP,IMAGE,IMAGE,BACKGROUND,TEMP 'image = linear_interpolate(image, background, temp)
END

Explanation:

ARYOP is a function that applies a simple operation to every element in an array.
It is called like ARYOP mode, output_array, input_array_1, input_array_2, ...

First, to determine which pixels in the image are green, -16711936 (the RGBA representation of the green color) is subtracted from each pixel in the foreground image. This gives an array where 0 represents green pixels, and any other number represents non-green pixels.

To convert all nonzero values to 1, they are squared (to remove negative numbers), then clamped to between 0 and 1.

This results in an array with only 0s and 1s.
0s represent green pixels in the foreground image, and should be replaced with pixels from the background.
1s represent non-green pixels, and those will need to be replaced with pixels from the foreground.

This can easily be done using linear interpolation.

12Me21

Posted 2017-07-16T19:14:55.030

Reputation: 6 110

0

PHP, 187 bytes

for($y=imagesy($a=($p=imagecreatefrompng)($argv[1]))-1,$b=$p($argv[2]);$x<imagesx($a)?:$y--+$x=0;$x++)($t=imagecolorat)($b,$x,$y)-65280?:imagesetpixel($b,$x,$y,$t($a,$x,$y));imagepng($b);

assumes 24bit PNG files; takes file names from command lines arguments, writes to stdout.
Run with -r.

breakdown

for($y=imagesy(                                 # 2. set $y to image height-1
        $a=($p=imagecreatefrompng)($argv[1])    # 1. import first image to $a
    )-1,
    $b=$p($argv[2]);                            # 3. import second image to $b
    $x<imagesx($a)?:                            # Loop 1: $x from 0 to width-1
        $y--+$x=0;                              # Loop 2: $y from height-1 to 0
        $x++)
            ($t=imagecolorat)($b,$x,$y)-65280?:     # if color in $b is #00ff00
                imagesetpixel($b,$x,$y,$t($a,$x,$y));   # then copy pixel from $a to $b
imagepng($b);                                   # 5. output

Titus

Posted 2017-07-16T19:14:55.030

Reputation: 13 814

0

JavaScript (ES6), 290 bytes

a=>b=>(c=document.createElement`canvas`,w=c.width=a.width,h=c.height=a.height,x=c.getContext`2d`,x.drawImage(a,0,0),d=x.getImageData(0,0,w,h),o=d.data,o.map((_,i)=>i%4?0:o[i+3]=o[i++]|o[i++]<255|o[i]?255:0),x.drawImage(b,0,0),createImageBitmap(d).then(m=>x.drawImage(m,0,0)||c.toDataURL()))

Takes input as two Image objects (in currying syntax), which can be created with an HTML <image> element. Returns a Promise that resolves to the Base64 data URL of the resulting image, which can be applied to the src of an <image>.

The idea here was to set the alpha value for each #00FF00 pixel to 0 and then paint the foreground, with its background keyed out, on top of the background.

Test Snippet

Including the foreground and background by their data URLs was too large to post here, so it was moved to CodePen:

Try it online!

Justin Mariner

Posted 2017-07-16T19:14:55.030

Reputation: 4 746

0

OSL, 83 bytes

shader a(color a=0,color b=0,output color c=0){if(a==color(0,1,0)){c=b;}else{c=a;}}

Takes two inputs. The first is the foreground, and the second, the background.

Scott Milner

Posted 2017-07-16T19:14:55.030

Reputation: 1 806