Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BasedOnStyle: LLVM
UseTab: Always
TabWidth: 4
IndentWidth: 4
ColumnLimit: 100
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
12 changes: 12 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Checks: >
clang-diagnostic-*,
clang-analyzer-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
performance-*,
readability-*,
-readability-magic-numbers,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-braces-around-statements
HeaderFilterRegex: '^(src|include)/.*'
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: C CI

on:
push:
pull_request:
branches: [ "main" ]

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential clang libcmocka-dev
sudo apt-get install -y build-essential clang clang-format clang-tidy libcmocka-dev bear

- name: Build
run: make CC=gcc

- name: Run tests
run: make test

- name: Check code formatting
run: |
find src/ -name '*.c' -o -name '*.h' | xargs clang-format --dry-run -Werror

- name: Generate compile_commands.json
run: bear -- make

- name: Static analysis
run: |
find src/ -name '*.c' | xargs clang-tidy --quiet
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -O2 -D_POSIX_C_SOURCE=199309L \
-Iinclude -Ithird_party/stb
LDFLAGS = -lm
TARGET = conv
VPATH = src src/lib
SRCS = main.c conv.c cli.c filters.c

OBJS = $(SRCS:.c=.o)

LIB_OBJS = conv.o cli.o filters.o
TEST_SRC = tests/test_conv.c
TEST_BIN = test_runner

all: $(TARGET)

$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@

test: $(TEST_BIN)
./$(TEST_BIN)

$(TEST_BIN): $(LIB_OBJS) $(TEST_SRC)
$(CC) -std=c11 -Wall -Wextra -O0 -g -Iinclude -Ithird_party/stb \
$^ -o $@ -lm -lcmocka

clean:
rm -f $(TARGET) $(OBJS) $(TEST_BIN)

.PHONY: all test clean
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Image Convolution — Task 1: Sequential

Simple CLI tool for applying convolution filters images.

## Build & Run

```bash
make # build
./conv in.bmp out.bmp --filter sharpen # apply filter
make test # run tests
```

## Usage

```bash
./conv <input> <output> --filter <name> [--repeat=N]
```

**Filters**: identity, blur, gaussian, sharpen, edges, emboss

**Formats**: BMP, PNG, JPEG

## Example

```bash
./conv photo.jpg result.png --filter edges
```

## Performance (sequential, white_cat.jpeg, avg over ×100 runs)

| Filter | Kernel | Time (ms) |
| -------- | ------ | --------- |
| sharpen | 3×3 | 30.7 |
| sharpen5 | 5×5 | 75.9 |
| sharpen7 | 7×7 | 144.6 |
| sharpen9 | 9×9 | 235.6 |

Runtime scales roughly as kernel area (k²): 3×3→9×9 gives ~7.7× slowdown vs theoretical 9×.
Single runs show up to 30% variance; repeated runs stabilize within ~2%
Binary file added images/input/Car.bmp
Binary file not shown.
Binary file added images/input/cat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/input/cat_cry.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/input/elbrus.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/input/white_cat.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/output/blur.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/output/edges.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/output/emboss.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/output/gaussian.bmp
Binary file not shown.
Binary file added images/output/sharpen.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions include/cli.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once
#include "conv.h"

typedef struct {
const char *input_path;
const char *output_path;
const char *filter_name;
} CliConfig;

int cli_parse(int argc, char *argv[], CliConfig *cfg);
void cli_print_help(const char *prog_name);
14 changes: 14 additions & 0 deletions include/conv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include "filters.h"
#include <stddef.h>
#include <stdint.h>

typedef struct {
uint8_t *pixels;
size_t width;
size_t height;
int channels;
} Image;

void convolve_sequential(const Image *input, Image *output, const struct Filter *filter);
47 changes: 47 additions & 0 deletions include/filters.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include <stddef.h>

struct Filter {
size_t size;
double *data;
double factor;
double bias;
};

/* 3x3 filters */
struct Filter filter_identity(void);
struct Filter filter_blur(void);
struct Filter filter_gaussian(void);
struct Filter filter_sharpen(void);
struct Filter filter_edges(void);
struct Filter filter_emboss(void);

