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.

499 lines
18KB

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