Nicolas Winkler 6 years ago
parent
commit
7638c683e0
11 changed files with 227 additions and 121 deletions
  1. 2 0
      Almond.pro
  2. 0 81
      Bitmap.cpp
  3. 0 3
      Bitmap.h
  4. 132 0
      MandelVideoGenerator.cpp
  5. 34 0
      MandelVideoGenerator.h
  6. 37 7
      VideoStream.cpp
  7. 4 4
      VideoStream.h
  8. 8 6
      exportdialogs.cpp
  9. 2 12
      exportdialogs.h
  10. 7 7
      exportimagedialog.ui
  11. 1 1
      libmandel/src/mandel.cpp

+ 2 - 0
Almond.pro

@@ -28,6 +28,7 @@ CONFIG += c++17
 SOURCES += \
         Almond.cpp \
         Bitmap.cpp \
+        MandelVideoGenerator.cpp \
         MandelWidget.cpp \
         SectionManager.cpp \
         VideoStream.cpp \
@@ -39,6 +40,7 @@ HEADERS += \
         Almond.h \
         Bitmap.h \
         Color.h \
+        MandelVideoGenerator.h \
         MandelWidget.h \
         SectionManager.h \
         VideoStream.h \

+ 0 - 81
Bitmap.cpp

@@ -1,83 +1,2 @@
 #include "Bitmap.h"
-#include <cstring>
-#ifdef _WIN32
-#include <Windows.h>
-#else
-// for Linux platform, plz make sure the size of data type is correct for BMP spec.
-// if you use this on Windows or other platforms, plz pay attention to this.
-typedef int LONG;
-typedef unsigned char BYTE;
-typedef unsigned int DWORD;
-typedef unsigned short WORD;
 
-// __attribute__((packed)) on non-Intel arch may cause some unexpected error, plz be informed.
-
-typedef struct tagBITMAPFILEHEADER
-{
-    WORD    bfType; // 2  /* Magic identifier */
-    DWORD   bfSize; // 4  /* File size in bytes */
-    WORD    bfReserved1; // 2
-    WORD    bfReserved2; // 2
-    DWORD   bfOffBits; // 4 /* Offset to image data, bytes */ 
-} __attribute__((packed)) BITMAPFILEHEADER;
-
-typedef struct tagBITMAPINFOHEADER
-{
-    DWORD    biSize; // 4 /* Header size in bytes */
-    LONG     biWidth; // 4 /* Width of image */
-    LONG     biHeight; // 4 /* Height of image */
-    WORD     biPlanes; // 2 /* Number of colour planes */
-    WORD     biBitCount; // 2 /* Bits per pixel */
-    DWORD    biCompression; // 4 /* Compression type */
-    DWORD    biSizeImage; // 4 /* Image size in bytes */
-    LONG     biXPelsPerMeter; // 4
-    LONG     biYPelsPerMeter; // 4 /* Pixels per meter */
-    DWORD    biClrUsed; // 4 /* Number of colours */ 
-    DWORD    biClrImportant; // 4 /* Important colours */ 
-} __attribute__((packed)) BITMAPINFOHEADER;
-#endif
-#include <cstdlib>
-
-template struct Bitmap<RGBColor>;
-
-
-
-template<>
-template<>
-void Bitmap<RGBColor>::createPng<RGBColor>(const std::string& path) const
-{
-    std::FILE* out = std::fopen(path.c_str(), "wb");
-
-    BITMAPFILEHEADER bmfh;
-    bmfh.bfType = 0x4d42;
-    bmfh.bfSize = 14 + 40 + (width * height * 3);
-    bmfh.bfReserved1 = 0;
-    bmfh.bfReserved2 = 0;
-    bmfh.bfOffBits = 14 + 40;
-
-    BITMAPINFOHEADER bmih;
-    memset(&bmih, 0, 40);
-    bmih.biSize = 40;
-    bmih.biWidth = width;
-    bmih.biHeight = -long(height);
-    bmih.biPlanes = 1;
-    bmih.biBitCount = 24;
-    bmih.biCompression = 0;
-
-    std::fwrite(&bmfh, sizeof bmfh, 1, out);
-    std::fwrite(&bmih, sizeof bmih, 1, out);
-
-    size_t lineLength = (width * 3 + 3) & ~3;
-
-    std::unique_ptr<unsigned char[]> linebuf = std::make_unique<unsigned char[]>(lineLength);
-    memset(linebuf.get(), 0, lineLength);
-    for (long i = 0; i < height; i++) {
-        for (long j = 0; j < width; j++) {
-            linebuf[j * 3 + 0] = pixels[i * height + j].b;
-            linebuf[j * 3 + 1] = pixels[i * height + j].g;
-            linebuf[j * 3 + 2] = pixels[i * height + j].r;
-        }
-        std::fwrite(linebuf.get(), 1, lineLength, out);
-    }
-    std::fclose(out);
-}

