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:
parent
f0560cefbb
commit
c37c18c5b4
1 changed files with 98 additions and 85 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue