ソースを参照

improving jpeg support

Nicolas Winkler 5 年 前
コミット
419373142b

+ 1 - 1
CMakeLists.txt

@@ -71,7 +71,7 @@ ELSEIF (UNIX AND NOT APPLE)
     install(TARGETS Almond RUNTIME DESTINATION "bin")
     install(FILES Almond.desktop DESTINATION "share/applications")
     install(FILES Almond.png DESTINATION "share/pixmaps")
-    install(FILES LICENSE DESTINATION "share/doc/almond/" RENAME "copyright")
+    install(FILES debian/copyright DESTINATION "share/doc/almond/" RENAME "copyright")
     set(CPACK_GENERATOR "DEB")
     set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE)
     set(CPACK_STRIP_FILES TRUE)

+ 15 - 2
exportdialogs.cpp

@@ -52,10 +52,23 @@ QString ExportImageDialog::getPath(void) const
 
 void ExportImageDialog::on_pushButton_clicked()
 {
+    std::string formatString = "";
+    if (alm::supportsImageFormat(alm::ImageFormat::PNG)) {
+        formatString += "PNG image (*.png);;";
+    }
+    if (alm::supportsImageFormat(alm::ImageFormat::JPEG)) {
+        formatString += "JPEG image (*.jpg *.jpeg)";
+    }
+    if (alm::supportsImageFormat(alm::ImageFormat::BMP)) {
+        formatString += "BMP image (*.bmp *.dib);;";
+    }
+    if (!formatString.empty()) {
+        formatString.erase(formatString.end() - 2, formatString.end());
+    }
+
     QString saveAs = QFileDialog::getSaveFileName(this,
             tr("Save exported image"), "",
-            //tr("PNG image (*.png);;JPEG image (*.jpg);;All Files (*)"));
-            tr("PNG image (*.png)"));
+            tr(formatString.c_str()));
     if(!saveAs.isEmpty() && !saveAs.isNull())
         eid.savePath->setText(saveAs);
     this->repaint();

+ 22 - 13
libalmond/CMakeLists.txt

@@ -12,6 +12,7 @@ add_subdirectory(../libmandel ./libmandel)
 set(Boost_USE_STATIC_LIBS ON)
 find_package(Boost 1.65 REQUIRED)
 find_package(PNG)
+find_package (JPEG)
 
 set(CMAKE_CXX_STANDARD 17)
 
@@ -28,26 +29,34 @@ target_include_directories(libalmond SYSTEM PUBLIC ${FFMPEG_INCLUDE_DIRS})
 target_link_libraries(libalmond PUBLIC ${FFMPEG_LIBRARIES})
 
 if (PNG_FOUND)
-    target_include_directories(libalmond SYSTEM PUBLIC ${PNG_INCLUDE_DIRS})
-    target_link_libraries(libalmond PUBLIC ${PNG_LIBRARIES})
+    #target_include_directories(libalmond SYSTEM PUBLIC ${PNG_INCLUDE_DIRS})
+    #target_link_libraries(libalmond PUBLIC ${PNG_LIBRARIES})
+    target_link_libraries(libalmond PUBLIC PNG::PNG)
 else()
     set(PNG_BUILD_ZLIB ON CACHE BOOL "build zlib ourselves")
     add_subdirectory(zlib-1.2.11)
     set(ZLIB_LIBRARY zlibstatic)
-    foreach(header ${ZLIB_PUBLIC_HDRS})
-        get_filename_component(the_incluude ${header} DIRECTORY)
-        list(APPEND ZLIB_PUB_INCLUDE ${the_incluude})
-    endforeach()
-    set(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib-1.2.11 ${ZLIB_PUB_INCLUDE} )
+    #foreach(header ${ZLIB_PUBLIC_HDRS})
+    #    get_filename_component(the_incluude ${header} DIRECTORY)
+    #    list(APPEND ZLIB_PUB_INCLUDE ${the_incluude})
+    #endforeach()
+    #set(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib-1.2.11 ${ZLIB_PUB_INCLUDE} )
     
     set(SKIP_INSTALL_ALL ON)
     add_subdirectory(lpng1637)
