-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconverter.c
156 lines (117 loc) · 5.15 KB
/
converter.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdio.h>
#include <time.h>
#define CHARACTERS_NUMBER 8
// The ASCII characters to be printed
const char CHARACTERS[CHARACTERS_NUMBER] = {' ', '.', '-', '*', 'o', 'O', '#', '@'};
// Maximum value of the RGB channels
const unsigned char MAX_CHANNEL_INTENSITY = 255;
const unsigned short MAX_CHANNEL_VALUES = MAX_CHANNEL_INTENSITY * 3; // 3 is the number of channels of a Pixel (red, green, blue)
// Some useful type definitions
// Pixel is an array of bytes (unsigned char) representing the RGB values of a pixel
typedef unsigned char* Pixel;
// Associates a pixel intensity to an ASCII character
char mapIntensityToCharacter(float intensity) {
return CHARACTERS[(int) roundf(intensity * (CHARACTERS_NUMBER - 1))];
}
// Converts a pixel to a floating point intensity
float getPixelIntensity(Pixel pixel) {
return (float) (pixel[0] + pixel[1] + pixel[2]) / MAX_CHANNEL_VALUES;
}
PyObject* fast_print(PyObject* self, PyObject* args)
{
// Get the starting time in nanoseconds
struct timespec start, end;
timespec_get(&start, TIME_UTC);
// Declare the arguments from Python
const char* frame;
unsigned int base_delay;
// Parse the arguments from Python
PyArg_ParseTuple(args, "sI", &frame, &base_delay);
// Clear screen
printf("\033[H\033[J");
// Print the frame to the console
printf("%s", frame);
// Get the end time in nanoseconds
timespec_get(&end, TIME_UTC);
// Calculate the difference between the two times
long int diff = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec);
// Calculate the delay to sleep
long int delay = base_delay - diff;
// Sleep for the specified amount of time to achieve the desired frame rate
if (delay > 0) {
nanosleep((const struct timespec[]){{0, delay}}, NULL);
}
// Return a None object to Python, since the function doesn't return anything
Py_RETURN_NONE;
}
PyObject* fast_convert_frame(PyObject* self, PyObject* args)
{
// Declare the arguments that will be passed by Python
PyBytesObject* frameBytes;
unsigned short frameWidth;
unsigned short frameHeight;
// Parse the arguments
if (!PyArg_ParseTuple(args, "SHH", &frameBytes, &frameWidth, &frameHeight)) {
return NULL;
}
// Get the frame data
Py_buffer pyBuffer;
if (PyObject_GetBuffer((PyObject*) frameBytes, &pyBuffer, PyBUF_READ) < 0) {
printf("Error getting buffer from frame\n");
return NULL;
}
const unsigned char* frameBuffer = pyBuffer.buf;
// Make space for the extra space after every ASCII character
const unsigned short STRING_WIDTH = frameWidth * 2;
// Each pixel is composed of 3 channels
const unsigned short BUFFER_WIDTH = frameWidth * 3;
// Create the string to store the frame that will be returned
char string[frameHeight * STRING_WIDTH];
// Iterate over the rows of the frame and convert them to ASCII
unsigned int yString = 0;
for (unsigned short y = 0; y < frameHeight; y++, yString += STRING_WIDTH) {
for (unsigned short xBuffer = 0, xString = 0; xString < STRING_WIDTH; xBuffer+=3, xString+=2) {
// Calculate the position of the pixel in the frame
const unsigned int framePosition = y * BUFFER_WIDTH + xBuffer;
// Calculate the intensity of the pixel by its RGB values
const float intensity = getPixelIntensity((Pixel) &frameBuffer[framePosition]);
// Convert the intensity to a character using the CHARAACTERS lookup table
const char character = mapIntensityToCharacter(intensity);
// Store the character in the string
string[yString + xString] = character;
// Add a space after the character to fix the console aspect ratio
string[yString + xString + 1] = ' ';
}
// Add a new line after each row
string[yString + STRING_WIDTH - 1] = '\n';
}
// Add the null termination character at the end of the string
string[frameHeight * STRING_WIDTH - 1] = '\0';
// Release the previously allocated buffer to avoid memory leaks
PyBuffer_Release(&pyBuffer);
// return a Python string representing the frame
return Py_BuildValue("s", string);
}
// Define the functions that will be exported to Python
PyMethodDef module_methods[] =
{
{"fast_print", fast_print, METH_VARARGS, "Print a string"},
{"fast_convert_frame", fast_convert_frame, METH_VARARGS, "Convert a frame into a string"},
{NULL} // this struct signals the end of the array
};
// Struct representing the module
struct PyModuleDef c_module =
{
PyModuleDef_HEAD_INIT, // PyModuleDef_HEAD_INIT should always be the first element of the struct
"c_converter", // Module name
"Rapidly convert and print a frame to the console", // module description
-1, // Module size (https://docs.python.org/3/extending/extending.html)
module_methods // Methods associated with the module
};
// The initialization function that will be called when the module is loaded
PyMODINIT_FUNC PyInit_c_converter()
{
return PyModule_Create(&c_module);
}