1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

macOS: Prevent visual glitches in images

CGImages created from a juce Image may be drawn after or during
changes to the underlying data. This change copies the required
data into a new buffer to ensure the CGImage data is always
independent from the juce image data.
This commit is contained in:
Anthony Nicholls 2025-10-30 16:21:06 +00:00 committed by Anthony Nicholls
parent f0560cefbb
commit c37c18c5b4

View file

@ -45,25 +45,35 @@ public:
using Ptr = ReferenceCountedObjectPtr<CoreGraphicsPixelData>; using Ptr = ReferenceCountedObjectPtr<CoreGraphicsPixelData>;
CoreGraphicsPixelData (const Image::PixelFormat format, int w, int h, bool clearImage) CoreGraphicsPixelData (const Image::PixelFormat format, int w, int h, bool clearImage)
: ImagePixelData (format, w, h) : ImagePixelData (format, w, h),
pixelStride (format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1)),
lineStride ((pixelStride * jmax (1, width) + 3) & ~3),
// SDK version 10.14+ intermittently requires a bit of extra space
// at the end of the image data so we add an extra line stride. This
// feels like something has gone wrong in Apple's code.
data ((size_t) lineStride * (size_t) (jmax (1, height) + 1), clearImage)
{ {
pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1); const auto colourSpaceName = (format == Image::SingleChannel) ? kCGColorSpaceGenericGrayGamma2_2
lineStride = (pixelStride * jmax (1, width) + 3) & ~3; : kCGColorSpaceSRGB;
auto numComponents = (size_t) lineStride * (size_t) jmax (1, height); const detail::ColorSpacePtr colourSpace { CGColorSpaceCreateWithName (colourSpaceName) };
// SDK version 10.14+ intermittently requires a bit of extra space context.reset (CGBitmapContextCreate (data.getData(),
// at the end of the image data. This feels like something has gone (size_t) width,
// wrong in Apple's code. (size_t) height,
numComponents += (size_t) lineStride; 8,
(size_t) lineStride,
colourSpace.get(),
getCGImageFlags (format)));
}
imageData->data.allocate (numComponents, clearImage); CoreGraphicsPixelData (const CoreGraphicsPixelData& other)
: CoreGraphicsPixelData (other.pixelFormat, other.width, other.height, false)
auto colourSpace = detail::ColorSpacePtr { CGColorSpaceCreateWithName ((format == Image::SingleChannel) ? kCGColorSpaceGenericGrayGamma2_2 // Don't try to recreate the CIContext here. It's expensive to do so,
: kCGColorSpaceSRGB) }; // therefore it's best to leave it null and recreate it lazily.
{
context.reset (CGBitmapContextCreate (imageData->data, (size_t) width, (size_t) height, 8, (size_t) lineStride, jassert (data.getSize() == other.data.getSize());
colourSpace.get(), getCGImageFlags (format))); data.copyFrom (other.data.getData(), 0, other.data.getSize());
} }
~CoreGraphicsPixelData() override ~CoreGraphicsPixelData() override
@ -78,10 +88,13 @@ public:
return std::make_unique<CoreGraphicsContext> (context.get(), height); return std::make_unique<CoreGraphicsContext> (context.get(), height);
} }
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override void initialiseBitmapData (Image::BitmapData& bitmap,
int x,
int y,
Image::BitmapData::ReadWriteMode mode) override
{ {
const auto offset = (size_t) (x * pixelStride + y * lineStride); const auto offset = (size_t) (x * pixelStride + y * lineStride);
bitmap.data = imageData->data + offset; bitmap.data = (uint8*) data.getData() + offset;
bitmap.size = (size_t) (lineStride * height) - offset; bitmap.size = (size_t) (lineStride * height) - offset;
bitmap.pixelFormat = pixelFormat; bitmap.pixelFormat = pixelFormat;
bitmap.lineStride = lineStride; bitmap.lineStride = lineStride;
@ -96,12 +109,14 @@ public:
ImagePixelData::Ptr clone() override ImagePixelData::Ptr clone() override
{ {
auto im = new CoreGraphicsPixelData (pixelFormat, width, height, false); auto im = new CoreGraphicsPixelData (*this);
memcpy (im->imageData->data, imageData->data, (size_t) (lineStride * height));
return *im; return *im;
} }
std::unique_ptr<ImageType> createType() const override { return std::make_unique<NativeImageType>(); } std::unique_ptr<ImageType> createType() const override
{
return std::make_unique<NativeImageType>();
}
void applyGaussianBlurEffectInArea (Rectangle<int> area, float radius) override void applyGaussianBlurEffectInArea (Rectangle<int> area, float radius) override
{ {
@ -114,7 +129,8 @@ public:
} }
//============================================================================== //==============================================================================
static CFUniquePtr<CGImageRef> getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace) static CFUniquePtr<CGImageRef> getCachedImageRef (const Image& juceImage,
CGColorSpaceRef colourSpace)
{ {
auto cgim = std::invoke ([&]() -> CFUniquePtr<CGImageRef> auto cgim = std::invoke ([&]() -> CFUniquePtr<CGImageRef>
{ {
@ -129,62 +145,36 @@ public:
const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly); const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly);
const auto usableSize = jmin ((size_t) srcData.lineStride * (size_t) srcData.height, srcData.size); return createCGImage (srcData.data,
CFUniquePtr<CFDataRef> data (CFDataCreate (nullptr, (const UInt8*) srcData.data, (CFIndex) usableSize)); srcData.size,
detail::DataProviderPtr provider { CGDataProviderCreateWithCFData (data.get()) }; (size_t) srcData.width,
(size_t) srcData.height,
return CFUniquePtr<CGImageRef> { CGImageCreate ((size_t) srcData.width, (size_t) srcData.pixelStride,
(size_t) srcData.height, (size_t) srcData.lineStride,
8, colourSpace,
(size_t) srcData.pixelStride * 8, getCGImageFlags (juceImage.getFormat()));
(size_t) srcData.lineStride,
colourSpace,
getCGImageFlags (juceImage.getFormat()),
provider.get(),
nullptr,
true,
kCGRenderingIntentDefault) };
} }
//============================================================================== //==============================================================================
detail::ContextPtr context;
detail::ImagePtr cachedImageRef;
NSUniquePtr<CIContext> ciContext;
struct ImageDataContainer final : public ReferenceCountedObject
{
ImageDataContainer() = default;
using Ptr = ReferenceCountedObjectPtr<ImageDataContainer>;
HeapBlock<uint8> data;
};
CFUniquePtr<CGImageRef> getCGImage (CGColorSpaceRef colourSpace) CFUniquePtr<CGImageRef> getCGImage (CGColorSpaceRef colourSpace)
{ {
if (cachedImageRef != nullptr) if (cachedImageRef == nullptr)
return CFUniquePtr<CGImageRef> { CGImageRetain (cachedImageRef.get()) }; {
cachedImageRef = createCGImage (data.getData(),
const Image::BitmapData srcData { Image { this }, Image::BitmapData::readOnly }; data.getSize(),
(size_t) width,
detail::DataProviderPtr provider { CGDataProviderCreateWithData (new ImageDataContainer::Ptr (imageData), (size_t) height,
srcData.data, (size_t) pixelStride,
srcData.size, (size_t) lineStride,
[] (void * __nullable info, const void*, size_t) { delete (ImageDataContainer::Ptr*) info; }) }; colourSpace,
CGBitmapContextGetBitmapInfo (context.get()));
cachedImageRef.reset (CGImageCreate ((size_t) srcData.width, }
(size_t) srcData.height,
8,
(size_t) srcData.pixelStride * 8,
(size_t) srcData.lineStride,
colourSpace, getCGImageFlags (pixelFormat), provider.get(),
nullptr, true, kCGRenderingIntentDefault));
return CFUniquePtr<CGImageRef> { CGImageRetain (cachedImageRef.get()) }; return CFUniquePtr<CGImageRef> { CGImageRetain (cachedImageRef.get()) };
} }
ImageDataContainer::Ptr imageData = new ImageDataContainer();
int pixelStride, lineStride;
NativeExtensions getNativeExtensions() override NativeExtensions getNativeExtensions() override
{ {
struct Wrapped struct Wrapped
@ -213,6 +203,31 @@ public:
} }
private: private:
static CFUniquePtr<CGImageRef> createCGImage (void* data,
size_t size,
size_t imageWidth,
size_t imageHeight,
size_t imagePixelStride,
size_t imageLineStride,
CGColorSpaceRef imageColourSpace,
CGBitmapInfo bitmapInfo)
{
CFUniquePtr<CFDataRef> cfData (CFDataCreate (nullptr, (const UInt8*) data, (CFIndex) size));
detail::DataProviderPtr provider { CGDataProviderCreateWithCFData (cfData.get()) };
return CFUniquePtr<CGImageRef> { CGImageCreate (imageWidth,
imageHeight,
8,
imagePixelStride * 8,
imageLineStride,
imageColourSpace,
bitmapInfo,
provider.get(),
nullptr,
true,
kCGRenderingIntentDefault) };
}
template <typename BuildFilter> template <typename BuildFilter>
bool applyFilterInArea (Rectangle<int> area, BuildFilter&& buildFilter) bool applyFilterInArea (Rectangle<int> area, BuildFilter&& buildFilter)
{ {
@ -263,14 +278,21 @@ private:
static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
{ {
#if JUCE_BIG_ENDIAN if (format != Image::ARGB)
return format == Image::ARGB ? ((uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault; return kCGImageByteOrderDefault;
#else
return format == Image::ARGB ? ((uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault; return (uint32_t) kCGImageAlphaPremultipliedFirst
#endif | (uint32_t) kCGBitmapByteOrder32Host;
} }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsPixelData) int pixelStride;
int lineStride;
detail::ContextPtr context;
MemoryBlock data;
detail::ImagePtr cachedImageRef;
NSUniquePtr<CIContext> ciContext;
JUCE_LEAK_DETECTOR (CoreGraphicsPixelData)
}; };
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
@ -1108,7 +1130,10 @@ Image juce_loadWithCoreImage (InputStream& input)
auto provider = detail::DataProviderPtr { CGDataProviderCreateWithData (new MemoryBlockHolder::Ptr (memBlockHolder), auto provider = detail::DataProviderPtr { CGDataProviderCreateWithData (new MemoryBlockHolder::Ptr (memBlockHolder),
memBlockHolder->block.getData(), memBlockHolder->block.getData(),
memBlockHolder->block.getSize(), memBlockHolder->block.getSize(),
[] (void * __nullable info, const void*, size_t) { delete (MemoryBlockHolder::Ptr*) info; }) }; [] (void * __nullable info, const void*, size_t)
{
delete (MemoryBlockHolder::Ptr*) info;
}) };
if (auto imageSource = CFUniquePtr<CGImageSourceRef> (CGImageSourceCreateWithDataProvider (provider.get(), nullptr))) if (auto imageSource = CFUniquePtr<CGImageSourceRef> (CGImageSourceCreateWithDataProvider (provider.get(), nullptr)))
{ {
@ -1160,18 +1185,6 @@ Image juce_loadWithCoreImage (InputStream& input)
} }
#endif #endif
Image juce_createImageFromCIImage (CIImage*, int, int);
Image juce_createImageFromCIImage (CIImage* im, int w, int h)
{
auto cgImage = new CoreGraphicsPixelData (Image::ARGB, w, h, false);
CIContext* cic = [CIContext contextWithCGContext: cgImage->context.get() options: nil];
[cic drawImage: im inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)];
CGContextFlush (cgImage->context.get());
return Image (*cgImage);
}
CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace) CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace)
{ {
return CoreGraphicsPixelData::getCachedImageRef (juceImage, colourSpace).release(); return CoreGraphicsPixelData::getCachedImageRef (juceImage, colourSpace).release();