1 /**********************************************************************
2 * File: charsample.cpp (Formerly charsample.c)
3 * Description: Class to contain character samples and match scores
4 * to be used for adaption
5 * Author: Chris Newton
6 * Created: Thu Oct 7 13:40:37 BST 1993
7 *
8 * (C) Copyright 1993, Hewlett-Packard Ltd.
9 ** Licensed under the Apache License, Version 2.0 (the "License");
10 ** you may not use this file except in compliance with the License.
11 ** You may obtain a copy of the License at
12 ** http://www.apache.org/licenses/LICENSE-2.0
13 ** Unless required by applicable law or agreed to in writing, software
14 ** distributed under the License is distributed on an "AS IS" BASIS,
15 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 ** See the License for the specific language governing permissions and
17 ** limitations under the License.
18 *
19 **********************************************************************/
20
21 #include "mfcpch.h"
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <math.h>
25 #ifdef __UNIX__
26 #include <assert.h>
27 #include <unistd.h>
28 #endif
29 #include "memry.h"
30 #include "tessvars.h"
31 #include "statistc.h"
32 #include "charsample.h"
33 #include "paircmp.h"
34 #include "matmatch.h"
35 #include "adaptions.h"
36 #include "secname.h"
37 #include "notdll.h"
38 #include "tesseractclass.h"
39
40 extern inT32 demo_word; // Hack for demos
41
ELISTIZE(CHAR_SAMPLES)42 ELISTIZE (CHAR_SAMPLE) ELISTIZE (CHAR_SAMPLES) CHAR_SAMPLE::CHAR_SAMPLE () {
43 sample_blob = NULL;
44 sample_denorm = NULL;
45 sample_image = NULL;
46 ch = '\0';
47 n_samples_matched = 0;
48 total_match_scores = 0.0;
49 sumsq_match_scores = 0.0;
50 }
51
52
CHAR_SAMPLE(PBLOB * blob,DENORM * denorm,char c)53 CHAR_SAMPLE::CHAR_SAMPLE(PBLOB *blob, DENORM *denorm, char c) {
54 sample_blob = blob;
55 sample_denorm = denorm;
56 sample_image = NULL;
57 ch = c;
58 n_samples_matched = 0;
59 total_match_scores = 0.0;
60 sumsq_match_scores = 0.0;
61 }
62
63
CHAR_SAMPLE(IMAGE * image,char c)64 CHAR_SAMPLE::CHAR_SAMPLE(IMAGE *image, char c) {
65 sample_blob = NULL;
66 sample_denorm = NULL;
67 sample_image = image;
68 ch = c;
69 n_samples_matched = 0;
70 total_match_scores = 0.0;
71 sumsq_match_scores = 0.0;
72 }
73
74
match_sample(CHAR_SAMPLE * test_sample,BOOL8 updating,tesseract::Tesseract * tess)75 float CHAR_SAMPLE::match_sample( // Update match scores
76 CHAR_SAMPLE *test_sample,
77 BOOL8 updating,
78 tesseract::Tesseract* tess) {
79 float score1;
80 float score2;
81 IMAGE *image = test_sample->image ();
82
83 if (sample_blob != NULL && test_sample->blob () != NULL) {
84 PBLOB *blob = test_sample->blob ();
85 DENORM *denorm = test_sample->denorm ();
86
87 score1 = tess->compare_bln_blobs (sample_blob, sample_denorm, blob, denorm);
88 score2 = tess->compare_bln_blobs (blob, denorm, sample_blob, sample_denorm);
89
90 score1 = (score1 > score2) ? score1 : score2;
91 }
92 else if (sample_image != NULL && image != NULL) {
93 CHAR_PROTO *sample = new CHAR_PROTO (this);
94
95 score1 = matrix_match (sample_image, image);
96 delete sample;
97 }
98 else
99 return BAD_SCORE;
100
101 if ((tessedit_use_best_sample || tessedit_cluster_debug) && updating) {
102 n_samples_matched++;
103 total_match_scores += score1;
104 sumsq_match_scores += score1 * score1;
105 }
106 return score1;
107 }
108
109
mean_score()110 double CHAR_SAMPLE::mean_score() {
111 if (n_samples_matched > 0)
112 return (total_match_scores / n_samples_matched);
113 else
114 return BAD_SCORE;
115 }
116
117
variance()118 double CHAR_SAMPLE::variance() {
119 double mean = mean_score ();
120
121 if (n_samples_matched > 0) {
122 return (sumsq_match_scores / n_samples_matched) - mean * mean;
123 }
124 else
125 return BAD_SCORE;
126 }
127
128
print(FILE * f)129 void CHAR_SAMPLE::print(FILE *f) {
130 if (!tessedit_cluster_debug)
131 return;
132
133 if (n_samples_matched > 0)
134 fprintf (f,
135 "%c - sample matched against " INT32FORMAT
136 " blobs, mean: %f, var: %f\n", ch, n_samples_matched,
137 mean_score (), variance ());
138 else
139 fprintf (f, "No matches for this sample (%c)\n", ch);
140 }
141
142
reset_match_statistics()143 void CHAR_SAMPLE::reset_match_statistics() {
144 n_samples_matched = 0;
145 total_match_scores = 0.0;
146 sumsq_match_scores = 0.0;
147 }
148
149
CHAR_SAMPLES()150 CHAR_SAMPLES::CHAR_SAMPLES() {
151 type = UNKNOWN;
152 samples.clear ();
153 ch = '\0';
154 best_sample = NULL;
155 proto = NULL;
156 }
157
158
CHAR_SAMPLES(CHAR_SAMPLE * sample)159 CHAR_SAMPLES::CHAR_SAMPLES(CHAR_SAMPLE *sample) {
160 CHAR_SAMPLE_IT sample_it = &samples;
161
162 ASSERT_HOST (sample->image () != NULL || sample->blob () != NULL);
163
164 if (sample->image () != NULL)
165 type = IMAGE_CLUSTER;
166 else if (sample->blob () != NULL)
167 type = BLOB_CLUSTER;
168
169 samples.clear ();
170 sample_it.add_to_end (sample);
171 if (tessedit_mm_only_match_same_char)
172 ch = sample->character ();
173 else
174 ch = '\0';
175 best_sample = NULL;
176 proto = NULL;
177 }
178
179
add_sample(CHAR_SAMPLE * sample,tesseract::Tesseract * tess)180 void CHAR_SAMPLES::add_sample(CHAR_SAMPLE *sample, tesseract::Tesseract* tess) {
181 CHAR_SAMPLE_IT sample_it = &samples;
182
183 if (tessedit_use_best_sample || tessedit_cluster_debug)
184 for (sample_it.mark_cycle_pt ();
185 !sample_it.cycled_list (); sample_it.forward ()) {
186 sample_it.data ()->match_sample (sample, TRUE, tess);
187 sample->match_sample (sample_it.data (), TRUE, tess);
188 }
189
190 sample_it.add_to_end (sample);
191
192 if (tessedit_mm_use_prototypes && type == IMAGE_CLUSTER) {
193 if (samples.length () == tessedit_mm_prototype_min_size)
194 this->build_prototype ();
195 else if (samples.length () > tessedit_mm_prototype_min_size)
196 this->add_sample_to_prototype (sample);
197 }
198 }
199
200
add_sample_to_prototype(CHAR_SAMPLE * sample)201 void CHAR_SAMPLES::add_sample_to_prototype(CHAR_SAMPLE *sample) {
202 BOOL8 rebuild = FALSE;
203 inT32 new_xsize = proto->x_size ();
204 inT32 new_ysize = proto->y_size ();
205 inT32 sample_xsize = sample->image ()->get_xsize ();
206 inT32 sample_ysize = sample->image ()->get_ysize ();
207
208 if (sample_xsize > new_xsize) {
209 new_xsize = sample_xsize;
210 rebuild = TRUE;
211 }
212 if (sample_ysize > new_ysize) {
213 new_ysize = sample_ysize;
214 rebuild = TRUE;
215 }
216
217 if (rebuild)
218 proto->enlarge_prototype (new_xsize, new_ysize);
219
220 proto->add_sample (sample);
221 }
222
223
build_prototype()224 void CHAR_SAMPLES::build_prototype() {
225 CHAR_SAMPLE_IT sample_it = &samples;
226 CHAR_SAMPLE *sample;
227 inT32 proto_xsize = 0;
228 inT32 proto_ysize = 0;
229
230 if (type != IMAGE_CLUSTER
231 || samples.length () < tessedit_mm_prototype_min_size)
232 return;
233
234 for (sample_it.mark_cycle_pt ();
235 !sample_it.cycled_list (); sample_it.forward ()) {
236 sample = sample_it.data ();
237 if (sample->image ()->get_xsize () > proto_xsize)
238 proto_xsize = sample->image ()->get_xsize ();
239 if (sample->image ()->get_ysize () > proto_ysize)
240 proto_ysize = sample->image ()->get_ysize ();
241 }
242
243 proto = new CHAR_PROTO (proto_xsize, proto_ysize, 0, 0, '\0');
244
245 for (sample_it.mark_cycle_pt ();
246 !sample_it.cycled_list (); sample_it.forward ())
247 this->add_sample_to_prototype (sample_it.data ());
248
249 }
250
251
find_best_sample()252 void CHAR_SAMPLES::find_best_sample() {
253 CHAR_SAMPLE_IT sample_it = &samples;
254 double score;
255 double best_score = MAX_INT32;
256
257 if (ch == '\0' || samples.length () < tessedit_mm_prototype_min_size)
258 return;
259
260 for (sample_it.mark_cycle_pt ();
261 !sample_it.cycled_list (); sample_it.forward ()) {
262 score = sample_it.data ()->mean_score ();
263 if (score < best_score) {
264 best_score = score;
265 best_sample = sample_it.data ();
266 }
267 }
268 #ifndef SECURE_NAMES
269 if (tessedit_cluster_debug) {
270 tprintf ("Best sample for this %c cluster:\n", ch);
271 best_sample->print (debug_fp);
272 }
273 #endif
274 }
275
276
match_score(CHAR_SAMPLE * sample,tesseract::Tesseract * tess)277 float CHAR_SAMPLES::match_score(CHAR_SAMPLE *sample,
278 tesseract::Tesseract* tess) {
279 if (tessedit_mm_only_match_same_char && sample->character () != ch)
280 return BAD_SCORE;
281
282 if (tessedit_use_best_sample && best_sample != NULL)
283 return best_sample->match_sample (sample, FALSE, tess);
284 else if ((tessedit_mm_use_prototypes
285 || tessedit_mm_adapt_using_prototypes) && proto != NULL)
286 return proto->match_sample (sample);
287 else
288 return this->nn_match_score (sample, tess);
289 }
290
291
nn_match_score(CHAR_SAMPLE * sample,tesseract::Tesseract * tess)292 float CHAR_SAMPLES::nn_match_score(CHAR_SAMPLE *sample,
293 tesseract::Tesseract* tess) {
294 CHAR_SAMPLE_IT sample_it = &samples;
295 float score;
296 float min_score = MAX_INT32;
297
298 for (sample_it.mark_cycle_pt ();
299 !sample_it.cycled_list (); sample_it.forward ()) {
300 score = sample_it.data ()->match_sample (sample, FALSE, tess);
301 if (score < min_score)
302 min_score = score;
303 }
304
305 return min_score;
306 }
307
308
assign_to_char()309 void CHAR_SAMPLES::assign_to_char() {
310 STATS char_frequency(FIRST_CHAR, LAST_CHAR);
311 CHAR_SAMPLE_IT sample_it = &samples;
312 inT32 i;
313 inT32 max_index = 0;
314 inT32 max_freq = 0;
315
316 if (samples.length () == 0 || tessedit_mm_only_match_same_char)
317 return;
318
319 for (sample_it.mark_cycle_pt ();
320 !sample_it.cycled_list (); sample_it.forward ())
321 char_frequency.add ((inT32) sample_it.data ()->character (), 1);
322
323 for (i = FIRST_CHAR; i <= LAST_CHAR; i++)
324 if (char_frequency.pile_count (i) > max_freq) {
325 max_index = i;
326 max_freq = char_frequency.pile_count (i);
327 }
328
329 if (samples.length () >= tessedit_cluster_min_size
330 && max_freq > samples.length () * tessedit_cluster_accept_fraction)
331 ch = (char) max_index;
332 }
333
334
print(FILE * f)335 void CHAR_SAMPLES::print(FILE *f) {
336 CHAR_SAMPLE_IT sample_it = &samples;
337
338 fprintf (f, "Collected " INT32FORMAT " samples\n", samples.length ());
339
340 #ifndef SECURE_NAMES
341 if (tessedit_cluster_debug)
342 for (sample_it.mark_cycle_pt ();
343 !sample_it.cycled_list (); sample_it.forward ())
344 sample_it.data ()->print (f);
345
346 if (ch == '\0')
347 fprintf (f, "\nCluster not used for adaption\n");
348 else
349 fprintf (f, "\nCluster used to adapt to '%c's\n", ch);
350 #endif
351 }
352
353
CHAR_PROTO()354 CHAR_PROTO::CHAR_PROTO() {
355 xsize = 0;
356 ysize = 0;
357 ch = '\0';
358 nsamples = 0;
359 proto_data = NULL;
360 proto = NULL;
361 }
362
363
CHAR_PROTO(inT32 x_size,inT32 y_size,inT32 n_samples,float initial_value,char c)364 CHAR_PROTO::CHAR_PROTO(inT32 x_size,
365 inT32 y_size,
366 inT32 n_samples,
367 float initial_value,
368 char c) {
369 inT32 x;
370 inT32 y;
371
372 xsize = x_size;
373 ysize = y_size;
374 ch = c;
375 nsamples = n_samples;
376
377 ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
378
379 for (y = 0; y < ysize; y++)
380 for (x = 0; x < xsize; x++)
381 proto[x][y] = initial_value;
382 }
383
384
CHAR_PROTO(CHAR_SAMPLE * sample)385 CHAR_PROTO::CHAR_PROTO(CHAR_SAMPLE *sample) {
386 inT32 x;
387 inT32 y;
388 IMAGELINE imline_s;
389
390 if (sample->image () == NULL) {
391 xsize = 0;
392 ysize = 0;
393 ch = '\0';
394 nsamples = 0;
395 proto_data = NULL;
396 proto = NULL;
397 }
398 else {
399 ch = sample->character ();
400 xsize = sample->image ()->get_xsize ();
401 ysize = sample->image ()->get_ysize ();
402 nsamples = 1;
403
404 ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
405
406 for (y = 0; y < ysize; y++) {
407 sample->image ()->fast_get_line (0, y, xsize, &imline_s);
408 for (x = 0; x < xsize; x++)
409 if (imline_s.pixels[x] == BINIM_WHITE)
410 proto[x][y] = 1.0;
411 else
412 proto[x][y] = -1.0;
413 }
414 }
415 }
416
417
~CHAR_PROTO()418 CHAR_PROTO::~CHAR_PROTO () {
419 if (proto_data != NULL)
420 FREE_2D_ARRAY(proto_data, proto);
421 }
422
423
match_sample(CHAR_SAMPLE * test_sample)424 float CHAR_PROTO::match_sample(CHAR_SAMPLE *test_sample) {
425 CHAR_PROTO *test_proto;
426 float score;
427
428 if (test_sample->image () != NULL) {
429 test_proto = new CHAR_PROTO (test_sample);
430 if (xsize > test_proto->x_size ())
431 score = this->match (test_proto);
432 else {
433 demo_word = -demo_word; // Flag different call
434 score = test_proto->match (this);
435 }
436 }
437 else
438 return BAD_SCORE;
439
440 delete test_proto;
441
442 return score;
443 }
444
445
match(CHAR_PROTO * test_proto)446 float CHAR_PROTO::match(CHAR_PROTO *test_proto) {
447 inT32 xsize2 = test_proto->x_size ();
448 inT32 y_size;
449 inT32 y_size2;
450 inT32 x_offset;
451 inT32 y_offset;
452 inT32 x;
453 inT32 y;
454 CHAR_PROTO *match_proto;
455 float score;
456 float sum = 0.0;
457
458 ASSERT_HOST (xsize >= xsize2);
459
460 x_offset = (xsize - xsize2) / 2;
461
462 if (ysize < test_proto->y_size ()) {
463 y_size = test_proto->y_size ();
464 y_size2 = ysize;
465 y_offset = (y_size - y_size2) / 2;
466
467 match_proto = new CHAR_PROTO (xsize,
468 y_size,
469 nsamples * test_proto->n_samples (),
470 0, '\0');
471
472 for (y = 0; y < y_offset; y++) {
473 for (x = 0; x < xsize2; x++) {
474 match_proto->data ()[x + x_offset][y] =
475 test_proto->data ()[x][y] * nsamples;
476 sum += match_proto->data ()[x + x_offset][y];
477 }
478 }
479
480 for (y = y_offset + y_size2; y < y_size; y++) {
481 for (x = 0; x < xsize2; x++) {
482 match_proto->data ()[x + x_offset][y] =
483 test_proto->data ()[x][y] * nsamples;
484 sum += match_proto->data ()[x + x_offset][y];
485 }
486 }
487
488 for (y = y_offset; y < y_offset + y_size2; y++) {
489 for (x = 0; x < x_offset; x++) {
490 match_proto->data ()[x][y] = proto[x][y - y_offset] *
491 test_proto->n_samples ();
492 sum += match_proto->data ()[x][y];
493 }
494
495 for (x = x_offset + xsize2; x < xsize; x++) {
496 match_proto->data ()[x][y] = proto[x][y - y_offset] *
497 test_proto->n_samples ();
498 sum += match_proto->data ()[x][y];
499 }
500
501 for (x = x_offset; x < x_offset + xsize2; x++) {
502 match_proto->data ()[x][y] =
503 proto[x][y - y_offset] * test_proto->data ()[x - x_offset][y];
504 sum += match_proto->data ()[x][y];
505 }
506 }
507 }
508 else {
509 y_size = ysize;
510 y_size2 = test_proto->y_size ();
511 y_offset = (y_size - y_size2) / 2;
512
513 match_proto = new CHAR_PROTO (xsize,
514 y_size,
515 nsamples * test_proto->n_samples (),
516 0, '\0');
517
518 for (y = 0; y < y_offset; y++)
519 for (x = 0; x < xsize; x++) {
520 match_proto->data ()[x][y] =
521 proto[x][y] * test_proto->n_samples ();
522 sum += match_proto->data ()[x][y];
523 }
524
525 for (y = y_offset + y_size2; y < y_size; y++)
526 for (x = 0; x < xsize; x++) {
527 match_proto->data ()[x][y] =
528 proto[x][y] * test_proto->n_samples ();
529 sum += match_proto->data ()[x][y];
530 }
531
532 for (y = y_offset; y < y_offset + y_size2; y++) {
533 for (x = 0; x < x_offset; x++) {
534 match_proto->data ()[x][y] =
535 proto[x][y] * test_proto->n_samples ();
536 sum += match_proto->data ()[x][y];
537 }
538
539 for (x = x_offset + xsize2; x < xsize; x++) {
540 match_proto->data ()[x][y] =
541 proto[x][y] * test_proto->n_samples ();
542 sum += match_proto->data ()[x][y];
543 }
544
545 for (x = x_offset; x < x_offset + xsize2; x++) {
546 match_proto->data ()[x][y] = proto[x][y] *
547 test_proto->data ()[x - x_offset][y - y_offset];
548 sum += match_proto->data ()[x][y];
549 }
550 }
551 }
552
553 score = (1.0 - sum /
554 (xsize * y_size * nsamples * test_proto->n_samples ()));
555
556 if (tessedit_mm_debug) {
557 if (score < 0) {
558 tprintf ("Match score %f\n", score);
559 tprintf ("x: %d, y: %d, ns: %d, nt: %d, dx %d, dy: %d\n",
560 xsize, y_size, nsamples, test_proto->n_samples (),
561 x_offset, y_offset);
562 for (y = 0; y < y_size; y++) {
563 tprintf ("\n%d", y);
564 for (x = 0; x < xsize; x++)
565 tprintf ("\t%d", match_proto->data ()[x][y]);
566
567 }
568 tprintf ("\n");
569 fflush(debug_fp);
570 }
571 }
572
573 #ifndef GRAPHICS_DISABLED
574 if (tessedit_display_mm) {
575 tprintf ("Match score %f\n", score);
576 display_images (this->make_image (),
577 test_proto->make_image (), match_proto->make_image ());
578 }
579 else if (demo_word != 0) {
580 if (demo_word > 0)
581 display_image (test_proto->make_image (), "Test sample",
582 300, 400, FALSE);
583 else
584 display_image (this->make_image (), "Test sample", 300, 400, FALSE);
585
586 display_image (match_proto->make_image (), "Best match",
587 700, 400, TRUE);
588 }
589 #endif
590
591 delete match_proto;
592
593 return score;
594 }
595
596
enlarge_prototype(inT32 new_xsize,inT32 new_ysize)597 void CHAR_PROTO::enlarge_prototype(inT32 new_xsize, inT32 new_ysize) {
598 float *old_proto_data = proto_data;
599 float **old_proto = proto;
600 inT32 old_xsize = xsize;
601 inT32 old_ysize = ysize;
602 inT32 x_offset;
603 inT32 y_offset;
604 inT32 x;
605 inT32 y;
606
607 ASSERT_HOST (new_xsize >= xsize && new_ysize >= ysize);
608
609 xsize = new_xsize;
610 ysize = new_ysize;
611 ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
612 x_offset = (xsize - old_xsize) / 2;
613 y_offset = (ysize - old_ysize) / 2;
614
615 for (y = 0; y < y_offset; y++)
616 for (x = 0; x < xsize; x++)
617 proto[x][y] = nsamples;
618
619 for (y = y_offset + old_ysize; y < ysize; y++)
620 for (x = 0; x < xsize; x++)
621 proto[x][y] = nsamples;
622
623 for (y = y_offset; y < y_offset + old_ysize; y++) {
624 for (x = 0; x < x_offset; x++)
625 proto[x][y] = nsamples;
626
627 for (x = x_offset + old_xsize; x < xsize; x++)
628 proto[x][y] = nsamples;
629
630 for (x = x_offset; x < x_offset + old_xsize; x++)
631 proto[x][y] = old_proto[x - x_offset][y - y_offset];
632 }
633
634 FREE_2D_ARRAY(old_proto_data, old_proto);
635 }
636
637
add_sample(CHAR_SAMPLE * sample)638 void CHAR_PROTO::add_sample(CHAR_SAMPLE *sample) {
639 inT32 x_offset;
640 inT32 y_offset;
641 inT32 x;
642 inT32 y;
643 IMAGELINE imline_s;
644 inT32 sample_xsize = sample->image ()->get_xsize ();
645 inT32 sample_ysize = sample->image ()->get_ysize ();
646
647 x_offset = (xsize - sample_xsize) / 2;
648 y_offset = (ysize - sample_ysize) / 2;
649
650 ASSERT_HOST (x_offset >= 0 && y_offset >= 0);
651
652 for (y = 0; y < y_offset; y++)
653 for (x = 0; x < xsize; x++)
654 proto[x][y]++; // Treat pixels outside the
655 // range as white
656 for (y = y_offset + sample_ysize; y < ysize; y++)
657 for (x = 0; x < xsize; x++)
658 proto[x][y]++;
659
660 for (y = y_offset; y < y_offset + sample_ysize; y++) {
661 sample->image ()->fast_get_line (0,
662 y - y_offset, sample_xsize, &imline_s);
663 for (x = x_offset; x < x_offset + sample_xsize; x++) {
664 if (imline_s.pixels[x - x_offset] == BINIM_WHITE)
665 proto[x][y]++;
666 else
667 proto[x][y]--;
668 }
669
670 for (x = 0; x < x_offset; x++)
671 proto[x][y]++;
672
673 for (x = x_offset + sample_xsize; x < xsize; x++)
674 proto[x][y]++;
675 }
676
677 nsamples++;
678 }
679
680
make_image()681 IMAGE *CHAR_PROTO::make_image() {
682 IMAGE *image;
683 IMAGELINE imline_p;
684 inT32 x;
685 inT32 y;
686
687 ASSERT_HOST (nsamples != 0);
688
689 image = new (IMAGE);
690 image->create (xsize, ysize, 8);
691
692 for (y = 0; y < ysize; y++) {
693 image->fast_get_line (0, y, xsize, &imline_p);
694
695 for (x = 0; x < xsize; x++) {
696 imline_p.pixels[x] = 128 +
697 (uinT8) ((proto[x][y] * 128.0) / (0.00001 + nsamples));
698 }
699
700 image->fast_put_line (0, y, xsize, &imline_p);
701 }
702 return image;
703 }
704