
Originally Posted by
neutron7
That is a nice solution! "milkdrop" here we come

Done 
I just added a new 'blend()' method to the library that performs the blitting of a sprite onto an image using a custom blending operator. If it works fine, I will also later add 'blendMask()' and 'blendScaleRotated()' methods (similar to the existing 'blitMask()' and 'blitScaleRotated()' methods). The documentation for using the method is, as usual, located above its declaration (Image.h, line 876). Here is an almost complete example for performing multiplicative blending of two images:
Code:
#include <tgx.h>
using namespace tgx;
uint16_t dst_buf[320 * 240]; // buffer
Image<RGB565> dst(dst_buf, 320, 240); // image
uint16_t src_buf[200*200]; // buffer
Image<RGB565> src(src_buf, 200, 200); // sprite
/* blending operator: perform multiplicative blending of two RGBf colors */
RGBf mult_op(RGBf col_src, RGBf col_dst) {
return RGBf(col_src.R*col_dst.R, col_src.G*col_dst.G, col_src.B*col_dst.B);
}
void setup() {
// source image: draw an horizontal gradient and a filled circle
src.fillScreenHGradient(RGB565_Purple, RGB565_Orange);
src.fillCircle({ 100, 100 }, 80, RGB565_Salmon, RGB565_Black);
// destination image: draw a vertical gradient
dst.fillScreenVGradient(RGB565_Green, RGB565_White);
// perform the blending of src over dst using the 'mult_op' operator
dst.blend(src, { 60 , 20 }, mult_op);
// *** add code to display dst on screen ***
}
void loop() {
}
As you can see, the blending operator does not need to have the same color type as the sprite and destination images (which can also have different color types!). Color conversion is performed automatically when needed. However, one should favor a blending operator with the same color types as the images as this will give the best performance. Here, I used the RGBf color type just because I was lazy and it was simpler to write the blending operator with floating point valued channels...
Note also that the blending operator does not need to be a function, it can be a functor object so it can have an internal state. For example, here is a (stupid) custom blending operator that depends on some parameter p:
Code:
struct MyBlend
{
/* Set the p parameter in the constructor */
MyBlend(float p) : _p(p) {}
RGBf operator()(RGBf col_src, RGBf col_dst) const
{
return RGBf(col_src.R*_p + col_dst.R*(1-_p), col_src.G*(1-_p) + col_dst.G*_p , max(col_src.B,col_dst.B));
}
float _p;
};
Then, as in the example before, we can use this operator by simply calling 'blend()' with a temporary instance like so:
Code:
// perform the blending of src over dst using the 'MyBlend' operator with param 0.7
dst.blend(src, { 60 , 20 }, MyBlend(0.7f));