Bitmap Manipulations & Image Effects
Last updated
Last updated
PImage img;
// Declare a variable of type PImage to store the image that will be loaded.
float colorNoise;
// Declare a float variable to store the value generated by Perlin noise, which will be used to modify colors.
float counter = 0;
// Declare and initialize a float variable 'counter' to control the progression of the noise function.
void setup(){
size(1000,1000);
// Set the size of the canvas to 1000x1000 pixels.
img = loadImage("grimes.jpg");
// Load the image "grimes.jpg" from the sketch's data folder and store it in the 'img' variable.
}
void draw(){
background(255);
// Set the background color to white (255) at the beginning of every frame, clearing the canvas.
counter += 0.1;
// Increment the 'counter' variable by 0.1 in each frame. This controls how the noise value changes over time.
colorNoise = noise(counter) * 255;
// Generate a Perlin noise value using 'counter', scale it to range from 0 to 255, and store it in 'colorNoise'.
// Perlin noise creates smooth random variations, which will be used to adjust image colors.
tint(200, colorNoise, 155);
// Apply a tint (color filter) with red fixed at 200, green controlled by 'colorNoise', and blue fixed at 155.
// This affects the next image drawn, changing its appearance.
image(img, 0, 0, width/2, height/2);
// Draw the image at the top-left corner (0,0), scaled to half the canvas width and height.
tint(200, random(255), 200);
// Apply a new tint with red fixed at 200, green set to a random value between 0 and 255, and blue fixed at 200.
image(img, width/2, 0, width/2, height/2);
// Draw the image in the top-right corner of the canvas (width/2, 0), again scaled to half the canvas size.
tint(200, 200, colorNoise);
// Apply a tint with red and green fixed at 200, and blue controlled by 'colorNoise'.
image(img, 0, height/2, width/2, height/2);
// Draw the image in the bottom-left corner of the canvas (0, height/2), again scaled to half size.
tint(colorNoise);
// Apply a grayscale tint, where all the color components (red, green, blue) are set to 'colorNoise'.
image(img, width/2, height/2, width/2, height/2);
// Draw the image in the bottom-right corner of the canvas (width/2, height/2), again scaled to half size.
}
Assignment:
Write code in SuperCollider that sends OSC messages to trigger the intensity of the image effects in the earlier example.
Create sounds in SC that align with – or contradict – the visual structures.
PImage img;
// Declare a variable to hold the image.
float resX = 30;
float resY = 30;
// Set initial values for the resolution in X and Y directions (spacing for the grid).
int shape = 2;
// Set the initial shape mode (1 for ellipse, 2 for rectangle, 3 for lines).
int thickness = 1;
// Set the initial thickness for line strokes.
int hueLevel = 0;
// Initialize a variable to adjust hue.
int savecounter = 1;
// Initialize a counter for saving screenshots.
void setup(){
size(1000,1000);
// Set the canvas size to 1000x1000 pixels.
//size(1000, 1000, PDF, "filename.pdf");
// (Commented out) Option to save the output as a PDF.
img = loadImage("grimes.jpg");
// Load the image "grimes.jpg" from the sketch's data folder.
img.loadPixels();
// Load the pixels of the image for manipulation.
colorMode(HSB);
// Set the color mode to HSB (Hue, Saturation, Brightness).
}
void draw(){
background(255);
// Clear the background with white color.
for(int y=0; y < height; y += resY){
// Loop through the canvas vertically, stepping by 'resY'.
for(int x=0; x < width; x += resX){
// Loop through the canvas horizontally, stepping by 'resX'.
int pos = x + y * width;
// Calculate the pixel position based on x and y.
color c = img.get(x, y);
// Get the color from the image at position (x, y).
fill(c, 100);
// Fill shapes with the color from the image, with transparency set to 100.
if(shape == 1){
// If the shape mode is 1, draw ellipses.
noStroke();
// Disable the outline (stroke) of the shape.
ellipse(x, y, resX * 2, resY * 2);
// Draw an ellipse at (x, y) with size scaled by resX and resY.
} else if(shape == 2){
// If the shape mode is 2, draw rectangles.
stroke(140);
// Set the outline color to grey.
noStroke();
// Disable stroke again (since stroke color is set but not used here).
rectMode(CENTER);
// Set the rectangle mode to draw from the center.
rect(x, y, resX * randomGaussian() * 10, resY * randomGaussian() * 1);
// Draw a rectangle with a random width and height, scaled by Gaussian distribution.
} else if(shape == 3){
// If the shape mode is 3, draw lines.
stroke(c, 100);
// Set the stroke color to the color from the image with transparency.
strokeWeight(thickness);
// Set the thickness of the stroke.
line(x, y, x + resX, y + resY);
// Draw a line from (x, y) to (x+resX, y+resY).
}
}
}
}
void keyPressed(){
if(key == 'h'){
hueLevel += 10;
// Press 'h' to increase the hue level (this part isn't used directly, but could be added for hue shifts).
}
if(key == '+'){
resX += 1; resY += 1;
// Press '+' to increase the resolution (make shapes larger).
}
if(key == '-' && resX > 1){
resX -= 1; resY -= 1;
// Press '-' to decrease the resolution (make shapes smaller), but prevent negative sizes.
}
if(key == '1'){ shape = 1; }
// Press '1' to switch to ellipse mode.
if(key == '2'){ shape = 2; }
// Press '2' to switch to rectangle mode.
if(key == '3'){ shape = 3; }
// Press '3' to switch to line mode.
if(key == 'y' && thickness > 1){ thickness /= 2; }
// Press 'y' to reduce the stroke thickness (but only if it's greater than 1).
if(key == 'x'){ thickness *= 2; }
// Press 'x' to double the stroke thickness.
if(key == 's'){
save("screenshot" + savecounter + ".png");
// Press 's' to save a screenshot as a PNG file.
savecounter++;
// Increment the screenshot counter so each save is a unique file.
}
}
// SuperCollider
import processing.video.*;
// Import the video library, which is used to load and play video files.
import oscP5.*;
// Import the oscP5 library to handle sending and receiving OSC (Open Sound Control) messages.
Movie clip;
// Declare a Movie object to store the video that will be played.
OscP5 oscP5;
// Declare an OscP5 object to handle OSC communication (listening for messages on a specific port).
int timerStart, timeCounter, interval;
// Declare variables to manage timing: 'timerStart' stores the start time, 'timeCounter' tracks the elapsed time, and 'interval' sets how often certain actions occur.
void setup() {
size(800, 600);
// Set the window size to 800x600 pixels.
oscP5 = new OscP5(this, 12000);
// Initialize oscP5 to listen for incoming OSC messages on port 12000.
clip = new Movie(this, "video-2.mp4");
// Load the video file "video-2.mp4" into the 'clip' object.
clip.play();
// Start playing the video as soon as the program runs.
interval = 500;
// Set the initial time interval to 500 milliseconds.
timerStart = millis();
// Store the current time (in milliseconds) when the program starts running.
println(clip.duration());
// Print the total duration of the video (in seconds) to the console.
}
void draw() {
interval = mouseX;
// Set the interval based on the horizontal position of the mouse (x-coordinate).
// Moving the mouse left or right will change how frequently the video skips to a new position.
timeCounter = millis() - timerStart;
// Calculate how much time has passed since the timer was started.
if (timeCounter > interval) {
// If the time that has passed exceeds the interval (set by the mouse position):
clip.jump(random(0, clip.duration() - 0.1));
// Jump to a random position in the video, anywhere from the start to almost the end (subtracting 0.1 seconds to avoid errors).
timerStart = millis();
// Reset the timer to the current time, so the countdown restarts.
}
image(clip, 0, 0, width, height);
// Display the video on the screen, stretched to fit the entire window (800x600 pixels).
println(clip.time());
// Print the current playhead position (in seconds) of the video to the console.
}
void movieEvent(Movie m) {
// This function is called automatically whenever a new frame of the video is ready to be displayed.
m.read();
// Read and process the new frame so it can be displayed in the 'image()' function in draw().
}
void oscEvent(OscMessage theOscMessage) {
// This function is triggered whenever an OSC message is received.
if (theOscMessage.checkAddrPattern("/ping") == true) {
// Check if the OSC message has the address "/ping".
clip.jump(random(0, clip.duration() - 0.5));
// If the message is "/ping", jump to a random position in the video, but not within the last 0.5 seconds to avoid errors.
}
}
s.boot;
// Boot the SuperCollider server (s) to start the audio engine.
~processing = NetAddr.new("127.0.0.1", 12000);
// Create a new OSC connection (NetAddr) to send messages to an address (in this case, "127.0.0.1" which refers to the local machine), and on port 12000.This address will be used later to send OSC messages.
(
{
RLPF.ar(Saw.ar([100,250], 0.05), XLine.kr(500, 200, 5), 0.05)
// Generate a sound:
// - `Saw.ar([100, 250], 0.05)` creates a stereo (2-channel) sawtooth waveform, with frequencies of 100 Hz in the left channel and 250 Hz in the right channel.
// The amplitude is set to 0.05 (quite low to avoid loud sounds).
// - `XLine.kr(500, 200, 5)` creates a control-rate (kr) signal that linearly decays from 500 Hz to 200 Hz over 5 seconds.
// - `RLPF.ar(..., 0.05)` applies a resonant low-pass filter to the sound, with the cutoff frequency set by the decaying signal, and resonance (bandwidth) set to 0.05.
}.play;
// Play the sound on the server.
~processing.sendMsg('/ping');
// Send an OSC message with the address `/ping` to the address `127.0.0.1` on port 12000.
// This could be used to trigger an event or message in an external program, such as Processing, which is listening on that port.
)