1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
#pragma once
#define IMAGE_STORE_SIZE 256
#define MIN_TILE_SIZE 8
#define MAX_TILE_SIZE 16
#include "ofMain.h"
#include "ofxJSONElement.h"
long ofToLong(const string& intString);
class imageStore : public ofThread{
public:
float interval; //time between refreshes in seconds
std::string instagram_url;
ofxJSONElement response;
std::map<std::string,ofImage> images;
deque<std::string> to_update;
imageStore(){
instagram_url = "https://api.instagram.com/v1/tags/tycleeson/media/recent?client_id=c1d74d701fdf4ddd9f8d30ee9e8f944b";
interval=5.00f;
ofImage img;
img.allocate(MAX_TILE_SIZE,MAX_TILE_SIZE,OF_IMAGE_COLOR);
images["000000"]=img;
colours["000000"]=ofColor(0,0,0);
}
void set_interval(float _interval){
interval=_interval;
}
void start(){
startThread(true, false); // blocking, verbose
}
void stop(){
stopThread();
}
//naive implementation
//too slow!
//looks shit!
//how to make the search algorithm faster and better
//http://www.semanticmetadata.net/lire/
/*
1. The box nature of the image matching isn't appealing
2. The way that the images flip quickly isn't appealing
can we improve this by changing the screen gradually in some way?
search tree/ octree
each node associates an image, start with a black image
a node owns a volume of RGB space
new image: determine which octant it lands in
subdivide the octant in R, G or B and give half to each image
Q: does this lead to the possibility that images end up in the wrong place?
with the naive algorithm, we measure distance between the colours in RGB space
it becomes a long process because the number of calculations increases exponentially
space partitioning: we observe that the image will often be nearest to an image in the same box
although it may not be if they are at the edges
so at each level, each node of the tree contains a list of images
we traverse the tree and try to find the lowest level box with a match
if the lowest level box with a match has more than 1 we compute distance?
a difference algorithm
minimise the error
this involves comparing every pixel though
gpu?
put every quarter of every image into an image tree
every image has an entry on levels 1-8
start at level 8 (most detailed)
check the leaf node ie 10110101 01101001 00110101
if there are 1 or more images here choose one
if none go to the next level...
ie 1011010 0110100 0011010
if there are more than 1 images here find the nearest
if there is 1 choose it
if there are none go to the next level
*/
std::map<std::string,ofColor> colours;
ofImage& get_image(const ofColor& col){
//float shortest_dist=999999.0f;
int sd=1000;
ofImage& im=images.begin()->second;
std::string s=images.begin()->first;
for (map<string,ofImage>::iterator it=images.begin();it!=images.end();++it){
ofColor& c=colours[it->first];
int rd=c.v[0]-col.v[0];
int gd=c.v[1]-col.v[1];
int bd=c.v[2]-col.v[2];
//float dist=pow((float)((rd*rd)+(gd*gd)+(bd*bd)),0.5);
int dist=abs(rd)+abs(gd)+abs(bd);
if (dist<sd){
sd=dist;
im=it->second;
s=it->first;
}
}
//cerr<<"got image "<<s<<endl;
return im;
}
ofColor get_colour(const ofImage& _img){
ofImage img=_img;
img.resize(1,1);
return ofColor(img.getPixels()[0],img.getPixels()[1],img.getPixels()[2]);
}
//--------------------------
void threadedFunction(){
//1st get the pre-existing images
ofDirectory image_path(ofToString(IMAGE_STORE_SIZE)+"/");
cerr<<"image path: "<<image_path.getAbsolutePath()<<endl;
if (image_path.exists()){
image_path.listDir();
cerr<<"image path found, "<<image_path.size()<<" images"<<endl;
for (int i=0;i<image_path.size();i++){
ofImage img;
img.setUseTexture(false);
img.loadImage(ofToString(IMAGE_STORE_SIZE)+"/"+image_path.getFiles()[i].getFileName());
img.resize(MAX_TILE_SIZE,MAX_TILE_SIZE);
if( lock() ){
colours[image_path.getFiles()[i].getBaseName()]=get_colour(img);
images[image_path.getFiles()[i].getBaseName()]=img;
cerr<<image_path.getFiles()[i].getBaseName()<<": "<<colours[image_path.getFiles()[i].getBaseName()]<<endl;
to_update.push_back(image_path.getFiles()[i].getBaseName());
unlock();
}
}
}
else {
cerr<<"creating image path"<<endl;
image_path.create();
}
cout << "Api: " << instagram_url<<endl;
while( isThreadRunning() != 0 ){
cout<<"."<<std::flush;
if (!response.open(instagram_url)) {
cout << "Failed to parse JSON\n" << endl;
}
else { //int numImages = MIN(5,response["data"].size());
for(int i=0; i< response["data"].size(); i++) {
//cout << "response " <<response["data"][i]["caption"]["id"].asString()<< endl;
if (images.find(response["data"][i]["caption"]["id"].asString())==images.end()){
std::string url = response["data"][i]["images"]["standard_resolution"]["url"].asString();
std::string id = response["data"][i]["caption"]["id"].asString();
cout<<"fetching "<<id<<":"<<instagram_url<<endl;
ofImage img;
img.setUseTexture(false);
img.loadImage(url);
img.resize(IMAGE_STORE_SIZE,IMAGE_STORE_SIZE);
img.saveImage(ofToString(IMAGE_STORE_SIZE)+"/"+id+".png");
img.resize(MAX_TILE_SIZE,MAX_TILE_SIZE);
if( lock() ){
colours[id]=get_colour(img);
images[id]=img;
to_update.push_back(id);
unlock();
}
}
}
}
ofSleepMillis(interval * 1000);
}
}
void update(){
//loads one texture
if( lock() ){
if (to_update.size()){
std::string im = to_update.front();
const ofPixels& pix = images[im].getPixelsRef();
images[im].getTextureReference().allocate(
pix.getWidth()
,pix.getHeight()
,ofGetGlInternalFormat(pix)
);
images[im].setUseTexture(true);
images[im].update();
to_update.pop_front();
//int drawcount=0;
//for (map<string,ofImage>::iterator i=images.begin();i!=images.end();++i){
// if(i->second.isUsingTexture()){
// drawcount++;
// }
//}
//cout<<"loaded "<<im<<" "<<ofToLong(im)%(long)(ofGetWidth()-images[im].getWidth()+1)<<","<<ofToLong(im)%(long)(ofGetHeight()-images[im].getHeight()+1)<<endl;
}
unlock();
}
}
//--------------------------
void draw(){
if( lock() ){
/*
for (map<string,ofImage>::iterator i=images.begin();i!=images.end();++i){
if(i->second.isUsingTexture()){
i->second.draw(ofToLong(i->first)%(long)(ofGetWidth()-i->second.getWidth()+1),ofToLong(i->first)%(long)(ofGetHeight()-i->second.getHeight()+1));
}
}
*/
map<string,ofImage>::iterator it=images.begin();
if (it!=images.end()){
for (int i=0;i<ofGetWidth()/MAX_TILE_SIZE;i++){
for (int j=0;j<ofGetHeight()/MAX_TILE_SIZE;j++){
if (it->second.isUsingTexture()){
it->second.draw(i*MAX_TILE_SIZE,j*MAX_TILE_SIZE);
}
it++;
if (it==images.end()) it=images.begin();
}
}
}
unlock();
}
}
};
|