/*============================================================================== Copyright 2018 by Roland Rabien For more information visit www.rabiensoftware.com ==============================================================================*/ #if JUCE_INTEL #define USE_SSE 1 #endif template inline uint8 toByte (T v) { if (v < 0) return 0; if (v > 255) return 255; return uint8 (v); } inline uint8 getIntensity (uint8 r, uint8 g, uint8 b) { return (uint8)((7471 * b + 38470 * g + 19595 * r) >> 16); } inline uint8 computeAlpha (uint8 la, uint8 ra) { return (uint8)(((la * (256 - (ra + (ra >> 7)))) >> 8) + ra); } template inline T blend (const T& c1, const T& c2) { int a = c1.getAlpha(); int invA = 255 - a; int r = ((c2.getRed() * invA) + (c1.getRed() * a)) / 256; int g = ((c2.getGreen() * invA) + (c1.getGreen() * a)) / 256; int b = ((c2.getBlue() * invA) + (c1.getBlue() * a)) / 256; uint8 a2 = computeAlpha (c2.getAlpha(), c1.getAlpha()); T res; res.setARGB (a2, toByte (r), toByte (g), toByte (b)); return res; } template inline T2 convert (const T1& in) { T2 out; out.setARGB (in.getAlpha(), in.getRed(), in.getGreen(), in.getBlue()); return out; } //============================================================================== template void applyVignette (Image& img, float amountIn, float radiusIn, float fallOff, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; double outA = w * 0.5 * radiusIn; double outB = h * 0.5 * radiusIn; double inA = outA * fallOff; double inB = outB * fallOff; double cx = w * 0.5; double cy = h * 0.5; double amount = 1.0 - amountIn; Image::BitmapData data (img, Image::BitmapData::readWrite); Ellipse outE { outA, outB }; Ellipse inE { inA, inB }; multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); double dy = y - cy; for (int x = 0; x < w; x++) { double dx = x - cx; bool outside = outE.isPointOutside ({dx, dy}); bool inside = inE.isPointInside ({dx, dy}); T* s = (T*)p; if (outside) { uint8 r = toByte (0.5 + (s->getRed() * amount)); uint8 g = toByte (0.5 + (s->getGreen() * amount)); uint8 b = toByte (0.5 + (s->getBlue() * amount)); uint8 a = s->getAlpha(); s->setARGB (a, r, g, b); } else if (! inside) { double angle = std::atan2 (dy, dx); auto p1 = outE.pointAtAngle (angle); auto p2 = inE.pointAtAngle (angle); auto l1 = Line ({dx,dy}, p2); auto l2 = Line (p1, p2); double factor = 1.0 - (amountIn * jlimit (0.0, 1.0, l1.getLength() / l2.getLength())); uint8 r = toByte (0.5 + (s->getRed() * factor)); uint8 g = toByte (0.5 + (s->getGreen() * factor)); uint8 b = toByte (0.5 + (s->getBlue() * factor)); uint8 a = s->getAlpha(); s->setARGB (a, r, g, b); } p += data.pixelStride; } }); } void applyVignette (Image& img, float amountIn, float radiusIn, float fallOff, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyVignette (img, amountIn, radiusIn, fallOff, threadPool); else if (img.getFormat() == Image::RGB) applyVignette (img, amountIn, radiusIn, fallOff, threadPool); else jassertfalse; } template void applySepia (Image& img, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { PixelARGB* s = (PixelARGB*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); uint8 ro = toByte ((r * .393) + (g *.769) + (b * .189)); uint8 go = toByte ((r * .349) + (g *.686) + (b * .168)); uint8 bo = toByte ((r * .272) + (g *.534) + (b * .131)); s->setARGB (a, ro, go, bo); p += data.pixelStride; } }); } void applySepia (Image& img, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applySepia (img, threadPool); else if (img.getFormat() == Image::RGB) applySepia (img, threadPool); else jassertfalse; } template void applyGreyScale (Image& img, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); uint8 ro = toByte (r * 0.30 + 0.5); uint8 go = toByte (g * 0.59 + 0.5); uint8 bo = toByte (b * 0.11 + 0.5); s->setARGB (a, toByte (ro + go + bo), toByte (ro + go + bo), toByte (ro + go + bo)); p += data.pixelStride; } }); } void applyGreyScale (Image& img, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyGreyScale (img, threadPool); else if (img.getFormat() == Image::RGB) applyGreyScale (img, threadPool); else jassertfalse; } template void applySoften (Image& img, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image dst (img.getFormat(), w, h, true); Image::BitmapData srcData (img, Image::BitmapData::readOnly); Image::BitmapData dstData (dst, Image::BitmapData::writeOnly); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { for (int x = 0; x < w; x++) { int ro = 0, go = 0, bo = 0; uint8 a = 0; for (int m = -1; m <= 1; m++) { for (int n = -1; n <= 1; n++) { int cx = jlimit (0, w - 1, x + m); int cy = jlimit (0, h - 1, y + n); T* s = (T*) srcData.getPixelPointer (cx, cy); ro += s->getRed(); go += s->getGreen(); bo += s->getBlue(); } } T* s = (T*) srcData.getPixelPointer (x, y); a = s->getAlpha(); T* d = (T*) dstData.getPixelPointer (x, y); d->setARGB (a, toByte (ro / 9), toByte (go / 9), toByte (bo / 9)); } }); img = dst; } void applySoften (Image& img, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applySoften (img, threadPool); else if (img.getFormat() == Image::RGB) applySoften (img, threadPool); else jassertfalse; } template void applySharpen (Image& img, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image dst (img.getFormat(), w, h, true); Image::BitmapData srcData (img, Image::BitmapData::readOnly); Image::BitmapData dstData (dst, Image::BitmapData::writeOnly); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { for (int x = 0; x < w; x++) { auto getPixelPointer = [&] (int cx, int cy) -> T* { cx = jlimit (0, w - 1, cx); cy = jlimit (0, h - 1, cy); return (T*) srcData.getPixelPointer (cx, cy); }; int ro = 0, go = 0, bo = 0; uint8 ao = 0; T* s = getPixelPointer (x, y); ro = s->getRed() * 5; go = s->getGreen() * 5; bo = s->getBlue() * 5; ao = s->getAlpha(); s = getPixelPointer (x, y - 1); ro -= s->getRed(); go -= s->getGreen(); bo -= s->getBlue(); s = getPixelPointer (x - 1, y); ro -= s->getRed(); go -= s->getGreen(); bo -= s->getBlue(); s = getPixelPointer (x + 1, y); ro -= s->getRed(); go -= s->getGreen(); bo -= s->getBlue(); s = getPixelPointer (x, y + 1); ro -= s->getRed(); go -= s->getGreen(); bo -= s->getBlue(); T* d = (T*) dstData.getPixelPointer (x, y); d->setARGB (ao, toByte (ro), toByte (go), toByte (bo)); } }); img = dst; } void applySharpen (Image& img, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applySharpen (img, threadPool); else if (img.getFormat() == Image::RGB) applySharpen (img, threadPool); else jassertfalse; } template void applyGamma (Image& img, float gamma, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); uint8 ro = toByte (std::pow (r / 255.0, gamma) * 255.0 + 0.5); uint8 go = toByte (std::pow (g / 255.0, gamma) * 255.0 + 0.5); uint8 bo = toByte (std::pow (b / 255.0, gamma) * 255.0 + 0.5); s->setARGB (a, ro, go, bo); p += data.pixelStride; } }); } void applyGamma (Image& img, float gamma, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyGamma (img, gamma, threadPool); else if (img.getFormat() == Image::RGB) applyGamma (img, gamma, threadPool); else jassertfalse; } template void applyInvert (Image& img, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); uint8 ro = 255 - r; uint8 go = 255 - g; uint8 bo = 255 - b; s->setARGB (a, ro, go, bo); p += data.pixelStride; } }); } void applyInvert (Image& img, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyInvert (img, threadPool); else if (img.getFormat() == Image::RGB) applyInvert (img, threadPool); else jassertfalse; } template void applyContrast (Image& img, float contrast, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; contrast = (100.0f + contrast) / 100.0f; contrast = square (contrast); Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); double ro = (double) r / 255.0; ro = ro - 0.5; ro = ro * contrast; ro = ro + 0.5; ro = ro * 255.0; double go = (double) g / 255.0; go = go - 0.5; go = go * contrast; go = go + 0.5; go = go * 255.0; double bo = (double) b / 255.0; bo = bo - 0.5; bo = bo * contrast; bo = bo + 0.5; bo = bo * 255.0; ro = toByte (ro); go = toByte (go); bo = toByte (bo); s->setARGB (a, toByte (ro), toByte (go), toByte (bo)); p += data.pixelStride; } }); } void applyContrast (Image& img, float contrast, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyContrast (img, contrast, threadPool); else if (img.getFormat() == Image::RGB) applyContrast (img, contrast, threadPool); else jassertfalse; } template void applyBrightnessContrast (Image& img, float brightness, float contrast, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData data (img, Image::BitmapData::readWrite); double multiply = 1; double divide = 1; if (contrast < 0) { multiply = contrast + 100; divide = 100; } else if (contrast > 0) { multiply = 100; divide = 100 - contrast; } else { multiply = 1; divide = 1; } uint8* rgbTable = new uint8[65536]; if (divide == 0) { for (int intensity = 0; intensity < 256; intensity++) { if (intensity + brightness < 128) rgbTable[intensity] = 0; else rgbTable[intensity] = 255; } } else if (divide == 100) { for (int intensity = 0; intensity < 256; intensity++) { int shift = int ((intensity - 127) * multiply / divide + 127 - intensity + brightness); for (int col = 0; col < 256; col++) { int index = (intensity * 256) + col; rgbTable[index] = toByte (col + shift); } } } else { for (int intensity = 0; intensity < 256; intensity++) { int shift = int ((intensity - 127 + brightness) * multiply / divide + 127 - intensity); for (int col = 0; col < 256; col++) { int index = (intensity * 256) + col; rgbTable[index] = toByte (col + shift); } } } multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); if (divide == 0) { int i = getIntensity (toByte (r), toByte (g), toByte (b)); uint8 c = rgbTable[i]; s->setARGB (a, c, c, c); } else { int i = getIntensity (toByte (r), toByte (g), toByte (b)); int shiftIndex = i * 256; uint8 ro = rgbTable[shiftIndex + r]; uint8 go = rgbTable[shiftIndex + g]; uint8 bo = rgbTable[shiftIndex + b]; ro = toByte (ro); go = toByte (go); bo = toByte (bo); s->setARGB (a, ro, go, bo); } p += data.pixelStride; } }); delete[] rgbTable; } void applyBrightnessContrast (Image& img, float brightness, float contrast, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyBrightnessContrast (img, brightness, contrast, threadPool); else if (img.getFormat() == Image::RGB) applyBrightnessContrast (img, brightness, contrast, threadPool); else jassertfalse; } template void applyHueSaturationLightness (Image& img, float hueIn, float saturation, float lightness, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; if (saturation > 100) saturation = ((saturation - 100) * 3) + 100; saturation = (saturation * 1024) / 100; hueIn /= 360.0f; Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); int intensity = getIntensity (toByte (r), toByte (g), toByte (b)); int ro = toByte (int (intensity * 1024 + (r - intensity) * saturation) >> 10); int go = toByte (int (intensity * 1024 + (g - intensity) * saturation) >> 10); int bo = toByte (int (intensity * 1024 + (b - intensity) * saturation) >> 10); Colour c (toByte (ro), toByte (go), toByte (bo)); float hue = c.getHue(); hue += hueIn; while (hue < 0.0f) hue += 1.0f; while (hue >= 1.0f) hue -= 1.0f; c = Colour::fromHSV (hue, c.getSaturation(), c.getBrightness(), float (a)); ro = c.getRed(); go = c.getGreen(); bo = c.getBlue(); ro = toByte (ro); go = toByte (go); bo = toByte (bo); s->setARGB (a, toByte (ro), toByte (go), toByte (bo)); if (lightness > 0) { auto blended = blend (PixelARGB (toByte ((lightness * 255) / 100 * (a / 255.0)), 255, 255, 255), convert (*s)); *s = convert (blended); } else if (lightness < 0) { auto blended = blend (PixelARGB (toByte ((-lightness * 255) / 100 * (a / 255.0)), 0, 0, 0), convert (*s)); *s = convert (blended); } p += data.pixelStride; } }); } void applyHueSaturationLightness (Image& img, float hue, float saturation, float lightness, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyHueSaturationLightness (img, hue, saturation, lightness, threadPool); else if (img.getFormat() == Image::RGB) applyHueSaturationLightness (img, hue, saturation, lightness, threadPool); else jassertfalse; } Image applyResize (const Image& src, int width, int height) { Image dst (src.getFormat(), width, height, true); Image::BitmapData srcData (src, Image::BitmapData::readOnly); Image::BitmapData dstData (dst, Image::BitmapData::readWrite); int channels = 0; if (src.getFormat() == Image::ARGB) channels = 4; else if (src.getFormat() == Image::RGB) channels = 3; else if (src.getFormat() == Image::SingleChannel) channels = 1; else return {}; // JUCE images may have padding at the end of each scan line. // Avir expects the image data to be packed. So we need to // pack and unpack the image data before and after resizing. HeapBlock srcPacked (src.getWidth() * src.getHeight() * channels); HeapBlock dstPacked (dst.getWidth() * dst.getHeight() * channels); uint8* rawSrc = srcPacked.getData(); uint8* rawDst = dstPacked.getData(); for (int y = 0; y < src.getHeight(); y++) memcpy (rawSrc + y * src.getWidth() * channels, srcData.getLinePointer (y), (size_t) (src.getWidth() * channels)); #if USE_SSE avir::CImageResizer imageResizer (8); imageResizer.resizeImage (rawSrc, src.getWidth(), src.getHeight(), 0, rawDst, dst.getWidth(), dst.getHeight(), channels, 0); #else avir::CImageResizer<> imageResizer (8); imageResizer.resizeImage (rawSrc, src.getWidth(), src.getHeight(), 0, rawDst, dst.getWidth(), dst.getHeight(), channels, 0); #endif for (int y = 0; y < dst.getHeight(); y++) memcpy (dstData.getLinePointer (y), rawDst + y * dst.getWidth() * channels, (size_t) (dst.getWidth() * channels)); return dst; } Image applyResize (const Image& src, float factor) { return applyResize (src, roundToInt (factor * src.getWidth()), roundToInt (factor * src.getHeight())); } template void applyGradientMap (Image& img, const ColourGradient& gradient, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; uint8 r = s->getRed(); uint8 g = s->getGreen(); uint8 b = s->getBlue(); uint8 a = s->getAlpha(); uint8 ro = toByte (r * 0.30 + 0.5); uint8 go = toByte (g * 0.59 + 0.5); uint8 bo = toByte (b * 0.11 + 0.5); float proportion = float (ro + go + bo) / 256.0f; auto c = gradient.getColourAtPosition (proportion); s->setARGB (a, c.getRed(), c.getGreen(), c.getBlue()); p += data.pixelStride; } }); } void applyGradientMap (Image& img, const ColourGradient& gradient, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyGradientMap (img, gradient, threadPool); else if (img.getFormat() == Image::RGB) applyGradientMap (img, gradient, threadPool); else jassertfalse; } void applyGradientMap (Image& img, const Colour c1, const Colour c2, ThreadPool* threadPool) { ColourGradient g; g.addColour (0.0, c1); g.addColour (1.0, c2); applyGradientMap (img, g, threadPool); } template void applyColour (Image& img, Colour c, ThreadPool* threadPool) { const int w = img.getWidth(); const int h = img.getHeight(); threadPool = (w >= 256 || h >= 256) ? threadPool : nullptr; uint8 r = c.getRed(); uint8 g = c.getGreen(); uint8 b = c.getBlue(); uint8 a = c.getAlpha(); Image::BitmapData data (img, Image::BitmapData::readWrite); multiThreadedFor (0, h, 1, threadPool, [&] (int y) { uint8* p = data.getLinePointer (y); for (int x = 0; x < w; x++) { T* s = (T*)p; s->setARGB (a, r, g, b); p += data.pixelStride; } }); } void applyColour (Image& img, Colour c, ThreadPool* threadPool) { if (img.getFormat() == Image::ARGB) applyColour (img, c, threadPool); else if (img.getFormat() == Image::RGB) applyColour (img, c, threadPool); else jassertfalse; }