grxiv.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #include <QApplication>
  2. #include <QMainWindow>
  3. #include <QImage>
  4. #include <QMessageBox>
  5. #include <QOpenGLWidget>
  6. #include <QOpenGLFunctions>
  7. #include <QOpenGLShaderProgram>
  8. #include <QOpenGLTexture>
  9. #include <QWheelEvent>
  10. #include <QCommandLineParser>
  11. #include <QSurfaceFormat>
  12. #include <QDebug>
  13. #include <QKeyEvent>
  14. #include <QDir>
  15. #include <QFileInfoList>
  16. class ImageGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
  17. Q_OBJECT
  18. public:
  19. ImageGLWidget(const QString& path, QWidget* parent = nullptr)
  20. : QOpenGLWidget(parent), shaderProgram(nullptr), texture(nullptr), zoomLevel(1.0f), currentImageIndex(-1) {
  21. QSurfaceFormat format;
  22. format.setVersion(2, 1);
  23. format.setProfile(QSurfaceFormat::CompatibilityProfile);
  24. setFormat(format);
  25. setFocusPolicy(Qt::StrongFocus);
  26. // Check if the path is a directory or a file
  27. QDir dir(path);
  28. if (dir.exists()) {
  29. // Load the list of images from the directory
  30. QStringList filters;
  31. filters << "*.jpg" << "*.jpeg" << "*.png" << "*.bmp" << "*.gif";
  32. dir.setNameFilters(filters);
  33. dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
  34. dir.setSorting(QDir::Name);
  35. imageFiles = dir.entryInfoList();
  36. if (imageFiles.isEmpty()) {
  37. qDebug() << "No images in directory:" << path;
  38. QMessageBox::critical(this, "Error", "No images in directory: " + path);
  39. return;
  40. }
  41. } else {
  42. // Check if the path is a file
  43. QFileInfo fileInfo(path);
  44. if (fileInfo.isFile()) {
  45. imageFiles << fileInfo;
  46. } else {
  47. qDebug() << "Invalid path:" << path;
  48. QMessageBox::critical(this, "Error", "Invalid path specified: " + path);
  49. return;
  50. }
  51. }
  52. }
  53. ~ImageGLWidget() {
  54. makeCurrent();
  55. delete texture;
  56. delete shaderProgram;
  57. glDeleteBuffers(1, &VBO);
  58. glDeleteBuffers(1, &EBO);
  59. doneCurrent();
  60. }
  61. protected:
  62. void initializeGL() override {
  63. if (!context()) {
  64. qDebug() << "OpenGL context not created!";
  65. return;
  66. }
  67. initializeOpenGLFunctions();
  68. const char* glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
  69. const char* glslVersion = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
  70. qDebug() << "OpenGL Version:" << (glVersion ? glVersion : "unknown");
  71. qDebug() << "GLSL Version:" << (glslVersion ? glslVersion : "unknown");
  72. shaderProgram = new QOpenGLShaderProgram(this);
  73. if (!shaderProgram) {
  74. qDebug() << "Error creating shader program object";
  75. return;
  76. }
  77. bool success = true;
  78. success &= shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex,
  79. "#version 120\n"
  80. "attribute vec2 position;\n"
  81. "attribute vec2 texCoord;\n"
  82. "varying vec2 vTexCoord;\n"
  83. "uniform mat4 mvp;\n"
  84. "void main() {\n"
  85. " gl_Position = mvp * vec4(position, 0.0, 1.0);\n"
  86. " vTexCoord = texCoord;\n"
  87. "}\n");
  88. if (!success) {
  89. qDebug() << "Vertex shader compilation error:" << shaderProgram->log();
  90. return;
  91. }
  92. success &= shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
  93. "#version 120\n"
  94. "varying vec2 vTexCoord;\n"
  95. "uniform sampler2D tex;\n"
  96. "void main() {\n"
  97. " gl_FragColor = texture2D(tex, vTexCoord);\n"
  98. "}\n");
  99. if (!success) {
  100. qDebug() << "Fragment shader compilation error:" << shaderProgram->log();
  101. return;
  102. }
  103. success &= shaderProgram->link();
  104. if (!success) {
  105. qDebug() << "Shader linking error:" << shaderProgram->log();
  106. return;
  107. }
  108. qDebug() << "Shaders successfully initialized";
  109. float vertices[] = {
  110. -1.0f, -1.0f, 0.0f, 1.0f,
  111. 1.0f, -1.0f, 1.0f, 1.0f,
  112. 1.0f, 1.0f, 1.0f, 0.0f,
  113. -1.0f, 1.0f, 0.0f, 0.0f
  114. };
  115. unsigned int indices[] = {
  116. 0, 1, 2,
  117. 2, 3, 0
  118. };
  119. glGenBuffers(1, &VBO);
  120. if (VBO == 0) {
  121. qDebug() << "Error creating VBO";
  122. return;
  123. }
  124. glGenBuffers(1, &EBO);
  125. if (EBO == 0) {
  126. qDebug() << "Error creating EBO";
  127. return;
  128. }
  129. glBindBuffer(GL_ARRAY_BUFFER, VBO);
  130. glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  131. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
  132. glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
  133. // Load the first image after OpenGL initialization
  134. if (!imageFiles.isEmpty()) {
  135. loadImage(0);
  136. }
  137. }
  138. void paintGL() override {
  139. glClear(GL_COLOR_BUFFER_BIT);
  140. if (!texture || !texture->isCreated() || !shaderProgram || !shaderProgram->isLinked()) {
  141. qDebug() << "Cannot render: texture or shader error";
  142. return;
  143. }
  144. shaderProgram->bind();
  145. glBindBuffer(GL_ARRAY_BUFFER, VBO);
  146. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
  147. GLint posLoc = shaderProgram->attributeLocation("position");
  148. GLint texLoc = shaderProgram->attributeLocation("texCoord");
  149. glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
  150. glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
  151. glEnableVertexAttribArray(posLoc);
  152. glEnableVertexAttribArray(texLoc);
  153. QMatrix4x4 mvp;
  154. float imageAspect = static_cast<float>(image.width()) / image.height();
  155. float windowAspect = static_cast<float>(width()) / height();
  156. float scaleX = 1.0f;
  157. float scaleY = 1.0f;
  158. if (imageAspect > windowAspect) {
  159. scaleY = windowAspect / imageAspect;
  160. } else {
  161. scaleX = imageAspect / windowAspect;
  162. }
  163. mvp.scale(scaleX * zoomLevel, scaleY * zoomLevel, 1.0f);
  164. shaderProgram->setUniformValue("mvp", mvp);
  165. texture->bind();
  166. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
  167. glDisableVertexAttribArray(posLoc);
  168. glDisableVertexAttribArray(texLoc);
  169. shaderProgram->release();
  170. }
  171. void resizeGL(int w, int h) override {
  172. glViewport(0, 0, w, h);
  173. }
  174. void wheelEvent(QWheelEvent* event) override {
  175. float delta = event->angleDelta().y() > 0 ? 1.1f : 0.9f;
  176. zoomLevel *= delta;
  177. zoomLevel = qMax(0.1f, qMin(zoomLevel, 10.0f));
  178. update();
  179. }
  180. void keyPressEvent(QKeyEvent* event) override {
  181. if (event->key() == Qt::Key_Q) {
  182. window()->close();
  183. } else if (event->key() == Qt::Key_Left) {
  184. loadPreviousImage();
  185. } else if (event->key() == Qt::Key_Right) {
  186. loadNextImage();
  187. }
  188. QOpenGLWidget::keyPressEvent(event);
  189. }
  190. private:
  191. void loadImage(int index) {
  192. if (index < 0 || index >= imageFiles.size()) {
  193. return;
  194. }
  195. currentImageIndex = index;
  196. QString imagePath = imageFiles[index].absoluteFilePath();
  197. qDebug() << "Loading image:" << imagePath;
  198. if (!image.load(imagePath)) {
  199. qDebug() << "Failed to load image:" << imagePath;
  200. QMessageBox::critical(this, "Error", "Failed to load image: " + imagePath);
  201. return;
  202. }
  203. if (image.isNull()) {
  204. qDebug() << "The image is empty after loading";
  205. return;
  206. }
  207. image = image.convertToFormat(QImage::Format_ARGB32);
  208. qDebug() << "The image is successfully loaded. Size:" << image.size();
  209. zoomLevel = 1.0f; // Reset zoom when loading a new image
  210. updateTexture();
  211. update();
  212. }
  213. void loadNextImage() {
  214. if (currentImageIndex + 1 < imageFiles.size()) {
  215. loadImage(currentImageIndex + 1);
  216. }
  217. }
  218. void loadPreviousImage() {
  219. if (currentImageIndex - 1 >= 0) {
  220. loadImage(currentImageIndex - 1);
  221. }
  222. }
  223. void updateTexture() {
  224. makeCurrent();
  225. qDebug() << "Context active:" << (context() && context()->isValid());
  226. if (texture) {
  227. texture->destroy();
  228. delete texture;
  229. texture = nullptr;
  230. }
  231. if (!image.isNull()) {
  232. texture = new QOpenGLTexture(image);
  233. if (texture && texture->isCreated()) {
  234. texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
  235. texture->setMagnificationFilter(QOpenGLTexture::Linear);
  236. texture->generateMipMaps();
  237. qDebug() << "Texture updated";
  238. } else {
  239. qDebug() << "Texture creation error";
  240. delete texture;
  241. texture = nullptr;
  242. }
  243. }
  244. doneCurrent();
  245. }
  246. QImage image;
  247. QOpenGLShaderProgram* shaderProgram;
  248. QOpenGLTexture* texture;
  249. unsigned int VBO, EBO;
  250. float zoomLevel;
  251. QFileInfoList imageFiles;
  252. int currentImageIndex;
  253. };
  254. class ImageViewer : public QMainWindow {
  255. Q_OBJECT
  256. public:
  257. ImageViewer(const QString& path, QWidget* parent = nullptr)
  258. : QMainWindow(parent) {
  259. setWindowTitle(" grxiv");
  260. resize(800, 600);
  261. ImageGLWidget* glWidget = new ImageGLWidget(path, this);
  262. setCentralWidget(glWidget);
  263. }
  264. };
  265. int main(int argc, char* argv[]) {
  266. QApplication app(argc, argv);
  267. QCommandLineParser parser;
  268. parser.addHelpOption();
  269. parser.addPositionalArgument("path", "Path to image file or directory");
  270. parser.process(app);
  271. QStringList args = parser.positionalArguments();
  272. if (args.isEmpty()) {
  273. QMessageBox::critical(nullptr, "Error", "No path specified.\nUsage: grxiv <path_to_image_or_directory>");
  274. return 1;
  275. }
  276. ImageViewer viewer(args[0]);
  277. viewer.show();
  278. return app.exec();
  279. }
  280. #include "grxiv.moc"