+ 0 - 3
Bitmap.h

@@ -32,9 +32,6 @@ public:
 
     ~Bitmap() = default;
 
-    template<typename P = Pixel>
-    auto createPng(const std::string& path) const -> typename std::enable_if<std::is_same<P, RGBColor>::value>::type;
-
     template<typename T>
     Bitmap<T> map(std::function<T(Pixel)> f) const {
         Bitmap<T> b{ width, height };

+ 132 - 0
MandelVideoGenerator.cpp

@@ -0,0 +1,132 @@
+#include "MandelVideoGenerator.h"
+#include "VideoStream.h"
+#include <thread>
+#include <cmath>
+
+MandelVideoGenerator::MandelVideoGenerator(const ExportVideoInfo& evi) :
+    evi{ evi }
+{
+}
+
+
+void MandelVideoGenerator::generate(void)
+{
+    mnd::MandelContext ctxt = mnd::initializeContext();
+    mnd::Generator& gen = *ctxt.getDevices()[0].getGeneratorDouble();
+    mnd::MandelInfo mi;
+    mi.bWidth = evi.width * 2;
+    mi.bHeight = evi.height * 2;
+    mi.maxIter = evi.maxIterations;
+
+    VideoStream vs(evi.width, evi.height, evi.path);
+
+    double x = evi.end.x + evi.end.width / 2;
+    double y = evi.end.y + evi.end.height / 2;
+    double w = evi.start.width;
+    double h = evi.start.height;
+
+    double bigW = 10000000000000000.0;
+    double bigFac = 1.0;
+    Bitmap<RGBColor> big;
+    Bitmap<RGBColor> small;
+
+    while(w > evi.end.width || h > evi.end.height) {
+        mi.view = mnd::MandelViewport{ x - w/2, y - h/2, w, h };
+
+        if (bigW > 2 * w) {
+            Bitmap<float> raw{ evi.width * 2, evi.height * 2 };
+            gen.generate(mi, raw.pixels.get());
+            big = raw.map<RGBColor>([] (float x) { return
+                RGBColor{ uint8_t(::sin(x / 100) * 127 + 127), uint8_t(::sin(x / 213) * 127 + 127), uint8_t(::cos(x / 173) * 127 + 127) };
+            });
+            /*mi.view.zoomCenter(0.5);
+            gen.generate(mi, raw.pixels.get());
+            small = raw.map<RGBColor>([] (float x) { return
+                RGBColor{ uint8_t(::sin(x / 100) * 127 + 127), uint8_t(::sin(x / 213) * 127 + 127), uint8_t(::cos(x / 173) * 127 + 127) };
+            });*/
+            bigW = w;
+            bigFac = 1.0;
+        }
+
+        vs.addFrame(overlay(big, small, bigFac));
+
+        w *= 0.99;
+        h *= 0.99;
+        bigFac *= 0.99;
+    }
+}
+
+
+inline RGBColor biliniear(const Bitmap<RGBColor>& img, double x, double y)
+{
+    int xfloor = int(::floor(x));
+    int yfloor = int(::floor(y));
+    int xceil = int(::ceil(x));
+    int yceil = int(::ceil(y));
+
+    double xLerp = x - xfloor;
+    double yLerp = y - yfloor;
+
+    RGBColor samples[2][2] = {
+        {
+            img.get(xfloor, yfloor),
+            img.get(xfloor, yceil),
+        },
+        {
+            img.get(xceil, yfloor),
+            img.get(xceil, yceil),
+        }
+    };
+
+    double r = 0, g = 0, b = 0;
+
+    auto mklin = [] (double x) {
+        return ::pow(x, 2.4);
+    };
+    auto unlin = [] (double x) {
+        return ::pow(x, 1.0 / 2.4);
+    };
+
+    r += (1 - xLerp) * (1 - yLerp) * mklin(samples[0][0].r);
+    r += (1 - xLerp) * yLerp * mklin(samples[0][1].r);
+    r += xLerp * (1 - yLerp) * mklin(samples[1][0].r);
+    r += xLerp * yLerp * mklin(samples[1][1].r);
+
+    g += (1 - xLerp) * (1 - yLerp) * mklin(samples[0][0].g);
+    g += (1 - xLerp) * yLerp * mklin(samples[0][1].g);
+    g += xLerp * (1 - yLerp) * mklin(samples[1][0].g);
+    g += xLerp * yLerp * mklin(samples[1][1].g);
+
+    b += (1 - xLerp) * (1 - yLerp) * mklin(samples[0][0].b);
+    b += (1 - xLerp) * yLerp * mklin(samples[0][1].b);
+    b += xLerp * (1 - yLerp) * mklin(samples[1][0].b);
+    b += xLerp * yLerp * mklin(samples[1][1].b);
+
+    return RGBColor{ uint8_t(unlin(r)), uint8_t(unlin(g)), uint8_t(unlin(b)) };
+}
+
+
+Bitmap<RGBColor> MandelVideoGenerator::overlay(const Bitmap<RGBColor>& outer,
+                         const Bitmap<RGBColor>& inner, double scale)
+{
+    printf("%lf\n", scale);
+    Bitmap<RGBColor> ret{ outer.width / 2, outer.height / 2 };
+    double newW = outer.width * scale * 2;
+    double newH = outer.height * scale * 2;
+    double newX = outer.width * (1 - scale) / 2;
+    double newY = outer.height * (1 - scale) / 2;
+
+    for (int i = 0; i < ret.height; i++) {
+        for (int j = 0; j < ret.width; j++) {
+            double newJ = newX + j * newW / outer.width;
+            double newI = newY + i * newH / outer.height;
+            RGBColor a = biliniear(outer, newJ, newI);
+            ret.get(j, i) = a;
+        }
+    }
+    /*for (int i = 0; i < ret.height * ret.width; i++) {
+        ret.pixels[i] = outer.pixels[i];
+    }*/
+
+    return ret;
+}

+ 34 - 0
MandelVideoGenerator.h

@@ -0,0 +1,34 @@
+#ifndef MANDELVIDEOGENERATOR_H
+#define MANDELVIDEOGENERATOR_H
+
+#include "Mandel.h"
+#include "Bitmap.h"
+
+struct ExportVideoInfo
+{
+    mnd::MandelViewport start;
+    mnd::MandelViewport end;
+
+    int width;
+    int height;
+    int maxIterations;
+
+    std::string path;
+};
+
+
+class MandelVideoGenerator
+{
+    const ExportVideoInfo evi;
+public:
+    MandelVideoGenerator(const ExportVideoInfo& evi);
+
+    void generate(void);
+
+private:
+    Bitmap<RGBColor> overlay(const Bitmap<RGBColor>& outer,
+                             const Bitmap<RGBColor>& inner,
+                             double scale);
+};
+
+#endif // MANDELVIDEOGENERATOR_H

+ 37 - 7
VideoStream.cpp

@@ -15,25 +15,25 @@
 const uint8_t VideoStream::endcode[] = { 0, 0, 1, 0xb7 };
 
 
-VideoStream::VideoStream(::size_t width, ::size_t height, const std::string& filename) :
-    width{ width }, height{ height }
+VideoStream::VideoStream(int width, int height, const std::string& filename) :
+    width{ width & (~1) }, height{ height & (~1) }
 {
-    avcodec_register_all();
+    // only needed with ffmpeg version < 4
+    //avcodec_register_all();
 
-    codec = avcodec_find_encoder(AV_CODEC_ID_MPEG4);
+    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
     if (!codec) {
         fprintf(stderr, "invalid codec\n");
         exit(1);
     }
 
     codecContext = avcodec_alloc_context3(codec);
-    picture = av_frame_alloc();
 
     pkt = av_packet_alloc();
     if (!pkt)
         exit(1);
 
-    codecContext->bit_rate = 100 * 1000 * 1000;
+    codecContext->bit_rate = 50 * 1000 * 1000;
     codecContext->width = width;
     codecContext->height = height;
     codecContext->time_base = AVRational{ 1, 60 };
@@ -43,6 +43,9 @@ VideoStream::VideoStream(::size_t width, ::size_t height, const std::string& fil
     codecContext->max_b_frames = 1;
     codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
 
+    if (codec->id == AV_CODEC_ID_H264)
+        av_opt_set(codecContext->priv_data, "preset", "slow", 0);
+
     if (avcodec_open2(codecContext, codec, nullptr) < 0) {
         fprintf(stderr, "could not open codec\n");
         exit(1);
@@ -54,6 +57,7 @@ VideoStream::VideoStream(::size_t width, ::size_t height, const std::string& fil
         exit(1);
     }
 
+    picture = av_frame_alloc();
     picture->format = codecContext->pix_fmt;
     picture->width  = codecContext->width;
     picture->height = codecContext->height;
@@ -102,7 +106,7 @@ static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
 VideoStream::~VideoStream()
 {
     /* flush the encoder */
-    encode(codecContext, NULL, pkt, file);
+    encode(codecContext, nullptr, pkt, file);
 
     /* add sequence end code to have a real MPEG file */
     fwrite(endcode, 1, sizeof(endcode), file);
@@ -111,6 +115,32 @@ VideoStream::~VideoStream()
     avcodec_free_context(&codecContext);
     av_frame_free(&picture);
     av_packet_free(&pkt);
+
+/*
+    AVPacket pkt;
+    av_init_packet(&pkt);
+    pkt.data = nullptr;
+    pkt.size = 0;
+
+    for (;;) {
+        avcodec_send_frame(codecContext, NULL);
+        if (avcodec_receive_packet(codecContext, &pkt) == 0) {
+            av_interleaved_write_frame(codecContext, &pkt);
+            av_packet_unref(&pkt);
+        }
+        else {
+            break;
+        }
+    }
+
+    av_write_trailer();
+    if (!(oformat->flags & AVFMT_NOFILE)) {
+        int err = avio_close(ofctx->pb);
+        if (err < 0) {
+            Debug("Failed to close file", err);
+        }
+    }*/
+
 }
 
 

+ 4 - 4
VideoStream.h

@@ -29,12 +29,12 @@ class VideoStream
     SwsContext* swsContext;
     static const uint8_t endcode[];
 
-    size_t width;
-    size_t height;
+    int width;
+    int height;
 
-    uint64_t frameIndex = 0;
+    int64_t frameIndex = 0;
 public:
-    VideoStream(::size_t width, ::size_t height, const std::string& filename);
+    VideoStream(int width, int height, const std::string& filename);
     ~VideoStream(void);
 
     void addFrame(const Bitmap<RGBColor>& frame);

+ 8 - 6
exportdialogs.cpp

@@ -96,7 +96,7 @@ void ExportVideoDialog::on_buttonBox_accepted()
         emit reject();
     }
 
-    evi.path = evd.savePath->text();
+    evi.path = evd.savePath->text().toStdString();
     evi.width = evd.vidWidth->text().toInt();
     evi.height = evd.vidHeight->text().toInt();
     evi.maxIterations = evd.maxIterations->text().toInt();
@@ -116,18 +116,20 @@ void ExportVideoDialog::on_buttonBox_accepted()
     evi.start.adjustAspectRatio(evi.width, evi.height);
     evi.end.adjustAspectRatio(evi.width, evi.height);
 
-    if (exportVideo(evi)) {
+    MandelVideoGenerator mvg(evi);
+    mvg.generate();
+    //if (exportVideo(evi)) {
         QMessageBox* msgBox = new QMessageBox;
         msgBox->setText("Video successfully exported.");
         msgBox->exec();
-    }
+    //}
 }
 
 void ExportVideoDialog::on_pushButton_clicked()
 {
     QString saveAs = QFileDialog::getSaveFileName(this,
             tr("Save exported image"), "",
-            tr("MPEG video (*.mp4);;All Files (*)"));
+            tr("H264 video (*.h264);;All Files (*)"));
     evd.savePath->setText(saveAs);
     this->repaint();
 }
@@ -140,13 +142,13 @@ bool exportVideo(const ExportVideoInfo& evi)
     };
 
     mnd::MandelContext ctxt = mnd::initializeContext();
