/*============================================================================== Copyright 2018 by Roland Rabien For more information visit www.rabiensoftware.com ==============================================================================*/ inline uint8 channelBlendNormal (int A, int) { return ((uint8)(A)); } inline uint8 channelBlendLighten (int A, int B) { return ((uint8)((B > A) ? B : A)); } inline uint8 channelBlendDarken (int A, int B) { return ((uint8)((B > A) ? A : B)); } inline uint8 channelBlendMultiply (int A, int B) { return ((uint8)((A * B) / 255)); } inline uint8 channelBlendAverage (int A, int B) { return ((uint8)((A + B) / 2)); } inline uint8 channelBlendAdd (int A, int B) { return ((uint8)(jmin (255, (A + B)))); } inline uint8 channelBlendSubtract (int A, int B) { return ((uint8)((A + B < 255) ? 0 : (A + B - 255))); } inline uint8 channelBlendDifference (int A, int B) { return ((uint8)(std::abs (A - B))); } inline uint8 channelBlendNegation (int A, int B) { return ((uint8)(255 - std::abs (255 - A - B))); } inline uint8 channelBlendScreen (int A, int B) { return ((uint8)(255 - (((255 - A) * (255 - B)) >> 8))); } inline uint8 channelBlendExclusion (int A, int B) { return ((uint8)(A + B - 2 * A * B / 255)); } inline uint8 channelBlendOverlay (int A, int B) { return ((uint8)((B < 128) ? (2 * A * B / 255) : (255 - 2 * (255 - A) * (255 - B) / 255))); } inline uint8 channelBlendSoftLight (int A, int B) { return ((uint8)((B < 128) ? (2 * ((A >> 1) + 64)) * ((float)B / 255) : (255 - (2 * (255 - ((A >> 1) + 64)) * (float)(255 - B) / 255)))); } inline uint8 channelBlendHardLight (int A, int B) { return (channelBlendOverlay (B,A)); } inline uint8 channelBlendColorDodge (int A, int B) { return ((uint8)((B == 255) ? B : jmin (255, ((A << 8 ) / (255 - B))))); } inline uint8 channelBlendColorBurn (int A, int B) { return ((uint8)((B == 0) ? B : jmax (0, (255 - ((255 - A) << 8 ) / B)))); } inline uint8 channelBlendLinearDodge (int A, int B) { return (channelBlendAdd (A, B)); } inline uint8 channelBlendLinearBurn (int A, int B) { return (channelBlendSubtract (A, B)); } inline uint8 channelBlendLinearLight (int A, int B) { return ((uint8)(B < 128) ? channelBlendLinearBurn (A,(2 * B)) : channelBlendLinearDodge (A, (2 * (B - 128)))); } inline uint8 channelBlendVividLight (int A, int B) { return ((uint8)(B < 128) ? channelBlendColorBurn (A,(2 * B)) : channelBlendColorDodge (A, (2 * (B - 128)))); } inline uint8 channelBlendPinLight (int A, int B) { return ((uint8)(B < 128) ? channelBlendDarken (A,(2 * B)) : channelBlendLighten (A, (2 * (B - 128)))); } inline uint8 channelBlendHardMix (int A, int B) { return ((uint8)((channelBlendVividLight (A, B) < 128) ? 0:255)); } inline uint8 channelBlendReflect (int A, int B) { return ((uint8)((B == 255) ? B : jmin (255, (A * A / (255 - B))))); } inline uint8 channelBlendGlow (int A, int B) { return (channelBlendReflect (B, A)); } inline uint8 channelBlendPhoenix (int A, int B) { return ((uint8)(jmin (A, B) - jmax (A, B) + 255)); } inline uint8 channelBlendAlpha (uint8 A, uint8 B, float O) { return ((uint8)(O * A + (1 - O) * B)); } template void applyBlend (Image& dst, const Image& src, float alpha, juce::Point position, ThreadPool* threadPool) { auto rcLower = Rectangle (0, 0, dst.getWidth(), dst.getHeight()); auto rcUpper = Rectangle (position.x, position.y, src.getWidth(), src.getHeight()); auto rcOverlap = rcLower.getIntersection (rcUpper); if (rcOverlap.isEmpty()) return; int w = rcOverlap.getWidth(); int h = rcOverlap.getHeight(); int cropX = position.x < 0 ? -position.x : 0; int cropY = position.y < 0 ? -position.y : 0; threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData srcData (src, Image::BitmapData::readOnly); Image::BitmapData dstData (dst, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* pSrc = srcData.getLinePointer (cropY + y); uint8* pDst = dstData.getLinePointer (rcOverlap.getY() + y); pSrc += srcData.pixelStride * cropX; pDst += dstData.pixelStride * rcOverlap.getX(); for (int x = 0; x < w; x++) { T* ac = (T*)pSrc; T* bc = (T*)pDst; uint8 ar = ac->getRed(); uint8 ag = ac->getGreen(); uint8 ab = ac->getBlue(); uint8 aa = ac->getAlpha(); uint8 br = bc->getRed(); uint8 bg = bc->getGreen(); uint8 bb = bc->getBlue(); uint8 ba = bc->getAlpha(); if (ba == 255) { float pixelAlpha = alpha * aa / 255.0f; br = channelBlendAlpha (F (ar, br), br, pixelAlpha); bg = channelBlendAlpha (F (ag, bg), bg, pixelAlpha); bb = channelBlendAlpha (F (ab, bb), bb, pixelAlpha); } else { float srcAlpha = alpha * aa / 255.0f; float dstAlpha = ba / 255.0f; float outAlpha = srcAlpha + dstAlpha * (1.0f - srcAlpha); if (outAlpha == 0.0) { br = 0; bg = 0; bb = 0; } else { uint8 r = F (ar, br); uint8 g = F (ag, bg); uint8 b = F (ab, bb); br = uint8 ((r * srcAlpha + br * dstAlpha * (1.0f - srcAlpha)) / outAlpha); bg = uint8 ((g * srcAlpha + bg * dstAlpha * (1.0f - srcAlpha)) / outAlpha); bb = uint8 ((b * srcAlpha + bb * dstAlpha * (1.0f - srcAlpha)) / outAlpha); } } bc->setARGB (ba, br, bg, bb); pSrc += srcData.pixelStride; pDst += dstData.pixelStride; } }); } template void applyBlend (Image& dst, const Image& src, BlendMode mode, float alpha, juce::Point position, ThreadPool* threadPool) { switch (mode) { case Normal: applyBlend (dst, src, alpha, position, threadPool); break; case Lighten: applyBlend (dst, src, alpha, position, threadPool); break; case Darken: applyBlend (dst, src, alpha, position, threadPool); break; case Multiply: applyBlend (dst, src, alpha, position, threadPool); break; case Average: applyBlend (dst, src, alpha, position, threadPool); break; case Add: applyBlend (dst, src, alpha, position, threadPool); break; case Subtract: applyBlend (dst, src, alpha, position, threadPool); break; case Difference: applyBlend (dst, src, alpha, position, threadPool); break; case Negation: applyBlend (dst, src, alpha, position, threadPool); break; case Screen: applyBlend (dst, src, alpha, position, threadPool); break; case Exclusion: applyBlend (dst, src, alpha, position, threadPool); break; case Overlay: applyBlend (dst, src, alpha, position, threadPool); break; case SoftLight: applyBlend (dst, src, alpha, position, threadPool); break; case HardLight: applyBlend (dst, src, alpha, position, threadPool); break; case ColorDodge: applyBlend (dst, src, alpha, position, threadPool); break; case ColorBurn: applyBlend (dst, src, alpha, position, threadPool); break; case LinearDodge: applyBlend (dst, src, alpha, position, threadPool); break; case LinearBurn: applyBlend (dst, src, alpha, position, threadPool); break; case LinearLight: applyBlend (dst, src, alpha, position, threadPool); break; case VividLight: applyBlend (dst, src, alpha, position, threadPool); break; case PinLight: applyBlend (dst, src, alpha, position, threadPool); break; case HardMix: applyBlend (dst, src, alpha, position, threadPool); break; case Reflect: applyBlend (dst, src, alpha, position, threadPool); break; case Glow: applyBlend (dst, src, alpha, position, threadPool); break; case Phoenix: applyBlend (dst, src, alpha, position, threadPool); break; } } void applyBlend (Image& dst, const Image& src, BlendMode mode, float alpha, juce::Point position, ThreadPool* threadPool) { if (src.getFormat() != dst.getFormat()) { Image copy = src.createCopy(); copy = copy.convertedToFormat (dst.getFormat()); if (src.getFormat() == Image::ARGB) applyBlend (dst, copy, mode, alpha, position, threadPool); else if (src.getFormat() == Image::RGB) applyBlend (dst, copy, mode, alpha, position, threadPool); else jassertfalse; } else { if (src.getFormat() == Image::ARGB) applyBlend (dst, src, mode, alpha, position, threadPool); else if (src.getFormat() == Image::RGB) applyBlend (dst, src, mode, alpha, position, threadPool); else jassertfalse; } } template void applyBlend (Image& dst, Colour c, ThreadPool* threadPool) { int w = dst.getWidth(); int h = dst.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData dstData (dst, Image::BitmapData::readWrite); uint8 ar = c.getRed(); uint8 ag = c.getGreen(); uint8 ab = c.getBlue(); uint8 aa = c.getAlpha(); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* pDst = dstData.getLinePointer (y); for (int x = 0; x < w; x++) { T* bc = (T*)pDst; uint8 br = bc->getRed(); uint8 bg = bc->getGreen(); uint8 bb = bc->getBlue(); uint8 ba = bc->getAlpha(); if (ba == 255) { float pixelAlpha = aa / 255.0f; br = channelBlendAlpha (F (ar, br), br, pixelAlpha); bg = channelBlendAlpha (F (ag, bg), bg, pixelAlpha); bb = channelBlendAlpha (F (ab, bb), bb, pixelAlpha); } else { float srcAlpha = aa / 255.0f; float dstAlpha = ba / 255.0f; float outAlpha = srcAlpha + dstAlpha * (1.0f - srcAlpha); if (outAlpha == 0.0) { br = 0; bg = 0; bb = 0; } else { uint8 r = F (ar, br); uint8 g = F (ag, bg); uint8 b = F (ab, bb); br = uint8 ((r * srcAlpha + br * dstAlpha * (1.0f - srcAlpha)) / outAlpha); bg = uint8 ((g * srcAlpha + bg * dstAlpha * (1.0f - srcAlpha)) / outAlpha); bb = uint8 ((b * srcAlpha + bb * dstAlpha * (1.0f - srcAlpha)) / outAlpha); } } bc->setARGB (ba, br, bg, bb); pDst += dstData.pixelStride; } }); } template void applyBlend (Image& dst, BlendMode mode, Colour c, ThreadPool* threadPool) { switch (mode) { case Normal: applyBlend (dst, c, threadPool); break; case Lighten: applyBlend (dst, c, threadPool); break; case Darken: applyBlend (dst, c, threadPool); break; case Multiply: applyBlend (dst, c, threadPool); break; case Average: applyBlend (dst, c, threadPool); break; case Add: applyBlend (dst, c, threadPool); break; case Subtract: applyBlend (dst, c, threadPool); break; case Difference: applyBlend (dst, c, threadPool); break; case Negation: applyBlend (dst, c, threadPool); break; case Screen: applyBlend (dst, c, threadPool); break; case Exclusion: applyBlend (dst, c, threadPool); break; case Overlay: applyBlend (dst, c, threadPool); break; case SoftLight: applyBlend (dst, c, threadPool); break; case HardLight: applyBlend (dst, c, threadPool); break; case ColorDodge: applyBlend (dst, c, threadPool); break; case ColorBurn: applyBlend (dst, c, threadPool); break; case LinearDodge: applyBlend (dst, c, threadPool); break; case LinearBurn: applyBlend (dst, c, threadPool); break; case LinearLight: applyBlend (dst, c, threadPool); break; case VividLight: applyBlend (dst, c, threadPool); break; case PinLight: applyBlend (dst, c, threadPool); break; case HardMix: applyBlend (dst, c, threadPool); break; case Reflect: applyBlend (dst, c, threadPool); break; case Glow: applyBlend (dst, c, threadPool); break; case Phoenix: applyBlend (dst, c, threadPool); break; } } void applyBlend (Image& dst, BlendMode mode, Colour c, ThreadPool* threadPool) { if (dst.getFormat() == Image::ARGB) applyBlend (dst, mode, c, threadPool); else if (dst.getFormat() == Image::RGB) applyBlend (dst, mode, c, threadPool); else jassertfalse; }