@shqld/canvas 2.11.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +600 -0
- package/binding.gyp +230 -0
- package/browser.js +35 -0
- package/index.js +94 -0
- package/lib/DOMMatrix.js +620 -0
- package/lib/bindings.js +80 -0
- package/lib/canvas.js +113 -0
- package/lib/context2d.js +14 -0
- package/lib/image.js +96 -0
- package/lib/jpegstream.js +41 -0
- package/lib/parse-font.js +101 -0
- package/lib/pattern.js +17 -0
- package/lib/pdfstream.js +35 -0
- package/lib/pngstream.js +42 -0
- package/package.json +71 -0
- package/scripts/install.js +24 -0
- package/src/Backends.cc +18 -0
- package/src/Backends.h +10 -0
- package/src/Canvas.cc +965 -0
- package/src/Canvas.h +96 -0
- package/src/CanvasError.h +23 -0
- package/src/CanvasGradient.cc +123 -0
- package/src/CanvasGradient.h +22 -0
- package/src/CanvasPattern.cc +136 -0
- package/src/CanvasPattern.h +37 -0
- package/src/CanvasRenderingContext2d.cc +3360 -0
- package/src/CanvasRenderingContext2d.h +225 -0
- package/src/Image.cc +1434 -0
- package/src/Image.h +127 -0
- package/src/ImageData.cc +146 -0
- package/src/ImageData.h +27 -0
- package/src/JPEGStream.h +167 -0
- package/src/PNG.h +292 -0
- package/src/Point.h +11 -0
- package/src/Util.h +9 -0
- package/src/backend/Backend.cc +112 -0
- package/src/backend/Backend.h +69 -0
- package/src/backend/ImageBackend.cc +74 -0
- package/src/backend/ImageBackend.h +26 -0
- package/src/backend/PdfBackend.cc +53 -0
- package/src/backend/PdfBackend.h +24 -0
- package/src/backend/SvgBackend.cc +61 -0
- package/src/backend/SvgBackend.h +24 -0
- package/src/bmp/BMPParser.cc +457 -0
- package/src/bmp/BMPParser.h +60 -0
- package/src/bmp/LICENSE.md +24 -0
- package/src/closure.cc +26 -0
- package/src/closure.h +81 -0
- package/src/color.cc +779 -0
- package/src/color.h +30 -0
- package/src/dll_visibility.h +20 -0
- package/src/init.cc +94 -0
- package/src/register_font.cc +408 -0
- package/src/register_font.h +7 -0
- package/types/index.d.ts +484 -0
- package/util/has_lib.js +119 -0
- package/util/win_jpeg_lookup.js +21 -0
package/src/Image.cc
ADDED
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|
2
|
+
|
|
3
|
+
#include "Image.h"
|
|
4
|
+
|
|
5
|
+
#include "bmp/BMPParser.h"
|
|
6
|
+
#include "Canvas.h"
|
|
7
|
+
#include <cerrno>
|
|
8
|
+
#include <cstdlib>
|
|
9
|
+
#include <cstring>
|
|
10
|
+
#include <node_buffer.h>
|
|
11
|
+
|
|
12
|
+
/* Cairo limit:
|
|
13
|
+
* https://lists.cairographics.org/archives/cairo/2010-December/021422.html
|
|
14
|
+
*/
|
|
15
|
+
static constexpr int canvas_max_side = (1 << 15) - 1;
|
|
16
|
+
|
|
17
|
+
#ifdef HAVE_GIF
|
|
18
|
+
typedef struct {
|
|
19
|
+
uint8_t *buf;
|
|
20
|
+
unsigned len;
|
|
21
|
+
unsigned pos;
|
|
22
|
+
} gif_data_t;
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
#ifdef HAVE_JPEG
|
|
26
|
+
#include <csetjmp>
|
|
27
|
+
|
|
28
|
+
struct canvas_jpeg_error_mgr: jpeg_error_mgr {
|
|
29
|
+
Image* image;
|
|
30
|
+
jmp_buf setjmp_buffer;
|
|
31
|
+
};
|
|
32
|
+
#endif
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Read closure used by loadFromBuffer.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
typedef struct {
|
|
39
|
+
unsigned len;
|
|
40
|
+
uint8_t *buf;
|
|
41
|
+
} read_closure_t;
|
|
42
|
+
|
|
43
|
+
using namespace v8;
|
|
44
|
+
|
|
45
|
+
Nan::Persistent<FunctionTemplate> Image::constructor;
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* Initialize Image.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
void
|
|
52
|
+
Image::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
|
53
|
+
Nan::HandleScope scope;
|
|
54
|
+
|
|
55
|
+
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Image::New);
|
|
56
|
+
constructor.Reset(ctor);
|
|
57
|
+
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
|
58
|
+
ctor->SetClassName(Nan::New("Image").ToLocalChecked());
|
|
59
|
+
|
|
60
|
+
// Prototype
|
|
61
|
+
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
|
|
62
|
+
Nan::SetAccessor(proto, Nan::New("complete").ToLocalChecked(), GetComplete);
|
|
63
|
+
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
|
|
64
|
+
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
|
|
65
|
+
Nan::SetAccessor(proto, Nan::New("naturalWidth").ToLocalChecked(), GetNaturalWidth);
|
|
66
|
+
Nan::SetAccessor(proto, Nan::New("naturalHeight").ToLocalChecked(), GetNaturalHeight);
|
|
67
|
+
Nan::SetAccessor(proto, Nan::New("dataMode").ToLocalChecked(), GetDataMode, SetDataMode);
|
|
68
|
+
|
|
69
|
+
ctor->Set(Nan::New("MODE_IMAGE").ToLocalChecked(), Nan::New<Number>(DATA_IMAGE));
|
|
70
|
+
ctor->Set(Nan::New("MODE_MIME").ToLocalChecked(), Nan::New<Number>(DATA_MIME));
|
|
71
|
+
|
|
72
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
73
|
+
Nan::Set(target, Nan::New("Image").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
|
|
74
|
+
|
|
75
|
+
// Used internally in lib/image.js
|
|
76
|
+
NAN_EXPORT(target, GetSource);
|
|
77
|
+
NAN_EXPORT(target, SetSource);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Initialize a new Image.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
NAN_METHOD(Image::New) {
|
|
85
|
+
if (!info.IsConstructCall()) {
|
|
86
|
+
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Image *img = new Image;
|
|
90
|
+
img->data_mode = DATA_IMAGE;
|
|
91
|
+
img->Wrap(info.This());
|
|
92
|
+
Nan::Set(info.This(), Nan::New("onload").ToLocalChecked(), Nan::Null()).Check();
|
|
93
|
+
Nan::Set(info.This(), Nan::New("onerror").ToLocalChecked(), Nan::Null()).Check();
|
|
94
|
+
info.GetReturnValue().Set(info.This());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
* Get complete boolean.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
NAN_GETTER(Image::GetComplete) {
|
|
102
|
+
info.GetReturnValue().Set(Nan::New<Boolean>(true));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Get dataMode.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
NAN_GETTER(Image::GetDataMode) {
|
|
110
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
111
|
+
Nan::ThrowTypeError("Method Image.GetDataMode called on incompatible receiver");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
115
|
+
info.GetReturnValue().Set(Nan::New<Number>(img->data_mode));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Set dataMode.
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
NAN_SETTER(Image::SetDataMode) {
|
|
123
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
124
|
+
Nan::ThrowTypeError("Method Image.SetDataMode called on incompatible receiver");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (value->IsNumber()) {
|
|
128
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
129
|
+
int mode = Nan::To<uint32_t>(value).FromMaybe(0);
|
|
130
|
+
img->data_mode = (data_mode_t) mode;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* Get natural width
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
NAN_GETTER(Image::GetNaturalWidth) {
|
|
139
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
140
|
+
Nan::ThrowTypeError("Method Image.GetNaturalWidth called on incompatible receiver");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
144
|
+
info.GetReturnValue().Set(Nan::New<Number>(img->naturalWidth));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
* Get width.
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
NAN_GETTER(Image::GetWidth) {
|
|
152
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
153
|
+
Nan::ThrowTypeError("Method Image.GetWidth called on incompatible receiver");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
157
|
+
info.GetReturnValue().Set(Nan::New<Number>(img->width));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* Set width.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
NAN_SETTER(Image::SetWidth) {
|
|
165
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
166
|
+
Nan::ThrowTypeError("Method Image.SetWidth called on incompatible receiver");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (value->IsNumber()) {
|
|
170
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
171
|
+
img->width = Nan::To<uint32_t>(value).FromMaybe(0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* Get natural height
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
NAN_GETTER(Image::GetNaturalHeight) {
|
|
180
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
181
|
+
Nan::ThrowTypeError("Method Image.GetNaturalHeight called on incompatible receiver");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
185
|
+
info.GetReturnValue().Set(Nan::New<Number>(img->naturalHeight));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* Get height.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
NAN_GETTER(Image::GetHeight) {
|
|
193
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
194
|
+
Nan::ThrowTypeError("Method Image.GetHeight called on incompatible receiver");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
198
|
+
info.GetReturnValue().Set(Nan::New<Number>(img->height));
|
|
199
|
+
}
|
|
200
|
+
/*
|
|
201
|
+
* Set height.
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
NAN_SETTER(Image::SetHeight) {
|
|
205
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
206
|
+
// #1534
|
|
207
|
+
Nan::ThrowTypeError("Method Image.SetHeight called on incompatible receiver");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (value->IsNumber()) {
|
|
211
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
212
|
+
img->height = Nan::To<uint32_t>(value).FromMaybe(0);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/*
|
|
217
|
+
* Get src path.
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
NAN_METHOD(Image::GetSource){
|
|
221
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
222
|
+
// #1534
|
|
223
|
+
Nan::ThrowTypeError("Method Image.GetSource called on incompatible receiver");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
227
|
+
info.GetReturnValue().Set(Nan::New<String>(img->filename ? img->filename : "").ToLocalChecked());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/*
|
|
231
|
+
* Clean up assets and variables.
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
void
|
|
235
|
+
Image::clearData() {
|
|
236
|
+
if (_surface) {
|
|
237
|
+
cairo_surface_destroy(_surface);
|
|
238
|
+
Nan::AdjustExternalMemory(-_data_len);
|
|
239
|
+
_data_len = 0;
|
|
240
|
+
_surface = NULL;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
delete[] _data;
|
|
244
|
+
_data = nullptr;
|
|
245
|
+
|
|
246
|
+
free(filename);
|
|
247
|
+
filename = NULL;
|
|
248
|
+
|
|
249
|
+
#ifdef HAVE_RSVG
|
|
250
|
+
if (_rsvg != NULL) {
|
|
251
|
+
g_object_unref(_rsvg);
|
|
252
|
+
_rsvg = NULL;
|
|
253
|
+
}
|
|
254
|
+
#endif
|
|
255
|
+
|
|
256
|
+
width = height = 0;
|
|
257
|
+
naturalWidth = naturalHeight = 0;
|
|
258
|
+
state = DEFAULT;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
* Set src path.
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
NAN_METHOD(Image::SetSource){
|
|
266
|
+
if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
|
|
267
|
+
// #1534
|
|
268
|
+
Nan::ThrowTypeError("Method Image.SetSource called on incompatible receiver");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
|
272
|
+
cairo_status_t status = CAIRO_STATUS_READ_ERROR;
|
|
273
|
+
|
|
274
|
+
Local<Value> value = info[0];
|
|
275
|
+
|
|
276
|
+
img->clearData();
|
|
277
|
+
// Clear errno in case some unrelated previous syscall failed
|
|
278
|
+
errno = 0;
|
|
279
|
+
|
|
280
|
+
// url string
|
|
281
|
+
if (value->IsString()) {
|
|
282
|
+
Nan::Utf8String src(value);
|
|
283
|
+
if (img->filename) free(img->filename);
|
|
284
|
+
img->filename = strdup(*src);
|
|
285
|
+
status = img->load();
|
|
286
|
+
// Buffer
|
|
287
|
+
} else if (node::Buffer::HasInstance(value)) {
|
|
288
|
+
uint8_t *buf = (uint8_t *) node::Buffer::Data(Nan::To<Object>(value).ToLocalChecked());
|
|
289
|
+
unsigned len = node::Buffer::Length(Nan::To<Object>(value).ToLocalChecked());
|
|
290
|
+
status = img->loadFromBuffer(buf, len);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (status) {
|
|
294
|
+
Local<Value> onerrorFn = Nan::Get(info.This(), Nan::New("onerror").ToLocalChecked()).ToLocalChecked();
|
|
295
|
+
if (onerrorFn->IsFunction()) {
|
|
296
|
+
Local<Value> argv[1];
|
|
297
|
+
CanvasError errorInfo = img->errorInfo;
|
|
298
|
+
if (errorInfo.cerrno) {
|
|
299
|
+
argv[0] = Nan::ErrnoException(errorInfo.cerrno, errorInfo.syscall.c_str(), errorInfo.message.c_str(), errorInfo.path.c_str());
|
|
300
|
+
} else if (!errorInfo.message.empty()) {
|
|
301
|
+
argv[0] = Nan::Error(Nan::New(errorInfo.message).ToLocalChecked());
|
|
302
|
+
} else {
|
|
303
|
+
argv[0] = Nan::Error(Nan::New(cairo_status_to_string(status)).ToLocalChecked());
|
|
304
|
+
}
|
|
305
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
306
|
+
Nan::Call(onerrorFn.As<Function>(), ctx->Global(), 1, argv);
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
img->loaded();
|
|
310
|
+
Local<Value> onloadFn = Nan::Get(info.This(), Nan::New("onload").ToLocalChecked()).ToLocalChecked();
|
|
311
|
+
if (onloadFn->IsFunction()) {
|
|
312
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
313
|
+
Nan::Call(onloadFn.As<Function>(), ctx->Global(), 0, NULL);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
* Load image data from `buf` by sniffing
|
|
320
|
+
* the bytes to determine format.
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
cairo_status_t
|
|
324
|
+
Image::loadFromBuffer(uint8_t *buf, unsigned len) {
|
|
325
|
+
uint8_t data[4] = {0};
|
|
326
|
+
memcpy(data, buf, (len < 4 ? len : 4) * sizeof(uint8_t));
|
|
327
|
+
|
|
328
|
+
if (isPNG(data)) return loadPNGFromBuffer(buf);
|
|
329
|
+
|
|
330
|
+
if (isGIF(data)) {
|
|
331
|
+
#ifdef HAVE_GIF
|
|
332
|
+
return loadGIFFromBuffer(buf, len);
|
|
333
|
+
#else
|
|
334
|
+
this->errorInfo.set("node-canvas was built without GIF support");
|
|
335
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
336
|
+
#endif
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (isJPEG(data)) {
|
|
340
|
+
#ifdef HAVE_JPEG
|
|
341
|
+
if (DATA_IMAGE == data_mode) return loadJPEGFromBuffer(buf, len);
|
|
342
|
+
if (DATA_MIME == data_mode) return decodeJPEGBufferIntoMimeSurface(buf, len);
|
|
343
|
+
if ((DATA_IMAGE | DATA_MIME) == data_mode) {
|
|
344
|
+
cairo_status_t status;
|
|
345
|
+
status = loadJPEGFromBuffer(buf, len);
|
|
346
|
+
if (status) return status;
|
|
347
|
+
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
|
348
|
+
}
|
|
349
|
+
#else // HAVE_JPEG
|
|
350
|
+
this->errorInfo.set("node-canvas was built without JPEG support");
|
|
351
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
352
|
+
#endif
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// confirm svg using first 1000 chars
|
|
356
|
+
// if a very long comment precedes the root <svg> tag, isSVG returns false
|
|
357
|
+
unsigned head_len = (len < 1000 ? len : 1000);
|
|
358
|
+
if (isSVG(buf, head_len)) {
|
|
359
|
+
#ifdef HAVE_RSVG
|
|
360
|
+
return loadSVGFromBuffer(buf, len);
|
|
361
|
+
#else
|
|
362
|
+
this->errorInfo.set("node-canvas was built without SVG support");
|
|
363
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
364
|
+
#endif
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (isBMP(buf, len))
|
|
368
|
+
return loadBMPFromBuffer(buf, len);
|
|
369
|
+
|
|
370
|
+
this->errorInfo.set("Unsupported image type");
|
|
371
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/*
|
|
375
|
+
* Load PNG data from `buf`.
|
|
376
|
+
*/
|
|
377
|
+
|
|
378
|
+
cairo_status_t
|
|
379
|
+
Image::loadPNGFromBuffer(uint8_t *buf) {
|
|
380
|
+
read_closure_t closure;
|
|
381
|
+
closure.len = 0;
|
|
382
|
+
closure.buf = buf;
|
|
383
|
+
_surface = cairo_image_surface_create_from_png_stream(readPNG, &closure);
|
|
384
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
385
|
+
if (status) return status;
|
|
386
|
+
return CAIRO_STATUS_SUCCESS;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/*
|
|
390
|
+
* Read PNG data.
|
|
391
|
+
*/
|
|
392
|
+
|
|
393
|
+
cairo_status_t
|
|
394
|
+
Image::readPNG(void *c, uint8_t *data, unsigned int len) {
|
|
395
|
+
read_closure_t *closure = (read_closure_t *) c;
|
|
396
|
+
memcpy(data, closure->buf + closure->len, len);
|
|
397
|
+
closure->len += len;
|
|
398
|
+
return CAIRO_STATUS_SUCCESS;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/*
|
|
402
|
+
* Initialize a new Image.
|
|
403
|
+
*/
|
|
404
|
+
|
|
405
|
+
Image::Image() {
|
|
406
|
+
filename = NULL;
|
|
407
|
+
_data = nullptr;
|
|
408
|
+
_data_len = 0;
|
|
409
|
+
_surface = NULL;
|
|
410
|
+
width = height = 0;
|
|
411
|
+
naturalWidth = naturalHeight = 0;
|
|
412
|
+
state = DEFAULT;
|
|
413
|
+
#ifdef HAVE_RSVG
|
|
414
|
+
_rsvg = NULL;
|
|
415
|
+
_is_svg = false;
|
|
416
|
+
_svg_last_width = _svg_last_height = 0;
|
|
417
|
+
#endif
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/*
|
|
421
|
+
* Destroy image and associated surface.
|
|
422
|
+
*/
|
|
423
|
+
|
|
424
|
+
Image::~Image() {
|
|
425
|
+
clearData();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* Initiate image loading.
|
|
430
|
+
*/
|
|
431
|
+
|
|
432
|
+
cairo_status_t
|
|
433
|
+
Image::load() {
|
|
434
|
+
if (LOADING != state) {
|
|
435
|
+
state = LOADING;
|
|
436
|
+
return loadSurface();
|
|
437
|
+
}
|
|
438
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/*
|
|
442
|
+
* Set state, assign dimensions.
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
void
|
|
446
|
+
Image::loaded() {
|
|
447
|
+
Nan::HandleScope scope;
|
|
448
|
+
state = COMPLETE;
|
|
449
|
+
|
|
450
|
+
width = naturalWidth = cairo_image_surface_get_width(_surface);
|
|
451
|
+
height = naturalHeight = cairo_image_surface_get_height(_surface);
|
|
452
|
+
_data_len = naturalHeight * cairo_image_surface_get_stride(_surface);
|
|
453
|
+
Nan::AdjustExternalMemory(_data_len);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/*
|
|
457
|
+
* Returns this image's surface.
|
|
458
|
+
*/
|
|
459
|
+
cairo_surface_t *Image::surface() {
|
|
460
|
+
#ifdef HAVE_RSVG
|
|
461
|
+
if (_is_svg && (_svg_last_width != width || _svg_last_height != height)) {
|
|
462
|
+
if (_surface != NULL) {
|
|
463
|
+
cairo_surface_destroy(_surface);
|
|
464
|
+
_surface = NULL;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
cairo_status_t status = renderSVGToSurface();
|
|
468
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
469
|
+
g_object_unref(_rsvg);
|
|
470
|
+
Nan::ThrowError(Canvas::Error(status));
|
|
471
|
+
return NULL;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
#endif
|
|
475
|
+
return _surface;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/*
|
|
479
|
+
* Load cairo surface from the image src.
|
|
480
|
+
*
|
|
481
|
+
* TODO: support more formats
|
|
482
|
+
* TODO: use node IO or at least thread pool
|
|
483
|
+
*/
|
|
484
|
+
|
|
485
|
+
cairo_status_t
|
|
486
|
+
Image::loadSurface() {
|
|
487
|
+
FILE *stream = fopen(filename, "rb");
|
|
488
|
+
if (!stream) {
|
|
489
|
+
this->errorInfo.set(NULL, "fopen", errno, filename);
|
|
490
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
491
|
+
}
|
|
492
|
+
uint8_t buf[5];
|
|
493
|
+
if (1 != fread(&buf, 5, 1, stream)) {
|
|
494
|
+
fclose(stream);
|
|
495
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
496
|
+
}
|
|
497
|
+
rewind(stream);
|
|
498
|
+
|
|
499
|
+
// png
|
|
500
|
+
if (isPNG(buf)) {
|
|
501
|
+
fclose(stream);
|
|
502
|
+
return loadPNG();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
if (isGIF(buf)) {
|
|
507
|
+
#ifdef HAVE_GIF
|
|
508
|
+
return loadGIF(stream);
|
|
509
|
+
#else
|
|
510
|
+
this->errorInfo.set("node-canvas was built without GIF support");
|
|
511
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
512
|
+
#endif
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (isJPEG(buf)) {
|
|
516
|
+
#ifdef HAVE_JPEG
|
|
517
|
+
return loadJPEG(stream);
|
|
518
|
+
#else
|
|
519
|
+
this->errorInfo.set("node-canvas was built without JPEG support");
|
|
520
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
521
|
+
#endif
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// confirm svg using first 1000 chars
|
|
525
|
+
// if a very long comment precedes the root <svg> tag, isSVG returns false
|
|
526
|
+
uint8_t head[1000] = {0};
|
|
527
|
+
fseek(stream, 0 , SEEK_END);
|
|
528
|
+
long len = ftell(stream);
|
|
529
|
+
unsigned head_len = (len < 1000 ? len : 1000);
|
|
530
|
+
unsigned head_size = head_len * sizeof(uint8_t);
|
|
531
|
+
rewind(stream);
|
|
532
|
+
if (head_size != fread(&head, 1, head_size, stream)) {
|
|
533
|
+
fclose(stream);
|
|
534
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
535
|
+
}
|
|
536
|
+
rewind(stream);
|
|
537
|
+
if (isSVG(head, head_len)) {
|
|
538
|
+
#ifdef HAVE_RSVG
|
|
539
|
+
return loadSVG(stream);
|
|
540
|
+
#else
|
|
541
|
+
this->errorInfo.set("node-canvas was built without SVG support");
|
|
542
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
543
|
+
#endif
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (isBMP(buf, 2))
|
|
547
|
+
return loadBMP(stream);
|
|
548
|
+
|
|
549
|
+
fclose(stream);
|
|
550
|
+
|
|
551
|
+
this->errorInfo.set("Unsupported image type");
|
|
552
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/*
|
|
556
|
+
* Load PNG.
|
|
557
|
+
*/
|
|
558
|
+
|
|
559
|
+
cairo_status_t
|
|
560
|
+
Image::loadPNG() {
|
|
561
|
+
_surface = cairo_image_surface_create_from_png(filename);
|
|
562
|
+
return cairo_surface_status(_surface);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// GIF support
|
|
566
|
+
|
|
567
|
+
#ifdef HAVE_GIF
|
|
568
|
+
|
|
569
|
+
/*
|
|
570
|
+
* Return the alpha color for `gif` at `frame`, or -1.
|
|
571
|
+
*/
|
|
572
|
+
|
|
573
|
+
int
|
|
574
|
+
get_gif_transparent_color(GifFileType *gif, int frame) {
|
|
575
|
+
ExtensionBlock *ext = gif->SavedImages[frame].ExtensionBlocks;
|
|
576
|
+
int len = gif->SavedImages[frame].ExtensionBlockCount;
|
|
577
|
+
for (int x = 0; x < len; ++x, ++ext) {
|
|
578
|
+
if ((ext->Function == GRAPHICS_EXT_FUNC_CODE) && (ext->Bytes[0] & 1)) {
|
|
579
|
+
return ext->Bytes[3] == 0 ? 0 : (uint8_t) ext->Bytes[3];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return -1;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/*
|
|
586
|
+
* Memory GIF reader callback.
|
|
587
|
+
*/
|
|
588
|
+
|
|
589
|
+
int
|
|
590
|
+
read_gif_from_memory(GifFileType *gif, GifByteType *buf, int len) {
|
|
591
|
+
gif_data_t *data = (gif_data_t *) gif->UserData;
|
|
592
|
+
if ((data->pos + len) > data->len) len = data->len - data->pos;
|
|
593
|
+
memcpy(buf, data->pos + data->buf, len);
|
|
594
|
+
data->pos += len;
|
|
595
|
+
return len;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/*
|
|
599
|
+
* Load GIF.
|
|
600
|
+
*/
|
|
601
|
+
|
|
602
|
+
cairo_status_t
|
|
603
|
+
Image::loadGIF(FILE *stream) {
|
|
604
|
+
struct stat s;
|
|
605
|
+
int fd = fileno(stream);
|
|
606
|
+
|
|
607
|
+
// stat
|
|
608
|
+
if (fstat(fd, &s) < 0) {
|
|
609
|
+
fclose(stream);
|
|
610
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
uint8_t *buf = (uint8_t *) malloc(s.st_size);
|
|
614
|
+
|
|
615
|
+
if (!buf) {
|
|
616
|
+
fclose(stream);
|
|
617
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
618
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
size_t read = fread(buf, s.st_size, 1, stream);
|
|
622
|
+
fclose(stream);
|
|
623
|
+
|
|
624
|
+
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
|
625
|
+
if (1 == read) result = loadGIFFromBuffer(buf, s.st_size);
|
|
626
|
+
free(buf);
|
|
627
|
+
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/*
|
|
632
|
+
* Load give from `buf` and the given `len`.
|
|
633
|
+
*/
|
|
634
|
+
|
|
635
|
+
cairo_status_t
|
|
636
|
+
Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) {
|
|
637
|
+
int i = 0;
|
|
638
|
+
GifFileType* gif;
|
|
639
|
+
|
|
640
|
+
gif_data_t gifd = { buf, len, 0 };
|
|
641
|
+
|
|
642
|
+
#if GIFLIB_MAJOR >= 5
|
|
643
|
+
int errorcode;
|
|
644
|
+
if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory, &errorcode)) == NULL)
|
|
645
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
646
|
+
#else
|
|
647
|
+
if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory)) == NULL)
|
|
648
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
649
|
+
#endif
|
|
650
|
+
|
|
651
|
+
if (GIF_OK != DGifSlurp(gif)) {
|
|
652
|
+
GIF_CLOSE_FILE(gif);
|
|
653
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (gif->SWidth > canvas_max_side || gif->SHeight > canvas_max_side) {
|
|
657
|
+
GIF_CLOSE_FILE(gif);
|
|
658
|
+
return CAIRO_STATUS_INVALID_SIZE;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
width = naturalWidth = gif->SWidth;
|
|
662
|
+
height = naturalHeight = gif->SHeight;
|
|
663
|
+
|
|
664
|
+
uint8_t *data = new uint8_t[naturalWidth * naturalHeight * 4];
|
|
665
|
+
if (!data) {
|
|
666
|
+
GIF_CLOSE_FILE(gif);
|
|
667
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
668
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
GifImageDesc *img = &gif->SavedImages[i].ImageDesc;
|
|
672
|
+
|
|
673
|
+
// local colormap takes precedence over global
|
|
674
|
+
ColorMapObject *colormap = img->ColorMap
|
|
675
|
+
? img->ColorMap
|
|
676
|
+
: gif->SColorMap;
|
|
677
|
+
|
|
678
|
+
if (colormap == nullptr) {
|
|
679
|
+
GIF_CLOSE_FILE(gif);
|
|
680
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
int bgColor = 0;
|
|
684
|
+
int alphaColor = get_gif_transparent_color(gif, i);
|
|
685
|
+
if (gif->SColorMap) bgColor = (uint8_t) gif->SBackGroundColor;
|
|
686
|
+
else if(alphaColor >= 0) bgColor = alphaColor;
|
|
687
|
+
|
|
688
|
+
uint8_t *src_data = (uint8_t*) gif->SavedImages[i].RasterBits;
|
|
689
|
+
uint32_t *dst_data = (uint32_t*) data;
|
|
690
|
+
|
|
691
|
+
if (!gif->Image.Interlace) {
|
|
692
|
+
if (naturalWidth == img->Width && naturalHeight == img->Height) {
|
|
693
|
+
for (int y = 0; y < naturalHeight; ++y) {
|
|
694
|
+
for (int x = 0; x < naturalWidth; ++x) {
|
|
695
|
+
*dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
|
|
696
|
+
| colormap->Colors[*src_data].Red << 16
|
|
697
|
+
| colormap->Colors[*src_data].Green << 8
|
|
698
|
+
| colormap->Colors[*src_data].Blue;
|
|
699
|
+
|
|
700
|
+
dst_data++;
|
|
701
|
+
src_data++;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
// Image does not take up whole "screen" so we need to fill-in the background
|
|
706
|
+
int bottom = img->Top + img->Height;
|
|
707
|
+
int right = img->Left + img->Width;
|
|
708
|
+
|
|
709
|
+
uint32_t bgPixel =
|
|
710
|
+
((bgColor == alphaColor) ? 0 : 255) << 24
|
|
711
|
+
| colormap->Colors[bgColor].Red << 16
|
|
712
|
+
| colormap->Colors[bgColor].Green << 8
|
|
713
|
+
| colormap->Colors[bgColor].Blue;
|
|
714
|
+
|
|
715
|
+
for (int y = 0; y < naturalHeight; ++y) {
|
|
716
|
+
for (int x = 0; x < naturalWidth; ++x) {
|
|
717
|
+
if (y < img->Top || y >= bottom || x < img->Left || x >= right) {
|
|
718
|
+
*dst_data = bgPixel;
|
|
719
|
+
dst_data++;
|
|
720
|
+
} else {
|
|
721
|
+
*dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
|
|
722
|
+
| colormap->Colors[*src_data].Red << 16
|
|
723
|
+
| colormap->Colors[*src_data].Green << 8
|
|
724
|
+
| colormap->Colors[*src_data].Blue;
|
|
725
|
+
dst_data++;
|
|
726
|
+
src_data++;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
} else {
|
|
732
|
+
// Image is interlaced so that it streams nice over 14.4k and 28.8k modems :)
|
|
733
|
+
// We first load in 1/8 of the image, followed by another 1/8, followed by
|
|
734
|
+
// 1/4 and finally the remaining 1/2.
|
|
735
|
+
int ioffs[] = { 0, 4, 2, 1 };
|
|
736
|
+
int ijumps[] = { 8, 8, 4, 2 };
|
|
737
|
+
|
|
738
|
+
uint8_t *src_ptr = src_data;
|
|
739
|
+
uint32_t *dst_ptr;
|
|
740
|
+
|
|
741
|
+
for(int z = 0; z < 4; z++) {
|
|
742
|
+
for(int y = ioffs[z]; y < naturalHeight; y += ijumps[z]) {
|
|
743
|
+
dst_ptr = dst_data + naturalWidth * y;
|
|
744
|
+
for(int x = 0; x < naturalWidth; ++x) {
|
|
745
|
+
*dst_ptr = ((*src_ptr == alphaColor) ? 0 : 255) << 24
|
|
746
|
+
| (colormap->Colors[*src_ptr].Red) << 16
|
|
747
|
+
| (colormap->Colors[*src_ptr].Green) << 8
|
|
748
|
+
| (colormap->Colors[*src_ptr].Blue);
|
|
749
|
+
|
|
750
|
+
dst_ptr++;
|
|
751
|
+
src_ptr++;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
GIF_CLOSE_FILE(gif);
|
|
758
|
+
|
|
759
|
+
// New image surface
|
|
760
|
+
_surface = cairo_image_surface_create_for_data(
|
|
761
|
+
data
|
|
762
|
+
, CAIRO_FORMAT_ARGB32
|
|
763
|
+
, naturalWidth
|
|
764
|
+
, naturalHeight
|
|
765
|
+
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
|
|
766
|
+
|
|
767
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
768
|
+
|
|
769
|
+
if (status) {
|
|
770
|
+
delete[] data;
|
|
771
|
+
return status;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
_data = data;
|
|
775
|
+
|
|
776
|
+
return CAIRO_STATUS_SUCCESS;
|
|
777
|
+
}
|
|
778
|
+
#endif /* HAVE_GIF */
|
|
779
|
+
|
|
780
|
+
// JPEG support
|
|
781
|
+
|
|
782
|
+
#ifdef HAVE_JPEG
|
|
783
|
+
|
|
784
|
+
// libjpeg 6.2 does not have jpeg_mem_src; define it ourselves here unless
|
|
785
|
+
// libjpeg 8 is installed.
|
|
786
|
+
#if JPEG_LIB_VERSION < 80 && !defined(MEM_SRCDST_SUPPORTED)
|
|
787
|
+
|
|
788
|
+
/* Read JPEG image from a memory segment */
|
|
789
|
+
static void
|
|
790
|
+
init_source(j_decompress_ptr cinfo) {}
|
|
791
|
+
|
|
792
|
+
static boolean
|
|
793
|
+
fill_input_buffer(j_decompress_ptr cinfo) {
|
|
794
|
+
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
|
795
|
+
return TRUE;
|
|
796
|
+
}
|
|
797
|
+
static void
|
|
798
|
+
skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
|
|
799
|
+
struct jpeg_source_mgr* src = (struct jpeg_source_mgr*) cinfo->src;
|
|
800
|
+
if (num_bytes > 0) {
|
|
801
|
+
src->next_input_byte += (size_t) num_bytes;
|
|
802
|
+
src->bytes_in_buffer -= (size_t) num_bytes;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
static void term_source (j_decompress_ptr cinfo) {}
|
|
807
|
+
static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) {
|
|
808
|
+
struct jpeg_source_mgr* src;
|
|
809
|
+
|
|
810
|
+
if (cinfo->src == NULL) {
|
|
811
|
+
cinfo->src = (struct jpeg_source_mgr *)
|
|
812
|
+
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
|
813
|
+
sizeof(struct jpeg_source_mgr));
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
src = (struct jpeg_source_mgr*) cinfo->src;
|
|
817
|
+
src->init_source = init_source;
|
|
818
|
+
src->fill_input_buffer = fill_input_buffer;
|
|
819
|
+
src->skip_input_data = skip_input_data;
|
|
820
|
+
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
|
|
821
|
+
src->term_source = term_source;
|
|
822
|
+
src->bytes_in_buffer = nbytes;
|
|
823
|
+
src->next_input_byte = (JOCTET*)buffer;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
#endif
|
|
827
|
+
|
|
828
|
+
void Image::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode) {
|
|
829
|
+
int stride = naturalWidth * 4;
|
|
830
|
+
for (int y = 0; y < naturalHeight; ++y) {
|
|
831
|
+
jpeg_read_scanlines(args, &src, 1);
|
|
832
|
+
uint32_t *row = (uint32_t*)(data + stride * y);
|
|
833
|
+
for (int x = 0; x < naturalWidth; ++x) {
|
|
834
|
+
int bx = args->output_components * x;
|
|
835
|
+
row[x] = decode(src + bx);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/*
|
|
841
|
+
* Takes an initialised jpeg_decompress_struct and decodes the
|
|
842
|
+
* data into _surface.
|
|
843
|
+
*/
|
|
844
|
+
|
|
845
|
+
cairo_status_t
|
|
846
|
+
Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) {
|
|
847
|
+
cairo_status_t status = CAIRO_STATUS_SUCCESS;
|
|
848
|
+
|
|
849
|
+
uint8_t *data = new uint8_t[naturalWidth * naturalHeight * 4];
|
|
850
|
+
if (!data) {
|
|
851
|
+
jpeg_abort_decompress(args);
|
|
852
|
+
jpeg_destroy_decompress(args);
|
|
853
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
854
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
uint8_t *src = new uint8_t[naturalWidth * args->output_components];
|
|
858
|
+
if (!src) {
|
|
859
|
+
free(data);
|
|
860
|
+
jpeg_abort_decompress(args);
|
|
861
|
+
jpeg_destroy_decompress(args);
|
|
862
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
863
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// These are the three main cases to handle. libjpeg converts YCCK to CMYK
|
|
867
|
+
// and YCbCr to RGB by default.
|
|
868
|
+
switch (args->out_color_space) {
|
|
869
|
+
case JCS_CMYK:
|
|
870
|
+
jpegToARGB(args, data, src, [](uint8_t const* src) {
|
|
871
|
+
uint16_t k = static_cast<uint16_t>(src[3]);
|
|
872
|
+
uint8_t r = k * src[0] / 255;
|
|
873
|
+
uint8_t g = k * src[1] / 255;
|
|
874
|
+
uint8_t b = k * src[2] / 255;
|
|
875
|
+
return 255 << 24 | r << 16 | g << 8 | b;
|
|
876
|
+
});
|
|
877
|
+
break;
|
|
878
|
+
case JCS_RGB:
|
|
879
|
+
jpegToARGB(args, data, src, [](uint8_t const* src) {
|
|
880
|
+
uint8_t r = src[0], g = src[1], b = src[2];
|
|
881
|
+
return 255 << 24 | r << 16 | g << 8 | b;
|
|
882
|
+
});
|
|
883
|
+
break;
|
|
884
|
+
case JCS_GRAYSCALE:
|
|
885
|
+
jpegToARGB(args, data, src, [](uint8_t const* src) {
|
|
886
|
+
uint8_t v = src[0];
|
|
887
|
+
return 255 << 24 | v << 16 | v << 8 | v;
|
|
888
|
+
});
|
|
889
|
+
break;
|
|
890
|
+
default:
|
|
891
|
+
this->errorInfo.set("Unsupported JPEG encoding");
|
|
892
|
+
status = CAIRO_STATUS_READ_ERROR;
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (!status) {
|
|
897
|
+
_surface = cairo_image_surface_create_for_data(
|
|
898
|
+
data
|
|
899
|
+
, CAIRO_FORMAT_ARGB32
|
|
900
|
+
, naturalWidth
|
|
901
|
+
, naturalHeight
|
|
902
|
+
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
jpeg_finish_decompress(args);
|
|
906
|
+
jpeg_destroy_decompress(args);
|
|
907
|
+
status = cairo_surface_status(_surface);
|
|
908
|
+
|
|
909
|
+
delete[] src;
|
|
910
|
+
|
|
911
|
+
if (status) {
|
|
912
|
+
delete[] data;
|
|
913
|
+
return status;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
_data = data;
|
|
917
|
+
|
|
918
|
+
return CAIRO_STATUS_SUCCESS;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/*
|
|
922
|
+
* Callback to recover from jpeg errors
|
|
923
|
+
*/
|
|
924
|
+
|
|
925
|
+
static void canvas_jpeg_error_exit(j_common_ptr cinfo) {
|
|
926
|
+
canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
|
|
927
|
+
cjerr->output_message(cinfo);
|
|
928
|
+
// Return control to the setjmp point
|
|
929
|
+
longjmp(cjerr->setjmp_buffer, 1);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Capture libjpeg errors instead of writing stdout
|
|
933
|
+
static void canvas_jpeg_output_message(j_common_ptr cinfo) {
|
|
934
|
+
canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
|
|
935
|
+
char buff[JMSG_LENGTH_MAX];
|
|
936
|
+
cjerr->format_message(cinfo, buff);
|
|
937
|
+
// (Only the last message will be returned to JS land.)
|
|
938
|
+
cjerr->image->errorInfo.set(buff);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/*
|
|
942
|
+
* Takes a jpeg data buffer and assigns it as mime data to a
|
|
943
|
+
* dummy surface
|
|
944
|
+
*/
|
|
945
|
+
|
|
946
|
+
cairo_status_t
|
|
947
|
+
Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
|
|
948
|
+
// TODO: remove this duplicate logic
|
|
949
|
+
// JPEG setup
|
|
950
|
+
struct jpeg_decompress_struct args;
|
|
951
|
+
struct canvas_jpeg_error_mgr err;
|
|
952
|
+
|
|
953
|
+
err.image = this;
|
|
954
|
+
args.err = jpeg_std_error(&err);
|
|
955
|
+
args.err->error_exit = canvas_jpeg_error_exit;
|
|
956
|
+
args.err->output_message = canvas_jpeg_output_message;
|
|
957
|
+
|
|
958
|
+
// Establish the setjmp return context for canvas_jpeg_error_exit to use
|
|
959
|
+
if (setjmp(err.setjmp_buffer)) {
|
|
960
|
+
// If we get here, the JPEG code has signaled an error.
|
|
961
|
+
// We need to clean up the JPEG object, close the input file, and return.
|
|
962
|
+
jpeg_destroy_decompress(&args);
|
|
963
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
jpeg_create_decompress(&args);
|
|
967
|
+
|
|
968
|
+
jpeg_mem_src(&args, buf, len);
|
|
969
|
+
|
|
970
|
+
jpeg_read_header(&args, 1);
|
|
971
|
+
jpeg_start_decompress(&args);
|
|
972
|
+
width = naturalWidth = args.output_width;
|
|
973
|
+
height = naturalHeight = args.output_height;
|
|
974
|
+
|
|
975
|
+
// Data alloc
|
|
976
|
+
// 8 pixels per byte using Alpha Channel format to reduce memory requirement.
|
|
977
|
+
int buf_size = naturalHeight * cairo_format_stride_for_width(CAIRO_FORMAT_A1, naturalWidth);
|
|
978
|
+
uint8_t *data = new uint8_t[buf_size];
|
|
979
|
+
if (!data) {
|
|
980
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
981
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// New image surface
|
|
985
|
+
_surface = cairo_image_surface_create_for_data(
|
|
986
|
+
data
|
|
987
|
+
, CAIRO_FORMAT_A1
|
|
988
|
+
, naturalWidth
|
|
989
|
+
, naturalHeight
|
|
990
|
+
, cairo_format_stride_for_width(CAIRO_FORMAT_A1, naturalWidth));
|
|
991
|
+
|
|
992
|
+
// Cleanup
|
|
993
|
+
jpeg_abort_decompress(&args);
|
|
994
|
+
jpeg_destroy_decompress(&args);
|
|
995
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
996
|
+
|
|
997
|
+
if (status) {
|
|
998
|
+
delete[] data;
|
|
999
|
+
return status;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
_data = data;
|
|
1003
|
+
|
|
1004
|
+
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/*
|
|
1008
|
+
* Helper function for disposing of a mime data closure.
|
|
1009
|
+
*/
|
|
1010
|
+
|
|
1011
|
+
void
|
|
1012
|
+
clearMimeData(void *closure) {
|
|
1013
|
+
Nan::AdjustExternalMemory(
|
|
1014
|
+
-static_cast<int>((static_cast<read_closure_t *>(closure)->len)));
|
|
1015
|
+
free(static_cast<read_closure_t *>(closure)->buf);
|
|
1016
|
+
free(closure);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/*
|
|
1020
|
+
* Assign a given buffer as mime data against the surface.
|
|
1021
|
+
* The provided buffer will be copied, and the copy will
|
|
1022
|
+
* be automatically freed when the surface is destroyed.
|
|
1023
|
+
*/
|
|
1024
|
+
|
|
1025
|
+
cairo_status_t
|
|
1026
|
+
Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) {
|
|
1027
|
+
uint8_t *mime_data = (uint8_t *) malloc(len);
|
|
1028
|
+
if (!mime_data) {
|
|
1029
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
1030
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
read_closure_t *mime_closure = (read_closure_t *) malloc(sizeof(read_closure_t));
|
|
1034
|
+
if (!mime_closure) {
|
|
1035
|
+
free(mime_data);
|
|
1036
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
1037
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
memcpy(mime_data, data, len);
|
|
1041
|
+
|
|
1042
|
+
mime_closure->buf = mime_data;
|
|
1043
|
+
mime_closure->len = len;
|
|
1044
|
+
|
|
1045
|
+
Nan::AdjustExternalMemory(len);
|
|
1046
|
+
|
|
1047
|
+
return cairo_surface_set_mime_data(_surface
|
|
1048
|
+
, mime_type
|
|
1049
|
+
, mime_data
|
|
1050
|
+
, len
|
|
1051
|
+
, clearMimeData
|
|
1052
|
+
, mime_closure);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/*
|
|
1056
|
+
* Load jpeg from buffer.
|
|
1057
|
+
*/
|
|
1058
|
+
|
|
1059
|
+
cairo_status_t
|
|
1060
|
+
Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
|
|
1061
|
+
// TODO: remove this duplicate logic
|
|
1062
|
+
// JPEG setup
|
|
1063
|
+
struct jpeg_decompress_struct args;
|
|
1064
|
+
struct canvas_jpeg_error_mgr err;
|
|
1065
|
+
|
|
1066
|
+
err.image = this;
|
|
1067
|
+
args.err = jpeg_std_error(&err);
|
|
1068
|
+
args.err->error_exit = canvas_jpeg_error_exit;
|
|
1069
|
+
args.err->output_message = canvas_jpeg_output_message;
|
|
1070
|
+
|
|
1071
|
+
// Establish the setjmp return context for canvas_jpeg_error_exit to use
|
|
1072
|
+
if (setjmp(err.setjmp_buffer)) {
|
|
1073
|
+
// If we get here, the JPEG code has signaled an error.
|
|
1074
|
+
// We need to clean up the JPEG object, close the input file, and return.
|
|
1075
|
+
jpeg_destroy_decompress(&args);
|
|
1076
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
jpeg_create_decompress(&args);
|
|
1080
|
+
|
|
1081
|
+
jpeg_mem_src(&args, buf, len);
|
|
1082
|
+
|
|
1083
|
+
jpeg_read_header(&args, 1);
|
|
1084
|
+
jpeg_start_decompress(&args);
|
|
1085
|
+
width = naturalWidth = args.output_width;
|
|
1086
|
+
height = naturalHeight = args.output_height;
|
|
1087
|
+
|
|
1088
|
+
return decodeJPEGIntoSurface(&args);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/*
|
|
1092
|
+
* Load JPEG, convert RGB to ARGB.
|
|
1093
|
+
*/
|
|
1094
|
+
|
|
1095
|
+
cairo_status_t
|
|
1096
|
+
Image::loadJPEG(FILE *stream) {
|
|
1097
|
+
cairo_status_t status;
|
|
1098
|
+
|
|
1099
|
+
#if defined(_MSC_VER)
|
|
1100
|
+
if (false) { // Force using loadJPEGFromBuffer
|
|
1101
|
+
#else
|
|
1102
|
+
if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG.
|
|
1103
|
+
#endif
|
|
1104
|
+
// JPEG setup
|
|
1105
|
+
struct jpeg_decompress_struct args;
|
|
1106
|
+
struct canvas_jpeg_error_mgr err;
|
|
1107
|
+
|
|
1108
|
+
err.image = this;
|
|
1109
|
+
args.err = jpeg_std_error(&err);
|
|
1110
|
+
args.err->error_exit = canvas_jpeg_error_exit;
|
|
1111
|
+
args.err->output_message = canvas_jpeg_output_message;
|
|
1112
|
+
|
|
1113
|
+
// Establish the setjmp return context for canvas_jpeg_error_exit to use
|
|
1114
|
+
if (setjmp(err.setjmp_buffer)) {
|
|
1115
|
+
// If we get here, the JPEG code has signaled an error.
|
|
1116
|
+
// We need to clean up the JPEG object, close the input file, and return.
|
|
1117
|
+
jpeg_destroy_decompress(&args);
|
|
1118
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
jpeg_create_decompress(&args);
|
|
1122
|
+
|
|
1123
|
+
jpeg_stdio_src(&args, stream);
|
|
1124
|
+
|
|
1125
|
+
jpeg_read_header(&args, 1);
|
|
1126
|
+
jpeg_start_decompress(&args);
|
|
1127
|
+
|
|
1128
|
+
if (args.output_width > canvas_max_side || args.output_height > canvas_max_side) {
|
|
1129
|
+
jpeg_destroy_decompress(&args);
|
|
1130
|
+
return CAIRO_STATUS_INVALID_SIZE;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
width = naturalWidth = args.output_width;
|
|
1134
|
+
height = naturalHeight = args.output_height;
|
|
1135
|
+
|
|
1136
|
+
status = decodeJPEGIntoSurface(&args);
|
|
1137
|
+
fclose(stream);
|
|
1138
|
+
} else { // We'll need the actual source jpeg data, so read fully.
|
|
1139
|
+
uint8_t *buf;
|
|
1140
|
+
unsigned len;
|
|
1141
|
+
|
|
1142
|
+
fseek(stream, 0, SEEK_END);
|
|
1143
|
+
len = ftell(stream);
|
|
1144
|
+
fseek(stream, 0, SEEK_SET);
|
|
1145
|
+
|
|
1146
|
+
buf = (uint8_t *) malloc(len);
|
|
1147
|
+
if (!buf) {
|
|
1148
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
1149
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (fread(buf, len, 1, stream) != 1) {
|
|
1153
|
+
status = CAIRO_STATUS_READ_ERROR;
|
|
1154
|
+
} else if ((DATA_IMAGE | DATA_MIME) == data_mode) {
|
|
1155
|
+
status = loadJPEGFromBuffer(buf, len);
|
|
1156
|
+
if (!status) status = assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
|
1157
|
+
} else if (DATA_MIME == data_mode) {
|
|
1158
|
+
status = decodeJPEGBufferIntoMimeSurface(buf, len);
|
|
1159
|
+
}
|
|
1160
|
+
#if defined(_MSC_VER)
|
|
1161
|
+
else if (DATA_IMAGE == data_mode) {
|
|
1162
|
+
status = loadJPEGFromBuffer(buf, len);
|
|
1163
|
+
}
|
|
1164
|
+
#endif
|
|
1165
|
+
else {
|
|
1166
|
+
status = CAIRO_STATUS_READ_ERROR;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
fclose(stream);
|
|
1170
|
+
free(buf);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
return status;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
#endif /* HAVE_JPEG */
|
|
1177
|
+
|
|
1178
|
+
#ifdef HAVE_RSVG
|
|
1179
|
+
|
|
1180
|
+
/*
|
|
1181
|
+
* Load SVG from buffer
|
|
1182
|
+
*/
|
|
1183
|
+
|
|
1184
|
+
cairo_status_t
|
|
1185
|
+
Image::loadSVGFromBuffer(uint8_t *buf, unsigned len) {
|
|
1186
|
+
_is_svg = true;
|
|
1187
|
+
|
|
1188
|
+
cairo_status_t status;
|
|
1189
|
+
GError *gerr = NULL;
|
|
1190
|
+
|
|
1191
|
+
if (NULL == (_rsvg = rsvg_handle_new_from_data(buf, len, &gerr))) {
|
|
1192
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
RsvgDimensionData *dims = new RsvgDimensionData();
|
|
1196
|
+
rsvg_handle_get_dimensions(_rsvg, dims);
|
|
1197
|
+
|
|
1198
|
+
width = naturalWidth = dims->width;
|
|
1199
|
+
height = naturalHeight = dims->height;
|
|
1200
|
+
|
|
1201
|
+
status = renderSVGToSurface();
|
|
1202
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
1203
|
+
g_object_unref(_rsvg);
|
|
1204
|
+
return status;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
return CAIRO_STATUS_SUCCESS;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/*
|
|
1211
|
+
* Renders the Rsvg handle to this image's surface
|
|
1212
|
+
*/
|
|
1213
|
+
cairo_status_t
|
|
1214
|
+
Image::renderSVGToSurface() {
|
|
1215
|
+
cairo_status_t status;
|
|
1216
|
+
|
|
1217
|
+
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
|
1218
|
+
|
|
1219
|
+
status = cairo_surface_status(_surface);
|
|
1220
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
1221
|
+
g_object_unref(_rsvg);
|
|
1222
|
+
return status;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
cairo_t *cr = cairo_create(_surface);
|
|
1226
|
+
cairo_scale(cr,
|
|
1227
|
+
(double)width / (double)naturalWidth,
|
|
1228
|
+
(double)height / (double)naturalHeight);
|
|
1229
|
+
status = cairo_status(cr);
|
|
1230
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
1231
|
+
g_object_unref(_rsvg);
|
|
1232
|
+
return status;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
gboolean render_ok = rsvg_handle_render_cairo(_rsvg, cr);
|
|
1236
|
+
if (!render_ok) {
|
|
1237
|
+
g_object_unref(_rsvg);
|
|
1238
|
+
cairo_destroy(cr);
|
|
1239
|
+
return CAIRO_STATUS_READ_ERROR; // or WRITE?
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
cairo_destroy(cr);
|
|
1243
|
+
|
|
1244
|
+
_svg_last_width = width;
|
|
1245
|
+
_svg_last_height = height;
|
|
1246
|
+
|
|
1247
|
+
return status;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/*
|
|
1251
|
+
* Load SVG
|
|
1252
|
+
*/
|
|
1253
|
+
|
|
1254
|
+
cairo_status_t
|
|
1255
|
+
Image::loadSVG(FILE *stream) {
|
|
1256
|
+
_is_svg = true;
|
|
1257
|
+
|
|
1258
|
+
struct stat s;
|
|
1259
|
+
int fd = fileno(stream);
|
|
1260
|
+
|
|
1261
|
+
// stat
|
|
1262
|
+
if (fstat(fd, &s) < 0) {
|
|
1263
|
+
fclose(stream);
|
|
1264
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
uint8_t *buf = (uint8_t *) malloc(s.st_size);
|
|
1268
|
+
|
|
1269
|
+
if (!buf) {
|
|
1270
|
+
fclose(stream);
|
|
1271
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
size_t read = fread(buf, s.st_size, 1, stream);
|
|
1275
|
+
fclose(stream);
|
|
1276
|
+
|
|
1277
|
+
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
|
1278
|
+
if (1 == read) result = loadSVGFromBuffer(buf, s.st_size);
|
|
1279
|
+
free(buf);
|
|
1280
|
+
|
|
1281
|
+
return result;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
#endif /* HAVE_RSVG */
|
|
1285
|
+
|
|
1286
|
+
/*
|
|
1287
|
+
* Load BMP from buffer.
|
|
1288
|
+
*/
|
|
1289
|
+
|
|
1290
|
+
cairo_status_t Image::loadBMPFromBuffer(uint8_t *buf, unsigned len){
|
|
1291
|
+
BMPParser::Parser parser;
|
|
1292
|
+
|
|
1293
|
+
// Reversed ARGB32 with pre-multiplied alpha
|
|
1294
|
+
uint8_t pixFmt[5] = {2, 1, 0, 3, 1};
|
|
1295
|
+
parser.parse(buf, len, pixFmt);
|
|
1296
|
+
|
|
1297
|
+
if (parser.getStatus() != BMPParser::Status::OK) {
|
|
1298
|
+
errorInfo.reset();
|
|
1299
|
+
errorInfo.message = parser.getErrMsg();
|
|
1300
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
width = naturalWidth = parser.getWidth();
|
|
1304
|
+
height = naturalHeight = parser.getHeight();
|
|
1305
|
+
uint8_t *data = parser.getImgd();
|
|
1306
|
+
|
|
1307
|
+
_surface = cairo_image_surface_create_for_data(
|
|
1308
|
+
data,
|
|
1309
|
+
CAIRO_FORMAT_ARGB32,
|
|
1310
|
+
width,
|
|
1311
|
+
height,
|
|
1312
|
+
cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)
|
|
1313
|
+
);
|
|
1314
|
+
|
|
1315
|
+
// No need to delete the data
|
|
1316
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
1317
|
+
if (status) return status;
|
|
1318
|
+
|
|
1319
|
+
_data = data;
|
|
1320
|
+
parser.clearImgd();
|
|
1321
|
+
|
|
1322
|
+
return CAIRO_STATUS_SUCCESS;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/*
|
|
1326
|
+
* Load BMP.
|
|
1327
|
+
*/
|
|
1328
|
+
|
|
1329
|
+
cairo_status_t Image::loadBMP(FILE *stream){
|
|
1330
|
+
struct stat s;
|
|
1331
|
+
int fd = fileno(stream);
|
|
1332
|
+
|
|
1333
|
+
// Stat
|
|
1334
|
+
if (fstat(fd, &s) < 0) {
|
|
1335
|
+
fclose(stream);
|
|
1336
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
uint8_t *buf = new uint8_t[s.st_size];
|
|
1340
|
+
|
|
1341
|
+
if (!buf) {
|
|
1342
|
+
fclose(stream);
|
|
1343
|
+
errorInfo.set(NULL, "malloc", errno);
|
|
1344
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
size_t read = fread(buf, s.st_size, 1, stream);
|
|
1348
|
+
fclose(stream);
|
|
1349
|
+
|
|
1350
|
+
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
|
1351
|
+
if (read == 1) result = loadBMPFromBuffer(buf, s.st_size);
|
|
1352
|
+
delete[] buf;
|
|
1353
|
+
|
|
1354
|
+
return result;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/*
|
|
1358
|
+
* Return UNKNOWN, SVG, GIF, JPEG, or PNG based on the filename.
|
|
1359
|
+
*/
|
|
1360
|
+
|
|
1361
|
+
Image::type
|
|
1362
|
+
Image::extension(const char *filename) {
|
|
1363
|
+
size_t len = strlen(filename);
|
|
1364
|
+
filename += len;
|
|
1365
|
+
if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return Image::JPEG;
|
|
1366
|
+
if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF;
|
|
1367
|
+
if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG;
|
|
1368
|
+
if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG;
|
|
1369
|
+
if (len >= 4 && 0 == strcmp(".svg", filename - 4)) return Image::SVG;
|
|
1370
|
+
return Image::UNKNOWN;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/*
|
|
1374
|
+
* Sniff bytes 0..1 for JPEG's magic number ff d8.
|
|
1375
|
+
*/
|
|
1376
|
+
|
|
1377
|
+
int
|
|
1378
|
+
Image::isJPEG(uint8_t *data) {
|
|
1379
|
+
return 0xff == data[0] && 0xd8 == data[1];
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/*
|
|
1383
|
+
* Sniff bytes 0..2 for "GIF".
|
|
1384
|
+
*/
|
|
1385
|
+
|
|
1386
|
+
int
|
|
1387
|
+
Image::isGIF(uint8_t *data) {
|
|
1388
|
+
return 'G' == data[0] && 'I' == data[1] && 'F' == data[2];
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/*
|
|
1392
|
+
* Sniff bytes 1..3 for "PNG".
|
|
1393
|
+
*/
|
|
1394
|
+
|
|
1395
|
+
int
|
|
1396
|
+
Image::isPNG(uint8_t *data) {
|
|
1397
|
+
return 'P' == data[1] && 'N' == data[2] && 'G' == data[3];
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/*
|
|
1401
|
+
* Skip "<?" and "<!" tags to test if root tag starts "<svg"
|
|
1402
|
+
*/
|
|
1403
|
+
int
|
|
1404
|
+
Image::isSVG(uint8_t *data, unsigned len) {
|
|
1405
|
+
for (unsigned i = 3; i < len; i++) {
|
|
1406
|
+
if ('<' == data[i-3]) {
|
|
1407
|
+
switch (data[i-2]) {
|
|
1408
|
+
case '?':
|
|
1409
|
+
case '!':
|
|
1410
|
+
break;
|
|
1411
|
+
case 's':
|
|
1412
|
+
return ('v' == data[i-1] && 'g' == data[i]);
|
|
1413
|
+
default:
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/*
|
|
1422
|
+
* Check for valid BMP signatures
|
|
1423
|
+
*/
|
|
1424
|
+
|
|
1425
|
+
int Image::isBMP(uint8_t *data, unsigned len) {
|
|
1426
|
+
if(len < 2) return false;
|
|
1427
|
+
std::string sig = std::string(1, (char)data[0]) + (char)data[1];
|
|
1428
|
+
return sig == "BM" ||
|
|
1429
|
+
sig == "BA" ||
|
|
1430
|
+
sig == "CI" ||
|
|
1431
|
+
sig == "CP" ||
|
|
1432
|
+
sig == "IC" ||
|
|
1433
|
+
sig == "PT";
|
|
1434
|
+
}
|