-    mnd::Generator& gen = *ctxt.getDevices()[0].getGeneratorDouble();
+    mnd::Generator& gen = *ctxt.getDevices()[0].getGeneratorFloat();
     mnd::MandelInfo mi;
     mi.bWidth = evi.width;
     mi.bHeight = evi.height;
     mi.maxIter = evi.maxIterations;
 
-    VideoStream vs(evi.width, evi.height, evi.path.toStdString());
+    VideoStream vs(evi.width, evi.height, evi.path);
 
     double x = evi.end.x + evi.end.width / 2;
     double y = evi.end.y + evi.end.height / 2;

+ 2 - 12
exportdialogs.h

@@ -8,6 +8,8 @@
 #include "ui_exportimagedialog.h"
 #include "ui_exportvideodialog.h"
 
+#include "MandelVideoGenerator.h"
+
 class ExportImageDialog : public QDialog
 {
     Q_OBJECT
@@ -26,18 +28,6 @@ private slots:
 };
 
 
-struct ExportVideoInfo {
-    mnd::MandelViewport start;
-    mnd::MandelViewport end;
-
-    int width;
-    int height;
-    int maxIterations;
-
-    QString path;
-};
-
-
 class ExportVideoDialog : public QDialog
 {
     Q_OBJECT

+ 7 - 7
exportimagedialog.ui

@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
+    <width>392</width>
     <height>170</height>
    </rect>
   </property>
@@ -60,17 +60,17 @@
       </property>
      </widget>
     </item>
-    <item row="2" column="0">
-     <widget class="QLabel" name="label_3">
+    <item row="1" column="1">
+     <widget class="QLineEdit" name="imgWidth">
       <property name="text">
-       <string>Image Height</string>
+       <string>1920</string>
       </property>
      </widget>
     </item>
-    <item row="1" column="1">
-     <widget class="QLineEdit" name="imgWidth">
+    <item row="2" column="0">
+     <widget class="QLabel" name="label_3">
       <property name="text">
-       <string>1920</string>
+       <string>Image Height</string>
       </property>
      </widget>
     </item>

+ 1 - 1
libmandel/src/mandel.cpp

@@ -87,7 +87,7 @@ std::vector<MandelDevice> MandelContext::createDevices(void)
 #ifdef WITH_OPENCL
     std::vector<cl::Platform> platforms;
     cl::Platform::get(&platforms);
-    platforms.erase(platforms.begin() + 1);
+    //platforms.erase(platforms.begin() + 1);
 
     for (auto& platform : platforms) {
         std::string name = platform.getInfo<CL_PLATFORM_NAME>();