1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-08 23:24:19 +00:00
JUCE/extras/Build/juce_build_tools/utils/juce_Icons.cpp
2023-10-09 14:49:18 +01:00

498 lines
18 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce::build_tools
{
Array<Drawable*> asArray (const Icons& icons)
{
Array<Drawable*> result;
if (icons.small != nullptr)
result.add (icons.small.get());
if (icons.big != nullptr)
result.add (icons.big.get());
return result;
}
namespace mac
{
static Image fixIconImageSize (Drawable& image)
{
const int validSizes[] = { 16, 32, 64, 128, 256, 512, 1024 };
auto w = image.getWidth();
auto h = image.getHeight();
int bestSize = 16;
for (int size : validSizes)
{
if (w == h && w == size)
{
bestSize = w;
break;
}
if (jmax (w, h) > size)
bestSize = size;
}
return rescaleImageForIcon (image, bestSize);
}
static void writeIconData (MemoryOutputStream& out, const Image& image, const char* type)
{
MemoryOutputStream pngData;
PNGImageFormat pngFormat;
pngFormat.writeImageToStream (image, pngData);
out.write (type, 4);
out.writeIntBigEndian (8 + (int) pngData.getDataSize());
out << pngData;
}
} // namespace mac
static void writeMacIcon (const Icons& icons, OutputStream& out)
{
MemoryOutputStream data;
auto smallest = std::numeric_limits<int>::max();
Drawable* smallestImage = nullptr;
const auto images = asArray (icons);
for (int i = 0; i < images.size(); ++i)
{
auto image = mac::fixIconImageSize (*images[i]);
jassert (image.getWidth() == image.getHeight());
if (image.getWidth() < smallest)
{
smallest = image.getWidth();
smallestImage = images[i];
}
switch (image.getWidth())
{
case 16: mac::writeIconData (data, image, "icp4"); break;
case 32: mac::writeIconData (data, image, "icp5"); break;
case 64: mac::writeIconData (data, image, "icp6"); break;
case 128: mac::writeIconData (data, image, "ic07"); break;
case 256: mac::writeIconData (data, image, "ic08"); break;
case 512: mac::writeIconData (data, image, "ic09"); break;
case 1024: mac::writeIconData (data, image, "ic10"); break;
default: break;
}
}
jassert (data.getDataSize() > 0); // no suitable sized images?
// If you only supply a 1024 image, the file doesn't work on 10.8, so we need
// to force a smaller one in there too..
if (smallest > 512 && smallestImage != nullptr)
mac::writeIconData (data, rescaleImageForIcon (*smallestImage, 512), "ic09");
out.write ("icns", 4);
out.writeIntBigEndian ((int) data.getDataSize() + 8);
out << data;
}
Image getBestIconForSize (const Icons& icons,
int size,
bool returnNullIfNothingBigEnough)
{
auto* const im = [&]() -> Drawable*
{
if ((icons.small != nullptr) != (icons.big != nullptr))
return icons.small != nullptr ? icons.small.get() : icons.big.get();
if (icons.small != nullptr && icons.big != nullptr)
{
if (icons.small->getWidth() >= size && icons.big->getWidth() >= size)
return icons.small->getWidth() < icons.big->getWidth() ? icons.small.get() : icons.big.get();
if (icons.small->getWidth() >= size)
return icons.small.get();
if (icons.big->getWidth() >= size)
return icons.big.get();
}
return nullptr;
}();
if (im == nullptr)
return {};
if (returnNullIfNothingBigEnough && im->getWidth() < size && im->getHeight() < size)
return {};
return rescaleImageForIcon (*im, size);
}
namespace win
{
static void writeBMPImage (const Image& image, const int w, const int h, MemoryOutputStream& out)
{
int maskStride = (w / 8 + 3) & ~3;
out.writeInt (40); // bitmapinfoheader size
out.writeInt (w);
out.writeInt (h * 2);
out.writeShort (1); // planes
out.writeShort (32); // bits
out.writeInt (0); // compression
out.writeInt ((h * w * 4) + (h * maskStride)); // size image
out.writeInt (0); // x pixels per meter
out.writeInt (0); // y pixels per meter
out.writeInt (0); // clr used
out.writeInt (0); // clr important
Image::BitmapData bitmap (image, Image::BitmapData::readOnly);
int alphaThreshold = 5;
int y;
for (y = h; --y >= 0;)
{
for (int x = 0; x < w; ++x)
{
auto pixel = bitmap.getPixelColour (x, y);
if (pixel.getAlpha() <= alphaThreshold)
{
out.writeInt (0);
}
else
{
out.writeByte ((char) pixel.getBlue());
out.writeByte ((char) pixel.getGreen());
out.writeByte ((char) pixel.getRed());
out.writeByte ((char) pixel.getAlpha());
}
}
}
for (y = h; --y >= 0;)
{
int mask = 0, count = 0;
for (int x = 0; x < w; ++x)
{
auto pixel = bitmap.getPixelColour (x, y);
mask <<= 1;
if (pixel.getAlpha() <= alphaThreshold)
mask |= 1;
if (++count == 8)
{
out.writeByte ((char) mask);
count = 0;
mask = 0;
}
}
if (mask != 0)
out.writeByte ((char) mask);
for (int i = maskStride - w / 8; --i >= 0;)
out.writeByte (0);
}
}
static void writeIcon (const Array<Image>& images, OutputStream& out)
{
out.writeShort (0); // reserved
out.writeShort (1); // .ico tag
out.writeShort ((short) images.size());
MemoryOutputStream dataBlock;
int imageDirEntrySize = 16;
int dataBlockStart = 6 + images.size() * imageDirEntrySize;
for (int i = 0; i < images.size(); ++i)
{
auto oldDataSize = dataBlock.getDataSize();
auto& image = images.getReference (i);
auto w = image.getWidth();
auto h = image.getHeight();
if (w >= 256 || h >= 256)
{
PNGImageFormat pngFormat;
pngFormat.writeImageToStream (image, dataBlock);
}
else
{
writeBMPImage (image, w, h, dataBlock);
}
out.writeByte ((char) w);
out.writeByte ((char) h);
out.writeByte (0);
out.writeByte (0);
out.writeShort (1); // colour planes
out.writeShort (32); // bits per pixel
out.writeInt ((int) (dataBlock.getDataSize() - oldDataSize));
out.writeInt (dataBlockStart + (int) oldDataSize);
}
jassert (out.getPosition() == dataBlockStart);
out << dataBlock;
}
} // namespace win
static void writeWinIcon (const Icons& icons, OutputStream& os)
{
Array<Image> images;
int sizes[] = { 16, 32, 48, 256 };
for (int size : sizes)
{
auto im = getBestIconForSize (icons, size, true);
if (im.isValid())
images.add (im);
}
if (images.size() > 0)
win::writeIcon (images, os);
}
void writeMacIcon (const Icons& icons, const File& file)
{
writeStreamToFile (file, [&] (MemoryOutputStream& mo) { writeMacIcon (icons, mo); });
}
void writeWinIcon (const Icons& icons, const File& file)
{
writeStreamToFile (file, [&] (MemoryOutputStream& mo) { writeWinIcon (icons, mo); });
}
Image rescaleImageForIcon (Drawable& d, const int size)
{
if (auto* drawableImage = dynamic_cast<DrawableImage*> (&d))
{
auto im = SoftwareImageType().convert (drawableImage->getImage());
if (im.getWidth() == size && im.getHeight() == size)
return im;
// (scale it down in stages for better resampling)
while (im.getWidth() > 2 * size && im.getHeight() > 2 * size)
im = im.rescaled (im.getWidth() / 2,
im.getHeight() / 2);
Image newIm (Image::ARGB, size, size, true, SoftwareImageType());
Graphics g (newIm);
g.drawImageWithin (im, 0, 0, size, size,
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false);
return newIm;
}
Image im (Image::ARGB, size, size, true, SoftwareImageType());
Graphics g (im);
d.drawWithin (g, im.getBounds().toFloat(), RectanglePlacement::centred, 1.0f);
return im;
}
struct AppIconType
{
const char* idiom;
const char* sizeString;
const char* filename;
const char* scale;
int size;
};
static const AppIconType iOSAppIconTypes[]
{
{ "iphone", "20x20", "Icon-Notification-20@2x.png", "2x", 40 },
{ "iphone", "20x20", "Icon-Notification-20@3x.png", "3x", 60 },
{ "iphone", "29x29", "Icon-29.png", "1x", 29 },
{ "iphone", "29x29", "Icon-29@2x.png", "2x", 58 },
{ "iphone", "29x29", "Icon-29@3x.png", "3x", 87 },
{ "iphone", "40x40", "Icon-Spotlight-40@2x.png", "2x", 80 },
{ "iphone", "40x40", "Icon-Spotlight-40@3x.png", "3x", 120 },
{ "iphone", "60x60", "Icon-60@2x.png", "2x", 120 },
{ "iphone", "60x60", "Icon-@3x.png", "3x", 180 },
{ "ipad", "20x20", "Icon-Notifications-20.png", "1x", 20 },
{ "ipad", "20x20", "Icon-Notifications-20@2x.png", "2x", 40 },
{ "ipad", "29x29", "Icon-Small-1.png", "1x", 29 },
{ "ipad", "29x29", "Icon-Small@2x-1.png", "2x", 58 },
{ "ipad", "40x40", "Icon-Spotlight-40.png", "1x", 40 },
{ "ipad", "40x40", "Icon-Spotlight-40@2x-1.png", "2x", 80 },
{ "ipad", "76x76", "Icon-76.png", "1x", 76 },
{ "ipad", "76x76", "Icon-76@2x.png", "2x", 152 },
{ "ipad", "83.5x83.5", "Icon-83.5@2x.png", "2x", 167 },
{ "ios-marketing", "1024x1024", "Icon-AppStore-1024.png", "1x", 1024 }
};
static void createiOSIconFiles (const Icons& icons, File appIconSet)
{
auto* imageToUse = icons.big != nullptr ? icons.big.get()
: icons.small.get();
if (imageToUse != nullptr)
{
for (auto& type : iOSAppIconTypes)
{
auto image = rescaleImageForIcon (*imageToUse, type.size);
if (image.hasAlphaChannel())
{
Image background (Image::RGB, image.getWidth(), image.getHeight(), false);
Graphics g (background);
g.fillAll (Colours::white);
g.drawImageWithin (image, 0, 0, image.getWidth(), image.getHeight(),
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize);
image = background;
}
MemoryOutputStream pngData;
PNGImageFormat pngFormat;
pngFormat.writeImageToStream (image, pngData);
overwriteFileIfDifferentOrThrow (appIconSet.getChildFile (type.filename), pngData);
}
}
}
static String getiOSAssetContents (var images)
{
DynamicObject::Ptr v (new DynamicObject());
var info (new DynamicObject());
info.getDynamicObject()->setProperty ("version", 1);
info.getDynamicObject()->setProperty ("author", "xcode");
v->setProperty ("images", images);
v->setProperty ("info", info);
return JSON::toString (var (v.get()));
}
//==============================================================================
static String getiOSAppIconContents()
{
var images;
for (auto& type : iOSAppIconTypes)
{
DynamicObject::Ptr d (new DynamicObject());
d->setProperty ("idiom", type.idiom);
d->setProperty ("size", type.sizeString);
d->setProperty ("filename", type.filename);
d->setProperty ("scale", type.scale);
images.append (var (d.get()));
}
return getiOSAssetContents (images);
}
struct ImageType
{
const char* orientation;
const char* idiom;
const char* subtype;
const char* extent;
const char* scale;
const char* filename;
int width;
int height;
};
static const ImageType iOSLaunchImageTypes[]
{
{ "portrait", "iphone", nullptr, "full-screen", "2x", "LaunchImage-iphone-2x.png", 640, 960 },
{ "portrait", "iphone", "retina4", "full-screen", "2x", "LaunchImage-iphone-retina4.png", 640, 1136 },
{ "portrait", "ipad", nullptr, "full-screen", "1x", "LaunchImage-ipad-portrait-1x.png", 768, 1024 },
{ "landscape","ipad", nullptr, "full-screen", "1x", "LaunchImage-ipad-landscape-1x.png", 1024, 768 },
{ "portrait", "ipad", nullptr, "full-screen", "2x", "LaunchImage-ipad-portrait-2x.png", 1536, 2048 },
{ "landscape","ipad", nullptr, "full-screen", "2x", "LaunchImage-ipad-landscape-2x.png", 2048, 1536 }
};
static void createiOSLaunchImageFiles (const File& launchImageSet)
{
for (auto& type : iOSLaunchImageTypes)
{
Image image (Image::ARGB, type.width, type.height, true); // (empty black image)
image.clear (image.getBounds(), Colours::black);
MemoryOutputStream pngData;
PNGImageFormat pngFormat;
pngFormat.writeImageToStream (image, pngData);
build_tools::overwriteFileIfDifferentOrThrow (launchImageSet.getChildFile (type.filename), pngData);
}
}
static String getiOSLaunchImageContents()
{
var images;
for (auto& type : iOSLaunchImageTypes)
{
DynamicObject::Ptr d (new DynamicObject());
d->setProperty ("orientation", type.orientation);
d->setProperty ("idiom", type.idiom);
d->setProperty ("extent", type.extent);
d->setProperty ("minimum-system-version", "7.0");
d->setProperty ("scale", type.scale);
d->setProperty ("filename", type.filename);
if (type.subtype != nullptr)
d->setProperty ("subtype", type.subtype);
images.append (var (d.get()));
}
return getiOSAssetContents (images);
}
RelativePath createXcassetsFolderFromIcons (const Icons& icons,
const File& targetFolder,
String projectFilenameRootString)
{
const auto assets = targetFolder.getChildFile (projectFilenameRootString)
.getChildFile ("Images.xcassets");
const auto iconSet = assets.getChildFile ("AppIcon.appiconset");
const auto launchImage = assets.getChildFile ("LaunchImage.launchimage");
overwriteFileIfDifferentOrThrow (iconSet.getChildFile ("Contents.json"), getiOSAppIconContents());
createiOSIconFiles (icons, iconSet);
overwriteFileIfDifferentOrThrow (launchImage.getChildFile ("Contents.json"), getiOSLaunchImageContents());
createiOSLaunchImageFiles (launchImage);
return { assets, targetFolder, RelativePath::buildTargetFolder };
}
} // namespace juce::build_tools