RGB-Channel-Shifting

How to glitch images using RGB channel shifting

Channel shifting is the process of taking the red, green or blue values of pixels in an image and applying those values to pixels in different positions on the image. In this tutorial we are going to accomplish this effect using the Processing language.

If you don’t have the time or inclination to glitch images using scripts you can use dedicated apps such as Glitch for iOS.

glitch4ios

To get started download and install the latest version of Processing, version 3.1.1 at the time of writing this. I’ve written a channel shifting script you can download here, you’ll have to unzip it once it’s downloaded. Once you’ve installed and opened Processing you can load the script by accessing the menu.

File > Open

And navigating to the ChannelShiftGlitch.pde script file. In the script, which are referred to as sketches in Processing, you’ll need to change the following lines to point the script at the image you want to glitch. It’s easiest to place the image in the same directory as the script file.

// image path is relative to sketch directory
String imgFileName = "MyImage";
String fileType = "jpg";

I’ve set up some settings variables to make the script easier to use, you can see these towards the top of the script under the comment of script settings.

// repeat the process this many times
int iterations = 5;

// use result image as new source for iterations
boolean recursiveIterations = false;

// shift the image vertically true/false
boolean shiftVertically = false;

// shift the image horizontally true/false
boolean shiftHorizontally = true;

This script is able to apply the same channel shifting effect multiple times, the number of times is specified by the iterations variable, currently set to 5. This variable drives a for loop around the channel shifting code as seen below.

