The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

498 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. namespace build_tools
  16. {
  17. Array<Drawable*> asArray (const Icons& icons)
  18. {
  19. Array<Drawable*> result;
  20. if (icons.small != nullptr)
  21. result.add (icons.small.get());
  22. if (icons.big != nullptr)
  23. result.add (icons.big.get());
  24. return result;
  25. }
  26. namespace mac
  27. {
  28. static Image fixIconImageSize (Drawable& image)
  29. {
  30. const int validSizes[] = { 16, 32, 64, 128, 256, 512, 1024 };
  31. auto w = image.getWidth();
  32. auto h = image.getHeight();
  33. int bestSize = 16;
  34. for (int size : validSizes)
  35. {
  36. if (w == h && w == size)
  37. {
  38. bestSize = w;
  39. break;
  40. }
  41. if (jmax (w, h) > size)
  42. bestSize = size;
  43. }
  44. return rescaleImageForIcon (image, bestSize);
  45. }
  46. static void writeIconData (MemoryOutputStream& out, const Image& image, const char* type)
  47. {
  48. MemoryOutputStream pngData;
  49. PNGImageFormat pngFormat;
  50. pngFormat.writeImageToStream (image, pngData);
  51. out.write (type, 4);
  52. out.writeIntBigEndian (8 + (int) pngData.getDataSize());
  53. out << pngData;
  54. }
  55. } // namespace mac
  56. void writeMacIcon (const Icons& icons, OutputStream& out)
  57. {
  58. MemoryOutputStream data;
  59. auto smallest = std::numeric_limits<int>::max();
  60. Drawable* smallestImage = nullptr;
  61. const auto images = asArray (icons);
  62. for (int i = 0; i < images.size(); ++i)
  63. {
  64. auto image = mac::fixIconImageSize (*images[i]);
  65. jassert (image.getWidth() == image.getHeight());
  66. if (image.getWidth() < smallest)
  67. {
  68. smallest = image.getWidth();
  69. smallestImage = images[i];
  70. }
  71. switch (image.getWidth())
  72. {
  73. case 16: mac::writeIconData (data, image, "icp4"); break;
  74. case 32: mac::writeIconData (data, image, "icp5"); break;
  75. case 64: mac::writeIconData (data, image, "icp6"); break;
  76. case 128: mac::writeIconData (data, image, "ic07"); break;
  77. case 256: mac::writeIconData (data, image, "ic08"); break;
  78. case 512: mac::writeIconData (data, image, "ic09"); break;
  79. case 1024: mac::writeIconData (data, image, "ic10"); break;
  80. default: break;
  81. }
  82. }
  83. jassert (data.getDataSize() > 0); // no suitable sized images?
  84. // If you only supply a 1024 image, the file doesn't work on 10.8, so we need
  85. // to force a smaller one in there too..
  86. if (smallest > 512 && smallestImage != nullptr)
  87. mac::writeIconData (data, rescaleImageForIcon (*smallestImage, 512), "ic09");
  88. out.write ("icns", 4);
  89. out.writeIntBigEndian ((int) data.getDataSize() + 8);
  90. out << data;
  91. }
  92. Image getBestIconForSize (const Icons& icons,
  93. int size,
  94. bool returnNullIfNothingBigEnough)
  95. {
  96. auto* const im = [&]() -> Drawable*
  97. {
  98. if ((icons.small != nullptr) != (icons.big != nullptr))
  99. return icons.small != nullptr ? icons.small.get() : icons.big.get();
  100. if (icons.small != nullptr && icons.big != nullptr)
  101. {
  102. if (icons.small->getWidth() >= size && icons.big->getWidth() >= size)
  103. return icons.small->getWidth() < icons.big->getWidth() ? icons.small.get() : icons.big.get();
  104. if (icons.small->getWidth() >= size)
  105. return icons.small.get();
  106. if (icons.big->getWidth() >= size)
  107. return icons.big.get();
  108. }
  109. return nullptr;
  110. }();
  111. if (im == nullptr)
  112. return {};
  113. if (returnNullIfNothingBigEnough && im->getWidth() < size && im->getHeight() < size)
  114. return {};
  115. return rescaleImageForIcon (*im, size);
  116. }
  117. namespace win
  118. {
  119. static void writeBMPImage (const Image& image, const int w, const int h, MemoryOutputStream& out)
  120. {
  121. int maskStride = (w / 8 + 3) & ~3;
  122. out.writeInt (40); // bitmapinfoheader size
  123. out.writeInt (w);
  124. out.writeInt (h * 2);
  125. out.writeShort (1); // planes
  126. out.writeShort (32); // bits
  127. out.writeInt (0); // compression
  128. out.writeInt ((h * w * 4) + (h * maskStride)); // size image
  129. out.writeInt (0); // x pixels per meter
  130. out.writeInt (0); // y pixels per meter
  131. out.writeInt (0); // clr used
  132. out.writeInt (0); // clr important
  133. Image::BitmapData bitmap (image, Image::BitmapData::readOnly);
  134. int alphaThreshold = 5;
  135. int y;
  136. for (y = h; --y >= 0;)
  137. {
  138. for (int x = 0; x < w; ++x)
  139. {
  140. auto pixel = bitmap.getPixelColour (x, y);
  141. if (pixel.getAlpha() <= alphaThreshold)
  142. {
  143. out.writeInt (0);
  144. }
  145. else
  146. {
  147. out.writeByte ((char) pixel.getBlue());
  148. out.writeByte ((char) pixel.getGreen());
  149. out.writeByte ((char) pixel.getRed());
  150. out.writeByte ((char) pixel.getAlpha());
  151. }
  152. }
  153. }
  154. for (y = h; --y >= 0;)
  155. {
  156. int mask = 0, count = 0;
  157. for (int x = 0; x < w; ++x)
  158. {
  159. auto pixel = bitmap.getPixelColour (x, y);
  160. mask <<= 1;
  161. if (pixel.getAlpha() <= alphaThreshold)
  162. mask |= 1;
  163. if (++count == 8)
  164. {
  165. out.writeByte ((char) mask);
  166. count = 0;
  167. mask = 0;
  168. }
  169. }
  170. if (mask != 0)
  171. out.writeByte ((char) mask);
  172. for (int i = maskStride - w / 8; --i >= 0;)
  173. out.writeByte (0);
  174. }
  175. }
  176. static void writeIcon (const Array<Image>& images, OutputStream& out)
  177. {
  178. out.writeShort (0); // reserved
  179. out.writeShort (1); // .ico tag
  180. out.writeShort ((short) images.size());
  181. MemoryOutputStream dataBlock;
  182. int imageDirEntrySize = 16;
  183. int dataBlockStart = 6 + images.size() * imageDirEntrySize;
  184. for (int i = 0; i < images.size(); ++i)
  185. {
  186. auto oldDataSize = dataBlock.getDataSize();
  187. auto& image = images.getReference (i);
  188. auto w = image.getWidth();
  189. auto h = image.getHeight();
  190. if (w >= 256 || h >= 256)
  191. {
  192. PNGImageFormat pngFormat;
  193. pngFormat.writeImageToStream (image, dataBlock);
  194. }
  195. else
  196. {
  197. writeBMPImage (image, w, h, dataBlock);
  198. }
  199. out.writeByte ((char) w);
  200. out.writeByte ((char) h);
  201. out.writeByte (0);
  202. out.writeByte (0);
  203. out.writeShort (1); // colour planes
  204. out.writeShort (32); // bits per pixel
  205. out.writeInt ((int) (dataBlock.getDataSize() - oldDataSize));
  206. out.writeInt (dataBlockStart + (int) oldDataSize);
  207. }
  208. jassert (out.getPosition() == dataBlockStart);
  209. out << dataBlock;
  210. }
  211. } // namespace win
  212. void writeWinIcon (const Icons& icons, OutputStream& os)
  213. {
  214. Array<Image> images;
  215. int sizes[] = { 16, 32, 48, 256 };
  216. for (int size : sizes)
  217. {
  218. auto im = getBestIconForSize (icons, size, true);
  219. if (im.isValid())
  220. images.add (im);
  221. }
  222. if (images.size() > 0)
  223. win::writeIcon (images, os);
  224. }
  225. void writeMacIcon (const Icons& icons, const File& file)
  226. {
  227. writeStreamToFile (file, [&] (juce::MemoryOutputStream& mo) { writeMacIcon (icons, mo); });
  228. }
  229. void writeWinIcon (const Icons& icons, const File& file)
  230. {
  231. writeStreamToFile (file, [&] (juce::MemoryOutputStream& mo) { writeWinIcon (icons, mo); });
  232. }
  233. Image rescaleImageForIcon (Drawable& d, const int size)
  234. {
  235. if (auto* drawableImage = dynamic_cast<DrawableImage*> (&d))
  236. {
  237. auto im = SoftwareImageType().convert (drawableImage->getImage());
  238. if (im.getWidth() == size && im.getHeight() == size)
  239. return im;
  240. // (scale it down in stages for better resampling)
  241. while (im.getWidth() > 2 * size && im.getHeight() > 2 * size)
  242. im = im.rescaled (im.getWidth() / 2,
  243. im.getHeight() / 2);
  244. Image newIm (Image::ARGB, size, size, true, SoftwareImageType());
  245. Graphics g (newIm);
  246. g.drawImageWithin (im, 0, 0, size, size,
  247. RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false);
  248. return newIm;
  249. }
  250. Image im (Image::ARGB, size, size, true, SoftwareImageType());
  251. Graphics g (im);
  252. d.drawWithin (g, im.getBounds().toFloat(), RectanglePlacement::centred, 1.0f);
  253. return im;
  254. }
  255. struct AppIconType
  256. {
  257. const char* idiom;
  258. const char* sizeString;
  259. const char* filename;
  260. const char* scale;
  261. int size;
  262. };
  263. static const AppIconType iOSAppIconTypes[]
  264. {
  265. { "iphone", "20x20", "Icon-Notification-20@2x.png", "2x", 40 },
  266. { "iphone", "20x20", "Icon-Notification-20@3x.png", "3x", 60 },
  267. { "iphone", "29x29", "Icon-29.png", "1x", 29 },
  268. { "iphone", "29x29", "Icon-29@2x.png", "2x", 58 },
  269. { "iphone", "29x29", "Icon-29@3x.png", "3x", 87 },
  270. { "iphone", "40x40", "Icon-Spotlight-40@2x.png", "2x", 80 },
  271. { "iphone", "40x40", "Icon-Spotlight-40@3x.png", "3x", 120 },
  272. { "iphone", "57x57", "Icon.png", "1x", 57 },
  273. { "iphone", "57x57", "Icon@2x.png", "2x", 114 },
  274. { "iphone", "60x60", "Icon-60@2x.png", "2x", 120 },
  275. { "iphone", "60x60", "Icon-@3x.png", "3x", 180 },
  276. { "ipad", "20x20", "Icon-Notifications-20.png", "1x", 20 },
  277. { "ipad", "20x20", "Icon-Notifications-20@2x.png", "2x", 40 },
  278. { "ipad", "29x29", "Icon-Small-1.png", "1x", 29 },
  279. { "ipad", "29x29", "Icon-Small@2x-1.png", "2x", 58 },
  280. { "ipad", "40x40", "Icon-Spotlight-40.png", "1x", 40 },
  281. { "ipad", "40x40", "Icon-Spotlight-40@2x-1.png", "2x", 80 },
  282. { "ipad", "50x50", "Icon-Small-50.png", "1x", 50 },
  283. { "ipad", "50x50", "Icon-Small-50@2x.png", "2x", 100 },
  284. { "ipad", "72x72", "Icon-72.png", "1x", 72 },
  285. { "ipad", "72x72", "Icon-72@2x.png", "2x", 144 },
  286. { "ipad", "76x76", "Icon-76.png", "1x", 76 },
  287. { "ipad", "76x76", "Icon-76@2x.png", "2x", 152 },
  288. { "ipad", "83.5x83.5", "Icon-83.5@2x.png", "2x", 167 },
  289. { "ios-marketing", "1024x1024", "Icon-AppStore-1024.png", "1x", 1024 }
  290. };
  291. static void createiOSIconFiles (const Icons& icons, File appIconSet)
  292. {
  293. const auto images = asArray (icons);
  294. if (! images.isEmpty())
  295. {
  296. for (auto& type : iOSAppIconTypes)
  297. {
  298. auto image = rescaleImageForIcon (*images.getFirst(), type.size);
  299. if (image.hasAlphaChannel())
  300. {
  301. Image background (Image::RGB, image.getWidth(), image.getHeight(), false);
  302. Graphics g (background);
  303. g.fillAll (Colours::white);
  304. g.drawImageWithin (image, 0, 0, image.getWidth(), image.getHeight(),
  305. RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize);
  306. image = background;
  307. }
  308. MemoryOutputStream pngData;
  309. PNGImageFormat pngFormat;
  310. pngFormat.writeImageToStream (image, pngData);
  311. overwriteFileIfDifferentOrThrow (appIconSet.getChildFile (type.filename), pngData);
  312. }
  313. }
  314. }
  315. static String getiOSAssetContents (var images)
  316. {
  317. DynamicObject::Ptr v (new DynamicObject());
  318. var info (new DynamicObject());
  319. info.getDynamicObject()->setProperty ("version", 1);
  320. info.getDynamicObject()->setProperty ("author", "xcode");
  321. v->setProperty ("images", images);
  322. v->setProperty ("info", info);
  323. return JSON::toString (var (v.get()));
  324. }
  325. //==============================================================================
  326. static String getiOSAppIconContents()
  327. {
  328. var images;
  329. for (auto& type : iOSAppIconTypes)
  330. {
  331. DynamicObject::Ptr d (new DynamicObject());
  332. d->setProperty ("idiom", type.idiom);
  333. d->setProperty ("size", type.sizeString);
  334. d->setProperty ("filename", type.filename);
  335. d->setProperty ("scale", type.scale);
  336. images.append (var (d.get()));
  337. }
  338. return getiOSAssetContents (images);
  339. }
  340. struct ImageType
  341. {
  342. const char* orientation;
  343. const char* idiom;
  344. const char* subtype;
  345. const char* extent;
  346. const char* scale;
  347. const char* filename;
  348. int width;
  349. int height;
  350. };
  351. static const ImageType iOSLaunchImageTypes[]
  352. {
  353. { "portrait", "iphone", nullptr, "full-screen", "2x", "LaunchImage-iphone-2x.png", 640, 960 },
  354. { "portrait", "iphone", "retina4", "full-screen", "2x", "LaunchImage-iphone-retina4.png", 640, 1136 },
  355. { "portrait", "ipad", nullptr, "full-screen", "1x", "LaunchImage-ipad-portrait-1x.png", 768, 1024 },
  356. { "landscape","ipad", nullptr, "full-screen", "1x", "LaunchImage-ipad-landscape-1x.png", 1024, 768 },
  357. { "portrait", "ipad", nullptr, "full-screen", "2x", "LaunchImage-ipad-portrait-2x.png", 1536, 2048 },
  358. { "landscape","ipad", nullptr, "full-screen", "2x", "LaunchImage-ipad-landscape-2x.png", 2048, 1536 }
  359. };
  360. static void createiOSLaunchImageFiles (const File& launchImageSet)
  361. {
  362. for (auto& type : iOSLaunchImageTypes)
  363. {
  364. Image image (Image::ARGB, type.width, type.height, true); // (empty black image)
  365. image.clear (image.getBounds(), Colours::black);
  366. MemoryOutputStream pngData;
  367. PNGImageFormat pngFormat;
  368. pngFormat.writeImageToStream (image, pngData);
  369. build_tools::overwriteFileIfDifferentOrThrow (launchImageSet.getChildFile (type.filename), pngData);
  370. }
  371. }
  372. static String getiOSLaunchImageContents()
  373. {
  374. var images;
  375. for (auto& type : iOSLaunchImageTypes)
  376. {
  377. DynamicObject::Ptr d (new DynamicObject());
  378. d->setProperty ("orientation", type.orientation);
  379. d->setProperty ("idiom", type.idiom);
  380. d->setProperty ("extent", type.extent);
  381. d->setProperty ("minimum-system-version", "7.0");
  382. d->setProperty ("scale", type.scale);
  383. d->setProperty ("filename", type.filename);
  384. if (type.subtype != nullptr)
  385. d->setProperty ("subtype", type.subtype);
  386. images.append (var (d.get()));
  387. }
  388. return getiOSAssetContents (images);
  389. }
  390. RelativePath createXcassetsFolderFromIcons (const Icons& icons,
  391. const File& targetFolder,
  392. String projectFilenameRootString)
  393. {
  394. const auto assets = targetFolder.getChildFile (projectFilenameRootString)
  395. .getChildFile ("Images.xcassets");
  396. const auto iconSet = assets.getChildFile ("AppIcon.appiconset");
  397. const auto launchImage = assets.getChildFile ("LaunchImage.launchimage");
  398. overwriteFileIfDifferentOrThrow (iconSet.getChildFile ("Contents.json"), getiOSAppIconContents());
  399. createiOSIconFiles (icons, iconSet);
  400. overwriteFileIfDifferentOrThrow (launchImage.getChildFile ("Contents.json"), getiOSLaunchImageContents());
  401. createiOSLaunchImageFiles (launchImage);
  402. return { assets, targetFolder, RelativePath::buildTargetFolder };
  403. }
  404. }
  405. }