Merge branch 'blender2.7'

This commit is contained in:
Brecht Van Lommel 2019-03-19 18:54:17 +01:00
commit fa59c6a3e7
11 changed files with 735 additions and 36 deletions

View File

@ -124,9 +124,48 @@ class CYCLES_OT_denoise_animation(Operator):
return {'FINISHED'}
class CYCLES_OT_merge_images(Operator):
"Combine OpenEXR multilayer images rendered with different sample" \
"ranges into one image with reduced noise."
bl_idname = "cycles.merge_images"
bl_label = "Merge Images"
input_filepath1: StringProperty(
name='Input Filepath',
description='File path for image to merge',
default='',
subtype='FILE_PATH')
input_filepath2: StringProperty(
name='Input Filepath',
description='File path for image to merge',
default='',
subtype='FILE_PATH')
output_filepath: StringProperty(
name='Output Filepath',
description='File path for merged image',
default='',
subtype='FILE_PATH')
def execute(self, context):
in_filepaths = [self.input_filepath1, self.input_filepath2]
out_filepath = self.output_filepath
import _cycles
try:
_cycles.merge(input=in_filepaths, output=out_filepath)
except Exception as e:
self.report({'ERROR'}, str(e))
return {'FINISHED'}
return {'FINISHED'}
classes = (
CYCLES_OT_use_shading_nodes,
CYCLES_OT_denoise_animation
CYCLES_OT_denoise_animation,
CYCLES_OT_merge_images
)
def register():

View File

@ -23,6 +23,7 @@
#include "blender/blender_session.h"
#include "render/denoising.h"
#include "render/merge.h"
#include "util/util_debug.h"
#include "util/util_foreach.h"
@ -642,9 +643,8 @@ static PyObject *opencl_compile_func(PyObject * /*self*/, PyObject *args)
}
#endif
static bool denoise_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
static bool image_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
{
if(PyUnicode_Check(pyfilepaths)) {
const char *filepath = PyUnicode_AsUTF8(pyfilepaths);
filepaths.push_back(filepath);
@ -713,12 +713,12 @@ static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *key
/* Parse file paths list. */
vector<string> input, output;
if(!denoise_parse_filepaths(pyinput, input)) {
if(!image_parse_filepaths(pyinput, input)) {
return NULL;
}
if(pyoutput) {
if(!denoise_parse_filepaths(pyoutput, output)) {
if(!image_parse_filepaths(pyoutput, output)) {
return NULL;
}
}
@ -757,6 +757,42 @@ static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *key
Py_RETURN_NONE;
}
static PyObject *merge_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
{
static const char *keyword_list[] = {"input", "output", NULL};
PyObject *pyinput, *pyoutput = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywords, "OO", (char**)keyword_list, &pyinput, &pyoutput)) {
return NULL;
}
/* Parse input list. */
vector<string> input;
if(!image_parse_filepaths(pyinput, input)) {
return NULL;
}
/* Parse output string. */
if(!PyUnicode_Check(pyoutput)) {
PyErr_SetString(PyExc_ValueError, "Output must be a string.");
return NULL;
}
string output = PyUnicode_AsUTF8(pyoutput);
/* Merge. */
ImageMerger merger;
merger.input = input;
merger.output = output;
if(!merger.run()) {
PyErr_SetString(PyExc_ValueError, merger.error.c_str());
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyscene;
@ -920,6 +956,7 @@ static PyMethodDef methods[] = {
/* Standalone denoising */
{"denoise", (PyCFunction)denoise_func, METH_VARARGS|METH_KEYWORDS, ""},
{"merge", (PyCFunction)merge_func, METH_VARARGS|METH_KEYWORDS, ""},
/* Debugging routines */
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},

View File

@ -393,15 +393,6 @@ static void add_cryptomatte_layer(BL::RenderResult& b_rr, string name, string ma
render_add_metadata(b_rr, prefix+"manifest", manifest);
}
/* TODO(sergey): Ideally this will be an utility function in util string.h, but
* currently is relying on Blender side function, so can not do that. */
static string make_human_readable_time(double time)
{
char time_str[128];
BLI_timecode_string_from_time_simple(time_str, sizeof(time_str), time);
return time_str;
}
void BlenderSession::stamp_view_layer_metadata(Scene *scene, const string& view_layer_name)
{
BL::RenderResult b_rr = b_engine.get_result();
@ -440,11 +431,11 @@ void BlenderSession::stamp_view_layer_metadata(Scene *scene, const string& view_
double total_time, render_time;
session->progress.get_time(total_time, render_time);
b_rr.stamp_data_add_field((prefix + "total_time").c_str(),
make_human_readable_time(total_time).c_str());
time_human_readable_from_seconds(total_time).c_str());
b_rr.stamp_data_add_field((prefix + "render_time").c_str(),
make_human_readable_time(render_time).c_str());
time_human_readable_from_seconds(render_time).c_str());
b_rr.stamp_data_add_field((prefix + "synchronization_time").c_str(),
make_human_readable_time(total_time - render_time).c_str());
time_human_readable_from_seconds(total_time - render_time).c_str());
}
void BlenderSession::render(BL::Depsgraph& b_depsgraph_)
@ -1014,7 +1005,6 @@ void BlenderSession::update_status_progress()
string scene_status = "";
float progress;
double total_time, remaining_time = 0, render_time;
char time_str[128];
float mem_used = (float)session->stats.mem_used / 1024.0f / 1024.0f;
float mem_peak = (float)session->stats.mem_peak / 1024.0f / 1024.0f;
@ -1034,8 +1024,7 @@ void BlenderSession::update_status_progress()
scene_status += ", " + b_rview_name;
if(remaining_time > 0) {
BLI_timecode_string_from_time_simple(time_str, sizeof(time_str), remaining_time);
timestatus += "Remaining:" + string(time_str) + " | ";
timestatus += "Remaining:" + time_human_readable_from_seconds(remaining_time) + " | ";
}
timestatus += string_printf("Mem:%.2fM, Peak:%.2fM", (double)mem_used, (double)mem_peak);

View File

@ -32,7 +32,6 @@
* todo: clean this up ... */
extern "C" {
size_t BLI_timecode_string_from_time_simple(char *str, size_t maxlen, double time_seconds);
void BKE_image_user_frame_calc(void *iuser, int cfra);
void BKE_image_user_file_path(void *iuser, void *ima, char *path);
unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame);

View File

@ -22,6 +22,7 @@ set(SRC
image.cpp
integrator.cpp
light.cpp
merge.cpp
mesh.cpp
mesh_displace.cpp
mesh_subdivision.cpp
@ -55,6 +56,7 @@ set(SRC_HEADERS
image.h
integrator.h
light.h
merge.h
mesh.h
nodes.h
object.h

View File

@ -0,0 +1,456 @@
/*
* Copyright 2011-2019 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "render/merge.h"
#include "util/util_array.h"
#include "util/util_map.h"
#include "util/util_system.h"
#include "util/util_time.h"
#include "util/util_unique_ptr.h"
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/filesystem.h>
OIIO_NAMESPACE_USING
CCL_NAMESPACE_BEGIN
/* Merge Image Layer */
enum MergeChannelOp {
MERGE_CHANNEL_COPY,
MERGE_CHANNEL_SUM,
MERGE_CHANNEL_AVERAGE
};
struct MergeImageLayer {
/* Layer name. */
string name;
/* All channels belonging to this MergeImageLayer. */
vector<string> channel_names;
/* Offsets of layer channels in image. */
vector<int> channel_offsets;
/* Type of operation to perform when merging. */
vector<MergeChannelOp> channel_ops;
/* Sample amount that was used for rendering this layer. */
int samples;
};
/* Merge Image */
class MergeImage {
public:
/* OIIO file handle. */
unique_ptr<ImageInput> in;
/* Image file path. */
string filepath;
/* Render layers. */
vector<MergeImageLayer> layers;
};
/* Channel Parsing */
static MergeChannelOp parse_channel_operation(const string& pass_name)
{
if(pass_name == "Depth" ||
pass_name == "IndexMA" ||
pass_name == "IndexOB" ||
string_startswith(pass_name, "Crypto"))
{
return MERGE_CHANNEL_COPY;
}
else if(string_startswith(pass_name, "Debug BVH") ||
string_startswith(pass_name, "Debug Ray") ||
string_startswith(pass_name, "Debug Render Time"))
{
return MERGE_CHANNEL_SUM;
}
else {
return MERGE_CHANNEL_AVERAGE;
}
}
/* Splits in at its last dot, setting suffix to the part after the dot and
* into the part before it. Returns whether a dot was found. */
static bool split_last_dot(string &in, string &suffix)
{
size_t pos = in.rfind(".");
if(pos == string::npos) {
return false;
}
suffix = in.substr(pos+1);
in = in.substr(0, pos);
return true;
}
/* Separate channel names as generated by Blender.
* Multiview format: RenderLayer.Pass.View.Channel
* Otherwise: RenderLayer.Pass.Channel */
static bool parse_channel_name(string name,
string &renderlayer,
string &pass,
string &channel,
bool multiview_channels)
{
if(!split_last_dot(name, channel)) {
return false;
}
string view;
if(multiview_channels && !split_last_dot(name, view)) {
return false;
}
if(!split_last_dot(name, pass)) {
return false;
}
renderlayer = name;
if(multiview_channels) {
renderlayer += "." + view;
}
return true;
}
static bool parse_channels(const ImageSpec &in_spec,
vector<MergeImageLayer>& layers,
string& error)
{
const std::vector<string> &channels = in_spec.channelnames;
const ParamValue *multiview = in_spec.find_attribute("multiView");
const bool multiview_channels = (multiview &&
multiview->type().basetype == TypeDesc::STRING &&
multiview->type().arraylen >= 2);
layers.clear();
/* Loop over all the channels in the file, parse their name and sort them
* by RenderLayer.
* Channels that can't be parsed are directly passed through to the output. */
map<string, MergeImageLayer> file_layers;
for(int i = 0; i < channels.size(); i++) {
string layer, pass, channel;
if(parse_channel_name(channels[i], layer, pass, channel, multiview_channels)) {
file_layers[layer].channel_names.push_back(pass + "." + channel);
file_layers[layer].channel_offsets.push_back(i);
file_layers[layer].channel_ops.push_back(parse_channel_operation(pass));
}
/* Any unparsed channels are copied from the first image. */
}
/* Loop over all detected RenderLayers, check whether they contain a full set of input channels.
* Any channels that won't be processed internally are also passed through. */
for(map<string, MergeImageLayer>::iterator i = file_layers.begin(); i != file_layers.end(); ++i) {
const string& name = i->first;
MergeImageLayer& layer = i->second;
layer.name = name;
layer.samples = 0;
/* If the sample value isn't set yet, check if there is a layer-specific one in the input file. */
if(layer.samples < 1) {
string sample_string = in_spec.get_string_attribute("cycles." + name + ".samples", "");
if(sample_string != "") {
if(!sscanf(sample_string.c_str(), "%d", &layer.samples)) {
error = "Failed to parse samples metadata: " + sample_string;
return false;
}
}
}
if(layer.samples < 1) {
error = string_printf("No sample number specified in the file for layer %s or on the command line", name.c_str());
return false;
}
layers.push_back(layer);
}
return true;
}
static bool open_images(const vector<string>& filepaths,
vector<MergeImage>& images,
string& error)
{
for(const string& filepath: filepaths) {
unique_ptr<ImageInput> in(ImageInput::open(filepath));
if(!in) {
error = "Couldn't open file: " + filepath;
return false;
}
MergeImage image;
image.in = std::move(in);
image.filepath = filepath;
if(!parse_channels(image.in->spec(), image.layers, error)) {
return false;
}
if(image.layers.size() == 0) {
error = "Could not find a render layer for merging";
return false;
}
if(image.in->spec().deep) {
error = "Merging deep images not supported.";
return false;
}
if(images.size() > 0) {
const ImageSpec& base_spec = images[0].in->spec();
const ImageSpec& spec = image.in->spec();
if(base_spec.width != spec.width ||
base_spec.height != spec.height ||
base_spec.depth != spec.depth ||
base_spec.nchannels != spec.nchannels ||
base_spec.format != spec.format ||
base_spec.channelformats != spec.channelformats ||
base_spec.channelnames != spec.channelnames ||
base_spec.deep != spec.deep)
{
error = "Images do not have exact matching data and channel layout.";
return false;
}
}
images.push_back(std::move(image));
}
return true;
}
static bool load_pixels(const MergeImage& image, array<float>& pixels, string& error)
{
const ImageSpec& in_spec = image.in->spec();
const size_t width = in_spec.width;
const size_t height = in_spec.height;
const size_t num_channels = in_spec.nchannels;
const size_t num_pixels = (size_t)width * (size_t)height;
pixels.resize(num_pixels * num_channels);
/* Read all channels into buffer. Reading all channels at once is faster
* than individually due to interleaved EXR channel storage. */
if(!image.in->read_image(TypeDesc::FLOAT, pixels.data())) {
error = "Failed to read image: " + image.filepath;
return false;
}
return true;
}
static void merge_render_time(ImageSpec& spec,
const vector<MergeImage>& images,
const string& name,
const bool average)
{
double time = 0.0;
for(const MergeImage& image: images) {
string time_str = image.in->spec().get_string_attribute(name, "");
time += time_human_readable_to_seconds(time_str);
}
if(average) {
time /= images.size();
}
spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
}
static void merge_layer_render_time(ImageSpec& spec,
const vector<MergeImage>& images,
const string& time_name,
const bool average)
{
for(size_t i = 0; i < images[0].layers.size(); i++) {
string name = "cycles." + images[0].layers[i].name + "." + time_name;
double time = 0.0;
for(const MergeImage& image: images) {
string time_str = image.in->spec().get_string_attribute(name, "");
time += time_human_readable_to_seconds(time_str);
}
if(average) {
time /= images.size();
}
spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
}
}
static bool save_output(const string& filepath,
const ImageSpec& spec,
const array<float>& pixels,
string& error)
{
/* Write to temporary file path, so we merge images in place and don't
* risk destroying files when something goes wrong in file saving. */
string extension = OIIO::Filesystem::extension(filepath);
string unique_name = ".merge-tmp-" + OIIO::Filesystem::unique_path();
string tmp_filepath = filepath + unique_name + extension;
unique_ptr<ImageOutput> out(ImageOutput::create(tmp_filepath));
if(!out) {
error = "Failed to open temporary file " + tmp_filepath + " for writing";
return false;
}
/* Open temporary file and write image buffers. */
if(!out->open(tmp_filepath, spec)) {
error = "Failed to open file " + tmp_filepath + " for writing: " + out->geterror();
return false;
}
bool ok = true;
if(!out->write_image(TypeDesc::FLOAT, pixels.data())) {
error = "Failed to write to file " + tmp_filepath + ": " + out->geterror();
ok = false;
}
if(!out->close()) {
error = "Failed to save to file " + tmp_filepath + ": " + out->geterror();
ok = false;
}
out.reset();
/* Copy temporary file to outputput filepath. */
string rename_error;
if(ok && !OIIO::Filesystem::rename(tmp_filepath, filepath, rename_error)) {
error = "Failed to move merged image to " + filepath + ": " + rename_error;
ok = false;
}
if(!ok) {
OIIO::Filesystem::remove(tmp_filepath);
}
return ok;
}
/* Image Merger */
ImageMerger::ImageMerger()
{
}
bool ImageMerger::run()
{
if(input.empty()) {
error = "No input file paths specified.";
return false;
}
if(output.empty()) {
error = "No output file path specified.";
return false;
}
/* Open images and verify they have matching layout. */
vector<MergeImage> images;
if(!open_images(input, images, error)) {
return false;
}
/* Merge pixels. */
array<float> merge_pixels;
vector<int> merge_samples;
/* Load first image. */
if(!load_pixels(images[0], merge_pixels, error)) {
return false;
}
for(size_t layer = 0; layer < images[0].layers.size(); layer++) {
merge_samples.push_back(images[0].layers[layer].samples);
}
/* Merge other images. */
for(size_t i = 1; i < images.size(); i++) {
const MergeImage& image = images[i];
array<float> pixels;
if(!load_pixels(image, pixels, error)) {
return false;
}
for(size_t li = 0; li < image.layers.size(); li++) {
const MergeImageLayer& layer = image.layers[li];
const int *offsets = layer.channel_offsets.data();
const MergeChannelOp *ops = layer.channel_ops.data();
const size_t stride = image.in->spec().nchannels;
const size_t num_channels = layer.channel_offsets.size();
const size_t num_pixels = pixels.size();
/* Weights based on sample metadata. */
const int sum_samples = merge_samples[li] + layer.samples;
const float t = (float)layer.samples / (float)sum_samples;
for(size_t pixel = 0; pixel < num_pixels; pixel += stride) {
for(size_t channel = 0; channel < num_channels; channel++) {
size_t offset = pixel + offsets[channel];
switch(ops[channel]) {
case MERGE_CHANNEL_COPY:
/* Already copied from first image. */
break;
case MERGE_CHANNEL_SUM:
merge_pixels[offset] += pixels[offset];
break;
case MERGE_CHANNEL_AVERAGE:
merge_pixels[offset] = (1.0f - t) * merge_pixels[offset] + t * pixels[offset];
break;
}
}
}
merge_samples[li] += layer.samples;
}
}
/* Save image with identical dimensions, channels and metadata. */
ImageSpec out_spec = images[0].in->spec();
/* Merge metadata. */
for(size_t i = 0; i < images[0].layers.size(); i++) {
string name = "cycles." + images[0].layers[i].name + ".samples";
out_spec.attribute(name, TypeDesc::STRING, string_printf("%d", merge_samples[i]));
}
merge_render_time(out_spec, images, "RenderTime", false);
merge_layer_render_time(out_spec, images, "total_time", false);
merge_layer_render_time(out_spec, images, "render_time", false);
merge_layer_render_time(out_spec, images, "synchronization_time", true);
/* We don't need input anymore at this point, and will possibly
* overwrite the same file. */
images.clear();
/* Save output file. */
return save_output(output, out_spec, merge_pixels, error);
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,43 @@
/*
* Copyright 2011-2019 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __MERGE_H__
#define __MERGE_H__
#include "util/util_string.h"
#include "util/util_vector.h"
CCL_NAMESPACE_BEGIN
/* Merge OpenEXR multilayer renders. */
class ImageMerger {
public:
ImageMerger();
bool run();
/* Error message after running, in case of failure. */
string error;
/* List of image filepaths to merge. */
vector<string> input;
/* Output filepath. */
string output;
};
CCL_NAMESPACE_END
#endif /* __MERGE_H__ */

View File

@ -103,3 +103,4 @@ CYCLES_TEST(util_aligned_malloc "cycles_util")
CYCLES_TEST(util_path "cycles_util;${BOOST_LIBRARIES};${OPENIMAGEIO_LIBRARIES}")
CYCLES_TEST(util_string "cycles_util;${BOOST_LIBRARIES};${OPENIMAGEIO_LIBRARIES}")
CYCLES_TEST(util_task "cycles_util;${BOOST_LIBRARIES};${OPENIMAGEIO_LIBRARIES};bf_intern_numaapi")
CYCLES_TEST(util_time "cycles_util;${BOOST_LIBRARIES};${OPENIMAGEIO_LIBRARIES}")

View File

@ -0,0 +1,64 @@
/*
* Copyright 2011-2019 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "testing/testing.h"
#include "util/util_time.h"
CCL_NAMESPACE_BEGIN
TEST(time_human_readable_to_seconds, Empty) {
EXPECT_EQ(time_human_readable_to_seconds(""), 0.0);
EXPECT_EQ(time_human_readable_from_seconds(0.0), "00:00.00");
}
TEST(time_human_readable_to_seconds, Fraction) {
EXPECT_NEAR(time_human_readable_to_seconds(".1"), 0.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds(".10"), 0.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(0.1), "00:00.10");
}
TEST(time_human_readable_to_seconds, Seconds) {
EXPECT_NEAR(time_human_readable_to_seconds("2.1"), 2.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds("02.10"), 2.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(2.1), "00:02.10");
EXPECT_NEAR(time_human_readable_to_seconds("12.1"), 12.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds("12.10"), 12.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(12.1), "00:12.10");
}
TEST(time_human_readable_to_seconds, MinutesSeconds) {
EXPECT_NEAR(time_human_readable_to_seconds("3:2.1"), 182.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds("03:02.10"), 182.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(182.1), "03:02.10");
EXPECT_NEAR(time_human_readable_to_seconds("34:12.1"), 2052.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds("34:12.10"), 2052.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(2052.1), "34:12.10");
}
TEST(time_human_readable_to_seconds, HoursMinutesSeconds) {
EXPECT_NEAR(time_human_readable_to_seconds("4:3:2.1"), 14582.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds("04:03:02.10"), 14582.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(14582.1), "04:03:02.10");
EXPECT_NEAR(time_human_readable_to_seconds("56:34:12.1"), 203652.1, 1e-8f);
EXPECT_NEAR(time_human_readable_to_seconds("56:34:12.10"), 203652.1, 1e-8f);
EXPECT_EQ(time_human_readable_from_seconds(203652.1), "56:34:12.10");
}
CCL_NAMESPACE_END

View File

@ -14,15 +14,22 @@
* limitations under the License.
*/
#include "util/util_time.h"
#include <stdlib.h>
#include "util/util_time.h"
#include "util/util_windows.h"
#if !defined(_WIN32)
# include <sys/time.h>
# include <unistd.h>
#endif
#ifdef _WIN32
#include "util/util_math.h"
#include "util/util_string.h"
#include "util/util_windows.h"
CCL_NAMESPACE_BEGIN
#ifdef _WIN32
double time_dt()
{
__int64 frequency, counter;
@ -37,16 +44,7 @@ void time_sleep(double t)
{
Sleep((int)(t*1000));
}
CCL_NAMESPACE_END
#else
#include <sys/time.h>
#include <unistd.h>
CCL_NAMESPACE_BEGIN
double time_dt()
{
struct timeval now;
@ -73,7 +71,69 @@ void time_sleep(double t)
if(us > 0)
usleep(us);
}
#endif
/* Time in format "hours:minutes:seconds.hundreds" */
string time_human_readable_from_seconds(const double seconds)
{
const int h = (((int)seconds) / (60 * 60));
const int m = (((int)seconds) / 60) % 60;
const int s = (((int)seconds) % 60);
const int r = (((int)(seconds * 100)) % 100);
if(h > 0) {
return string_printf("%.2d:%.2d:%.2d.%.2d", h, m, s, r);
}
else {
return string_printf("%.2d:%.2d.%.2d", m, s, r);
}
}
double time_human_readable_to_seconds(const string& time_string)
{
/* Those are multiplies of a corresponding token surrounded by : in the
* time string, which denotes how to convert value to seconds.
* Effectively: seconds, minutes, hours, days in seconds. */
const int multipliers[] = {1, 60, 60*60, 24*60*60};
const int num_multiplies = sizeof(multipliers) / sizeof(*multipliers);
if(time_string.empty()) {
return 0.0;
}
double result = 0.0;
/* Split fractions of a second from the encoded time. */
vector<string> fraction_tokens;
string_split(fraction_tokens, time_string, ".", false);
const int num_fraction_tokens = fraction_tokens.size();
if(num_fraction_tokens == 0) {
/* Time string is malformed. */
return 0.0;
}
else if(fraction_tokens.size() == 1) {
/* There is no fraction of a second specified, the rest of the code
* handles this normally. */
}
else if(fraction_tokens.size() == 2) {
result = atof(fraction_tokens[1].c_str());
result *= pow(0.1, fraction_tokens[1].length());
}
else {
/* This is not a valid string, the result can not be reliable. */
return 0.0;
}
/* Split hours, minutes and seconds.
* Hours part is optional. */
vector<string> tokens;
string_split(tokens, fraction_tokens[0], ":", false);
const int num_tokens = tokens.size();
if(num_tokens > num_multiplies) {
/* Can not reliably represent the value. */
return 0.0;
}
for(int i = 0; i < num_tokens; ++i) {
result += atoi(tokens[num_tokens - i - 1].c_str()) * multipliers[i];
}
return result;
}
CCL_NAMESPACE_END
#endif

View File

@ -17,16 +17,20 @@
#ifndef __UTIL_TIME_H__
#define __UTIL_TIME_H__
#include "util/util_string.h"
CCL_NAMESPACE_BEGIN
/* Give current time in seconds in double precision, with good accuracy. */
double time_dt();
/* Sleep for the specified number of seconds */
/* Sleep for the specified number of seconds. */
void time_sleep(double t);
/* Scoped timer. */
class scoped_timer {
public:
explicit scoped_timer(double *value = NULL) : value_(value)
@ -56,6 +60,11 @@ protected:
double time_start_;
};
/* Make human readable string from time, compatible with Blender metadata. */
string time_human_readable_from_seconds(const double seconds);
double time_human_readable_to_seconds(const string& str);
CCL_NAMESPACE_END
#endif