import PDF loading code from the OHP3D private prototype v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed, 27 Dec 2023 10:34:12 +0100
branchv_0
changeset 30 02972f051744
parent 29 dc3c102e1264
child 31 ed3caeea978a
import PDF loading code from the OHP3D private prototype
CLIParser.h
Configuration.h
ImageLoader.cpp
ImageLoader.h
Makefile
OHP3D.cpp
PageBuffer.h
Texture.cpp
bash-completion.sh
nbproject/configurations.xml
--- a/CLIParser.h	Wed Dec 27 01:50:38 2023 +0100
+++ b/CLIParser.h	Wed Dec 27 10:34:12 2023 +0100
@@ -25,7 +25,7 @@
 class CLIParser {
 private:
 
-	static const std::string OPTION_TEXTURE;
+	static const std::string OPTION_DOCUMENT;
 	static const std::string OPTION_SHADER;
 	static const std::string OPTION_BACKGROUND_COLOR;
 	static const std::string OPTION_ROOT_WINDOW;
@@ -69,10 +69,10 @@
 		for (int i = 0; i < arguments.size();) {
 			const std::string& option = readNext(arguments, i);
 
-			if (option == OPTION_TEXTURE) {
-				Configuration::Texture tex;
-				tex.fileName = readNext(arguments, i);
-				c.textures.push_back(tex);
+			if (option == OPTION_DOCUMENT) {
+				Configuration::Document document;
+				document.fileName = readNext(arguments, i);
+				c.documents.push_back(document);
 			} else if (option == OPTION_SHADER) {
 				const auto type = readNext(arguments, i);
 				const auto file = readNext(arguments, i);
@@ -88,7 +88,7 @@
 	}
 };
 
-const std::string CLIParser::OPTION_TEXTURE = "--texture";
+const std::string CLIParser::OPTION_DOCUMENT = "--document";
 const std::string CLIParser::OPTION_SHADER = "--shader";
 const std::string CLIParser::OPTION_BACKGROUND_COLOR = "--background-color";
 const std::string CLIParser::OPTION_ROOT_WINDOW = "--root-window";
--- a/Configuration.h	Wed Dec 27 01:50:38 2023 +0100
+++ b/Configuration.h	Wed Dec 27 10:34:12 2023 +0100
@@ -29,7 +29,7 @@
 		std::string fileName;
 	};
 
-	class Texture : public File {
+	class Document : public File {
 	};
 
 	class Shader : public File {
@@ -43,8 +43,8 @@
 
 	};
 
-	// TODO: support loading whole directory and monitoring using inotify
-	std::vector<Texture> textures;
+	double dpi = 600.;
+	std::vector<Document> documents;
 	std::vector<Shader> shaders;
 
 	unsigned long backgroundColor = (0x33 << 16 | 0x33 << 8 | 0x33);
--- a/ImageLoader.cpp	Wed Dec 27 01:50:38 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * OHP3D
- * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <iostream>
-
-#include <Magick++.h>
-
-#include "ImageLoader.h"
-#include "AllocatedBuffer.h"
-
-ImageLoader::ImageBuffer* ImageLoader::loadImage(const Buffer& input) {
-	Magick::Blob inputBlob(input.getData(), input.getSize());
-	Magick::Image image(inputBlob);
-	// Magick::Image image("magick:LOGO");
-	// Magick::Image image("magick:WIZARD");
-	// Magick::Image image("https://.../remote-image.png");
-	// Magick::Image image("x:root"); // whole desktop
-	// Magick::Image image("x:0x6a0001c"); // single window, use e.g. xwininfo
-
-	// Magick::PixelPacket* parsedPixels = parsedImage.getPixels(
-	//     0, 0, parsedImage.size().width(), parsedImage.size().height());
-	
-	Magick::Blob outputBlob;
-	image.magick("RGBA");
-	image.depth(8); // TODO: pass more bits to OpenGL?
-	image.write(&outputBlob);
-
-	ImageBuffer* outputBuffer = new ImageBuffer(
-			(void*) outputBlob.data(),
-			outputBlob.length(),
-			image.size().width(),
-			image.size().height());
-
-	return outputBuffer;
-}
\ No newline at end of file
--- a/ImageLoader.h	Wed Dec 27 01:50:38 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/**
- * OHP3D
- * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <string.h>
-
-#include "Buffer.h"
-#include "AllocatedBuffer.h"
-
-class ImageLoader {
-public:
-
-	class ImageBuffer : public AllocatedBuffer {
-	public:
-		int width;
-		int height;
-
-		ImageBuffer(void* buffer, const size_t size,
-				const int width, const int height) :
-				AllocatedBuffer(width * height * 4),
-				width(width), height(height) {
-			if (size == width * height * 4) memcpy(data, buffer, size);
-			else throw std::invalid_argument("invalid image size");
-		}
-
-	};
-
-	ImageBuffer* loadImage(const Buffer& inputBuffer);
-};
--- a/Makefile	Wed Dec 27 01:50:38 2023 +0100
+++ b/Makefile	Wed Dec 27 10:34:12 2023 +0100
@@ -26,7 +26,6 @@
 SRC= \
     OHP3D.cpp \
     ohp3d.cpp \
-    ImageLoader.cpp \
     Texture.cpp \
     Shader.cpp \
     Program.cpp \
@@ -36,4 +35,4 @@
 build/ohp3d: $(SRC) *.h
 	mkdir -p build
 	$(CXX) -std=c++20 -g -o $(@) $(SRC) $$(pkg-config --cflags --libs \
-	    epoxy x11 glu glm Magick++)
+	    epoxy x11 glu glm poppler-cpp)
--- a/OHP3D.cpp	Wed Dec 27 01:50:38 2023 +0100
+++ b/OHP3D.cpp	Wed Dec 27 10:34:12 2023 +0100
@@ -23,13 +23,21 @@
 #include <functional>
 #include <sstream>
 #include <vector>
+#include <chrono>
+#include <unistd.h>
+
+#include <poppler/cpp/poppler-document.h>
+#include <poppler/cpp/poppler-page.h>
+#include <poppler/cpp/poppler-toc.h>
+#include <sys/stat.h>
+#include <poppler/cpp/poppler-page-renderer.h>
 
 #include "x11.h"
 #include "opengl.h"
 #include "EPoll.h"
 #include "Logger.h"
 #include "MappedFile.h"
-#include "ImageLoader.h"
+#include "PageBuffer.h"
 #include "Texture.h"
 #include "Shader.h"
 #include "Program.h"
@@ -166,7 +174,6 @@
 
 	FileMonitor fileMonitor;
 	std::vector<WatchedFile> watchedFiles;
-	ImageLoader imageLoader;
 	std::vector<std::shared_ptr<Shader>> shaders;
 	std::shared_ptr<Program> shaderProgram;
 	std::vector<std::shared_ptr<Texture>> textures;
@@ -184,9 +191,7 @@
 	void log(LogLevel level, std::string message);
 	int setNonBlocking(int fd);
 	void loadVertices();
-	void parametrizeTexture(std::shared_ptr<Texture> tex);
-	bool reloadTexture(const std::string& fileName);
-	void loadTextures();
+	void loadDocuments();
 	void loadShaders();
 	void updateVariableLocations();
 	bool reloadShader(const std::string& fileName);
@@ -271,9 +276,8 @@
 	glXSwapBuffers(dpy, win);
 
 
-	// Load GLSL shaders:
 	loadShaders();
-	loadTextures();
+	loadDocuments();
 	loadVertices();
 
 	auto toggleFullscreen = [&]() {
@@ -409,7 +413,6 @@
 							<< " mask=" << fe.mask
 							<< std::endl;
 					try {
-						redraw |= reloadTexture(fe.fileName);
 						redraw |= reloadShader(fe.fileName);
 						setTitle();
 					} catch (const std::exception& e) {
@@ -548,66 +551,57 @@
 	}
 }
 
-void OHP3D::Impl::parametrizeTexture(std::shared_ptr<Texture> tex) {
-	XAttrs xa(tex->getFileName());
-	std::string magf = xa["ohp3d.texture.mag-filter"];
-	std::string minf = xa["ohp3d.texture.min-filter"];
-	std::string scale = xa["ohp3d.texture.scale"];
+void OHP3D::Impl::loadDocuments() {
+	for (const Configuration::File& document : cfg.documents) {
+		std::cerr << "loading document: " << document.fileName.c_str() << "\n";
+
+		MappedFile buffer(document.fileName);
 
-	auto GLT2D = GL_TEXTURE_2D;
-	auto MAG = GL_TEXTURE_MAG_FILTER;
-	auto MIN = GL_TEXTURE_MIN_FILTER;
+		namespace pp = poppler;
+		using Document = pp::document;
+
+		std::shared_ptr<Document> doc = std::shared_ptr<Document>(
+				Document::load_from_raw_data(buffer.getData(), buffer.getSize()));
 
-	if (magf == "linear") glTexParameteri(GLT2D, MAG, GL_LINEAR);
-	else if (magf == "nearest") glTexParameteri(GLT2D, MAG, GL_NEAREST);
-
-	if (minf == "linear") glTexParameteri(GLT2D, MIN, GL_LINEAR);
-	else if (minf == "nearest") glTexParameteri(GLT2D, MIN, GL_NEAREST);
+		log(LogLevel::INFO, std::string("PDF parsed:")
+				+ " pages=" + std::to_string(doc->pages()));
 
-	if (scale.size()) {
-		float sc;
-		if (std::from_chars(scale.data(), scale.data() + scale.size(), sc).ec
-				== std::errc{}) tex->setScale(sc);
-		else std::cerr << "Invalid texture scale value - expecting float\n";
-		// tex->setScale(std::stof(scale)); // locale-dependent (. vs ,)
-	}
-}
+		log(LogLevel::INFO, "loadig pages to textures...");
+		auto timingStart = std::chrono::steady_clock::now();
+		pp::page_renderer renderer;
+		renderer.set_image_format(pp::image::format_rgb24);
+		double dpi = cfg.dpi;
+		for (int i = 0, limit = doc->pages(); i < limit; i++) {
+			std::shared_ptr<pp::page> page(doc->create_page(i));
+			pp::image pageImage = renderer.render_page(page.get(), dpi, dpi);
 
-void OHP3D::Impl::loadTextures() {
-	// Load default texture if there is no configured:
-	if (cfg.textures.empty())
-		cfg.textures.push_back({getDefaultFile("textures/default.png")});
+			PageBuffer img(
+					pageImage.data(),
+					pageImage.bytes_per_row() * pageImage.height(),
+					pageImage.width(), pageImage.height());
+
+			textures.push_back(std::make_shared<Texture>(
+					pageImage.width(), pageImage.height(),
+					img, document.fileName));
+			log(LogLevel::INFO, "  page " + std::to_string(i + 1)
+					+ "/" + std::to_string(limit));
+		}
 
-	for (const Configuration::Texture& tex : cfg.textures) {
-		std::shared_ptr<ImageLoader::ImageBuffer>
-				img(imageLoader.loadImage(MappedFile(tex.fileName)));
-		textures.push_back(std::make_shared<Texture>(
-				img->width, img->height, *img, tex.fileName));
-		parametrizeTexture(textures.back());
-		// static const uint32_t watchMask = IN_CLOSE_WRITE | IN_ATTRIB;
-		// watchedFiles.push_back(fileMonitor.watch(tex.fileName, watchMask));
-		watchedFiles.push_back(fileMonitor.watch(tex.fileName));
-		// TODO: review texture loading and binding
-		// works even without this - default texture
-		// glUniform1i(..., ...);
-		// checkError(&std::cerr);
+		auto timingEnd = std::chrono::steady_clock::now();
+		auto timingTotal = std::chrono::duration_cast<std::chrono::microseconds>
+				(timingEnd - timingStart).count();
+
+		std::stringstream timingMsg;
+		timingMsg.imbue(std::locale(""));
+		timingMsg << "PDF to texture load time:"
+				<< " page average = " << (timingTotal / doc->pages()) << " µs,"
+				<< " total = " << timingTotal << " µs ("
+				<< doc->pages() << " pages)";
+		log(LogLevel::INFO, timingMsg.str());
+		if (timingTotal < 400000) logOutput << ">>> Doherty threshold met! <<<\n";
 	}
 }
 
-bool OHP3D::Impl::reloadTexture(const std::string& fileName) {
-	for (std::shared_ptr<Texture> tex : textures) {
-		if (tex->getFileName() == fileName) {
-			std::shared_ptr<ImageLoader::ImageBuffer>
-					img(imageLoader.loadImage(MappedFile(fileName)));
-			tex->update(img->width, img->height, *img);
-			parametrizeTexture(tex);
-			loadVertices();
-			return true;
-		}
-	}
-	return false;
-}
-
 void OHP3D::Impl::loadShaders() {
 	// Vertex Array Object (VAO)
 	GLuint vao;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PageBuffer.h	Wed Dec 27 10:34:12 2023 +0100
@@ -0,0 +1,37 @@
+/**
+ * OHP3D
+ * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <string.h>
+
+#include "AllocatedBuffer.h"
+
+class PageBuffer : public AllocatedBuffer {
+public:
+	int width;
+	int height;
+
+	PageBuffer(void* buffer, const size_t size,
+			const int width, const int height) :
+			AllocatedBuffer(width * height * 3),
+			width(width), height(height) {
+		if (size == width * height * 3) memcpy(data, buffer, size);
+		else throw std::invalid_argument("invalid image size");
+	}
+
+};
--- a/Texture.cpp	Wed Dec 27 01:50:38 2023 +0100
+++ b/Texture.cpp	Wed Dec 27 10:34:12 2023 +0100
@@ -77,19 +77,19 @@
 	impl->width = width;
 	impl->height = height;
 
-	if (img.getSize() != impl->width * impl->height * 4)
+	if (img.getSize() != impl->width * impl->height * 3)
 		throw std::invalid_argument("invalid image size");
 
 	glBindTexture(GL_TEXTURE_2D, impl->id);
 	auto GLT2D = GL_TEXTURE_2D;
-	glTexImage2D(GLT2D, 0, GL_RGBA,
+	glTexImage2D(GLT2D, 0, GL_RGB,
 			impl->width, impl->height,
-			0, GL_RGBA, GL_UNSIGNED_BYTE,
+			0, GL_RGB, GL_UNSIGNED_BYTE,
 			img.getData());
 	glTexParameteri(GLT2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 	glTexParameteri(GLT2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 	glTexParameteri(GLT2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // blurry upscale
-	glTexParameteri(GLT2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // sharp upscale
+	// glTexParameteri(GLT2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); sharp upscale
 	glTexParameteri(GLT2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 	glGenerateMipmap(GLT2D);
 	checkError(&std::cerr);
--- a/bash-completion.sh	Wed Dec 27 01:50:38 2023 +0100
+++ b/bash-completion.sh	Wed Dec 27 10:34:12 2023 +0100
@@ -33,7 +33,7 @@
 		"fragment"
 	)
 
-	if   [[ "$w1" == "--texture" ]]; then
+	if [[ "$w1" == "--document" ]]; then
 		COMPREPLY=($(compgen -f -- "$w0"; compgen -d -S / -- "$w0"))
 	elif [[ "$w1" == "--shader" ]]; then
 		COMPREPLY=($(compgen -W "${SHADER_TYPE[*]}" -- "$w0"))
@@ -43,7 +43,7 @@
 		COMPREPLY=($(compgen -W "${COLOR[*]}" -- "$w0"))
 	else
 		local OPTIONS=(
-			"--texture"
+			"--document"
 			"--shader"
 			"--background-color"
 			"--root-window"
--- a/nbproject/configurations.xml	Wed Dec 27 01:50:38 2023 +0100
+++ b/nbproject/configurations.xml	Wed Dec 27 10:34:12 2023 +0100
@@ -3,10 +3,9 @@
   <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
     <df root="." name="0">
       <in>FileMonitor.cpp</in>
-      <in>ImageLoader.cpp</in>
+      <in>OHP3D.cpp</in>
       <in>Program.cpp</in>
       <in>Shader.cpp</in>
-      <in>OHP3D.cpp</in>
       <in>Texture.cpp</in>
       <in>XAttrs.cpp</in>
       <in>ohp3d.cpp</in>
@@ -46,6 +45,8 @@
             <incDir>
               <pElem>/usr/include/x86_64-linux-gnu/ImageMagick-6</pElem>
               <pElem>/usr/include/ImageMagick-6</pElem>
+              <pElem>/usr/include/poppler/cpp</pElem>
+              <pElem>/usr/include/poppler</pElem>
             </incDir>
             <preprocessorList>
               <Elem>MAGICKCORE_HDRI_ENABLE=0</Elem>
@@ -62,7 +63,7 @@
         <ccTool flags="0">
         </ccTool>
       </item>
-      <item path="ImageLoader.cpp" ex="false" tool="1" flavor2="0">
+      <item path="OHP3D.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>
       </item>
@@ -74,10 +75,6 @@
         <ccTool flags="0">
         </ccTool>
       </item>
-      <item path="OHP3D.cpp" ex="false" tool="1" flavor2="0">
-        <ccTool flags="0">
-        </ccTool>
-      </item>
       <item path="Texture.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>