C++ version added
This commit is contained in:
parent
d1debd7f3a
commit
2e91d92936
60470
src/main/cpp/CImg.h
Normal file
60470
src/main/cpp/CImg.h
Normal file
File diff suppressed because it is too large
Load Diff
13
src/main/cpp/Makefile
Normal file
13
src/main/cpp/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
default: tiv
|
||||
|
||||
tiv.o: tiv.cpp CImg.h
|
||||
g++ -Wall -fexceptions -O2 -c tiv.cpp -o tiv.o
|
||||
|
||||
tiv : tiv.o
|
||||
g++ tiv.o -o tiv -pthread -s
|
||||
|
||||
install: tiv
|
||||
cp tiv /usr/local/bin/tiv
|
||||
|
||||
clean:
|
||||
rm -f tiv tiv.o
|
414
src/main/cpp/tiv.cpp
Normal file
414
src/main/cpp/tiv.cpp
Normal file
@ -0,0 +1,414 @@
|
||||
#include <iostream>
|
||||
#include <bitset>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#define cimg_display 0
|
||||
#include "CImg.h"
|
||||
|
||||
//using namespace std;
|
||||
|
||||
const int FLAG_FG = 1;
|
||||
const int FLAG_BG = 2;
|
||||
const int FLAG_MODE_256 = 4;
|
||||
const int FLAG_24BIT = 8;
|
||||
|
||||
const int COLOR_STEP_COUNT = 6;
|
||||
const int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
|
||||
|
||||
const int GRAYSCALE_STEP_COUNT = 24;
|
||||
const int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = {
|
||||
0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76,
|
||||
0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee};
|
||||
|
||||
const unsigned int BITMAPS[] = {
|
||||
0x00000000, 0x00a0,
|
||||
|
||||
// Block graphics
|
||||
// 0xffff0000, 0x2580, // upper 1/2; redundant with inverse lower 1/2
|
||||
|
||||
0x0000000f, 0x2581, // lower 1/8
|
||||
0x000000ff, 0x2582, // lower 1/4
|
||||
0x00000fff, 0x2583,
|
||||
0x0000ffff, 0x2584, // lower 1/2
|
||||
0x000fffff, 0x2585,
|
||||
0x00ffffff, 0x2586, // lower 3/4
|
||||
0x0fffffff, 0x2587,
|
||||
//0xffffffff, 0x2588, // full; redundant with inverse space
|
||||
|
||||
0xeeeeeeee, 0x258a, // left 3/4
|
||||
0xcccccccc, 0x258c, // left 1/2
|
||||
0x88888888, 0x258e, // left 1/4
|
||||
|
||||
0x0000cccc, 0x2596, // quadrant lower left
|
||||
0x00003333, 0x2597, // quadrant lower right
|
||||
0xcccc0000, 0x2598, // quadrant upper left
|
||||
//0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4
|
||||
0xcccc3333, 0x259a, // diagonal 1/2
|
||||
//0xffffcccc, 0x259b, // 3/4 redundant
|
||||
//0xffff3333, 0x259c, // 3/4 redundant
|
||||
0x33330000, 0x259d, // quadrant upper right
|
||||
//0x3333cccc, 0x259e, // 3/4 redundant
|
||||
//0x3333ffff, 0x259f, // 3/4 redundant
|
||||
|
||||
// Line drawing subset: no double lines, no complex light lines
|
||||
// Simple light lines duplicated because there is no center pixel int the 4x8 matrix
|
||||
|
||||
0x000ff000, 0x2501, // Heavy horizontal
|
||||
0x66666666, 0x2503, // Heavy vertical
|
||||
|
||||
0x00077666, 0x250f, // Heavy down and right
|
||||
0x000ee666, 0x2513, // Heavy down and left
|
||||
0x66677000, 0x2517, // Heavy up and right
|
||||
0x666ee000, 0x251b, // Heavy up and left
|
||||
|
||||
0x66677666, 0x2523, // Heavy vertical and right
|
||||
0x666ee666, 0x252b, // Heavy vertical and left
|
||||
0x000ff666, 0x2533, // Heavy down and horizontal
|
||||
0x666ff000, 0x253b, // Heavy up and horizontal
|
||||
0x666ff666, 0x254b, // Heavy cross
|
||||
|
||||
0x000cc000, 0x2578, // Bold horizontal left
|
||||
0x00066000, 0x2579, // Bold horizontal up
|
||||
0x00033000, 0x257a, // Bold horizontal right
|
||||
0x00066000, 0x257b, // Bold horizontal down
|
||||
|
||||
0x06600660, 0x254f, // Heavy double dash vertical
|
||||
|
||||
0x000f0000, 0x2500, // Light horizontal
|
||||
0x0000f000, 0x2500, //
|
||||
0x44444444, 0x2502, // Light vertical
|
||||
0x22222222, 0x2502,
|
||||
|
||||
0x000e0000, 0x2574, // light left
|
||||
0x0000e000, 0x2574, // light left
|
||||
0x44440000, 0x2575, // light up
|
||||
0x22220000, 0x2575, // light up
|
||||
0x00030000, 0x2576, // light right
|
||||
0x00003000, 0x2576, // light right
|
||||
0x00004444, 0x2575, // light down
|
||||
0x00002222, 0x2575, // light down
|
||||
|
||||
// Misc technical
|
||||
|
||||
0x44444444, 0x23a2, // [ extension
|
||||
0x22222222, 0x23a5, // ] extension
|
||||
|
||||
0x0f000000, 0x23ba, // Horizontal scanline 1
|
||||
0x00f00000, 0x23bb, // Horizontal scanline 3
|
||||
0x00000f00, 0x23bc, // Horizontal scanline 7
|
||||
0x000000f0, 0x23bd, // Horizontal scanline 9
|
||||
|
||||
// Geometrical shapes. Tricky because some of them are too wide.
|
||||
|
||||
//0x00ffff00, 0x25fe, // Black medium small square
|
||||
0x00066000, 0x25aa, // Black small square
|
||||
|
||||
//0x11224488, 0x2571, // diagonals
|
||||
//0x88442211, 0x2572,
|
||||
//0x99666699, 0x2573,
|
||||
//0x000137f0, 0x25e2, // Triangles
|
||||
//0x0008cef0, 0x25e3,
|
||||
//0x000fec80, 0x25e4,
|
||||
//0x000f7310, 0x25e5,
|
||||
|
||||
0, 0 // End marker
|
||||
};
|
||||
|
||||
|
||||
struct CharData {
|
||||
int fgColor[3] = {0};
|
||||
int bgColor[3] = {0};
|
||||
int codePoint;
|
||||
};
|
||||
|
||||
|
||||
CharData getCharData(const cimg_library::CImg<unsigned char> & image, int x0, int y0) {
|
||||
int min[3] = {255, 255, 255};
|
||||
int max[3] = {0};
|
||||
|
||||
CharData result;
|
||||
|
||||
// Determine the minimum and maximum value for each color channel
|
||||
for (int y = 0; y < 8; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int d = image(x0 + x, y0 + y, 0, i);
|
||||
min[i] = std::min(min[i], d);
|
||||
max[i] = std::max(max[i], d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the color channel with the greatest range.
|
||||
int splitIndex = 0;
|
||||
int bestSplit = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (max[i] - min[i] > bestSplit) {
|
||||
bestSplit = max[i] - min[i];
|
||||
splitIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// We just split at the middle of the interval instead of computing the median.
|
||||
int splitValue = min[splitIndex] + bestSplit / 2;
|
||||
|
||||
// Compute a bitmap using the given split and sum the color values for both buckets.
|
||||
unsigned int bits = 0;
|
||||
for (int y = 0; y < 8; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
bits = bits << 1;
|
||||
if (image(x0 + x, y0 + y, 0, splitIndex) > splitValue) {
|
||||
bits |= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the best bitmap match by counting the bits that don't match,
|
||||
// including the inverted bitmaps.
|
||||
int best_diff = 8;
|
||||
unsigned int best_pattern = 0x0000ffff;
|
||||
result.codePoint = 0x2584;
|
||||
for (int i = 0; BITMAPS[i + 1] != 0; i += 2) {
|
||||
unsigned int pattern = BITMAPS[i];
|
||||
for (int j = 0; j < 2; j++) {
|
||||
int diff = (std::bitset<32>(pattern ^ bits)).count();
|
||||
if (diff < best_diff) {
|
||||
best_pattern = BITMAPS[i]; // pattern might be inverted.
|
||||
result.codePoint = BITMAPS[i + 1];
|
||||
best_diff = diff;
|
||||
}
|
||||
pattern = ~pattern;
|
||||
}
|
||||
}
|
||||
|
||||
int fg_count = 0;
|
||||
int bg_count = 0;
|
||||
unsigned int mask = 0x80000000;
|
||||
|
||||
for (int y = 0; y < 8; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
int* avg;
|
||||
if (best_pattern & mask) {
|
||||
avg = result.fgColor;
|
||||
fg_count++;
|
||||
} else {
|
||||
avg = result.bgColor;
|
||||
bg_count++;
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
avg[i] += image(x0 + x, y0 + y, 0, i);
|
||||
}
|
||||
mask = mask >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the average color value for each bucket
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (bg_count != 0) {
|
||||
result.bgColor[i] /= bg_count;
|
||||
}
|
||||
if (fg_count != 0) {
|
||||
result.fgColor[i] /= fg_count;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int clamp_byte(int value) {
|
||||
return value < 0 ? 0 : (value > 255 ? 255 : value);
|
||||
}
|
||||
|
||||
double sqr(double n) {
|
||||
return n*n;
|
||||
}
|
||||
|
||||
int best_index(int value, const int data[], int count) {
|
||||
int best_diff = std::abs(data[0] - value);
|
||||
int result = 0;
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (std::abs(data[i] - value) < best_diff) {
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void emit_color(int flags, int r, int g, int b) {
|
||||
r = clamp_byte(r);
|
||||
g = clamp_byte(g);
|
||||
b = clamp_byte(b);
|
||||
|
||||
bool bg = (flags & FLAG_BG) != 0;
|
||||
|
||||
if ((flags & FLAG_MODE_256) == 0) {
|
||||
std::cout << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' << b << 'm';
|
||||
return;
|
||||
}
|
||||
|
||||
int ri = best_index(r, COLOR_STEPS, COLOR_STEP_COUNT);
|
||||
int gi = best_index(g, COLOR_STEPS, COLOR_STEP_COUNT);
|
||||
int bi = best_index(b, COLOR_STEPS, COLOR_STEP_COUNT);
|
||||
|
||||
int rq = COLOR_STEPS[ri];
|
||||
int gq = COLOR_STEPS[gi];
|
||||
int bq = COLOR_STEPS[bi];
|
||||
|
||||
int gray = std::round(r * 0.2989f + g * 0.5870f + b * 0.1140f);
|
||||
|
||||
int gri = best_index(gray, GRAYSCALE_STEPS, GRAYSCALE_STEP_COUNT);
|
||||
int grq = GRAYSCALE_STEPS[gri];
|
||||
|
||||
int color_index;
|
||||
if (0.3 * sqr(rq-r) + 0.59 * sqr(gq-g) + 0.11 * sqr(bq-b) <
|
||||
0.3 * sqr(grq-r) + 0.59 * sqr(grq-g) + 0.11 * sqr(grq-b)) {
|
||||
color_index = 16 + 36 * ri + 6 * gi + bi;
|
||||
} else {
|
||||
color_index = 232 + gri; // 1..24 -> 232..255
|
||||
}
|
||||
std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m";
|
||||
}
|
||||
|
||||
|
||||
|
||||
void emitCodepoint(int codepoint) {
|
||||
if (codepoint < 128) {
|
||||
std::cout << (char) codepoint;
|
||||
} else if (codepoint < 0x7ff) {
|
||||
std::cout << (char) (0xc0 | (codepoint >> 6));
|
||||
std::cout << (char) (0x80 | (codepoint & 0x3f));
|
||||
} else if (codepoint < 0xffff) {
|
||||
std::cout << (char) (0xe0 | (codepoint >> 12));
|
||||
std::cout << (char) (0x80 | ((codepoint >> 6) & 0x3f));
|
||||
std::cout << (char) (0x80 | (codepoint & 0x3f));
|
||||
} else if (codepoint < 0x10ffff) {
|
||||
std::cout << (char) (0xf0 | (codepoint >> 18));
|
||||
std::cout << (char) (0x80 | ((codepoint >> 12) & 0x3f));
|
||||
std::cout << (char) (0x80 | ((codepoint >> 6) & 0x3f));
|
||||
std::cout << (char) (0x80 | (codepoint & 0x3f));
|
||||
} else {
|
||||
std::cout << "ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
void emit_image(const cimg_library::CImg<unsigned char> & image, int flags) {
|
||||
for (int y = 0; y < image.height() - 8; y += 8) {
|
||||
for (int x = 0; x < image.width() - 4; x += 4) {
|
||||
CharData charData = getCharData(image, x, y);
|
||||
emit_color(flags | FLAG_BG, charData.bgColor[0], charData.bgColor[1], charData.bgColor[2]);
|
||||
emit_color(flags | FLAG_FG, charData.fgColor[0], charData.fgColor[1], charData.fgColor[2]);
|
||||
emitCodepoint(charData.codePoint);
|
||||
}
|
||||
std::cout << "\x1b[0m" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void emit_usage() {
|
||||
std::cerr << "Terminal Image Viewer" << std::endl << std::endl;
|
||||
std::cerr << "usage: tiv [options] <image> [<image>...]" << std::endl << std::endl;
|
||||
std::cerr << " -256 : Use 256 color mode." << std::endl;
|
||||
std::cerr << " -c <num> : Number of thumbnail columns in 'dir' mode (3)." << std::endl;
|
||||
std::cerr << " -d : Force 'dir' mode. Automatially selected for more than one input." << std::endl;
|
||||
std::cerr << " -f : Force 'full' mode. Automatically selected for one input." << std::endl;
|
||||
std::cerr << " -help : Display this help text." << std::endl;
|
||||
std::cerr << " -h <num> : Set the maximum height to <num> lines." << std::endl;
|
||||
std::cerr << " -w <num> : Set the maximum width to <num> characters." << std::endl << std::endl;
|
||||
}
|
||||
|
||||
enum Mode {AUTO, THUMBNAILS, FULL_SIZE};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct winsize w;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||
|
||||
int maxWidth = w.ws_col * 4;
|
||||
int maxHeight = w.ws_row * 8;
|
||||
int flags = 0;
|
||||
Mode mode = AUTO;
|
||||
int columns = 3;
|
||||
|
||||
std::vector<std::string> file_names;
|
||||
int error = 0;
|
||||
|
||||
if (argc <= 1) {
|
||||
emit_usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg(argv[i]);
|
||||
if (arg == "-c") {
|
||||
columns = std::stoi(argv[++i]);
|
||||
} else if (arg == "-d") {
|
||||
mode = THUMBNAILS;
|
||||
} else if (arg == "-f") {
|
||||
mode = FULL_SIZE;
|
||||
} else if (arg == "-w") {
|
||||
maxWidth = 4 * std::stoi(argv[++i]);
|
||||
} else if (arg == "-h") {
|
||||
maxHeight = 8 * std::stoi(argv[++i]);
|
||||
} else if (arg == "-256") {
|
||||
flags |= FLAG_MODE_256;
|
||||
} else if (arg == "--help" || arg == "-help") {
|
||||
emit_usage();
|
||||
} else if (arg[0] == '-') {
|
||||
std::cerr << "Unrecognized argument: " << arg << std::endl;
|
||||
} else {
|
||||
file_names.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == FULL_SIZE || (mode == AUTO && file_names.size() == 1)) {
|
||||
for (unsigned int i = 0; i < file_names.size(); i++) {
|
||||
try {
|
||||
cimg_library::CImg<unsigned char> image(file_names[i].c_str());
|
||||
|
||||
if (image.width() > maxWidth || image.height() > maxHeight) {
|
||||
double scale = std::min(maxWidth / (double) image.width(), maxHeight / (double) image.height());
|
||||
image.resize((int) (image.width() * scale), (int) (image.height() * scale), -100, -100, 5);
|
||||
}
|
||||
emit_image(image, flags);
|
||||
} catch(cimg_library::CImgIOException e) {
|
||||
error = 1;
|
||||
std::cerr << "File format not recogmized for '" << file_names[i] << "'" << std::endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Thumbnail mode
|
||||
|
||||
unsigned int index = 0;
|
||||
int cw = (((maxWidth / 4) - 2 * (columns - 1)) / columns);
|
||||
int tw = cw * 4;
|
||||
cimg_library::CImg<unsigned char> image(tw * columns + 2 * 4 * (columns - 1), tw, 1, 3);
|
||||
|
||||
while (index < file_names.size()) {
|
||||
image.fill(0);
|
||||
int count = 0;
|
||||
std::string sb;
|
||||
while (index < file_names.size() && count < columns) {
|
||||
std::string name = file_names[index++];
|
||||
try {
|
||||
cimg_library::CImg<unsigned char> original(name.c_str());
|
||||
unsigned int cut = name.find_last_of("/");
|
||||
sb += cut == std::string::npos ? name : name.substr(cut + 1);
|
||||
int th = original.height() * tw / original.width();
|
||||
original.resize(tw, th, 1, -100, 5);
|
||||
image.draw_image(count * (tw + 8), (tw - th) / 2, 0, 0, original);
|
||||
count++;
|
||||
unsigned int sl = count * (cw + 2);
|
||||
sb.resize(sl - 2, ' ');
|
||||
sb += " ";
|
||||
} catch (std::exception e) {
|
||||
// Probably no image; ignore.
|
||||
}
|
||||
}
|
||||
emit_image(image, flags);
|
||||
std::cout << sb << std::endl << std::endl;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
Loading…
Reference in New Issue
Block a user