mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
ImagePixelData: Update signatures of functions providing whole-image effects
This commit is contained in:
parent
8cb8c5e572
commit
ada2c88b03
12 changed files with 587 additions and 398 deletions
|
|
@ -1,5 +1,33 @@
|
|||
# JUCE breaking changes
|
||||
|
||||
# develop
|
||||
|
||||
## Change
|
||||
|
||||
The signatures of virtual functions ImagePixelData::applyGaussianBlurEffect()
|
||||
and ImagePixelData::applySingleChannelBoxBlurEffect() have changed.
|
||||
ImageEffects::applyGaussianBlurEffect() and
|
||||
ImageEffects::applySingleChannelBoxBlurEffect() have been removed.
|
||||
|
||||
**Possible Issues**
|
||||
|
||||
User code overriding or calling these functions will fail to compile.
|
||||
|
||||
**Workaround**
|
||||
|
||||
The blur functions now operate within a specified area of the image. Update
|
||||
overriding implementations accordingly. Instead of using the ImageEffects
|
||||
static functions, call the corresponding ImagePixelData member functions
|
||||
directly.
|
||||
|
||||
**Rationale**
|
||||
|
||||
The blur functions had a 'temporary storage' parameter which was not
|
||||
particularly useful in practice, so this has been removed. Moving the
|
||||
functionality of the ImageEffects static members directly into corresponding
|
||||
member functions of ImagePixelData simplifies the public API.
|
||||
|
||||
|
||||
# Version 8.0.5
|
||||
|
||||
## Change
|
||||
|
|
|
|||
|
|
@ -48,10 +48,8 @@ void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const
|
|||
if (! srcImage.isValid())
|
||||
return;
|
||||
|
||||
Image blurred;
|
||||
ImageEffects::applySingleChannelBoxBlurEffect (radius,
|
||||
srcImage.convertedToFormat (Image::SingleChannel),
|
||||
blurred);
|
||||
auto blurred = srcImage.convertedToFormat (Image::SingleChannel);
|
||||
blurred.getPixelData()->applySingleChannelBoxBlurEffect (radius);
|
||||
|
||||
g.setColour (colour);
|
||||
g.drawImageAt (blurred, offset.x, offset.y, true);
|
||||
|
|
@ -76,11 +74,10 @@ void DropShadow::drawForPath (Graphics& g, const Path& path) const
|
|||
(float) (offset.y - area.getY())));
|
||||
}
|
||||
|
||||
Image blurred;
|
||||
ImageEffects::applySingleChannelBoxBlurEffect (radius, pathImage, blurred);
|
||||
pathImage.getPixelData()->applySingleChannelBoxBlurEffect (radius);
|
||||
|
||||
g.setColour (colour);
|
||||
g.drawImageAt (blurred, area.getX(), area.getY(), true);
|
||||
g.drawImageAt (pathImage, area.getX(), area.getY(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,10 +47,11 @@ void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point<int
|
|||
|
||||
void GlowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha)
|
||||
{
|
||||
ImageEffects::applyGaussianBlurEffect (radius * scaleFactor, image, cachedImage);
|
||||
auto blurred = image.createCopy();
|
||||
blurred.getPixelData()->applyGaussianBlurEffect (radius * scaleFactor);
|
||||
|
||||
g.setColour (colour.withMultipliedAlpha (alpha));
|
||||
g.drawImageAt (cachedImage, offset.x, offset.y, true);
|
||||
g.drawImageAt (blurred, offset.x, offset.y, true);
|
||||
|
||||
g.setOpacity (alpha);
|
||||
g.drawImageAt (image, offset.x, offset.y, false);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ private:
|
|||
float radius = 2.0f;
|
||||
Colour colour { Colours::white };
|
||||
Point<int> offset;
|
||||
Image cachedImage;
|
||||
|
||||
JUCE_LEAK_DETECTOR (GlowEffect)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -173,10 +173,10 @@ namespace BitmapDataDetail
|
|||
};
|
||||
};
|
||||
|
||||
static void convert (const Image::BitmapData& src, Image::BitmapData& dest)
|
||||
static bool convert (const Image::BitmapData& src, Image::BitmapData& dest)
|
||||
{
|
||||
jassert (src.width == dest.width);
|
||||
jassert (src.height == dest.height);
|
||||
if (std::tuple (src.width, src.height) != std::tuple (dest.width, dest.height))
|
||||
return false;
|
||||
|
||||
static constexpr auto converterFnTable = ConverterFnTable<RGB, ARGB, A>{};
|
||||
|
||||
|
|
@ -190,19 +190,76 @@ namespace BitmapDataDetail
|
|||
if (auto* converter = converterFnTable.getConverterFor (src.pixelFormat, dest.pixelFormat))
|
||||
converter (src, dest, dest.width, dest.height);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Image convert (const Image::BitmapData& src, const ImageType& type)
|
||||
{
|
||||
Image result (type.create (src.pixelFormat, src.width, src.height, false));
|
||||
|
||||
{
|
||||
Image::BitmapData dest (result, Image::BitmapData::writeOnly);
|
||||
BitmapDataDetail::convert (src, dest);
|
||||
}
|
||||
Image::BitmapData (result, Image::BitmapData::writeOnly).convertFrom (src);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void blurDataTriplets (uint8* d, int num, const int delta) noexcept
|
||||
{
|
||||
uint32 last = d[0];
|
||||
d[0] = (uint8) ((d[0] + d[delta] + 1) / 3);
|
||||
d += delta;
|
||||
|
||||
num -= 2;
|
||||
|
||||
do
|
||||
{
|
||||
const uint32 newLast = d[0];
|
||||
d[0] = (uint8) ((last + d[0] + d[delta] + 1) / 3);
|
||||
d += delta;
|
||||
last = newLast;
|
||||
}
|
||||
while (--num > 0);
|
||||
|
||||
d[0] = (uint8) ((last + d[0] + 1) / 3);
|
||||
}
|
||||
|
||||
static void blurSingleChannelImage (uint8* const data, const int w, const int h,
|
||||
const int lineStride, const int repetitions) noexcept
|
||||
{
|
||||
jassert (w > 2 && h > 2);
|
||||
|
||||
for (int y = 0; y < h; ++y)
|
||||
for (int i = repetitions; --i >= 0;)
|
||||
blurDataTriplets (data + lineStride * y, w, 1);
|
||||
|
||||
for (int x = 0; x < w; ++x)
|
||||
for (int i = repetitions; --i >= 0;)
|
||||
blurDataTriplets (data + x, h, lineStride);
|
||||
}
|
||||
|
||||
template <class PixelType>
|
||||
struct PixelIterator
|
||||
{
|
||||
template <class PixelOperation>
|
||||
static void iterate (const Image::BitmapData& data, const PixelOperation& pixelOp)
|
||||
{
|
||||
for (int y = 0; y < data.height; ++y)
|
||||
for (int x = 0; x < data.width; ++x)
|
||||
pixelOp (*reinterpret_cast<PixelType*> (data.getPixelPointer (x, y)));
|
||||
}
|
||||
};
|
||||
|
||||
template <class PixelOperation>
|
||||
static void performPixelOp (const Image::BitmapData& data, const PixelOperation& pixelOp)
|
||||
{
|
||||
switch (data.pixelFormat)
|
||||
{
|
||||
case Image::ARGB: PixelIterator<PixelARGB> ::iterate (data, pixelOp); break;
|
||||
case Image::RGB: PixelIterator<PixelRGB> ::iterate (data, pixelOp); break;
|
||||
case Image::SingleChannel: PixelIterator<PixelAlpha>::iterate (data, pixelOp); break;
|
||||
case Image::UnknownFormat:
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubsectionPixelData : public ImagePixelData
|
||||
|
|
@ -250,10 +307,35 @@ public:
|
|||
|
||||
std::unique_ptr<ImageType> createType() const override { return sourceImage->createType(); }
|
||||
|
||||
void applySingleChannelBoxBlurEffectInArea (Rectangle<int> b, int radius) override
|
||||
{
|
||||
sourceImage->applySingleChannelBoxBlurEffectInArea (getIntersection (b), radius);
|
||||
}
|
||||
|
||||
void applyGaussianBlurEffectInArea (Rectangle<int> b, float radius) override
|
||||
{
|
||||
sourceImage->applyGaussianBlurEffectInArea (getIntersection (b), radius);
|
||||
}
|
||||
|
||||
void multiplyAllAlphasInArea (Rectangle<int> b, float amount) override
|
||||
{
|
||||
sourceImage->multiplyAllAlphasInArea (getIntersection (b), amount);
|
||||
}
|
||||
|
||||
void desaturateInArea (Rectangle<int> b) override
|
||||
{
|
||||
sourceImage->desaturateInArea (getIntersection (b));
|
||||
}
|
||||
|
||||
/* as we always hold a reference to image, don't double count */
|
||||
int getSharedCount() const noexcept override { return getReferenceCount() + sourceImage->getSharedCount() - 1; }
|
||||
|
||||
private:
|
||||
Rectangle<int> getIntersection (Rectangle<int> b) const
|
||||
{
|
||||
return area.getIntersection (b + area.getTopLeft());
|
||||
}
|
||||
|
||||
friend class Image;
|
||||
const ImagePixelData::Ptr sourceImage;
|
||||
const Rectangle<int> area;
|
||||
|
|
@ -284,14 +366,40 @@ int ImagePixelData::getSharedCount() const noexcept
|
|||
return getReferenceCount();
|
||||
}
|
||||
|
||||
void ImagePixelData::applyGaussianBlurEffect ([[maybe_unused]] float radius, Image& result)
|
||||
void ImagePixelData::applySingleChannelBoxBlurEffectInArea (Rectangle<int> bounds, int radius)
|
||||
{
|
||||
result = {};
|
||||
if (pixelFormat == Image::SingleChannel)
|
||||
{
|
||||
const Image::BitmapData bm (Image { this }, bounds, Image::BitmapData::readWrite);
|
||||
BitmapDataDetail::blurSingleChannelImage (bm.data, bm.width, bm.height, bm.lineStride, 2 * radius);
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePixelData::applySingleChannelBoxBlurEffect ([[maybe_unused]] int radius, juce::Image &result)
|
||||
void ImagePixelData::applyGaussianBlurEffectInArea (Rectangle<int> bounds, float radius)
|
||||
{
|
||||
result = {};
|
||||
ImageConvolutionKernel blurKernel (roundToInt (radius * 2.0f));
|
||||
blurKernel.createGaussianBlur (radius);
|
||||
|
||||
Image target { this };
|
||||
blurKernel.applyToImage (target, Image { this }.createCopy(), bounds);
|
||||
}
|
||||
|
||||
void ImagePixelData::multiplyAllAlphasInArea (Rectangle<int> b, float amount)
|
||||
{
|
||||
if (pixelFormat == Image::ARGB || pixelFormat == Image::SingleChannel)
|
||||
{
|
||||
const Image::BitmapData destData (Image { this }, b, Image::BitmapData::readWrite);
|
||||
BitmapDataDetail::performPixelOp (destData, [&] (auto& p) { p.multiplyAlpha (amount); });
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePixelData::desaturateInArea (Rectangle<int> b)
|
||||
{
|
||||
if (pixelFormat == Image::ARGB || pixelFormat == Image::RGB)
|
||||
{
|
||||
const Image::BitmapData destData (Image { this }, b, Image::BitmapData::readWrite);
|
||||
BitmapDataDetail::performPixelOp (destData, [] (auto& p) { p.desaturate(); });
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -634,6 +742,11 @@ void Image::BitmapData::setPixelColour (int x, int y, Colour colour) const noexc
|
|||
}
|
||||
}
|
||||
|
||||
bool Image::BitmapData::convertFrom (const BitmapData& source)
|
||||
{
|
||||
return BitmapDataDetail::convert (source, *this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Image::clear (const Rectangle<int>& area, Colour colourToClearTo)
|
||||
{
|
||||
|
|
@ -680,46 +793,16 @@ void Image::multiplyAlphaAt (int x, int y, float multiplier)
|
|||
}
|
||||
}
|
||||
|
||||
template <class PixelType>
|
||||
struct PixelIterator
|
||||
{
|
||||
template <class PixelOperation>
|
||||
static void iterate (const Image::BitmapData& data, const PixelOperation& pixelOp)
|
||||
{
|
||||
for (int y = 0; y < data.height; ++y)
|
||||
for (int x = 0; x < data.width; ++x)
|
||||
pixelOp (*reinterpret_cast<PixelType*> (data.getPixelPointer (x, y)));
|
||||
}
|
||||
};
|
||||
|
||||
template <class PixelOperation>
|
||||
static void performPixelOp (const Image::BitmapData& data, const PixelOperation& pixelOp)
|
||||
{
|
||||
switch (data.pixelFormat)
|
||||
{
|
||||
case Image::ARGB: PixelIterator<PixelARGB> ::iterate (data, pixelOp); break;
|
||||
case Image::RGB: PixelIterator<PixelRGB> ::iterate (data, pixelOp); break;
|
||||
case Image::SingleChannel: PixelIterator<PixelAlpha>::iterate (data, pixelOp); break;
|
||||
case Image::UnknownFormat:
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
void Image::multiplyAllAlphas (float amountToMultiplyBy)
|
||||
{
|
||||
jassert (hasAlphaChannel());
|
||||
|
||||
const BitmapData destData (*this, 0, 0, getWidth(), getHeight(), BitmapData::readWrite);
|
||||
performPixelOp (destData, [&] (auto& p) { p.multiplyAlpha (amountToMultiplyBy); });
|
||||
if (auto ptr = image)
|
||||
ptr->multiplyAllAlphas (amountToMultiplyBy);
|
||||
}
|
||||
|
||||
void Image::desaturate()
|
||||
{
|
||||
if (isARGB() || isRGB())
|
||||
{
|
||||
const BitmapData destData (*this, 0, 0, getWidth(), getHeight(), BitmapData::readWrite);
|
||||
performPixelOp (destData, [] (auto& p) { p.desaturate(); });
|
||||
}
|
||||
if (auto ptr = image)
|
||||
ptr->desaturate();
|
||||
}
|
||||
|
||||
void Image::createSolidAreaMask (RectangleList<int>& result, float alphaThreshold) const
|
||||
|
|
@ -842,111 +925,6 @@ void Image::moveImageSection (int dx, int dy,
|
|||
}
|
||||
}
|
||||
|
||||
void ImageEffects::applyGaussianBlurEffect (float radius, const Image& input, Image& result)
|
||||
{
|
||||
auto image = input.getPixelData();
|
||||
|
||||
if (image == nullptr)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
|
||||
auto copy = result;
|
||||
image->applyGaussianBlurEffect (radius, copy);
|
||||
|
||||
if (copy.isValid())
|
||||
{
|
||||
result = std::move (copy);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto tie = [] (const auto& x) { return std::tuple (x.getFormat(), x.getWidth(), x.getHeight()); };
|
||||
|
||||
if (tie (input) != tie (result))
|
||||
result = Image { input.getFormat(), input.getWidth(), input.getHeight(), false };
|
||||
|
||||
ImageConvolutionKernel blurKernel (roundToInt (radius * 2.0f));
|
||||
|
||||
blurKernel.createGaussianBlur (radius);
|
||||
|
||||
blurKernel.applyToImage (result, input, result.getBounds());
|
||||
}
|
||||
|
||||
static void blurDataTriplets (uint8* d, int num, const int delta) noexcept
|
||||
{
|
||||
uint32 last = d[0];
|
||||
d[0] = (uint8) ((d[0] + d[delta] + 1) / 3);
|
||||
d += delta;
|
||||
|
||||
num -= 2;
|
||||
|
||||
do
|
||||
{
|
||||
const uint32 newLast = d[0];
|
||||
d[0] = (uint8) ((last + d[0] + d[delta] + 1) / 3);
|
||||
d += delta;
|
||||
last = newLast;
|
||||
}
|
||||
while (--num > 0);
|
||||
|
||||
d[0] = (uint8) ((last + d[0] + 1) / 3);
|
||||
}
|
||||
|
||||
static void blurSingleChannelImage (uint8* const data, const int width, const int height,
|
||||
const int lineStride, const int repetitions) noexcept
|
||||
{
|
||||
jassert (width > 2 && height > 2);
|
||||
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int i = repetitions; --i >= 0;)
|
||||
blurDataTriplets (data + lineStride * y, width, 1);
|
||||
|
||||
for (int x = 0; x < width; ++x)
|
||||
for (int i = repetitions; --i >= 0;)
|
||||
blurDataTriplets (data + x, height, lineStride);
|
||||
}
|
||||
|
||||
static void blurSingleChannelImage (Image& image, int radius)
|
||||
{
|
||||
const Image::BitmapData bm (image, Image::BitmapData::readWrite);
|
||||
blurSingleChannelImage (bm.data, bm.width, bm.height, bm.lineStride, 2 * radius);
|
||||
}
|
||||
|
||||
void ImageEffects::applySingleChannelBoxBlurEffect (int radius, const Image& input, Image& result)
|
||||
{
|
||||
auto image = input.getPixelData();
|
||||
|
||||
if (image == nullptr)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
|
||||
auto copy = result;
|
||||
image->applySingleChannelBoxBlurEffect (radius, copy);
|
||||
|
||||
if (copy.isValid())
|
||||
{
|
||||
result = std::move (copy);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto inputConfig = std::tuple (Image::SingleChannel, input.getWidth(), input.getHeight());
|
||||
const auto outputConfig = std::tuple (result.getFormat(), result.getWidth(), result.getHeight());
|
||||
|
||||
if (inputConfig != outputConfig)
|
||||
result = Image { Image::SingleChannel, input.getWidth(), input.getHeight(), false };
|
||||
|
||||
{
|
||||
Image::BitmapData source { input, Image::BitmapData::readOnly };
|
||||
Image::BitmapData dest { result, Image::BitmapData::writeOnly };
|
||||
BitmapDataDetail::convert (source, dest);
|
||||
}
|
||||
|
||||
blurSingleChannelImage (result, radius);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES
|
||||
|
||||
|
|
|
|||
|
|
@ -357,6 +357,15 @@ public:
|
|||
/** Returns the size of the bitmap. */
|
||||
Rectangle<int> getBounds() const noexcept { return Rectangle<int> (width, height); }
|
||||
|
||||
/** Attempts to copy the contents of src into this bitmap data.
|
||||
Returns true on success, and false otherwise.
|
||||
|
||||
The source BitmapData must be readable, and the destination (current) BitmapData must
|
||||
be writeable. This function cannot check for this precondition, so you must ensure this
|
||||
yourself!
|
||||
*/
|
||||
bool convertFrom (const Image::BitmapData& src);
|
||||
|
||||
uint8* data; /**< The raw pixel data, packed according to the image's pixel format. */
|
||||
size_t size; /**< The number of valid/allocated bytes after data. May be smaller than "lineStride * height" if this is a section of a larger image. */
|
||||
PixelFormat pixelFormat; /**< The format of the data. */
|
||||
|
|
@ -477,26 +486,58 @@ public:
|
|||
This blur applies to all channels of the input image. It may be more expensive to
|
||||
calculate than a box blur, but should produce higher-quality results.
|
||||
|
||||
Implementations should attempt to re-use the storage provided in the result out-parameter
|
||||
when possible.
|
||||
|
||||
If native blurs are unsupported, or if creating a blur fails for any other reason,
|
||||
the result out-parameter will be reset to an invalid image.
|
||||
The default implementation will modify the image pixel-by-pixel on the CPU, which will be slow.
|
||||
Native image types may provide optimised implementations.
|
||||
*/
|
||||
virtual void applyGaussianBlurEffect (float radius, Image& result);
|
||||
virtual void applyGaussianBlurEffectInArea (Rectangle<int> bounds, float radius);
|
||||
|
||||
/** @see applyGaussianBlurEffectInArea() */
|
||||
void applyGaussianBlurEffect (float radius)
|
||||
{
|
||||
applyGaussianBlurEffectInArea ({ width, height }, radius);
|
||||
}
|
||||
|
||||
/** Applies a native blur effect to this image, if available.
|
||||
This is intended for blurring single-channel images, which is useful when rendering drop
|
||||
shadows. This is implemented as several box-blurs in series. The results should be visually
|
||||
similar to a Gaussian blur, but less accurate.
|
||||
|
||||
Implementations should attempt to re-use the storage provided in the result out-parameter
|
||||
when possible.
|
||||
|
||||
If native blurs are unsupported, or if creating a blur fails for any other reason,
|
||||
the result out-parameter will be reset to an invalid image.
|
||||
The default implementation will modify the image pixel-by-pixel on the CPU, which will be slow.
|
||||
Native image types may provide optimised implementations.
|
||||
*/
|
||||
virtual void applySingleChannelBoxBlurEffect (int radius, Image& result);
|
||||
virtual void applySingleChannelBoxBlurEffectInArea (Rectangle<int> bounds, int radius);
|
||||
|
||||
/** @see applySingleChannelBoxBlurEffectInArea() */
|
||||
void applySingleChannelBoxBlurEffect (int radius)
|
||||
{
|
||||
applySingleChannelBoxBlurEffectInArea ({ width, height }, radius);
|
||||
}
|
||||
|
||||
/** Multiples all alpha-channel values in the image by the specified amount.
|
||||
|
||||
The default implementation will modify the image pixel-by-pixel on the CPU, which will be slow.
|
||||
Native image types may provide optimised implementations.
|
||||
*/
|
||||
virtual void multiplyAllAlphasInArea (Rectangle<int> bounds, float amount);
|
||||
|
||||
/** @see multiplyAllAlphasInArea() */
|
||||
void multiplyAllAlphas (float amount)
|
||||
{
|
||||
multiplyAllAlphasInArea ({ width, height }, amount);
|
||||
}
|
||||
|
||||
/** Changes all the colours to be shades of grey, based on their current luminosity.
|
||||
|
||||
The default implementation will modify the image pixel-by-pixel on the CPU, which will be slow.
|
||||
Native image types may provide optimised implementations.
|
||||
*/
|
||||
virtual void desaturateInArea (Rectangle<int> bounds);
|
||||
|
||||
/** @see desaturateInArea() */
|
||||
void desaturate()
|
||||
{
|
||||
desaturateInArea ({ width, height });
|
||||
}
|
||||
|
||||
/** The pixel format of the image data. */
|
||||
const Image::PixelFormat pixelFormat;
|
||||
|
|
@ -588,43 +629,4 @@ public:
|
|||
int getTypeID() const override;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Utility functions for applying effects to images. These effects may or may not
|
||||
be hardware-accelerated.
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
struct ImageEffects
|
||||
{
|
||||
ImageEffects() = delete;
|
||||
|
||||
/** Applies a blur to this image, placing the blurred image in the result out-parameter.
|
||||
This will attempt to call the applyGaussianBlurEffect() member of the input image's
|
||||
underlying ImagePixelData, which will use hardware acceleration if available. If this
|
||||
fails, then a software blur will be applied instead.
|
||||
|
||||
This blur applies to all channels of the input image. It may be more expensive to
|
||||
calculate than a box blur, but should produce higher-quality results.
|
||||
|
||||
If result is already the correct size, then its storage will be reused directly.
|
||||
Otherwise, new storage may be allocated for the blurred image.
|
||||
*/
|
||||
static void applyGaussianBlurEffect (float radius, const Image& input, Image& result);
|
||||
|
||||
/** Applies a blur to this image, placing the blurred image in the result out-parameter.
|
||||
This will attempt to call the applySingleChannelBoxBlurEffect() member of the input image's
|
||||
underlying ImagePixelData, which will use hardware acceleration if available. If this
|
||||
fails, then a software blur will be applied instead.
|
||||
|
||||
This kind of blur is only capable of blurring single-channel images, which is useful when
|
||||
rendering drop shadows. The blur is implemented as several box-blurs in series. The results
|
||||
should be visually similar to a Gaussian blur, but less accurate.
|
||||
|
||||
If result is already the correct size, then its storage will be reused directly.
|
||||
Otherwise, new storage may be allocated for the blurred image.
|
||||
*/
|
||||
static void applySingleChannelBoxBlurEffect (int radius, const Image& input, Image& result);
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
//==============================================================================
|
||||
#if JUCE_MAC
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#include <CoreImage/CIRenderDestination.h>
|
||||
#include <CoreImage/CoreImage.h>
|
||||
#include <CoreText/CTFont.h>
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ public:
|
|||
auto colourSpace = detail::ColorSpacePtr { CGColorSpaceCreateWithName ((format == Image::SingleChannel) ? kCGColorSpaceGenericGrayGamma2_2
|
||||
: kCGColorSpaceSRGB) };
|
||||
|
||||
context = detail::ContextPtr { CGBitmapContextCreate (imageData->data, (size_t) width, (size_t) height, 8, (size_t) lineStride,
|
||||
colourSpace.get(), getCGImageFlags (format)) };
|
||||
context.reset (CGBitmapContextCreate (imageData->data, (size_t) width, (size_t) height, 8, (size_t) lineStride,
|
||||
colourSpace.get(), getCGImageFlags (format)));
|
||||
}
|
||||
|
||||
~CoreGraphicsPixelData() override
|
||||
|
|
@ -101,6 +101,46 @@ public:
|
|||
|
||||
std::unique_ptr<ImageType> createType() const override { return std::make_unique<NativeImageType>(); }
|
||||
|
||||
void applyGaussianBlurEffectInArea (Rectangle<int> area, float radius) override
|
||||
{
|
||||
const auto buildFilter = [radius]
|
||||
{
|
||||
return [CIFilter filterWithName: @"CIGaussianBlur"
|
||||
withInputParameters: @{ kCIInputRadiusKey: [NSNumber numberWithFloat: radius] }];
|
||||
};
|
||||
applyFilterInArea (area, buildFilter);
|
||||
}
|
||||
|
||||
void applySingleChannelBoxBlurEffectInArea (Rectangle<int> area, int radius) override
|
||||
{
|
||||
const auto buildFilter = [radius]
|
||||
{
|
||||
return [CIFilter filterWithName: @"CIBoxBlur"
|
||||
withInputParameters: @{ kCIInputRadiusKey: [NSNumber numberWithFloat: (float) radius] }];
|
||||
};
|
||||
applyFilterInArea (area, buildFilter);
|
||||
}
|
||||
|
||||
void multiplyAllAlphasInArea (Rectangle<int> area, float amount) override
|
||||
{
|
||||
const auto buildFilter = [amount]
|
||||
{
|
||||
return [CIFilter filterWithName: @"CIColorMatrix"
|
||||
withInputParameters: @{ @"inputAVector": [CIVector vectorWithX: 0 Y: 0 Z: 0 W: amount] }];
|
||||
};
|
||||
applyFilterInArea (area, buildFilter);
|
||||
}
|
||||
|
||||
void desaturateInArea (Rectangle<int> area) override
|
||||
{
|
||||
const auto buildFilter = []
|
||||
{
|
||||
return [CIFilter filterWithName: @"CIColorControls"
|
||||
withInputParameters: @{ kCIInputSaturationKey: [NSNumber numberWithFloat: 0] }];
|
||||
};
|
||||
applyFilterInArea (area, buildFilter);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace)
|
||||
{
|
||||
|
|
@ -148,6 +188,7 @@ public:
|
|||
//==============================================================================
|
||||
detail::ContextPtr context;
|
||||
detail::ImagePtr cachedImageRef;
|
||||
NSUniquePtr<CIContext> ciContext;
|
||||
|
||||
struct ImageDataContainer final : public ReferenceCountedObject
|
||||
{
|
||||
|
|
@ -161,6 +202,49 @@ public:
|
|||
int pixelStride, lineStride;
|
||||
|
||||
private:
|
||||
template <typename BuildFilter>
|
||||
bool applyFilterInArea (Rectangle<int> area, BuildFilter&& buildFilter)
|
||||
{
|
||||
// This function might be called on the OpenGL rendering thread, or some other background
|
||||
// thread that doesn't necessarily have an autorelease pool in scope.
|
||||
// Note that buildFilter is called within this pool, to ensure that the filter is released
|
||||
// upon leaving the pool's scope.
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
auto* filter = buildFilter();
|
||||
|
||||
if (filter == nullptr || context == nullptr)
|
||||
return false;
|
||||
|
||||
const ImagePtr content { CGBitmapContextCreateImage (context.get()) };
|
||||
|
||||
if (content == nullptr)
|
||||
return false;
|
||||
|
||||
const auto cgArea = makeCGRect (area);
|
||||
auto* ciImage = [[CIImage imageWithCGImage: content.get()] imageByCroppingToRect: cgArea];
|
||||
|
||||
if (ciImage == nullptr)
|
||||
return false;
|
||||
|
||||
if (ciContext == nullptr)
|
||||
ciContext.reset ([[CIContext contextWithCGContext: context.get() options: nullptr] retain]);
|
||||
|
||||
if (ciContext == nullptr)
|
||||
return false;
|
||||
|
||||
[filter setValue: ciImage forKey: kCIInputImageKey];
|
||||
auto* output = [filter outputImage];
|
||||
|
||||
if (output == nullptr)
|
||||
return false;
|
||||
|
||||
CGContextClearRect (context.get(), cgArea);
|
||||
[ciContext.get() drawImage: output inRect: cgArea fromRect: cgArea];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void freeCachedImageRef()
|
||||
{
|
||||
cachedImageRef.reset();
|
||||
|
|
|
|||
|
|
@ -94,6 +94,11 @@ Direct2DImageContext::Direct2DImageContext (ComSmartPtr<ID2D1DeviceContext1> con
|
|||
|
||||
Direct2DImageContext::~Direct2DImageContext() = default;
|
||||
|
||||
ComSmartPtr<ID2D1DeviceContext1> Direct2DImageContext::getDeviceContext() const
|
||||
{
|
||||
return getPimpl()->getDeviceContext();
|
||||
}
|
||||
|
||||
Direct2DGraphicsContext::Pimpl* Direct2DImageContext::getPimpl() const noexcept
|
||||
{
|
||||
return pimpl.get();
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ public:
|
|||
|
||||
~Direct2DImageContext() override;
|
||||
|
||||
ComSmartPtr<ID2D1DeviceContext1> getDeviceContext() const;
|
||||
|
||||
private:
|
||||
struct ImagePimpl;
|
||||
std::unique_ptr<ImagePimpl> pimpl;
|
||||
|
|
|
|||
|
|
@ -155,6 +155,15 @@ static bool readFromDirect2DBitmap (ComSmartPtr<ID2D1DeviceContext1> context,
|
|||
return {};
|
||||
}
|
||||
|
||||
// Unimplemented, should never be called
|
||||
void applyGaussianBlurEffectInArea (Rectangle<int>, float) override { jassertfalse; }
|
||||
// Unimplemented, should never be called
|
||||
void applySingleChannelBoxBlurEffectInArea (Rectangle<int>, int) override { jassertfalse; }
|
||||
// Unimplemented, should never be called
|
||||
void multiplyAllAlphasInArea (Rectangle<int>, float) override { jassertfalse; }
|
||||
// Unimplemented, should never be called
|
||||
void desaturateInArea (Rectangle<int>) override { jassertfalse; }
|
||||
|
||||
void initialiseBitmapData (Image::BitmapData& bd, int x, int y, Image::BitmapData::ReadWriteMode mode) override
|
||||
{
|
||||
if (mode != Image::BitmapData::readOnly)
|
||||
|
|
@ -292,6 +301,44 @@ Direct2DPixelData::~Direct2DPixelData()
|
|||
directX->adapters.removeListener (*this);
|
||||
}
|
||||
|
||||
bool Direct2DPixelData::createPersistentBackup (ComSmartPtr<ID2D1Device1> deviceHint)
|
||||
{
|
||||
if (state == State::drawing)
|
||||
{
|
||||
// Creating a backup while the image is being modified would leave the backup in an invalid state
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto iter = deviceHint != nullptr
|
||||
? pagesForDevice.find (deviceHint)
|
||||
: std::find_if (pagesForDevice.begin(),
|
||||
pagesForDevice.end(),
|
||||
[] (const auto& pair) { return pair.second.isUpToDate(); });
|
||||
|
||||
if (iter == pagesForDevice.end())
|
||||
{
|
||||
// There's no up-to-date image in graphics memory, so the graphics device probably got
|
||||
// removed, dropping our image data. The image data is irrevocably lost!
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& [device, pages] = *iter;
|
||||
const auto context = Direct2DDeviceContext::create (device);
|
||||
|
||||
if (context == nullptr)
|
||||
{
|
||||
// Unable to create a device context to read the image data
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = readFromDirect2DBitmap (context, pages.getPages().front().bitmap, backingData);
|
||||
state = State::drawn;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto Direct2DPixelData::getIteratorForDevice (ComSmartPtr<ID2D1Device1> device)
|
||||
{
|
||||
if (device == nullptr)
|
||||
|
|
@ -341,6 +388,77 @@ auto Direct2DPixelData::getIteratorForDevice (ComSmartPtr<ID2D1Device1> device)
|
|||
return pair.first;
|
||||
}
|
||||
|
||||
struct Direct2DPixelData::Context : public Direct2DImageContext
|
||||
{
|
||||
Context (Ptr selfIn,
|
||||
ComSmartPtr<ID2D1DeviceContext1> context,
|
||||
ComSmartPtr<ID2D1Bitmap1> target)
|
||||
: Direct2DImageContext (context, target, D2DUtilities::rectFromSize (target->GetPixelSize())),
|
||||
self (selfIn),
|
||||
frameStarted (startFrame (1.0f))
|
||||
{
|
||||
if (frameStarted)
|
||||
self->state = State::drawing;
|
||||
}
|
||||
|
||||
~Context() override
|
||||
{
|
||||
if (! frameStarted)
|
||||
return;
|
||||
|
||||
endFrame();
|
||||
|
||||
self->createPersistentBackup (D2DUtilities::getDeviceForContext (getDeviceContext()));
|
||||
}
|
||||
|
||||
Ptr self;
|
||||
bool frameStarted = false;
|
||||
};
|
||||
|
||||
auto Direct2DPixelData::createNativeContext() -> std::unique_ptr<Context>
|
||||
{
|
||||
if (state == State::drawing)
|
||||
return nullptr;
|
||||
|
||||
sendDataChangeMessage();
|
||||
|
||||
const auto adapter = directX->adapters.getDefaultAdapter();
|
||||
|
||||
if (adapter == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto device = adapter->direct2DDevice;
|
||||
|
||||
if (device == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto context = Direct2DDeviceContext::create (device);
|
||||
|
||||
if (context == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto maxSize = (int) context->GetMaximumBitmapSize();
|
||||
|
||||
if (maxSize < width || maxSize < height)
|
||||
return nullptr;
|
||||
|
||||
const auto iter = getIteratorForDevice (device);
|
||||
jassert (iter != pagesForDevice.end());
|
||||
|
||||
const auto pages = iter->second.getPages();
|
||||
|
||||
if (pages.empty() || pages.front().bitmap == nullptr)
|
||||
return nullptr;
|
||||
|
||||
// Every page *other than the page we're about to render onto* will need to be updated from the
|
||||
// software image before it is next read.
|
||||
for (auto i = pagesForDevice.begin(); i != pagesForDevice.end(); ++i)
|
||||
if (i != iter)
|
||||
i->second.markOutdated();
|
||||
|
||||
return std::make_unique<Context> (this, context, pages.front().bitmap);
|
||||
}
|
||||
|
||||
std::unique_ptr<LowLevelGraphicsContext> Direct2DPixelData::createLowLevelContext()
|
||||
{
|
||||
if (state == State::drawing)
|
||||
|
|
@ -390,86 +508,17 @@ std::unique_ptr<LowLevelGraphicsContext> Direct2DPixelData::createLowLevelContex
|
|||
return std::make_unique<InertContext>();
|
||||
}
|
||||
|
||||
sendDataChangeMessage();
|
||||
if (auto ptr = createNativeContext())
|
||||
return ptr;
|
||||
|
||||
const auto invalidateAllAndReturnSoftwareContext = [this]
|
||||
{
|
||||
// If this is hit, something has gone wrong when trying to create a Direct2D renderer,
|
||||
// and we're about to fall back to a software renderer instead.
|
||||
jassertfalse;
|
||||
// If this is hit, something has gone wrong when trying to create a Direct2D renderer,
|
||||
// and we're about to fall back to a software renderer instead.
|
||||
jassertfalse;
|
||||
|
||||
for (auto& pair : pagesForDevice)
|
||||
pair.second.markOutdated();
|
||||
for (auto& pair : pagesForDevice)
|
||||
pair.second.markOutdated();
|
||||
|
||||
return backingData->createLowLevelContext();
|
||||
};
|
||||
|
||||
const auto adapter = directX->adapters.getDefaultAdapter();
|
||||
|
||||
if (adapter == nullptr)
|
||||
return invalidateAllAndReturnSoftwareContext();
|
||||
|
||||
const auto device = adapter->direct2DDevice;
|
||||
|
||||
if (device == nullptr)
|
||||
return invalidateAllAndReturnSoftwareContext();
|
||||
|
||||
const auto context = Direct2DDeviceContext::create (device);
|
||||
|
||||
if (context == nullptr)
|
||||
return invalidateAllAndReturnSoftwareContext();
|
||||
|
||||
const auto maxSize = (int) context->GetMaximumBitmapSize();
|
||||
|
||||
if (maxSize < width || maxSize < height)
|
||||
return invalidateAllAndReturnSoftwareContext();
|
||||
|
||||
const auto iter = getIteratorForDevice (device);
|
||||
jassert (iter != pagesForDevice.end());
|
||||
|
||||
const auto pages = iter->second.getPages();
|
||||
|
||||
if (pages.empty() || pages.front().bitmap == nullptr)
|
||||
return invalidateAllAndReturnSoftwareContext();
|
||||
|
||||
// Every page *other than the page we're about to render onto* will need to be updated from the
|
||||
// software image before it is next read.
|
||||
for (auto i = pagesForDevice.begin(); i != pagesForDevice.end(); ++i)
|
||||
if (i != iter)
|
||||
i->second.markOutdated();
|
||||
|
||||
struct FlushingContext : public Direct2DImageContext
|
||||
{
|
||||
FlushingContext (Ptr selfIn,
|
||||
ComSmartPtr<ID2D1DeviceContext1> context,
|
||||
ComSmartPtr<ID2D1Bitmap1> target)
|
||||
: Direct2DImageContext (context, target, D2DUtilities::rectFromSize (target->GetPixelSize())),
|
||||
storedContext (context),
|
||||
storedTarget (target),
|
||||
self (selfIn),
|
||||
backup (startFrame (1.0f) ? selfIn->backingData : nullptr)
|
||||
{
|
||||
if (backup != nullptr)
|
||||
self->state = State::drawing;
|
||||
}
|
||||
|
||||
~FlushingContext() override
|
||||
{
|
||||
if (backup == nullptr)
|
||||
return;
|
||||
|
||||
endFrame();
|
||||
readFromDirect2DBitmap (storedContext, storedTarget, backup);
|
||||
self->state = State::drawn;
|
||||
}
|
||||
|
||||
ComSmartPtr<ID2D1DeviceContext1> storedContext;
|
||||
ComSmartPtr<ID2D1Bitmap1> storedTarget;
|
||||
Ptr self;
|
||||
ImagePixelData::Ptr backup;
|
||||
};
|
||||
|
||||
return std::make_unique<FlushingContext> (this, context, pages.front().bitmap);
|
||||
return backingData->createLowLevelContext();
|
||||
}
|
||||
|
||||
void Direct2DPixelData::initialiseBitmapData (Image::BitmapData& bitmap,
|
||||
|
|
@ -513,138 +562,151 @@ void Direct2DPixelData::initialiseBitmapData (Image::BitmapData& bitmap,
|
|||
bitmap.dataReleaser = std::make_unique<Releaser> (std::move (bitmap.dataReleaser), this);
|
||||
}
|
||||
|
||||
void Direct2DPixelData::applyGaussianBlurEffect (float radius, Image& result)
|
||||
template <typename Fn>
|
||||
bool Direct2DPixelData::applyEffectInArea (Rectangle<int> area, Fn&& configureEffect)
|
||||
{
|
||||
// The result must be a separate image!
|
||||
jassert (result.getPixelData().get() != this);
|
||||
const auto internalGraphicsContext = createNativeContext();
|
||||
|
||||
const auto adapter = directX->adapters.getDefaultAdapter();
|
||||
|
||||
if (adapter == nullptr)
|
||||
if (internalGraphicsContext == nullptr)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
// Something when wrong while trying to create a device context with this image as a target
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto device = adapter->direct2DDevice;
|
||||
const auto context = internalGraphicsContext->getDeviceContext();
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
if (context == nullptr)
|
||||
return false;
|
||||
|
||||
const auto context = Direct2DDeviceContext::create (device);
|
||||
const auto maxSize = (int) context->GetMaximumBitmapSize();
|
||||
ComSmartPtr<ID2D1Image> target;
|
||||
context->GetTarget (target.resetAndGetPointerAddress());
|
||||
|
||||
if (context == nullptr || maxSize < width || maxSize < height)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
if (target == nullptr)
|
||||
return false;
|
||||
|
||||
ComSmartPtr<ID2D1Effect> effect;
|
||||
if (const auto hr = context->CreateEffect (CLSID_D2D1GaussianBlur, effect.resetAndGetPointerAddress());
|
||||
FAILED (hr) || effect == nullptr)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
const auto size = D2D1::SizeU ((UINT32) area.getWidth(), (UINT32) area.getHeight());
|
||||
|
||||
effect->SetInput (0, getFirstPageForDevice (device));
|
||||
effect->SetValue (D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, radius / 3.0f);
|
||||
ComSmartPtr<ID2D1Bitmap> copy;
|
||||
context->CreateBitmap (size,
|
||||
D2D1::BitmapProperties (context->GetPixelFormat()),
|
||||
copy.resetAndGetPointerAddress());
|
||||
|
||||
const auto outputPixelData = Direct2DBitmap::createBitmap (context,
|
||||
Image::ARGB,
|
||||
D2D1::SizeU ((UINT32) width, (UINT32) height),
|
||||
D2D1_BITMAP_OPTIONS_TARGET);
|
||||
if (copy == nullptr)
|
||||
return false;
|
||||
|
||||
context->SetTarget (outputPixelData);
|
||||
context->BeginDraw();
|
||||
context->Clear();
|
||||
context->DrawImage (effect);
|
||||
context->EndDraw();
|
||||
const auto rect = D2DUtilities::toRECT_U (area);
|
||||
copy->CopyFromRenderTarget (nullptr, context, &rect);
|
||||
|
||||
result = Image { new Direct2DPixelData { device, outputPixelData } };
|
||||
const auto effect = configureEffect (context, copy);
|
||||
|
||||
if (effect == nullptr)
|
||||
return false;
|
||||
|
||||
const auto destPoint = D2D1::Point2F ((float) area.getX(), (float) area.getY());
|
||||
|
||||
context->PushAxisAlignedClip (D2DUtilities::toRECT_F (area), D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
context->DrawImage (effect,
|
||||
&destPoint,
|
||||
nullptr,
|
||||
D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
|
||||
D2D1_COMPOSITE_MODE_SOURCE_COPY);
|
||||
context->PopAxisAlignedClip();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Direct2DPixelData::applySingleChannelBoxBlurEffect (int radius, Image& result)
|
||||
void Direct2DPixelData::applyGaussianBlurEffectInArea (Rectangle<int> b, float radius)
|
||||
{
|
||||
// The result must be a separate image!
|
||||
jassert (result.getPixelData().get() != this);
|
||||
|
||||
const auto adapter = directX->adapters.getDefaultAdapter();
|
||||
|
||||
if (adapter == nullptr)
|
||||
applyEffectInArea (b, [&] (auto dc, auto input) -> ComSmartPtr<ID2D1Effect>
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
|
||||
const auto device = adapter->direct2DDevice;
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
const auto context = Direct2DDeviceContext::create (device);
|
||||
const auto maxSize = (int) context->GetMaximumBitmapSize();
|
||||
|
||||
if (context == nullptr || maxSize < width || maxSize < height)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr FLOAT kernel[] { 1.0f / 9.0f, 2.0f / 9.0f, 3.0f / 9.0f, 2.0f / 9.0f, 1.0f / 9.0f };
|
||||
|
||||
ComSmartPtr<ID2D1Effect> begin, end;
|
||||
|
||||
for (auto horizontal : { false, true })
|
||||
{
|
||||
for (auto i = 0; i < radius; ++i)
|
||||
ComSmartPtr<ID2D1Effect> effect;
|
||||
if (const auto hr = dc->CreateEffect (CLSID_D2D1GaussianBlur, effect.resetAndGetPointerAddress());
|
||||
FAILED (hr) || effect == nullptr)
|
||||
{
|
||||
ComSmartPtr<ID2D1Effect> effect;
|
||||
if (const auto hr = context->CreateEffect (CLSID_D2D1ConvolveMatrix, effect.resetAndGetPointerAddress());
|
||||
FAILED (hr) || effect == nullptr)
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
effect->SetValue (D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_X, (UINT32) (horizontal ? std::size (kernel) : 1));
|
||||
effect->SetValue (D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_Y, (UINT32) (horizontal ? 1 : std::size (kernel)));
|
||||
effect->SetValue (D2D1_CONVOLVEMATRIX_PROP_KERNEL_MATRIX, kernel);
|
||||
effect->SetInput (0, input);
|
||||
effect->SetValue (D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, radius / 3.0f);
|
||||
return effect;
|
||||
});
|
||||
}
|
||||
|
||||
if (begin == nullptr)
|
||||
void Direct2DPixelData::applySingleChannelBoxBlurEffectInArea (Rectangle<int> b, int radius)
|
||||
{
|
||||
applyEffectInArea (b, [&] (auto dc, auto input) -> ComSmartPtr<ID2D1Effect>
|
||||
{
|
||||
constexpr FLOAT kernel[] { 1.0f / 9.0f, 2.0f / 9.0f, 3.0f / 9.0f, 2.0f / 9.0f, 1.0f / 9.0f };
|
||||
|
||||
ComSmartPtr<ID2D1Effect> begin, end;
|
||||
|
||||
for (auto horizontal : { false, true })
|
||||
{
|
||||
for (auto i = 0; i < roundToInt (radius); ++i)
|
||||
{
|
||||
begin = effect;
|
||||
end = effect;
|
||||
}
|
||||
else
|
||||
{
|
||||
effect->SetInputEffect (0, end);
|
||||
end = effect;
|
||||
ComSmartPtr<ID2D1Effect> effect;
|
||||
if (const auto hr = dc->CreateEffect (CLSID_D2D1ConvolveMatrix, effect.resetAndGetPointerAddress());
|
||||
FAILED (hr) || effect == nullptr)
|
||||
{
|
||||
// Unable to create effect!
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
effect->SetValue (D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_X, (UINT32) (horizontal ? std::size (kernel) : 1));
|
||||
effect->SetValue (D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_Y, (UINT32) (horizontal ? 1 : std::size (kernel)));
|
||||
effect->SetValue (D2D1_CONVOLVEMATRIX_PROP_KERNEL_MATRIX, kernel);
|
||||
|
||||
if (begin == nullptr)
|
||||
{
|
||||
begin = effect;
|
||||
end = effect;
|
||||
}
|
||||
else
|
||||
{
|
||||
effect->SetInputEffect (0, end);
|
||||
end = effect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (begin == nullptr)
|
||||
begin->SetInput (0, input);
|
||||
return end;
|
||||
});
|
||||
}
|
||||
|
||||
void Direct2DPixelData::multiplyAllAlphasInArea (Rectangle<int> b, float value)
|
||||
{
|
||||
applyEffectInArea (b, [&] (auto dc, auto input) -> ComSmartPtr<ID2D1Effect>
|
||||
{
|
||||
result = {};
|
||||
return;
|
||||
}
|
||||
ComSmartPtr<ID2D1Effect> effect;
|
||||
if (const auto hr = dc->CreateEffect (CLSID_D2D1Opacity, effect.resetAndGetPointerAddress());
|
||||
FAILED (hr) || effect == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
begin->SetInput (0, getFirstPageForDevice (device));
|
||||
effect->SetInput (0, input);
|
||||
effect->SetValue (D2D1_OPACITY_PROP_OPACITY, value);
|
||||
return effect;
|
||||
});
|
||||
}
|
||||
|
||||
const auto outputPixelData = Direct2DBitmap::createBitmap (context,
|
||||
Image::ARGB,
|
||||
D2D1::SizeU ((UINT32) width, (UINT32) height),
|
||||
D2D1_BITMAP_OPTIONS_TARGET);
|
||||
void Direct2DPixelData::desaturateInArea (Rectangle<int> b)
|
||||
{
|
||||
applyEffectInArea (b, [&] (auto dc, auto input) -> ComSmartPtr<ID2D1Effect>
|
||||
{
|
||||
ComSmartPtr<ID2D1Effect> effect;
|
||||
if (const auto hr = dc->CreateEffect (CLSID_D2D1Saturation, effect.resetAndGetPointerAddress());
|
||||
FAILED (hr) || effect == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
context->SetTarget (outputPixelData);
|
||||
context->BeginDraw();
|
||||
context->Clear();
|
||||
context->DrawImage (end);
|
||||
context->EndDraw();
|
||||
|
||||
result = Image { new Direct2DPixelData { device, outputPixelData } };
|
||||
effect->SetInput (0, input);
|
||||
effect->SetValue (D2D1_SATURATION_PROP_SATURATION, 0.0f);
|
||||
return effect;
|
||||
});
|
||||
}
|
||||
|
||||
auto Direct2DPixelData::getPagesForDevice (ComSmartPtr<ID2D1Device1> device) -> Span<const Page>
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ public:
|
|||
upToDate = false;
|
||||
}
|
||||
|
||||
bool isUpToDate() const
|
||||
{
|
||||
return upToDate;
|
||||
}
|
||||
|
||||
private:
|
||||
ImagePixelData::Ptr backingData;
|
||||
std::vector<Direct2DPixelDataPage> pages;
|
||||
|
|
@ -170,8 +175,10 @@ public:
|
|||
*/
|
||||
void initialiseBitmapData (Image::BitmapData&, int, int, Image::BitmapData::ReadWriteMode) override;
|
||||
|
||||
void applyGaussianBlurEffect (float radius, Image& result) override;
|
||||
void applySingleChannelBoxBlurEffect (int radius, Image& result) override;
|
||||
void applyGaussianBlurEffectInArea (Rectangle<int>, float) override;
|
||||
void applySingleChannelBoxBlurEffectInArea (Rectangle<int>, int) override;
|
||||
void multiplyAllAlphasInArea (Rectangle<int>, float) override;
|
||||
void desaturateInArea (Rectangle<int>) override;
|
||||
|
||||
/* This returns image data that is suitable for use when drawing with the provided context.
|
||||
This image data should be treated as a read-only view - making modifications directly
|
||||
|
|
@ -201,6 +208,30 @@ private:
|
|||
Direct2DPixelData (ImagePixelData::Ptr, State);
|
||||
auto getIteratorForDevice (ComSmartPtr<ID2D1Device1>);
|
||||
|
||||
/* Attempts to copy the content of the corresponding texture in graphics storage into
|
||||
persistent software storage.
|
||||
The argument specifies the device holding the texture that should be backed up.
|
||||
Passing null will instead search through all devices to find which device has the most
|
||||
recent copy of the image data.
|
||||
|
||||
In most cases it is unnecessary to call this function directly.
|
||||
|
||||
Returns true on success, i.e. the backup is already up-to-date or the backup was updated
|
||||
successfully.
|
||||
|
||||
Returns false on failure. The backup process may fail if the graphics storage became
|
||||
unavailable for some reason, such as an external GPU being disconnected, or a remote desktop
|
||||
session ending. If this happens, the image content is *irrevocably lost* and will need to
|
||||
be recreated.
|
||||
*/
|
||||
bool createPersistentBackup (ComSmartPtr<ID2D1Device1> deviceHint);
|
||||
|
||||
struct Context;
|
||||
std::unique_ptr<Context> createNativeContext();
|
||||
|
||||
template <typename Fn>
|
||||
bool applyEffectInArea (Rectangle<int>, Fn&&);
|
||||
|
||||
void adapterCreated (DxgiAdapter::Ptr) override {}
|
||||
void adapterRemoved (DxgiAdapter::Ptr adapter) override
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue