SVNからのミラー

This commit is contained in:
2026-01-06 21:30:11 +09:00
commit 7fe7cf216e
8 changed files with 693 additions and 0 deletions

34
CHANGELOG.md Normal file
View File

@@ -0,0 +1,34 @@
# 0.6.0 (2024/06/07)
* MakefileとREADME.mdファイルの更新
* NetBSDは「/usr/pkg」→「/usr/local」
* スペルミスの修正
* manpageでコントロールの説明の追加
* 再レンダーリングの問題の修正
* 画像回転機能性の追加
* 画像反転機能性の追加
* 画像をダウンロード機能性
# 0.5.0 (2024/03/03)
* .desktopファイルの追加
* CDNを回避する様に
* 画像は大き過ぎるの場合、画面に合うまで小さくにする様に
* 「Q」キーを押すと、終了する様に
* サイズ変更の修正
* Pixivからダウンロード出来る様に
* ズーム機能性の追加
* マウスで使って画像を移動出来る様に
* OpenBSDで靜的リンクを可能に
# 0.4.0 (2024/04/07)
* URLから画像ファイルを開ける様に
* GPLv2→BSD2clouseライセンスに変更
* 画面アスペクト比ロック
# 0.3.0 (2024/03/0?)
* 負荷をすくなくするために画面の更新を減らした (たかしさん)
# 0.2.0 (2024/02/0?)
* XlibからSDL2に変えた方が良いと思います
# 0.1.0 (2024/01/0?)
* 開始

13
LICENSE.txt Normal file
View File

@@ -0,0 +1,13 @@
Copyright © 2018-2024 by 076.moe
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

78
Makefile Normal file
View File

@@ -0,0 +1,78 @@
UNAME_S!=uname -s
UNAME_M!=uname -m
NAME!=cat main.c | grep "const char \*sofname" | awk '{print $$5}' | \
sed "s/\"//g" | sed "s/;//"
VERSION!=cat main.c | grep "const char \*version" | awk '{print $$5}' | \
sed "s/\"//g" | sed "s/;//"
PREFIX=/usr/local
.if ${UNAME_S} == "Linux"
PREFIX=/usr
.endif
CC=cc
FILES=main.c
CFLAGS=-Wall -Wextra -g -I/usr/include -I/usr/local/include
LIBS=-lSDL2 -lSDL2_image -lcurl
LDFLAGS=-L/usr/lib -L/usr/local/lib
MANPREFIX=${PREFIX}/share/man
.if ${UNAME_S} == "OpenBSD"
MANPREFIX=${PREFIX}/man
LDFLAGS+=-L/usr/X11R6/lib
CFLAGS+=-I/usr/X11R6/include
.elif ${UNAME_S} == "NetBSD"
LDFLAGS+=-L/usr/X11R7/lib -L/usr/pkg/lib
CFLAGS+=-I/usr/X11R7/include -I/usr/pkg/include
.endif
all:
${CC} ${CFLAGS} -o ${NAME} ${FILES} ${LDFLAGS} ${LIBS}
strip ${NAME}
clean:
rm -f ${NAME}
dist:
mkdir -p ${NAME}-${VERSION} release/src
cp -R LICENSE.txt Makefile *.md ${NAME}.desktop ${NAME}.1 *.c ${NAME}-${VERSION}
tar zcfv release/src/${NAME}-${VERSION}.tar.gz ${NAME}-${VERSION}
rm -rf ${NAME}-${VERSION}
release-openbsd:
mkdir -p release/bin
${CC} ${CFLAGS} -o release/bin/${NAME}-${VERSION}-openbsd-${UNAME_M} ${FILES}\
${LDFLAGS} -static ${LIBS} -lc -lm -liconv -lsndio -lsamplerate -lX11\
-lxcb -lXext -lXcursor -lXrender -lXfixes -lXi -lXrandr -lXss -lusbhid -lpthread\
-ljxl -ljxl_cms -llcms2 -lhwy -lc++ -lc++abi -lbrotlidec\
-lbrotlicommon -lbrotlienc -lyuv -ltiff -lz -ljpeg -llzma -lzstd -lavif\
-ldav1d -laom -lsharpyuv -lwebpdemux -lwebp -lnghttp3 -lngtcp2_crypto_quictls\
-lngtcp2 -lssl -lcrypto -lnghttp2 -lXau -lXdmcp
strip release/bin/${NAME}-${VERSION}-openbsd-${UNAME_M}
release-netbsd:
mkdir -p release/bin
${CC} ${CFLAGS} -o release/bin/${NAME}-${VERSION}-netbsd-${UNAME_M} ${FILES}\
${LDFLAGS} -static ${LIBS} -lc -lm -lossaudio -lX11 -lxcb -lXau -lXdmcp -lgcc\
-lXext -lXcursor -lXrender -lXi -lXfixes -lXrandr -lXss -ldrm -lpci -lgbm -lglapi\
-lstdc++ -lexpat -lusbhid -lusb-1.0 -lpthread -lnghttp2 -lidn2 -lunistring\
-lintl -lgssapi -lkrb5 -lhx509 -lssl -lcrypto -lcrypt -lasn1 -lcom_err -lroken\
-lutil -lwind -lheimbase -lheimntlm -lz
strip release/bin/${NAME}-${VERSION}-netbsd-${UNAME_M}
install:
mkdir -p ${DESTDIR}${PREFIX}/bin\
${DESTDIR}${MANPREFIX}/man1\
${DESTDIR}${PREFIX}/share/applications
cp -f ${NAME} ${DESTDIR}${PREFIX}/bin
sed "s/VERSION/${VERSION}/g" < ${NAME}.1 > ${DESTDIR}${MANPREFIX}/man1/${NAME}.1
cp -f ${NAME}.desktop ${DESTDIR}${PREFIX}/share/applications
chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME}
uninstall:
rm -f ${DESTDIR}${PREFIX}/share/applications/${NAME}.desktop
rm -f ${DESTDIR}${PREFIX}/man1/${NAME}.1
rm -f ${DESTDIR}${PREFIX}/bin/${NAME}
.PHONY: all clean install uninstall

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
# mivfx - minimalist image view for x
ミニマリストな画像ビューアー
# インストールする方法
## OpenBSD
```sh
doas pkg_add sdl2 sdl2_image curl
make
doas make install
```
## FreeBSD
```sh
doas pkg install sdl2 sdl2_image curl
make
doas make install
```
## NetBSD
```sh
doas pkgin install SDL2-2.28.5 SDL2_image curl
make
doas make install
```
## CRUX
```sh
doas prt-get depinst libsdl2 sdl2_image curl bmake
bmake
doas bmake install
```
## Void Linux
```sh
doas xbps-install -S SDL2 SDL2_image curl bmake
bmake
doas bmake install
```
## Artix Linux
```sh
doas pacman -S sdl2 sdl2_image curl bmake
bmake
doas bmake install
```
## Devuan GNU/Linux
```sh
doas apt install libsdl2-dev libsdl2-image-dev libcurl4 bmake
bmake
doas bmake install
```
![](scrot.png)

456
main.c Normal file
View File

