|
| 1 | +// mapnik |
| 2 | +#include <mapnik/image.hpp> // for image types |
| 3 | +#include <mapnik/image_util.hpp> // for save_to_stream |
| 4 | + |
| 5 | +#include "mapnik_image_encode.hpp" |
| 6 | +#include "mapnik_color.hpp" |
| 7 | + |
| 8 | +#include "utils.hpp" |
| 9 | +#include "callback_streambuf.hpp" |
| 10 | + |
| 11 | +struct chunked_encode_image_baton_t |
| 12 | +{ |
| 13 | + encode_image_baton_t image_baton; |
| 14 | + |
| 15 | + uv_async_t async; |
| 16 | + uv_mutex_t mutex; |
| 17 | + |
| 18 | + using char_type = char; |
| 19 | + using buffer_type = std::vector<char_type>; |
| 20 | + using buffer_list_type = std::vector<buffer_type>; |
| 21 | + buffer_list_type buffers; |
| 22 | + |
| 23 | + const std::size_t buffer_size; |
| 24 | + |
| 25 | + chunked_encode_image_baton_t(std::size_t buffer_size_) |
| 26 | + : buffer_size(buffer_size_) |
| 27 | + { |
| 28 | + // The reinterpret_cast is for backward compatibility |
| 29 | + // https://github.com/libuv/libuv/commit/db2a9072bce129630214904be5e2eedeaafc9835 |
| 30 | + if (uv_async_init(uv_default_loop(), &async, |
| 31 | + reinterpret_cast<uv_async_cb>(yield_chunk))) |
| 32 | + { |
| 33 | + throw std::runtime_error("Cannot create async handler"); |
| 34 | + } |
| 35 | + |
| 36 | + if (uv_mutex_init(&mutex)) |
| 37 | + { |
| 38 | + uv_close(reinterpret_cast<uv_handle_t*>(&async), NULL); |
| 39 | + throw std::runtime_error("Cannot create mutex"); |
| 40 | + } |
| 41 | + |
| 42 | + async.data = this; |
| 43 | + } |
| 44 | + |
| 45 | + ~chunked_encode_image_baton_t() |
| 46 | + { |
| 47 | + uv_mutex_destroy(&mutex); |
| 48 | + } |
| 49 | + |
| 50 | + template<class Char, class Size> |
| 51 | + bool operator()(const Char* buffer, Size size) |
| 52 | + { |
| 53 | + uv_mutex_lock(&mutex); |
| 54 | + buffers.emplace_back(buffer, buffer + size); |
| 55 | + uv_mutex_unlock(&mutex); |
| 56 | + |
| 57 | + return uv_async_send(&async) == 0; |
| 58 | + } |
| 59 | + |
| 60 | + static void yield_chunk(uv_async_t* handle) |
| 61 | + { |
| 62 | + using closure_type = chunked_encode_image_baton_t; |
| 63 | + closure_type & closure = *reinterpret_cast<closure_type*>(handle->data); |
| 64 | + |
| 65 | + if (closure.image_baton.error) |
| 66 | + { |
| 67 | + uv_close(reinterpret_cast<uv_handle_t*>(handle), async_close_cb); |
| 68 | + return; |
| 69 | + } |
| 70 | + |
| 71 | + buffer_list_type local_buffers; |
| 72 | + |
| 73 | + uv_mutex_lock(&closure.mutex); |
| 74 | + closure.buffers.swap(local_buffers); |
| 75 | + uv_mutex_unlock(&closure.mutex); |
| 76 | + |
| 77 | + Nan::HandleScope scope; |
| 78 | + bool done = false; |
| 79 | + |
| 80 | + for (auto const & buffer : local_buffers) |
| 81 | + { |
| 82 | + v8::Local<v8::Value> argv[2] = { |
| 83 | + Nan::Null(), Nan::CopyBuffer(buffer.data(), |
| 84 | + buffer.size()).ToLocalChecked() }; |
| 85 | + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), |
| 86 | + Nan::New(closure.image_baton.cb), 2, argv); |
| 87 | + done = buffer.empty(); |
| 88 | + } |
| 89 | + |
| 90 | + if (done) |
| 91 | + { |
| 92 | + uv_close(reinterpret_cast<uv_handle_t*>(handle), async_close_cb); |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + static void async_close_cb(uv_handle_t* handle) |
| 97 | + { |
| 98 | + using closure_type = chunked_encode_image_baton_t; |
| 99 | + closure_type & closure = *reinterpret_cast<closure_type*>(handle->data); |
| 100 | + |
| 101 | + if (closure.image_baton.error) |
| 102 | + { |
| 103 | + Nan::HandleScope scope; |
| 104 | + v8::Local<v8::Value> argv[1] = { |
| 105 | + Nan::Error(closure.image_baton.error_name.c_str()) }; |
| 106 | + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), |
| 107 | + Nan::New(closure.image_baton.cb), 1, argv); |
| 108 | + } |
| 109 | + |
| 110 | + closure.image_baton.im->_unref(); |
| 111 | + closure.image_baton.cb.Reset(); |
| 112 | + delete &closure; |
| 113 | + } |
| 114 | +}; |
| 115 | + |
| 116 | +void Image::EIO_EncodeChunked(uv_work_t* work) |
| 117 | +{ |
| 118 | + using closure_type = chunked_encode_image_baton_t; |
| 119 | + closure_type & closure = *reinterpret_cast<closure_type*>(work->data); |
| 120 | + try |
| 121 | + { |
| 122 | + callback_streambuf<closure_type&> streambuf(closure, closure.buffer_size); |
| 123 | + std::ostream stream(&streambuf); |
| 124 | + |
| 125 | + if (closure.image_baton.palette) |
| 126 | + { |
| 127 | + mapnik::save_to_stream(*closure.image_baton.im->this_, |
| 128 | + stream, |
| 129 | + closure.image_baton.format, |
| 130 | + *closure.image_baton.palette); |
| 131 | + } |
| 132 | + else |
| 133 | + { |
| 134 | + mapnik::save_to_stream(*closure.image_baton.im->this_, |
| 135 | + stream, |
| 136 | + closure.image_baton.format); |
| 137 | + } |
| 138 | + |
| 139 | + stream.flush(); |
| 140 | + } |
| 141 | + catch (std::exception const& ex) |
| 142 | + { |
| 143 | + closure.image_baton.error = true; |
| 144 | + closure.image_baton.error_name = ex.what(); |
| 145 | + } |
| 146 | + |
| 147 | + // Signalize end of stream |
| 148 | + closure(static_cast<char *>(NULL), 0); |
| 149 | +} |
| 150 | + |
| 151 | +NAN_METHOD(Image::encodeChunked) |
| 152 | +{ |
| 153 | + Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder()); |
| 154 | + |
| 155 | + std::string format = "png"; |
| 156 | + palette_ptr palette; |
| 157 | + |
| 158 | + if (info.Length() != 4) |
| 159 | + { |
| 160 | + Nan::ThrowTypeError("Function requires four arguments"); |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + // accept custom format |
| 165 | + if (!info[0]->IsString()) |
| 166 | + { |
| 167 | + Nan::ThrowTypeError("first arg, 'format' must be a string"); |
| 168 | + return; |
| 169 | + } |
| 170 | + format = TOSTR(info[0]); |
| 171 | + |
| 172 | + // options hash |
| 173 | + if (!info[1]->IsObject()) |
| 174 | + { |
| 175 | + Nan::ThrowTypeError("second arg must be an options object"); |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + v8::Local<v8::Object> options = info[1].As<v8::Object>(); |
| 180 | + |
| 181 | + if (options->Has(Nan::New("palette").ToLocalChecked())) |
| 182 | + { |
| 183 | + v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked()); |
| 184 | + if (!format_opt->IsObject()) |
| 185 | + { |
| 186 | + Nan::ThrowTypeError("'palette' must be an object"); |
| 187 | + return; |
| 188 | + } |
| 189 | + |
| 190 | + v8::Local<v8::Object> obj = format_opt.As<v8::Object>(); |
| 191 | + if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) |
| 192 | + { |
| 193 | + Nan::ThrowTypeError("mapnik.Palette expected as second arg"); |
| 194 | + return; |
| 195 | + } |
| 196 | + |
| 197 | + palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette(); |
| 198 | + } |
| 199 | + |
| 200 | + int buffer_size; |
| 201 | + if (!info[2]->IsNumber() || (buffer_size = info[2]->IntegerValue()) < 1) |
| 202 | + { |
| 203 | + Nan::ThrowTypeError("third arg must be a positive integer"); |
| 204 | + return; |
| 205 | + } |
| 206 | + |
| 207 | + // ensure callback is a function |
| 208 | + v8::Local<v8::Value> callback = info[info.Length() - 1]; |
| 209 | + if (!callback->IsFunction()) |
| 210 | + { |
| 211 | + Nan::ThrowTypeError("last argument must be a callback function"); |
| 212 | + return; |
| 213 | + } |
| 214 | + |
| 215 | + chunked_encode_image_baton_t *closure = new chunked_encode_image_baton_t(buffer_size); |
| 216 | + closure->image_baton.request.data = closure; |
| 217 | + closure->image_baton.im = im; |
| 218 | + closure->image_baton.format = format; |
| 219 | + closure->image_baton.palette = palette; |
| 220 | + closure->image_baton.error = false; |
| 221 | + closure->image_baton.cb.Reset(callback.As<v8::Function>()); |
| 222 | + |
| 223 | + uv_queue_work(uv_default_loop(), &closure->image_baton.request, EIO_EncodeChunked, NULL); |
| 224 | + im->Ref(); |
| 225 | +} |
0 commit comments