ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/CV.xs
Revision: 1.61
Committed: Sun Jul 25 11:24:48 2021 UTC (2 years, 9 months ago) by root
Branch: MAIN
Changes since 1.60: +29 -0 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 #include "EXTERN.h"
2 #include "perl.h"
3 #include "XSUB.h"
4
5 #include <string.h>
6 #include <setjmp.h>
7 #include <math.h>
8
9 #include <magic.h>
10
11 #include <jpeglib.h>
12 #include <jerror.h>
13
14 #include <glib.h>
15 #include <gtk/gtk.h>
16 #include <gdk/gdkx.h>
17 #include <gdk-pixbuf/gdk-pixbuf.h>
18
19 #include <gperl.h>
20 #include <gtk2perl.h>
21
22 #include <assert.h>
23
24 #if WEBP
25 #include <webp/demux.h>
26 #include <webp/decode.h>
27 #endif
28
29 #if JXL
30 #include <jxl/decode.h>
31 #include "jxl/thread_parallel_runner.h"
32 #endif
33
34 #include "perlmulticore.h"
35
36 #define IW 80 /* MUST match Schnauzer.pm! */
37 #define IH 60 /* MUST match Schnauzer.pm! */
38
39 #define LINELENGTH 240
40
41 #define ELLIPSIS "\xe2\x80\xa6"
42
43 typedef char *octet_string;
44
45 static magic_t magic_cookie[2]; /* !mime, mime */
46
47 struct jpg_err_mgr
48 {
49 struct jpeg_error_mgr err;
50 jmp_buf setjmp_buffer;
51 };
52
53 static void
54 cv_error_exit (j_common_ptr cinfo)
55 {
56 cinfo->err->output_message (cinfo);
57 longjmp (((struct jpg_err_mgr *)cinfo->err)->setjmp_buffer, 99);
58 }
59
60 static void
61 cv_error_output (j_common_ptr cinfo)
62 {
63 char msg[JMSG_LENGTH_MAX];
64
65 cinfo->err->format_message (cinfo, msg);
66
67 fprintf (stderr, "JPEG decoding error: %s\n", msg);
68 return;
69 }
70
71 static void
72 rgb_to_hsv (unsigned int r, unsigned int g, unsigned int b,
73 unsigned int *h, unsigned int *s, unsigned int *v)
74 {
75 unsigned int mx = r; if (g > mx) mx = g; if (b > mx) mx = b;
76 unsigned int mn = r; if (g < mn) mn = g; if (b < mn) mn = b;
77 unsigned int delta = mx - mn;
78
79 *v = mx;
80
81 *s = mx ? delta * 255 / mx : 0;
82
83 if (delta == 0)
84 *h = 0;
85 else
86 {
87 if (r == mx)
88 *h = ((int)g - (int)b) * 255 / (int)(delta * 3);
89 else if (g == mx)
90 *h = ((int)b - (int)r) * 255 / (int)(delta * 3) + 52;
91 else if (b == mx)
92 *h = ((int)r - (int)g) * 255 / (int)(delta * 3) + 103;
93
94 *h &= 255;
95 }
96 }
97
98 struct feature {
99 float v1, v2, v3; // mean, square, cube
100 int n;
101 };
102
103 static void
104 feature_init (struct feature *f)
105 {
106 f->v1 = 0.;
107 f->v2 = 0.;
108 f->v3 = 0.;
109 f->n = 0;
110 }
111
112 // didn't find an algorithm to neatly do mean, variance and skew in one pass.
113 // elmex ist schuld.
114 static void
115 feature_update_pass_1 (struct feature *f, unsigned int v)
116 {
117 f->v1 += v;
118 f->n += 1;
119 }
120
121 static void
122 feature_finish_pass_1 (struct feature *f)
123 {
124 if (f->n < 1)
125 return;
126
127 f->v1 /= f->n;
128 }
129
130 static void
131 feature_update_pass_2 (struct feature *f, unsigned int v)
132 {
133 float d = v - f->v1;
134
135 f->v2 += d * d;
136 f->v3 += d * d * d;
137 }
138
139 static void
140 feature_finish_pass_2 (struct feature *f)
141 {
142 if (f->n < 1)
143 return;
144
145 f->v2 /= f->n;
146 f->v3 /= f->n;
147
148 f->v1 /= 255.;
149 f->v2 /= 255. * 255.; f->v2 = sqrtf (f->v2);
150 f->v3 /= 255. * 255. * 255.; f->v3 = powf (fabsf (f->v3), 1./3.);
151 }
152
153 static guint32 a85_val;
154 static guint a85_cnt;
155 static guchar a85_buf[LINELENGTH], *a85_ptr;
156
157 static void
158 a85_init (void)
159 {
160 a85_cnt = 4;
161 a85_ptr = a85_buf;
162 }
163
164 static void
165 a85_push (PerlIO *fp, guchar c)
166 {
167 a85_val = a85_val << 8 | c;
168
169 if (!--a85_cnt)
170 {
171 a85_cnt = 4;
172 if (a85_val)
173 {
174 a85_ptr[4] = (a85_val % 85) + 33; a85_val /= 85;
175 a85_ptr[3] = (a85_val % 85) + 33; a85_val /= 85;
176 a85_ptr[2] = (a85_val % 85) + 33; a85_val /= 85;
177 a85_ptr[1] = (a85_val % 85) + 33; a85_val /= 85;
178 a85_ptr[0] = (a85_val ) + 33;
179
180 a85_ptr += 5;
181 }
182 else
183 *a85_ptr++ = 'z';
184
185 if (a85_ptr >= a85_buf + sizeof (a85_buf) - 7)
186 {
187 *a85_ptr++ = '\n';
188 PerlIO_write (fp, a85_buf, a85_ptr - a85_buf);
189 a85_ptr = a85_buf;
190 }
191 }
192
193 }
194
195 static void
196 a85_finish (PerlIO *fp)
197 {
198 while (a85_cnt != 4)
199 a85_push (fp, 0);
200
201 *a85_ptr++ = '~'; // probably buggy end-marker
202 *a85_ptr++ = '>'; // probably buggy end-marker
203 *a85_ptr++ = '\n';
204
205 PerlIO_write (fp, a85_buf, a85_ptr - a85_buf);
206 }
207
208 /////////////////////////////////////////////////////////////////////////////
209 // memory source for libjpeg
210
211 static void cv_ms_init (j_decompress_ptr cinfo)
212 {
213 }
214
215 static void cv_ms_term (j_decompress_ptr cinfo)
216 {
217 }
218
219 static boolean cv_ms_fill (j_decompress_ptr cinfo)
220 {
221 // unexpected EOF, warn and generate fake EOI marker
222
223 WARNMS (cinfo, JWRN_JPEG_EOF);
224
225 struct jpeg_source_mgr *src = (struct jpeg_source_mgr *)cinfo->src;
226
227 static const JOCTET eoi[] = { 0xFF, JPEG_EOI };
228
229 src->next_input_byte = eoi;
230 src->bytes_in_buffer = sizeof (eoi);
231
232 return TRUE;
233 }
234
235 static void cv_ms_skip (j_decompress_ptr cinfo, long num_bytes)
236 {
237 struct jpeg_source_mgr *src = (struct jpeg_source_mgr *)cinfo->src;
238
239 src->next_input_byte += num_bytes;
240 src->bytes_in_buffer -= num_bytes;
241 }
242
243 static void cv_jpeg_mem_src (j_decompress_ptr cinfo, void *buf, size_t buflen)
244 {
245 struct jpeg_source_mgr *src;
246
247 if (!cinfo->src)
248 cinfo->src = (struct jpeg_source_mgr *)
249 (*cinfo->mem->alloc_small) (
250 (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr)
251 );
252
253 src = (struct jpeg_source_mgr *)cinfo->src;
254 src->init_source = cv_ms_init;
255 src->fill_input_buffer = cv_ms_fill;
256 src->skip_input_data = cv_ms_skip;
257 src->resync_to_restart = jpeg_resync_to_restart;
258 src->term_source = cv_ms_term;
259 src->next_input_byte = (JOCTET *)buf;
260 src->bytes_in_buffer = buflen;
261 }
262
263 /////////////////////////////////////////////////////////////////////////////
264
265 /* great, the jpeg-xl reference implementaton requires us to parse bmff files */
266
267 struct bmff_box
268 {
269 char type[4];
270 const uint8_t *ptr;
271 size_t size;
272 };
273
274 static int
275 bmff_parse_box (struct bmff_box *box, const uint8_t **next_in, size_t *avail_in)
276 {
277 if (*avail_in < 8)
278 return 0;
279
280 box->size = ((*next_in)[0] << 24)
281 | ((*next_in)[1] << 16)
282 | ((*next_in)[2] << 8)
283 | ((*next_in)[3] );
284
285 if (box->size < 8)
286 return 0;
287
288 if (*avail_in < box->size)
289 return 0;
290
291 memcpy (box->type, *next_in + 4, 4);
292 box->ptr = *next_in + 8;
293
294 *next_in += box->size;
295 *avail_in -= box->size;
296
297 box->size -= 8;
298
299 return 1;
300 }
301
302 /////////////////////////////////////////////////////////////////////////////
303
304 MODULE = Gtk2::CV PACKAGE = Gtk2::CV
305
306 PROTOTYPES: ENABLE
307
308 # calculate the common prefix length of two strings
309 # missing function in perl. really :)
310 int
311 common_prefix_length (a, b)
312 unsigned char *a = (unsigned char *)SvPVutf8_nolen ($arg);
313 unsigned char *b = (unsigned char *)SvPVutf8_nolen ($arg);
314 CODE:
315 RETVAL = 0;
316
317 while (*a == *b && *a)
318 {
319 RETVAL += (*a & 0xc0) != 0x80;
320 a++, b++;
321 }
322
323 OUTPUT:
324 RETVAL
325
326 const char *
327 magic (SV *path_or_data)
328 ALIAS:
329 magic = 0
330 magic_mime = 1
331 magic_buffer = 2
332 magic_buffer_mime = 3
333 CODE:
334 {
335 STRLEN len;
336 char *data = SvPVbyte (path_or_data, len);
337
338 if (!magic_cookie[0])
339 {
340 magic_cookie[0] = magic_open (MAGIC_SYMLINK);
341 magic_cookie[1] = magic_open (MAGIC_SYMLINK | MAGIC_MIME_TYPE);
342 magic_load (magic_cookie[0], 0);
343 magic_load (magic_cookie[1], 0);
344 }
345
346 perlinterp_release ();
347
348 RETVAL = ix & 2
349 ? magic_buffer (magic_cookie[ix & 1], data, len)
350 : magic_file (magic_cookie[ix & 1], data);
351
352 perlinterp_acquire ();
353 }
354 OUTPUT:
355 RETVAL
356
357 # missing/broken in Gtk2 perl module
358
359 void
360 gdk_window_clear_hints (GdkWindow *window)
361 CODE:
362 gdk_window_set_geometry_hints (window, 0, 0);
363
364 gboolean
365 gdk_net_wm_supports (GdkAtom property)
366 CODE:
367 #if defined(GDK_WINDOWING_X11) && !defined(GDK_MULTIHEAD_SAFE)
368 RETVAL = gdk_net_wm_supports (property);
369 #else
370 RETVAL = 0;
371 #endif
372 OUTPUT:
373 RETVAL
374
375 GdkPixbuf_noinc *
376 dealpha_expose (GdkPixbuf *pb)
377 CODE:
378 perlinterp_release ();
379 {
380 int w = gdk_pixbuf_get_width (pb);
381 int h = gdk_pixbuf_get_height (pb);
382 int bpp = gdk_pixbuf_get_n_channels (pb);
383 int x, y, i;
384 guchar *src = gdk_pixbuf_get_pixels (pb), *dst;
385 int sstr = gdk_pixbuf_get_rowstride (pb), dstr;
386
387 RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, 0, 8, w, h);
388
389 dst = gdk_pixbuf_get_pixels (RETVAL);
390 dstr = gdk_pixbuf_get_rowstride (RETVAL);
391
392 for (x = 0; x < w; x++)
393 for (y = 0; y < h; y++)
394 for (i = 0; i < 3; i++)
395 dst[x * 3 + y * dstr + i] = src[x * bpp + y * sstr + i];
396 }
397 perlinterp_acquire ();
398 OUTPUT:
399 RETVAL
400
401 GdkPixbuf_noinc *
402 rotate (GdkPixbuf *pb, int angle)
403 CODE:
404 perlinterp_release ();
405 if (angle < 0)
406 angle += 360;
407 RETVAL = gdk_pixbuf_rotate_simple (pb, angle == 0 ? GDK_PIXBUF_ROTATE_NONE
408 : angle == 90 ? GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE
409 : angle == 180 ? GDK_PIXBUF_ROTATE_UPSIDEDOWN
410 : angle == 270 ? GDK_PIXBUF_ROTATE_CLOCKWISE
411 : angle);
412 perlinterp_acquire ();
413 OUTPUT:
414 RETVAL
415
416 const char *
417 filetype (SV *image_data)
418 CODE:
419 {
420 STRLEN data_len;
421 U8 *data = SvPVbyte (image_data, data_len);
422 static const unsigned char jxl_header[] = {
423 0, 0, 0, 0x0c, 0x4a, 0x58, 0x4c, 0x20,
424 0x0d, 0xa, 0x87, 0x0a, 0, 0, 0, 0x14,
425 0x66, 0x74, 0x79, 0x70, 0x6a, 0x78, 0x6c, 0x20,
426 0, 0, 0, 0, 0x6a, 0x78, 0x6c, 0x20,
427 };
428
429 if (data_len >= 20
430 && data[0] == 0xff
431 && data[1] == 0xd8
432 && data[2] == 0xff)
433 RETVAL = "image/jpeg";
434 else if (data_len >= 12
435 && data[ 0] == (U8)'R'
436 && data[ 1] == (U8)'I'
437 && data[ 2] == (U8)'F'
438 && data[ 3] == (U8)'F'
439 && data[ 8] == (U8)'W'
440 && data[ 9] == (U8)'E'
441 && data[10] == (U8)'B'
442 && data[11] == (U8)'P')
443 RETVAL = "image/webp";
444 else if (data_len >= 16
445 && data[ 0] == 0x89
446 && data[ 1] == (U8)'P'
447 && data[ 2] == (U8)'N'
448 && data[ 3] == (U8)'G'
449 && data[ 4] == 0x0d
450 && data[ 5] == 0x0a
451 && data[ 6] == 0x1a
452 && data[ 7] == 0x0a)
453 RETVAL = "image/png";
454 else if (data_len >= sizeof (jxl_header) && memcmp (data, jxl_header, sizeof (jxl_header)) == 0)
455 RETVAL = "image/jxl";
456 else if (data_len >= 13
457 && data[0] == 'G'
458 && data[1] == 'I'
459 && data[2] == 'F'
460 && data[3] == '8'
461 //&& (data[4] == '7' || data[4] == '9')
462 && data[5] == 'a')
463 {
464 RETVAL = "image/gif";
465
466 // now see if its animated - we require the netscape application header for this
467 int ofs = 13;
468
469 if (data[10] & 0x80)
470 ofs += (1 << ((data[10] & 7) + 1)) * 3;
471
472 if (data_len >= ofs + 2 + 1 + 11)
473 {
474 // skip a rare first graphic control extension block
475 // we assume there is exactly one block
476 if (data[ofs] == 0x21 && data[ofs + 1] == 0xf9)
477 ofs += 3 + data[ofs + 2] + 1;
478
479 if (data_len >= ofs + 2 + 1 + 11)
480 if (!memcmp (data + ofs, "\x21\xff\x0bNETSCAPE2.0", sizeof ("\x21\xff\x0bNETSCAPE2.0") - 1))
481 RETVAL = "video/gif";
482 }
483 }
484
485 else
486 XSRETURN_UNDEF;
487 }
488 OUTPUT:
489 RETVAL
490
491 GdkPixbuf_noinc *
492 decode_webp (SV *image_data, int thumbnail = 0, int iw = 0, int ih = 0)
493 CODE:
494 {
495 #if WEBP
496 STRLEN data_size;
497 int alpha;
498 WebPData data;
499 WebPDemuxer *demux;
500 WebPIterator iter;
501 WebPDecoderConfig config;
502 int inw, inh;
503
504 data.bytes = (uint8_t *)SvPVbyte (image_data, data_size);
505 data.size = data_size;
506
507 perlinterp_release ();
508
509 RETVAL = 0;
510
511 if (!(demux = WebPDemux (&data)))
512 goto err_demux;
513
514 if (!WebPDemuxGetFrame (demux, 1, &iter))
515 goto err_iter;
516
517 if (!WebPInitDecoderConfig (&config))
518 goto err_iter;
519
520 config.options.use_threads = 1;
521
522 if (WebPGetFeatures (iter.fragment.bytes, iter.fragment.size, &config.input) != VP8_STATUS_OK)
523 goto err_iter;
524
525 inw = config.input.width;
526 inh = config.input.height;
527
528 if (thumbnail)
529 {
530 if (inw * ih > inh * iw)
531 ih = (iw * inh + inw - 1) / inw;
532 else
533 iw = (ih * inw + inh - 1) / inh;
534
535 config.options.bypass_filtering = 1;
536 config.options.no_fancy_upsampling = 1;
537
538 config.options.use_scaling = 1;
539 config.options.scaled_width = iw;
540 config.options.scaled_height = ih;
541 }
542 else
543 {
544 iw = inw;
545 ih = inh;
546 }
547
548 alpha = !!config.input.has_alpha;
549
550 RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, alpha, 8, iw, ih);
551 if (!RETVAL)
552 goto err_iter;
553
554 config.output.colorspace = alpha ? MODE_RGBA : MODE_RGB;
555 config.output.u.RGBA.rgba = gdk_pixbuf_get_pixels (RETVAL);
556 config.output.u.RGBA.stride = gdk_pixbuf_get_rowstride (RETVAL);
557 config.output.u.RGBA.size = gdk_pixbuf_get_byte_length (RETVAL);
558 config.output.is_external_memory = 1;
559
560 if (WebPDecode (iter.fragment.bytes, iter.fragment.size, &config) != VP8_STATUS_OK)
561 {
562 g_object_unref (RETVAL);
563 RETVAL = 0;
564 goto err_iter;
565 }
566
567 err_iter:
568 WebPDemuxReleaseIterator (&iter);
569 err_demux:
570 WebPDemuxDelete (demux);
571
572 perlinterp_acquire ();
573 #else
574 croak ("load_webp: webp not enabled at compile time");
575 #endif
576 }
577 OUTPUT:
578 RETVAL
579
580 GdkPixbuf_noinc *
581 decode_jxl (SV *image_data, int thumbnail = 0, int iw = 0, int ih = 0)
582 CODE:
583 {
584 #if JXL
585 JxlDecoder *dec = JxlDecoderCreate (0);
586 JxlBasicInfo info;
587 const uint8_t *next_in = (uint8_t *)SvPVbyte_nolen (image_data);
588 size_t avail_in = SvCUR (image_data);
589 const char *error = 0;
590 void *runner = 0;
591 struct bmff_box box;
592
593 perlinterp_release ();
594
595 RETVAL = 0;
596
597 error = "JxlDecoderCreate failed";
598 if (!dec)
599 goto done;
600
601 runner = JxlThreadParallelRunnerCreate (0, JxlThreadParallelRunnerDefaultNumWorkerThreads ());
602
603 error = "JxlDecoderSetParallelRunner failed";
604 if (JxlDecoderSetParallelRunner (dec, JxlThreadParallelRunner, runner) != JXL_DEC_SUCCESS)
605 goto done;
606
607 error = "JxlDecoderSubscribeEvents failed";
608 if (JxlDecoderSubscribeEvents (dec, JXL_DEC_FULL_IMAGE | JXL_DEC_BASIC_INFO) != JXL_DEC_SUCCESS)
609 goto done;
610
611 error = "JxlDecoderSetInput failed";
612 if (JxlDecoderSetInput (dec, next_in, avail_in) != JXL_DEC_SUCCESS)
613 goto done;
614
615 for (;;)
616 {
617 JxlDecoderStatus status = JxlDecoderProcessInput (dec);
618
619 printf ("status %d\n",status);
620
621 switch (status)
622 {
623 case JXL_DEC_ERROR:
624 error = "JxlDecoderProcessInput failed";
625 goto done;
626
627 case JXL_DEC_NEED_MORE_INPUT:
628 error = "incomplete file";
629 goto done;
630
631 case JXL_DEC_SUCCESS:
632 goto done;
633
634 case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
635 {
636 error = "JxlDecoderGetBasicInfo failed";
637 if (JxlDecoderGetBasicInfo (dec, &info) != JXL_DEC_SUCCESS)
638 goto done;
639
640 RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, !!info.alpha_bits, 8, info.xsize, info.ysize);
641 error = "unable to allocate pixbuf";
642 if (!RETVAL)
643 goto done;
644
645 JxlPixelFormat format = {
646 info.alpha_bits ? 4 : 3,
647 JXL_TYPE_UINT8,
648 JXL_NATIVE_ENDIAN,
649 gdk_pixbuf_get_rowstride (RETVAL)
650 };
651
652 error = "JxlDecoderSetImageOutBuffer failed";
653 if (JxlDecoderSetImageOutBuffer (
654 dec,
655 &format,
656 gdk_pixbuf_get_pixels (RETVAL),
657 gdk_pixbuf_get_byte_length (RETVAL)
658 ) != JXL_DEC_SUCCESS)
659 goto done;
660 }
661 break;
662
663 default:
664 error = "unexpected event";
665 goto done;
666 }
667 }
668
669 done:
670 if (dec)
671 JxlDecoderDestroy (dec);
672
673 if (runner)
674 JxlThreadParallelRunnerDestroy (runner);
675
676 perlinterp_acquire ();
677
678 if (error)
679 {
680 if (RETVAL)
681 g_object_unref (RETVAL);
682
683 croak ("load_jxl: %s", error);
684 }
685 #else
686 croak ("load_jxl: jpeg-xl not enabled at compile time");
687 #endif
688 }
689 OUTPUT:
690 RETVAL
691
692 GdkPixbuf_noinc *
693 decode_jpeg (SV *image_data, int thumbnail = 0, int iw = 0, int ih = 0)
694 CODE:
695 {
696 struct jpeg_decompress_struct cinfo;
697 struct jpg_err_mgr jerr;
698 int rs;
699 volatile GdkPixbuf *pb = 0;
700 STRLEN data_len;
701 guchar *data = SvPVbyte (image_data, data_len);
702
703 RETVAL = 0;
704
705 perlinterp_release ();
706
707 cinfo.err = jpeg_std_error (&jerr.err);
708
709 jerr.err.error_exit = cv_error_exit;
710 jerr.err.output_message = cv_error_output;
711
712 if ((rs = setjmp (jerr.setjmp_buffer)))
713 {
714 jpeg_destroy_decompress (&cinfo);
715
716 if (pb)
717 g_object_unref ((gpointer)pb);
718
719 perlinterp_acquire ();
720 XSRETURN_UNDEF;
721 }
722
723 if (!data_len)
724 longjmp (jerr.setjmp_buffer, 4);
725
726 jpeg_create_decompress (&cinfo);
727 cv_jpeg_mem_src (&cinfo, data, data_len);
728
729 jpeg_read_header (&cinfo, TRUE);
730
731 cinfo.dct_method = JDCT_DEFAULT;
732 cinfo.do_fancy_upsampling = FALSE; /* worse quality, but nobody compained so far, and gdk-pixbuf does the same */
733 cinfo.do_block_smoothing = FALSE;
734 cinfo.out_color_space = JCS_RGB;
735 cinfo.quantize_colors = FALSE;
736
737 cinfo.scale_num = 1;
738 cinfo.scale_denom = 1;
739
740 jpeg_calc_output_dimensions (&cinfo);
741
742 if (thumbnail)
743 {
744 cinfo.dct_method = JDCT_FASTEST;
745 cinfo.do_fancy_upsampling = FALSE;
746
747 while (cinfo.scale_denom < 8
748 && cinfo.output_width >= iw*4
749 && cinfo.output_height >= ih*4)
750 {
751 cinfo.scale_denom <<= 1;
752 jpeg_calc_output_dimensions (&cinfo);
753 }
754 }
755
756 if (cinfo.output_components != 3)
757 longjmp (jerr.setjmp_buffer, 3);
758
759 if (cinfo.jpeg_color_space == JCS_YCCK || cinfo.jpeg_color_space == JCS_CMYK)
760 {
761 cinfo.out_color_space = JCS_CMYK;
762 cinfo.output_components = 4;
763 }
764
765 pb = RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, cinfo.output_components == 4, 8, cinfo.output_width, cinfo.output_height);
766 if (!RETVAL)
767 longjmp (jerr.setjmp_buffer, 2);
768
769 data = gdk_pixbuf_get_pixels (RETVAL);
770 rs = gdk_pixbuf_get_rowstride (RETVAL);
771
772 jpeg_start_decompress (&cinfo);
773
774 while (cinfo.output_scanline < cinfo.output_height)
775 {
776 int remaining = cinfo.output_height - cinfo.output_scanline;
777 JSAMPROW rp[4];
778
779 rp [0] = data + cinfo.output_scanline * rs;
780 rp [1] = (guchar *)rp [0] + rs;
781 rp [2] = (guchar *)rp [1] + rs;
782 rp [3] = (guchar *)rp [2] + rs;
783
784 jpeg_read_scanlines (&cinfo, rp, remaining < 4 ? remaining : 4);
785 }
786
787 if (cinfo.out_color_space == JCS_CMYK)
788 {
789 guchar *end = data + cinfo.output_height * rs;
790
791 while (data < end)
792 {
793 U32 c = data [0];
794 U32 m = data [1];
795 U32 y = data [2];
796 U32 k = data [3];
797
798 if (0)
799 if (cinfo.Adobe_transform == 2)
800 {
801 c ^= 0xff;
802 m ^= 0xff;
803 y ^= 0xff;
804 k ^= 0xff;
805 }
806
807 data [0] = (c * k + 0x80) / 0xff;
808 data [1] = (m * k + 0x80) / 0xff;
809 data [2] = (y * k + 0x80) / 0xff;
810 data [3] = 0xff;
811
812 data += 4;
813 }
814 }
815
816 jpeg_finish_decompress (&cinfo);
817 jpeg_destroy_decompress (&cinfo);
818 perlinterp_acquire ();
819 }
820 OUTPUT:
821 RETVAL
822
823 void
824 compare (GdkPixbuf *a, GdkPixbuf *b)
825 PPCODE:
826 perlinterp_release ();
827 {
828 int w = gdk_pixbuf_get_width (a);
829 int h = gdk_pixbuf_get_height (a);
830
831 int sa = gdk_pixbuf_get_rowstride (a);
832 int sb = gdk_pixbuf_get_rowstride (b);
833
834 guchar *pa = gdk_pixbuf_get_pixels (a);
835 guchar *pb = gdk_pixbuf_get_pixels (b);
836
837 int x, y;
838
839 assert (w == gdk_pixbuf_get_width (b));
840 assert (h == gdk_pixbuf_get_height (b));
841
842 assert (gdk_pixbuf_get_n_channels (a) == 3);
843 assert (gdk_pixbuf_get_n_channels (b) == 3);
844
845 double diff = 0.;
846 int peak = 0;
847
848 if (w && h)
849 for (y = 0; y < h; y++)
850 {
851 guchar *pa_ = pa + y * sa;
852 guchar *pb_ = pb + y * sb;
853
854 for (x = 0; x < w; x++)
855 {
856 int d;
857
858 d = ((int)*pa_++) - ((int)*pb_++); diff += d*d; peak = MAX (peak, abs (d));
859 d = ((int)*pa_++) - ((int)*pb_++); diff += d*d; peak = MAX (peak, abs (d));
860 d = ((int)*pa_++) - ((int)*pb_++); diff += d*d; peak = MAX (peak, abs (d));
861 }
862 }
863
864 perlinterp_acquire ();
865
866 EXTEND (SP, 2);
867 PUSHs (sv_2mortal (newSVnv (sqrt (diff / (w * h * 3. * 255. * 255.)))));
868 PUSHs (sv_2mortal (newSVnv (peak / 255.)));
869 }
870
871 #############################################################################
872
873 MODULE = Gtk2::CV PACKAGE = Gtk2::CV::Schnauzer
874
875 # currently only works for filenames (octet strings)
876
877 SV *
878 foldcase (SV *pathsv)
879 PROTOTYPE: $
880 CODE:
881 {
882 STRLEN plen;
883 U8 *path = (U8 *)SvPV (pathsv, plen);
884 U8 *pend = path + plen;
885 U8 dst [plen * 8 * 3], *dstp = dst;
886
887 while (path < pend)
888 {
889 U8 ch = *path;
890
891 if (ch >= 'a' && ch <= 'z')
892 *dstp++ = *path++;
893 else if (ch >= 'A' && ch <= 'Z')
894 *dstp++ = *path++ + ('a' - 'A');
895 else if (ch >= '0' && ch <= '9')
896 {
897 /* version sort, up to 8 digits */
898 STRLEN el, nl = 0;
899 while (*path >= '0' && *path <= '9' && path < pend)
900 path++, nl++;
901
902 for (el = nl; el < 8; el++)
903 *dstp++ = '0';
904
905 memcpy (dstp, path - nl, nl);
906 dstp += nl;
907 }
908 else
909 *dstp++ = *path++;
910 #if 0
911 else
912 {
913 STRLEN cl;
914 to_utf8_fold (path, dstp, &cl);
915 dstp += cl;
916 path += is_utf8_char (path);
917 }
918 #endif
919 }
920
921 RETVAL = newSVpvn ((const char *)dst, dstp - dst);
922 }
923 OUTPUT:
924 RETVAL
925
926 GdkPixbuf_noinc *
927 p7_to_pb (int w, int h, SV *src_sv)
928 PROTOTYPE: @
929 CODE:
930 {
931 int x, y;
932 guchar *dst, *d;
933 int dstr;
934 guchar *src = (guchar *)SvPVbyte_nolen (src_sv);
935
936 RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, 0, 8, w, h);
937 dst = gdk_pixbuf_get_pixels (RETVAL);
938 dstr = gdk_pixbuf_get_rowstride (RETVAL);
939
940 for (y = 0; y < h; y++)
941 for (d = dst + y * dstr, x = 0; x < w; x++)
942 {
943 *d++ = (((*src >> 5) & 7) * 255 + 4) / 7;
944 *d++ = (((*src >> 2) & 7) * 255 + 4) / 7;
945 *d++ = (((*src >> 0) & 3) * 255 + 2) / 3;
946
947 src++;
948 }
949 }
950 OUTPUT:
951 RETVAL
952
953 #############################################################################
954
955 MODULE = Gtk2::CV PACKAGE = Gtk2::CV::PostScript
956
957 void
958 dump_ascii85 (PerlIO *fp, GdkPixbuf *pb)
959 CODE:
960 {
961 int w = gdk_pixbuf_get_width (pb);
962 int h = gdk_pixbuf_get_height (pb);
963 int x, y, i;
964 guchar *dst;
965 int bpp = gdk_pixbuf_get_n_channels (pb);
966 guchar *src = gdk_pixbuf_get_pixels (pb);
967 int sstr = gdk_pixbuf_get_rowstride (pb);
968
969 a85_init ();
970
971 for (y = 0; y < h; y++)
972 for (x = 0; x < w; x++)
973 for (i = 0; i < (bpp < 3 ? 1 : 3); i++)
974 a85_push (fp, src [x * bpp + y * sstr + i]);
975
976 a85_finish (fp);
977 }
978
979 void
980 dump_binary (PerlIO *fp, GdkPixbuf *pb)
981 CODE:
982 {
983 int w = gdk_pixbuf_get_width (pb);
984 int h = gdk_pixbuf_get_height (pb);
985 int x, y, i;
986 guchar *dst;
987 int bpp = gdk_pixbuf_get_n_channels (pb);
988 guchar *src = gdk_pixbuf_get_pixels (pb);
989 int sstr = gdk_pixbuf_get_rowstride (pb);
990
991 for (y = 0; y < h; y++)
992 for (x = 0; x < w; x++)
993 for (i = 0; i < (bpp < 3 ? 1 : 3); i++)
994 PerlIO_putc (fp, src [x * bpp + y * sstr + i]);
995 }
996
997 #############################################################################
998
999 MODULE = Gtk2::CV PACKAGE = Gtk2::CV
1000
1001 SV *
1002 pb_to_hv84 (GdkPixbuf *pb)
1003 CODE:
1004 {
1005 int w = gdk_pixbuf_get_width (pb);
1006 int h = gdk_pixbuf_get_height (pb);
1007 int x, y;
1008 guchar *dst;
1009 int bpp = gdk_pixbuf_get_n_channels (pb);
1010 guchar *src = gdk_pixbuf_get_pixels (pb);
1011 int sstr = gdk_pixbuf_get_rowstride (pb);
1012
1013 RETVAL = newSV (6 * 8 * 12 / 8);
1014 SvPOK_only (RETVAL);
1015 SvCUR_set (RETVAL, 6 * 8 * 12 / 8);
1016
1017 dst = (guchar *)SvPVX (RETVAL);
1018
1019 /* some primitive error distribution + random dithering */
1020
1021 for (y = 0; y < h; y++)
1022 {
1023 guchar *p = src + y * sstr;
1024
1025 for (x = 0; x < w; x += 2)
1026 {
1027 unsigned int r, g, b, h, s, v, H, V1, V2;
1028
1029 if (bpp == 3)
1030 r = *p++, g = *p++, b = *p++;
1031 else if (bpp == 1)
1032 r = g = b = *p++;
1033 else
1034 abort ();
1035
1036 rgb_to_hsv (r, g, b, &h, &s, &v);
1037
1038 H = (h * 15 / 255) << 4;
1039 V1 = v;
1040
1041 if (bpp == 3)
1042 r = *p++, g = *p++, b = *p++;
1043 else if (bpp == 1)
1044 r = g = b = *p++;
1045 else
1046 abort ();
1047
1048 rgb_to_hsv (r, g, b, &h, &s, &v);
1049
1050 H |= h * 15 / 255;
1051 V2 = v;
1052
1053 *dst++ = H;
1054 *dst++ = V1;
1055 *dst++ = V2;
1056 }
1057 }
1058 }
1059 OUTPUT:
1060 RETVAL
1061
1062 SV *
1063 hv84_to_av (unsigned char *hv84)
1064 CODE:
1065 {
1066 int i = 72 / 3;
1067 AV *av = newAV ();
1068
1069 RETVAL = (SV *)newRV_noinc ((SV *)av);
1070 while (i--)
1071 {
1072 int h = *hv84++;
1073 int v1 = *hv84++;
1074 int v2 = *hv84++;
1075
1076 av_push (av, newSViv (v1));
1077 av_push (av, newSViv ((h >> 4) * 255 / 15));
1078 av_push (av, newSViv (v2));
1079 av_push (av, newSViv ((h & 15) * 255 / 15));
1080 }
1081 }
1082 OUTPUT:
1083 RETVAL
1084
1085 #############################################################################
1086
1087 MODULE = Gtk2::CV PACKAGE = Gtk2::CV::Plugin::RCluster
1088
1089 SV *
1090 extract_features (SV *ar)
1091 CODE:
1092 {
1093 int i;
1094 AV *av, *result;
1095
1096 if (!SvROK (ar) || SvTYPE (SvRV (ar)) != SVt_PVAV)
1097 croak ("Not an array ref as first argument to extract_features");
1098
1099 av = (AV *) SvRV (ar);
1100 result = newAV ();
1101
1102 for (i = 0; i <= av_len (av); ++i)
1103 {
1104 SV *sv = *av_fetch (av, i, 1);
1105 SV *histsv = newSV (9 * sizeof (float) + 1);
1106
1107 SvPOK_on (histsv);
1108 SvCUR_set (histsv, 9 * sizeof (float));
1109 float *hist = (float *)SvPVX (histsv);
1110
1111 struct feature f_h, f_s, f_v;
1112 feature_init (&f_h);
1113 feature_init (&f_s);
1114 feature_init (&f_v);
1115
1116 {
1117 STRLEN len;
1118 unsigned char *buf = (unsigned char *)SvPVbyte (sv, len);
1119 while (len >= 3)
1120 {
1121 unsigned int r, g, b, h, s, v;
1122 r = *buf++; g = *buf++; b = *buf++;
1123 rgb_to_hsv (r, g, b, &h, &s, &v);
1124
1125 feature_update_pass_1 (&f_h, h);
1126 feature_update_pass_1 (&f_s, s);
1127 feature_update_pass_1 (&f_v, v);
1128
1129 len -= 3;
1130 }
1131
1132 feature_finish_pass_1 (&f_h);
1133 feature_finish_pass_1 (&f_s);
1134 feature_finish_pass_1 (&f_v);
1135 }
1136
1137 {
1138 STRLEN len;
1139 unsigned char *buf = (unsigned char *)SvPVbyte (sv, len);
1140 while (len >= 3)
1141 {
1142 unsigned int r, g, b, h, s, v;
1143 r = *buf++; g = *buf++; b = *buf++;
1144 rgb_to_hsv (r, g, b, &h, &s, &v);
1145
1146 feature_update_pass_2 (&f_h, h);
1147 feature_update_pass_2 (&f_s, s);
1148 feature_update_pass_2 (&f_v, v);
1149
1150 len -= 3;
1151 }
1152
1153 feature_finish_pass_2 (&f_h);
1154 feature_finish_pass_2 (&f_s);
1155 feature_finish_pass_2 (&f_v);
1156 }
1157
1158 hist [0] = f_h.v1 * 2.; hist [1] = f_h.v2 * 2.; hist [2] = f_h.v3 * 2.;
1159 hist [3] = f_s.v1 ; hist [4] = f_s.v2 ; hist [5] = f_s.v3 ;
1160 hist [6] = f_v.v1 * .5; hist [7] = f_v.v2 * .5; hist [8] = f_v.v3 * .5;
1161
1162 av_push (result, histsv);
1163 }
1164
1165 RETVAL = newRV_noinc ((SV *)result);
1166 }
1167 OUTPUT:
1168 RETVAL
1169