// repeat the process according 
// to the iterations variable
    for(int i = 0;i < iterations;i++)
    {
      // generate random numbers 
      // for which channels to swap
      int sourceChannel = int(random(3));
      int targetChannel = int(random(3));

You can also see in this code where the script generates a random number which will determine which of the three channels, red, green or blue are used as a source, and which of the three channels are used as a target. Next the script sets up the shifting positions, how far vertically and how far horizontally the channel should be shifted. These are either 0 if shifting is set to false for that plane (determined by the shiftHorizontally and shiftVertically settings), or a random number between the 0 and the height or width of the image.

// start with no horizontalShift 
int horizontalShift = 0; 

// if shiftHorizontally is true generate a 
// random number to shift horizontally by
if(shiftHorizontally)
  horizontalShift = int(random(width));
      
// start with no verticalShift 
int verticalShift = 0;
      
// if shiftVertically is true generate a 
// random number to shift vertically by
if(shiftVertically)
  verticalShift = int(random(height));

Next the script calls the main copyChannel method. This method accepts pixel arrays of the source and target images and will copy one channel to another from one part of the image to another and wrap around both horizontally and vertically if it runs out of space.

// shift the channel
copyChannel(
  sourceImg.pixels, 
  targetImg.pixels, 
  verticalShift, 
  horizontalShift, 
  sourceChannel, 
  targetChannel
  );

The method starts by starting a counter to loop through the rows of pixels in the image, top-to-bottom. This counter is added to the sourceYOffset variable to apply the vertical shift. If the vertical shift plus the counter is higher than the image height we subtract the image height to wrap the shift around to the top of the image.

// starting at the sourceY and pointerY
// loop through the rows
for(int y = 0; y < height; y++) { 
  // add y counter to sourceY 
  int sourceYOffset = sourceY + y; 

  // wrap around the top of the 
  // image if we've hit the bottom 
  if(sourceYOffset >= height)
    sourceYOffset -= height;

Within the row loop the script starts another counter to loop through the columns in that row, left-to-right. It also adds that counter to the sourceXOffset to apply the horizontal shift. If the horizontal shift plus the counter is wider than the image width we subtract the image width to wrap the shift around to the left of the image.

// starting at the sourceX and pointerX 
// loop through the pixels in this row
for(int x = 0; x < width; x++) 
{ 
  // add x counter to sourceX 
  int sourceXOffset = sourceX + x; 

  // wrap around the left side of the 
  // image if we've hit the right side 
  if(sourceXOffset >= width)
    sourceXOffset -= width;

Processing stores image pixels in an array as illustrated in the image below.

pixelarray

In order to access a pixel at specific x/y coordinates in the image we use the formula below.

y * width + x

Next the script isolates the RGB (red, green, blue) values for both the source and target pixels by using the formula above to access the pixel and then some Processing methods to extract the separate RGB channel values.

// get the color of the source pixel
color sourcePixel = 
  sourcePixels[sourceYOffset * width + sourceXOffset];
            
// get the RGB values of the source pixel
float sourceRed = red(sourcePixel);
float sourceGreen = green(sourcePixel);
float sourceBlue = blue(sourcePixel);
   
// get the color of the target pixel
color targetPixel = targetPixels[y * width + x]; 

// get the RGB of the target pixel
// two of the RGB channel values are required 
// to create the new target color
// the new target color is two of the target
// RGB channel values and one RGB channel value 
// from the source
float targetRed = red(targetPixel);
float targetGreen = green(targetPixel);
float targetBlue = blue(targetPixel);

Now that the script has the RGB of the source pixel and RGB of the target pixel we can proceed to shift one of the channels. We use a switch statement for this, deciding which source channel to use based on the sourceChannel variable which has a holds a random number we generated earlier, either 0, 1 or 2.

// create a variable to hold 
// the new source RGB channel value
float sourceChannelValue = 0;
            
// assigned the source channel value 
// based on sourceChannel random number passed in
switch(sourceChannel)
{
  case 0:
    // use red channel from source
    sourceChannelValue = sourceRed;
    break;
  case 1:
    // use green channel from source
    sourceChannelValue = sourceGreen;
    break;
  case 2:
    // use blue channel from source
    sourceChannelValue = sourceBlue;
    break;
}

After selecting a source channel we apply that channel value to either the red, green or blue channel of the target pixel, again using a switch statement, this time based on the targetChannel variable.

// assigned the source channel value to a 
// target channel based on targetChannel 
// random number passed in
switch(targetChannel)
{
  case 0:
    // assign value to target red channel
    targetPixels[y * width + x] = 
      color(sourceChannelValue, 
        targetGreen, 
        targetBlue);
    break;
 case 1:
    // assign value to target green channel
    targetPixels[y * width + x] = 
      color(targetRed, 
        sourceChannelValue, 
        targetBlue);
    break;
 case 2:
    // assign value to target blue channel
    targetPixels[y * width + x] = 
      color(targetRed, 
        targetGreen, 
        sourceChannelValue);
    break;
}

That’s it for the copyChannel method. The channel has been shifted in the target image at this point. Back in the main draw method of the script there is an if statement that determines whether or not the next iteration (if the iterations variable is to to greater than 1) will use the original image as a source, or use the new shifted image as a source.

// use the target as the new source 
// for the next iteration
if(recursiveIterations)
  sourceImg.pixels = targetImg.pixels;

Using the original image as a source for more than 3 iterations is rather pointless because there are only three channels in the original image to shift to around, always resulting in three shifted ghost images. So if you set iterations higher than 3 you should probably set recursiveIterations to true.

Vertical-Channel-Shift

Setting the recursiveIterations variable to true at the beginning of the script will use each new shifted image as a source for the next iteration and will result in much more dynamic results when iterations is set higher than 3, say 25 or 50.

Recursive-Channel-Shift

Personally I prefer restricting the shifting to either horizontal or vertical alone, but the script allows for the combination  by changing the shiftVertically and shiftHorizontally settings. You can find more Processing tutorials here, and remember, if you’re going to corrupt, corrupt absolutely. #corruptabsolutely

iOS (iPhone, iPad)

Windows

OSX

Processing

3 thoughts on “How to glitch images using RGB channel shifting”

  1. Hello Phil.
    This tutorial is amazing and so is the technique. I’m having some trouble using it though. I have set the sizes of the images and ran it through the program. Ok.
    When I try presenting it, I keep on getting this error “ArrayIndexOutOfBoundsException: 1705600”.
    Could you please help me get through it?

Leave a Reply

Your email address will not be published. Required fields are marked *