Skip to content

Commit 779358f

Browse files
committed
Chunked image encoding
1 parent 699a272 commit 779358f

File tree

7 files changed

+413
-13
lines changed

7 files changed

+413
-13
lines changed

binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"src/mapnik_geometry.cpp",
1414
"src/mapnik_feature.cpp",
1515
"src/mapnik_image.cpp",
16+
"src/mapnik_image_encode_chunked.cpp",
1617
"src/mapnik_image_view.cpp",
1718
"src/mapnik_grid.cpp",
1819
"src/mapnik_grid_view.cpp",

src/callback_streambuf.hpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#ifndef CALLBACK_STREAMBUF_H
2+
#define CALLBACK_STREAMBUF_H
3+
4+
#include <array>
5+
#include <iostream>
6+
7+
template<typename Callback, class Char = char>
8+
class callback_streambuf : public std::basic_streambuf<Char>
9+
{
10+
public:
11+
using base_type = std::streambuf;
12+
using char_type = typename base_type::char_type;
13+
using int_type = typename base_type::int_type;
14+
15+
callback_streambuf(Callback callback, std::size_t buffer_size)
16+
: callback_(callback),
17+
buffer_(buffer_size)
18+
{
19+
base_type::setp(buffer_.data(), buffer_.data() + buffer_.size());
20+
}
21+
22+
protected:
23+
int sync()
24+
{
25+
bool ok = callback_(base_type::pbase(),
26+
base_type::pptr() - base_type::pbase());
27+
base_type::setp(buffer_.data(), buffer_.data() + buffer_.size());
28+
return ok ? 0 : -1;
29+
}
30+
31+
int_type overflow(int_type ch)
32+
{
33+
int ret = sync();
34+
if (ch == base_type::traits_type::eof())
35+
{
36+
return ch;
37+
}
38+
base_type::sputc(ch);
39+
return ret == 0 ? 0 : base_type::traits_type::eof();
40+
}
41+
42+
private:
43+
Callback callback_;
44+
std::vector<char_type> buffer_;
45+
};
46+
47+
#endif

src/mapnik_image.cpp

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
#include <mapnik/svg/svg_renderer_agg.hpp>
2323
#include <mapnik/svg/svg_path_attributes.hpp>
2424

25-
#include "mapnik_image.hpp"
25+
#include "mapnik_image_encode.hpp"
2626
#include "mapnik_image_view.hpp"
27-
#include "mapnik_palette.hpp"
2827
#include "mapnik_color.hpp"
2928

3029
#include "utils.hpp"
30+
#include "callback_streambuf.hpp"
3131

3232
#include "agg_rasterizer_scanline_aa.h"
3333
#include "agg_basics.h"
@@ -82,6 +82,7 @@ void Image::Initialize(v8::Local<v8::Object> target) {
8282
Nan::SetPrototypeMethod(lcons, "setPixel", setPixel);
8383
Nan::SetPrototypeMethod(lcons, "encodeSync", encodeSync);
8484
Nan::SetPrototypeMethod(lcons, "encode", encode);
85+
Nan::SetPrototypeMethod(lcons, "encodeChunked", encodeChunked);
8586
Nan::SetPrototypeMethod(lcons, "view", view);
8687
Nan::SetPrototypeMethod(lcons, "saveSync", saveSync);
8788
Nan::SetPrototypeMethod(lcons, "save", save);
@@ -3609,17 +3610,6 @@ NAN_METHOD(Image::encodeSync)
36093610
}
36103611
}
36113612

3612-
typedef struct {
3613-
uv_work_t request;
3614-
Image* im;
3615-
std::string format;
3616-
palette_ptr palette;
3617-
bool error;
3618-
std::string error_name;
3619-
Nan::Persistent<v8::Function> cb;
3620-
std::string result;
3621-
} encode_image_baton_t;
3622-
36233613
/**
36243614
* Encode this image into a buffer of encoded data
36253615
*

src/mapnik_image.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ class Image: public Nan::ObjectWrap {
2929
static NAN_METHOD(setPixel);
3030
static NAN_METHOD(encodeSync);
3131
static NAN_METHOD(encode);
32+
static NAN_METHOD(encodeChunked);
3233
static void EIO_Encode(uv_work_t* req);
3334
static void EIO_AfterEncode(uv_work_t* req);
35+
static void EIO_EncodeChunked(uv_work_t* req);
36+
static void EIO_AfterEncodeChunked(uv_work_t* req, int status);
3437

3538
static NAN_METHOD(setGrayScaleToAlpha);
3639
static NAN_METHOD(width);

src/mapnik_image_encode.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef __NODE_MAPNIK_IMAGE_ENCODE_H__
2+
#define __NODE_MAPNIK_IMAGE_ENCODE_H__
3+
4+
#include "mapnik_image.hpp"
5+
#include "mapnik_palette.hpp"
6+
7+
typedef struct {
8+
uv_work_t request;
9+
Image* im;
10+
std::string format;
11+
palette_ptr palette;
12+
bool error;
13+
std::string error_name;
14+
Nan::Persistent<v8::Function> cb;
15+
std::string result;
16+
} encode_image_baton_t;
17+
18+
#endif

src/mapnik_image_encode_chunked.cpp

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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

Comments
 (0)