The Different Rainbows of the Instagram LGBT Rainbow
My project explores the LGBT community by means of social media, Instagram in particular, and employs color mapping to determine which colors are used most often in photos of each facet of LGBT. To gather data, I used IFTTT to save all photos with a particular hashtag to a folder in my Dropbox. For example:
I then wrote code a color-mapping algorithm in Processing, as well as a user interface with six functions. My color-mapping algorithm looks at the HSB color values of each pixel of each photograph. If the saturation and brightness are within certain ranges, the pixel is classified as black, white, or grey. If the hue, saturation and brightness are within certain other ranges, the pixel is classified as brown or as “white people color.” If the pixel does not fall into any of those categories, the algorithm finds the closest hue (out of 8 hues that I chose) and puts the pixel in that category. Then the pixels colors are aggregated within each category, and I calculate what percentage of the total pixels in each category are each color. Screens 1 and 2 visualize the color distributions of each category:
Screen 1 shows the color distribution with a large sample size (several thousand photos in each category), and Screen 2 shows the color distribution of only the most recent 50 photos in each category. With the large sample size, we see that most of the categories have nearly identical color distributions, with the transgender category being visibly different.
Screen 3 shows very small versions of the most recent 140 photos in each category. Here we can begin to see why the color distributions differ. Just at first glance, it seems that in the transgender category has more images that have a white background and text in the foreground, and comparatively less photos of people than the other categories.
Screen 4 shows a slideshow of photos from the collection, chosen randomly. Looking into all of the faces is almost haunting. The photos are beautiful, sad, random. Screen 5 shows photos randomly from the collection, with a large image of my color-mapping algorithm applied to the photo and a small image of the original photo.
Screen 6 is not functional in this prototype, but would essentially allow users to tag photos with different identity categories (gay, lesbian, etc.), sentiments (happy, sad, etc.), or to identify what is in the photo (person, object, etc.). With this application of crowd-sourcing, tagging data could be collected and then used to classify the photos in more ways than just their original hashtags. The photos would then be searchable in several ways (i.e. “show me pictures of sad bisexual people”).
This project taught me quite a bit about gathering and analyzing social media data, user-interface design, color, and algorithm development, and challenged my programming skills. What it’s still lacking is a well-defined “what’s at stake” claim.
Code:
/////////////////////////////////////////////////////////////////
// The Different Rainbows of the Instagram LGBT Rainbow //
// Copyright December 2012 //
// created by Oliver Haimson (oliverhaimson@gmail.com) //
// 67-210 - Electronic Media Studio 2 - Prof. Golan Levin //
// Final Project //
/////////////////////////////////////////////////////////////////
float [] hues = {
10, // red
30, // orange
60, // yellow
120, // green
195, // light blue
240, // blue
280, // violet
330, // pink
345, // red
};
int [][] colors = {
{10,360,360},
{30,360,360},
{60,360,360},
{120,360,360},
{195,360,360},
{240,360,360},
{280,360,360},
{330,360,360},
{345,360,360},
{20,104,316},
{40,360,130},
{0,0,360},
{0,0,180},
{0,0,0}
};
String [] button5Pics = {"button5a.png","button5b.png","button5c.png",
"button5d.png","button5e.png","button5f.png","button5g.png",
"button5h.png","button5i.png","button5j.png","button5k.png",
"button5l.png","button5m.png","button5n.png","button5o.png",
"button5p.png","button5q.png","button5r.png","button5s.png",
"button5t.png","button5u.png","button5v.png","button5w.png"};
String [] folders;
String [] labels = {"#lesbian","#gay","#bi","#transgender","#lgbt"};
// 5 folders, 14 colors
int [][] colorDistr = new int [5][14];
float [][] percents = new float [5][14];
float [][] savedData = new float [5][14];
PFont font;
PImage insta, loading, button1, button2, button3, button4, button5,
button6;
int phase;
Boolean isLoading, slideshow, loadAllPhotos, button4Switch,
button5Switch;
void setup() {
smooth();
colorMode(HSB, 360);
size(700, 775);
background(210,227,180);
font=createFont("Helvetica-Bold", 48);
insta = loadImage("insta.jpg");
button1 = loadImage("button1.png");
button2 = loadImage("button2.png");
button3 = loadImage("button3.png");
button6 = loadImage("button6.png");
loading = loadImage("loading.gif");
phase = 100;
isLoading = false;
slideshow = false;
loadAllPhotos = false;
button4Switch = true;
button5Switch = true;
File dir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data");
folders = subset(dir.list(), 1,5);
//loadAllPhotos();
}
void loadData() {
for (int m=0; m<folders.length; m++) {
String [] data = loadStrings("Array"+m);
for (int n=0; n<data.length; n++) {
savedData[m][n] = float(data[n]);
}
}
}
void saveData() {
String [][] strPercents = new String [5][14];
for (int m=0; m<folders.length; m++) {
strPercents[m] = str(percents[m]);
saveStrings("Array"+m, strPercents[m]);
println(strPercents[m]);
}
}
void loadAllPhotos() {
loadAllPhotos=true;
loadPhotos();
drawPhotos();
loadAllPhotos=false;
}
void draw() {
if (phase==100) {
drawStartScreen();
}
else if (phase==1) {
loadData();
drawSavedColorData();
}
else if (phase==2) {
loadPhotos();
}
else if (phase==3) {
drawPhotos();
}
else if (phase==4) {
randomPic();
}
else if (phase==5) {
randomPicColors();
}
else if (phase==6) {
evaluatePics();
}
}
void evaluatePics() {
background(210,227,180);
drawTitle();
drawBackButton();
fill(0,0,0);
rect(0,25,width,750);
int folderNum = int(random(5));
File innerDir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data/"+folders[folderNum]);
String[] photos = innerDir.list();
int photoNum = int(random(photos.length));
PImage photo = loadImage(folders[folderNum]+"/"+photos[photoNum]);
image(photo, 75,25,width-150,width-150);
fill(201,47,331);
textAlign(LEFT);
text("TAG THIS PHOTO:", 75,605);
textSize(12);
text("(not functional", 75,618);
text("in this prototype)", 75,630);
fill(210,227,180);
int width1 = 260;
int width2 = 386;
int width3 = 470;
text("LESBIAN", width1,605);
text("GAY", width1,630);
text("BISEXUAL", width1,655);
text("TRANSGENDER", width1,680);
text("STRAIGHT", width1,705);
text("HOMOPHOBIC", width1, 730);
text("TRANSPHOBIC", width1,755);
text("PERSON", width2, 605);
text("BODY", width2, 630);
text("PEOPLE", width2, 655);
text("PLACE", width2, 680);
text("OBJECT", width2, 705);
text("ANIMAL", width2, 730);
text("TEXT", width2, 755);
text("HAPPY", width3,605);
text("EXCITED", width3,630);
text("SEXY", width3, 655);
text("CONTENT", width3,680);
text("SAD", width3,705);
text("ANGRY", width3,730);
text("AFRAID", width3,755);
strokeWeight(5);
stroke(210,227,180);
line(600,720, 620,735);
line(600,750, 620,735);
noLoop();
}
void drawSavedColorData() {
background(210,227,180);
drawTitle();
drawBackButton();
int y=25;
for (int i=0; i<folders.length; i++) {
float x=0;
for (int n=0; n<colors.length; n++) {
fill(colors[n][0],colors[n][1],colors[n][2]);
rect(x,y,width*savedData[i][n],height);
x+=width*savedData[i][n];
}
fill(201,47,331);
textSize(14);
textAlign(RIGHT);
text(labels[i], width*.95, y+40);
y+=150;
}
}
void randomPicColors() {
background(210,227,180);
drawTitle();
drawBackButton();
fill(0,0,0);
noStroke();
rect(0,25,width,750);
int folderNum = int(random(5));
File innerDir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data/"+folders[folderNum]);
String[] photos = innerDir.list();
int photoNum = int(random(photos.length));
PImage photo = loadImage(folders[folderNum]+"/"+photos[photoNum]);
findPixelColors(photo, folderNum);
fill(201,47,331);
textAlign(LEFT);
textSize(14);
text(labels[folderNum], 10, 655);
String name = photos[photoNum].substring(0, photos[photoNum].length()-4);
text(name, 107,655);
image(photo, 500,575,200,200);
fill(210,227,180);
noStroke();
if (slideshow) {
text("SLIDESHOW MODE ON", 10,765);
}
else {
text("CLICK FOR SLIDESHOW MODE", 10,765);
strokeWeight(5);
stroke(210,227,180);
line(650,316, 670,331);
line(650,346, 670,331);
noLoop();
}
}
void randomPic() {
background(210,227,180);
drawTitle();
drawBackButton();
int folderNum = int(random(5));
File innerDir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data/"+folders[folderNum]);
String[] photos = innerDir.list();
int photoNum = int(random(photos.length));
PImage photo = loadImage(folders[folderNum]+"/"+photos[photoNum]);
image(photo, 0,25,width,width);
fill(0,0,0);
rect(0,725,width,50);
fill(201,47,331);
textAlign(LEFT);
textSize(14);
text(labels[folderNum], 10, 742);
String name = photos[photoNum].substring(0, photos[photoNum].length()-4);
text(name, 280,742);
fill(210,227,180);
if (slideshow) {
text("SLIDESHOW MODE ON", 10,765);
}
else {
text("CLICK FOR SLIDESHOW MODE", 10,765);
strokeWeight(5);
stroke(210,227,180);
line(660,735, 680,750);
line(660,765, 680,750);
noLoop();
}
}
void drawPhotos() {
background(210,227,180);
drawTitle();
drawBackButton();
float x=0;
float y=25;
for (int i=0; i<folders.length; i++) {
File innerDir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data/"+folders[i]);
String[] photos = innerDir.list();
fill(0,0,0);
rect(0,y,width,25);
fill(201,47,331);
textAlign(LEFT);
textSize(14);
text(labels[i], 10, y+20);
y+=25;
x=0;
for (int n=photos.length-1; n>(photos.length-141); n--) {
PImage photo = loadImage(folders[i]+"/"+photos[n]);
image(photo,x,y,25,25);
if (x<675) {
x+=25;
}
else {
x=0;
y+=25;
}
}
}
noLoop();
}
void drawBackButton() {
noStroke();
fill(201,47,331);
triangle(13,6,13,19,5,12.5);
rect(13,6,20,13);
}
void drawTitle() {
textFont(font);
textSize(18);
textAlign(CENTER);
fill(201,47,331);
text("THE DIFFERENT RAINBOWS OF THE INSTAGRAM LGBT RAINBOW",
width*.5, 19);
}
void drawStartScreen() {
// find random pic for button 4
if (button4Switch) {
int folderNum = int(random(5));
File innerDir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data/"+folders[folderNum]);
String[] photos = innerDir.list();
int photoNum = int(random(photos.length));
button4 = loadImage(folders[folderNum]+"/"+photos[photoNum]);
button4Switch = false;
}
// find random pic for button 5
if (button5Switch) {
int picNumber = int(random(button5Pics.length));
button5 = loadImage(button5Pics[picNumber]);
button5Switch = false;
}
// switch button 4 and 5 pic every 200 framecounts
if (frameCount%200 == 0) {
button4Switch = true;
}
if (frameCount%200 == 100) {
button5Switch = true;
}
//
background(210,227,180);
drawTitle();
fill(29,30,335);
noStroke();
rect(0,25,width,height-50);
image(insta, 0, height-284);
fill(26,94,47);
roundRect(50,70,166.67,166.67);
image(button1, 61,81,146.67,146.67);
roundRect(266.67,70,166.67,166.67);
image(button2, 277.67,81,146.67,146.67);
roundRect(483.34,70,166.67,166.67);
image(button3, 494.34,81,146,146.67);
roundRect(50,275,166.67,166.67);
image(button4, 61,286,146.67,146.67);
roundRect(266.67,275,166.67,166.67);
image(button5, 277.67,286,146.67,146.67);
roundRect(483.34,275,166.67,166.67);
image(button6, 494,286,146,146.67);
// change button color and display text if it's hovered over
// button 1
if (mouseX>50 && mouseX<216.67 && mouseY>70 && mouseY<236.67) {
fill(26,200,200);
roundRect(50,70,166.67,166.67);
image(button1, 61,81,146.67,146.67);
fill(0,0,0,250);
rect(61,175,146,52);
fill(210,300,300);
textSize(10);
text("DISPLAY SAVED COLOR", 133.33, 190);
text("DATA FROM FULL", 133.33, 205);
text("SAMPLE OF PICTURES", 133.33, 220);
// button 2
}
else if (mouseX>266.67 && mouseX<433.34 &&
mouseY>70 && mouseY<236.67) {
fill(26,200,200);
roundRect(266.67,70,166.67,166.67);
image(button2, 277.67,81,146.67,146.67);
fill(0,0,0,250);
rect(277,175,146,52);
fill(210,300,300);
textSize(10);
text("DISPLAY COLOR DATA FROM", 349.5, 190);
text("50 MOST RECENT PHOTOS", 349.5, 205);
text("IN EACH CATEGORY", 349.5, 220);
}
// button 3
else if (mouseX>483 && mouseX<639.67 && mouseY>70 && mouseY<236.67) {
fill(26,200,200);
roundRect(483.34,70,166.67,166.67);
image(button3, 494.34,81,146,146.67);
fill(0,0,0,250);
rect(494,175,146,52);
fill(210,300,300);
textSize(10);
text("DISPLAY", 566, 190);
text("140 MOST RECENT PHOTOS", 566, 205);
text("IN EACH CATEGORY", 566, 220);
}
// button 4
else if (mouseX>50 && mouseX<216.67 && mouseY>275 && mouseY<441.67) {
fill(26,200,200);
roundRect(50,275,166.67,166.67);
image(button4, 61,286,146.67,146.67);
fill(0,0,0,250);
rect(61,380,146,52);
fill(210,300,300);
textSize(10);
text("DISPLAY RANDOM", 133.33, 395);
text("PICTURES FROM THE", 133.33, 410);
text("DATA COLLECTION", 133.33, 425);
}
// button 5
else if (mouseX>266.67 && mouseX<433.34 &&
mouseY>275 && mouseY<441.67) {
fill(26,200,200);
roundRect(266.67,275,166.67,166.67);
image(button5, 277.67,286,146.67,146.67);
fill(0,0,0,250);
rect(277,380,147,52);
fill(210,300,300);
textSize(10);
text("DISPLAY RANDOM PICTURES", 350, 395);
text("WITH COLOR MAPPING", 350, 410);
text("ALGORITHM APPLIED", 350, 425);
}
// button 6
else if (mouseX>483 && mouseX<639.67 && mouseY>275 && mouseY<441.67) {
fill(26,200,200);
roundRect(483.34,275,166.67,166.67);
image(button6, 494,286,146,146.67);
fill(0,0,0,250);
rect(494,380,146,52);
fill(210,300,300);
textSize(10);
text("TAG", 566, 402.5);
text("PICTURES", 566, 417.5);
}
if (isLoading) {
image(loading, width/2.0-loading.width/2.0, 106);
}
}
void loadPhotos() {
background(210,227,180);
drawTitle();
drawBackButton();
// citation: help with reading in files from a folder came from
// Processing Forum, a user named "clankill3r" at
// http://processing.org/discourse/beta/num_1253083238.html
File dir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data");
folders = subset(dir.list(), 1,5);
int y=25;
textSize(14);
textAlign(RIGHT);
// i<folders.length
for (int i=0; i<folders.length; i++) {
File innerDir = new File("/Users/oliverhaimson/Dropbox/Processing/project4/data/"+folders[i]);
String[] photos = innerDir.list();
getColors(photos, folders[i], i);
drawColors(photos.length, i, y);
fill(201,47,331);
text(labels[i], width*.95, y+40);
y+=150;
}
noLoop();
}
void drawColors(int len, int fold, int y) {
noStroke();
// find total number of pixels in the folder
float totalPixels=0;
for (int n=0; n<colors.length; n++) {
totalPixels+=colorDistr[fold][n];
}
// calculate percentage of each color and draw rectangles
float x=0;
for (int i=0; i<colors.length; i++) {
percents[fold][i] = colorDistr[fold][i]/totalPixels;
fill(colors[i][0],colors[i][1],colors[i][2]);
rect(x,y,width*percents[fold][i],height);
x+=width*percents[fold][i];
}
}
void getColors(String [] photos, String folder, int fold) {
int amount;
if (loadAllPhotos) {
amount = photos.length;
}
else {
// load only latest 50 photos
amount = 50;
}
for (int i=photos.length-1; i>photos.length-amount; i--) {
PImage photo = loadImage(folder+"/"+photos[i]);
findPixelColors(photo, fold);
println("Photo "+i+" of "+(photos.length-1)+" in "+folder+
" done!");
}
}
void findPixelColors(PImage photo, int fold) {
int i=0;
for (int x=0; x<photo.width; x++) {
for (int y=0; y<photo.height; y++) {
float h = hue(photo.pixels[i]);
float s = saturation(photo.pixels[i]);
float b = brightness(photo.pixels[i]);
// determine if the pixel is black, white, or grey
float closeHue, closeSat, closeBright;
if (b <= 120) { // black
closeHue = 0;
closeSat = 0;
closeBright = 0;
colorDistr[fold][13]+=1;
}
else if (s < 100 && b > 300) { // white
closeHue = 0;
closeSat = 0;
closeBright = 360;
colorDistr[fold][11]+=1;
}
else if (s < 100 && b > 70 && b <= 300) { // grey
closeHue = 0;
closeSat = 0;
closeBright = 180;
colorDistr[fold][12]+=1;
}
// if the pixel is not black, white, or grey, find the pixel's
// closest hue
else {
closeHue = closestHue(h);
// if the hue is <=40, determine if the pixel is brown or
// white people color
if ((h <= 40 | h >= 345) && b < 260) { // brown
closeHue = 40;
closeSat = 360;
closeBright = 130;
colorDistr[fold][10]+=1;
}
else if (h <= 40 && s <= 300 && b >= 190) {
// white people color
closeHue = 20;
closeSat = 104;
closeBright = 316;
colorDistr[fold][9]+=1;
}
else {
// change second red to first red
if (closeHue == 345) {
closeHue = 10;
}
closeBright = 360;
closeSat = 360;
// add to corresponding colorDistr vector
for (int n=0; n<hues.length; n++) {
if (closeHue == hues[n]) {
colorDistr[fold][n]+=1;
}
}
}
}
i = photo.width*y + x;
if (phase==5) {
stroke(closeHue, closeSat, closeBright);
point(x,y+28);
}
}
}
}
float closestHue(float h) {
int currentColor = 0;
float distance = abs(h-hues[0]);
for (int i=1; i<hues.length; i++) {
float d = abs(h-hues[i]);
if (d < distance) {
distance = d;
currentColor = i;
}
}
return hues[currentColor];
}
void roundRect(float x, float y, float w, float h) {
rect(x+w*.09,y,w*.82,h);
rect(x,y+h*.09,w,h*.82);
arc(x+w*.1,y+h*.1, w*.2,h*.2, PI,PI+HALF_PI);
arc(x+w*.9,y+h*.1, w*.2,h*.2, PI+HALF_PI,TWO_PI);
arc(x+w*.1,y+h*.9, w*.2,h*.2, HALF_PI,PI);
arc(x+w*.9,y+h*.9, w*.2,h*.2, 0,HALF_PI);
}
void mousePressed() {
if (phase==100) {
if (mouseX>50 && mouseX<216.67 && mouseY>70 && mouseY<236.67) {
phase=1;
isLoading=false;
}
else if (mouseX>266.67 && mouseX<433.34 &&
mouseY>70 && mouseY<236.67) {
isLoading=true;
drawStartScreen();
phase=2;
isLoading=false;
}
else if (mouseX>483.34 && mouseX<650 &&
mouseY>70 && mouseY<236.67) {
isLoading=true;
drawStartScreen();
phase=3;
isLoading=false;
}
else if (mouseX>50 && mouseX<216.67 &&
mouseY>275 && mouseY<441.67) {
phase=4;
}
else if (mouseX>266.67 && mouseX<433.34 &&
mouseY>275 && mouseY<441.67) {
isLoading=true;
drawStartScreen();
phase=5;
isLoading=false;
}
else if (mouseX>483.34 && mouseX<650 &&
mouseY>275 && mouseY<441.67) {
phase=6;
}
}
else if (phase!=100) {
if (mouseX>5 && mouseX<33 && mouseY>6 && mouseY<19) {
loop();
phase=100;
slideshow=false;
}
}
if (phase==4 || phase==5) {
if (mouseX<220 && mouseY>750) {
slideshow=!slideshow;
}
loop();
}
if (phase==6) {
if (mouseX>600 && mouseX<625 && mouseY>720 && mouseY<750) {
loop();
}
}
}
void keyPressed() {
if (phase==4 || phase==5) {
loop();
}
if (key == 'P') {
saveFrame("screenshot.png");
}
// if (key == 'S') {
// println("Saving....");
// saveData();
// }
}