Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbc34345a1 | ||
|
|
53dff6f5b9 | ||
|
|
12089c098a | ||
|
|
1cdfc4d9db | ||
|
|
128e106b82 | ||
|
|
5ef1f70a3a | ||
|
|
f055e5b675 | ||
|
|
d749d51042 | ||
|
|
cd2805d33d | ||
|
|
7e5b9d94ee | ||
|
|
0973fbd76c | ||
|
|
419056506f | ||
|
|
f97c6e9eb0 | ||
|
|
06467c38d8 | ||
|
|
6b80bc0717 | ||
|
|
69f57a4730 | ||
|
|
b58820e647 | ||
|
|
3e57ebb80c | ||
|
|
28e9f5e40e | ||
|
|
9717adc840 | ||
|
|
543cebef37 | ||
|
|
aef2fbccdc | ||
|
|
9734ff585d | ||
|
|
9943deed2c | ||
|
|
287fde3341 | ||
|
|
949f72ffa9 | ||
|
|
f80071243f | ||
|
|
34dd22e1e8 | ||
|
|
b27e359e4f | ||
|
|
cca06fdaca | ||
|
|
deab25bf68 | ||
|
|
37ceba3e0b | ||
|
|
ed158d6ba9 | ||
|
|
dd92c03299 | ||
|
|
073b76d9b4 | ||
|
|
141205e83e | ||
|
|
4a1a82a5c0 | ||
|
|
b66e502782 | ||
|
|
40cd46ea4e | ||
|
|
945502ff76 | ||
|
|
4b116332f7 | ||
|
|
c7c05bba79 |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -91,4 +91,17 @@ Icon
|
|||||||
.AppleDesktop
|
.AppleDesktop
|
||||||
Network Trash Folder
|
Network Trash Folder
|
||||||
Temporary Items
|
Temporary Items
|
||||||
.apdisk
|
.apdisk
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
|
||||||
|
###Snap###
|
||||||
|
|
||||||
|
parts/
|
||||||
|
prime/
|
||||||
|
snap/
|
||||||
|
stage/
|
||||||
|
*.snap
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -13,14 +13,27 @@ For each 4x8 pixel cell of the (potentially downscaled) image:
|
|||||||
|
|
||||||
See the difference by disabling this optimization using the `-0` option. Or just take a look at the comparison image at the end of this text.
|
See the difference by disabling this optimization using the `-0` option. Or just take a look at the comparison image at the end of this text.
|
||||||
|
|
||||||
|
## News
|
||||||
|
|
||||||
|
- 2019-03-26: Exciting week: @Cableo has fixed output redirection, @boretom has added cross-compilation support to the build file and @AlanDeSmet has fixed tall thumbnails and greyscale images.
|
||||||
|
- 2019-01-14: Install via snap: `sudo snap install --edge tiv`
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Snap
|
||||||
|
|
||||||
|
sudo snap install --edge tiv
|
||||||
|
|
||||||
|
### Build from source
|
||||||
|
|
||||||
sudo apt install imagemagick || yum install ImageMagick
|
sudo apt install imagemagick || yum install ImageMagick
|
||||||
git clone https://github.com/stefanhaustein/TerminalImageViewer.git
|
git clone https://github.com/stefanhaustein/TerminalImageViewer.git
|
||||||
cd TerminalImageViewer/src/main/cpp
|
cd TerminalImageViewer/src/main/cpp
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
|
|
||||||
|
Note: On MacOS, you'll need to install GCC because of this issue: https://stackoverflow.com/q/42633477. Find some more details here: https://github.com/stefanhaustein/TerminalImageViewer/issues/36
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
tiv [options] <filename(s)>
|
tiv [options] <filename(s)>
|
||||||
@@ -33,11 +46,14 @@ The shell will expand wildcards. By default, thumbnails and file names will be d
|
|||||||
https://build.opensuse.org/package/show/home:megamaced/terminalimageviewer
|
https://build.opensuse.org/package/show/home:megamaced/terminalimageviewer
|
||||||
- bperel has created a Docker image:
|
- bperel has created a Docker image:
|
||||||
https://hub.docker.com/r/bperel/terminalimageviewer
|
https://hub.docker.com/r/bperel/terminalimageviewer
|
||||||
|
- teresaejunior has created a snapcraft.yaml file, which can build a Snap package with `sudo docker run -it --rm -v "$PWD:$PWD" -w "$PWD" snapcore/snapcraft sh -c 'apt-get update && snapcraft'`, and then installed with `sudo snap install --dangerous ./*.snap`.
|
||||||
|
|
||||||
## Common problems
|
## Common problems
|
||||||
|
|
||||||
- If you see strange horizontal lines, the characters don't fully fill the character cell. Remove additional line spacing in your terminal app
|
- If you see strange horizontal lines, the characters don't fully fill the character cell. Remove additional line spacing in your terminal app
|
||||||
- Wrong colors? Try -256 to use a 256 color palette instead of 24 bit colors
|
- Wrong colors? Try -256 to use a 256 color palette instead of 24 bit colors
|
||||||
|
- Strange characters? Try -0 or install an use full unicode font (e.g. inconsolata or firacode)
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
73
snapcraft.yaml
Normal file
73
snapcraft.yaml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: tiv
|
||||||
|
version: "v1.0.0"
|
||||||
|
summary: Terminal Image Viewer
|
||||||
|
description: |
|
||||||
|
tiv is a small C++ program to display images in a (modern) terminal using
|
||||||
|
RGB ANSI codes and unicode block graphic characters.
|
||||||
|
|
||||||
|
apps:
|
||||||
|
tiv:
|
||||||
|
command: usr/bin/tiv
|
||||||
|
plugs: [home]
|
||||||
|
|
||||||
|
parts:
|
||||||
|
tiv:
|
||||||
|
plugin: make
|
||||||
|
source-type: tar
|
||||||
|
source: https://github.com/stefanhaustein/TerminalImageViewer/archive/v1.0.0.tar.gz
|
||||||
|
source-subdir: src/main/cpp
|
||||||
|
build-packages:
|
||||||
|
- g++
|
||||||
|
- make
|
||||||
|
- imagemagick
|
||||||
|
override-build: |
|
||||||
|
snapcraftctl build
|
||||||
|
mkdir -p $SNAPCRAFT_PART_INSTALL/usr/bin/
|
||||||
|
install $SNAPCRAFT_PART_BUILD/tiv $SNAPCRAFT_PART_INSTALL/usr/bin/
|
||||||
|
imagemagick:
|
||||||
|
plugin: autotools
|
||||||
|
source: https://www.imagemagick.org/download/releases/ImageMagick-7.0.8-23.tar.xz
|
||||||
|
source-type: tar
|
||||||
|
configflags:
|
||||||
|
- --enable-hdri=yes
|
||||||
|
- --enable-shared=yes
|
||||||
|
- --enable-static=yes
|
||||||
|
- --with-autotrace=yes
|
||||||
|
- --with-fpx=no
|
||||||
|
- --with-gnu-ld=yes
|
||||||
|
- --with-gslib=yes
|
||||||
|
- --with-modules=no
|
||||||
|
- --with-quantum-depth=32
|
||||||
|
- --with-rsvg=yes
|
||||||
|
build-packages:
|
||||||
|
- autoconf
|
||||||
|
- build-essential
|
||||||
|
- fftw-dev
|
||||||
|
- libautotrace-dev
|
||||||
|
- libbz2-dev
|
||||||
|
- libdjvulibre-dev
|
||||||
|
- libfftw3-dev
|
||||||
|
- libfontconfig1-dev
|
||||||
|
- libfreetype6-dev
|
||||||
|
- libgs-dev
|
||||||
|
- libgvc6
|
||||||
|
- libjbig-dev
|
||||||
|
- libjpeg-dev
|
||||||
|
- liblcms2-dev
|
||||||
|
- liblqr-1-0-dev
|
||||||
|
- libltdl-dev
|
||||||
|
- libmagick++-dev
|
||||||
|
- libopenexr-dev
|
||||||
|
- libopenjp2-7-dev
|
||||||
|
- libpango1.0-dev
|
||||||
|
- libperl-dev
|
||||||
|
- libpng12-dev
|
||||||
|
- librsvg2-dev
|
||||||
|
- libtiff5-dev
|
||||||
|
- libwebp-dev
|
||||||
|
- libwmf-dev
|
||||||
|
- libx11-dev
|
||||||
|
- lzma-dev
|
||||||
|
- ocl-icd-opencl-dev
|
||||||
|
- perlmagick
|
||||||
|
- zlib1g-dev
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
CXX=g++
|
# set CXX to g++ if not set
|
||||||
|
CXX ?= g++
|
||||||
|
|
||||||
default: tiv
|
# append necessary arguments
|
||||||
|
override CPPFLAGS += -std=c++17 -Wall -fpermissive -fexceptions -O2
|
||||||
|
override LDFLAGS += -lstdc++fs -pthread -s
|
||||||
|
|
||||||
|
all: tiv
|
||||||
|
|
||||||
tiv.o: tiv.cpp CImg.h
|
tiv.o: tiv.cpp CImg.h
|
||||||
$(CXX) -std=c++17 -Wall -fpermissive -fexceptions -O2 -c tiv.cpp -o tiv.o
|
$(CXX) $(CPPFLAGS) -c tiv.cpp -o $@
|
||||||
|
|
||||||
tiv : tiv.o
|
tiv : tiv.o
|
||||||
$(CXX) tiv.o -o tiv -lstdc++fs -pthread -s
|
$(CXX) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
install: tiv
|
.PHONY: all install clean
|
||||||
cp tiv /usr/local/bin/tiv
|
install: all
|
||||||
|
test -d $(DESTDIR)/usr/local/bin || mkdir -p $(DESTDIR)/usr/local/bin
|
||||||
|
cp tiv $(DESTDIR)/usr/local/bin/tiv
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f tiv tiv.o
|
rm -f tiv tiv.o
|
||||||
|
|||||||
@@ -195,8 +195,7 @@ CharData getCharData(const cimg_library::CImg<unsigned char> & image, int x0, in
|
|||||||
int count2 = iter->first;
|
int count2 = iter->first;
|
||||||
long max_count_color_1 = iter->second;
|
long max_count_color_1 = iter->second;
|
||||||
long max_count_color_2 = max_count_color_1;
|
long max_count_color_2 = max_count_color_1;
|
||||||
if (iter != color_per_count.rend()) {
|
if ((++iter) != color_per_count.rend()) {
|
||||||
++iter;
|
|
||||||
count2 += iter->first;
|
count2 += iter->first;
|
||||||
max_count_color_2 = iter->second;
|
max_count_color_2 = iter->second;
|
||||||
}
|
}
|
||||||
@@ -330,7 +329,7 @@ void emit_color(int flags, int r, int g, int b) {
|
|||||||
int gq = COLOR_STEPS[gi];
|
int gq = COLOR_STEPS[gi];
|
||||||
int bq = COLOR_STEPS[bi];
|
int bq = COLOR_STEPS[bi];
|
||||||
|
|
||||||
int gray = std::round(r * 0.2989f + g * 0.5870f + b * 0.1140f);
|
int gray = static_cast<int>(std::round(r * 0.2989f + g * 0.5870f + b * 0.1140f));
|
||||||
|
|
||||||
int gri = best_index(gray, GRAYSCALE_STEPS, GRAYSCALE_STEP_COUNT);
|
int gri = best_index(gray, GRAYSCALE_STEPS, GRAYSCALE_STEP_COUNT);
|
||||||
int grq = GRAYSCALE_STEPS[gri];
|
int grq = GRAYSCALE_STEPS[gri];
|
||||||
@@ -382,6 +381,31 @@ void emit_image(const cimg_library::CImg<unsigned char> & image, int flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct size {
|
||||||
|
size(unsigned int in_width, unsigned int in_height) :
|
||||||
|
width(in_width), height(in_height) {
|
||||||
|
}
|
||||||
|
size(cimg_library::CImg<unsigned int> img) :
|
||||||
|
width(img.width()), height(img.height()) {
|
||||||
|
}
|
||||||
|
unsigned int width;
|
||||||
|
unsigned int height;
|
||||||
|
size scaled(double scale) {
|
||||||
|
return size(width*scale, height*scale);
|
||||||
|
}
|
||||||
|
size fitted_within(size container) {
|
||||||
|
double scale = std::min(container.width / (double) width, container.height / (double) height);
|
||||||
|
return scaled(scale);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::ostream& operator<<(std::ostream& stream, size sz) {
|
||||||
|
stream << sz.width << "x" << sz.height;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void emit_usage() {
|
void emit_usage() {
|
||||||
std::cerr << "Terminal Image Viewer" << std::endl << std::endl;
|
std::cerr << "Terminal Image Viewer" << std::endl << std::endl;
|
||||||
std::cerr << "usage: tiv [options] <image> [<image>...]" << std::endl << std::endl;
|
std::cerr << "usage: tiv [options] <image> [<image>...]" << std::endl << std::endl;
|
||||||
@@ -397,12 +421,42 @@ void emit_usage() {
|
|||||||
|
|
||||||
enum Mode {AUTO, THUMBNAILS, FULL_SIZE};
|
enum Mode {AUTO, THUMBNAILS, FULL_SIZE};
|
||||||
|
|
||||||
|
|
||||||
|
/* Wrapper around CImg<T>(const char*) to ensure the result has 3 channels as RGB
|
||||||
|
*/
|
||||||
|
cimg_library::CImg<unsigned char> load_rgb_CImg(const char * const filename) {
|
||||||
|
cimg_library::CImg<unsigned char> image(filename);
|
||||||
|
if(image.spectrum() == 1) {
|
||||||
|
// Greyscale. Just copy greyscale data to all channels
|
||||||
|
cimg_library::CImg<unsigned char> rgb_image(image.width(), image.height(), image.depth(), 3);
|
||||||
|
for(unsigned int chn = 0; chn < 3; chn++) {
|
||||||
|
rgb_image.draw_image(0, 0, 0,chn, image);
|
||||||
|
}
|
||||||
|
return rgb_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
int maxWidth = 80;
|
||||||
|
int maxHeight = 24;
|
||||||
|
bool sizeDetectionSuccessful = true;
|
||||||
struct winsize w;
|
struct winsize w;
|
||||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
int ioStatus = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||||
|
// If redirecting STDOUT to one file ( col or row == 0, or the previous ioctl call's failed )
|
||||||
int maxWidth = w.ws_col * 4;
|
if (ioStatus != 0 || (w.ws_col | w.ws_row) == 0) {
|
||||||
int maxHeight = w.ws_row * 8;
|
ioStatus = ioctl(STDIN_FILENO, TIOCGWINSZ, &w);
|
||||||
|
if (ioStatus != 0 || (w.ws_col | w.ws_row) == 0) {
|
||||||
|
std::cerr << "Warning: failed to determine most reasonable size, defaulting to 80x24" << std::endl;
|
||||||
|
sizeDetectionSuccessful = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sizeDetectionSuccessful)
|
||||||
|
{
|
||||||
|
maxWidth = w.ws_col * 4;
|
||||||
|
maxHeight = w.ws_row * 8;
|
||||||
|
}
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
Mode mode = AUTO;
|
Mode mode = AUTO;
|
||||||
int columns = 3;
|
int columns = 3;
|
||||||
@@ -452,14 +506,14 @@ int main(int argc, char* argv[]) {
|
|||||||
if (mode == FULL_SIZE || (mode == AUTO && file_names.size() == 1)) {
|
if (mode == FULL_SIZE || (mode == AUTO && file_names.size() == 1)) {
|
||||||
for (unsigned int i = 0; i < file_names.size(); i++) {
|
for (unsigned int i = 0; i < file_names.size(); i++) {
|
||||||
try {
|
try {
|
||||||
cimg_library::CImg<unsigned char> image(file_names[i].c_str());
|
cimg_library::CImg<unsigned char> image = load_rgb_CImg(file_names[i].c_str());
|
||||||
|
|
||||||
if (image.width() > maxWidth || image.height() > maxHeight) {
|
if (image.width() > maxWidth || image.height() > maxHeight) {
|
||||||
double scale = std::min(maxWidth / (double) image.width(), maxHeight / (double) image.height());
|
size new_size = size(image).fitted_within(size(maxWidth,maxHeight));
|
||||||
image.resize((int) (image.width() * scale), (int) (image.height() * scale), -100, -100, 5);
|
image.resize(new_size.width, new_size.height, -100, -100, 5);
|
||||||
}
|
}
|
||||||
emit_image(image, flags);
|
emit_image(image, flags);
|
||||||
} catch(cimg_library::CImgIOException e) {
|
} catch(cimg_library::CImgIOException & e) {
|
||||||
error = 1;
|
error = 1;
|
||||||
std::cerr << "File format is not recognized for '" << file_names[i] << "'" << std::endl;
|
std::cerr << "File format is not recognized for '" << file_names[i] << "'" << std::endl;
|
||||||
}
|
}
|
||||||
@@ -471,6 +525,7 @@ int main(int argc, char* argv[]) {
|
|||||||
int cw = (((maxWidth / 4) - 2 * (columns - 1)) / columns);
|
int cw = (((maxWidth / 4) - 2 * (columns - 1)) / columns);
|
||||||
int tw = cw * 4;
|
int tw = cw * 4;
|
||||||
cimg_library::CImg<unsigned char> image(tw * columns + 2 * 4 * (columns - 1), tw, 1, 3);
|
cimg_library::CImg<unsigned char> image(tw * columns + 2 * 4 * (columns - 1), tw, 1, 3);
|
||||||
|
size maxThumbSize(tw, tw);
|
||||||
|
|
||||||
while (index < file_names.size()) {
|
while (index < file_names.size()) {
|
||||||
image.fill(0);
|
image.fill(0);
|
||||||
@@ -479,21 +534,21 @@ int main(int argc, char* argv[]) {
|
|||||||
while (index < file_names.size() && count < columns) {
|
while (index < file_names.size() && count < columns) {
|
||||||
std::string name = file_names[index++];
|
std::string name = file_names[index++];
|
||||||
try {
|
try {
|
||||||
cimg_library::CImg<unsigned char> original(name.c_str());
|
cimg_library::CImg<unsigned char> original = load_rgb_CImg(name.c_str());
|
||||||
unsigned int cut = name.find_last_of("/");
|
auto cut = name.find_last_of("/");
|
||||||
sb += cut == std::string::npos ? name : name.substr(cut + 1);
|
sb += cut == std::string::npos ? name : name.substr(cut + 1);
|
||||||
int th = original.height() * tw / original.width();
|
size newSize = size(original).fitted_within(maxThumbSize);
|
||||||
original.resize(tw, th, 1, -100, 5);
|
original.resize(newSize.width, newSize.height, 1, -100, 5);
|
||||||
image.draw_image(count * (tw + 8), (tw - th) / 2, 0, 0, original);
|
image.draw_image(count * (tw + 8) + (tw - newSize.width) / 2, (tw - newSize.height) / 2, 0, 0, original);
|
||||||
count++;
|
count++;
|
||||||
unsigned int sl = count * (cw + 2);
|
unsigned int sl = count * (cw + 2);
|
||||||
sb.resize(sl - 2, ' ');
|
sb.resize(sl - 2, ' ');
|
||||||
sb += " ";
|
sb += " ";
|
||||||
} catch (std::exception e) {
|
} catch (std::exception & e) {
|
||||||
// Probably no image; ignore.
|
// Probably no image; ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit_image(image, flags);
|
if (count) emit_image(image, flags);
|
||||||
std::cout << sb << std::endl << std::endl;
|
std::cout << sb << std::endl << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user