Files
libimg/src/filters.c
T

293 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);
free((void*)src_buf);
return dst_buf;
}