C, Lab renk alanı ve gelişmiş renk taklidi ile
Yapıldığımı mı söyledim? Yalan söyledim. Diğer çözümümdeki algoritmanın dışarıdaki en iyisi olduğunu düşünüyorum, ancak Perl sadece sayı çarpma işleri için yeterince hızlı değil, bu yüzden çalışmamı C de tekrar uyguladım. bu yazıdaki tüm görüntüleri daha yüksek kalitede çalıştırıyor. görüntü başına yaklaşık 3 dakika orijinalden daha düşüktür ve biraz daha düşük kalite (% 0,5 düzeyinde) görüntü başına 20-30 saniye içinde çalışır. Temel olarak tüm çalışmalar ImageMagick ile, dithering ise ImageMagick'in kübik spline enterpolasyonu kullanılarak yapılır, bu da daha iyi / daha az desenli bir sonuç verir.
kod
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <wand/MagickWand.h>
#define ThrowWandException(wand) \
{ \
char \
*description; \
\
ExceptionType \
severity; \
\
description=MagickGetException(wand,&severity); \
(void) fprintf(stderr,"%s %s %lu %s\n",GetMagickModule(),description); \
description=(char *) MagickRelinquishMemory(description); \
abort(); \
exit(-1); \
}
int width, height; /* Target image size */
MagickWand *source_wand, *target_wand, *img_wand, *target_lab_wand, *img_lab_wand;
PixelPacket *source_pixels, *target_pixels, *img_pixels, *target_lab_pixels, *img_lab_pixels;
Image *img, *img_lab, *target, *target_lab;
CacheView *img_lab_view, *target_lab_view;
ExceptionInfo *e;
MagickWand *load_image(const char *filename) {
MagickWand *img = NewMagickWand();
if (!MagickReadImage(img, filename)) {
ThrowWandException(img);
}
return img;
}
PixelPacket *get_pixels(MagickWand *wand) {
PixelPacket *ret = GetAuthenticPixels(
GetImageFromMagickWand(wand), 0, 0,
MagickGetImageWidth(wand), MagickGetImageHeight(wand), e);
CatchException(e);
return ret;
}
void sync_pixels(MagickWand *wand) {
SyncAuthenticPixels(GetImageFromMagickWand(wand), e);
CatchException(e);
}
MagickWand *transfer_pixels() {
if (MagickGetImageWidth(source_wand) * MagickGetImageHeight(source_wand)
!= MagickGetImageWidth(target_wand) * MagickGetImageHeight(target_wand)) {
perror("size mismtch");
}
MagickWand *img_wand = CloneMagickWand(target_wand);
img_pixels = get_pixels(img_wand);
memcpy(img_pixels, source_pixels,
MagickGetImageWidth(img_wand) * MagickGetImageHeight(img_wand) * sizeof(PixelPacket));
sync_pixels(img_wand);
return img_wand;
}
MagickWand *image_to_lab(MagickWand *img) {
MagickWand *lab = CloneMagickWand(img);
TransformImageColorspace(GetImageFromMagickWand(lab), LabColorspace);
return lab;
}
int lab_distance(PixelPacket *a, PixelPacket *b) {
int l_diff = (GetPixelL(a) - GetPixelL(b)) / 256,
a_diff = (GetPixela(a) - GetPixela(b)) / 256,
b_diff = (GetPixelb(a) - GetPixelb(b)) / 256;
return (l_diff * l_diff + a_diff * a_diff + b_diff * b_diff);
}
int should_swap(int x1, int x2, int y1, int y2) {
int dist = lab_distance(&img_lab_pixels[width * y1 + x1], &target_lab_pixels[width * y1 + x1])
+ lab_distance(&img_lab_pixels[width * y2 + x2], &target_lab_pixels[width * y2 + x2]);
int swapped_dist = lab_distance(&img_lab_pixels[width * y2 + x2], &target_lab_pixels[width * y1 + x1])
+ lab_distance(&img_lab_pixels[width * y1 + x1], &target_lab_pixels[width * y2 + x2]);
return swapped_dist < dist;
}
void pixel_multiply_add(MagickPixelPacket *dest, PixelPacket *src, double mult) {
dest->red += (double)GetPixelRed(src) * mult;
dest->green += ((double)GetPixelGreen(src) - 32768) * mult;
dest->blue += ((double)GetPixelBlue(src) - 32768) * mult;
}
#define min(x,y) (((x) < (y)) ? (x) : (y))
#define max(x,y) (((x) > (y)) ? (x) : (y))
double mpp_distance(MagickPixelPacket *a, MagickPixelPacket *b) {
double l_diff = QuantumScale * (a->red - b->red),
a_diff = QuantumScale * (a->green - b->green),
b_diff = QuantumScale * (a->blue - b->blue);
return (l_diff * l_diff + a_diff * a_diff + b_diff * b_diff);
}
void do_swap(PixelPacket *pix, int x1, int x2, int y1, int y2) {
PixelPacket tmp = pix[width * y1 + x1];
pix[width * y1 + x1] = pix[width * y2 + x2];
pix[width * y2 + x2] = tmp;
}
int should_swap_dither(double detail, int x1, int x2, int y1, int y2) {
// const InterpolatePixelMethod method = Average9InterpolatePixel;
const InterpolatePixelMethod method = SplineInterpolatePixel;
MagickPixelPacket img1, img2, img1s, img2s, target1, target2;
GetMagickPixelPacket(img, &img1);
GetMagickPixelPacket(img, &img2);
GetMagickPixelPacket(img, &img1s);
GetMagickPixelPacket(img, &img2s);
GetMagickPixelPacket(target, &target1);
GetMagickPixelPacket(target, &target2);
InterpolateMagickPixelPacket(img, img_lab_view, method, x1, y1, &img1, e);
InterpolateMagickPixelPacket(img, img_lab_view, method, x2, y2, &img2, e);
InterpolateMagickPixelPacket(target, target_lab_view, method, x1, y1, &target1, e);
InterpolateMagickPixelPacket(target, target_lab_view, method, x2, y2, &target2, e);
do_swap(img_lab_pixels, x1, x2, y1, y2);
// sync_pixels(img_wand);
InterpolateMagickPixelPacket(img, img_lab_view, method, x1, y1, &img1s, e);
InterpolateMagickPixelPacket(img, img_lab_view, method, x2, y2, &img2s, e);
do_swap(img_lab_pixels, x1, x2, y1, y2);
// sync_pixels(img_wand);
pixel_multiply_add(&img1, &img_lab_pixels[width * y1 + x1], detail);
pixel_multiply_add(&img2, &img_lab_pixels[width * y2 + x2], detail);
pixel_multiply_add(&img1s, &img_lab_pixels[width * y2 + x2], detail);
pixel_multiply_add(&img2s, &img_lab_pixels[width * y1 + x1], detail);
pixel_multiply_add(&target1, &target_lab_pixels[width * y1 + x1], detail);
pixel_multiply_add(&target2, &target_lab_pixels[width * y2 + x2], detail);
double dist = mpp_distance(&img1, &target1)
+ mpp_distance(&img2, &target2);
double swapped_dist = mpp_distance(&img1s, &target1)
+ mpp_distance(&img2s, &target2);
return swapped_dist + 1.0e-4 < dist;
}
int main(int argc, char *argv[]) {
if (argc != 7) {
fprintf(stderr, "Usage: %s source.png target.png dest nodither_pct dither_pct detail\n", argv[0]);
return 1;
}
char *source_filename = argv[1];
char *target_filename = argv[2];
char *dest = argv[3];
double nodither_pct = atof(argv[4]);
double dither_pct = atof(argv[5]);
double detail = atof(argv[6]) - 1;
const int SWAPS_PER_LOOP = 1000000;
int nodither_limit = ceil(SWAPS_PER_LOOP * nodither_pct / 100);
int dither_limit = ceil(SWAPS_PER_LOOP * dither_pct / 100);
int dither = 0, frame = 0;
char outfile[256], cmdline[1024];
sprintf(outfile, "out/%s.png", dest);
MagickWandGenesis();
e = AcquireExceptionInfo();
source_wand = load_image(source_filename);
source_pixels = get_pixels(source_wand);
target_wand = load_image(target_filename);
target_pixels = get_pixels(target_wand);
img_wand = transfer_pixels();
img_pixels = get_pixels(img_wand);
target_lab_wand = image_to_lab(target_wand);
target_lab_pixels = get_pixels(target_lab_wand);
img_lab_wand = image_to_lab(img_wand);
img_lab_pixels = get_pixels(img_lab_wand);
img = GetImageFromMagickWand(img_lab_wand);
target = GetImageFromMagickWand(target_lab_wand);
img_lab_view = AcquireAuthenticCacheView(img, e);
target_lab_view = AcquireAuthenticCacheView(target,e);
CatchException(e);
width = MagickGetImageWidth(img_wand);
height = MagickGetImageHeight(img_wand);
while (1) {
int swaps_made = 0;
for (int n = 0 ; n < SWAPS_PER_LOOP ; n++) {
int x1 = rand() % width,
x2 = rand() % width,
y1 = rand() % height,
y2 = rand() % height;
int swap = dither ?
should_swap_dither(detail, x1, x2, y1, y2)
: should_swap(x1, x2, y1, y2);
if (swap) {
do_swap(img_pixels, x1, x2, y1, y2);
do_swap(img_lab_pixels, x1, x2, y1, y2);
swaps_made ++;
}
}
sync_pixels(img_wand);
if (!MagickWriteImages(img_wand, outfile, MagickTrue)) {
ThrowWandException(img_wand);
}
img_pixels = get_pixels(img_wand);
sprintf(cmdline, "cp out/%s.png anim/%s/%05i.png", dest, dest, frame++);
system(cmdline);
if (!dither && swaps_made < nodither_limit) {
sprintf(cmdline, "cp out/%s.png out/%s-nodither.png", dest, dest);
system(cmdline);
dither = 1;
} else if (dither && swaps_made < dither_limit)
break;
}
return 0;
}
İle derleyin
gcc -std=gnu99 -O3 -march=native -ffast-math \
-o transfer `pkg-config --cflags MagickWand` \
transfer.c `pkg-config --libs MagickWand` -lm
Sonuçlar
Çoğunlukla Perl sürümüyle aynı, sadece biraz daha iyi, ancak birkaç istisna var. Dithering, genel olarak daha az fark edilir. Çığlık -> Yıldızlı Gece "yanan dağ" etkisine sahip değildir ve Camaro gri piksellerle daha az bulanık görünür. Perl versiyonunun colorspace kodunun düşük doygunluk piksellerine sahip bir hatası olduğunu düşünüyorum.
Amerikan Gotik paleti
Mona Lisa paleti
Yıldızlı Gece paleti
Çığlık paleti
Küreler paleti
Mustang (Camaro paleti)
Camaro (Mustang paleti)