├── .env ├── .envrc ├── .gitignore ├── Makefile ├── README.md ├── ascii.c ├── ascii.h ├── aspect_ratio.c ├── aspect_ratio.h ├── bin └── .gitkeep ├── build └── .gitkeep ├── client.c ├── client.h ├── example.envrc ├── ext └── fmemopen │ ├── fmemopen.c │ └── fmemopen.h ├── image.c ├── image.h ├── options.c ├── options.h ├── round.h ├── server.c ├── webcam.cpp └── webcam.hpp /.env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | if [[ `uname -s 2>/dev/null` == Darwin ]]; then 4 | #export PATH="/usr/local/opt/findutils/libexec/gnubin:$PATH" 5 | #export PATH="/usr/local/opt/opencv@2/bin:$PATH" 6 | 7 | #export LDFLAGS="-L/usr/local/opt/opencv@2/lib" 8 | #export CPPFLAGS="-I/usr/local/opt/opencv@2/include" 9 | 10 | export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/opt/opencv@2/lib/pkgconfig" 11 | fi 12 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | if [[ `uname -s 2>/dev/null` == Darwin ]]; then 4 | #export PATH="/usr/local/opt/findutils/libexec/gnubin:$PATH" 5 | #export PATH="/usr/local/opt/opencv@2/bin:$PATH" 6 | 7 | #export LDFLAGS="-L/usr/local/opt/opencv@2/lib" 8 | #export CPPFLAGS="-I/usr/local/opt/opencv@2/include" 9 | 10 | export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/opt/opencv@2/lib/pkgconfig" 11 | fi 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /build/* 3 | /compile_commands.json 4 | .env 5 | /webcam.cpp.tu-* 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | CC := clang 4 | CXX := clang++ 5 | PKG_CONFIG_LIBS := opencv libjpeg 6 | 7 | BIN_D = bin 8 | OUT_D = build 9 | 10 | CFLAGS += -DUSE_CLANG_COMPLETER -Wall -Wextra #-Wl,--no-as-needed 11 | CXXFLAGS += $(CFLAGS) 12 | 13 | PKG_CFLAGS := $(shell pkg-config --cflags $(PKG_CONFIG_LIBS)) 14 | CFLAGS += $(filter-out --std=c99, ${PKG_CFLAGS}) 15 | CXXFLAGS += $(filter-out --std-c++11, ${PKG_CFLAGS}) 16 | CFLAGS += -x c -std=c11 17 | CXXFLAGS += -x c++ -std=c++11 -stdlib=libc++ 18 | 19 | LDFLAGS += -lstdc++ 20 | LDFLAGS += $(shell pkg-config --libs --static $(PKG_CONFIG_LIBS)) 21 | 22 | TARGETS = $(addprefix $(BIN_D)/, server client) 23 | 24 | OBJS_C = $(patsubst %.c, $(OUT_D)/%.o, $(wildcard *.c)) 25 | OBJS_CPP = $(patsubst %.cpp, $(OUT_D)/%.o, $(wildcard *.cpp)) 26 | OBJS_CEXT = $(patsubst %.c, $(OUT_D)/%.o, $(wildcard $(addprefix ext/, $(EXT_CDEPS))/*.c)) 27 | 28 | OBJS = $(OBJS_C) $(OBJS_CPP) $(OBJS_CEXT) 29 | OBJS_ = $(filter-out $(patsubst $(BIN_D)/%, $(OUT_D)/%.o, $(TARGETS)), $(OBJS)) 30 | # ^ non-targets; files without main methods 31 | 32 | HEADERS_C = $(wildcard *.h) 33 | HEADERS_CPP = $(wildcard *.hpp) 34 | HEADERS_CEXT = $(wildcard $(addprefix ext/, $(EXT_CDEPS))/*.h) 35 | 36 | HEADERS = $(HEADERS_C) $(HEADERS_CPP) $(HEADERS_CEXT) 37 | 38 | # util 39 | RM = /bin/rm 40 | 41 | 42 | .DEFAULT: default 43 | .PHONY: all clean 44 | 45 | .PRECIOUS: $(OBJS_) 46 | 47 | 48 | default: $(TARGETS) 49 | 50 | all: default 51 | 52 | 53 | $(TARGETS): $(BIN_D)/%: $(OUT_D)/%.o $(OBJS_) 54 | $(CXX) -o $@ \ 55 | $(LDFLAGS) \ 56 | $? 57 | 58 | $(OBJS_C): $(OUT_D)/%.o: %.c $(HEADERS) 59 | $(CC) -c $< -o $@ \ 60 | $(CFLAGS) 61 | 62 | $(OBJS_CPP): $(OUT_D)/%.o: %.cpp $(HEADERS) 63 | $(CXX) -c $< -o $@ \ 64 | $(CXXFLAGS) 65 | 66 | $(OBJS_CEXT): $(OUT_D)/%.o: %.c $(HEADERS_CEXT) 67 | @mkdir -p $(dir $@) 68 | $(CC) -c $< -o $@ \ 69 | $(CFLAGS) 70 | 71 | clean: 72 | @echo 'cleaning...' 73 | @printf '\t' 74 | @find $(OUT_D) -mindepth 1 \ 75 | -type f -not -iname '.gitkeep' \ 76 | -printf '%P ' -delete 77 | @find $(BIN_D) -mindepth 1 \ 78 | -type f -not -iname '.gitkeep' \ 79 | -printf '%P ' -delete 80 | @printf '\n' 81 | @echo 'done!' 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ascii-chat 2 | ========== 3 | 4 | ASCII video chat. 5 | 6 | Probably the first command line video chat progam. 7 | 8 | It just prints ASCII, so it works on your rxvt-unicode in OpenBox, a Putty SSH session, and even iTerm on OSX. 9 | It even works in an initial UNIX login shell, i.e. the login shell that runs 'startx'. 10 | 11 | Eventually it will support 3+ simultaneous people, 'google-hangouts' style, and sound via PulseAudio or something. 12 | 13 | ![Animated demonstration](http://i.imgur.com/E4OuqvX.gif) 14 | 15 | 16 | Dependencies 17 | ========== 18 | - Most people: `apt-get install clang libopencv-dev libjpeg-dev` 19 | - ArchLinux masterrace: `pacman -S clang opencv libjpeg-turbo` 20 | - MacOS: `brew install opencv@2 jpeg findutils` 21 | - `cp .macos.env .env` 22 | - `source .env` 23 | 24 | NOTE: I recommend using `direnv` with a `.envrc` file and sourcing the .env file from there 25 | 26 | 27 | Build and run 28 | ========== 29 | - Clone this repo onto a computer with a webcam. 30 | - Install the dependencies. 31 | - run `make`. 32 | - run `./bin/server -p 9001` in one terminal, and then 33 | - run `./bin/client -p 9001 -a 127.0.0.1` in another. 34 | 35 | NOTE: run `./bin/server -h` to see options 36 | 37 | 38 | TODO 39 | ========== 40 | - Client should continuously attempt to reconnect 41 | - Client program should accept URL arguments, as well as IP addresses like it does now 42 | - Colorize ASCII output 43 | - Refactor image processing algorithms 44 | - Client should gracefully handle `frame width > term width` 45 | - Client should gracefully handle `term resize` event 46 | - Rewrite entire thing in Rust! 47 | - Compile to WASM/WASI. 48 | 49 | 50 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/zfogg/ascii-chat/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 51 | -------------------------------------------------------------------------------- /ascii.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "webcam.hpp" 7 | 8 | #include "ascii.h" 9 | #include "image.h" 10 | #include "options.h" 11 | 12 | 13 | void ascii_read_init(unsigned short int webcam_index) { 14 | webcam_init(webcam_index); 15 | } 16 | 17 | void ascii_write_init() { 18 | console_clear(); 19 | cursor_reset(); 20 | cursor_hide(); 21 | } 22 | 23 | char *ascii_read() { 24 | FILE *jpeg = webcam_read(); 25 | 26 | image_t *original = image_read(jpeg), 27 | *resized = image_new(opt_width, opt_height); 28 | 29 | fclose(jpeg); 30 | 31 | image_clear(resized); 32 | image_resize(original, resized); 33 | 34 | char *ascii = image_print(resized); 35 | 36 | image_destroy(original); 37 | image_destroy(resized); 38 | 39 | return ascii; 40 | } 41 | 42 | void ascii_write(char *f) { 43 | for (int i = 0; f[i] != '\0'; ++i) 44 | if (f[i] == '\t') 45 | cursor_reset(); 46 | else 47 | putchar(f[i]); 48 | ascii_zzz(); 49 | } 50 | 51 | void ascii_write_destroy() { 52 | console_clear(); 53 | cursor_reset(); 54 | cursor_show(); 55 | } 56 | 57 | void ascii_read_destroy() { 58 | console_clear(); 59 | cursor_reset(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /ascii.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | void ascii_read_init(unsigned short int webcam_index); 6 | void ascii_write_init(); 7 | 8 | char *ascii_read(); 9 | void ascii_write(char *); 10 | 11 | void ascii_read_destroy(); 12 | void ascii_write_destroy(); 13 | 14 | /*static char *from_jpeg(FILE *);*/ 15 | 16 | 17 | #define ASCII_DELIMITER '\t' 18 | 19 | #define ASCII_SLEEP_NS 50000L 20 | 21 | 22 | #define print(s) fwrite(s, 1, sizeof(s)/sizeof(s[0]), stdout) 23 | 24 | #define console_clear() print("\e[1;1H\e[2J") 25 | 26 | #define cursor_reset() print("\033[0;0H") 27 | 28 | #define cursor_hide() print("\e[?25l") 29 | 30 | #define cursor_show() print("\e[?25h") 31 | 32 | 33 | static const struct timespec 34 | ASCII_SLEEP_START = { 35 | .tv_sec = 0, 36 | .tv_nsec = 500 37 | }, 38 | ASCII_SLEEP_STOP = { 39 | .tv_sec = 0, 40 | .tv_nsec = 0 41 | }; 42 | 43 | #define ascii_zzz() nanosleep( \ 44 | (struct timespec *)&ASCII_SLEEP_START, \ 45 | (struct timespec *)&ASCII_SLEEP_STOP) 46 | -------------------------------------------------------------------------------- /aspect_ratio.c: -------------------------------------------------------------------------------- 1 | #include "options.h" 2 | #include "round.h" 3 | 4 | 5 | // Calculate width or height, but not both 6 | void aspect_ratio(const int jpeg_w, const int jpeg_h) { 7 | 8 | // The 2.0f and 0.5f factors are used for text displays that (usually) 9 | // have characters that are taller than they are wide. 10 | #define CALC_W ROUND(2.0f * (float) opt_height * (float) jpeg_w / (float) jpeg_h) 11 | #define CALC_H ROUND(0.5f * (float) opt_width * (float) jpeg_h / (float) jpeg_w) 12 | 13 | // calc width 14 | if (auto_width && !auto_height) { 15 | opt_width = CALC_W; 16 | 17 | // adjust for too small dimensions 18 | while (opt_width == 0) { 19 | ++opt_height; 20 | aspect_ratio(jpeg_w, jpeg_h); 21 | } 22 | } 23 | 24 | // calc height 25 | if ( !auto_width && auto_height ) { 26 | opt_height = CALC_H; 27 | // adjust for too small dimensions 28 | while (opt_height == 0) { 29 | ++opt_width; 30 | aspect_ratio(jpeg_w, jpeg_h); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /aspect_ratio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 Christian Stigen Larsen, http://csl.sublevel3.org 3 | * Distributed under the GNU General Public License (GPL) v2. 4 | * 5 | * Project homepage on http://jp2a.sf.net 6 | * 7 | * $Id: aspect_ratio.c 466 2006-10-02 11:35:03Z csl $ 8 | */ 9 | 10 | void aspect_ratio(const int jpeg_width, const int jpeg_height); 11 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfogg/ascii-chat/6433b31543ecc7f95ea38849c9d83ee800874b04/bin/.gitkeep -------------------------------------------------------------------------------- /build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfogg/ascii-chat/6433b31543ecc7f95ea38849c9d83ee800874b04/build/.gitkeep -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "ascii.h" 16 | #include "client.h" 17 | #include "options.h" 18 | 19 | 20 | int sockfd = 0; 21 | 22 | void sigint_handler(int sigint) { 23 | (void) (sigint); 24 | ascii_write_destroy(); 25 | printf("Closing connection and exiting . . .\n"); 26 | close(sockfd); 27 | exit(0); 28 | } 29 | 30 | 31 | int main(int argc, char *argv[]) { 32 | options_init(argc, argv); 33 | char *address = opt_address; 34 | int port = strtoint(opt_port); 35 | 36 | char recvBuff[40000]; 37 | struct sockaddr_in serv_addr; 38 | 39 | struct timespec 40 | sleep_start = { 41 | .tv_sec = 3, 42 | .tv_nsec = 0 43 | }, 44 | sleep_stop = { 45 | .tv_sec = 0, 46 | .tv_nsec = 0 47 | }; 48 | 49 | // Cleanup nicely on Ctrl+C. 50 | signal(SIGINT, sigint_handler); 51 | 52 | /* read from the socket as long as the size of the read is > 0 */ 53 | while(1) { 54 | // try to open a socket 55 | memset(recvBuff, '0',sizeof(recvBuff)); 56 | // error creating socket 57 | if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 58 | printf("\n Error: could not create socket \n"); 59 | return 1; 60 | } 61 | 62 | // reserve memory space to store IP address 63 | memset(&serv_addr, '0', sizeof(serv_addr)); 64 | 65 | // set type of address to IPV4 and port to 5000 66 | printf("Connecting on port %d\n", port); 67 | 68 | serv_addr.sin_family = AF_INET; 69 | serv_addr.sin_port = htons(port); 70 | 71 | // an error occurred when trying to set server address and port number 72 | if(inet_pton(AF_INET, address, &serv_addr.sin_addr) <= 0) { 73 | printf("\n Error: inet_pton \n"); 74 | return 1; 75 | } 76 | 77 | int read_result = 0; 78 | ascii_write_init(); 79 | 80 | printf("Attempting to connect...\n"); 81 | // failed when trying to connect to the server 82 | if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { 83 | printf("\n Error: connect failed \n"); 84 | } 85 | 86 | while (0 < (read_result = read(sockfd, recvBuff, sizeof(recvBuff)-1))) { 87 | recvBuff[read_result] = 0; 88 | ascii_write(recvBuff); 89 | } 90 | 91 | console_clear(); 92 | 93 | nanosleep((struct timespec *)&sleep_start,(struct timespec *)&sleep_stop); 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | void sigint_handler(int sig_no); 2 | 3 | -------------------------------------------------------------------------------- /example.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ `uname -s 2>/dev/null` == Darwin ]]; then 4 | export PATH="/usr/local/opt/opencv@2/bin:$PATH" 5 | export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/opt/opencv@2/lib/pkgconfig" 6 | fi 7 | -------------------------------------------------------------------------------- /ext/fmemopen/fmemopen.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // Originally ported from https://github.com/ingenuitas/python-tesseract/blob/master/fmemopen.c 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "fmemopen.h" 23 | 24 | struct fmem { 25 | size_t pos; 26 | size_t size; 27 | char *buffer; 28 | }; 29 | typedef struct fmem fmem_t; 30 | 31 | static int readfn(void *handler, char *buf, int size) { 32 | fmem_t *mem = handler; 33 | size_t available = mem->size - mem->pos; 34 | 35 | if ((size_t)size > available) { 36 | size = available; 37 | } 38 | memcpy(buf, mem->buffer + mem->pos, sizeof(char) * size); 39 | mem->pos += size; 40 | 41 | return size; 42 | } 43 | 44 | static int writefn(void *handler, const char *buf, int size) { 45 | fmem_t *mem = handler; 46 | size_t available = mem->size - mem->pos; 47 | 48 | if ((size_t)size > available) { 49 | size = available; 50 | } 51 | memcpy(mem->buffer + mem->pos, buf, sizeof(char) * size); 52 | mem->pos += size; 53 | 54 | return size; 55 | } 56 | 57 | static fpos_t seekfn(void *handler, fpos_t offset, int whence) { 58 | size_t pos; 59 | fmem_t *mem = handler; 60 | 61 | switch (whence) { 62 | case SEEK_SET: { 63 | if (offset >= 0) { 64 | pos = (size_t)offset; 65 | } else { 66 | pos = 0; 67 | } 68 | break; 69 | } 70 | case SEEK_CUR: { 71 | if (offset >= 0 || (size_t)(-offset) <= mem->pos) { 72 | pos = mem->pos + (size_t)offset; 73 | } else { 74 | pos = 0; 75 | } 76 | break; 77 | } 78 | case SEEK_END: pos = mem->size + (size_t)offset; break; 79 | default: return -1; 80 | } 81 | 82 | if (pos > mem->size) { 83 | return -1; 84 | } 85 | 86 | mem->pos = pos; 87 | return (fpos_t)pos; 88 | } 89 | 90 | static int closefn(void *handler) { 91 | free(handler); 92 | return 0; 93 | } 94 | 95 | FILE *fmemopen(void *buf, size_t size, const char *mode) { 96 | // This data is released on fclose. 97 | fmem_t* mem = (fmem_t *) malloc(sizeof(fmem_t)); 98 | 99 | // Zero-out the structure. 100 | memset(mem, 0, sizeof(fmem_t)); 101 | 102 | mem->size = size; 103 | mem->buffer = buf; 104 | 105 | // funopen's man page: https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/funopen.3.html 106 | return funopen(mem, readfn, writefn, seekfn, closefn); 107 | } 108 | -------------------------------------------------------------------------------- /ext/fmemopen/fmemopen.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2011-2014 NimbusKit 3 | // Originally ported from https://github.com/ingenuitas/python-tesseract/blob/master/fmemopen.c 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | #ifndef FMEMOPEN_H_ 19 | #define FMEMOPEN_H_ 20 | 21 | #if defined __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | #include 26 | 27 | /** 28 | * A BSD port of the fmemopen Linux method using funopen. 29 | * 30 | * man docs for fmemopen: 31 | * http://linux.die.net/man/3/fmemopen 32 | * 33 | * man docs for funopen: 34 | * https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/funopen.3.html 35 | * 36 | * This method is ported from ingenuitas' python-tesseract project. 37 | * 38 | * You must call fclose on the returned file pointer or memory will be leaked. 39 | * 40 | * @param buf The data that will be used to back the FILE* methods. Must be at least 41 | * @c size bytes. 42 | * @param size The size of the @c buf data. 43 | * @param mode The permitted stream operation modes. 44 | * @return A pointer that can be used in the fread/fwrite/fseek/fclose family of methods. 45 | * If a failure occurred NULL will be returned. 46 | * @ingroup NimbusMemoryMappping 47 | */ 48 | FILE *fmemopen(void *buf, size_t size, const char *mode); 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif // #ifndef FMEMOPEN_H_ 55 | -------------------------------------------------------------------------------- /image.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ascii.h" 8 | #include "aspect_ratio.h" 9 | #include "image.h" 10 | #include "options.h" 11 | #include "round.h" 12 | 13 | 14 | typedef void (*image_resize_ptrfun)(const image_t* , image_t*); 15 | image_resize_ptrfun global_image_resize_fun = NULL; 16 | 17 | 18 | image_t* image_new(int width, int height) { 19 | image_t *p; 20 | 21 | if (!(p = (image_t *) malloc(sizeof(image_t)))) { 22 | perror("jp2a: coudln't allocate memory for image"); 23 | exit(EXIT_FAILURE); 24 | } 25 | 26 | if (!(p->pixels = (rgb_t *) malloc(width*height*sizeof(rgb_t)))) { 27 | perror("jp2a: couldn't allocate memory for image"); 28 | exit(EXIT_FAILURE); 29 | } 30 | 31 | p->w = width; 32 | p->h = height; 33 | return p; 34 | } 35 | 36 | void image_destroy(image_t *p) { 37 | free(p->pixels); 38 | free(p); 39 | } 40 | 41 | void image_clear(image_t *p) { 42 | memset(p->pixels, 0, p->w*p->h*sizeof(rgb_t)); 43 | } 44 | 45 | inline 46 | rgb_t* image_pixel(image_t *p, const int x, const int y) { 47 | return &p->pixels[x + y*p->w]; 48 | } 49 | 50 | void image_resize(const image_t *s, image_t *d) { 51 | global_image_resize_fun(s, d); 52 | } 53 | 54 | void image_resize_interpolation(const image_t *source, image_t *dest) { 55 | register unsigned int r, g, b; 56 | 57 | const int ynrat = (float)source->h / (float)dest->h; 58 | const int xnrat = (float)source->w / (float)dest->w; 59 | 60 | const int yinc = ynrat*source->w; 61 | const unsigned int adds = xnrat * ynrat; 62 | 63 | register rgb_t* pix = dest->pixels; 64 | register rgb_t* pix_next = pix + dest->w; 65 | 66 | register const rgb_t* samp_end; 67 | register const rgb_t* src = source->pixels; 68 | register const rgb_t* src_end = source->pixels + dest->h*yinc; 69 | 70 | while ( src < src_end ) { 71 | 72 | const rgb_t *sample_start_plus_yinc = src + yinc; 73 | 74 | while ( pix < pix_next ) { 75 | 76 | r = g = b = 0; 77 | samp_end = src + xnrat; 78 | 79 | while ( src < sample_start_plus_yinc ) { 80 | 81 | while ( src < samp_end ) { 82 | r += src->r; 83 | g += src->g; 84 | b += src->b; 85 | ++src; 86 | } 87 | 88 | src += source->w - xnrat; 89 | samp_end += source->w; 90 | } 91 | 92 | pix->r = r/adds; 93 | pix->g = g/adds; 94 | pix->b = b/adds; 95 | 96 | ++pix; 97 | src -= yinc - xnrat; 98 | } 99 | 100 | src = sample_start_plus_yinc; 101 | pix_next += dest->w; 102 | } 103 | } 104 | 105 | image_t *image_read(FILE *fp) { 106 | JSAMPARRAY buffer; 107 | int row_stride; 108 | struct jpeg_decompress_struct jpg; 109 | struct jpeg_error_mgr jerr; 110 | image_t *p; 111 | 112 | global_image_resize_fun = image_resize_interpolation; 113 | 114 | jpg.err = jpeg_std_error(&jerr); 115 | 116 | jpeg_create_decompress(&jpg); 117 | jpeg_stdio_src(&jpg, fp); 118 | jpeg_read_header(&jpg, TRUE); 119 | jpeg_start_decompress(&jpg); 120 | 121 | if (jpg.data_precision != 8) { 122 | fprintf(stderr, "jp2a: can only handle 8-bit color channels\n"); 123 | exit(1); 124 | } 125 | 126 | row_stride = jpg.output_width * jpg.output_components; 127 | buffer = (*jpg.mem->alloc_sarray)((j_common_ptr) &jpg, JPOOL_IMAGE, row_stride, 1); 128 | 129 | aspect_ratio(jpg.output_width, jpg.output_height); 130 | p = image_new(jpg.output_width, jpg.output_height); 131 | 132 | while (jpg.output_scanline < jpg.output_height) { 133 | jpeg_read_scanlines(&jpg, buffer, 1); 134 | 135 | if (jpg.output_components == 3) { 136 | memcpy(&p->pixels[(jpg.output_scanline-1)*p->w], &buffer[0][0], sizeof(rgb_t)*p->w); 137 | } else { 138 | rgb_t *pixels = &p->pixels[(jpg.output_scanline-1) * p->w]; 139 | 140 | // grayscale 141 | for (int x = 0; x < (int)jpg.output_width; ++x) 142 | pixels[x].r = pixels[x].g = pixels[x].b = buffer[0][x]; 143 | } 144 | } 145 | 146 | jpeg_finish_decompress(&jpg); 147 | jpeg_destroy_decompress(&jpg); 148 | return p; 149 | } 150 | 151 | char* get_lum_palette() { 152 | static char palette[256]; 153 | for (int n = 0; n < 256; n++) { 154 | palette[n] = ascii_palette[ROUND( 155 | (float)(strlen(ascii_palette) - 1) * (float)n / (float)MAXJSAMPLE 156 | )]; 157 | } 158 | return palette; 159 | } 160 | 161 | char *image_print(const image_t *p) { 162 | int h = p->h, 163 | w = p->w; 164 | 165 | rgb_t* pix = p->pixels; 166 | char* palette = get_lum_palette(); 167 | 168 | int len = h*w; 169 | char* lines = (char*)malloc((len + 2)*sizeof(char)); 170 | 171 | lines[len ] = ASCII_DELIMITER; 172 | lines[len+1] = '\0'; 173 | 174 | for (int y = 0; y < h; y++) { 175 | for (int x = 0; x < w; x++, pix++) 176 | lines[(y*w)+x] = palette[ 177 | RED[pix->r] + GREEN[pix->g] + BLUE[pix->b] 178 | ]; 179 | lines[(y*w)+w-1] = '\n'; 180 | } 181 | 182 | return lines; 183 | } 184 | -------------------------------------------------------------------------------- /image.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 Christian Stigen Larsen, http://csl.sublevel3.org 3 | * Distributed under the GNU General Public License (GPL) v2. 4 | * 5 | * Project homepage on http://jp2a.sf.net 6 | * 7 | * $Id: image.h 470 2006-10-12 08:13:37Z cslsublevel3org $ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | typedef struct rgb_t { 16 | JSAMPLE r, g, b; 17 | } rgb_t; 18 | 19 | typedef struct image_t { 20 | int w, h; 21 | rgb_t *pixels; 22 | } image_t; 23 | 24 | image_t* image_read(FILE *); 25 | image_t* image_new(int, int); 26 | void image_destroy(image_t *); 27 | void image_clear(image_t *); 28 | char* image_print(const image_t *); 29 | void image_resize(const image_t *, image_t *); 30 | -------------------------------------------------------------------------------- /options.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ascii.h" 8 | #include "options.h" 9 | 10 | 11 | // Default weights; must add up to 1.0 12 | static const float weight_red = 0.2989f; 13 | static const float weight_green = 0.5866f; 14 | static const float weight_blue = 0.1145f; 15 | 16 | unsigned short int opt_width = 80, 17 | opt_height = 0, 18 | 19 | auto_width = 0, 20 | auto_height = 1; 21 | 22 | char opt_address[OPTIONS_BUFF_SIZE] = "0.0.0.0", 23 | opt_port [OPTIONS_BUFF_SIZE] = "9001"; 24 | 25 | unsigned short int opt_webcam_index = 0; 26 | 27 | char ascii_palette[ASCII_PALETTE_SIZE + 1] = 28 | " ...',;:clodxkO0KXNWM"; 29 | 30 | unsigned short int RED [ASCII_PALETTE_SIZE], 31 | GREEN[ASCII_PALETTE_SIZE], 32 | BLUE [ASCII_PALETTE_SIZE], 33 | GRAY [ASCII_PALETTE_SIZE]; 34 | 35 | static struct option long_options[] = { 36 | {"address", required_argument, NULL, 'a'}, 37 | {"port", required_argument, NULL, 'p'}, 38 | {"width", optional_argument, NULL, 'x'}, 39 | {"height", optional_argument, NULL, 'y'}, 40 | {"webcam-index", required_argument, NULL, 'c'}, 41 | {"help", optional_argument, NULL, 'h'}, 42 | {0, 0, 0, 0} 43 | }; 44 | 45 | 46 | void options_init(int argc, char** argv) { 47 | precalc_rgb(weight_red, weight_green, weight_blue); 48 | while (1) { 49 | int index = 0, 50 | c = getopt_long(argc, argv, "a:p:x:y:c:h", long_options, &index); 51 | if (c == -1) 52 | break; 53 | 54 | char argbuf[1024]; 55 | switch (c) { 56 | case 0: 57 | break; 58 | 59 | case 'a': 60 | snprintf(opt_address, OPTIONS_BUFF_SIZE, "%s", optarg); 61 | break; 62 | 63 | case 'p': 64 | snprintf(opt_port, OPTIONS_BUFF_SIZE, "%s", optarg); 65 | break; 66 | 67 | case 'x': 68 | snprintf(argbuf, OPTIONS_BUFF_SIZE, "%s", optarg); 69 | opt_width = strtoint(argbuf); 70 | break; 71 | 72 | case 'y': 73 | snprintf(argbuf, OPTIONS_BUFF_SIZE, "%s", optarg); 74 | opt_height = strtoint(argbuf); 75 | break; 76 | 77 | case 'c': 78 | snprintf(argbuf, OPTIONS_BUFF_SIZE, "%s", optarg); 79 | opt_webcam_index = strtoint(argbuf); 80 | break; 81 | 82 | case '?': 83 | fprintf(stderr, "Unknown option %c\n", optopt); 84 | usage(stderr); 85 | exit(EXIT_FAILURE); 86 | break; 87 | 88 | case 'h': 89 | usage(stdout); 90 | exit(EXIT_SUCCESS); 91 | break; 92 | 93 | default: 94 | abort(); 95 | } 96 | } 97 | } 98 | 99 | void usage(FILE* desc /* stdout|stderr*/ ) { 100 | fprintf(desc, "ascii-chat\n"); 101 | fprintf(desc, "\toptions:\n"); 102 | fprintf(desc, "\t\t -a --address (server|client) \t ip address\n"); 103 | fprintf(desc, "\t\t -p --port (server|client) \t tcp port\n"); 104 | fprintf(desc, "\t\t -x --width (client) \t render width\n"); 105 | fprintf(desc, "\t\t -y --height (client) \t render height\n"); 106 | fprintf(desc, "\t\t -c --webcam-index (server) \t webcam device index\n"); 107 | fprintf(desc, "\t\t -h --help (server|client) \t print this help\n"); 108 | } 109 | 110 | void precalc_rgb(const float red, const float green, const float blue) { 111 | for (int n = 0; n < ASCII_PALETTE_SIZE; ++n) { 112 | RED[n] = ((float) n) * red; 113 | GREEN[n] = ((float) n) * green; 114 | BLUE[n] = ((float) n) * blue; 115 | GRAY[n] = ((float) n); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /options.h: -------------------------------------------------------------------------------- 1 | #define ASCII_PALETTE_SIZE 256 2 | 3 | #define OPTIONS_BUFF_SIZE 256 4 | 5 | #define strtoint(s) (int)strtol(s, (char **)NULL, 10) 6 | 7 | 8 | extern 9 | char ascii_palette[]; 10 | 11 | extern 12 | unsigned short int opt_width, 13 | opt_height, 14 | auto_width, 15 | auto_height; 16 | 17 | extern 18 | char opt_address[], 19 | opt_port []; 20 | 21 | extern 22 | unsigned short int opt_webcam_index; 23 | 24 | extern 25 | char ascii_palette[]; 26 | 27 | extern 28 | unsigned short int RED [], 29 | GREEN[], 30 | BLUE [], 31 | GRAY []; 32 | 33 | 34 | void options_init(int, char **); 35 | 36 | void usage(); 37 | 38 | void precalc_rgb(const float, const float, const float); 39 | -------------------------------------------------------------------------------- /round.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 Christian Stigen Larsen, http://csl.sublevel3.org 3 | * Distributed under the GNU General Public License (GPL) v2. 4 | * 5 | * Project homepage on http://jp2a.sf.net 6 | * 7 | * $Id: round.h 466 2006-10-02 11:35:03Z csl $ 8 | */ 9 | 10 | #define ROUND(xfloat) (int) ( 0.5f + xfloat ) 11 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "ascii.h" 15 | #include "options.h" 16 | 17 | 18 | int main(int argc, char *argv[]) { 19 | options_init(argc, argv); 20 | int port = strtoint(opt_port); 21 | unsigned short int webcam_index = opt_webcam_index; 22 | 23 | // file descriptors for I/O 24 | int listenfd = 0, 25 | connfd = 0; 26 | 27 | struct sockaddr_in serv_addr; 28 | 29 | listenfd = socket(AF_INET, SOCK_STREAM, 0); // initialize socket 30 | 31 | printf("Running server on port %d\n", port); 32 | 33 | serv_addr.sin_family = AF_INET; // set address family to IPV4, AF_INET6 would be IPV6 34 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // set address for socket 35 | serv_addr.sin_port = htons(port); // set port for socket 36 | 37 | // bind socket based on address and ports set in serv_addr 38 | bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 39 | 40 | int yes = 1; 41 | 42 | // lose the pesky "Address already in use" error message 43 | if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) { 44 | perror("setsockopt"); 45 | exit(1); 46 | } 47 | 48 | // ignore SIGPIPE signal 49 | signal(SIGPIPE, SIG_IGN); 50 | 51 | // listen on socket listenfd with max backlog of 10 connections 52 | listen(listenfd, 10); 53 | while(1) { 54 | printf("1) Waiting for a connection...\n"); 55 | connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 56 | printf("2) Connection initiated, sending data.\n"); 57 | 58 | ascii_read_init(webcam_index); 59 | char *frame = NULL; 60 | 61 | for (int conn = 0; true;) { 62 | frame = ascii_read(); /* malloc happens here */ 63 | conn = send(connfd, frame, strlen(frame), 0); 64 | if (conn == -1) { 65 | break; 66 | } 67 | free(frame); 68 | } 69 | 70 | close(connfd); 71 | printf("3) Closing connection.\n---------------------\n"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /webcam.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | //#include "ext/fmemopen/fmemopen.h" 9 | 10 | #include "webcam.hpp" 11 | 12 | using namespace std; 13 | using namespace cv; 14 | 15 | 16 | vector jpegbuf; 17 | 18 | vector jpegbuf_params; 19 | 20 | VideoCapture camera; 21 | 22 | void webcam_init(unsigned short int webcam_index) { 23 | camera.open(webcam_index); 24 | if(!camera.isOpened()) { 25 | perror("Failed to connect to a webcam."); 26 | exit(1); 27 | } 28 | 29 | jpegbuf_params.push_back(CV_IMWRITE_JPEG_QUALITY); 30 | jpegbuf_params.push_back(100); 31 | } 32 | 33 | 34 | FILE *webcam_read() { 35 | Mat frame, edges; 36 | 37 | camera >> frame; 38 | 39 | flip(frame, frame, +1); 40 | 41 | cvtColor(frame, edges, CV_BGR2GRAY); 42 | 43 | imencode(".jpg", frame, jpegbuf, jpegbuf_params); 44 | 45 | FILE *jpegfile = fmemopen(jpegbuf.data(), jpegbuf.size()+1, "r"); 46 | 47 | // FIXME: do I even need this? 48 | waitKey(16); // FPS 49 | 50 | return jpegfile; 51 | } 52 | -------------------------------------------------------------------------------- /webcam.hpp: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | #include 6 | 7 | void webcam_init(unsigned short int webcam_index); 8 | FILE *webcam_read(); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | --------------------------------------------------------------------------------