#include "image/filters.h" #include #include #include "image/utils.h" unsigned int u32_put_alpha(const unsigned int clr, const unsigned char alpha) { return (clr & BYTE_RGB) | alpha << SHIFT_24; } unsigned int u32_invert(const unsigned int clr) { return ((~clr) & BYTE_RGB) | (clr & BYTE_A); } unsigned int u32_swap_rb(const unsigned int clr) { return (clr & 0xFF00FF00) | ((clr & BYTE_R) >> SHIFT_16) | ((clr & BYTE_B) << SHIFT_16); } unsigned int u32_grayscale(const unsigned int clr) { unsigned int red = DECODE_R(clr); unsigned int green = DECODE_G(clr); unsigned int blue = DECODE_B(clr); /* * WHY: The colors use the REC 601 Standard: In layman's terms they are * weighed in how the human eye perceives color. So green is the largest * because we are more sensitive to green and least to blue. * The Shift_8 is to divide 256 to bring the color value back into range of 0 - 255 */ unsigned int gray = ((red * 77) + (green * 150) + (blue * 29)) >> SHIFT_8; return (clr & BYTE_A) | (gray << SHIFT_16) | (gray << SHIFT_8) | gray; } unsigned int u32_step(const unsigned int clr, const unsigned char max) { if (DECODE_B(u32_grayscale(clr)) < max) { return ENCODE_ARGB(0, 0, 0, 0); } return ENCODE_ARGB(MAX, MAX, MAX, MAX); } unsigned int u32_brighten(const unsigned int clr, const float factor) { unsigned int red = CLAMP_MAX(DECODE_R(clr) * factor); unsigned int green = CLAMP_MAX(DECODE_G(clr) * factor); unsigned int blue = CLAMP_MAX(DECODE_B(clr) * factor); return (clr & BYTE_A) | (red << SHIFT_16) | (green << SHIFT_8) | blue; } unsigned int u32_mix(const unsigned int clr_a, const unsigned int clr_b) { unsigned char blue = (DECODE_B(clr_a) + DECODE_B(clr_b)) / 2; unsigned char green = (DECODE_G(clr_a) + DECODE_G(clr_b)) / 2; unsigned char red = (DECODE_R(clr_a) + DECODE_R(clr_b)) / 2; return ENCODE_ARGB(DECODE_A(clr_a), red, green, blue); } unsigned int u32_lerp(const unsigned int clr_a, const unsigned int clr_b, const float ratio) { /* * A more powerful mix using floating points to more precisely and * smoothly mix colors from 2 pixels. Doesn't really work on PPM images * because they are whole number pixels. So JPEG are better because the * numbers result in floating point colors. * */ unsigned char blue = ((DECODE_B(clr_a) * (1 - ratio)) + ((DECODE_B(clr_b) * ratio))); unsigned char green = ((DECODE_G(clr_a) * (1 - ratio)) + ((DECODE_G(clr_b) * ratio))); unsigned char red = ((DECODE_R(clr_a) * (1 - ratio)) + ((DECODE_R(clr_b) * ratio))); return ENCODE_ARGB(DECODE_A(clr_a), red, green, blue); } unsigned int u32_mask(const unsigned int clr, const char keep) { switch (keep) { case 'r': return clr & (BYTE_A | BYTE_R); break; case 'g': return clr & (BYTE_A | BYTE_G); break; case 'b': return clr & (BYTE_A | BYTE_B); break; default: return FAILURE; break; } return FAILURE; } unsigned int u32_rotl(const unsigned int clr, const int n) { if (n > 8) { LOGERROR(ERR_INVALID_SIZE); return ERR_INVALID_SIZE; } unsigned int red = DECODE_R(clr); unsigned int green = DECODE_G(clr); unsigned int blue = DECODE_B(clr); /* * Rotates the color channels. Essentially it moves bits left a certain * n and then wraps if it leaves the left to the right. * * If you input 1 to n and have: * 0 0 0 0 0 0 0 1 # 1 * * You will get: * 0 0 0 0 0 0 1 0 # 2 * * But this gets complex with larger numbers: * 0 1 0 1 1 1 0 0 # 4 + 8 + 16 + 64 = 92 * * Rotating by 2 will wrap and give: * 0 1 1 1 0 0 0 1 # 1 + 16 + 32 + 64 = 113 * * This creates a pixilated looking image of random colors while still * holding the original image's structure under. Almost a distorted, * broken TV look. * * N has a max of 8 because any number after 8 results in the bits * shifting negatively due to the wrapping math, which cant happen and * just makes the pixels black. If the number is 8 it just results in * the pixels being the same as they are just rotated back to their * original spot. * */ red = ((red << n) | (red >> (SHIFT_8 - n))) & BYTE; green = ((green << n) | (green >> (SHIFT_8 - n))) & BYTE; blue = ((blue << n) | (blue >> (SHIFT_8 - n))) & BYTE; return ENCODE_RGB(red, green, blue); } unsigned int u32_contrast(const unsigned int clr, const float factor) { int red = CLAMP_MIN(CLAMP_MAX( (int)(factor * (float)(int)(DECODE_R(clr) - HALF_MAX)) + HALF_MAX)); int green = CLAMP_MIN(CLAMP_MAX( (int)(factor * (float)(int)(DECODE_G(clr) - HALF_MAX)) + HALF_MAX)); int blue = CLAMP_MIN(CLAMP_MAX( (int)(factor * (float)(int)(DECODE_B(clr) - HALF_MAX)) + HALF_MAX)); return BYTE_A | (red << SHIFT_16) | (green << SHIFT_8) | blue; } unsigned int u32_avg_gb(const unsigned int clr) { unsigned char red = DECODE_R(clr); unsigned char blue = DECODE_B(clr); unsigned char green = (red + blue) / 2; return ENCODE_RGB(red, green, blue); } /** * @brief Blurs column at (x,y) using -radius to radius * @details Starts at the top extreme and moves to the bottom extreme, * taking the pixel and adding each channel. Because each channel is separated * into their own int, it adds each int together and at the end divides each * channel back below 256 and converts back to one int. * @param[in] src Pointer to buffer image. * @param[in] x Location in the image buffer for column of original pixel * @param[in] y Location in the image buffer for row of original pixel * @param[in] img Pointer to PPM container * @param[in] radius The value to find -extreme and +extreme to add to pixel * @return unsigned int Packed color with new alpha channel. */ unsigned int blur_col(const unsigned int* src, const int x, const int y, const PPM* img, const int radius) { unsigned long pt = 0, cnt = 0; for (int dx = -radius; dx <= radius; dx++) { int px = x + dx; if (px >= 0 && px < img->width) { pt += u32to_u64(src[y * img->width + px]); cnt++; } } // WHY: takes the count (cnt) of pixels from column and divides the // point (pt) by (cnt) to get back below 256 and then converted back // into an int from a long if (cnt > 0) { return u64to_u32(pt, cnt); } return src[y * img->width + x]; } /** * @brief Blurs column at (x,y) using -radius to radius * @details Starts at the left extreme and moves to the right extreme taking the * pixel and adding each channel. Because each channel is separated into their * own int, it adds each int together and at the end divides each channel back * below 256 and converts back to one int. * @param[in] src Pointer to buffer image. * @param[in] x Location in the image buffer for column of original pixel * @param[in] y Location in the image buffer for row of original pixel * @param[in] img Pointer to PPM container * @param[in] radius The value to find -extreme and +extreme to add to pixel * @return unsigned int Packed color with new alpha channel. */ unsigned int blur_row(const unsigned int* src, const int x, const int y, const PPM* img, const int radius) { unsigned long pt = 0, cnt = 0; for (int dy = -radius; dy <= radius; dy++) { int py = y + dy; if (py >= 0 && py < img->height) { pt += u32to_u64(src[py * img->width + x]); cnt++; } } // WHY: takes the count (cnt) of pixels from row and divides the // point (pt) by (cnt) to get back below 256 and then converted back // into an int from a long if (cnt > 0) { return u64to_u32(pt, cnt); } return src[y * img->width + x]; } unsigned int* u32_box_blur(const unsigned int* src_buf, const PPM* img, const int radius) { unsigned int* first_pass = malloc(img->len * sizeof(unsigned int)); if (!first_pass) { LOGERROR(ERR_BUF_NULL); return NULL; } unsigned int* dst_buf = malloc(img->len * sizeof(unsigned int)); if (!dst_buf) { free(first_pass); LOGERROR(ERR_BUF_NULL); return NULL; } for (int y = 0; y < img->height; y++) { for (int x = 0; x < img->width; x++) { first_pass[y * img->width + x] = blur_col(src_buf, x, y, img, radius); } } for (int y = 0; y < img->height; y++) { for (int x = 0; x < img->width; x++) { dst_buf[y * img->width + x] = blur_row(first_pass, x, y, img, radius); } } free(first_pass); free((void*)src_buf); return dst_buf; }