@@ -0,0 +1,456 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <stdio.h>
#include <stdbool.h>
#include <curl/curl.h>
#include <string.h>
#include <unistd.h>
#define DELAY_MS 50
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL;
bool quit = false;
bool hasdownloaded = false; // DDoSを避ける為
float aspectRatio;
int imgX = 10;
int imgY = 10;
int imgWidth;
int imgHeight;
int screenWidth;
int screenHeight;
int init = 0;
char *imgfilename;
char *imgurl;
SDL_Rect renderQuad;
float newWidth;
float newHeight;
// マウス
int mouseX = 10;
int mouseY = 10;
int drag = 0;
// ズーム
float zoom = 1.0f;
// 回転
float angle = 0.0f;
// 反転
bool flippedH = false;
bool flippedV = false;
const char *sofname = "mivfx";
const char *version = "0.6.0";
bool dlfile(const char *url, const char *filename) {
CURL *curl = curl_easy_init();
if (!curl) {
return false;
}
FILE *file = fopen(filename, "wb");
if (!file) {
return false;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
// Clownflareは面倒くさいわね・・・
curl_easy_setopt(
curl,
CURLOPT_USERAGENT,
"Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0"
);
// Pixivも結構面倒くさい
if (
strstr("s.pximg.net", url) == 0 ||
strstr("i.pximg.net", url) == 0
) {
curl_easy_setopt(curl, CURLOPT_REFERER, "https://www.pixiv.net/");
}
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
CURLcode res = curl_easy_perform(curl);
fclose(file);
if (res != CURLE_OK) {
remove(filename);
return false;
}
return true;
}
char *fromurl(char *imgfile, char *dir) {
char s[512], *p, *tokens[128];
char *last;
int i = 0;
imgurl = imgfile;
snprintf(s, sizeof(s), "%s", imgfile);
for (
(p = strtok_r(s, "/", &last));
p;
(p = strtok_r(NULL, "/", &last))
) {
if (i < 127) tokens[i++] = p;
}
tokens[i] = NULL;
imgfilename = tokens[i - 1];
char *res;
res = (char *)malloc(1024);
if (res == NULL) {
return NULL;
}
snprintf(res, 1024, "%s/%s", dir, imgfilename);
if (!dlfile(imgfile, res)) {
printf("画像をダウンロードに失敗。URL: %s\n", imgfile);
return NULL;
}
return res;
}
void RenderCopy(SDL_Rect renderQuad) {
SDL_RenderCopyEx(
renderer,
texture,
NULL,
&renderQuad,
angle,
NULL,
flippedV ? SDL_FLIP_VERTICAL : (flippedH ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE)
);
}
void rotateWindow(int w, int h) {
if (angle >= 360.0f) angle = 0.0f;
else if (angle <= -360.0f) angle = 0.0f;
else if (angle == 270.0f) angle = -90.0f;
else if (angle == -180.0f) angle = 180.0f;
imgWidth = h;
imgHeight = w;
aspectRatio = (float)imgWidth / imgHeight;
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_RenderClear(renderer);
SDL_SetWindowSize(window, imgWidth + (imgX * 2), imgHeight + (imgY * 2));
RenderCopy(renderQuad);
SDL_RenderPresent(renderer);
}
void windowevent(SDL_Event e) {
int windowWidth, windowHeight;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
if (e.type == SDL_QUIT) {
quit = true;
} else if (e.type == SDL_KEYDOWN) {
if (e.key.keysym.sym == SDLK_q) {
quit = true;
} else if (e.key.keysym.sym == SDLK_e) {
// GIFアニメーションの停止・続き、0.7.0から追加する予定
} else if (e.key.keysym.sym == SDLK_r) {
angle -= 90.0f;
rotateWindow(imgWidth, imgHeight);
} else if (e.key.keysym.sym == SDLK_t) {
angle += 90.0f;
rotateWindow(imgWidth, imgHeight);
} else if (e.key.keysym.sym == SDLK_y) {
SDL_RenderClear(renderer);
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_SetWindowSize(window, imgWidth + (imgX * 2), imgHeight + (imgY * 2));
RenderCopy(renderQuad);
flippedV = !flippedV;
SDL_RenderPresent(renderer);
} else if (e.key.keysym.sym == SDLK_u) {
SDL_RenderClear(renderer);
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_SetWindowSize(window, imgWidth + (imgX * 2), imgHeight + (imgY * 2));
RenderCopy(renderQuad);
flippedH = !flippedH;
SDL_RenderPresent(renderer);
} else if (e.key.keysym.sym == SDLK_o) {
bool isurl = strncmp(imgurl, "http://", 7) == 0 ||
strncmp(imgurl, "https://", 8) == 0;
if (isurl && !hasdownloaded) {
char path[1024];
getcwd(path, 1024);
if (fromurl(imgurl, path) != NULL) puts("ダウンロード済み");
hasdownloaded = true;
}
} else if (e.key.keysym.sym == SDLK_p) {
// 画像をrsync|sftp|http postで使って共有する、0.7.0から追加する予定
}
} else if (e.type == SDL_MOUSEBUTTONDOWN) {
if (e.button.button == SDL_BUTTON_LEFT) {
drag = 1;
SDL_GetMouseState(&mouseX, &mouseY);
}
} else if (e.type == SDL_MOUSEBUTTONUP) {
if (e.button.button == SDL_BUTTON_LEFT) {
drag = 0;
}
} else if (e.type == SDL_MOUSEMOTION) {
if (drag) {
int newMouseX, newMouseY;
SDL_GetMouseState(&newMouseX, &newMouseY);
SDL_RenderClear(renderer);
if (newWidth != 0.0f) imgWidth = (newWidth);
if (newHeight != 0.0f) imgHeight = (newHeight);
imgX = newMouseX - (imgWidth / 2);
imgY = newMouseY - (imgHeight / 2);
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_RenderClear(renderer);
RenderCopy(renderQuad);
SDL_RenderPresent(renderer);
}
} else if (e.type == SDL_MOUSEWHEEL) {
int curWidth = imgWidth;
int curHeight = imgHeight;
float zoomSpeed = 0.1f;
if (e.wheel.y > 0) {
zoom += zoomSpeed;
} else if (e.wheel.y < 0) {
zoom -= zoomSpeed;
}
if (zoom < 0.1f) {
zoom = 0.1f;
}
// 画像のサイズが変わった場合
newWidth = (float)imgWidth * zoom;
newHeight = (float)imgHeight * zoom;
float minLimit = 50.0f;
// 画像は50x50以下じゃ駄目
if (newWidth < minLimit) {
newWidth = minLimit;
newHeight = curHeight;
} else if (newHeight < minLimit) {
printf("%d\n", curWidth);
newWidth = curWidth;
newHeight = minLimit;
}
// テキスチャーのレンダーリングサイズの設定
SDL_RenderClear(renderer);
imgWidth = (int)newWidth;
imgHeight = (int)newHeight;
imgX = (windowWidth - imgWidth) / 2;
imgY = (windowHeight - imgHeight) / 2;
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_RenderClear(renderer);
RenderCopy(renderQuad);
SDL_RenderPresent(renderer);
} else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED) {
SDL_RenderClear(renderer);
// ウィンドウのサイズが変わった場合
int newWidth = e.window.data1;
int newHeight = e.window.data2;
// 縦横比を変わらずに新しい大きさの算数
float newAspectRatio = (float)newWidth / newHeight;
int scaledWidth, scaledHeight;
if (newAspectRatio != aspectRatio) {
// 画像よりウィンドウの方が広い場合
scaledHeight = angle == 90 || angle == -90 ? newWidth : newHeight;
scaledHeight = newHeight;
scaledWidth = (int)(scaledHeight * aspectRatio);
} else {
// 画像よりウィンドウの方が高い場合
scaledWidth = angle == 90 || angle == -90 ? newHeight : newWidth;
scaledHeight = angle == 90 || angle == -90 ? newWidth : newHeight;
}
// 画像は50x50以下じゃ駄目
int minWidth = 50;
int minHeight = 50;
if (scaledWidth < minWidth) scaledWidth = minWidth;
if (scaledHeight < minHeight) scaledHeight = minHeight;
// 大きすぎの場合もふざけんな
if (scaledWidth >= (screenWidth-20)) scaledWidth = screenWidth-20;
if (scaledHeight >= (screenHeight-20)) scaledHeight = screenHeight-20;
imgWidth = scaledWidth;
imgHeight = scaledHeight;
// テキスチャーのレンダーリングサイズの設定
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_SetWindowSize(window, imgWidth + (imgX * 2), imgHeight + (imgY * 2));
RenderCopy(renderQuad);
SDL_RenderPresent(renderer);
} else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_MOVED) {
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
SDL_RenderClear(renderer);
RenderCopy(renderQuad);
SDL_SetWindowSize(window, imgWidth + (imgX * 2), imgHeight + (imgY * 2));
SDL_RenderPresent(renderer);
} else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_EXPOSED) {
if (init == 1) return;
init = 1;
SDL_RenderClear(renderer);
if (
(imgWidth >= (screenWidth - 100)) &&
imgHeight >= (screenHeight - 100)
) {
imgWidth = (screenWidth - 100);
imgHeight = (screenHeight - 100);
} else if (
(imgWidth >= (screenWidth - 100)) &&
imgHeight <= (screenHeight - 100)
) {
imgWidth = (screenWidth - 100);
imgHeight = (imgWidth * aspectRatio);
} else if (
(imgWidth <= (screenWidth - 100)) &&
imgHeight >= (screenHeight - 100)
) {
imgHeight = (screenHeight - 100);
imgWidth = (imgHeight * aspectRatio);
}
SDL_Rect renderQuad = { imgX, imgY, imgWidth, imgHeight };
RenderCopy(renderQuad);
SDL_SetWindowSize(window, imgWidth + (imgX * 2), imgHeight + (imgY * 2));
SDL_RenderPresent(renderer);
}
}
void usage() {
printf("%s-%s\nusage: %s [file or url]\n", sofname, version, sofname);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
usage();
return 1;
}
// SDL2とSDL2_imageの初期設定
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDLを初期設定出来なかった%s\n", SDL_GetError());
return 1;
}
if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
printf("SDL_imageを初期設定出来なかった%s\n", SDL_GetError());
SDL_Quit();
return 1;
}
const char *imgfile = argv[1];
// URLの場合、仮にダウンロードして
bool isurl = strncmp(imgfile, "http://", 7) == 0 ||
strncmp(imgfile, "https://", 8) == 0;
imgfile = isurl ? fromurl(argv[1], "/tmp") : argv[1];
if (imgfile == NULL) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
// 画像の読込
SDL_Surface *imgsurface = IMG_Load(imgfile);
if (imgsurface == NULL) {
printf("画像の読込に失敗:%s\n", IMG_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// 画像の大きさの受取
imgWidth = imgsurface->w;
imgHeight = imgsurface->h;
aspectRatio = (float)imgWidth / imgHeight;
// 画面の大きさの受取
SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
screenWidth = DM.w;
screenHeight = DM.h;
// ウィンドウの大きさの設定
int windowWidth = (imgWidth >= (screenWidth-20)) ? screenWidth-20 : imgWidth;
int windowHeight = (imgHeight >= (screenHeight-20)) ? screenHeight-20 : imgHeight;
// ウィンドウの作成
SDL_SetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT, "1");
window = SDL_CreateWindow(
"mivfx",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
windowWidth,
windowHeight,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
);
if (window == NULL) {
printf("ウィンドウの作成に失敗:%s\n", SDL_GetError());
SDL_Quit();
return 1;
}
// レンダーの作成
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) {
printf("レンダーの作成に失敗:%s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
texture = SDL_CreateTextureFromSurface(renderer, imgsurface);
SDL_FreeSurface(imgsurface);
if (texture == NULL) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// メインループ
SDL_Event e;
while (!quit) {
// イベント
while (SDL_PollEvent(&e) != 0) {
windowevent(e);
}
// 休ませる
SDL_Delay(DELAY_MS);
}
// 掃除
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
IMG_Quit();
SDL_Quit();
return 0;
}

48
mivfx.1 Normal file
View File

@@ -0,0 +1,48 @@
.TH MIVFX 1 VERSION
.SH NAME
mivfx - minimalist image view for x
.SH SYNOPSIS
.B mivfx
[\fI\,file\fR|\fI\,url\fR]
.SH DESCRIPTION
.PP
ミニマリストな画像ビューアーです。
.SH KEYBINDING
.TP
\fB\,Q\fR
終了
.TP
\fB\,R\fR
画像を-90°で回転する
.TP
\fB\,T\fR
画像を+90°で回転する
.TP
\fB\,Y\fR
縦側に反転する
.TP
\fB\,U\fR
横側に反転する
.TP
\fB\,マウスでスクロール\fR
ズームインやズームアウト
.TP
\fB\,マウスで移動\fR
画像の移動
.SH EXAMPLES
\&...
$ mivfx ~/画像/image.png
.ED
.br
ローカルな画像を開く
\&...
$ mivfx https://076.moe/image.png
.ED
.br
リモートサーバから画像を開く
.SH AUTHORS
.PP
テクニカル諏訪子

9
mivfx.desktop Normal file
View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=mivfx
GenericName=Image Viewer
GenericName[ja]=画像ビューアー
Exec=mivfx %F
MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap;image/webp;image/heic;image/svg+xml;application/postscript;image/jp2;image/jxl;image/avif;image/heif;
NoDisplay=true
Icon=mivfx

BIN
scrot.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB