+void write_ppm(const char *filename, const uint8_t *rgba, unsigned width, unsigned height)
+{
+ unique_ptr<uint8_t[]> rgb_line(new uint8_t[width * 3 + 1]);
+
+ FILE *fp = fopen(filename, "wb");
+ fprintf(fp, "P6\n%d %d\n255\n", width, height);
+ for (unsigned y = 0; y < height; ++y) {
+ unsigned y2 = height - 1 - y;
+ for (size_t x = 0; x < width; ++x) {
+ memcpy(&rgb_line[x * 3], &rgba[(y2 * width + x) * 4], 4);
+ }
+ fwrite(rgb_line.get(), width * 3, 1, fp);
+ }
+ fclose(fp);
+}
+
+struct FlowType {
+ using type = float;
+ static constexpr GLenum gl_format = GL_RG;
+ static constexpr GLenum gl_type = GL_FLOAT;
+ static constexpr int num_channels = 2;
+};
+
+struct RGBAType {
+ using type = uint8_t;
+ static constexpr GLenum gl_format = GL_RGBA;
+ static constexpr GLenum gl_type = GL_UNSIGNED_BYTE;
+ static constexpr int num_channels = 4;
+};
+
+template <class Type>
+void finish_one_read(GLuint width, GLuint height)
+{
+ using T = typename Type::type;
+ constexpr int bytes_per_pixel = Type::num_channels * sizeof(T);
+
+ assert(!reads_in_progress.empty());
+ ReadInProgress read = reads_in_progress.front();
+ reads_in_progress.pop_front();
+
+ unique_ptr<T[]> flow(new typename Type::type[width * height * Type::num_channels]);
+ void *buf = glMapNamedBufferRange(read.pbo, 0, width * height * bytes_per_pixel, GL_MAP_READ_BIT); // Blocks if the read isn't done yet.
+ memcpy(flow.get(), buf, width * height * bytes_per_pixel); // TODO: Unneeded for RGBType, since flip_coordinate_system() does nothing.:
+ glUnmapNamedBuffer(read.pbo);
+ spare_pbos.push(read.pbo);
+
+ flip_coordinate_system(flow.get(), width, height);
+ if (!read.flow_filename.empty()) {
+ write_flow(read.flow_filename.c_str(), flow.get(), width, height);
+ fprintf(stderr, "%s %s -> %s\n", read.filename0.c_str(), read.filename1.c_str(), read.flow_filename.c_str());
+ }
+ if (!read.ppm_filename.empty()) {
+ write_ppm(read.ppm_filename.c_str(), flow.get(), width, height);
+ }
+}
+
+template <class Type>
+void schedule_read(GLuint tex, GLuint width, GLuint height, const char *filename0, const char *filename1, const char *flow_filename, const char *ppm_filename)
+{
+ using T = typename Type::type;
+ constexpr int bytes_per_pixel = Type::num_channels * sizeof(T);
+
+ if (spare_pbos.empty()) {
+ finish_one_read<Type>(width, height);
+ }
+ assert(!spare_pbos.empty());
+ reads_in_progress.emplace_back(ReadInProgress{ spare_pbos.top(), filename0, filename1, flow_filename, ppm_filename });
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, spare_pbos.top());
+ spare_pbos.pop();
+ glGetTextureImage(tex, 0, Type::gl_format, Type::gl_type, width * height * bytes_per_pixel, nullptr);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+}
+
+void compute_flow_only(int argc, char **argv, int optind)
+{
+ const char *filename0 = argc >= (optind + 1) ? argv[optind] : "test1499.png";
+ const char *filename1 = argc >= (optind + 2) ? argv[optind + 1] : "test1500.png";
+ const char *flow_filename = argc >= (optind + 3) ? argv[optind + 2] : "flow.flo";
+
+ // Load pictures.
+ unsigned width1, height1, width2, height2;
+ GLuint tex0 = load_texture(filename0, &width1, &height1, WITHOUT_MIPMAPS);
+ GLuint tex1 = load_texture(filename1, &width2, &height2, WITHOUT_MIPMAPS);
+
+ if (width1 != width2 || height1 != height2) {
+ fprintf(stderr, "Image dimensions don't match (%dx%d versus %dx%d)\n",
+ width1, height1, width2, height2);
+ exit(1);
+ }
+
+ // Set up some PBOs to do asynchronous readback.
+ GLuint pbos[5];
+ glCreateBuffers(5, pbos);
+ for (int i = 0; i < 5; ++i) {
+ glNamedBufferData(pbos[i], width1 * height1 * 2 * sizeof(float), nullptr, GL_STREAM_READ);
+ spare_pbos.push(pbos[i]);
+ }
+
+ int levels = find_num_levels(width1, height1);
+ GLuint tex0_gray, tex1_gray;
+ glCreateTextures(GL_TEXTURE_2D, 1, &tex0_gray);
+ glCreateTextures(GL_TEXTURE_2D, 1, &tex1_gray);
+ glTextureStorage2D(tex0_gray, levels, GL_R8, width1, height1);
+ glTextureStorage2D(tex1_gray, levels, GL_R8, width1, height1);
+
+ GrayscaleConversion gray;
+ gray.exec(tex0, tex0_gray, width1, height1);
+ glDeleteTextures(1, &tex0);
+ glGenerateTextureMipmap(tex0_gray);
+
+ gray.exec(tex1, tex1_gray, width1, height1);
+ glDeleteTextures(1, &tex1);
+ glGenerateTextureMipmap(tex1_gray);
+
+ DISComputeFlow compute_flow(width1, height1);
+ GLuint final_tex = compute_flow.exec(tex0_gray, tex1_gray, DISComputeFlow::RESIZE_FLOW_TO_FULL_SIZE);
+
+ schedule_read<FlowType>(final_tex, width1, height1, filename0, filename1, flow_filename, "flow.ppm");
+ compute_flow.release_texture(final_tex);
+
+ // See if there are more flows on the command line (ie., more than three arguments),
+ // and if so, process them.
+ int num_flows = (argc - optind) / 3;
+ for (int i = 1; i < num_flows; ++i) {
+ const char *filename0 = argv[optind + i * 3 + 0];
+ const char *filename1 = argv[optind + i * 3 + 1];
+ const char *flow_filename = argv[optind + i * 3 + 2];
+ GLuint width, height;
+ GLuint tex0 = load_texture(filename0, &width, &height, WITHOUT_MIPMAPS);
+ if (width != width1 || height != height1) {
+ fprintf(stderr, "%s: Image dimensions don't match (%dx%d versus %dx%d)\n",
+ filename0, width, height, width1, height1);
+ exit(1);
+ }
+ gray.exec(tex0, tex0_gray, width, height);
+ glGenerateTextureMipmap(tex0_gray);
+ glDeleteTextures(1, &tex0);
+
+ GLuint tex1 = load_texture(filename1, &width, &height, WITHOUT_MIPMAPS);
+ if (width != width1 || height != height1) {
+ fprintf(stderr, "%s: Image dimensions don't match (%dx%d versus %dx%d)\n",
+ filename1, width, height, width1, height1);
+ exit(1);
+ }
+ gray.exec(tex1, tex1_gray, width, height);
+ glGenerateTextureMipmap(tex1_gray);
+ glDeleteTextures(1, &tex1);
+
+ GLuint final_tex = compute_flow.exec(tex0_gray, tex1_gray, DISComputeFlow::RESIZE_FLOW_TO_FULL_SIZE);
+
+ schedule_read<FlowType>(final_tex, width1, height1, filename0, filename1, flow_filename, "");
+ compute_flow.release_texture(final_tex);
+ }
+ glDeleteTextures(1, &tex0_gray);
+ glDeleteTextures(1, &tex1_gray);
+
+ while (!reads_in_progress.empty()) {
+ finish_one_read<FlowType>(width1, height1);
+ }
+}
+
+// Interpolate images based on
+//
+// Herbst, Seitz, Baker: “Occlusion Reasoning for Temporal Interpolation
+// Using Optical Flow”
+//
+// or at least a reasonable subset thereof. Unfinished.
+void interpolate_image(int argc, char **argv, int optind)
+{
+ const char *filename0 = argc >= (optind + 1) ? argv[optind] : "test1499.png";
+ const char *filename1 = argc >= (optind + 2) ? argv[optind + 1] : "test1500.png";
+ //const char *out_filename = argc >= (optind + 3) ? argv[optind + 2] : "interpolated.png";
+
+ // Load pictures.
+ unsigned width1, height1, width2, height2;
+ GLuint tex0 = load_texture(filename0, &width1, &height1, WITH_MIPMAPS);
+ GLuint tex1 = load_texture(filename1, &width2, &height2, WITH_MIPMAPS);
+
+ if (width1 != width2 || height1 != height2) {
+ fprintf(stderr, "Image dimensions don't match (%dx%d versus %dx%d)\n",
+ width1, height1, width2, height2);
+ exit(1);
+ }
+
+ // Set up some PBOs to do asynchronous readback.
+ GLuint pbos[5];
+ glCreateBuffers(5, pbos);
+ for (int i = 0; i < 5; ++i) {
+ glNamedBufferData(pbos[i], width1 * height1 * 4 * sizeof(uint8_t), nullptr, GL_STREAM_READ);
+ spare_pbos.push(pbos[i]);
+ }
+
+ DISComputeFlow compute_flow(width1, height1);
+ GrayscaleConversion gray;
+ Interpolate interpolate(width1, height1, finest_level);
+
+ int levels = find_num_levels(width1, height1);
+ GLuint tex0_gray, tex1_gray;
+ glCreateTextures(GL_TEXTURE_2D, 1, &tex0_gray);
+ glCreateTextures(GL_TEXTURE_2D, 1, &tex1_gray);
+ glTextureStorage2D(tex0_gray, levels, GL_R8, width1, height1);
+ glTextureStorage2D(tex1_gray, levels, GL_R8, width1, height1);
+
+ gray.exec(tex0, tex0_gray, width1, height1);
+ glGenerateTextureMipmap(tex0_gray);
+
+ gray.exec(tex1, tex1_gray, width1, height1);
+ glGenerateTextureMipmap(tex1_gray);
+
+ GLuint forward_flow_tex = compute_flow.exec(tex0_gray, tex1_gray, DISComputeFlow::DO_NOT_RESIZE_FLOW);
+ GLuint backward_flow_tex = compute_flow.exec(tex1_gray, tex0_gray, DISComputeFlow::DO_NOT_RESIZE_FLOW);
+
+ for (int frameno = 1; frameno < 60; ++frameno) {
+ char ppm_filename[256];
+ snprintf(ppm_filename, sizeof(ppm_filename), "interp%04d.ppm", frameno);
+
+ float alpha = frameno / 60.0f;
+ GLuint interpolated_tex = interpolate.exec(tex0, tex1, forward_flow_tex, backward_flow_tex, width1, height1, alpha);
+
+ schedule_read<RGBAType>(interpolated_tex, width1, height1, filename0, filename1, "", ppm_filename);
+ interpolate.release_texture(interpolated_tex);
+ }
+
+ while (!reads_in_progress.empty()) {
+ finish_one_read<RGBAType>(width1, height1);
+ }
+}
+