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.

506 lines
19KB

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