Created
July 19, 2024 09:13
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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