Files
proxmark3/client/src/imgutils.c
iceman1001 4df743c2a0 style
2024-01-15 13:08:55 +01:00

207 lines
6.6 KiB
C

#include <assert.h>
#include "imgutils.h"
struct ycbcr_t {
int y;
int cb;
int cr;
};
static void rgb_to_ycbcr(int rgb, struct ycbcr_t *ycbcr) {
int r = gdTrueColorGetRed(rgb);
int g = gdTrueColorGetGreen(rgb);
int b = gdTrueColorGetBlue(rgb);
/*
* Below is a fixed-point version of the following code:
* ycbcr->y = r * 0.29900 + g * 0.58700 + b * 0.11400;
* ycbcr->cb = r * -0.16874 + g * -0.33126 + b * 0.50000 + 128;
* ycbcr->cr = r * 0.50000 + g * -0.41869 + b * -0.08131 + 128;
*/
ycbcr->y = (r * 19595 + g * 38470 + b * 7471) / 65536;
ycbcr->cb = (r * -11059 + g * -21709 + b * 32768) / 65536 + 128;
ycbcr->cr = (r * 32768 + g * -27439 + b * -5329) / 65536 + 128;
}
static inline void cap_comp(int *x) {
if (*x < 0) {
*x = 0;
} else if (*x > 255) {
*x = 255;
}
}
gdImagePtr img_palettize(gdImagePtr rgb, int *palette, int palette_size) {
assert(rgb != NULL);
assert(palette != NULL);
assert(palette_size >= 2 && palette_size < 256);
// Create paletized image
gdImagePtr res = gdImageCreate(gdImageSX(rgb), gdImageSY(rgb));
if (!res) {
return NULL;
}
// Allocate space for palette in YCbCr
struct ycbcr_t *pal_ycbcr = calloc(palette_size, sizeof(struct ycbcr_t));
if (!pal_ycbcr) {
gdImageDestroy(res);
return NULL;
}
/*
* Initialize the column's error array.
*
* Note that we are storing two extra values so we don't have to do boundary checking at
* the left and right edges of the image.
*
* To reduce shifts and increase accuracy, each entry is stored with 16x times the error,
* and gets divided by that amount when it is read.
*/
struct ycbcr_t *forward = calloc(gdImageSX(rgb) + 2, sizeof(struct ycbcr_t));
if (!forward) {
free(pal_ycbcr);
gdImageDestroy(res);
return NULL;
}
// Convert palette to YCbCr and allocate in image
for (int i = 0; i < palette_size; i++) {
int c = palette[i];
rgb_to_ycbcr(c, pal_ycbcr + i);
gdImageColorAllocate(res, gdTrueColorGetRed(c), gdTrueColorGetGreen(c), gdTrueColorGetBlue(c));
}
for (int y = 0; y < gdImageSY(rgb); y++) {
// Load current row error and reset its storage
struct ycbcr_t row_err = forward[1];
forward[1].y = forward[1].cb = forward[1].cr = 0;
for (int x = 0; x < gdImageSX(rgb); x++) {
struct ycbcr_t pix;
rgb_to_ycbcr(gdImageGetTrueColorPixel(rgb, x, y), &pix);
// Add error for current pixel
pix.y += row_err.y / 16;
pix.cb += row_err.cb / 16;
pix.cr += row_err.cr / 16;
// Cap in case it went to imaginary color territory
cap_comp(&pix.y);
cap_comp(&pix.cb);
cap_comp(&pix.cr);
/*
* Iterate through all candidate colors and find the nearest one using the
* squared Euclidean distance.
*/
int best_idx = 0;
struct ycbcr_t best_err = { 0 };
int best_score = 0x7FFFFFFF;
for (int can_idx = 0; can_idx < palette_size; can_idx++) {
struct ycbcr_t can_err = {
.y = pix.y - pal_ycbcr[can_idx].y,
.cb = pix.cb - pal_ycbcr[can_idx].cb,
.cr = pix.cr - pal_ycbcr[can_idx].cr,
};
int can_score = (
can_err.y * can_err.y +
can_err.cb * can_err.cb +
can_err.cr * can_err.cr
);
if (can_score < best_score) {
best_idx = can_idx;
best_score = can_score;
best_err = can_err;
}
}
// Set current pixel
gdImageSetPixel(res, x, y, best_idx);
// Propagate error within the current row, to the pixel to the right
row_err.y = best_err.y * 7 + forward[x + 2].y;
row_err.cb = best_err.cb * 7 + forward[x + 2].cb;
row_err.cr = best_err.cr * 7 + forward[x + 2].cr;
// Add error to bottom left
forward[x + 0].y += best_err.y * 3;
forward[x + 0].cb += best_err.cb * 3;
forward[x + 0].cr += best_err.cr * 3;
// Add error to bottom center
forward[x + 1].y += best_err.y * 5;
forward[x + 1].cb += best_err.cb * 5;
forward[x + 1].cr += best_err.cr * 5;
// Set error to bottom right
forward[x + 2].y = best_err.y * 1;
forward[x + 2].cb = best_err.cb * 1;
forward[x + 2].cr = best_err.cr * 1;
}
}
free(forward);
free(pal_ycbcr);
return res;
}
gdImagePtr img_crop_to_fit(gdImagePtr orig, int width, int height) {
assert(orig != NULL);
assert(width >= 1);
assert(height >= 1);
gdImagePtr res;
if (gdImageTrueColor(orig)) {
res = gdImageCreateTrueColor(width, height);
} else {
res = gdImageCreate(width, height);
}
if (!res) {
return NULL;
}
if (gdImageSY(orig) * width <= gdImageSX(orig) * height) {
// Image is wider than expected, so we will crop the left and right sides
int crop_width = gdImageSY(orig) * width / height;
int crop_sx = gdImageSX(orig) / 2 - crop_width / 2;
gdImageCopyResampled(
res, // Dest img
orig, // Src image
0, // Dest X
0, // Dest Y
crop_sx, // Src X
0, // Src Y
width, // Dest width
height, // Dest height
crop_width, // Src width
gdImageSY(orig) // Src height
);
} else {
// Image is taller than expected, so we will crop the top and bottom sides
int crop_height = gdImageSX(orig) * height / width;
int crop_sy = gdImageSY(orig) / 2 - crop_height / 2;
gdImageCopyResampled(
res, // Dest img
orig, // Src image
0, // Dest X
0, // Dest Y
0, // Src X
crop_sy, // Src Y
width, // Dest width
height, // Dest height
gdImageSX(orig), // Src width
crop_height // Src height
);
}
return res;
}