292 lines
8.1 KiB
C
292 lines
8.1 KiB
C
#include "image/filters.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#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);
|
|
return dst_buf;
|
|
}
|