-    foreach(header ${libpng_public_hdrs})
-        get_filename_component(the_incluude ${header} DIRECTORY)
-        list(APPEND PNG_PUB_INCLUDE ${the_incluude})
-    endforeach()
-    target_include_directories(libalmond PRIVATE ${PNG_PUB_INCLUDE})
-    target_include_directories(libalmond PRIVATE ${ZLIB_INCLUDE_DIR})
+    #foreach(header ${libpng_public_hdrs})
+    #    get_filename_component(the_incluude ${header} DIRECTORY)
+    #    list(APPEND PNG_PUB_INCLUDE ${the_incluude})
+    #endforeach()
+    target_link_libraries(png_static PRIVATE zlibstatic)
+    #target_include_directories(libalmond PRIVATE ${PNG_PUB_INCLUDE})
+    #target_include_directories(libalmond PRIVATE ${ZLIB_INCLUDE_DIR})
     target_link_libraries(libalmond PRIVATE png_static)
 endif()
 
+if (JPEG_FOUND AND WITH_LIBJPEG)
+    target_link_libraries(libalmond PRIVATE JPEG::JPEG)
+    target_compile_definitions(libalmond PUBLIC WITH_LIBJPEG)
+endif()
+
+

+ 53 - 0
libalmond/include/ImageExport.h

@@ -4,15 +4,30 @@
 #include "Mandel.h"
 #include "Gradient.h"
 #include <functional>
+#include <vector>
 #include <stdexcept>
 
 namespace alm
 {
+    enum class ImageFormat
+    {
+        BMP,
+        PNG,
+        JPEG
+    };
+    bool supportsImageFormat(ImageFormat imgf);
+
+    struct ImageOptions
+    {
+        int jpegQuality = 80;
+    };
+
     struct ImageExportInfo
     {
         mnd::MandelInfo drawInfo;
         mnd::MandelGenerator* generator;
         Gradient gradient;
+        ImageOptions options;
         std::string path;
     };
 
@@ -22,6 +37,19 @@ namespace alm
         ImageExportException(const std::string& err);
     };
 
+
+    /**
+     * \brief generates and saves a fractal image. The format
+     *        will be guessed by the file extension
+     * 
+     * \param iei               info to generate the image
+     * \param progressCallback  optional function that is called to
+     *                          report progress; the float parameter
+     *                          contains a value from 0 to 100
+     */
+    void exportImage(const ImageExportInfo& iei,
+        std::function<void(float)> progressCallback = [](float){});
+
     /**
      * \brief generates and saves a fractal image in png format.
      * 
@@ -32,6 +60,31 @@ namespace alm
      */
     void exportPng(const ImageExportInfo& iei,
         std::function<void(float)> progressCallback = [](float){});
+
+
+#ifdef WITH_LIBJPEG
+    /**
+     * \brief generates and saves a fractal image in jpeg format.
+     * 
+     * \param iei               info to generate the image
+     * \param progressCallback  optional function that is called to
+     *                          report progress; the float parameter
+     *                          contains a value from 0 to 100
+     */
+    void exportJpeg(const ImageExportInfo& iei,
+        std::function<void(float)> progressCallback = [](float){});
+#endif // WITH_LIBJPEG
+
+    /**
+     * \brief generates and saves a fractal image in jpeg format.
+     * 
+     * \param iei               info to generate the image
+     * \param progressCallback  optional function that is called to
+     *                          report progress; the float parameter
+     *                          contains a value from 0 to 100
+     */
+    void exportBmp(const ImageExportInfo& iei,
+        std::function<void(float)> progressCallback = [](float){});
 }
 
 

+ 260 - 1
libalmond/src/ImageExport.cpp

@@ -1,16 +1,69 @@
 #include "ImageExport.h"
 #include "Bitmap.h"
