Created
October 1, 2014 14:42
-
-
Save elib/2d932355748c40132685 to your computer and use it in GitHub Desktop.
"Broken VHS" effect for static image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
float sidepercent = 6; | |
//10-20 -> amazing horizontals | |
//100 -> painterly | |
float initialChanceOfChunkGrab = 15; | |
float chanceOfChunkGrab; | |
float chanceOfChunkGrabChange = 10; | |
PImage originalImage; | |
PImage finalImage; | |
int yoff = 10; | |
PVector[] points; | |
void resetPoints() { | |
PVector[] tmppoints = { | |
//first two identical | |
new PVector(0, 50 + yoff), | |
new PVector(0, 50 + yoff), | |
new PVector(10, 100 + yoff), | |
new PVector(0, 151 + yoff), | |
new PVector(0, 151 + yoff), | |
new PVector(0, 151 + yoff), | |
new PVector(0, 151 + yoff), | |
new PVector(0, 151 + yoff), | |
new PVector(10, 200 + yoff), | |
new PVector(5, 300 + yoff), | |
new PVector(20, 330 + yoff), | |
new PVector(90, 400 + yoff), | |
new PVector(90, 400 + yoff) | |
//new PVector(10, 450 + yoff), | |
//new PVector(50, 500 + yoff), | |
//new PVector(20, 700 + yoff), | |
//new PVector(20, 700 + yoff), | |
//last two identical | |
}; | |
points = tmppoints; | |
int xchangeAmount = 90; | |
int ychangeAmount = 40; | |
for(int i = 0; i < points.length; i++) { | |
points[i].x = (int) constrain(points[i].x + random(xchangeAmount) - xchangeAmount/2, 0, 200); | |
points[i].y = (int) constrain(points[i].y + random(ychangeAmount) - ychangeAmount/2, 0, 700); | |
} | |
} | |
void setup() { | |
originalImage = loadImage("photo.jpg"); | |
size(originalImage.width, originalImage.height); | |
frameRate(1); | |
} | |
PImage makeImgSize(PImage img) { | |
PImage newImage = createImage(img.width, img.height, RGB); | |
return newImage; | |
} | |
PImage cloneImg(PImage img) { | |
PImage newImg = makeImgSize(img); | |
newImg.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height); | |
return newImg; | |
} | |
float easeInOut(float t, float duration, float starting, float change) { | |
return -change/2 * (cos(PI*t/duration) - 1) + starting; | |
} | |
int shouldSide(int x, int wid) { | |
float baserand = (random(30) + 225); | |
int amt = int(sidepercent * wid / 100.0f); | |
int whenease = amt/4; | |
if(x < whenease || x >= wid - whenease) { | |
return int(baserand); | |
//return 0; | |
} | |
float starting = 1; | |
float change = -1; | |
float duration = 3*amt/4; | |
if (x <= whenease + duration) { | |
return int( baserand * easeInOut(x - whenease, duration, starting, change)); | |
} | |
if (x >= wid - whenease - duration) { | |
return int( baserand * easeInOut(x - wid + whenease, -duration, starting, change)); | |
} | |
return 0; | |
} | |
PImage addSideStatic(PImage img) { | |
PImage sideStaticImg = cloneImg(img); | |
PImage noise = makeImgSize(img); | |
noise.loadPixels(); | |
for(int x = 0; x < noise.width; x++) { | |
for(int y = 0; y < noise.height; y++) { | |
noise.pixels[x + (y*noise.width)] = color(random(120),random(120),random(120), shouldSide(x, img.width)); | |
} | |
} | |
noise.updatePixels(); | |
sideStaticImg.blend(noise, 0, 0, sideStaticImg.width, sideStaticImg.height, 0, 0, sideStaticImg.width, sideStaticImg.height, BLEND); | |
return sideStaticImg; | |
} | |
PImage addStatic(PImage img) { | |
PImage staticImg = cloneImg(img); | |
PImage noise = makeImgSize(img); | |
noise.loadPixels(); | |
for(int x = 0; x < noise.width; x++) { | |
for(int y = 0; y < noise.height; y++) { | |
noise.pixels[x + (y*noise.width)] = color(random(255),random(255),random(255), random(80) + 30); | |
} | |
} | |
noise.updatePixels(); | |
staticImg.blend(noise, 0, 0, staticImg.width, staticImg.height, 0, 0, staticImg.width, staticImg.height, BLEND); | |
return staticImg; | |
} | |
PImage doCombs(PImage img) { | |
return doSingleComb(img); | |
} | |
PImage doSingleComb(PImage img) { | |
PImage combImage = cloneImg(img); | |
//decide starting point of comb | |
int startPoint = int(points[0].y); | |
//(int) ((img.height / 4) + random(img.height / 8)); | |
//decide ending point of comb | |
int endPoint = int(points[points.length - 1].y); | |
//3 * img.height / 4; | |
//decide width of comb -- usually all of width | |
int combWidth = img.width; | |
//decide abspoint of param; | |
float y0 = ((endPoint - startPoint) / 2) + startPoint; | |
//params | |
float a = 1; | |
float b = a/2 - 1/a; | |
float c = pow(a, 2)/4 + 1/(pow(a,2)); | |
//decide rate of pickup-new-pixel-colors | |
float newPixelRate = 0; | |
color[] currentColors = new color[combWidth]; | |
float beginningOffset = evaluateCurve(startPoint); | |
gatherColors(combImage, currentColors, startPoint, beginningOffset); | |
for(int y = startPoint; y < endPoint; y++) { | |
float offset = evaluateCurve(y); | |
if(random(1.0f) < 1/chanceOfChunkGrabChange) { | |
if(random(1.0f) < 0.5f) { | |
chanceOfChunkGrab *= 2; | |
} else { | |
chanceOfChunkGrab /= 2; | |
} | |
chanceOfChunkGrab = constrain(chanceOfChunkGrab, 5, initialChanceOfChunkGrab); | |
} | |
//float offset = evaluateFunction(y, startPoint, endPoint, a, b, c); | |
writeColors(combImage, currentColors, y, offset); | |
//handle new pixel rate | |
} | |
return combImage; | |
} | |
float evaluateCurve(int y) { | |
int ind = 1; | |
boolean found = false; | |
while(ind < points.length - 2 && !found) { | |
if(y >= points[ind].y && y <= points[ind+1].y) { | |
found = true; | |
} else { | |
ind ++; | |
} | |
} | |
if(!found) { | |
return 0; | |
} | |
float t = norm(y, points[ind].y, points[ind+1].y); | |
return curvePoint(points[ind-1].x, points[ind].x, points[ind+1].x, points[ind+2].x, t); | |
} | |
void gatherColors(PImage img, color[] currentColors, int y, float offset){ | |
img.loadPixels(); | |
int xoff = (int) offset; | |
int x = 0; | |
while(x < (img.width - xoff)) { | |
int chunksize = setCurrentChunk(y, img, currentColors, x, xoff); | |
x+=chunksize; | |
} | |
for(x = (img.width - xoff); x < img.width; x ++) { | |
currentColors[x] = 0; | |
} | |
} | |
int setCurrentChunk(int y, PImage img, color[] currentColors, int startx, int xoff) { | |
int rr = 0, gg = 0, bb = 0; | |
int chunksize = int(random(15) + 5); //random | |
if(chunksize + startx >= (img.width - xoff)) { | |
chunksize = (img.width - xoff) - startx; | |
} | |
for(int xtag = 0; xtag < chunksize; xtag++) { | |
//get average color | |
color oneColor = img.pixels[(startx + xoff + xtag) + y *(img.width)]; | |
rr += red(oneColor); | |
gg += green(oneColor); | |
bb += blue(oneColor); | |
} | |
rr /= chunksize; | |
gg /= chunksize; | |
bb /= chunksize; | |
for(int xtag = 0; xtag < chunksize; xtag++) { | |
currentColors[startx + xtag] = color(rr, gg, bb); | |
} | |
return chunksize; | |
} | |
void writeColors(PImage img, color[] currentColors, int y, float offset){ | |
img.loadPixels(); | |
int xoff = (int) offset; | |
xoff = constrain(xoff, 0, img.width - 10); | |
for(int x = 0; x < (img.width - xoff); x++) { | |
//chunk it! | |
if(random(1.0f) < 1 / (chanceOfChunkGrab)) { | |
setCurrentChunk(y, img, currentColors, x, xoff); | |
} | |
img.pixels[(x + xoff) + y *(img.width)] = currentColors[x]; | |
} | |
img.updatePixels(); | |
} | |
void draw() { | |
background(0); | |
chanceOfChunkGrab = initialChanceOfChunkGrab; | |
resetPoints(); | |
PImage img = addSideStatic(originalImage); | |
img = doCombs(img); | |
img = addStatic(img); | |
image(img, 0, 0); | |
//uncomment to save each frame to disk | |
//save("img_" + hour() + "_" + minute() + "_" + second() + ".png"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is "really old" code (January, 2013), written in a few long evenings and then never touched again. Please bear this in mind.
The goal of this Processing script was to simulate a single frame of a very old and/or broken VHS tape, right in the middle of an extreme magnetic corruption. I believe I may have used https://www.youtube.com/watch?v=mES3CHEnVyI and http://maxcapacity.tumblr.com/ as inspiration/reference.
Some sparse notes about the code...
initialChanceOfChunkGrab
constant to see the effect when changing the frequency of switching "chunks" when smearing the pixels across the image.addSideStatic
and the general static covering the image as a final pass inaddStatic
).If you'd like to ask me something more specific, please feel free to hit up @elibrody on Twitter.