blob: 6da7fdd74c6d1d87f22cbddc895c3ee600df99af [file] [log] [blame]
/*
* Copyright (C) 2003-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Color.h"
#if USE(CG)
#include "ColorSpaceCG.h"
#include "DestinationColorSpace.h"
#include <mutex>
#include <pal/spi/cg/CoreGraphicsSPI.h>
#include <wtf/Assertions.h>
#include <wtf/Lock.h>
#include <wtf/RetainPtr.h>
#include <wtf/StdLibExtras.h>
#include <wtf/TinyLRUCache.h>
namespace WebCore {
static RetainPtr<CGColorRef> createCGColor(const Color&);
}
namespace WTF {
template<>
RetainPtr<CGColorRef> TinyLRUCachePolicy<WebCore::Color, RetainPtr<CGColorRef>>::createValueForKey(const WebCore::Color& color)
{
return WebCore::createCGColor(color);
}
} // namespace WTF
namespace WebCore {
std::optional<SRGBA<uint8_t>> roundAndClampToSRGBALossy(CGColorRef color)
{
// FIXME: Interpreting components of a color in an arbitrary color space
// as sRGB could be wrong, not just lossy.
if (!color)
return std::nullopt;
size_t numComponents = CGColorGetNumberOfComponents(color);
const CGFloat* components = CGColorGetComponents(color);
float r = 0;
float g = 0;
float b = 0;
float a = 0;
switch (numComponents) {
case 2:
r = g = b = components[0];
a = components[1];
break;
case 4:
r = components[0];
g = components[1];
b = components[2];
a = components[3];
break;
default:
ASSERT_NOT_REACHED();
}
return convertColor<SRGBA<uint8_t>>(makeFromComponentsClamping<SRGBA<float>>(r, g, b, a ));
}
Color Color::createAndLosslesslyConvertToSupportedColorSpace(CGColorRef color, OptionSet<Flags> flags)
{
auto sourceCGColorSpace = CGColorGetColorSpace(color);
#if HAVE(CORE_GRAPHICS_XYZ_COLOR_SPACE)
auto destinationCGColorSpace = xyzD50ColorSpaceRef();
auto destinationColorSpace = ColorSpace::XYZ_D50;
#else
auto destinationCGColorSpace = sRGBColorSpaceRef();
auto destinationColorSpace = ColorSpace::SRGB;
#endif
ASSERT(CGColorSpaceGetNumberOfComponents(destinationCGColorSpace) == 3);
auto sourceComponents = CGColorGetComponents(color);
CGFloat destinationComponents[3] { };
auto transform = adoptCF(CGColorTransformCreate(destinationCGColorSpace, nullptr));
auto result = CGColorTransformConvertColorComponents(transform.get(), sourceCGColorSpace, kCGRenderingIntentDefault, sourceComponents, destinationComponents);
ASSERT_UNUSED(result, result);
float a = destinationComponents[0];
float b = destinationComponents[1];
float c = destinationComponents[2];
float alpha = CGColorGetAlpha(color);
return Color(OutOfLineComponents::create({ a, b, c, alpha }), destinationColorSpace, flags);
}
Color Color::createAndPreserveColorSpace(CGColorRef color, OptionSet<Flags> flags)
{
if (!color)
return Color();
size_t numComponents = CGColorGetNumberOfComponents(color);
auto colorSpace = colorSpaceForCGColorSpace(CGColorGetColorSpace(color));
if (numComponents != 4 || !colorSpace)
return createAndLosslesslyConvertToSupportedColorSpace(color, flags);
const CGFloat* components = CGColorGetComponents(color);
float a = components[0];
float b = components[1];
float c = components[2];
float alpha = components[3];
return Color(OutOfLineComponents::create({ a, b, c, alpha }), *colorSpace, flags);
}
static std::pair<CGColorSpaceRef, ColorComponents<float, 4>> convertToCGCompatibleComponents(ColorSpace colorSpace, ColorComponents<float, 4> components)
{
// Some CG ports don't support all the color spaces required and return
// nullptr for unsupported color spaces. In those cases, we eagerly convert
// the color into either extended sRGB or normal sRGB, if extended sRGB is
// not supported.
auto cgColorSpace = cachedNullableCGColorSpace(colorSpace);
if (!cgColorSpace) {
#if HAVE(CORE_GRAPHICS_EXTENDED_SRGB_COLOR_SPACE)
auto componentsConvertedToExtendedSRGBA = callWithColorType(components, colorSpace, [] (const auto& color) {
return asColorComponents(convertColor<ExtendedSRGBA<float>>(color));
});
return { extendedSRGBColorSpaceRef(), componentsConvertedToExtendedSRGBA };
#else
auto componentsConvertedToSRGBA = callWithColorType(components, colorSpace, [] (const auto& color) {
return asColorComponents(convertColor<SRGBA<float>>(color));
});
return { sRGBColorSpaceRef(), componentsConvertedToSRGBA };
#endif
}
return { cgColorSpace, components };
}
static RetainPtr<CGColorRef> createCGColor(const Color& color)
{
auto [colorSpace, components] = color.colorSpaceAndComponents();
auto [cgColorSpace, cgCompatibleComponents] = convertToCGCompatibleComponents(colorSpace, components);
auto [c1, c2, c3, c4] = cgCompatibleComponents;
CGFloat cgFloatComponents[4] { c1, c2, c3, c4 };
return adoptCF(CGColorCreate(cgColorSpace, cgFloatComponents));
}
RetainPtr<CGColorRef> cachedCGColor(const Color& color)
{
if (auto srgb = color.tryGetAsSRGBABytes()) {
switch (PackedColor::RGBA { *srgb }.value) {
case PackedColor::RGBA { Color::transparentBlack }.value: {
static LazyNeverDestroyed<RetainPtr<CGColorRef>> transparentCGColor;
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
transparentCGColor.construct(createCGColor(Color::transparentBlack));
});
return transparentCGColor.get();
}
case PackedColor::RGBA { Color::black }.value: {
static LazyNeverDestroyed<RetainPtr<CGColorRef>> blackCGColor;
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
blackCGColor.construct(createCGColor(Color::black));
});
return blackCGColor.get();
}
case PackedColor::RGBA { Color::white }.value: {
static LazyNeverDestroyed<RetainPtr<CGColorRef>> whiteCGColor;
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
whiteCGColor.construct(createCGColor(Color::white));
});
return whiteCGColor.get();
}
}
}
static Lock cachedColorLock;
Locker locker { cachedColorLock };
static NeverDestroyed<TinyLRUCache<Color, RetainPtr<CGColorRef>, 32>> cache;
return cache.get().get(color);
}
ColorComponents<float, 4> platformConvertColorComponents(ColorSpace inputColorSpace, ColorComponents<float, 4> inputColorComponents, const DestinationColorSpace& outputColorSpace)
{
// FIXME: Investigate optimizing this to use the builtin color conversion code for supported color spaces.
auto [cgInputColorSpace, cgCompatibleComponents] = convertToCGCompatibleComponents(inputColorSpace, inputColorComponents);
if (cgInputColorSpace == outputColorSpace.platformColorSpace())
return cgCompatibleComponents;
auto [c1, c2, c3, c4] = cgCompatibleComponents;
CGFloat sourceComponents[4] { c1, c2, c3, c4 };
CGFloat destinationComponents[4] { };
auto transform = adoptCF(CGColorTransformCreate(outputColorSpace.platformColorSpace(), nullptr));
auto result = CGColorTransformConvertColorComponents(transform.get(), cgInputColorSpace, kCGRenderingIntentDefault, sourceComponents, destinationComponents);
ASSERT_UNUSED(result, result);
// FIXME: CGColorTransformConvertColorComponents doesn't copy over any alpha component.
return { static_cast<float>(destinationComponents[0]), static_cast<float>(destinationComponents[1]), static_cast<float>(destinationComponents[2]), static_cast<float>(destinationComponents[3]) };
}
}
#endif // USE(CG)