Skip to content

Instantly share code, notes, and snippets.

@ItsCOMMANDer
Created July 19, 2024 09:13
Show Gist options
  • Save ItsCOMMANDer/6e25183b6b443e3ce9d790c23b69c552 to your computer and use it in GitHub Desktop.
Save ItsCOMMANDer/6e25183b6b443e3ce9d790c23b69c552 to your computer and use it in GitHub Desktop.
This is a little tool i made to play around with images in the jpeg format.
// Compile with "gcc -o main main.c -ljpeg -lm"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include <jpeglib.h>
#define MINMAX(min,val,max) ((min > val) ? (min) : (max < val) ? (max) : (val))
#define IDX_RED 0
#define IDX_GREEN 1
#define IDX_BLUE 2
void switchChannels(uint8_t *image_data, int width, int height, int *permutation) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
uint8_t temp[3];
temp[0] = pixel[permutation[0]];
temp[1] = pixel[permutation[1]];
temp[2] = pixel[permutation[2]];
pixel[0] = temp[0];
pixel[1] = temp[1];
pixel[2] = temp[2];
}
}
}
void changeChannelsBrightness(uint8_t *image_data, int width, int height, int red_diff, int green_diff, int blue_diff) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
pixel[0] = MINMAX(0, pixel[0] + red_diff, 255);
pixel[1] = MINMAX(0, pixel[1] + green_diff, 255);
pixel[2] = MINMAX(0, pixel[2] + blue_diff, 255);
}
}
}
void grayScaleChannles(uint8_t *image_data, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
pixel[0] = (int)round((pixel[0] + pixel[1] + pixel[2]) / 3.0f);
pixel[1] = (int)round((pixel[0] + pixel[1] + pixel[2]) / 3.0f);
pixel[2] = (int)round((pixel[0] + pixel[1] + pixel[2]) / 3.0f);
}
}
}
void channelsInvert(uint8_t *image_data, int width, int height, bool invert_red, bool invert_green, bool invert_blue) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
if(invert_red == true) pixel[0] = 255 - pixel[0];
if(invert_green == true) pixel[1] = 255 - pixel[1];
if(invert_blue == true) pixel[2] = 255 - pixel[2];
}
}
}
void channelsAdjustControast(uint8_t *image_data, int width, int height,
uint8_t midpoint_red, uint8_t midpoint_green, uint8_t midpoint_blue,
float contrast_factor_red, float contrast_factor_green, float contrast_factor_blue) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
pixel[0] = MINMAX(0, (int)round(((pixel[0] - midpoint_red) * contrast_factor_red) + midpoint_red), 255);
pixel[1] = MINMAX(0, (int)round(((pixel[1] - midpoint_green) * contrast_factor_green) + midpoint_green), 255);
pixel[2] = MINMAX(0, (int)round(((pixel[2] - midpoint_blue) * contrast_factor_blue) + midpoint_blue), 255);
}
}
}
void isolateChannels(uint8_t *image_data, int width, int height, bool isolate_red, bool isolate_green, bool isolate_blue) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
if(isolate_red) pixel[0] = 0;
if(isolate_green) pixel[1] = 0;
if(isolate_blue) pixel[2] = 0;
}
}
}
void copyChannels(uint8_t *image_data, int width, int height, int *copyMode) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
uint8_t temp[3];
temp[0] = pixel[copyMode[0]];
temp[1] = pixel[copyMode[1]];
temp[2] = pixel[copyMode[2]];
pixel[0] = temp[0];
pixel[1] = temp[1];
pixel[2] = temp[2];
}
}
}
void multiplyChannels(uint8_t *image_data, int width, int height, float red_copy, float green_copy, float blue_copy) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t *pixel = image_data + (y * width + x) * 3;
pixel[0] = MINMAX(0, round(pixel[0] * red_copy), 255);
pixel[1] = MINMAX(0, round(pixel[1] * green_copy), 255);
pixel[2] = MINMAX(0, round(pixel[2] * blue_copy), 255);
}
}
}
void readJPEGfile(const char *filename, uint8_t **image_data, int *width, int *height) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *infile = fopen(filename, "rb");
if (!infile) {
fprintf(stderr, "Cannot open %s\n", filename);
exit(1);
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
*width = cinfo.output_width;
*height = cinfo.output_height;
int num_components = cinfo.output_components;
if (num_components != 3) {
fprintf(stderr, "Unsupported number of color components: %d\n", num_components);
exit(1);
}
*image_data = (uint8_t *)malloc(*width * *height * num_components);
while (cinfo.output_scanline < cinfo.output_height) {
uint8_t *buffer_array[1];
buffer_array[0] = *image_data + (cinfo.output_scanline) * (*width) * num_components;
jpeg_read_scanlines(&cinfo, buffer_array, 1);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
}
void writeJPEGfile(const char *filename, int quality, uint8_t *image_data, int width, int height) {
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *outfile = fopen(filename, "wb");
if (!outfile) {
fprintf(stderr, "Cannot open %s\n", filename);
exit(1);
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, outfile);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
while (cinfo.next_scanline < cinfo.image_height) {
uint8_t *buffer_array[1];
buffer_array[0] = image_data + (cinfo.next_scanline) * width * 3;
jpeg_write_scanlines(&cinfo, buffer_array, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose(outfile);
}
bool isNumber(const char* str) {
for(int i = 0; i < strlen(str); i++) {
if(!(((str[i] >= '0') && (str[i] <= '9')) || (i == 0) && (str[0] == '-'))) return false;
}
return true;
}
bool isFloat(const char* str) {
bool dot = false;
for(int i = 0; i < strlen(str); i++) {
if(!(str[i] == '.' || str[i] == '-')) {
if(str[i] < '0' || str[i] > '9') return false;
}
if((str[i] == '-') && (i != 0)) return false;
if((str[i] == '.') && (dot == true))
if(str[i] == '.') dot = true;
}
return true;
}
int main(int argc, char* argv[]) {
if(argc == 1) {
printf("Try %s -?\n", argv[0]);
return -1;
}
if((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "-?") == 0) || (strcmp(argv[1], "--help") == 0)) {
printf("%s usage:\n", argv[0]);
printf("\t%s [OPTIONS]\n", argv[0]);
printf("\n");
printf("\t-h / --help / -? : Display this Message\n");
printf("\t-i / --input <file> : specify the input file\n");
printf("\t-o / --output <file> : specify the output file\n");
printf("\t-sc / --switch-channel <[(rR)/(gG)/(bB)][(rR)/(gG)/(bB)][(rR)/(gG)/(bB)]> : switch the channel values around, from rgb to the values specified.\n");
printf("\t-cb / --change-brightness <red diff> <green diff> <blue diff> : Changes the brightness of the image\n");
printf("\t-gs / -gray-scale : Turns the Image into grayscale\n");
printf("\t-ci / --channel-invert <1/0> <1/0> <1/0> : specifies what channels (red, green and blue) to invert; 1 = invert, 0 = dont invert\n");
printf("\t-cc / --channel-contrast <r midpt> <r factor> <g midpt> <g factor> <b midpt> <b factor> : adjust the contrast of a channel around a midpoint\n");
printf("\t-ic / --isolate-channel <1/0> <1/0> <1/0> : isolate a channel by setting the non isolated to 0; 1 = isolated, 0 = not isolated\n");
printf("\t-cp / --copy-channel <[rRgGbB][rRgGbB][rRgGbB]> : specifies what channel to copy into otheres.\n");
printf("\t-mc / --multiply-channel <r factor> <g factor> <b factor> : multiplies each channel with the given factor\n");
return 0;
}
char* inputFile = NULL;
char* outputFile = NULL;
for(int i = 1; i < argc; i++) {
if(((strcmp(argv[i], "-i") == 0) || (strcmp(argv[i], "--input") == 0)) && i + 1 <= argc) {
if(inputFile != NULL) {
printf("More than 1 input file given.\n");
return -1;
}
inputFile = calloc(strlen(argv[i + 1]), sizeof(char));
strncpy(inputFile, argv[i + 1], strlen(argv[i + 1]));
}
if(((strcmp(argv[i], "-o") == 0) || (strcmp(argv[i], "--output") == 0)) && i + 1 <= argc) {
if(outputFile != NULL) {
printf("More than 1 input file given.\n");
return -1;
}
outputFile = calloc(strlen(argv[i + 1]), sizeof(char));
strncpy(outputFile, argv[i + 1], strlen(argv[i + 1]));
}
}
if(inputFile == NULL) {
printf("Input file not specified\n");
return -1;
}
if(outputFile == NULL) {
printf("Output file not specified\n");
return -1;
}
uint8_t *image_data = NULL;
int width;
int height;
readJPEGfile(inputFile, &image_data, &width, &height);
for(int i = 1; i < argc; i++) {
if((strcmp(argv[i], "-sc") == 0) || (strcmp(argv[i], "--switch-channel") == 0)) {
if(argc - 1 < i + 1) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
if(strlen(argv[i + 1]) != 3) {
printf("switch channel param can only be 3 letters long\nDBG: %s\n", argv[i+1]);
free(image_data);
return -1;
}
if(
((argv[i + 1][0] == argv[i + 1][1]) || (argv[i + 1][0] == argv[i + 1][2])) ||
((argv[i + 1][1] == argv[i + 1][0]) || (argv[i + 1][0] == argv[i + 1][2])) ||
((argv[i + 1][2] == argv[i + 1][0]) || (argv[i + 1][0] == argv[i + 1][1]))
) {
printf("umm, only one r/g/b for arg\n");
free(image_data);
return -1;
}
int permutation[3] = {
((argv[i+1][0] == 'r') || (argv[i+1][0] == 'R')) ? IDX_RED : ((argv[i+1][0] == 'g') || (argv[i+1][0] == 'G')) ? IDX_GREEN : IDX_BLUE,
((argv[i+1][1] == 'r') || (argv[i+1][1] == 'R')) ? IDX_RED : ((argv[i+1][1] == 'g') || (argv[i+1][1] == 'G')) ? IDX_GREEN : IDX_BLUE,
((argv[i+1][2] == 'r') || (argv[i+1][2] == 'R')) ? IDX_RED : ((argv[i+1][2] == 'g') || (argv[i+1][2] == 'G')) ? IDX_GREEN : IDX_BLUE
};
switchChannels(image_data, width, height, permutation);
i++;
continue;
}
if((strcmp(argv[i], "-cb") == 0) || (strcmp(argv[i], "--change-brightness") == 0)) {
if(argc - 1 < i + 3) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
if(!(isNumber(argv[i + 1]) && isNumber(argv[i + 2]) && isNumber(argv[i + 3]))) {
printf("-cb params must be intigers.\n");
free(image_data);
return -1;
}
changeChannelsBrightness(image_data, width, height, atoi(argv[i + 1]), atoi(argv[i + 2]), atoi(argv[i + 3]));
continue;
}
if((strcmp(argv[i], "-gs") == 0) || (strcmp(argv[i], "--gray-scale") == 0)) {
grayScaleChannles(image_data, width, height);
}
if((strcmp(argv[i], "-ci") == 0) || (strcmp(argv[i], "--channel-invert") == 0)) {
if(argc - 1 < i + 3) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
channelsInvert(image_data, width, height, strcmp(argv[i + 1], "1") == 0, strcmp(argv[i + 2], "1") == 0, strcmp(argv[i + 3], "1") == 0);
i+=3;
continue;
}
if((strcmp(argv[i], "-cc") == 0) || (strcmp(argv[i], "--channel-contrast") == 0)) {
if(argc - 1 < i + 6) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
if(!(isNumber(argv[i + 1]) && isNumber(argv[i + 3]) && isNumber(argv[i + 5]))) {
printf("Midpoint values must be intigers.\n");
free(image_data);
return -1;
} else {
if(atoi(argv[i + 1]) < 0 && atoi(argv[i + 3]) < 0 && atoi(argv[i + 5]) < 0) {
printf("Midpoint values must be Positive.\n");
free(image_data);
return -1;
}
}
if(!(isFloat(argv[i + 2]) && !isFloat(argv[i + 4]) && !isFloat(argv[i + 6]))) {
printf("Contrast factors must be floats.\n");
free(image_data);
return -1;
}
channelsAdjustControast(image_data, width, height, atoi(argv[i + 1]), atof(argv[i + 3]), atoi(argv[i + 5]), atof(argv[i + 2]), atoi(argv[i + 4]), atof(argv[i + 6]));
i+=6;
continue;
}
if((strcmp(argv[i], "-ic") == 0) || (strcmp(argv[i], "--isolate-channel") == 0)) {
if(argc - 1 < i + 3) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
isolateChannels(image_data, width, height, strcmp(argv[i + 1], "0") == 0, strcmp(argv[i + 2], "0") == 0, strcmp(argv[i + 3], "0") == 0);
i+=3;
continue;
}
if((strcmp(argv[i], "-cp") == 0) || (strcmp(argv[i], "--copy-channel") == 0)) {
if(argc - 1 < i + 1) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
if(strlen(argv[i + 1]) != 3) {
printf("switch channel param can only be 3 letters long\nDBG: %s\n", argv[i+1]);
free(image_data);
return -1;
}
int copyMode[3] = {
((argv[i+1][0] == 'r') || (argv[i+1][0] == 'R')) ? IDX_RED : ((argv[i+1][0] == 'g') || (argv[i+1][0] == 'G')) ? IDX_GREEN : IDX_BLUE,
((argv[i+1][1] == 'r') || (argv[i+1][1] == 'R')) ? IDX_RED : ((argv[i+1][1] == 'g') || (argv[i+1][1] == 'G')) ? IDX_GREEN : IDX_BLUE,
((argv[i+1][2] == 'r') || (argv[i+1][2] == 'R')) ? IDX_RED : ((argv[i+1][2] == 'g') || (argv[i+1][2] == 'G')) ? IDX_GREEN : IDX_BLUE
};
copyChannels(image_data, width, height, copyMode);
i++;
continue;
}
if((strcmp(argv[i], "-mc") == 0) || (strcmp(argv[i], "--multiply-channel") == 0)) {
if(argc - 1 < i + 3) {
printf("Not enough params.\n");
free(image_data);
return -1;
}
if(!(isFloat(argv[i + 1]) && isFloat(argv[i + 2]) && isFloat(argv[i + 3]))) {
printf("Numbers for multipliy must be floats.\n");
free(image_data);
return -1;
}
multiplyChannels(image_data, width, height, atof(argv[i + 1]), atof(argv[i + 2]), atof(argv[i + 3]));
i+=3;
continue;
}
}
writeJPEGfile(outputFile, 100, image_data, width, height);
printf("DONE\n");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment