Image Atlas.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************/
  5. #define CC4_IMGA CC4('I','M','G','A')
  6. /******************************************************************************/
  7. DEFINE_CACHE(ImageAtlas, ImageAtlases, ImageAtlasPtr, "ImageAtlas");
  8. /******************************************************************************/
  9. static Bool TestCol(Image &image, Int x) {REPD(y, image.h())if(image.color(x, y).a)return true; return false;}
  10. static Bool TestRow(Image &image, Int y) {REPD(x, image.w())if(image.color(x, y).a)return true; return false;}
  11. /******************************************************************************/
  12. Int ImageAtlas::findPartI(C Str &name) {if(name.is())FREPA(parts)if(EqualPath(parts[i].name, name))return i; return -1;}
  13. Int ImageAtlas:: getPartI(C Str &name) {if(name.is()){Int i=findPartI(name); if(i<0)Exit(S+"Part \""+name+"\" not found in ImageAtlas."); return i;} return -1;}
  14. ImageAtlas::Part* ImageAtlas::findPart (C Str &name) {Int i=findPartI(name); return (i<0) ? null : &parts[i];}
  15. ImageAtlas::Part* ImageAtlas:: getPart (C Str &name) {Int i= getPartI(name); return (i<0) ? null : &parts[i];}
  16. /******************************************************************************/
  17. void ImageAtlas::del()
  18. {
  19. parts .del();
  20. images.del();
  21. }
  22. struct ImageAtlasSrc : RectSizeAnchor // trimmed size
  23. {
  24. Image *image;
  25. C Str *name;
  26. RectI opaque;
  27. VecI2 original_size;
  28. ImageAtlasSrc() {size.zero(); image=null; name=null; opaque.set(0, 0, -1, -1); original_size.zero();}
  29. };
  30. /******************************************************************************/
  31. Bool ImageAtlas::create(C MemPtr<Source> &images, IMAGE_TYPE image_type, Int mip_maps, Bool allow_rotate, Int border, Bool align_for_compression, Bool only_square, Int max_tex_size, Bool trim_transparent, Bool transparent_to_neighbors)
  32. {
  33. del();
  34. Clamp(max_tex_size, 1, 16384);
  35. Clamp(border, 0, max_tex_size);
  36. // get rectangles of opaque parts in images
  37. Memb<Image > image_decompressed; // use 'Memb' because we're storing pointer to element
  38. Memc<ImageAtlasSrc> ias; ias.setNum(images.elms());
  39. Memc<RectIndex > packed;
  40. Memc<VecI2 > image_sizes;
  41. FREPA(images)
  42. {
  43. C Source &image=images[i];
  44. ImageAtlasSrc &ia =ias [i];
  45. ia.name=&image.name;
  46. if(Image *src=image.image())if(src->is())
  47. {
  48. ia.original_size.set(src->w(), src->h());
  49. if( src->compressed()){Image &image_temp=image_decompressed.New(); if(!src->copyTry(image_temp, -1, -1, -1, -1, IMAGE_SOFT, 1))goto error; src=&image_temp;}
  50. if(!src->lock(LOCK_READ))goto error;
  51. RectI opaque; // this is inclusive
  52. if(!trim_transparent)opaque.set(0, 0, src->w()-1, src->h()-1);else
  53. {
  54. opaque.set(0, 0, -1, -1); // set as invalid on start
  55. FREPD(x, src->w())if(TestCol(*src, x)){opaque.min.x=x; break;} REPD(x, src->w())if(TestCol(*src, x)){opaque.max.x=x; break;}
  56. FREPD(y, src->h())if(TestRow(*src, y)){opaque.min.y=y; break;} REPD(y, src->h())if(TestRow(*src, y)){opaque.max.y=y; break;}
  57. }
  58. if(opaque.valid())
  59. {
  60. ia.size .set(opaque.w()+1, opaque.h()+1); // trimmed size, +1 because 'opaque' is inclusive
  61. ia.anchor.set(ia.original_size.x/2-opaque.min.x, ia.original_size.y/2-opaque.min.y);
  62. ia.image =src;
  63. ia.opaque=opaque;
  64. }
  65. }
  66. }
  67. if(!PackRectsMultiLimit(SCAST(Memc<RectSizeAnchor>, ias), packed, image_sizes, allow_rotate, border, align_for_compression, false, only_square, max_tex_size))goto error; // set false for 'compact_arrangement' because it's better to have some more spacing between parts
  68. // write parts to images
  69. T.parts .setNum(ias .elms());
  70. T.images.setNum(image_sizes.elms()); REPA(T.images)if(T.images[i].createTry(image_sizes[i].x, image_sizes[i].y, 1, IMAGE_R8G8B8A8, IMAGE_SOFT, 1))T.images[i].clear();else goto error;
  71. REPA(ias)
  72. {
  73. C RectIndex &pack=packed[i];
  74. ImageAtlasSrc &ia=ias [i];
  75. Part &part=parts [i];
  76. part.original_size= ia.original_size;
  77. part.name =*ia.name;
  78. if(ia.image)
  79. {
  80. const Flt e=0.0f; // 0.5f; don't apply LINEAR FILTER offset because it destroys animated images (jump of offset/scale is visible between 2 frames)
  81. Image &image=T.images[pack.index];
  82. part.image_index = pack.index;
  83. part.rotated =((pack.w()>pack.h())!=(ia.size.x>ia.size.y));
  84. part.tex_rect .set(Flt(pack.min.x-e)/image.w(), Flt(pack.min.y-e)/image.h(), Flt(pack.max.x+e)/image.w(), Flt(pack.max.y+e)/image.h());
  85. part.trimmed_size =ia.size;
  86. part.trim_pos =ia.opaque.min;
  87. part.center_offset.set(part.trim_pos.x-part.original_size.x*0.5f, -part.trim_pos.y+part.original_size.y*0.5f);
  88. // set texture data from the image
  89. if(part.rotated)
  90. {
  91. REPD(y, part.trimmed_size.y)
  92. REPD(x, part.trimmed_size.x)image.color(pack.max.x-1-y, x+pack.min.y, ia.image->color(x+part.trim_pos.x, y+part.trim_pos.y));
  93. }else
  94. {
  95. REPD(y, part.trimmed_size.y)
  96. REPD(x, part.trimmed_size.x)image.color(x+pack.min.x, y+pack.min.y, ia.image->color(x+part.trim_pos.x, y+part.trim_pos.y));
  97. }
  98. }else
  99. {
  100. part.rotated =false;
  101. part.image_index =0xFF;
  102. part.tex_rect .zero();
  103. part.center_offset.zero();
  104. part.trimmed_size .zero();
  105. part.trim_pos .zero();
  106. }
  107. }
  108. REPA(T.images)
  109. {
  110. Image &image=T.images[i];
  111. if(transparent_to_neighbors)image.transparentToNeighbor();
  112. if(!image.copyTry(image, -1, -1, -1, image_type, IMAGE_2D, mip_maps, FILTER_BEST, true, true))goto error;
  113. }
  114. // success
  115. {
  116. REPA(images)if(images[i].image)images[i].image->unlock();
  117. return true;
  118. }
  119. error:
  120. REPA(images)if(images[i].image)images[i].image->unlock(); del(); return false;
  121. }
  122. /******************************************************************************/
  123. void ImageAtlas::draw(Int part_index, C Vec2 &pos, Flt pixel_size)C
  124. {
  125. if(InRange(part_index, parts))
  126. {
  127. C Part &part=parts[part_index];
  128. if(C Image *image=images.addr(part.image_index))
  129. {
  130. Rect_LU screen_rect(part.center_offset*pixel_size+pos, // position
  131. part.trimmed_size *pixel_size ); // size
  132. if(part.rotated)image->drawPartVertical(screen_rect, part.tex_rect);
  133. else image->drawPart (screen_rect, part.tex_rect);
  134. }
  135. }
  136. }
  137. /******************************************************************************/
  138. Bool ImageAtlas::save(File &f)C
  139. {
  140. f.putUInt(CC4_IMGA).cmpUIntV(2);
  141. f.cmpUIntV(parts.elms());
  142. FREPA(parts)
  143. {
  144. C Part &part=parts[i];
  145. f.putMulti(part.image_index, part.rotated, part.tex_rect, part.center_offset, part.original_size, part.trimmed_size, part.trim_pos)<<part.name;
  146. }
  147. if(images.save(f))
  148. return f.ok();
  149. return false;
  150. }
  151. Bool ImageAtlas::load(File &f)
  152. {
  153. del();
  154. if(f.getUInt()==CC4_IMGA)switch(f.decUIntV())
  155. {
  156. case 2:
  157. {
  158. parts.setNum(f.decUIntV());
  159. FREPA(parts)
  160. {
  161. Part &part=parts[i];
  162. f.getMulti(part.image_index, part.rotated, part.tex_rect, part.center_offset, part.original_size, part.trimmed_size, part.trim_pos)>>part.name;
  163. }
  164. if(images.load(f))
  165. if(f.ok())return true;
  166. }break;
  167. case 1:
  168. {
  169. parts.setNum(f.decUIntV());
  170. FREPA(parts)
  171. {
  172. Part &part=parts[i];
  173. f.getMulti(part.image_index, part.rotated, part.tex_rect, part.center_offset, part.original_size, part.trimmed_size, part.trim_pos)._getStr2(part.name);
  174. }
  175. if(images.load(f))
  176. if(f.ok())return true;
  177. }break;
  178. case 0:
  179. {
  180. parts.setNum(f.decUIntV());
  181. FREPA(parts)
  182. {
  183. Part &part=parts[i];
  184. f>>part.image_index>>part.tex_rect>>part.center_offset>>part.original_size>>part.trimmed_size>>part.trim_pos;
  185. f._getStr(part.name);
  186. part.rotated=false;
  187. }
  188. if(images._load(f))
  189. if(f.ok())return true;
  190. }break;
  191. }
  192. del(); return false;
  193. }
  194. Bool ImageAtlas::save(C Str &name)C
  195. {
  196. File f; if(f.writeTry(name)){if(save(f) && f.flush())return true; f.del(); FDelFile(name);}
  197. return false;
  198. }
  199. Bool ImageAtlas::load(C Str &name)
  200. {
  201. File f; if(f.readTry(name))return load(f);
  202. del(); return false;
  203. }
  204. void ImageAtlas::operator=(C Str &name) {if(!load(name))Exit(S+"Can't load ImageAtlas \""+name+ "\".");}
  205. /******************************************************************************/
  206. }
  207. /******************************************************************************/