ICO.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************/
  5. #pragma pack(push, 1)
  6. struct ICONDIR
  7. {
  8. UShort reserved, type, images;
  9. };
  10. struct ICONDIRENTRY
  11. {
  12. Byte width, height, colors, reserved;
  13. UShort hot_x, hot_y;
  14. UInt size, offset;
  15. };
  16. #pragma pack(pop)
  17. /******************************************************************************/
  18. Bool Image::ImportICO(File &f)
  19. {
  20. Long pos=f.pos(); // remember current position in case we're not at the start
  21. ICONDIR dir; f>>dir;
  22. if(!Unaligned(dir.reserved) && Unaligned(dir.images)<=256 && (Unaligned(dir.type)==1 || Unaligned(dir.type)==2))
  23. {
  24. Bool ico=(Unaligned(dir.type)==1), cur=(Unaligned(dir.type)==2);
  25. Int best_x, best_y, best_col;
  26. UInt best_offset=0, best_size, end=0;
  27. REP(Unaligned(dir.images))
  28. {
  29. ICONDIRENTRY entry; f>>entry;
  30. if(Unaligned(entry.size)>=f.size() || Unaligned(entry.offset)>f.size() || Unaligned(entry.reserved))goto error;
  31. Int x=(Unaligned(entry.width ) ? Unaligned(entry.width ) : 256),
  32. y=(Unaligned(entry.height) ? Unaligned(entry.height) : 256),
  33. col=(ico ? Unaligned(entry.hot_y) : Unaligned(entry.colors) ? Unaligned(entry.colors) : 256);
  34. MAX(end, Unaligned(entry.offset)+Unaligned(entry.size));
  35. if(!best_offset || (x>best_x || y>best_y) || (x==best_x && y==best_x && (col>best_col || (col==best_col && Unaligned(entry.size)>best_size)))) // if we've encountered same size and colors then use the one of bigger raw size
  36. {
  37. best_offset=Unaligned(entry.offset);
  38. best_size =Unaligned(entry.size );
  39. best_x =x;
  40. best_y =y;
  41. best_col =col;
  42. }
  43. }
  44. if(best_offset && f.ok())
  45. {
  46. if(f.pos(best_offset+pos) && ImportBMPRaw(f, true)){f.pos(end+pos); return true;} // try as BMP and move to the end of the file on success
  47. if(f.pos(best_offset+pos) && ImportPNG (f )){f.pos(end+pos); return true;} // try as PNG and move to the end of the file on success
  48. }
  49. }
  50. error:;
  51. del(); return false;
  52. }
  53. Bool Image::ImportICO(C Str &name)
  54. {
  55. File f; if(f.readTry(name))return ImportICO(f);
  56. del(); return false;
  57. }
  58. /******************************************************************************/
  59. struct Mip
  60. {
  61. VecI2 size;
  62. File data;
  63. };
  64. Bool Image::ExportICO(File &f)C
  65. {
  66. if(!is())return false;
  67. C Image *src=this;
  68. Image temp;
  69. Bool legacy=false, full_size_included=false;
  70. Mip mips[5]; // up to 5 mip-maps, providing mip-maps with Esenthel's high quality filtering allows the OS to use them instead of its own filtering, one will be used for Windows XP, on Windows we don't need many mip maps
  71. FREPA(mips)
  72. {
  73. Mip &mip=mips[i];
  74. if(!legacy && (i==Elms(mips)-1 || src->size().max()<96)) // set one mip to be Windows XP compatible (48x48 not compressed)
  75. {
  76. legacy=true;
  77. VecI2 size=src->size();
  78. if(size.x>48)size=size*48/size.x;
  79. if(size.y>48)size=size*48/size.y;
  80. Image xp; if(src->copyTry(xp, Max(1, size.x), Max(1, size.y), 1, IMAGE_B8G8R8A8, IMAGE_SOFT, 1, FILTER_BEST, true, true)) // ICO uses BGRA order
  81. {
  82. if(xp.ExportBMPRaw(mip.data.writeMem(), 4, true))
  83. {
  84. mip.size=xp.size(); if(xp.size()==T.size())full_size_included=true;
  85. }else mip.data.del();
  86. }
  87. }else
  88. {
  89. if(full_size_included)
  90. {
  91. VecI2 size=src->size()/2; if(size.max()<16)break;
  92. if(src->copyTry(temp, Max(1, size.x), Max(1, size.y), 1, ImageTI[src->type()].compressed ? IMAGE_R8G8B8A8 : -1, IMAGE_SOFT, 1, FILTER_BEST, true, true))src=&temp;else break;
  93. }
  94. if(src->ExportPNG(mip.data.writeMem(), 1)){mip.size=src->size(); full_size_included=true;}else mip.data.del();
  95. }
  96. }
  97. Int images=0; FREPA(mips)if(mips[i].data.is())images++; if(!images)return false;
  98. ICONDIR dir; _Unaligned(dir.reserved, 0); _Unaligned(dir.type, 1); _Unaligned(dir.images, images); f<<dir;
  99. ICONDIRENTRY entry; _Unaligned(entry.colors, 0); _Unaligned(entry.reserved, 0); _Unaligned(entry.hot_x, 1); _Unaligned(entry.hot_y, 32);
  100. UInt offset=SIZE(ICONDIR)+SIZE(ICONDIRENTRY)*images;
  101. FREPA(mips)
  102. {
  103. Mip &mip=mips[i]; if(mip.data.is())
  104. {
  105. _Unaligned(entry.width , (mip.size.x>=256) ? 0 : mip.size.x);
  106. _Unaligned(entry.height, (mip.size.y>=256) ? 0 : mip.size.y);
  107. _Unaligned(entry.size , mip.data.size());
  108. _Unaligned(entry.offset, offset);
  109. f<<entry;
  110. offset+=mip.data.size();
  111. }
  112. }
  113. FREPA(mips)
  114. {
  115. File &data=mips[i].data; if(data.is())
  116. {
  117. data.pos(0); if(!data.copy(f))return false;
  118. }
  119. }
  120. return f.ok();
  121. }
  122. Bool Image::ExportICO(C Str &name)C
  123. {
  124. File f; if(f.writeTry(name)){if(ExportICO(f) && f.flush())return true; f.del(); FDelFile(name);}
  125. return false;
  126. }
  127. /******************************************************************************/
  128. static void ICNSEncodeRLE24(File &f, C Byte *data, Int size) // Code based on LibICNS - http://icns.sourceforge.net/, assumes that 'data' is in IMAGE_R8G8B8A8 format
  129. {
  130. // Assumptions of what icns rle data is all about:
  131. // A) Each channel is encoded indepenent of the next.
  132. // B) An encoded channel looks like this:
  133. // 0xRL 0xCV 0xCV 0xRL 0xCV - RL is run-length and CV is color value.
  134. // C) There are two types of runs
  135. // 1) Run of same value - high bit of RL is set
  136. // 2) Run of differing values - high bit of RL is NOT set
  137. // D) 0xRL also has two ranges
  138. // 1) for set high bit RL, 3 to 130
  139. // 2) for clr high bit RL, 1 to 128
  140. // E) 0xRL byte is therefore set as follows:
  141. // 1) for same values, RL=RL - 1
  142. // 2) different values, RL=RL+125
  143. // 3) both methods will automatically set the high bit appropriately
  144. // F) 0xCV byte are set accordingly
  145. // 1) for differing values, run of all differing values
  146. // 2) for same values, only one byte of that values
  147. // Estimations put the absolute worst case scenario as the
  148. // final compressed data being slightly LARGER. So we need to be
  149. // careful about allocating memory. (Did I miss something?)
  150. // tests seem to indicate it will never be larger than the original
  151. // There's always going to be 4 channels in this
  152. // so we want our counter to increment through
  153. // channels, not bytes....
  154. UInt dataInChanSize=size/4;
  155. // Move forward 4 bytes for big size - who knows why this should be
  156. if(size>=65536)f.putUInt(0);
  157. Byte dataRun[256]; Zero(dataRun);
  158. // Data is stored in red run, green run,blue run
  159. // So we compress from pixel format RGBA
  160. // RED: byte[0], byte[4], byte[8] ...
  161. // GREEN: byte[1], byte[5], byte[9] ...
  162. // BLUE: byte[2], byte[6], byte[10] ...
  163. // ALPHA: byte[3], byte[7], byte[11] do nothing with these bytes
  164. for(Byte colorOffset=0; colorOffset<3; colorOffset++)
  165. {
  166. Int runCount=0;
  167. // Set the first byte of the run...
  168. dataRun[0]=*(data+colorOffset);
  169. // Start with a runlength of 1 for the first byte
  170. Byte runLength=1; // Runs will never go over 130, one byte is ok
  171. // Assume that the run will be different for now... We can change this later
  172. Bool runType=false; // 0 for low bit (different), 1 for high bit (same)
  173. // Start one byte ahead
  174. for(UInt dataInCount=1; dataInCount<dataInChanSize; dataInCount++)
  175. {
  176. Byte dataByte=*(data+colorOffset+(dataInCount*4));
  177. if(runLength<2)
  178. {
  179. // Simply append to the current run
  180. dataRun[runLength++]=dataByte;
  181. }else
  182. if(runLength==2)
  183. {
  184. // Decide here if the run should be same values or different values
  185. // If the last three values were the same, we can change to a same-type run
  186. if(dataByte==dataRun[runLength-1] && dataByte==dataRun[runLength-2])runType=1;else runType=0;
  187. dataRun[runLength++]=dataByte;
  188. }else // Greater than or equal to 2
  189. {
  190. if(runType==0 && runLength<128) // Different type run
  191. {
  192. // If the new value matches both of the last two values, we have a new
  193. // same-type run starting with the previous two bytes
  194. if(dataByte==dataRun[runLength-1] && dataByte==dataRun[runLength-2])
  195. {
  196. // Set the RL byte
  197. f.putByte(runLength-3);
  198. // Copy 0 to runLength-2 bytes to the RLE data here
  199. f.put(dataRun, runLength-2);
  200. runCount++;
  201. // Set up the new same-type run
  202. dataRun[0]=dataRun[runLength-2];
  203. dataRun[1]=dataRun[runLength-1];
  204. dataRun[2]=dataByte;
  205. runLength=3;
  206. runType=1;
  207. }else // They don't match, so we can proceed
  208. {
  209. dataRun[runLength++]=dataByte;
  210. }
  211. }else
  212. if(runType==1 && runLength<130) // Same type run
  213. {
  214. // If the new value matches both of the last two values, we
  215. // can safely continue
  216. if(dataByte==dataRun[runLength-1] && dataByte==dataRun[runLength-2])
  217. {
  218. dataRun[runLength++]=dataByte;
  219. }else // They don't match, so we need to start a new run
  220. {
  221. // Set the RL byte
  222. f.putByte(runLength+125);
  223. // Only copy the first byte, since all the remaining values are identical
  224. f.putByte(dataRun[0]);
  225. runCount++;
  226. // Copy 0 to runLength bytes to the RLE data here
  227. dataRun[0]=dataByte;
  228. runLength=1;
  229. runType=0;
  230. }
  231. }else // Exceeded run limit, need to start a new one
  232. {
  233. if(runType==0)
  234. {
  235. // Set the RL byte low
  236. f.putByte(runLength-1);
  237. // Copy 0 to runLength bytes to the RLE data here
  238. f.put(dataRun, runLength);
  239. }else
  240. if(runType==1)
  241. {
  242. // Set the RL byte high
  243. f.putByte(runLength+125);
  244. // Only copy the first byte, since all the remaining values are identical
  245. f.putByte(dataRun[0]);
  246. }
  247. runCount++;
  248. // Copy 0 to runLength bytes to the RLE data here
  249. dataRun[0]=dataByte;
  250. runLength=1;
  251. runType=0;
  252. }
  253. }
  254. }
  255. // Copy the end of the last run
  256. if(runLength>0)
  257. {
  258. if(runType==0)
  259. {
  260. // Set the RL byte low
  261. f.putByte(runLength-1);
  262. // Copy 0 to runLength bytes to the RLE data here
  263. f.put(dataRun, runLength);
  264. }else
  265. if(runType==1)
  266. {
  267. // Set the RL byte high
  268. f.putByte(runLength+125);
  269. // Only copy the first byte, since all the remaining values are identical
  270. f.putByte(dataRun[0]);
  271. }
  272. runCount++;
  273. }
  274. }
  275. }
  276. struct MipAlpha : Mip
  277. {
  278. Bool png;
  279. File alpha;
  280. };
  281. Bool Image::ExportICNS(File &f)C // ICNS stores data using RLE, PNG or JPEG 2000 (following implementation uses RLE and PNG), format is big-endian
  282. {
  283. if(!is())return false;
  284. C Image *src=this;
  285. Image temp;
  286. // Mac OS X (as of 10.10) has following issues that affect opening of apps with those icons (this doesn't apply to opening just the icon themselves)
  287. // - setting up 1024 size will make it ignored (maybe it's because it's "@2x retina"
  288. // - setting up 16,32,64 size as PNG will make it look corrupt (maybe it's related to 'icp*' instead of 'ic0*' however those were tried too and failed)
  289. Int size=Mid(NearestPow2(src->size().avgI()), 16, 512);
  290. if(size==64)size=128; // because 64 size can't be used as PNG, it has to be done as RLE, and there it is processed as 48, which will be very low res, so use 128 as the max in that case
  291. if(src->w()!=size || src->h()!=size)if(src->copyTry(temp, size, size, 1, ImageTI[src->type()].compressed ? IMAGE_R8G8B8A8 : -1, IMAGE_SOFT, 1, FILTER_BEST, true, true))src=&temp;else return false;
  292. MipAlpha mips[3]; // up to 3 mip-maps, providing mip-maps with Esenthel's high quality filtering allows the OS to use them instead of its own filtering
  293. FREPA(mips)
  294. {
  295. MipAlpha &mip=mips[i];
  296. if(i)
  297. {
  298. Int s=src->w()/2; if(s<16)break;
  299. if(src->copyTry(temp, s, s, 1, ImageTI[src->type()].compressed ? IMAGE_R8G8B8A8 : -1, IMAGE_SOFT, 1, FILTER_BEST, true, true))src=&temp;else break;
  300. }
  301. if(src->w()<=64) // RLE
  302. {
  303. Int size=src->w(); if(size>128)size=128;else if(size==64)size=48; // RLE doesn't support sizes >128 and 64
  304. C Image *s=src;
  305. Image rle;
  306. if(s->hwType()!=IMAGE_R8G8B8A8 || s->w()!=size || s->size()!=s->hwSize()) // 'ICNSEncodeRLE24' requires IMAGE_R8G8B8A8 without any hw padding
  307. if(s->copyTry(rle, size, size, 1, IMAGE_R8G8B8A8, IMAGE_SOFT, 1, FILTER_BEST, true, true))s=&rle;else continue;
  308. if(s->lockRead())
  309. {
  310. mip.png=false;
  311. mip.size=s->size();
  312. ICNSEncodeRLE24(mip.data.writeMem(), s->data(), s->pitch2());
  313. mip.alpha.writeMem();
  314. FREPD(y, s->h())
  315. FREPD(x, s->w())mip.alpha.putByte(s->color(x, y).a);
  316. s->unlock();
  317. }
  318. }else // PNG
  319. {
  320. if(src->ExportPNG(mip.data.writeMem(), 1))
  321. {
  322. mip.png=true;
  323. mip.size=src->size();
  324. }else mip.data.del();
  325. }
  326. }
  327. // header
  328. f.putUInt(CC4('i', 'c', 'n', 's'));
  329. UInt u=4+4; FREPA(mips)
  330. {
  331. MipAlpha &mip=mips[i];
  332. if(mip.data .is())u+=4+4 + mip.data .size();
  333. if(mip.alpha.is())u+=4+4 + mip.alpha.size();
  334. }
  335. if(u<=4+4)return false; // no mips
  336. SwapEndian(u); f.putUInt(u); // (ICNS header) + (Image header + Image data)
  337. // mips
  338. FREPA(mips)
  339. {
  340. MipAlpha &mip=mips[i];
  341. if(mip.data.is())
  342. {
  343. // mip header
  344. if(mip.png)switch(mip.size.x)
  345. {
  346. //case 16: f.putUInt(CC4('i', 'c', 'p', '4')); break; corrupt
  347. //case 32: f.putUInt(CC4('i', 'c', 'p', '5')); break; corrupt
  348. //case 64: f.putUInt(CC4('i', 'c', 'p', '6')); break; corrupt
  349. case 128: f.putUInt(CC4('i', 'c', '0', '7')); break;
  350. case 256: f.putUInt(CC4('i', 'c', '0', '8')); break;
  351. case 512: f.putUInt(CC4('i', 'c', '0', '9')); break;
  352. //case 1024: f.putUInt(CC4('i', 'c', '1', '0')); break; ignored
  353. }else
  354. switch(mip.size.x)
  355. {
  356. case 16: f.putUInt(CC4('i', 's', '3', '2')); break;
  357. case 32: f.putUInt(CC4('i', 'l', '3', '2')); break;
  358. case 48: f.putUInt(CC4('i', 'h', '3', '2')); break;
  359. //case 128: f.putUInt(CC4('i', 't', '3', '2')); break; currently always saved as PNG
  360. }
  361. u=4+4 + mip.data.size(); SwapEndian(u); f.putUInt(u);
  362. // mip data
  363. mip.data.pos(0); if(!mip.data.copy(f))return false;
  364. }
  365. if(mip.alpha.is())
  366. {
  367. // mip header
  368. switch(mip.size.x)
  369. {
  370. case 16: f.putUInt(CC4('s', '8', 'm', 'k')); break;
  371. case 32: f.putUInt(CC4('l', '8', 'm', 'k')); break;
  372. case 48: f.putUInt(CC4('h', '8', 'm', 'k')); break;
  373. //case 128: f.putUInt(CC4('t', '8', 'm', 'k')); break; currently always saved as PNG
  374. }
  375. u=4+4 + mip.alpha.size(); SwapEndian(u); f.putUInt(u);
  376. // mip data
  377. mip.alpha.pos(0); if(!mip.alpha.copy(f))return false;
  378. }
  379. }
  380. return f.ok();
  381. }
  382. Bool Image::ExportICNS(C Str &name)C
  383. {
  384. File f; if(f.writeTry(name)){if(ExportICNS(f) && f.flush())return true; f.del(); FDelFile(name);}
  385. return false;
  386. }
  387. /******************************************************************************/
  388. }
  389. /******************************************************************************/