-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuildpdf.cc
1463 lines (1192 loc) · 40.4 KB
/
buildpdf.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright (C) 2013-2022 Alexis Bienvenüe <paamc@passoire.fr>
This file is part of Auto-Multiple-Choice
Auto-Multiple-Choice is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 2 of
the License, or (at your option) any later version.
Auto-Multiple-Choice is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with Auto-Multiple-Choice. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __BUILDPDF__
#define __BUILDPDF__ 1
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <cairo.h>
#include <cairo-pdf.h>
#include <poppler.h>
#include <pango/pangocairo.h>
#include <string>
#ifdef DEBUG
#include <fstream>
#include <iostream>
#endif
#include "opencv2/core/core.hpp"
#if CV_MAJOR_VERSION > 2
#define OPENCV_23 1
#define OPENCV_21 1
#define OPENCV_20 1
#define OPENCV_30 1
#else
#if CV_MAJOR_VERSION == 2
#define OPENCV_20 1
#if CV_MINOR_VERSION >= 1
#define OPENCV_21 1
#endif
#if CV_MINOR_VERSION >= 3
#define OPENCV_23 1
#endif
#endif
#endif
#include "opencv2/imgproc/imgproc.hpp"
#ifdef OPENCV_30
#include "opencv2/imgcodecs/imgcodecs.hpp"
#else
#include "opencv2/highgui/highgui.hpp"
#endif
#define FORMAT_JPEG 1
#define FORMAT_PNG 2
#define HIDE_ALPHA 0.95
#define HIDE_MARGIN 0.5
/*
Helpers to read PNG files from memory (as an array or as a vector)
with Cairo
*/
struct buffer_closure {
uchar *buffer;
unsigned long int length;
unsigned long int offset;
};
#define BUFFER_CLOSURE(ptr) ((buffer_closure*) (ptr))
static cairo_status_t read_buffer(void *closure, uchar *data, unsigned int length) {
if(BUFFER_CLOSURE(closure)->offset + length > BUFFER_CLOSURE(closure)->length) {
return(CAIRO_STATUS_READ_ERROR);
}
memcpy(data,BUFFER_CLOSURE(closure)->buffer + BUFFER_CLOSURE(closure)->offset,length);
BUFFER_CLOSURE(closure)->offset += length;
return(CAIRO_STATUS_SUCCESS);
}
struct vector_closure {
std::vector<uchar>::iterator iterator;
unsigned long int length;
};
#define VECTOR_CLOSURE(ptr) ((vector_closure*) (ptr))
static cairo_status_t read_vector(void *closure, uchar *data, unsigned int length) {
if(VECTOR_CLOSURE(closure)->length < length) {
return(CAIRO_STATUS_READ_ERROR);
}
std::copy(VECTOR_CLOSURE(closure)->iterator, VECTOR_CLOSURE(closure)->iterator+length,
(uchar*)data);
VECTOR_CLOSURE(closure)->iterator += length;
VECTOR_CLOSURE(closure)->length -= length;
return(CAIRO_STATUS_SUCCESS);
}
/*
BuildPdf
This class is used by AMC-annotate to build a pdf from image files
(scans), PDF files (subject pages), with transformation matrix
support and drawing support (rectangles, circles, marks, text).
*/
/* COORDINATE SYSTEMS:
All coordinate systems has top-left (0,0).
- LAYOUT/SUBJECT coordinates are pixel-based coordinates of an
image with density given with dpi, that corresponds to a question
page. So (0,0) is the top-left corner of the page, and the
bottom-right corner of the page is (,).
- SCAN coordinates are pixel-based coordinates on the scan image.
- PDF coordinates are point(=1/72 inch)-based coordinates of the
PDF page.
Note that when drawing, all coordinates will be given to BuildPdf
in pixels (with (0,0) = top-left corner), usually in LAYOUT
coordinates, but possibly also in PDF coordinates. These
coordinates will be mapped to PDF coordinates using a
transformation matrix.
*/
class BuildPdf {
public:
/* constructor: w and h are the page size in pixels (in the layout
coordinate system), and d the "dots per point", that is the
number of pixels in one pt (1pt is 1/72 inches).
*/
BuildPdf(double w, double h, double d):
width_in_pixels(w), height_in_pixels(h), dppt(d), n_pages(-1),
user_one_point(0), margin(0),
document(NULL), layout(NULL), surface(NULL), cr(NULL),
image_cr(NULL), image_surface(NULL), fake_image_buffer(NULL),
header_surface(NULL), header_cr(NULL), header_layout(NULL),
header_width(-1.0),
font_description(NULL),
line_width(1.0), font("Linux Libertine O 12"), debug(0),
scan_expansion(1.0), scan_resize_factor(1.0),
embedded_image_format(FORMAT_JPEG),
image_buffer(), scan_max_width(0), scan_max_height(0),
png_compression_level(9), jpeg_quality(75) {
printf(": w_pix=%g h_pix=%g dppt=%g\n",
width_in_pixels, height_in_pixels, dppt);
};
~BuildPdf();
/* call set_debug(1) to get more debugging output on stderr. */
void set_debug(int d) { debug = d; }
/* **************************************************************** */
/* METHODS TO INSERT SCANS (AS BACKGROUND) */
/* **************************************************************** */
/* the set_* functions can be used to set some parameters' values:
- embedded_image_format is the format scans will be converted to
before including them to the PDF file. There are currently two
choices: FORMAT_PNG or FORMAT_JPEG.
- jpeg_quality is the JPEG quality (between 0 and 100) used in
FORMAT_JPEG mode. A small value will lead to small PDF size,
but with small image quality...
- png_compression is the PNG compression level (from 1 to 9). Use
default value 9 for small PDF size (with images longer to
encode/decode).
- scan_max_height and scan_max_width define a maximum size for
the scans: scans which are larger will be resized so that their
dimensions don't exceed these values. This can allow to get
smaller PDF files.
- margin is the margin size in points, used for the global
verdict text and the questions scores.
*/
void set_embedded_image_format(int format) { embedded_image_format = format; }
void set_embedded_png() { embedded_image_format = FORMAT_PNG; }
void set_embedded_jpeg() { embedded_image_format = FORMAT_JPEG; }
void set_jpeg_quality(int quality) { jpeg_quality = quality; }
void set_png_compression_level(int l) { png_compression_level = l; }
void set_scan_max_height(int mh) { scan_max_height = mh; }
void set_scan_max_width(int mw) { scan_max_width = mw; }
void set_margin(double m) { margin = m; }
/* start_output starts to build a PDF into file
output_filename. Call it once for each PDF to create, before
addind images or drawing on it */
int start_output(char* output_filename);
/* close_output finishes with building the PDF, and closes the
file. */
void close_output();
/* next_page begins with another blank page. */
int next_page();
/* new_page_from_png begins with another page, with the PNG image as
a background. The PNG image can be given with its filename, as a
memoty buffer (pointer and lenght), or as a std::vector memory
buffer.
next_page() is called first, unless skip_show_page is set.
*/
int new_page_from_png(const char* filename);
int new_page_from_png(void *buffer, unsigned long int buffer_length);
int new_page_from_png(std::vector<uchar> &buf, int skip_show_page = 0);
/* resize_scan resizes the cv::Mat image so that its dimensions does
not exceed scan_max_height and scan_max_width. The scaling factor
is saved for later use (we need to know it to get the right
scaling factor between PDF coordinates and scan coordinates) */
void resize_scan(cv::Mat &image);
/* new_page_from_image begins with another page, with the image as a
background.
The first version will first converted to PNG or JPEG (depending
on embedded_image_format) using OpenCV.
With the two latter versions, the image is given as a memory
buffer, and is attached to the PDF file using the specified
mime_type (which can be either image/png or image/jpeg).
*/
int new_page_from_image(const char* filename);
int new_page_from_image(std::vector<uchar> &image_data, const char* mime_type,
int width, int height);
int new_page_from_image(unsigned char *data, unsigned int size,
const char* mime_type,
int width, int height);
/* load_pdf loads a PDF file, to be used later by
new_page_from_pdf. */
int load_pdf(char* filename);
/* new_page_from_pdf begins with another page, using page page_nb of
the loaded PDF as a background. */
int new_page_from_pdf(int page_nb);
/* **************************************************************** */
/* METHODS TO DRAW ON THE PAGE */
/* **************************************************************** */
/* set_line_width sets the line width (in points) for next
drawings. */
void set_line_width(double lw);
/* set_font sets the font for next drawings.
It then calls validate_font, which updates the font
description with this new font. */
int set_font(const char* font);
int validate_font();
/* color sets the color for next drawings, either with RBG or
RGBA. Color values must be between 0.0 and 1.0. */
double current_r, current_g, current_b, current_a;
void temp_color(double r, double g, double b, double a) {
cairo_set_source_rgba(cr, r, g, b, a);
}
void restore_color() {
cairo_set_source_rgba(cr, current_r, current_g, current_b, current_a);
}
void color(double r, double g, double b, double a) {
cairo_set_source_rgba(cr, r, g, b, a);
current_r = r;
current_g = g;
current_b = b;
current_a = a;
}
void color(double r,double g, double b) {
color(r, g, b, 1.0);
}
void header_color(double r,double g, double b) {
cairo_set_source_rgba(header_cr, r, g, b, 1.0);
}
void background_rectangle(double x0, double y0, double width, double height) {
temp_color( 1.0, 1.0, 1.0, HIDE_ALPHA );
fill_rectangle(x0 - HIDE_MARGIN * em, x0 + width + 2*HIDE_MARGIN * em,
y0 - HIDE_MARGIN * em, y0 + height + 2*HIDE_MARGIN * em);
restore_color();
}
void clear_header(int destroy);
void start_header();
void show_header();
void set_header_width(double width) { header_width = width; }
/* set_matrix_to_scan sets the matrix that transforms layout (subject)
coordinates to scan coordinates (as recorded in the layout AMC
database). */
void set_matrix_to_scan(double a, double b, double c ,double d, double e, double f);
/* identity_matrix sets the matrix to identity. It can be used when
the background is the question page, not a scan, or when the
following drawings will be done with subject/layout coordinates.
*/
void identity_matrix();
/* keep_on_scan moves the (x,y) point (in layout/subject
coordinates) so that the corresponding point stays on the scan */
void keep_on_scan(double *x, double *y);
/* drawing symbols (the rectangle on which the symbol should be
based is given): */
void draw_rectangle(double xmin, double xmax, double ymin, double ymax);
void fill_rectangle(double xmin, double xmax, double ymin, double ymax);
void draw_mark(double xmin, double xmax, double ymin, double ymax);
void draw_circle(double xmin, double xmax, double ymin, double ymax);
/* draw_text draws the UTF8 string at (x,y), with x-anchor and
y-anchor given by xpos and ypos. When xpos=0.0, the text is
written at the right of (x,y). When xpos=1.0, the text is written
at the left of (x,y). When 0.5 for exemple, the text is
x-centered at (x,y). The same applies for ypos in the
y-direction. */
void draw_text(double x, double y,
double xpos, double ypos, const char *text,
int hide_background = 0);
void draw_text(cairo_t *local_cr, PangoLayout* local_layout,
double x, double y, double xpos, double ypos, const char *text,
int hide_background = 0);
/* cr_move moves the current point by (dx,dy) */
void cr_move(double dx, double dy);
void cr_move(cairo_t *local_cr, double dx, double dy);
/* draw_next_text draws a text at the following line */
void draw_next_text(cairo_t *local_cr, PangoLayout* local_layout,
double xpos, double ypos, const char * text,
int hide_background = 0);
void draw_next_text(double xpos, double ypos, const char * text,
int hide_background = 0);
/* draw_text_margin writes a text in the margin (left margin if
xside=0, and right margin if xside=1). The point used is a point
at the border of the margin, so that xpos=0.0 should be used for
left margin, and xpos=1.0 for tight margin.
*/
void draw_text_margin(int xside, double y,
double xpos, double ypos, const char *text);
/* draw_text_rectangle writes a text in the given rectangle (the
text is scaled down if necessary to fit in the rectangle).
*/
int draw_text_rectangle(double xmin, double xmax,
double ymin, double ymax,
const char *text);
private:
// dimensions of one subject page in the layout coordinate system
double width_in_pixels;
double height_in_pixels;
// dots per pt for the layout
double dppt;
// number of pages created so far for current PDF file. Equals -1 if
// no PDF file is opened for output
int n_pages;
// PDF document loaded (usualy the subject), from which one can copy
// pages to the output PDF
PopplerDocument *document;
// Pango layout used to write texts
PangoLayout *layout;
PangoLayout *header_layout;
// Cairo environment used to draw on the page
cairo_surface_t *surface;
cairo_t *cr;
cairo_matrix_t matrix;
// Header
cairo_surface_t *header_surface ;
cairo_t *header_cr;
double header_width;
// Cairo environment used to draw the background image (scan)
cairo_t *image_cr;
cairo_surface_t *image_surface;
// Fake PNG image used to attach images to the PDF file. It will
// never contain any particular image, only random stuff, but is
// needed to attach images properly to the PDF output.
unsigned char *fake_image_buffer;
// Image currently been attached to the PDF output
std::vector<uchar> image_buffer;
// use this dimension in Cairo user coordinate system to get one
// point (1/72 inch) in the PDF coordinate system
double user_one_point;
// scaling factor used to expand or shrink the scan to make it the
// same size as the PDF output.
double scan_expansion;
// scaling factor used when resizing the scan (when its dimensions
// exceed the scan_max_* values)
double scan_resize_factor;
// drawing parameters
double em;
double margin;
double line_width;
std::string font;
// debuging?
int debug;
// image parameters
int embedded_image_format;
int png_compression_level;
int jpeg_quality;
int scan_max_height;
int scan_max_width;
void set_matrix(double a, double b, double c ,double d, double e, double f);
void set_matrix(cairo_matrix_t *m);
double normalize_distance();
double normalize_matrix_distance(cairo_matrix_t *m);
PangoLayout* r_font_size_layout(double ratio);
PangoFontDescription *font_description;
int new_page_from_image_surface(cairo_surface_t *is);
void free_buffer();
};
BuildPdf::~BuildPdf() {
close_output();
if(document != NULL) g_object_unref(document);
}
void BuildPdf::clear_header(int destroy) {
if(destroy) {
printf(": header_surface...\n");
if(header_surface != NULL) {
cairo_surface_destroy(header_surface);
}
}
if(header_cr != NULL) {
cairo_destroy(header_cr);
header_cr = NULL;
}
printf(": header_layout...\n");
if(header_layout != NULL) {
g_object_unref(header_layout);
header_layout = NULL;
}
header_surface=NULL;
}
void BuildPdf::start_header() {
cairo_status_t status;
header_surface = cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL);
status = cairo_surface_status(header_surface);
if(status != CAIRO_STATUS_SUCCESS) {
printf("! ERROR : creating header surface - %s\n",
cairo_status_to_string(status));
cairo_surface_destroy(header_surface);
header_surface = NULL;
}
header_cr = cairo_create(header_surface);
if(cairo_status(header_cr) != CAIRO_STATUS_SUCCESS) {
printf("! ERROR : creating header cairo - %s\n",
cairo_status_to_string(cairo_status(header_cr)));
cairo_surface_destroy(header_surface);
cairo_destroy(header_cr);
header_surface = NULL;
header_cr = NULL;
}
header_layout = pango_cairo_create_layout(header_cr);
if(header_layout == NULL) {
printf("! ERROR : creating pango/cairo header layout - %s\n",
cairo_status_to_string(status));
cairo_surface_destroy(header_surface);
cairo_destroy(header_cr);
header_surface = NULL;
header_cr = NULL;
}
pango_cairo_context_set_resolution(pango_layout_get_context(header_layout),
user_one_point * 72.);
pango_cairo_update_layout(header_cr, header_layout);
pango_layout_set_width(header_layout,
(header_width < 0 ? -1 :
header_width * PANGO_SCALE));
pango_layout_set_font_description(header_layout, font_description);
}
void BuildPdf::show_header() {
// Header background
double x0, y0, width, height;
cairo_recording_surface_ink_extents(header_surface, &x0, &y0, &width, &height);
if(debug) {
printf("; ink extents (%f,%f)+(%f,%f) em=%f\n", x0, y0, width, height, em);
}
background_rectangle(x0, y0, width, height);
// Header
cairo_set_source_surface(cr, header_surface, 0.0, 0.0);
cairo_paint(cr);
}
int BuildPdf::start_output(char* output_filename) {
// close current PDF document, if one
close_output();
printf(": opening -> %s\n", output_filename);
if(debug) {
printf("; Create main surface\n");
}
// create a new PDF Cairo surface, with dimensions in points
surface = cairo_pdf_surface_create(output_filename,
width_in_pixels / dppt,
height_in_pixels / dppt);
cairo_status_t status = cairo_surface_status(surface);
if(status != CAIRO_STATUS_SUCCESS) {
printf("! ERROR : creating surface - %s\n",
cairo_status_to_string(status));
cairo_surface_destroy(surface);
surface = NULL;
return(1);
}
// Create Cairo context for drawings and texts (will not be used for
// images)
if(debug) {
printf("; Create cr\n");
}
cr = cairo_create(surface);
if(cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
printf("! ERROR : creating cairo - %s\n",
cairo_status_to_string(cairo_status(cr)));
cairo_surface_destroy(surface);
cairo_destroy(cr);
surface = NULL;
cr = NULL;
return(1);
}
// Create Pango Cairo layout for texts
if(debug) {
printf("; Create layout\n");
}
layout = pango_cairo_create_layout(cr);
if(layout == NULL) {
printf("! ERROR : creating pango/cairo layout - %s\n",
cairo_status_to_string(status));
cairo_surface_destroy(surface);
cairo_destroy(cr);
surface = NULL;
cr = NULL;
return(1);
}
// Updates the font description with the right font and uses it
// for the new layout
if(validate_font()) {
return(2);
}
// initialization. user_one_point will be set when using set_matrix
n_pages = 0;
user_one_point = 0;
if(debug) {
printf(": OK\n");
}
return(0);
}
void BuildPdf::close_output() {
if(n_pages >= 0) {
// free all allocated objects...
printf(": closing...\n");
next_page();
printf(": surface...\n");
if(surface != NULL) {
cairo_surface_finish(surface);
cairo_surface_destroy(surface);
surface = NULL;
}
if(cr != NULL) {
cairo_destroy(cr);
cr = NULL;
}
if(image_cr != NULL) {
cairo_destroy(image_cr);
image_cr = NULL;
}
printf(": layout...\n");
if(layout != NULL) {
g_object_unref(layout);
layout = NULL;
}
clear_header(1);
n_pages = -1;
} else {
printf(": closing unnecessary (not created)\n");
}
}
int BuildPdf::next_page() {
if(n_pages<0) {
printf("! ERROR: next_page in closed document\n");
return(1);
}
if(n_pages >= 1) {
// Adds current page to PDF output
if(debug) {
printf("; Show page\n");
}
// Show page
cairo_show_page(cr);
// Destroy objects used to insert the background scan
if(image_cr != NULL) {
if(debug) {
printf("; Destroy image_cr\n");
}
cairo_destroy(image_cr);
image_cr = NULL;
}
if(image_surface != NULL) {
if(debug) {
printf("; Destroy image_surface\n");
}
cairo_surface_finish(image_surface);
cairo_surface_destroy(image_surface);
image_surface = NULL;
}
if(fake_image_buffer != NULL) {
free_buffer();
}
scan_resize_factor = 1.0;
scan_expansion = 1.0;
}
n_pages++;
return(0);
}
int BuildPdf::new_page_from_png(const char* filename) {
if(next_page()) return(1);
if(debug) {
printf(": PNG < %s\n", filename);
printf("; Create image_surface from PNG\n");
}
cairo_surface_t *is = cairo_image_surface_create_from_png(filename);
return(new_page_from_image_surface(is));
}
int BuildPdf::new_page_from_png(void *buffer, unsigned long int buffer_length) {
if(next_page()) return(1);
buffer_closure closure;
closure.buffer = (uchar*) buffer;
closure.length = buffer_length;
closure.offset = 0;
if(debug) {
printf(": PNG < BUFFER\n");
printf("; Create image_surface from PNG stream\n");
}
cairo_surface_t *is = cairo_image_surface_create_from_png_stream(read_buffer, &closure);
return(new_page_from_image_surface(is));
}
int BuildPdf::new_page_from_png(std::vector<uchar> &buf, int skip_show_page) {
if(!skip_show_page) {
if(next_page()) return(1);
}
vector_closure closure;
closure.iterator = buf.begin();
closure.length = buf.size();
if(debug) {
printf(": PNG < BUFFER\n");
printf("; Create image_surface from PNG stream\n");
}
cairo_surface_t *is = cairo_image_surface_create_from_png_stream(read_vector, &closure);
return(new_page_from_image_surface(is));
}
// Free buffer used for the fake image
void BuildPdf::free_buffer() {
if(debug) {
printf("; Free fake_image_buffer\n");
}
free(fake_image_buffer);
fake_image_buffer = NULL;
}
void detach(void* args) {
if(*((int*) args)) {
printf("; DETACH\n");
}
}
#define ZFORMAT CAIRO_FORMAT_A1
int BuildPdf::new_page_from_image(unsigned char *data, unsigned int size,
const char* mime_type,
int width, int height) {
if(data == NULL) {
printf("! ERROR : new_page_from_image from null data\n");
return(1);
}
if(fake_image_buffer != NULL) {
printf("! ERROR : fake_image_buffer already present\n");
return(1);
}
#ifdef DEBUG
std::ofstream outfile("/tmp/opencv-exported",
std::ios::out | std::ios::binary);
outfile.write((const char*) data, size);
#endif
// Creates a fake image surface (to get minimal memory size, we use
// CAIRO_FORMAT_A1 format) with associated memory buffer.
int stride = cairo_format_stride_for_width(ZFORMAT, width);
if(debug) {
printf("; Create fake_image_buffer\n");
}
fake_image_buffer = (unsigned char*) malloc(stride * height);
if(debug) {
printf("; Create image_surface for DATA\n");
}
cairo_surface_t *is =
cairo_image_surface_create_for_data(fake_image_buffer,
ZFORMAT,
width, height,
stride);
if(debug) {
printf("; Attach mime %s to image_surface\n", mime_type);
}
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
if(!cairo_surface_supports_mime_type(surface, mime_type)) {
printf("! ERROR: surface does not handle %s\n",
mime_type);
return(1);
}
#endif
// Attach the real image to the surface
cairo_status_t status =
cairo_surface_set_mime_data(is, mime_type,
data, size,
detach, (void*) (&debug));
if(status != CAIRO_STATUS_SUCCESS) {
printf("! ERROR : setting mime data - %s\n",
cairo_status_to_string(status));
cairo_surface_destroy(is);
free_buffer();
return(-2);
}
// Uses the surface to create a new page
int status_np = new_page_from_image_surface(is);
return(status_np);
}
int BuildPdf::new_page_from_image(std::vector<uchar> &image_data,
const char* mime_type,
int width, int height) {
return(new_page_from_image(image_data.data(), image_data.size(),
mime_type, width, height));
}
void BuildPdf::resize_scan(cv::Mat &image) {
cv::Size s = image.size();
// compute the resize factor to be used to get dimensions no more
// than scan_max_*
double fx = 2;
double fy = 2;
if(scan_max_width > 0) {
fx = (double) scan_max_width / s.width;
}
if(scan_max_height > 0) {
fy = (double) scan_max_height / s.height;
}
if(debug) {
printf(": fx=%g fy=%g.\n", fx, fy);
}
if(fx < fy) {
scan_resize_factor = fx;
} else {
scan_resize_factor = fy;
}
// resize the image if needed
if(scan_resize_factor < 1.0) {
cv::resize(image, image, cv::Size(),
scan_resize_factor, scan_resize_factor, cv::INTER_AREA);
} else {
scan_resize_factor = 1.0;
if(debug) {
printf(": No need to resize.\n");
}
}
}
int BuildPdf::new_page_from_image(const char* filename) {
if(next_page()) return(1);
int direct_png = 0;
if(debug) {
printf(": IMAGE < %s\n", filename);
}
// read the image from disk to memory
cv::Mat image = cv::imread(filename);
const char* mime_type;
if(debug) {
printf(": type=%d depth=%d channels=%d\n",
image.type(), image.depth(), image.channels());
}
// resize it if needed
resize_scan(image);
// encode the image to a PNG or JPEG image buffer
if(embedded_image_format == FORMAT_JPEG) {
std::vector<int> params;
params.push_back(cv::IMWRITE_JPEG_QUALITY);
params.push_back(jpeg_quality);
imencode(".jpg", image, image_buffer, params);
mime_type = CAIRO_MIME_TYPE_JPEG;
} else if(embedded_image_format == FORMAT_PNG) {
std::vector<int> params;
params.push_back(cv::IMWRITE_PNG_COMPRESSION);
params.push_back(png_compression_level);
imencode(".png", image, image_buffer, params);
mime_type = CAIRO_MIME_TYPE_PNG;
direct_png = 1;
} else {
printf("! ERROR: invalid embedded_image_format - %d\n",
embedded_image_format);
return(3);
}
cv::Size s = image.size();
if(debug) {
printf(": converted to %s [Q=%d C=%d] (%.1f KB) w=%d h=%d\n",
mime_type,
jpeg_quality, png_compression_level,
(double) image_buffer.size() / 1024,
s.width, s.height);
}
int r;
if(direct_png) {
// PNG images can't be "attached"
// (cairo_surface_supports_mime_type would return FALSE), so we
// directly insert them into the PDF output
r = new_page_from_png(image_buffer, 1);
} else {
// JPEG images are attached to the image surface, and then
// inserted to the PDF output
r = new_page_from_image(image_buffer, mime_type, s.width, s.height);
}
if(debug) {
printf("; Image buffer exit\n");
}
return(r);
}
int BuildPdf::new_page_from_image_surface(cairo_surface_t *is) {
if(debug) {
printf("; Entering new_page_from_image_surface\n");
}
if(image_surface != NULL) {
printf("! ERROR : image_surface already in use\n");
return(1);
} else {
if(is == NULL) {
printf("! ERROR : NULL image_surface\n");
return(1);
}
image_surface = is;
}
cairo_status_t image_surface_status = cairo_surface_status(image_surface);
if(image_surface_status != CAIRO_STATUS_SUCCESS) {
printf("! ERROR : creating image surface / %s\n",
cairo_status_to_string(image_surface_status));
cairo_surface_destroy(image_surface);
return(1);
}
int w = cairo_image_surface_get_width(image_surface);
int h = cairo_image_surface_get_height(image_surface);
if(w <= 0 || h <= 0) {
printf("! ERROR : image dimensions should be positive (%dx%d)\n",
w, h);
cairo_surface_destroy(image_surface);
return(1);
}
// rx and ry are the scaling factors that has to be used to set the
// image surface with the same dimensions as the PDF output
double rx = width_in_pixels / dppt / w;
double ry = height_in_pixels / dppt / h;
if(rx < ry) {
scan_expansion = rx;
} else {
scan_expansion = ry;
}
if(debug) {
printf(": R=%g (%g,%g)\n", scan_expansion, rx, ry);