/* 5x5 filters */
struct Filter filter_blur5(void);
struct Filter filter_gaussian5(void);
struct Filter filter_sharpen5(void);
struct Filter filter_edges5(void);
struct Filter filter_emboss5(void);

/* 7x7 filters */
struct Filter filter_blur7(void);
struct Filter filter_gaussian7(void);
struct Filter filter_sharpen7(void);
struct Filter filter_edges7(void);
struct Filter filter_emboss7(void);

/* 9x9 filters */
struct Filter filter_blur9(void);
struct Filter filter_gaussian9(void);
struct Filter filter_sharpen9(void);
struct Filter filter_edges9(void);
struct Filter filter_emboss9(void);

typedef struct {
const char *name;
struct Filter (*create)(void);
} FilterDef;

extern const FilterDef filter_list[];
extern const size_t filter_count;
57 changes: 57 additions & 0 deletions src/lib/cli.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "cli.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void cli_print_help(const char *prog) {
printf("Usage: %s <input> <output> --filter <name>\n"
"Arguments:\n"
" input Input image (BMP/PNG/JPG)\n"
" output Output image (BMP/PNG/JPG)\n"
"Options:\n"
" --filter <name> Filter: identity, blur, gaussian, sharpen, edges, emboss\n"
"Add suffix 5/7/9 for larger kernels (e.g., emboss9)\n"
" -h, --help Show this help\n"
"Example:\n"
" %s photo.bmp out.bmp --filter edges5 \n",
prog, prog);
}

int cli_parse(int argc, char *argv[], CliConfig *cfg) {
cfg->input_path = NULL;
cfg->output_path = NULL;
cfg->filter_name = NULL;
int pos = 0;

for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
cli_print_help(argv[0]);
return 0;
}
if (strcmp(argv[i], "--filter") == 0) {
if (++i < argc)
cfg->filter_name = argv[i];
else {
fprintf(stderr, "Error: --filter requires a value\n");
return 0;
}
continue;
}
if (pos == 0)
cfg->input_path = argv[i];
else if (pos == 1)
cfg->output_path = argv[i];
else {
fprintf(stderr, "Error: too many positional arguments\n");
return 0;
}
pos++;
}

if (!cfg->input_path || !cfg->output_path || !cfg->filter_name) {
fprintf(stderr, "Error: missing required arguments\n");
cli_print_help(argv[0]);
return 0;
}
return 1;
}
44 changes: 44 additions & 0 deletions src/lib/conv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "conv.h"
#include <stddef.h>
#include <stdint.h>

#define PIXEL_MIN 0
#define PIXEL_MAX 255
#define ROUNDING_OFFSET 0.5

void convolve_sequential(const Image *input, Image *output, const struct Filter *filter) {
const int radius = (int)(filter->size / 2);
const double factor = filter->factor;
const double bias = filter->bias;
const double *kernel = filter->data;

const size_t width = input->width;
const size_t height = input->height;
const int channels = input->channels;
const size_t stride = width * channels;

for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < width; ++x) {
for (int c = 0; c < channels; ++c) {
double acc = 0.0;
for (int fy = 0; fy < (int)filter->size; ++fy) {
for (int fx = 0; fx < (int)filter->size; ++fx) {
int sy = (int)y + fy - radius;
int sx = (int)x + fx - radius;
sy %= (int)height;
if (sy < 0) sy += (int)height;
sx %= (int)width;
if (sx < 0) sx += (int)width;
double pixel =
input->pixels[(size_t)sy * stride + (size_t)sx * channels + (size_t)c];
acc += kernel[fy * filter->size + fx] * pixel;
}
}
double value = acc * factor + bias;
if (value < PIXEL_MIN) value = PIXEL_MIN;
if (value > PIXEL_MAX) value = PIXEL_MAX;
output->pixels[y * stride + x * channels + c] = (uint8_t)(value + ROUNDING_OFFSET);
}
}
}
}
Loading
Loading