C++, version 2.1
UPDATED code and images to account for an error I noticed on the river images: FreeImage stores bitmap bytes on scanline boundaries aligned to 32 bits. I needed to account for the pitch in some cases, like river.
Slower than my first answer, taking approximately 15 seconds per image on my machine now, but I prefer most of the results. Adding this as a second answer, because it generates vastly different results in all cases.
This program is a two-pass algorithm; pass 1 is like before, an extremely quick algorithm that makes two lists of pixels sorted by a weighted brightness, and matches up the pairs by index values, copying the results to the destination bitmap.
Pass 2 is a version of aditsu's "random swap" concept. I run random swaps on two pixels in the destination bitmap if the swap would reduce the 'error measurement' in the destination, and terminate this after a threshold of 10000 sequential misses. Increasing the threshold would increase quality, but at the expense of time.
Code
/* Inputs: 2 image files of same area
Outputs: image1 made from pixels of image2*/
#include <iostream>
#include <stdlib.h>
#include "FreeImage.h"
#include <vector>
#include <algorithm>
class pixel
{
public:
int x, y;
BYTE r, g, b;
float val; //color value; weighted 'brightness'
};
bool sortByColorVal(const pixel &lhs, const pixel &rhs) { return lhs.val > rhs.val; }
FIBITMAP* GenericLoader(const char* lpszPathName, int flag)
{
FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
// check the file signature and deduce its format
// (the second argument is currently not used by FreeImage)
fif = FreeImage_GetFileType(lpszPathName, 0);
if (fif == FIF_UNKNOWN)
{
// no signature ?
// try to guess the file format from the file extension
fif = FreeImage_GetFIFFromFilename(lpszPathName);
}
// check that the plugin has reading capabilities ...
if ((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif))
{
// ok, let's load the file
FIBITMAP *dib = FreeImage_Load(fif, lpszPathName, flag);
// unless a bad file format, we are done !
return dib;
}
return NULL;
}
bool GenericWriter(FIBITMAP* dib, const char* lpszPathName, int flag)
{
FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
BOOL bSuccess = FALSE;
if (dib)
{
// try to guess the file format from the file extension
fif = FreeImage_GetFIFFromFilename(lpszPathName);
if (fif != FIF_UNKNOWN)
{
// check that the plugin has sufficient writing and export capabilities ...
WORD bpp = FreeImage_GetBPP(dib);
if (FreeImage_FIFSupportsWriting(fif) && FreeImage_FIFSupportsExportBPP(fif, bpp))
{
// ok, we can save the file
bSuccess = FreeImage_Save(fif, dib, lpszPathName, flag);
// unless an abnormal bug, we are done !
}
}
}
return (bSuccess == TRUE) ? true : false;
}
void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message)
{
std::cout << std::endl << "*** ";
if (fif != FIF_UNKNOWN)
{
std::cout << "ERROR: " << FreeImage_GetFormatFromFIF(fif) << " Format" << std::endl;
}
std::cout << message;
std::cout << " ***" << std::endl;
}
FIBITMAP* Convert24BPP(FIBITMAP* dib)
{
if (FreeImage_GetBPP(dib) == 24) return dib;
FIBITMAP *dib2 = FreeImage_ConvertTo24Bits(dib);
FreeImage_Unload(dib);
return dib2;
}
// ----------------------------------------------------------
int main(int argc, char **argv)
{
// call this ONLY when linking with FreeImage as a static library
#ifdef FREEIMAGE_LIB
FreeImage_Initialise();
#endif
FIBITMAP *src = NULL, *pal = NULL;
int result = EXIT_FAILURE;
// initialize my own FreeImage error handler
FreeImage_SetOutputMessage(FreeImageErrorHandler);
// print version
std::cout << "FreeImage version : " << FreeImage_GetVersion() << std::endl;
if (argc != 4)
{
std::cout << "USAGE : Pic2Pic <source image> <palette image> <output file name>" << std::endl;
return EXIT_FAILURE;
}
// Load the src image
src = GenericLoader(argv[1], 0);
if (src)
{
// load the palette image
pal = GenericLoader(argv[2], 0);
if (pal)
{
//first make sure everything is 24 bit:
src = Convert24BPP(src);
pal = Convert24BPP(pal);
//compare areas
// if(!samearea) return EXIT_FAILURE;
unsigned int width_src = FreeImage_GetWidth(src);
unsigned int height_src = FreeImage_GetHeight(src);
unsigned int width_pal = FreeImage_GetWidth(pal);
unsigned int height_pal = FreeImage_GetHeight(pal);
//strides! FreeImage stores bitmap scanlines on 32-bit boundaries...old-school shit I haven't had to deal with in a while ;)
unsigned int pitch_src = FreeImage_GetPitch(src);
unsigned int pitch_pal = FreeImage_GetPitch(pal);
if (width_src * height_src != width_pal * height_pal)
{
std::cout << "ERROR: source and palette images do not have the same pixel area." << std::endl;
result = EXIT_FAILURE;
}
else
{
//go to work!
//retrieve the image data
BYTE *bits_src = FreeImage_GetBits(src);
BYTE *bits_pal = FreeImage_GetBits(pal);
//make destination image
FIBITMAP *dst = FreeImage_ConvertTo24Bits(src);
BYTE *bits_dst = FreeImage_GetBits(dst);
//probably same as src, but eh...
unsigned int pitch_dst = FreeImage_GetPitch(dst);
//make a vector of all the src pixels that we can sort by color value
std::vector<pixel> src_pixels;
for (unsigned int y = 0; y < height_src; ++y)
{
for (unsigned int x = 0; x < width_src; ++x)
{
pixel p;
p.x = x;
p.y = y;
p.b = bits_src[y*pitch_src + x * 3];
p.g = bits_src[y*pitch_src + x * 3 + 1];
p.r = bits_src[y*pitch_src + x * 3 + 2];
//calculate color value using a weighted brightness for each channel
//p.val = 0.2126f * p.r + 0.7152f * p.g + 0.0722f * p.b; //from http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html
p.val = 0.5f * p.r + p.g + 0.1f * p.b;
src_pixels.push_back(p);
}
}
//sort by color value
std::sort(src_pixels.begin(), src_pixels.end(), sortByColorVal);
//make a vector of all palette pixels we can use
std::vector<pixel> pal_pixels;
for (unsigned int y = 0; y < height_pal; ++y)
{
for (unsigned int x = 0; x < width_pal; ++x)
{
pixel p;
p.b = bits_pal[y*pitch_pal + x * 3];
p.g = bits_pal[y*pitch_pal + x * 3 + 1];
p.r = bits_pal[y*pitch_pal + x * 3 + 2];
p.val = 0.5f * p.r + p.g + 0.1f * p.b;
pal_pixels.push_back(p);
}
}
//sort by color value
std::sort(pal_pixels.begin(), pal_pixels.end(), sortByColorVal);
//for each src pixel, match it with same index palette pixel and copy to destination
for (unsigned int i = 0; i < width_src * height_src; ++i)
{
bits_dst[src_pixels[i].y * pitch_src + src_pixels[i].x * 3] = pal_pixels[i].b;
bits_dst[src_pixels[i].y * pitch_src + src_pixels[i].x * 3 + 1] = pal_pixels[i].g;
bits_dst[src_pixels[i].y * pitch_src + src_pixels[i].x * 3 + 2] = pal_pixels[i].r;
}
//improve the destination via random swaps until we miss threshold swaps in a row
unsigned int threshold = 10000;
unsigned int missed = 0;
unsigned int index1, index2, index1_dst, index2_dst;
unsigned int distance_squared_current, distance_squared_swapped;
BYTE tr, tg, tb;
unsigned int x1, x2, y1, y2;
srand(42); //for good luck!...and, er, repeatable results. Should maybe re-seed periodically if threshold is high.
for (;;)
{
//not assuming pitch_src == pitch_dst, for now
x1 = rand() % width_src;
x2 = rand() % width_src;
y1 = rand() % height_src;
y2 = rand() % height_src;
index1 = (x1) * 3 + (y1) * pitch_src;
index2 = (x2) * 3 + (y2) * pitch_src;
index1_dst = (x1)* 3 + (y1)* pitch_dst;
index2_dst = (x2)* 3 + (y2)* pitch_dst;
distance_squared_current =
((bits_src[index1 + 2] - bits_dst[index1_dst + 2]) * (bits_src[index1 + 2] - bits_dst[index1_dst + 2]) >> 3) +
(bits_src[index1 + 1] - bits_dst[index1_dst + 1]) * (bits_src[index1 + 1] - bits_dst[index1_dst + 1]) +
((bits_src[index1] - bits_dst[index1_dst]) * (bits_src[index1] - bits_dst[index1_dst]) >> 1 )+
((bits_src[index2 + 2] - bits_dst[index2_dst + 2]) * (bits_src[index2 + 2] - bits_dst[index2_dst + 2]) >> 3) +
(bits_src[index2 + 1] - bits_dst[index2_dst + 1]) * (bits_src[index2 + 1] - bits_dst[index2_dst + 1]) +
((bits_src[index2] - bits_dst[index2_dst]) * (bits_src[index2] - bits_dst[index2_dst]) >> 1);
distance_squared_swapped =
((bits_src[index1 + 2] - bits_dst[index2_dst + 2]) * (bits_src[index1 + 2] - bits_dst[index2_dst + 2]) >> 3) +
(bits_src[index1 + 1] - bits_dst[index2_dst + 1]) * (bits_src[index1 + 1] - bits_dst[index2_dst + 1]) +
((bits_src[index1] - bits_dst[index2_dst]) * (bits_src[index1] - bits_dst[index2_dst]) >> 1)+
((bits_src[index2 + 2] - bits_dst[index1_dst + 2]) * (bits_src[index2 + 2] - bits_dst[index1_dst + 2]) >> 3) +
(bits_src[index2 + 1] - bits_dst[index1_dst + 1]) * (bits_src[index2 + 1] - bits_dst[index1_dst + 1]) +
((bits_src[index2] - bits_dst[index1_dst]) * (bits_src[index2] - bits_dst[index1_dst]) >> 1);
if (distance_squared_swapped < distance_squared_current)
{
missed = 0;
tb = bits_dst[index1_dst];
tg = bits_dst[index1_dst + 1];
tr = bits_dst[index1_dst + 2];
bits_dst[index1_dst] = bits_dst[index2_dst];
bits_dst[index1_dst + 1] = bits_dst[index2_dst + 1];
bits_dst[index1_dst + 2] = bits_dst[index2_dst + 2];
bits_dst[index2_dst] = tb;
bits_dst[index2_dst + 1] = tg;
bits_dst[index2_dst + 2] = tr;
}
missed += 1;
if (missed > threshold) break; //done improvements
}
// Save the destination image
bool bSuccess = GenericWriter(dst, argv[3], 0);
if (!bSuccess)
{
std::cout << "ERROR: unable to save " << argv[3] << std::endl;
std::cout << "This format does not support 24-bit images" << std::endl;
result = EXIT_FAILURE;
}
else result = EXIT_SUCCESS;
FreeImage_Unload(dst);
}
// Free pal
FreeImage_Unload(pal);
}
// Free src
FreeImage_Unload(src);
}
#ifdef FREEIMAGE_LIB
FreeImage_DeInitialise();
#endif
if (result == EXIT_SUCCESS) std::cout << "SUCCESS!" << std::endl;
else std::cout << "FAILURE!" << std::endl;
return result;
}
Results
American Gothic palette

Mona Lisa palette

Rainbow palette (toughy for this program!)

River palette

Scream palette

Starry Night palette

and for fun: Mustang in the Camaro palette

Sorry for the length of this post, but I loved this puzzle; it kept me thinking and tinkering, off and on all day.