-#include <png.h>
+#include <boost/endian/buffers.hpp>
+extern "C" {
+#   include <png.h>
+}
 #include <cstdio>
 
+#ifdef WITH_LIBJPEG
+extern "C" {
+#   include <jpeglib.h>
+}
+#endif // WITH_LIBJPEG
+
 namespace alm
 {
 
+
+const std::vector<ImageFormat> supportedImageFormats = {
+    ImageFormat::BMP,
+    ImageFormat::PNG,
+#ifdef WITH_LIBJPEG
+    ImageFormat::JPEG,
+#endif
+};
+
+bool supportsImageFormat(ImageFormat imgf)
+{
+    for (auto f : supportedImageFormats) {
+        if (imgf == f)
+            return true;
+    }
+    return false;
+}
+
 ImageExportException::ImageExportException(const std::string& err) :
     std::runtime_error{ err }
 {
 }
 
+void exportImage(const ImageExportInfo& iei, std::function<void(float)> progressCallback)
+{
+    auto hasSuffix = [] (const std::string& path, const std::string& suffix) -> bool
+    {
+        return path.size() >= suffix.size() &&
+               path.compare(path.size() - suffix.size(),
+                        suffix.size(), suffix) == 0;
+    };
+    if (hasSuffix(iei.path, ".bmp") ||
+        hasSuffix(iei.path, ".dib")) {
+        exportBmp(iei, progressCallback);
+    }
+    else if (hasSuffix(iei.path, ".png")) {
+        exportPng(iei, progressCallback);
+    }
+#ifdef WITH_LIBJPEG
+    else if (hasSuffix(iei.path, ".jpg") ||
+             hasSuffix(iei.path, ".jpeg")) {
+        exportJpeg(iei, progressCallback);
+    }
+#endif // WITH_LIBJPEG
+    else {
+        throw ImageExportException{ "Could not guess image format" };
+    }
+}
+
 void exportPng(const ImageExportInfo& iei, std::function<void(float)> progressCallback)
 {
     if (iei.generator == nullptr) {
@@ -90,6 +143,212 @@ void exportPng(const ImageExportInfo& iei, std::function<void(float)> progressCa
 }
 
 
+#ifdef WITH_LIBJPEG
+void exportJpeg(const ImageExportInfo& iei, std::function<void(float)> progressCallback)
+{
+    if (iei.generator == nullptr) {
+        throw "no generator";
+    }
+
+    progressCallback(0.0f);
+
+    mnd::MandelGenerator& generator = *iei.generator;
+    FILE* file = fopen(iei.path.c_str(), "wb");
+    if(!file)
+        throw ImageExportException{ std::string("could not open file '") + iei.path + "'" };
+
+    const long width = iei.drawInfo.bWidth;
+    const long height = iei.drawInfo.bHeight;
+
+    struct jpeg_compress_struct jpegInfo;
+    struct jpeg_error_mgr jerr;
+
+    int rowLength = width * 3;
+
+    jpegInfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = [](j_common_ptr cinfo) {
+        auto errmsg = std::make_unique<char[]>(JMSG_LENGTH_MAX);
+        (cinfo->err->format_message)(cinfo, errmsg.get());
+        throw ImageExportException{ std::string("libjpeg error: ") + errmsg.get() };
+    };
+    jpeg_create_compress(&jpegInfo);
+
+    jpeg_stdio_dest(&jpegInfo, file);
+
+    jpegInfo.image_width = width;
+    jpegInfo.image_height = height;
+    jpegInfo.input_components = 3;
+    jpegInfo.in_color_space = JCS_RGB;
+
+    jpeg_set_defaults(&jpegInfo);
+    jpeg_set_quality(&jpegInfo, iei.options.jpegQuality, TRUE);
+    jpeg_start_compress(&jpegInfo, TRUE);
+
+
+    long chunkHeight = height / 20;
+    if (chunkHeight < 1)
+        chunkHeight = 1;
+    while (width * chunkHeight > 512 * 512)
+        chunkHeight /= 2;
+
+    auto rowPointers = std::make_unique<JSAMPROW[]>(chunkHeight);
+    for (long chunkY = 0; chunkY < height; chunkY += chunkHeight) {
+        auto minimum = [] (const auto& a, const auto& b) { return a < b ? a : b; };
+        long tmpHeight = minimum(chunkHeight, height - chunkY);
+        mnd::MandelInfo chunkInfo = iei.drawInfo;
+        chunkInfo.bHeight = tmpHeight;
+        chunkInfo.view.y += chunkInfo.view.height * chunkY / height;
+        chunkInfo.view.height *= mnd::Real(tmpHeight) / height;
+
+        Bitmap<float> chunk(width, tmpHeight);
+        generator.generate(chunkInfo, chunk.pixels.get());
+        Bitmap<RGBColor> coloredChunk = chunk.map<RGBColor>([&iei] (float i) {
+            return i >= iei.drawInfo.maxIter ? RGBColor{ 0, 0, 0 } : iei.gradient.get(i);
+        });
+        for (long i = 0; i < tmpHeight; i++) {
+            rowPointers[i] = reinterpret_cast<JSAMPROW>(&coloredChunk.get(0, i));
+        }
+        int written = jpeg_write_scanlines(&jpegInfo, rowPointers.get(), tmpHeight);
+        if (written < tmpHeight) {
+            throw ImageExportException{ "error writing jpeg rows" };
+        }
+        if (chunkY < height)
+            progressCallback(100.0f * chunkY / height);
+    }
+
+    jpeg_finish_compress(&jpegInfo);
+    fclose(file);
+    jpeg_destroy_compress(&jpegInfo);
+    progressCallback(100.0f);
+}
+
+#endif // WITH_LIBJPEG
+
+
+void exportBmp(const ImageExportInfo& iei, std::function<void(float)> progressCallback)
+{
+    if (iei.generator == nullptr) {
+        throw "no generator";
+    }
+
+    progressCallback(0.0f);
+
+    mnd::MandelGenerator& generator = *iei.generator;
+    FILE* file = fopen(iei.path.c_str(), "wb");
+    if(!file)
+        throw ImageExportException{ std::string("could not open file '") + iei.path + "'" };
+
+    const long width = iei.drawInfo.bWidth;
+    const long height = iei.drawInfo.bHeight;
+
+    // bmp needs rows to be 4-byte-aligned
+    const long rowLength = (width * 3 + 3) & ~0x03;
+
+    using namespace boost::endian;
+
+    const int fileHeaderSize = 14;
+    const int infoHeaderSize = 40;
+    const int pixelSize = rowLength * height;
+
+    // file header
+    little_int8_buf_t fhType[2];
+    little_uint32_buf_t fhSize;
+    little_uint16_buf_t fhReserved1;
+    little_uint16_buf_t fhReserved2;
+    little_uint32_buf_t fhOffset;
+    fhType[0] = 0x42; // 'B'
+    fhType[1] = 0x4D; // 'M'
+    fhSize = fileHeaderSize + infoHeaderSize + pixelSize;
+    fhReserved1 = 0;
+    fhReserved1 = 2;
+    fhOffset = fileHeaderSize + infoHeaderSize;
+
+    // info header
+    little_uint32_buf_t ihSize;
+    little_int32_buf_t ihWidth;
+    little_int32_buf_t ihHeight; // for top-down, height must be negative
+    little_uint16_buf_t ihPlanes;
+    little_uint16_buf_t ihBitCount;
+    little_uint32_buf_t ihCompression; // 0 = no compression
+    little_uint32_buf_t ihSizeImage; // for uncompressed bmps 0 can be set here
+    little_int32_buf_t ihXPixelsPerMeter;
+    little_int32_buf_t ihYPixelsPerMeter;
+    little_uint32_buf_t ihClrUsed;
+    little_uint32_buf_t ihClrImportant;
+    ihSize = infoHeaderSize;
+    ihWidth = width;
+    ihHeight = -height; // for top-down, height must be negative
+    ihPlanes = 1;
+    ihBitCount = 24;
+    ihCompression = 0; // 0 = no compression
+    ihSizeImage = 0; // for uncompressed bmps 0 can be set here
+    ihXPixelsPerMeter = 0;
+    ihYPixelsPerMeter = 0;
+    ihClrUsed = 0;
+    ihClrImportant = 0;
+    
+    auto writeCmp = [&file] (auto& buf) {
+        std::fwrite(&buf, sizeof(buf), 1, file);
+    };
+
+    writeCmp(fhType[0]);
+    writeCmp(fhType[1]);
+    writeCmp(fhSize);
+    writeCmp(fhReserved1);
+    writeCmp(fhReserved2);
+    writeCmp(fhOffset);
+
+    writeCmp(ihSize);
+    writeCmp(ihWidth);
+    writeCmp(ihHeight);
+    writeCmp(ihPlanes);
+    writeCmp(ihBitCount);
+    writeCmp(ihCompression);
+    writeCmp(ihSizeImage);
+    writeCmp(ihXPixelsPerMeter);
+    writeCmp(ihYPixelsPerMeter);
+    writeCmp(ihClrUsed);
+    writeCmp(ihClrImportant);
+
+    long chunkHeight = height / 20;
+    if (chunkHeight < 1)
+        chunkHeight = 1;
+    while (width * chunkHeight > 512 * 512)
+        chunkHeight /= 2;
+
+    for (long chunkY = 0; chunkY < height; chunkY += chunkHeight) {
+        auto minimum = [] (const auto& a, const auto& b) { return a < b ? a : b; };
+        long tmpHeight = minimum(chunkHeight, height - chunkY);
+        mnd::MandelInfo chunkInfo = iei.drawInfo;
+        chunkInfo.bHeight = tmpHeight;
+        chunkInfo.view.y += chunkInfo.view.height * chunkY / height;
+        chunkInfo.view.height *= mnd::Real(tmpHeight) / height;
+
+        Bitmap<float> chunk(width, tmpHeight);
+        generator.generate(chunkInfo, chunk.pixels.get());
+        for (long i = 0; i < tmpHeight; i++) {
+            for (long j = 0; j < width; j++) {
+                float iters = chunk.get(j, i);
+                RGBColor color =
+                    iters >= iei.drawInfo.maxIter ?
+                        RGBColor{ 0, 0, 0 } :
+                        iei.gradient.get(iters);
+                uint8_t bgr[3] = { color.b, color.g, color.r };
+                fwrite(bgr, 1, 3, file);
+            }
+
+            // write line padding
+            const uint8_t zeroes[] = { 0, 0, 0, 0 };
+            fwrite(zeroes, 1, rowLength - width * 3, file);
+        }
+        if (chunkY < height)
+            progressCallback(100.0f * chunkY / height);
+    }
+
+    fflush(file);
+    fclose(file);
+    progressCallback(100.0f);
+}
 }
 
 

+ 7 - 3
libmandel/CMakeLists.txt

@@ -18,6 +18,7 @@ find_package(OpenMP)
 #set(Boost_DEBUG 1)
 set(Boost_USE_STATIC_LIBS ON)
 set(ASMJIT_STATIC ON)
+set(ASMJIT_EMBED ON)
 find_package(Boost 1.65 REQUIRED)
 
 
@@ -77,9 +78,12 @@ target_include_directories(qd PUBLIC qd-2.3.22/include qd-2.3.22)
 target_link_libraries(mandel PUBLIC qd)
 
 if(WITH_ASMJIT)
-	add_subdirectory(asmjit)
-	target_compile_definitions(mandel PUBLIC WITH_ASMJIT)
-	target_link_libraries(mandel PUBLIC asmjit)
+    set(ASMJIT_LIBS "" PARENT_SCOPE)
+    add_subdirectory(asmjit)
+    message("depps " ${ASMJIT_LIBS})
+    target_compile_definitions(mandel PUBLIC WITH_ASMJIT)
+    target_link_libraries(mandel PUBLIC asmjit)
+    target_link_libraries(mandel PRIVATE ${ASMJIT_LIBS})
 endif(WITH_ASMJIT)
 
 

+ 1 - 1
libmandel/asmjit/CMakeLists.txt

@@ -220,7 +220,7 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
   list(APPEND ASMJIT_DEPS rt)
 endif()
 
-set(ASMJIT_LIBS ${ASMJIT_DEPS})
+set(ASMJIT_LIBS ${ASMJIT_DEPS} PARENT_SCOPE)
 if (NOT ASMJIT_EMBED)
   list(INSERT ASMJIT_LIBS 0 asmjit)
 endif()

+ 3 - 3
libmandel/src/Mandel.cpp

@@ -280,7 +280,7 @@ std::vector<std::unique_ptr<MandelDevice>> MandelContext::createDevices(void)
 
         std::string ext = platform.getInfo<CL_PLATFORM_EXTENSIONS>();
         //printf("Platform extensions: %s\n", ext.c_str());
-        printf("Platform: %s, %s\n", platformName.c_str(), profile.c_str());
+        //printf("Platform: %s, %s\n", platformName.c_str(), profile.c_str());
 
         std::vector<cl::Device> devices;
         platform.getDevices(CL_DEVICE_TYPE_GPU, &devices);
@@ -293,9 +293,9 @@ std::vector<std::unique_ptr<MandelDevice>> MandelContext::createDevices(void)
         
         cl::Context context{ devices, nullptr, onError };
         for (auto& device : devices) {
-            printf("Device: %s\n", device.getInfo<CL_DEVICE_NAME>().c_str());
+            //printf("Device: %s\n", device.getInfo<CL_DEVICE_NAME>().c_str());
             //printf("preferred float width: %d\n", device.getInfo<CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT>());
-            printf("vendor: %s\n", device.getInfo<CL_DEVICE_VENDOR>().c_str());
+            //printf("vendor: %s\n", device.getInfo<CL_DEVICE_VENDOR>().c_str());
 
 
             //printf("Device extensions: %s\n", ext.c_str());

+ 14 - 10
mandelvid/src/main.cpp

@@ -27,7 +27,7 @@ int main() {
     //evi.end.zoomCenter(1.0e+27);
     evi.gradient = Gradient::defaultGradient();
 
-    evi.mi.bWidth = 1280;
+    /*evi.mi.bWidth = 1280;
     evi.mi.bHeight = 720;
     evi.mi.maxIter = 1000;
     evi.fps = 60;
@@ -40,24 +40,28 @@ int main() {
 
     MandelVideoGenerator mvg(evi);
 
-    mvg.generate(mndCtxt.getDefaultGenerator());
-    //
+    mvg.generate(mndCtxt.getDefaultGenerator());*/
     
 
-/*
     mnd::MandelContext mc = mnd::initializeContext();
     mnd::MandelInfo mi;
     mi.view = evi.start;
-    mi.bWidth = 8000;
-    mi.bHeight = 8000;
-    mi.maxIter = 100;
+    mi.bWidth = 12000;
+    mi.bHeight = 12000;
+    mi.maxIter = 800;
     mi.smooth = true;
     alm::ImageExportInfo iei;
     iei.drawInfo = mi;
-    iei.gradient = &evi.gradient;
+    iei.gradient = evi.gradient;
     iei.generator = &mc.getDefaultGenerator();
-    alm::exportPng("file.png", iei);
-*/
+    iei.options.jpegQuality = 100;
+    iei.path = "file.jpg";
+    try {
+        alm::exportImage(iei);
+    } catch (alm::ImageExportException& iee) {
+        printf("%s\n", iee.what());
+        return 1;
+    }
 
     return 0;
 }