@shqld/canvas 3.2.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 +654 -0
- package/binding.gyp +229 -0
- package/browser.js +31 -0
- package/index.d.ts +507 -0
- package/index.js +94 -0
- package/lib/DOMMatrix.js +678 -0
- package/lib/bindings.js +113 -0
- package/lib/canvas.js +113 -0
- package/lib/context2d.js +11 -0
- package/lib/image.js +97 -0
- package/lib/jpegstream.js +41 -0
- package/lib/pattern.js +15 -0
- package/lib/pdfstream.js +35 -0
- package/lib/pngstream.js +42 -0
- package/package.json +77 -0
- package/scripts/install.js +19 -0
- package/src/Backends.h +9 -0
- package/src/Canvas.cc +1026 -0
- package/src/Canvas.h +128 -0
- package/src/CanvasError.h +37 -0
- package/src/CanvasGradient.cc +113 -0
- package/src/CanvasGradient.h +20 -0
- package/src/CanvasPattern.cc +129 -0
- package/src/CanvasPattern.h +33 -0
- package/src/CanvasRenderingContext2d.cc +3527 -0
- package/src/CanvasRenderingContext2d.h +238 -0
- package/src/CharData.h +233 -0
- package/src/FontParser.cc +605 -0
- package/src/FontParser.h +115 -0
- package/src/Image.cc +1719 -0
- package/src/Image.h +146 -0
- package/src/ImageData.cc +138 -0
- package/src/ImageData.h +26 -0
- package/src/InstanceData.h +12 -0
- package/src/JPEGStream.h +157 -0
- package/src/PNG.h +292 -0
- package/src/Point.h +11 -0
- package/src/Util.h +9 -0
- package/src/bmp/BMPParser.cc +459 -0
- package/src/bmp/BMPParser.h +60 -0
- package/src/bmp/LICENSE.md +24 -0
- package/src/closure.cc +52 -0
- package/src/closure.h +98 -0
- package/src/color.cc +796 -0
- package/src/color.h +30 -0
- package/src/dll_visibility.h +20 -0
- package/src/init.cc +114 -0
- package/src/register_font.cc +352 -0
- package/src/register_font.h +7 -0
- package/util/has_lib.js +119 -0
- package/util/win_jpeg_lookup.js +21 -0
package/src/Image.cc
ADDED
|
@@ -0,0 +1,1719 @@
|
|
|
1
|
+
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|
2
|
+
|
|
3
|
+
#include "Image.h"
|
|
4
|
+
#include "InstanceData.h"
|
|
5
|
+
|
|
6
|
+
#include "bmp/BMPParser.h"
|
|
7
|
+
#include "Canvas.h"
|
|
8
|
+
#include <cerrno>
|
|
9
|
+
#include <cstdlib>
|
|
10
|
+
#include <cstring>
|
|
11
|
+
#include <node_buffer.h>
|
|
12
|
+
#include <sys/stat.h>
|
|
13
|
+
|
|
14
|
+
/* Cairo limit:
|
|
15
|
+
* https://lists.cairographics.org/archives/cairo/2010-December/021422.html
|
|
16
|
+
*/
|
|
17
|
+
static constexpr int canvas_max_side = (1 << 15) - 1;
|
|
18
|
+
|
|
19
|
+
#ifdef HAVE_GIF
|
|
20
|
+
typedef struct {
|
|
21
|
+
uint8_t *buf;
|
|
22
|
+
unsigned len;
|
|
23
|
+
unsigned pos;
|
|
24
|
+
} gif_data_t;
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
#ifdef HAVE_JPEG
|
|
28
|
+
#include <csetjmp>
|
|
29
|
+
|
|
30
|
+
struct canvas_jpeg_error_mgr: jpeg_error_mgr {
|
|
31
|
+
Image* image;
|
|
32
|
+
jmp_buf setjmp_buffer;
|
|
33
|
+
};
|
|
34
|
+
#endif
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Read closure used by loadFromBuffer.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
typedef struct {
|
|
41
|
+
Napi::Env env;
|
|
42
|
+
unsigned len;
|
|
43
|
+
uint8_t *buf;
|
|
44
|
+
} read_closure_t;
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Initialize Image.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
void
|
|
51
|
+
Image::Initialize(Napi::Env& env, Napi::Object& exports) {
|
|
52
|
+
InstanceData *data = env.GetInstanceData<InstanceData>();
|
|
53
|
+
Napi::HandleScope scope(env);
|
|
54
|
+
|
|
55
|
+
Napi::Function ctor = DefineClass(env, "Image", {
|
|
56
|
+
InstanceAccessor<&Image::GetComplete>("complete", napi_default_jsproperty),
|
|
57
|
+
InstanceAccessor<&Image::GetWidth, &Image::SetWidth>("width", napi_default_jsproperty),
|
|
58
|
+
InstanceAccessor<&Image::GetHeight, &Image::SetHeight>("height", napi_default_jsproperty),
|
|
59
|
+
InstanceAccessor<&Image::GetNaturalWidth>("naturalWidth", napi_default_jsproperty),
|
|
60
|
+
InstanceAccessor<&Image::GetNaturalHeight>("naturalHeight", napi_default_jsproperty),
|
|
61
|
+
InstanceAccessor<&Image::GetDataMode, &Image::SetDataMode>("dataMode", napi_default_jsproperty),
|
|
62
|
+
StaticValue("MODE_IMAGE", Napi::Number::New(env, DATA_IMAGE), napi_default_jsproperty),
|
|
63
|
+
StaticValue("MODE_MIME", Napi::Number::New(env, DATA_MIME), napi_default_jsproperty)
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Used internally in lib/image.js
|
|
67
|
+
exports.Set("GetSource", Napi::Function::New(env, &GetSource));
|
|
68
|
+
exports.Set("SetSource", Napi::Function::New(env, &SetSource));
|
|
69
|
+
|
|
70
|
+
data->ImageCtor = Napi::Persistent(ctor);
|
|
71
|
+
exports.Set("Image", ctor);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Initialize a new Image.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
Image::Image(const Napi::CallbackInfo& info) : ObjectWrap<Image>(info), env(info.Env()) {
|
|
79
|
+
data_mode = DATA_IMAGE;
|
|
80
|
+
info.This().ToObject().Unwrap().Set("onload", env.Null());
|
|
81
|
+
info.This().ToObject().Unwrap().Set("onerror", env.Null());
|
|
82
|
+
filename = NULL;
|
|
83
|
+
_data = nullptr;
|
|
84
|
+
_data_len = 0;
|
|
85
|
+
_surface = NULL;
|
|
86
|
+
width = height = 0;
|
|
87
|
+
naturalWidth = naturalHeight = 0;
|
|
88
|
+
state = DEFAULT;
|
|
89
|
+
#ifdef HAVE_RSVG
|
|
90
|
+
_rsvg = NULL;
|
|
91
|
+
_is_svg = false;
|
|
92
|
+
_svg_last_width = _svg_last_height = 0;
|
|
93
|
+
#endif
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* Get complete boolean.
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
Napi::Value
|
|
101
|
+
Image::GetComplete(const Napi::CallbackInfo& info) {
|
|
102
|
+
return Napi::Boolean::New(env, true);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Get dataMode.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
Napi::Value
|
|
110
|
+
Image::GetDataMode(const Napi::CallbackInfo& info) {
|
|
111
|
+
return Napi::Number::New(env, data_mode);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* Set dataMode.
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
void
|
|
119
|
+
Image::SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
|
120
|
+
if (value.IsNumber()) {
|
|
121
|
+
int mode = value.As<Napi::Number>().Uint32Value();
|
|
122
|
+
data_mode = (data_mode_t) mode;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* Get natural width
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
Napi::Value
|
|
131
|
+
Image::GetNaturalWidth(const Napi::CallbackInfo& info) {
|
|
132
|
+
return Napi::Number::New(env, naturalWidth);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
* Get width.
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
Napi::Value
|
|
140
|
+
Image::GetWidth(const Napi::CallbackInfo& info) {
|
|
141
|
+
return Napi::Number::New(env, width);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* Set width.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
void
|
|
149
|
+
Image::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
|
150
|
+
if (value.IsNumber()) {
|
|
151
|
+
width = value.As<Napi::Number>().Uint32Value();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* Get natural height
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
Napi::Value
|
|
160
|
+
Image::GetNaturalHeight(const Napi::CallbackInfo& info) {
|
|
161
|
+
return Napi::Number::New(env, naturalHeight);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/*
|
|
165
|
+
* Get height.
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
Napi::Value
|
|
169
|
+
Image::GetHeight(const Napi::CallbackInfo& info) {
|
|
170
|
+
return Napi::Number::New(env, height);
|
|
171
|
+
}
|
|
172
|
+
/*
|
|
173
|
+
* Set height.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
void
|
|
177
|
+
Image::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
|
178
|
+
if (value.IsNumber()) {
|
|
179
|
+
height = value.As<Napi::Number>().Uint32Value();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* Get src path.
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
Napi::Value
|
|
188
|
+
Image::GetSource(const Napi::CallbackInfo& info){
|
|
189
|
+
Napi::Env env = info.Env();
|
|
190
|
+
Image *img = Image::Unwrap(info.This().As<Napi::Object>());
|
|
191
|
+
return Napi::String::New(env, img->filename ? img->filename : "");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
* Clean up assets and variables.
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
void
|
|
199
|
+
Image::clearData() {
|
|
200
|
+
if (_surface) {
|
|
201
|
+
cairo_surface_destroy(_surface);
|
|
202
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, -_data_len);
|
|
203
|
+
_data_len = 0;
|
|
204
|
+
_surface = NULL;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
delete[] _data;
|
|
208
|
+
_data = nullptr;
|
|
209
|
+
|
|
210
|
+
free(filename);
|
|
211
|
+
filename = NULL;
|
|
212
|
+
|
|
213
|
+
#ifdef HAVE_RSVG
|
|
214
|
+
if (_rsvg != NULL) {
|
|
215
|
+
g_object_unref(_rsvg);
|
|
216
|
+
_rsvg = NULL;
|
|
217
|
+
}
|
|
218
|
+
#endif
|
|
219
|
+
|
|
220
|
+
width = height = 0;
|
|
221
|
+
naturalWidth = naturalHeight = 0;
|
|
222
|
+
state = DEFAULT;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/*
|
|
226
|
+
* Set src path.
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
void
|
|
230
|
+
Image::SetSource(const Napi::CallbackInfo& info){
|
|
231
|
+
Napi::Env env = info.Env();
|
|
232
|
+
Napi::Object This = info.This().As<Napi::Object>();
|
|
233
|
+
Image *img = Image::Unwrap(This);
|
|
234
|
+
|
|
235
|
+
cairo_status_t status = CAIRO_STATUS_READ_ERROR;
|
|
236
|
+
|
|
237
|
+
Napi::Value value = info[0];
|
|
238
|
+
|
|
239
|
+
img->clearData();
|
|
240
|
+
// Clear errno in case some unrelated previous syscall failed
|
|
241
|
+
errno = 0;
|
|
242
|
+
|
|
243
|
+
// url string
|
|
244
|
+
if (value.IsString()) {
|
|
245
|
+
std::string src = value.As<Napi::String>().Utf8Value();
|
|
246
|
+
if (img->filename) free(img->filename);
|
|
247
|
+
img->filename = strdup(src.c_str());
|
|
248
|
+
status = img->load();
|
|
249
|
+
// Buffer
|
|
250
|
+
} else if (value.IsBuffer()) {
|
|
251
|
+
uint8_t *buf = value.As<Napi::Buffer<uint8_t>>().Data();
|
|
252
|
+
unsigned len = value.As<Napi::Buffer<uint8_t>>().Length();
|
|
253
|
+
status = img->loadFromBuffer(buf, len);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (status) {
|
|
257
|
+
Napi::Value onerrorFn;
|
|
258
|
+
if (This.Get("onerror").UnwrapTo(&onerrorFn) && onerrorFn.IsFunction()) {
|
|
259
|
+
Napi::Error arg;
|
|
260
|
+
if (img->errorInfo.empty()) {
|
|
261
|
+
arg = Napi::Error::New(env, Napi::String::New(env, cairo_status_to_string(status)));
|
|
262
|
+
} else {
|
|
263
|
+
arg = img->errorInfo.toError(env);
|
|
264
|
+
}
|
|
265
|
+
onerrorFn.As<Napi::Function>().Call({ arg.Value() });
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
img->loaded();
|
|
269
|
+
Napi::Value onloadFn;
|
|
270
|
+
if (This.Get("onload").UnwrapTo(&onloadFn) && onloadFn.IsFunction()) {
|
|
271
|
+
onloadFn.As<Napi::Function>().Call({});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/*
|
|
277
|
+
* Load image data from `buf` by sniffing
|
|
278
|
+
* the bytes to determine format.
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
cairo_status_t
|
|
282
|
+
Image::loadFromBuffer(uint8_t *buf, unsigned len) {
|
|
283
|
+
if (len == 0) return CAIRO_STATUS_READ_ERROR;
|
|
284
|
+
|
|
285
|
+
uint8_t data[4] = {0};
|
|
286
|
+
memcpy(data, buf, (len < 4 ? len : 4) * sizeof(uint8_t));
|
|
287
|
+
|
|
288
|
+
if (isPNG(data)) return loadPNGFromBuffer(buf);
|
|
289
|
+
|
|
290
|
+
if (isGIF(data)) {
|
|
291
|
+
#ifdef HAVE_GIF
|
|
292
|
+
return loadGIFFromBuffer(buf, len);
|
|
293
|
+
#else
|
|
294
|
+
this->errorInfo.set("node-canvas was built without GIF support");
|
|
295
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
296
|
+
#endif
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (isJPEG(data)) {
|
|
300
|
+
#ifdef HAVE_JPEG
|
|
301
|
+
if (DATA_IMAGE == data_mode) return loadJPEGFromBuffer(buf, len);
|
|
302
|
+
if (DATA_MIME == data_mode) return decodeJPEGBufferIntoMimeSurface(buf, len);
|
|
303
|
+
if ((DATA_IMAGE | DATA_MIME) == data_mode) {
|
|
304
|
+
cairo_status_t status;
|
|
305
|
+
status = loadJPEGFromBuffer(buf, len);
|
|
306
|
+
if (status) return status;
|
|
307
|
+
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
|
308
|
+
}
|
|
309
|
+
#else // HAVE_JPEG
|
|
310
|
+
this->errorInfo.set("node-canvas was built without JPEG support");
|
|
311
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
312
|
+
#endif
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// confirm svg using first 1000 chars
|
|
316
|
+
// if a very long comment precedes the root <svg> tag, isSVG returns false
|
|
317
|
+
unsigned head_len = (len < 1000 ? len : 1000);
|
|
318
|
+
if (isSVG(buf, head_len)) {
|
|
319
|
+
#ifdef HAVE_RSVG
|
|
320
|
+
return loadSVGFromBuffer(buf, len);
|
|
321
|
+
#else
|
|
322
|
+
this->errorInfo.set("node-canvas was built without SVG support");
|
|
323
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
324
|
+
#endif
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (isBMP(buf, len))
|
|
328
|
+
return loadBMPFromBuffer(buf, len);
|
|
329
|
+
|
|
330
|
+
this->errorInfo.set("Unsupported image type");
|
|
331
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
* Load PNG data from `buf`.
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
cairo_status_t
|
|
339
|
+
Image::loadPNGFromBuffer(uint8_t *buf) {
|
|
340
|
+
read_closure_t closure{ env, 0, buf };
|
|
341
|
+
_surface = cairo_image_surface_create_from_png_stream(readPNG, &closure);
|
|
342
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
343
|
+
if (status) return status;
|
|
344
|
+
return CAIRO_STATUS_SUCCESS;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/*
|
|
348
|
+
* Read PNG data.
|
|
349
|
+
*/
|
|
350
|
+
|
|
351
|
+
cairo_status_t
|
|
352
|
+
Image::readPNG(void *c, uint8_t *data, unsigned int len) {
|
|
353
|
+
read_closure_t *closure = (read_closure_t *) c;
|
|
354
|
+
memcpy(data, closure->buf + closure->len, len);
|
|
355
|
+
closure->len += len;
|
|
356
|
+
return CAIRO_STATUS_SUCCESS;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/*
|
|
360
|
+
* Destroy image and associated surface.
|
|
361
|
+
*/
|
|
362
|
+
|
|
363
|
+
Image::~Image() {
|
|
364
|
+
clearData();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/*
|
|
368
|
+
* Initiate image loading.
|
|
369
|
+
*/
|
|
370
|
+
|
|
371
|
+
cairo_status_t
|
|
372
|
+
Image::load() {
|
|
373
|
+
if (LOADING != state) {
|
|
374
|
+
state = LOADING;
|
|
375
|
+
return loadSurface();
|
|
376
|
+
}
|
|
377
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/*
|
|
381
|
+
* Set state, assign dimensions.
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
void
|
|
385
|
+
Image::loaded() {
|
|
386
|
+
Napi::HandleScope scope(env);
|
|
387
|
+
state = COMPLETE;
|
|
388
|
+
|
|
389
|
+
width = naturalWidth = cairo_image_surface_get_width(_surface);
|
|
390
|
+
height = naturalHeight = cairo_image_surface_get_height(_surface);
|
|
391
|
+
_data_len = naturalHeight * cairo_image_surface_get_stride(_surface);
|
|
392
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, _data_len);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/*
|
|
396
|
+
* Returns this image's surface.
|
|
397
|
+
*/
|
|
398
|
+
cairo_surface_t *Image::surface() {
|
|
399
|
+
#ifdef HAVE_RSVG
|
|
400
|
+
if (_is_svg && (_svg_last_width != width || _svg_last_height != height)) {
|
|
401
|
+
if (_surface != NULL) {
|
|
402
|
+
cairo_surface_destroy(_surface);
|
|
403
|
+
_surface = NULL;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
cairo_status_t status = renderSVGToSurface();
|
|
407
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
408
|
+
g_object_unref(_rsvg);
|
|
409
|
+
Napi::Error::New(env, cairo_status_to_string(status)).ThrowAsJavaScriptException();
|
|
410
|
+
|
|
411
|
+
return NULL;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
#endif
|
|
415
|
+
return _surface;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/*
|
|
419
|
+
* Load cairo surface from the image src.
|
|
420
|
+
*
|
|
421
|
+
* TODO: support more formats
|
|
422
|
+
* TODO: use node IO or at least thread pool
|
|
423
|
+
*/
|
|
424
|
+
|
|
425
|
+
cairo_status_t
|
|
426
|
+
Image::loadSurface() {
|
|
427
|
+
FILE *stream = fopen(filename, "rb");
|
|
428
|
+
if (!stream) {
|
|
429
|
+
this->errorInfo.set(NULL, "fopen", errno, filename);
|
|
430
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
431
|
+
}
|
|
432
|
+
uint8_t buf[5];
|
|
433
|
+
if (1 != fread(&buf, 5, 1, stream)) {
|
|
434
|
+
fclose(stream);
|
|
435
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
436
|
+
}
|
|
437
|
+
rewind(stream);
|
|
438
|
+
|
|
439
|
+
// png
|
|
440
|
+
if (isPNG(buf)) {
|
|
441
|
+
fclose(stream);
|
|
442
|
+
return loadPNG();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
if (isGIF(buf)) {
|
|
447
|
+
#ifdef HAVE_GIF
|
|
448
|
+
return loadGIF(stream);
|
|
449
|
+
#else
|
|
450
|
+
this->errorInfo.set("node-canvas was built without GIF support");
|
|
451
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
452
|
+
#endif
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (isJPEG(buf)) {
|
|
456
|
+
#ifdef HAVE_JPEG
|
|
457
|
+
return loadJPEG(stream);
|
|
458
|
+
#else
|
|
459
|
+
this->errorInfo.set("node-canvas was built without JPEG support");
|
|
460
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
461
|
+
#endif
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// confirm svg using first 1000 chars
|
|
465
|
+
// if a very long comment precedes the root <svg> tag, isSVG returns false
|
|
466
|
+
uint8_t head[1000] = {0};
|
|
467
|
+
fseek(stream, 0 , SEEK_END);
|
|
468
|
+
long len = ftell(stream);
|
|
469
|
+
unsigned head_len = (len < 1000 ? len : 1000);
|
|
470
|
+
unsigned head_size = head_len * sizeof(uint8_t);
|
|
471
|
+
rewind(stream);
|
|
472
|
+
if (head_size != fread(&head, 1, head_size, stream)) {
|
|
473
|
+
fclose(stream);
|
|
474
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
475
|
+
}
|
|
476
|
+
rewind(stream);
|
|
477
|
+
if (isSVG(head, head_len)) {
|
|
478
|
+
#ifdef HAVE_RSVG
|
|
479
|
+
return loadSVG(stream);
|
|
480
|
+
#else
|
|
481
|
+
this->errorInfo.set("node-canvas was built without SVG support");
|
|
482
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
483
|
+
#endif
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (isBMP(buf, 2))
|
|
487
|
+
return loadBMP(stream);
|
|
488
|
+
|
|
489
|
+
fclose(stream);
|
|
490
|
+
|
|
491
|
+
this->errorInfo.set("Unsupported image type");
|
|
492
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/*
|
|
496
|
+
* Load PNG.
|
|
497
|
+
*/
|
|
498
|
+
|
|
499
|
+
cairo_status_t
|
|
500
|
+
Image::loadPNG() {
|
|
501
|
+
_surface = cairo_image_surface_create_from_png(filename);
|
|
502
|
+
return cairo_surface_status(_surface);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// GIF support
|
|
506
|
+
|
|
507
|
+
#ifdef HAVE_GIF
|
|
508
|
+
|
|
509
|
+
/*
|
|
510
|
+
* Return the alpha color for `gif` at `frame`, or -1.
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
int
|
|
514
|
+
get_gif_transparent_color(GifFileType *gif, int frame) {
|
|
515
|
+
ExtensionBlock *ext = gif->SavedImages[frame].ExtensionBlocks;
|
|
516
|
+
int len = gif->SavedImages[frame].ExtensionBlockCount;
|
|
517
|
+
for (int x = 0; x < len; ++x, ++ext) {
|
|
518
|
+
if ((ext->Function == GRAPHICS_EXT_FUNC_CODE) && (ext->Bytes[0] & 1)) {
|
|
519
|
+
return ext->Bytes[3] == 0 ? 0 : (uint8_t) ext->Bytes[3];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return -1;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/*
|
|
526
|
+
* Memory GIF reader callback.
|
|
527
|
+
*/
|
|
528
|
+
|
|
529
|
+
int
|
|
530
|
+
read_gif_from_memory(GifFileType *gif, GifByteType *buf, int len) {
|
|
531
|
+
gif_data_t *data = (gif_data_t *) gif->UserData;
|
|
532
|
+
if ((data->pos + len) > data->len) len = data->len - data->pos;
|
|
533
|
+
memcpy(buf, data->pos + data->buf, len);
|
|
534
|
+
data->pos += len;
|
|
535
|
+
return len;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/*
|
|
539
|
+
* Load GIF.
|
|
540
|
+
*/
|
|
541
|
+
|
|
542
|
+
cairo_status_t
|
|
543
|
+
Image::loadGIF(FILE *stream) {
|
|
544
|
+
struct stat s;
|
|
545
|
+
int fd = fileno(stream);
|
|
546
|
+
|
|
547
|
+
// stat
|
|
548
|
+
if (fstat(fd, &s) < 0) {
|
|
549
|
+
fclose(stream);
|
|
550
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
uint8_t *buf = (uint8_t *) malloc(s.st_size);
|
|
554
|
+
|
|
555
|
+
if (!buf) {
|
|
556
|
+
fclose(stream);
|
|
557
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
558
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
size_t read = fread(buf, s.st_size, 1, stream);
|
|
562
|
+
fclose(stream);
|
|
563
|
+
|
|
564
|
+
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
|
565
|
+
if (1 == read) result = loadGIFFromBuffer(buf, s.st_size);
|
|
566
|
+
free(buf);
|
|
567
|
+
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/*
|
|
572
|
+
* Load give from `buf` and the given `len`.
|
|
573
|
+
*/
|
|
574
|
+
|
|
575
|
+
cairo_status_t
|
|
576
|
+
Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) {
|
|
577
|
+
int i = 0;
|
|
578
|
+
GifFileType* gif;
|
|
579
|
+
|
|
580
|
+
gif_data_t gifd = { buf, len, 0 };
|
|
581
|
+
|
|
582
|
+
#if GIFLIB_MAJOR >= 5
|
|
583
|
+
int errorcode;
|
|
584
|
+
if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory, &errorcode)) == NULL)
|
|
585
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
586
|
+
#else
|
|
587
|
+
if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory)) == NULL)
|
|
588
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
589
|
+
#endif
|
|
590
|
+
|
|
591
|
+
if (GIF_OK != DGifSlurp(gif)) {
|
|
592
|
+
GIF_CLOSE_FILE(gif);
|
|
593
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (gif->SWidth > canvas_max_side || gif->SHeight > canvas_max_side) {
|
|
597
|
+
GIF_CLOSE_FILE(gif);
|
|
598
|
+
return CAIRO_STATUS_INVALID_SIZE;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
width = naturalWidth = gif->SWidth;
|
|
602
|
+
height = naturalHeight = gif->SHeight;
|
|
603
|
+
|
|
604
|
+
uint8_t *data = new uint8_t[naturalWidth * naturalHeight * 4];
|
|
605
|
+
if (!data) {
|
|
606
|
+
GIF_CLOSE_FILE(gif);
|
|
607
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
608
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
GifImageDesc *img = &gif->SavedImages[i].ImageDesc;
|
|
612
|
+
|
|
613
|
+
// local colormap takes precedence over global
|
|
614
|
+
ColorMapObject *colormap = img->ColorMap
|
|
615
|
+
? img->ColorMap
|
|
616
|
+
: gif->SColorMap;
|
|
617
|
+
|
|
618
|
+
if (colormap == nullptr) {
|
|
619
|
+
GIF_CLOSE_FILE(gif);
|
|
620
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
int bgColor = 0;
|
|
624
|
+
int alphaColor = get_gif_transparent_color(gif, i);
|
|
625
|
+
if (gif->SColorMap) bgColor = (uint8_t) gif->SBackGroundColor;
|
|
626
|
+
else if(alphaColor >= 0) bgColor = alphaColor;
|
|
627
|
+
|
|
628
|
+
uint8_t *src_data = (uint8_t*) gif->SavedImages[i].RasterBits;
|
|
629
|
+
uint32_t *dst_data = (uint32_t*) data;
|
|
630
|
+
|
|
631
|
+
if (!gif->Image.Interlace) {
|
|
632
|
+
if (naturalWidth == img->Width && naturalHeight == img->Height) {
|
|
633
|
+
for (int y = 0; y < naturalHeight; ++y) {
|
|
634
|
+
for (int x = 0; x < naturalWidth; ++x) {
|
|
635
|
+
*dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
|
|
636
|
+
| colormap->Colors[*src_data].Red << 16
|
|
637
|
+
| colormap->Colors[*src_data].Green << 8
|
|
638
|
+
| colormap->Colors[*src_data].Blue;
|
|
639
|
+
|
|
640
|
+
dst_data++;
|
|
641
|
+
src_data++;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
// Image does not take up whole "screen" so we need to fill-in the background
|
|
646
|
+
int bottom = img->Top + img->Height;
|
|
647
|
+
int right = img->Left + img->Width;
|
|
648
|
+
|
|
649
|
+
uint32_t bgPixel =
|
|
650
|
+
((bgColor == alphaColor) ? 0 : 255) << 24
|
|
651
|
+
| colormap->Colors[bgColor].Red << 16
|
|
652
|
+
| colormap->Colors[bgColor].Green << 8
|
|
653
|
+
| colormap->Colors[bgColor].Blue;
|
|
654
|
+
|
|
655
|
+
for (int y = 0; y < naturalHeight; ++y) {
|
|
656
|
+
for (int x = 0; x < naturalWidth; ++x) {
|
|
657
|
+
if (y < img->Top || y >= bottom || x < img->Left || x >= right) {
|
|
658
|
+
*dst_data = bgPixel;
|
|
659
|
+
dst_data++;
|
|
660
|
+
} else {
|
|
661
|
+
*dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
|
|
662
|
+
| colormap->Colors[*src_data].Red << 16
|
|
663
|
+
| colormap->Colors[*src_data].Green << 8
|
|
664
|
+
| colormap->Colors[*src_data].Blue;
|
|
665
|
+
dst_data++;
|
|
666
|
+
src_data++;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
// Image is interlaced so that it streams nice over 14.4k and 28.8k modems :)
|
|
673
|
+
// We first load in 1/8 of the image, followed by another 1/8, followed by
|
|
674
|
+
// 1/4 and finally the remaining 1/2.
|
|
675
|
+
int ioffs[] = { 0, 4, 2, 1 };
|
|
676
|
+
int ijumps[] = { 8, 8, 4, 2 };
|
|
677
|
+
|
|
678
|
+
uint8_t *src_ptr = src_data;
|
|
679
|
+
uint32_t *dst_ptr;
|
|
680
|
+
|
|
681
|
+
for(int z = 0; z < 4; z++) {
|
|
682
|
+
for(int y = ioffs[z]; y < naturalHeight; y += ijumps[z]) {
|
|
683
|
+
dst_ptr = dst_data + naturalWidth * y;
|
|
684
|
+
for(int x = 0; x < naturalWidth; ++x) {
|
|
685
|
+
*dst_ptr = ((*src_ptr == alphaColor) ? 0 : 255) << 24
|
|
686
|
+
| (colormap->Colors[*src_ptr].Red) << 16
|
|
687
|
+
| (colormap->Colors[*src_ptr].Green) << 8
|
|
688
|
+
| (colormap->Colors[*src_ptr].Blue);
|
|
689
|
+
|
|
690
|
+
dst_ptr++;
|
|
691
|
+
src_ptr++;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
GIF_CLOSE_FILE(gif);
|
|
698
|
+
|
|
699
|
+
// New image surface
|
|
700
|
+
_surface = cairo_image_surface_create_for_data(
|
|
701
|
+
data
|
|
702
|
+
, CAIRO_FORMAT_ARGB32
|
|
703
|
+
, naturalWidth
|
|
704
|
+
, naturalHeight
|
|
705
|
+
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
|
|
706
|
+
|
|
707
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
708
|
+
|
|
709
|
+
if (status) {
|
|
710
|
+
delete[] data;
|
|
711
|
+
return status;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
_data = data;
|
|
715
|
+
|
|
716
|
+
return CAIRO_STATUS_SUCCESS;
|
|
717
|
+
}
|
|
718
|
+
#endif /* HAVE_GIF */
|
|
719
|
+
|
|
720
|
+
// JPEG support
|
|
721
|
+
|
|
722
|
+
#ifdef HAVE_JPEG
|
|
723
|
+
|
|
724
|
+
// libjpeg 6.2 does not have jpeg_mem_src; define it ourselves here unless
|
|
725
|
+
// libjpeg 8 is installed.
|
|
726
|
+
#if JPEG_LIB_VERSION < 80 && !defined(MEM_SRCDST_SUPPORTED)
|
|
727
|
+
|
|
728
|
+
/* Read JPEG image from a memory segment */
|
|
729
|
+
static void
|
|
730
|
+
init_source(j_decompress_ptr cinfo) {}
|
|
731
|
+
|
|
732
|
+
static boolean
|
|
733
|
+
fill_input_buffer(j_decompress_ptr cinfo) {
|
|
734
|
+
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
|
735
|
+
return TRUE;
|
|
736
|
+
}
|
|
737
|
+
static void
|
|
738
|
+
skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
|
|
739
|
+
struct jpeg_source_mgr* src = (struct jpeg_source_mgr*) cinfo->src;
|
|
740
|
+
if (num_bytes > 0) {
|
|
741
|
+
src->next_input_byte += (size_t) num_bytes;
|
|
742
|
+
src->bytes_in_buffer -= (size_t) num_bytes;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
static void term_source (j_decompress_ptr cinfo) {}
|
|
747
|
+
static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) {
|
|
748
|
+
struct jpeg_source_mgr* src;
|
|
749
|
+
|
|
750
|
+
if (cinfo->src == NULL) {
|
|
751
|
+
cinfo->src = (struct jpeg_source_mgr *)
|
|
752
|
+
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
|
753
|
+
sizeof(struct jpeg_source_mgr));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
src = (struct jpeg_source_mgr*) cinfo->src;
|
|
757
|
+
src->init_source = init_source;
|
|
758
|
+
src->fill_input_buffer = fill_input_buffer;
|
|
759
|
+
src->skip_input_data = skip_input_data;
|
|
760
|
+
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
|
|
761
|
+
src->term_source = term_source;
|
|
762
|
+
src->bytes_in_buffer = nbytes;
|
|
763
|
+
src->next_input_byte = (JOCTET*)buffer;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
#endif
|
|
767
|
+
|
|
768
|
+
class BufferReader : public Image::Reader {
|
|
769
|
+
public:
|
|
770
|
+
BufferReader(uint8_t* buf, unsigned len) : _buf(buf), _len(len), _idx(0) {}
|
|
771
|
+
|
|
772
|
+
bool hasBytes(unsigned n) const override { return (_idx + n - 1 < _len); }
|
|
773
|
+
|
|
774
|
+
uint8_t getNext() override {
|
|
775
|
+
return _buf[_idx++];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
void skipBytes(unsigned n) override { _idx += n; }
|
|
779
|
+
|
|
780
|
+
private:
|
|
781
|
+
uint8_t* _buf; // we do not own this
|
|
782
|
+
unsigned _len;
|
|
783
|
+
unsigned _idx;
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
class StreamReader : public Image::Reader {
|
|
787
|
+
public:
|
|
788
|
+
StreamReader(FILE *stream) : _stream(stream), _len(0), _idx(0) {
|
|
789
|
+
fseek(_stream, 0, SEEK_END);
|
|
790
|
+
_len = ftell(_stream);
|
|
791
|
+
fseek(_stream, 0, SEEK_SET);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
bool hasBytes(unsigned n) const override { return (_idx + n - 1 < _len); }
|
|
795
|
+
|
|
796
|
+
uint8_t getNext() override {
|
|
797
|
+
++_idx;
|
|
798
|
+
return getc(_stream);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
void skipBytes(unsigned n) override {
|
|
802
|
+
_idx += n;
|
|
803
|
+
fseek(_stream, _idx, SEEK_SET);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
private:
|
|
807
|
+
FILE* _stream;
|
|
808
|
+
unsigned _len;
|
|
809
|
+
unsigned _idx;
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
void Image::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode) {
|
|
813
|
+
int stride = naturalWidth * 4;
|
|
814
|
+
for (int y = 0; y < naturalHeight; ++y) {
|
|
815
|
+
jpeg_read_scanlines(args, &src, 1);
|
|
816
|
+
uint32_t *row = (uint32_t*)(data + stride * y);
|
|
817
|
+
for (int x = 0; x < naturalWidth; ++x) {
|
|
818
|
+
int bx = args->output_components * x;
|
|
819
|
+
row[x] = decode(src + bx);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/*
|
|
825
|
+
* Takes an initialised jpeg_decompress_struct and decodes the
|
|
826
|
+
* data into _surface.
|
|
827
|
+
*/
|
|
828
|
+
|
|
829
|
+
cairo_status_t
|
|
830
|
+
Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientation) {
|
|
831
|
+
const int channels = 4;
|
|
832
|
+
cairo_status_t status = CAIRO_STATUS_SUCCESS;
|
|
833
|
+
|
|
834
|
+
uint8_t *data = new uint8_t[naturalWidth * naturalHeight * channels];
|
|
835
|
+
if (!data) {
|
|
836
|
+
jpeg_abort_decompress(args);
|
|
837
|
+
jpeg_destroy_decompress(args);
|
|
838
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
839
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
uint8_t *src = new uint8_t[naturalWidth * args->output_components];
|
|
843
|
+
if (!src) {
|
|
844
|
+
free(data);
|
|
845
|
+
jpeg_abort_decompress(args);
|
|
846
|
+
jpeg_destroy_decompress(args);
|
|
847
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
848
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// These are the three main cases to handle. libjpeg converts YCCK to CMYK
|
|
852
|
+
// and YCbCr to RGB by default.
|
|
853
|
+
switch (args->out_color_space) {
|
|
854
|
+
case JCS_CMYK:
|
|
855
|
+
jpegToARGB(args, data, src, [](uint8_t const* src) {
|
|
856
|
+
uint16_t k = static_cast<uint16_t>(src[3]);
|
|
857
|
+
uint8_t r = k * src[0] / 255;
|
|
858
|
+
uint8_t g = k * src[1] / 255;
|
|
859
|
+
uint8_t b = k * src[2] / 255;
|
|
860
|
+
return 255 << 24 | r << 16 | g << 8 | b;
|
|
861
|
+
});
|
|
862
|
+
break;
|
|
863
|
+
case JCS_RGB:
|
|
864
|
+
jpegToARGB(args, data, src, [](uint8_t const* src) {
|
|
865
|
+
uint8_t r = src[0], g = src[1], b = src[2];
|
|
866
|
+
return 255 << 24 | r << 16 | g << 8 | b;
|
|
867
|
+
});
|
|
868
|
+
break;
|
|
869
|
+
case JCS_GRAYSCALE:
|
|
870
|
+
jpegToARGB(args, data, src, [](uint8_t const* src) {
|
|
871
|
+
uint8_t v = src[0];
|
|
872
|
+
return 255 << 24 | v << 16 | v << 8 | v;
|
|
873
|
+
});
|
|
874
|
+
break;
|
|
875
|
+
default:
|
|
876
|
+
this->errorInfo.set("Unsupported JPEG encoding");
|
|
877
|
+
status = CAIRO_STATUS_READ_ERROR;
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
updateDimensionsForOrientation(orientation);
|
|
882
|
+
|
|
883
|
+
if (!status) {
|
|
884
|
+
_surface = cairo_image_surface_create_for_data(
|
|
885
|
+
data
|
|
886
|
+
, CAIRO_FORMAT_ARGB32
|
|
887
|
+
, naturalWidth
|
|
888
|
+
, naturalHeight
|
|
889
|
+
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
jpeg_finish_decompress(args);
|
|
893
|
+
jpeg_destroy_decompress(args);
|
|
894
|
+
status = cairo_surface_status(_surface);
|
|
895
|
+
|
|
896
|
+
rotatePixels(data, naturalWidth, naturalHeight, channels, orientation);
|
|
897
|
+
|
|
898
|
+
delete[] src;
|
|
899
|
+
|
|
900
|
+
if (status) {
|
|
901
|
+
delete[] data;
|
|
902
|
+
return status;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
_data = data;
|
|
906
|
+
|
|
907
|
+
return CAIRO_STATUS_SUCCESS;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/*
|
|
911
|
+
* Callback to recover from jpeg errors
|
|
912
|
+
*/
|
|
913
|
+
|
|
914
|
+
static void canvas_jpeg_error_exit(j_common_ptr cinfo) {
|
|
915
|
+
canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
|
|
916
|
+
cjerr->output_message(cinfo);
|
|
917
|
+
// Return control to the setjmp point
|
|
918
|
+
longjmp(cjerr->setjmp_buffer, 1);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Capture libjpeg errors instead of writing stdout
|
|
922
|
+
static void canvas_jpeg_output_message(j_common_ptr cinfo) {
|
|
923
|
+
canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
|
|
924
|
+
char buff[JMSG_LENGTH_MAX];
|
|
925
|
+
cjerr->format_message(cinfo, buff);
|
|
926
|
+
// (Only the last message will be returned to JS land.)
|
|
927
|
+
cjerr->image->errorInfo.set(buff);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/*
|
|
931
|
+
* Takes a jpeg data buffer and assigns it as mime data to a
|
|
932
|
+
* dummy surface
|
|
933
|
+
*/
|
|
934
|
+
|
|
935
|
+
cairo_status_t
|
|
936
|
+
Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
|
|
937
|
+
// TODO: remove this duplicate logic
|
|
938
|
+
// JPEG setup
|
|
939
|
+
struct jpeg_decompress_struct args;
|
|
940
|
+
struct canvas_jpeg_error_mgr err;
|
|
941
|
+
|
|
942
|
+
err.image = this;
|
|
943
|
+
args.err = jpeg_std_error(&err);
|
|
944
|
+
args.err->error_exit = canvas_jpeg_error_exit;
|
|
945
|
+
args.err->output_message = canvas_jpeg_output_message;
|
|
946
|
+
|
|
947
|
+
// Establish the setjmp return context for canvas_jpeg_error_exit to use
|
|
948
|
+
if (setjmp(err.setjmp_buffer)) {
|
|
949
|
+
// If we get here, the JPEG code has signaled an error.
|
|
950
|
+
// We need to clean up the JPEG object, close the input file, and return.
|
|
951
|
+
jpeg_destroy_decompress(&args);
|
|
952
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
jpeg_create_decompress(&args);
|
|
956
|
+
|
|
957
|
+
jpeg_mem_src(&args, buf, len);
|
|
958
|
+
|
|
959
|
+
jpeg_read_header(&args, 1);
|
|
960
|
+
jpeg_start_decompress(&args);
|
|
961
|
+
width = naturalWidth = args.output_width;
|
|
962
|
+
height = naturalHeight = args.output_height;
|
|
963
|
+
|
|
964
|
+
// Data alloc
|
|
965
|
+
// 8 pixels per byte using Alpha Channel format to reduce memory requirement.
|
|
966
|
+
int buf_size = naturalHeight * cairo_format_stride_for_width(CAIRO_FORMAT_A1, naturalWidth);
|
|
967
|
+
uint8_t *data = new uint8_t[buf_size];
|
|
968
|
+
if (!data) {
|
|
969
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
970
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
BufferReader reader(buf, len);
|
|
974
|
+
Orientation orientation = getExifOrientation(reader);
|
|
975
|
+
updateDimensionsForOrientation(orientation);
|
|
976
|
+
|
|
977
|
+
// New image surface
|
|
978
|
+
_surface = cairo_image_surface_create_for_data(
|
|
979
|
+
data
|
|
980
|
+
, CAIRO_FORMAT_A1
|
|
981
|
+
, naturalWidth
|
|
982
|
+
, naturalHeight
|
|
983
|
+
, cairo_format_stride_for_width(CAIRO_FORMAT_A1, naturalWidth));
|
|
984
|
+
|
|
985
|
+
// Cleanup
|
|
986
|
+
jpeg_abort_decompress(&args);
|
|
987
|
+
jpeg_destroy_decompress(&args);
|
|
988
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
989
|
+
|
|
990
|
+
if (status) {
|
|
991
|
+
delete[] data;
|
|
992
|
+
return status;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
rotatePixels(data, naturalWidth, naturalHeight, 1, orientation);
|
|
996
|
+
|
|
997
|
+
_data = data;
|
|
998
|
+
|
|
999
|
+
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/*
|
|
1003
|
+
* Helper function for disposing of a mime data closure.
|
|
1004
|
+
*/
|
|
1005
|
+
|
|
1006
|
+
void
|
|
1007
|
+
clearMimeData(void *closure) {
|
|
1008
|
+
Napi::MemoryManagement::AdjustExternalMemory(
|
|
1009
|
+
static_cast<read_closure_t *>(closure)->env,
|
|
1010
|
+
-static_cast<int>((static_cast<read_closure_t *>(closure)->len)));
|
|
1011
|
+
free(static_cast<read_closure_t *>(closure)->buf);
|
|
1012
|
+
free(closure);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/*
|
|
1016
|
+
* Assign a given buffer as mime data against the surface.
|
|
1017
|
+
* The provided buffer will be copied, and the copy will
|
|
1018
|
+
* be automatically freed when the surface is destroyed.
|
|
1019
|
+
*/
|
|
1020
|
+
|
|
1021
|
+
cairo_status_t
|
|
1022
|
+
Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) {
|
|
1023
|
+
uint8_t *mime_data = (uint8_t *) malloc(len);
|
|
1024
|
+
if (!mime_data) {
|
|
1025
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
1026
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
read_closure_t *mime_closure = (read_closure_t *) malloc(sizeof(read_closure_t));
|
|
1030
|
+
if (!mime_closure) {
|
|
1031
|
+
free(mime_data);
|
|
1032
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
1033
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
memcpy(mime_data, data, len);
|
|
1037
|
+
|
|
1038
|
+
mime_closure->env = env;
|
|
1039
|
+
mime_closure->buf = mime_data;
|
|
1040
|
+
mime_closure->len = len;
|
|
1041
|
+
|
|
1042
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, len);
|
|
1043
|
+
|
|
1044
|
+
return cairo_surface_set_mime_data(_surface
|
|
1045
|
+
, mime_type
|
|
1046
|
+
, mime_data
|
|
1047
|
+
, len
|
|
1048
|
+
, clearMimeData
|
|
1049
|
+
, mime_closure);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/*
|
|
1053
|
+
* Load jpeg from buffer.
|
|
1054
|
+
*/
|
|
1055
|
+
|
|
1056
|
+
cairo_status_t
|
|
1057
|
+
Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
|
|
1058
|
+
BufferReader reader(buf, len);
|
|
1059
|
+
Orientation orientation = getExifOrientation(reader);
|
|
1060
|
+
|
|
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, orientation);
|
|
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
|
+
Orientation orientation = NORMAL;
|
|
1105
|
+
{
|
|
1106
|
+
StreamReader reader(stream);
|
|
1107
|
+
orientation = getExifOrientation(reader);
|
|
1108
|
+
rewind(stream);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// JPEG setup
|
|
1112
|
+
struct jpeg_decompress_struct args;
|
|
1113
|
+
struct canvas_jpeg_error_mgr err;
|
|
1114
|
+
|
|
1115
|
+
err.image = this;
|
|
1116
|
+
args.err = jpeg_std_error(&err);
|
|
1117
|
+
args.err->error_exit = canvas_jpeg_error_exit;
|
|
1118
|
+
args.err->output_message = canvas_jpeg_output_message;
|
|
1119
|
+
|
|
1120
|
+
// Establish the setjmp return context for canvas_jpeg_error_exit to use
|
|
1121
|
+
if (setjmp(err.setjmp_buffer)) {
|
|
1122
|
+
// If we get here, the JPEG code has signaled an error.
|
|
1123
|
+
// We need to clean up the JPEG object, close the input file, and return.
|
|
1124
|
+
jpeg_destroy_decompress(&args);
|
|
1125
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
jpeg_create_decompress(&args);
|
|
1129
|
+
|
|
1130
|
+
jpeg_stdio_src(&args, stream);
|
|
1131
|
+
|
|
1132
|
+
jpeg_read_header(&args, 1);
|
|
1133
|
+
jpeg_start_decompress(&args);
|
|
1134
|
+
|
|
1135
|
+
if (args.output_width > canvas_max_side || args.output_height > canvas_max_side) {
|
|
1136
|
+
jpeg_destroy_decompress(&args);
|
|
1137
|
+
return CAIRO_STATUS_INVALID_SIZE;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
width = naturalWidth = args.output_width;
|
|
1141
|
+
height = naturalHeight = args.output_height;
|
|
1142
|
+
|
|
1143
|
+
status = decodeJPEGIntoSurface(&args, orientation);
|
|
1144
|
+
fclose(stream);
|
|
1145
|
+
} else { // We'll need the actual source jpeg data, so read fully.
|
|
1146
|
+
uint8_t *buf;
|
|
1147
|
+
unsigned len;
|
|
1148
|
+
|
|
1149
|
+
fseek(stream, 0, SEEK_END);
|
|
1150
|
+
len = ftell(stream);
|
|
1151
|
+
fseek(stream, 0, SEEK_SET);
|
|
1152
|
+
|
|
1153
|
+
buf = (uint8_t *) malloc(len);
|
|
1154
|
+
if (!buf) {
|
|
1155
|
+
this->errorInfo.set(NULL, "malloc", errno);
|
|
1156
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (fread(buf, len, 1, stream) != 1) {
|
|
1160
|
+
status = CAIRO_STATUS_READ_ERROR;
|
|
1161
|
+
} else if ((DATA_IMAGE | DATA_MIME) == data_mode) {
|
|
1162
|
+
status = loadJPEGFromBuffer(buf, len);
|
|
1163
|
+
if (!status) status = assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
|
1164
|
+
} else if (DATA_MIME == data_mode) {
|
|
1165
|
+
status = decodeJPEGBufferIntoMimeSurface(buf, len);
|
|
1166
|
+
}
|
|
1167
|
+
#if defined(_MSC_VER)
|
|
1168
|
+
else if (DATA_IMAGE == data_mode) {
|
|
1169
|
+
status = loadJPEGFromBuffer(buf, len);
|
|
1170
|
+
}
|
|
1171
|
+
#endif
|
|
1172
|
+
else {
|
|
1173
|
+
status = CAIRO_STATUS_READ_ERROR;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
fclose(stream);
|
|
1177
|
+
free(buf);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return status;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/*
|
|
1184
|
+
* Returns the Exif orientation if one exists, otherwise returns NORMAL
|
|
1185
|
+
*/
|
|
1186
|
+
|
|
1187
|
+
Image::Orientation
|
|
1188
|
+
Image::getExifOrientation(Reader& jpeg) {
|
|
1189
|
+
static const char kJpegStartOfImage = (char)0xd8;
|
|
1190
|
+
static const char kJpegStartOfFrameBaseline = (char)0xc0;
|
|
1191
|
+
static const char kJpegStartOfFrameProgressive = (char)0xc2;
|
|
1192
|
+
static const char kJpegHuffmanTable = (char)0xc4;
|
|
1193
|
+
static const char kJpegQuantizationTable = (char)0xdb;
|
|
1194
|
+
static const char kJpegRestartInterval = (char)0xdd;
|
|
1195
|
+
static const char kJpegComment = (char)0xfe;
|
|
1196
|
+
static const char kJpegStartOfScan = (char)0xda;
|
|
1197
|
+
static const char kJpegApp0 = (char)0xe0;
|
|
1198
|
+
static const char kJpegApp1 = (char)0xe1;
|
|
1199
|
+
|
|
1200
|
+
// Find the Exif tag (if it exists)
|
|
1201
|
+
int exif_len = 0;
|
|
1202
|
+
bool done = false;
|
|
1203
|
+
while (!done && jpeg.hasBytes(1)) {
|
|
1204
|
+
while (jpeg.hasBytes(1) && jpeg.getNext() != 0xff) {
|
|
1205
|
+
// noop
|
|
1206
|
+
}
|
|
1207
|
+
if (jpeg.hasBytes(1)) {
|
|
1208
|
+
char tag = jpeg.getNext();
|
|
1209
|
+
switch (tag) {
|
|
1210
|
+
case kJpegStartOfImage:
|
|
1211
|
+
break; // beginning of file, no extra bytes
|
|
1212
|
+
case kJpegRestartInterval:
|
|
1213
|
+
jpeg.skipBytes(4);
|
|
1214
|
+
break;
|
|
1215
|
+
case kJpegStartOfFrameBaseline:
|
|
1216
|
+
case kJpegStartOfFrameProgressive:
|
|
1217
|
+
case kJpegHuffmanTable:
|
|
1218
|
+
case kJpegQuantizationTable:
|
|
1219
|
+
case kJpegComment:
|
|
1220
|
+
case kJpegApp0:
|
|
1221
|
+
case kJpegApp1: {
|
|
1222
|
+
if (jpeg.hasBytes(2)) {
|
|
1223
|
+
uint16_t tag_len = 0;
|
|
1224
|
+
tag_len |= jpeg.getNext() << 8;
|
|
1225
|
+
tag_len |= jpeg.getNext();
|
|
1226
|
+
// The tag length includes the two bytes for the length
|
|
1227
|
+
uint16_t tag_content_len = std::max(0, tag_len - 2);
|
|
1228
|
+
if (tag != kJpegApp1 || !jpeg.hasBytes(tag_content_len)) {
|
|
1229
|
+
jpeg.skipBytes(tag_content_len); // skip JPEG tags we ignore.
|
|
1230
|
+
} else if (!jpeg.hasBytes(6)) {
|
|
1231
|
+
jpeg.skipBytes(tag_content_len); // too short to have "Exif\0\0"
|
|
1232
|
+
} else {
|
|
1233
|
+
if (jpeg.getNext() == 'E' && jpeg.getNext() == 'x' &&
|
|
1234
|
+
jpeg.getNext() == 'i' && jpeg.getNext() == 'f' &&
|
|
1235
|
+
jpeg.getNext() == '\0' && jpeg.getNext() == '\0') {
|
|
1236
|
+
exif_len = tag_content_len - 6;
|
|
1237
|
+
done = true;
|
|
1238
|
+
} else {
|
|
1239
|
+
jpeg.skipBytes(tag_content_len); // too short to have "Exif\0\0"
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
} else {
|
|
1243
|
+
done = true; // shouldn't happen: corrupt file or we have a bug
|
|
1244
|
+
}
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
case kJpegStartOfScan:
|
|
1248
|
+
default:
|
|
1249
|
+
done = true; // got to the image, apparently no exif tags here
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// Parse exif if it exists. If it does, we have already checked that jpeglen
|
|
1256
|
+
// is longer than exifStart + exifLen, so we can safely index the data
|
|
1257
|
+
if (exif_len > 0) {
|
|
1258
|
+
// The first two bytes of TIFF header are "II" if little-endian ("Intel")
|
|
1259
|
+
// and "MM" if big-endian ("Motorola")
|
|
1260
|
+
const bool isLE = (jpeg.getNext() == 'I');
|
|
1261
|
+
jpeg.skipBytes(3); // +1 for the other I/M, +2 for 0x002a
|
|
1262
|
+
|
|
1263
|
+
auto readUint16Little = [](Reader &jpeg) -> uint32_t {
|
|
1264
|
+
uint16_t val = uint16_t(jpeg.getNext());
|
|
1265
|
+
val |= uint16_t(jpeg.getNext()) << 8;
|
|
1266
|
+
return val;
|
|
1267
|
+
};
|
|
1268
|
+
auto readUint32Little = [](Reader &jpeg) -> uint32_t {
|
|
1269
|
+
uint32_t val = uint32_t(jpeg.getNext());
|
|
1270
|
+
val |= uint32_t(jpeg.getNext()) << 8;
|
|
1271
|
+
val |= uint32_t(jpeg.getNext()) << 16;
|
|
1272
|
+
val |= uint32_t(jpeg.getNext()) << 24;
|
|
1273
|
+
return val;
|
|
1274
|
+
};
|
|
1275
|
+
auto readUint16Big = [](Reader &jpeg) -> uint32_t {
|
|
1276
|
+
uint16_t val = uint16_t(jpeg.getNext()) << 8;
|
|
1277
|
+
val |= uint16_t(jpeg.getNext());
|
|
1278
|
+
return val;
|
|
1279
|
+
};
|
|
1280
|
+
auto readUint32Big = [](Reader &jpeg) -> uint32_t {
|
|
1281
|
+
uint32_t val = uint32_t(jpeg.getNext()) << 24;
|
|
1282
|
+
val |= uint32_t(jpeg.getNext()) << 16;
|
|
1283
|
+
val |= uint32_t(jpeg.getNext()) << 8;
|
|
1284
|
+
val |= uint32_t(jpeg.getNext());
|
|
1285
|
+
return val;
|
|
1286
|
+
};
|
|
1287
|
+
// The first two bytes of TIFF header are "II" if little-endian ("Intel")
|
|
1288
|
+
// and "MM" if big-endian ("Motorola")
|
|
1289
|
+
auto readUint32 = [readUint32Little, readUint32Big, isLE](Reader &jpeg) -> uint32_t {
|
|
1290
|
+
return isLE ? readUint32Little(jpeg) : readUint32Big(jpeg);
|
|
1291
|
+
};
|
|
1292
|
+
auto readUint16 = [readUint16Little, readUint16Big, isLE](Reader &jpeg) -> uint32_t {
|
|
1293
|
+
return isLE ? readUint16Little(jpeg) : readUint16Big(jpeg);
|
|
1294
|
+
};
|
|
1295
|
+
// offset to the IFD0 (offset from beginning of TIFF header, II/MM,
|
|
1296
|
+
// which is 8 bytes before where we are after reading the uint32)
|
|
1297
|
+
jpeg.skipBytes(readUint32(jpeg) - 8);
|
|
1298
|
+
|
|
1299
|
+
// Read the IFD0 ("Image File Directory 0")
|
|
1300
|
+
// | NN | n entries in directory (2 bytes)
|
|
1301
|
+
// | TT | tt | nnnn | vvvv | entry: tag (2b), data type (2b),
|
|
1302
|
+
// n components (4b), value/offset (4b)
|
|
1303
|
+
if (jpeg.hasBytes(2)) {
|
|
1304
|
+
uint16_t nEntries = readUint16(jpeg);
|
|
1305
|
+
for (uint16_t i = 0; i < nEntries && jpeg.hasBytes(2); ++i) {
|
|
1306
|
+
uint16_t tag = readUint16(jpeg);
|
|
1307
|
+
// The entry is 12 bytes. We already read the 2 bytes for the tag.
|
|
1308
|
+
jpeg.skipBytes(6); // skip 2 for the data type, skip 4 n components.
|
|
1309
|
+
if (tag == 0x112) {
|
|
1310
|
+
switch (readUint16(jpeg)) { // orientation tag is always one uint16
|
|
1311
|
+
case 1: return NORMAL;
|
|
1312
|
+
case 2: return MIRROR_HORIZ;
|
|
1313
|
+
case 3: return ROTATE_180;
|
|
1314
|
+
case 4: return MIRROR_VERT;
|
|
1315
|
+
case 5: return MIRROR_HORIZ_AND_ROTATE_270_CW;
|
|
1316
|
+
case 6: return ROTATE_90_CW;
|
|
1317
|
+
case 7: return MIRROR_HORIZ_AND_ROTATE_90_CW;
|
|
1318
|
+
case 8: return ROTATE_270_CW;
|
|
1319
|
+
default: return NORMAL;
|
|
1320
|
+
}
|
|
1321
|
+
} else {
|
|
1322
|
+
jpeg.skipBytes(4); // skip the four bytes for the value
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
return NORMAL;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/*
|
|
1332
|
+
* Updates the dimensions of the bitmap according to the orientation
|
|
1333
|
+
*/
|
|
1334
|
+
|
|
1335
|
+
void Image::updateDimensionsForOrientation(Orientation orientation) {
|
|
1336
|
+
switch (orientation) {
|
|
1337
|
+
case ROTATE_90_CW:
|
|
1338
|
+
case ROTATE_270_CW:
|
|
1339
|
+
case MIRROR_HORIZ_AND_ROTATE_90_CW:
|
|
1340
|
+
case MIRROR_HORIZ_AND_ROTATE_270_CW: {
|
|
1341
|
+
int tmp = naturalWidth;
|
|
1342
|
+
naturalWidth = naturalHeight;
|
|
1343
|
+
naturalHeight = tmp;
|
|
1344
|
+
tmp = width;
|
|
1345
|
+
width = height;
|
|
1346
|
+
height = tmp;
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
case NORMAL:
|
|
1350
|
+
case MIRROR_HORIZ:
|
|
1351
|
+
case MIRROR_VERT:
|
|
1352
|
+
case ROTATE_180:
|
|
1353
|
+
default: {
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/*
|
|
1360
|
+
* Rotates the pixels to the correct orientation.
|
|
1361
|
+
*/
|
|
1362
|
+
|
|
1363
|
+
void
|
|
1364
|
+
Image::rotatePixels(uint8_t* pixels, int width, int height, int channels,
|
|
1365
|
+
Orientation orientation) {
|
|
1366
|
+
auto swapPixel = [channels](uint8_t* pixels, int src_idx, int dst_idx) {
|
|
1367
|
+
uint8_t tmp;
|
|
1368
|
+
for (int i = 0; i < channels; ++i) {
|
|
1369
|
+
tmp = pixels[src_idx + i];
|
|
1370
|
+
pixels[src_idx + i] = pixels[dst_idx + i];
|
|
1371
|
+
pixels[dst_idx + i] = tmp;
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
auto mirrorHoriz = [swapPixel](uint8_t* pixels, int width, int height, int channels) {
|
|
1376
|
+
int midX = width / 2; // ok to truncate if odd, since we don't swap a center pixel
|
|
1377
|
+
for (int y = 0; y < height; ++y) {
|
|
1378
|
+
for (int x = 0; x < midX; ++x) {
|
|
1379
|
+
int orig_idx = (y * width + x) * channels;
|
|
1380
|
+
int new_idx = (y * width + width - 1 - x) * channels;
|
|
1381
|
+
swapPixel(pixels, orig_idx, new_idx);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
auto mirrorVert = [swapPixel](uint8_t* pixels, int width, int height, int channels) {
|
|
1387
|
+
int midY = height / 2; // ok to truncate if odd, since we don't swap a center pixel
|
|
1388
|
+
for (int y = 0; y < midY; ++y) {
|
|
1389
|
+
for (int x = 0; x < width; ++x) {
|
|
1390
|
+
int orig_idx = (y * width + x) * channels;
|
|
1391
|
+
int new_idx = ((height - y - 1) * width + x) * channels;
|
|
1392
|
+
swapPixel(pixels, orig_idx, new_idx);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
auto rotate90 = [](uint8_t* pixels, int width, int height, int channels) {
|
|
1398
|
+
const int n_bytes = width * height * channels;
|
|
1399
|
+
uint8_t *unrotated = new uint8_t[n_bytes];
|
|
1400
|
+
if (!unrotated) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
std::memcpy(unrotated, pixels, n_bytes);
|
|
1404
|
+
for (int y = 0; y < height; ++y) {
|
|
1405
|
+
for (int x = 0; x < width ; ++x) {
|
|
1406
|
+
int orig_idx = (y * width + x) * channels;
|
|
1407
|
+
int new_idx = (x * height + height - 1 - y) * channels;
|
|
1408
|
+
std::memcpy(pixels + new_idx, unrotated + orig_idx, channels);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
auto rotate270 = [](uint8_t* pixels, int width, int height, int channels) {
|
|
1414
|
+
const int n_bytes = width * height * channels;
|
|
1415
|
+
uint8_t *unrotated = new uint8_t[n_bytes];
|
|
1416
|
+
if (!unrotated) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
std::memcpy(unrotated, pixels, n_bytes);
|
|
1420
|
+
for (int y = 0; y < height; ++y) {
|
|
1421
|
+
for (int x = 0; x < width ; ++x) {
|
|
1422
|
+
int orig_idx = (y * width + x) * channels;
|
|
1423
|
+
int new_idx = ((width - 1 - x) * height + y) * channels;
|
|
1424
|
+
std::memcpy(pixels + new_idx, unrotated + orig_idx, channels);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
switch (orientation) {
|
|
1430
|
+
case MIRROR_HORIZ:
|
|
1431
|
+
mirrorHoriz(pixels, width, height, channels);
|
|
1432
|
+
break;
|
|
1433
|
+
case MIRROR_VERT:
|
|
1434
|
+
mirrorVert(pixels, width, height, channels);
|
|
1435
|
+
break;
|
|
1436
|
+
case ROTATE_180:
|
|
1437
|
+
mirrorHoriz(pixels, width, height, channels);
|
|
1438
|
+
mirrorVert(pixels, width, height, channels);
|
|
1439
|
+
break;
|
|
1440
|
+
case ROTATE_90_CW:
|
|
1441
|
+
rotate90(pixels, height, width, channels); // swap w/h because we need orig w/h
|
|
1442
|
+
break;
|
|
1443
|
+
case ROTATE_270_CW:
|
|
1444
|
+
rotate270(pixels, height, width, channels); // swap w/h because we need orig w/h
|
|
1445
|
+
break;
|
|
1446
|
+
case MIRROR_HORIZ_AND_ROTATE_90_CW:
|
|
1447
|
+
mirrorHoriz(pixels, height, width, channels); // swap w/h because we need orig w/h
|
|
1448
|
+
rotate90(pixels, height, width, channels);
|
|
1449
|
+
break;
|
|
1450
|
+
case MIRROR_HORIZ_AND_ROTATE_270_CW:
|
|
1451
|
+
mirrorHoriz(pixels, height, width, channels); // swap w/h because we need orig w/h
|
|
1452
|
+
rotate270(pixels, height, width, channels);
|
|
1453
|
+
break;
|
|
1454
|
+
case NORMAL:
|
|
1455
|
+
default:
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
#endif /* HAVE_JPEG */
|
|
1461
|
+
|
|
1462
|
+
#ifdef HAVE_RSVG
|
|
1463
|
+
|
|
1464
|
+
/*
|
|
1465
|
+
* Load SVG from buffer
|
|
1466
|
+
*/
|
|
1467
|
+
|
|
1468
|
+
cairo_status_t
|
|
1469
|
+
Image::loadSVGFromBuffer(uint8_t *buf, unsigned len) {
|
|
1470
|
+
_is_svg = true;
|
|
1471
|
+
|
|
1472
|
+
if (NULL == (_rsvg = rsvg_handle_new_from_data(buf, len, nullptr))) {
|
|
1473
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
double d_width;
|
|
1477
|
+
double d_height;
|
|
1478
|
+
|
|
1479
|
+
rsvg_handle_get_intrinsic_size_in_pixels(_rsvg, &d_width, &d_height);
|
|
1480
|
+
|
|
1481
|
+
width = naturalWidth = d_width;
|
|
1482
|
+
height = naturalHeight = d_height;
|
|
1483
|
+
|
|
1484
|
+
if (width <= 0 || height <= 0) {
|
|
1485
|
+
this->errorInfo.set("Width and height must be set on the svg element");
|
|
1486
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
return renderSVGToSurface();
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/*
|
|
1493
|
+
* Renders the Rsvg handle to this image's surface
|
|
1494
|
+
*/
|
|
1495
|
+
cairo_status_t
|
|
1496
|
+
Image::renderSVGToSurface() {
|
|
1497
|
+
cairo_status_t status;
|
|
1498
|
+
|
|
1499
|
+
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
|
1500
|
+
|
|
1501
|
+
status = cairo_surface_status(_surface);
|
|
1502
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
1503
|
+
g_object_unref(_rsvg);
|
|
1504
|
+
return status;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
cairo_t *cr = cairo_create(_surface);
|
|
1508
|
+
status = cairo_status(cr);
|
|
1509
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
1510
|
+
g_object_unref(_rsvg);
|
|
1511
|
+
return status;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
RsvgRectangle viewport = {
|
|
1515
|
+
0, // x
|
|
1516
|
+
0, // y
|
|
1517
|
+
static_cast<double>(width),
|
|
1518
|
+
static_cast<double>(height)
|
|
1519
|
+
};
|
|
1520
|
+
gboolean render_ok = rsvg_handle_render_document(_rsvg, cr, &viewport, nullptr);
|
|
1521
|
+
if (!render_ok) {
|
|
1522
|
+
g_object_unref(_rsvg);
|
|
1523
|
+
cairo_destroy(cr);
|
|
1524
|
+
return CAIRO_STATUS_READ_ERROR; // or WRITE?
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
cairo_destroy(cr);
|
|
1528
|
+
|
|
1529
|
+
_svg_last_width = width;
|
|
1530
|
+
_svg_last_height = height;
|
|
1531
|
+
|
|
1532
|
+
return status;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/*
|
|
1536
|
+
* Load SVG
|
|
1537
|
+
*/
|
|
1538
|
+
|
|
1539
|
+
cairo_status_t
|
|
1540
|
+
Image::loadSVG(FILE *stream) {
|
|
1541
|
+
_is_svg = true;
|
|
1542
|
+
|
|
1543
|
+
struct stat s;
|
|
1544
|
+
int fd = fileno(stream);
|
|
1545
|
+
|
|
1546
|
+
// stat
|
|
1547
|
+
if (fstat(fd, &s) < 0) {
|
|
1548
|
+
fclose(stream);
|
|
1549
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
uint8_t *buf = (uint8_t *) malloc(s.st_size);
|
|
1553
|
+
|
|
1554
|
+
if (!buf) {
|
|
1555
|
+
fclose(stream);
|
|
1556
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
size_t read = fread(buf, s.st_size, 1, stream);
|
|
1560
|
+
fclose(stream);
|
|
1561
|
+
|
|
1562
|
+
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
|
1563
|
+
if (1 == read) result = loadSVGFromBuffer(buf, s.st_size);
|
|
1564
|
+
free(buf);
|
|
1565
|
+
|
|
1566
|
+
return result;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
#endif /* HAVE_RSVG */
|
|
1570
|
+
|
|
1571
|
+
/*
|
|
1572
|
+
* Load BMP from buffer.
|
|
1573
|
+
*/
|
|
1574
|
+
|
|
1575
|
+
cairo_status_t Image::loadBMPFromBuffer(uint8_t *buf, unsigned len){
|
|
1576
|
+
BMPParser::Parser parser;
|
|
1577
|
+
|
|
1578
|
+
// Reversed ARGB32 with pre-multiplied alpha
|
|
1579
|
+
uint8_t pixFmt[5] = {2, 1, 0, 3, 1};
|
|
1580
|
+
parser.parse(buf, len, pixFmt);
|
|
1581
|
+
|
|
1582
|
+
if (parser.getStatus() != BMPParser::Status::OK) {
|
|
1583
|
+
errorInfo.reset();
|
|
1584
|
+
errorInfo.message = parser.getErrMsg();
|
|
1585
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
width = naturalWidth = parser.getWidth();
|
|
1589
|
+
height = naturalHeight = parser.getHeight();
|
|
1590
|
+
uint8_t *data = parser.getImgd();
|
|
1591
|
+
|
|
1592
|
+
_surface = cairo_image_surface_create_for_data(
|
|
1593
|
+
data,
|
|
1594
|
+
CAIRO_FORMAT_ARGB32,
|
|
1595
|
+
width,
|
|
1596
|
+
height,
|
|
1597
|
+
cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
// No need to delete the data
|
|
1601
|
+
cairo_status_t status = cairo_surface_status(_surface);
|
|
1602
|
+
if (status) return status;
|
|
1603
|
+
|
|
1604
|
+
_data = data;
|
|
1605
|
+
parser.clearImgd();
|
|
1606
|
+
|
|
1607
|
+
return CAIRO_STATUS_SUCCESS;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/*
|
|
1611
|
+
* Load BMP.
|
|
1612
|
+
*/
|
|
1613
|
+
|
|
1614
|
+
cairo_status_t Image::loadBMP(FILE *stream){
|
|
1615
|
+
struct stat s;
|
|
1616
|
+
int fd = fileno(stream);
|
|
1617
|
+
|
|
1618
|
+
// Stat
|
|
1619
|
+
if (fstat(fd, &s) < 0) {
|
|
1620
|
+
fclose(stream);
|
|
1621
|
+
return CAIRO_STATUS_READ_ERROR;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
uint8_t *buf = new uint8_t[s.st_size];
|
|
1625
|
+
|
|
1626
|
+
if (!buf) {
|
|
1627
|
+
fclose(stream);
|
|
1628
|
+
errorInfo.set(NULL, "malloc", errno);
|
|
1629
|
+
return CAIRO_STATUS_NO_MEMORY;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
size_t read = fread(buf, s.st_size, 1, stream);
|
|
1633
|
+
fclose(stream);
|
|
1634
|
+
|
|
1635
|
+
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
|
1636
|
+
if (read == 1) result = loadBMPFromBuffer(buf, s.st_size);
|
|
1637
|
+
delete[] buf;
|
|
1638
|
+
|
|
1639
|
+
return result;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/*
|
|
1643
|
+
* Return UNKNOWN, SVG, GIF, JPEG, or PNG based on the filename.
|
|
1644
|
+
*/
|
|
1645
|
+
|
|
1646
|
+
Image::type
|
|
1647
|
+
Image::extension(const char *filename) {
|
|
1648
|
+
size_t len = strlen(filename);
|
|
1649
|
+
filename += len;
|
|
1650
|
+
if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return Image::JPEG;
|
|
1651
|
+
if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF;
|
|
1652
|
+
if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG;
|
|
1653
|
+
if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG;
|
|
1654
|
+
if (len >= 4 && 0 == strcmp(".svg", filename - 4)) return Image::SVG;
|
|
1655
|
+
return Image::UNKNOWN;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/*
|
|
1659
|
+
* Sniff bytes 0..1 for JPEG's magic number ff d8.
|
|
1660
|
+
*/
|
|
1661
|
+
|
|
1662
|
+
int
|
|
1663
|
+
Image::isJPEG(uint8_t *data) {
|
|
1664
|
+
return 0xff == data[0] && 0xd8 == data[1];
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/*
|
|
1668
|
+
* Sniff bytes 0..2 for "GIF".
|
|
1669
|
+
*/
|
|
1670
|
+
|
|
1671
|
+
int
|
|
1672
|
+
Image::isGIF(uint8_t *data) {
|
|
1673
|
+
return 'G' == data[0] && 'I' == data[1] && 'F' == data[2];
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/*
|
|
1677
|
+
* Sniff bytes 1..3 for "PNG".
|
|
1678
|
+
*/
|
|
1679
|
+
|
|
1680
|
+
int
|
|
1681
|
+
Image::isPNG(uint8_t *data) {
|
|
1682
|
+
return 'P' == data[1] && 'N' == data[2] && 'G' == data[3];
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/*
|
|
1686
|
+
* Skip "<?" and "<!" tags to test if root tag starts "<svg"
|
|
1687
|
+
*/
|
|
1688
|
+
int
|
|
1689
|
+
Image::isSVG(uint8_t *data, unsigned len) {
|
|
1690
|
+
for (unsigned i = 3; i < len; i++) {
|
|
1691
|
+
if ('<' == data[i-3]) {
|
|
1692
|
+
switch (data[i-2]) {
|
|
1693
|
+
case '?':
|
|
1694
|
+
case '!':
|
|
1695
|
+
break;
|
|
1696
|
+
case 's':
|
|
1697
|
+
return ('v' == data[i-1] && 'g' == data[i]);
|
|
1698
|
+
default:
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/*
|
|
1707
|
+
* Check for valid BMP signatures
|
|
1708
|
+
*/
|
|
1709
|
+
|
|
1710
|
+
int Image::isBMP(uint8_t *data, unsigned len) {
|
|
1711
|
+
if(len < 2) return false;
|
|
1712
|
+
std::string sig = std::string(1, (char)data[0]) + (char)data[1];
|
|
1713
|
+
return sig == "BM" ||
|
|
1714
|
+
sig == "BA" ||
|
|
1715
|
+
sig == "CI" ||
|
|
1716
|
+
sig == "CP" ||
|
|
1717
|
+
sig == "IC" ||
|
|
1718
|
+
sig == "PT";
|
|
1719
|
+
}
|