Programmatically generate a composite portrait mosaic


I need to create composite portrait mosaics (i.e. portraits made out of other portraits). See reference below.

Another good reference would be AndreaMosaic. http://www.andreaplanet.com/andreamosaic/samples/

Or this youtube tutorial (skip to the 5min mark) https://www.youtube.com/watch?v=9cy2gVm_ztQ

Looking for the best way to do this then generate a jpeg file that's ready to download. Ideally would like to do this with Node/Javascript but open to using PHP or whatever.

Any suggestions as to where to start? There are a few libraries here and there but nothing quite suited to what I'm trying to do.

enter image description here


Answers:


The faked mosaic is simple. Well I tried a simple multiplication and looks like ti works.

  1. create a photo texture pattern covering the size of input image

  2. modulate the grayscale photo pattern and original image

    simple multiplication will do.

Both steps can be combined into single one... here simple C++ code for this:

// globals
const int txrs=41; // number of textures for mosaic
picture txr[txrs]; // mosaic textures
picture pic0,pic1; // input and output images

// init
pic0.load("MonaLisa.jpg");
int sz=32; // mosaic grid size
for (int i=0;i<txrs;i++) // load/resize/grayscale textures
    {
    txr[i].load(AnsiString().sprintf("textures\\%03i.jpg",i));  // load image
    txr[i].resize_fit(sz,sz,0x00000000);                        // resize to tile size
    txr[i].enhance_range();
    txr[i].pixel_format(_pf_u);                                 // convert to grayscale <0,765>
    txr[i].pixel_format(_pf_rgba);                              // convert to grayscale RGBA
    }
pic0.resize_fit((pic0.xs/sz)*sz,(pic0.ys/sz)*sz,0x00000000);    // cut not full tile size part of pic1

// mosaic
int xx,yy,x,y,i,j,sz=txr[0].xs,a,b;
color c0,c1;
pic1=pic0;  // copy source image to destination
// process all regions
for (y=0;y<pic1.ys;y+=sz)
 for (x=0;x<pic1.xs;x+=sz)
    {
    // select random texture
    i=Random(txrs);
    // proces region
    for (yy=0;yy<sz;yy++)
     for (xx=0;xx<sz;xx++)
        {
        // grayscale texture and original color image pixels
        c0=txr[i].p[yy][xx];
        c1=pic1.p[y+yy][x+xx];
        // mutiply them
        for (j=0;j<3;j++)
            {
            a=BYTE(c0.db[j]);
            b=BYTE(c1.db[j]);
            a=(a*b)>>8;
            c0.db[j]=a;
            }
        // store to destinatio image
        pic1.p[y+yy][x+xx]=c0;
        }
    }
pic1.save("out.png");

I use my own picture class for images so some members are:


xs,ys is size of image in pixels
p[y][x].dd is pixel at (x,y) position as 32 bit integer type
clear(color) clears entire image with color
resize(xs,ys) resizes image to new resolution
bmp is VCL encapsulated GDI Bitmap with Canvas access
pf holds actual pixel format of the image:

enum _pixel_format_enum
    {
    _pf_none=0, // undefined
    _pf_rgba,   // 32 bit RGBA
    _pf_s,      // 32 bit signed int
    _pf_u,      // 32 bit unsigned int
    _pf_ss,     // 2x16 bit signed int
    _pf_uu,     // 2x16 bit unsigned int
    _pixel_format_enum_end
    };


color and pixels are encoded like this:

union color
    {
    DWORD dd; WORD dw[2]; byte db[4];
    int i; short int ii[2];
    color(){}; color(color& a){ *this=a; }; ~color(){}; color* operator = (const color *a) { dd=a->dd; return this; }; /*color* operator = (const color &a) { ...copy... return this; };*/
    };


The bands are:

enum{
    _x=0,   // dw
    _y=1,

    _b=0,   // db
    _g=1,
    _r=2,
    _a=3,

    _v=0,   // db
    _s=1,
    _h=2,
    };

The input image I used was this:

input

And here the result:

output

It might need some brightness tweaking to match original input image properties.