Detect Similar Textures.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. /******************************************************************************/
  4. DetectSimilarTextures DST;
  5. /******************************************************************************/
  6. /******************************************************************************/
  7. const bool DetectSimilarTextures::Decompress=false;
  8. const int DetectSimilarTextures::MaxTexSize=Decompress ? 64 : 128;
  9. /******************************************************************************/
  10. void DetectSimilarTextures::Data::set(C UID &id)
  11. {
  12. T.id=id;
  13. T.name=EncodeFileName(id);
  14. }
  15. void DetectSimilarTextures::Pair::set(C UID &a, C UID &b) {T.a=a; T.b=b;}
  16. void DetectSimilarTextures::ImageEx::create(bool mtrl_base_1, C Image &src)
  17. {
  18. T.mtrl_base_1=mtrl_base_1;
  19. mips.setNum(src.mipMaps());
  20. REPA(mips)src.extractMipMap(mips[i], Decompress ? IMAGE_R8G8B8A8 : -1, -1, i);
  21. }
  22. void DetectSimilarTextures::CompareImage(UID &a_id, UID &b_id, int thread_index)
  23. {
  24. if(ImageEx *a_image=DST.loaded_texs.find(a_id))
  25. if(ImageEx *b_image=DST.loaded_texs.find(b_id))
  26. {
  27. if(DST.mtrl_base_1!=(a_image->mtrl_base_1 || b_image->mtrl_base_1))goto compared;
  28. ImageCompare ic;
  29. flt avg_difference_mip=DST.avg_difference+0.041f; // this is used for mip-maps, because mip-maps (especially due to compression) can have bigger difference than the biggest mip-map. One image in tests with 0.002233495 'avg_dif2' on biggest mip had 0.043038268 'avg_dif2' on one of the mip maps.
  30. int mips=Min(a_image->mips.elms(), b_image->mips.elms());
  31. FREP(mips)
  32. {
  33. if(!ic.compare(a_image->mips[a_image->mips.elms()-1-i], b_image->mips[b_image->mips.elms()-1-i], DST.similar_dif, false))goto compared; // start comparing from smallest mip-map, if failed to compare
  34. if( ic.skipped || DST.pause)goto compared; // if comparison was skipped
  35. if(i==mips-1) // biggest mip-map that we're going to test
  36. {
  37. if(ic.avg_dif2>DST.avg_difference // if too different
  38. || ic.similar <DST.similar // if not similar enough
  39. )goto compared;
  40. }else // for smaller mip-maps, use values with epsilon added
  41. {
  42. if(ic.avg_dif2>avg_difference_mip // if too different
  43. || ic.similar <DST.similar // if not similar enough
  44. )goto compared;
  45. }
  46. }
  47. {
  48. SyncLocker locker(DST.similar_pair_lock);
  49. DST.similar_pair.New().set(a_id, b_id);
  50. }
  51. }
  52. compared:
  53. AtomicInc(DST.compared);
  54. }
  55. int DetectSimilarTextures::ImageLoad(ImageHeader &header, C Str &name)
  56. {
  57. if(GetThreadId()==DST.io_thread_id) // process only on the 'IOThread'
  58. {
  59. header.mode=IMAGE_SOFT;
  60. int shrink=0; for(uint max_size=header.size.max(); max_size>DST.MaxTexSize; max_size/=2)shrink++;
  61. return shrink;
  62. }
  63. return 0;
  64. }
  65. bool DetectSimilarTextures::IOThread(Thread &t) {return DST.ioThread();}
  66. bool DetectSimilarTextures::ioThread()
  67. {
  68. io_thread_id=GetThreadId();
  69. Image img;
  70. Memt<Threads::Call> calls;
  71. REPA(proj_texs)
  72. {
  73. if(io_thread.wantStop())break;
  74. C UIDEx &tex_id=proj_texs[i];
  75. if(!loaded_texs.find(tex_id)) // if not yet loaded
  76. {
  77. // no need for 'ThreadMayUseGPUData' because we use only IMAGE_SOFT
  78. Images.lock(); // lock because other threads may modify 'image_load_shrink' too
  79. int (*image_load_shrink)(ImageHeader &image_header, C Str &name)=D.image_load_shrink; // remember current
  80. D.image_load_shrink=ImageLoad ; bool ok=img.load(Proj.texPath(tex_id));
  81. D.image_load_shrink=image_load_shrink; // restore
  82. Images.unlock();
  83. if(ok)
  84. {
  85. ImageEx tex; tex.create(tex_id.mtrl_base_1, img);
  86. ImageEx &loaded =*loaded_texs(tex_id); Swap(loaded, tex);
  87. C UID &loaded_id=*loaded_texs.dataToKey(&loaded);
  88. REPA(loaded_texs)
  89. {
  90. C UID &tex_id=loaded_texs.lockedKey(i);
  91. if(loaded_id!=tex_id)calls.New().set(ConstCast(loaded_id), CompareImage, ConstCast(tex_id)); // since we can pass only pointers, then use Tex ID pointer to 'loaded_texs.key' and not 'proj_texs'
  92. }
  93. threads.queue(calls); calls.clear();
  94. }else proj_texs.remove(i); // if failed to load, then remove so we can set progress correctly
  95. }
  96. }
  97. return false;
  98. }
  99. void DetectSimilarTextures::Changed(C Property &prop) {DST.reset();}
  100. void DetectSimilarTextures::reset()
  101. {
  102. stop2();
  103. Memc<UID> &texs=proj_texs;
  104. Proj.getTextures(texs, true); // process only existing
  105. texs.sort(Compare);
  106. REPA(Proj.elms)
  107. {
  108. Elm &elm=Proj.elms[i];
  109. if(ElmMaterial *mtrl_data=elm.mtrlData())if(mtrl_data->base_1_tex.valid())
  110. {
  111. int texs_i; if(texs.binarySearch(mtrl_data->base_1_tex, texs_i, Compare))proj_texs[texs_i].mtrl_base_1=true;
  112. }
  113. }
  114. REPA(loaded_texs)if(!texs.binaryHas(loaded_texs.lockedKey(i), Compare))loaded_texs.remove(i); // remove loaded textures if they're no longer present in the project
  115. // request compare on all loaded textures up to this point
  116. Memt<Threads::Call> calls;
  117. REPA(loaded_texs)
  118. {
  119. C UID &tex_id=loaded_texs.lockedKey(i);
  120. REPD(j, i)calls.New().set(ConstCast(tex_id), CompareImage, ConstCast(loaded_texs.lockedKey(j)));
  121. }
  122. threads.queue(calls);
  123. io_thread.create(IOThread); // start thread at the end
  124. }
  125. DetectSimilarTextures::~DetectSimilarTextures() {stop();}
  126. void DetectSimilarTextures::stop2()
  127. {
  128. io_thread.del(); // delete the thread first, as it may queue calls for 'threads'
  129. pause=true;
  130. threads.cancelFunc(CompareImage); // cancel all comparison functions
  131. threads. waitFunc(CompareImage); // wait until all finished
  132. pause=false;
  133. compared=0;
  134. similar_pair.clear();
  135. data.clear();
  136. list.clear();
  137. }
  138. void DetectSimilarTextures::stop()
  139. {
  140. stop2();
  141. threads.del();
  142. loaded_texs.del();
  143. }
  144. DetectSimilarTextures& DetectSimilarTextures::show()
  145. {
  146. if(hidden())
  147. {
  148. ::EE::Window::show();
  149. rect(Rect_C(0, 0, 1.6f, D.h()*1.75f));
  150. progress.clear().show();
  151. threads.create(false);
  152. reset();
  153. }
  154. return T;
  155. }
  156. DetectSimilarTextures& DetectSimilarTextures::hide()
  157. {
  158. if(visible())
  159. {
  160. stop();
  161. ::PropWin::hide();
  162. }
  163. return T;
  164. }
  165. void DetectSimilarTextures::clearProj() {hide();}
  166. void DetectSimilarTextures::addSimilar(C UID &a, C UID &b)
  167. {
  168. FREPA(data)
  169. {
  170. Data &d=data[i];
  171. bool has_a=(d.id==a), has_b=(d.id==b);
  172. if( has_a || has_b)
  173. {
  174. for(i++; InRange(i, data); i++)
  175. {
  176. C UID &id=data[i].id;
  177. if(!id.valid())break;
  178. if( id==a || id==b)return;
  179. }
  180. if(list.cur>=i)list.cur++;
  181. data.NewAt(i).set(has_a ? b : a);
  182. return;
  183. }
  184. }
  185. if(data.elms())data.New(); // add empty separator
  186. data.New().set(a); // add 'a'
  187. data.New().set(b); // add 'b'
  188. }
  189. void DetectSimilarTextures::addSimilarAll(C UID &a, C UID &b)
  190. {
  191. bool start=true;
  192. FREPA(data)
  193. {
  194. Data &d=data[i];
  195. if(!d.id.valid())start=true;else
  196. if(start)
  197. {
  198. if(d.id==a)
  199. {
  200. for(i++; InRange(i, data) && data[i].id.valid(); )i++; // skip all children
  201. data.NewAt(i).set(b); // add 'b' as child to the end of the list
  202. return;
  203. }
  204. start=false;
  205. }
  206. }
  207. if(data.elms())data.New(); // add empty separator
  208. data.New().set(a); // add 'a' as first (parent)
  209. data.New().set(b); // add 'b' as next (child)
  210. }
  211. Rect DetectSimilarTextures::sizeLimit()C {Rect r=::EE::Window::sizeLimit(); r.min.set(0.6f, 0.4f); return r;}
  212. C Rect& DetectSimilarTextures::rect()C {return ::EE::Window::rect();}
  213. DetectSimilarTextures& DetectSimilarTextures::rect(C Rect &rect)
  214. {
  215. ::EE::Window::rect(rect);
  216. progress.rect(Rect_LU(0, 0, clientWidth(), 0.017f));
  217. region .rect(Rect(region.rect().min.x, -clientHeight()+0.03f, clientWidth()-0.02f, -0.03f));
  218. return T;
  219. }
  220. void DetectSimilarTextures::CurChanged(DetectSimilarTextures &dst)
  221. {
  222. if(Data *data=dst.list())Proj.elmActivate(Proj.findElmByTexture(data->id));
  223. }
  224. void DetectSimilarTextures::create()
  225. {
  226. add("Average Difference", MEMBER(DetectSimilarTextures, avg_difference)).range(0, 0.2f).desc("Total average difference between texture pixels").mouseEditSpeed(0.02f);
  227. add("Material Base 1" , MEMBER(DetectSimilarTextures, mtrl_base_1 )).desc("If compare Material Base 1 Textures, such as Normal, Specular and Glow");
  228. #if 0 // not needed
  229. add("And"); // use AND because OR would list more textures
  230. add("Similar Portion", MEMBER(DetectSimilarTextures, similar )).range(0, 1).desc("Portion of the entire texture that is similar.\nFor example if 2 textures have the same top half, but the bottom half is different, then this will be 0.5\nEach pixels are considered similar if their color difference is smaller than \"Similar Limit\".");
  231. add("Similar Limit" , MEMBER(DetectSimilarTextures, similar_dif)).range(0, 1).desc("Max difference between pixel colors to consider them similar");
  232. #endif
  233. flt h=0.043f;
  234. Rect params=::PropWin::create("Detect Similar Textures", Vec2(0.02f, -0.02f), 0.036f, h, 0.15f); button[2].func(HideProjAct, SCAST(GuiObj, T)).show();
  235. T.flag|=WIN_RESIZABLE;
  236. autoData(this).changed(Changed);
  237. T+=progress.create();
  238. T+=region .create(Vec2(params.max.x+0.05f, 0));
  239. ListColumn lc[]=
  240. {
  241. ListColumn(MEMBER(Data, name), LCW_DATA, "Texture"),
  242. };
  243. region+=list.create(lc, Elms(lc), true).elmHeight(0.038f).textSize(0, 1).curChanged(CurChanged, T); FlagDisable(list.flag, LIST_SORTABLE); list.cur_mode=LCM_ALWAYS;
  244. }
  245. void DetectSimilarTextures::update(C GuiPC &gpc)
  246. {
  247. ::EE::ClosableWindow::update(gpc);
  248. if(visible() && gpc.visible)
  249. {
  250. progress.set(compared, UniquePairs(proj_texs.elms())); progress.visible(progress()<1 && proj_texs.elms()); // !! do not merge into a single instruction !!
  251. if(similar_pair.elms())
  252. {
  253. Memt<Pair> similar;
  254. {
  255. SyncLocker locker(similar_pair_lock);
  256. similar=T.similar_pair; T.similar_pair.clear();
  257. }
  258. FREPA(similar)
  259. {
  260. #if 1
  261. addSimilar(similar[i].a, similar[i].b);
  262. #else // can be slow for lots of pairs
  263. addSimilarAll(similar[i].a, similar[i].b);
  264. addSimilarAll(similar[i].b, similar[i].a);
  265. #endif
  266. }
  267. list.setData(data, null, true);
  268. }
  269. }
  270. }
  271. DetectSimilarTextures::DetectSimilarTextures() : io_thread_id(0), loaded_texs(Compare), pause(false), compared(0), mtrl_base_1(false), avg_difference(0.02f), similar(0.0f), similar_dif(0.1f) {}
  272. DetectSimilarTextures::Data::Data() : id(UIDZero) {}
  273. DetectSimilarTextures::UIDEx::UIDEx() : mtrl_base_1(false) {}
  274. DetectSimilarTextures::ImageEx::ImageEx() : mtrl_base_1(false) {}
  275. /******************************************************************************/