Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

improved thread safety for Imager

  • Loading branch information...
commit 02c63035876648ef730cbde539cd75c53236a032 2 parents 41c938e + 7d99bed
Tony Cook authored

Showing 83 changed files with 3,420 additions and 1,091 deletions. Show diff stats Hide diff stats

  1. +15 0 Changes
  2. +5 0 FT2/Changes
  3. +50 1 FT2/FT2.pm
  4. +1 0  FT2/FT2.xs
  5. +1 0  FT2/MANIFEST
  6. +1 0  FT2/Makefile.PL
  7. +60 14 FT2/freetyp2.c
  8. +1 1  FT2/imft2.h
  9. +59 0 FT2/t/t20thread.t
  10. +1 1  GIF/GIF.pm
  11. +1 0  GIF/GIF.xs
  12. +41 4 GIF/imgif.c
  13. +1 0  GIF/imgif.h
  14. +6 9 Imager.pm
  15. +94 1 Imager.xs
  16. +1 1  JPEG/JPEG.pm
  17. +10 46 JPEG/imjpeg.c
  18. +9 1 MANIFEST
  19. +29 4 Makefile.PL
  20. +9 0 T1/Changes
  21. +1 0  T1/MANIFEST
  22. +64 27 T1/T1.pm
  23. +35 27 T1/T1.xs
  24. +194 50 T1/imt1.c
  25. +13 11 T1/imt1.h
  26. +54 27 T1/t/t10type1.t
  27. +1 0  T1/typemap
  28. +6 0 TIFF/Changes
  29. +1 1  TIFF/TIFF.pm
  30. +1 0  TIFF/TIFF.xs
  31. +204 15 TIFF/imtiff.c
  32. +1 0  TIFF/imtiff.h
  33. +3 3 apidocs.perl
  34. BIN  bench/largish.png
  35. BIN  bench/largish.tif
  36. +46 0 bench/tifthread.pl
  37. +39 20 bmp.c
  38. +284 0 context.c
  39. +5 3 conv.im
  40. +7 5 convert.im
  41. +4 2 datatypes.c
  42. +79 24 draw.c
  43. +8 4 dynaload.c
  44. +68 124 error.c
  45. +28 1 ext.h
  46. +1 0  fills.c
  47. +31 19 filters.im
  48. +5 4 flip.im
  49. +59 33 font.c → fontft1.c
  50. +3 1 gaussian.im
  51. +5 2 hlines.c
  52. +83 46 image.c
  53. +61 115 imager.h
  54. +26 0 imageri.h
  55. +35 0 imdatatypes.h
  56. +7 9 imerror.h
  57. +52 17 imext.c
  58. +43 59 imext.h
  59. +34 18 imexttypes.h
  60. +45 21 img16.c
  61. +41 61 img8.c
  62. +34 17 imgdouble.c
  63. +88 0 immacros.h
  64. +102 50 iolayer.c
  65. +4 4 iolayer.h
  66. +2 0  iolayert.h
  67. +219 13 lib/Imager/API.pod
  68. +310 65 lib/Imager/APIRef.pod
  69. +76 0 lib/Imager/Threads.pod
  70. +43 38 limits.c
  71. +82 42 log.c
  72. +19 2 log.h
  73. +14 4 maskimg.c
  74. +39 0 mutexnull.c
  75. +44 0 mutexpthr.c
  76. +94 0 mutexwin.c
  77. +32 13 palimg.c
  78. +8 8 plug.h
  79. +36 1 t/t82inline.t
  80. +92 0 t/t84inlinectx.t
  81. +2 0  t/x20spell.t
  82. +6 2 t/x90cmpversion.t
  83. +2 0  typemap.local
15 Changes
... ... @@ -1,5 +1,20 @@
1 1 Imager release history. Older releases can be found in Changes.old
2 2
  3 + - improved thread safety
  4 + - the internal error stack and log file handle are now in a per-thread
  5 + context object
  6 + - JPEG now captures IPTC information in a thread-safe way
  7 + - avoid globals where possible for warning capture in libtiff
  8 + - use a mutex to avoid re-entering thread-unsafe giflib
  9 + - use a mutex to avoid re-entering thread-unsafe tifflib
  10 + - use a mutex to avoid re-entering thread-unsafe T1Lib
  11 + - use a library handle per thread for freetype 2.
  12 + - use an engine handle per thread for freetype 1.x.
  13 +
  14 + - T1:
  15 + - improve error reporting
  16 + - provide better control of the level of anti-aliasing
  17 +
3 18 Imager 0.92 - 14 Aug 2012
4 19 ===========
5 20
5 FT2/Changes
... ... @@ -1,6 +1,11 @@
1 1 Imager-Font-FT2 0.85
2 2 ====================
3 3
  4 + - improve thread safety
  5 +
  6 +Imager-Font-FT2 0.85
  7 +====================
  8 +
4 9 - no longer fallback to using DynaLoader to load the XS code
5 10 https://rt.cpan.org/Ticket/Display.html?id=75560
6 11
51 FT2/FT2.pm
... ... @@ -1,11 +1,12 @@
1 1 package Imager::Font::FT2;
2 2 use strict;
3 3 use Imager;
  4 +use Scalar::Util ();
4 5 use vars qw($VERSION @ISA);
5 6 @ISA = qw(Imager::Font);
6 7
7 8 BEGIN {
8   - $VERSION = "0.85";
  9 + $VERSION = "0.86";
9 10
10 11 require XSLoader;
11 12 XSLoader::load('Imager::Font::FT2', $VERSION);
@@ -50,6 +51,10 @@ sub new {
50 51
51 52 sub _draw {
52 53 my $self = shift;
  54 +
  55 + $self->_valid
  56 + or return;
  57 +
53 58 my %input = @_;
54 59 if (exists $input{channel}) {
55 60 i_ft2_cp($self->{id}, $input{image}{IMG}, $input{'x'}, $input{'y'},
@@ -69,12 +74,19 @@ sub _bounding_box {
69 74 my $self = shift;
70 75 my %input = @_;
71 76
  77 + $self->_valid
  78 + or return;
  79 +
72 80 return i_ft2_bbox($self->{id}, $input{size}, $input{sizew}, $input{string},
73 81 $input{utf8});
74 82 }
75 83
76 84 sub dpi {
77 85 my $self = shift;
  86 +
  87 + $self->_valid
  88 + or return;
  89 +
78 90 my @old = i_ft2_getdpi($self->{id});
79 91 if (@_) {
80 92 my %hsh = @_;
@@ -97,12 +109,18 @@ sub dpi {
97 109 sub hinting {
98 110 my ($self, %opts) = @_;
99 111
  112 + $self->_valid
  113 + or return;
  114 +
100 115 i_ft2_sethinting($self->{id}, $opts{hinting} || 0);
101 116 }
102 117
103 118 sub _transform {
104 119 my $self = shift;
105 120
  121 + $self->_valid
  122 + or return;
  123 +
106 124 my %hsh = @_;
107 125 my $matrix = $hsh{matrix} or return undef;
108 126
@@ -117,6 +135,9 @@ sub utf8 {
117 135 sub has_chars {
118 136 my ($self, %hsh) = @_;
119 137
  138 + $self->_valid
  139 + or return;
  140 +
120 141 unless (defined $hsh{string} && length $hsh{string}) {
121 142 $Imager::ERRSTR = "No string supplied to \$font->has_chars()";
122 143 return;
@@ -128,6 +149,9 @@ sub has_chars {
128 149 sub face_name {
129 150 my ($self) = @_;
130 151
  152 + $self->_valid
  153 + or return;
  154 +
131 155 i_ft2_face_name($self->{id});
132 156 }
133 157
@@ -138,6 +162,9 @@ sub can_glyph_names {
138 162 sub glyph_names {
139 163 my ($self, %input) = @_;
140 164
  165 + $self->_valid
  166 + or return;
  167 +
141 168 my $string = $input{string};
142 169 defined $string
143 170 or return Imager->_set_error("no string parameter passed to glyph_names");
@@ -154,12 +181,18 @@ sub glyph_names {
154 181 sub is_mm {
155 182 my ($self) = @_;
156 183
  184 + $self->_valid
  185 + or return;
  186 +
157 187 i_ft2_is_multiple_master($self->{id});
158 188 }
159 189
160 190 sub mm_axes {
161 191 my ($self) = @_;
162 192
  193 + $self->_valid
  194 + or return;
  195 +
163 196 my ($num_axis, $num_design, @axes) =
164 197 i_ft2_get_multiple_masters($self->{id})
165 198 or return Imager->_set_error(Imager->_error_as_msg);
@@ -170,6 +203,9 @@ sub mm_axes {
170 203 sub set_mm_coords {
171 204 my ($self, %opts) = @_;
172 205
  206 + $self->_valid
  207 + or return;
  208 +
173 209 $opts{coords}
174 210 or return Imager->_set_error("Missing coords parameter");
175 211 ref($opts{coords}) && $opts{coords} =~ /ARRAY\(0x[\da-f]+\)$/
@@ -180,6 +216,19 @@ sub set_mm_coords {
180 216
181 217 return 1;
182 218 }
  219 +
  220 +# objects may be invalidated on thread creation (or Win32 fork emulation)
  221 +sub _valid {
  222 + my $self = shift;
  223 +
  224 + unless ($self->{id} && Scalar::Util::blessed($self->{id})) {
  225 + Imager->_set_error("font object was created in another thread");
  226 + return;
  227 + }
  228 +
  229 + return 1;
  230 +}
  231 +
183 232 1;
184 233
185 234 __END__
1  FT2/FT2.xs
@@ -356,3 +356,4 @@ i_ft2_set_mm_coords(handle, ...)
356 356
357 357 BOOT:
358 358 PERL_INITIALIZE_IMAGER_CALLBACKS;
  359 + i_ft2_start();
1  FT2/MANIFEST
@@ -16,4 +16,5 @@ MANIFEST This list of files
16 16 MANIFEST.SKIP
17 17 README
18 18 t/t10ft2.t
  19 +t/t20thread.t
19 20 typemap
1  FT2/Makefile.PL
@@ -61,6 +61,7 @@ else {
61 61 $opts{PREREQ_PM} =
62 62 {
63 63 @Imager_req,
  64 + 'Scalar::Util' => 1.00,
64 65 XSLoader => 0,
65 66 };
66 67 }
74 FT2/freetyp2.c
@@ -49,40 +49,84 @@ Truetype, Type1 and Windows FNT.
49 49
50 50 static void ft2_push_message(int code);
51 51
52   -static int ft2_initialized = 0;
53   -static FT_Library library;
  52 +static void ft2_final(void *);
  53 +
  54 +static im_slot_t slot = -1;
  55 +
  56 +typedef struct {
  57 + int initialized;
  58 + FT_Library library;
  59 + im_context_t ctx;
  60 +} ft2_state;
54 61
55 62 static i_img_dim i_min(i_img_dim a, i_img_dim b);
56 63 static i_img_dim i_max(i_img_dim a, i_img_dim b);
57 64
  65 +void
  66 +i_ft2_start(void) {
  67 + if (slot == -1)
  68 + slot = im_context_slot_new(ft2_final);
  69 +}
  70 +
58 71 /*
59 72 =item i_ft2_init(void)
60 73
61 74 Initializes the Freetype 2 library.
62 75
63   -Returns true on success, false on failure.
  76 +Returns ft2_state * on success or NULL on failure.
64 77
65 78 =cut
66 79 */
67   -int
  80 +
  81 +static ft2_state *
68 82 i_ft2_init(void) {
69 83 FT_Error error;
  84 + im_context_t ctx = im_get_context();
  85 + ft2_state *ft2 = im_context_slot_get(ctx, slot);
  86 +
  87 + if (ft2 == NULL) {
  88 + ft2 = mymalloc(sizeof(ft2_state));
  89 + ft2->initialized = 0;
  90 + ft2->library = NULL;
  91 + ft2->ctx = ctx;
  92 + im_context_slot_set(ctx, slot, ft2);
  93 + mm_log((1, "created FT2 state %p for context %p\n", ft2, ctx));
  94 + }
70 95
71 96 i_clear_error();
72   - error = FT_Init_FreeType(&library);
73   - if (error) {
74   - ft2_push_message(error);
75   - i_push_error(0, "Initializing Freetype2");
76   - return 0;
  97 + if (!ft2->initialized) {
  98 + error = FT_Init_FreeType(&ft2->library);
  99 + if (error) {
  100 + ft2_push_message(error);
  101 + i_push_error(0, "Initializing Freetype2");
  102 + return NULL;
  103 + }
  104 + mm_log((1, "initialized FT2 state %p\n", ft2));
  105 +
  106 + ft2->initialized = 1;
77 107 }
78 108
79   - ft2_initialized = 1;
  109 + return ft2;
  110 +}
  111 +
  112 +static void
  113 +ft2_final(void *state) {
  114 + ft2_state *ft2 = state;
  115 +
  116 + if (ft2->initialized) {
  117 + mm_log((1, "finalizing FT2 state %p\n", state));
  118 + FT_Done_FreeType(ft2->library);
  119 + ft2->library = NULL;
  120 + ft2->initialized = 0;
  121 + }
80 122
81   - return 1;
  123 + mm_log((1, "freeing FT2 state %p\n", state));
  124 + myfree(state);
82 125 }
83 126
84 127 struct FT2_Fonthandle {
85 128 FT_Face face;
  129 + ft2_state *state;
86 130 int xdpi, ydpi;
87 131 int hint;
88 132 FT_Encoding encoding;
@@ -138,14 +182,15 @@ i_ft2_new(const char *name, int index) {
138 182 int i, j;
139 183 FT_Encoding encoding;
140 184 int score;
  185 + ft2_state *ft2;
141 186
142 187 mm_log((1, "i_ft2_new(name %p, index %d)\n", name, index));
143 188
144   - if (!ft2_initialized && !i_ft2_init())
  189 + if ((ft2 = i_ft2_init()) == NULL)
145 190 return NULL;
146 191
147 192 i_clear_error();
148   - error = FT_New_Face(library, name, index, &face);
  193 + error = FT_New_Face(ft2->library, name, index, &face);
149 194 if (error) {
150 195 ft2_push_message(error);
151 196 i_push_error(error, "Opening face");
@@ -173,6 +218,7 @@ i_ft2_new(const char *name, int index) {
173 218
174 219 result = mymalloc(sizeof(FT2_Fonthandle));
175 220 result->face = face;
  221 + result->state = ft2;
176 222 result->xdpi = result->ydpi = 72;
177 223 result->encoding = encoding;
178 224
@@ -244,7 +290,7 @@ i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi) {
244 290 if (xdpi > 0 && ydpi > 0) {
245 291 handle->xdpi = xdpi;
246 292 handle->ydpi = ydpi;
247   - return 0;
  293 + return 1;
248 294 }
249 295 else {
250 296 i_push_error(0, "resolutions must be positive");
2  FT2/imft2.h
@@ -7,7 +7,7 @@ typedef struct FT2_Fonthandle FT2_Fonthandle;
7 7
8 8 typedef FT2_Fonthandle* Imager__Font__FT2x;
9 9
10   -extern int i_ft2_init(void);
  10 +extern void i_ft2_start(void);
11 11 extern FT2_Fonthandle * i_ft2_new(const char *name, int index);
12 12 extern void i_ft2_destroy(FT2_Fonthandle *handle);
13 13 extern int i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi);
59 FT2/t/t20thread.t
... ... @@ -0,0 +1,59 @@
  1 +#!perl -w
  2 +use strict;
  3 +use Imager;
  4 +
  5 +use Config;
  6 +my $loaded_threads;
  7 +BEGIN {
  8 + if ($Config{useithreads} && $] > 5.008007) {
  9 + $loaded_threads =
  10 + eval {
  11 + require threads;
  12 + threads->import;
  13 + 1;
  14 + };
  15 + }
  16 +}
  17 +
  18 +use Test::More;
  19 +
  20 +$Config{useithreads}
  21 + or plan skip_all => "can't test Imager's lack of threads support with no threads";
  22 +$] > 5.008007
  23 + or plan skip_all => "require a perl with CLONE_SKIP to test Imager's lack of threads support";
  24 +$loaded_threads
  25 + or plan skip_all => "couldn't load threads";
  26 +
  27 +$INC{"Devel/Cover.pm"}
  28 + and plan skip_all => "threads and Devel::Cover don't get along";
  29 +
  30 +# https://rt.cpan.org/Ticket/Display.html?id=65812
  31 +# https://github.com/schwern/test-more/issues/labels/Test-Builder2#issue/100
  32 +$Test::More::VERSION =~ /^2\.00_/
  33 + and plan skip_all => "threads are hosed in 2.00_06 and presumably all 2.00_*";
  34 +
  35 +plan tests => 8;
  36 +
  37 +Imager->open_log(log => "testout/t20thread.log");
  38 +
  39 +my $ft1 = Imager::Font->new(file => "fontfiles/dodge.ttf", type => "ft2");
  40 +ok($ft1, "make a font");
  41 +ok($ft1->_valid, "and it's valid");
  42 +my $ft2;
  43 +
  44 +my $thr = threads->create
  45 + (
  46 + sub {
  47 + ok(!$ft1->_valid, "first font no longer valid");
  48 + $ft2 = Imager::Font->new(file => "fontfiles/dodge.ttf", type => "ft2");
  49 + ok($ft2, "make a new font in thread");
  50 + ok($ft2->_valid, "and it's valid");
  51 + 1;
  52 + },
  53 + );
  54 +
  55 +ok($thr->join, "join the thread");
  56 +ok($ft1->_valid, "original font still valid in main thread");
  57 +is($ft2, undef, "font created in thread shouldn't be set in main thread");
  58 +
  59 +Imager->close_log();
2  GIF/GIF.pm
@@ -4,7 +4,7 @@ use Imager;
4 4 use vars qw($VERSION @ISA);
5 5
6 6 BEGIN {
7   - $VERSION = "0.84";
  7 + $VERSION = "0.85";
8 8
9 9 require XSLoader;
10 10 XSLoader::load('Imager::File::GIF', $VERSION);
1  GIF/GIF.xs
@@ -147,3 +147,4 @@ i_readgif_multi_wiol(ig)
147 147 BOOT:
148 148 PERL_INITIALIZE_IMAGER_CALLBACKS;
149 149 PERL_INITIALIZE_IMAGER_PERL_CALLBACKS;
  150 + i_init_gif();
45 GIF/imgif.c
@@ -69,6 +69,12 @@ static int
69 69 InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
70 70
71 71
  72 +static i_mutex_t mutex;
  73 +
  74 +void
  75 +i_init_gif(void) {
  76 + mutex = i_mutex_new();
  77 +}
72 78
73 79 static
74 80 void
@@ -846,17 +852,25 @@ static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
846 852 i_img **
847 853 i_readgif_multi_wiol(io_glue *ig, int *count) {
848 854 GifFileType *GifFile;
849   -
  855 + i_img **result;
  856 +
  857 + i_mutex_lock(mutex);
  858 +
850 859 i_clear_error();
851 860
852 861 if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
853 862 gif_push_error();
854 863 i_push_error(0, "Cannot create giflib callback object");
855 864 mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
  865 + i_mutex_unlock(mutex);
856 866 return NULL;
857 867 }
858 868
859   - return i_readgif_multi_low(GifFile, count, -1);
  869 + result = i_readgif_multi_low(GifFile, count, -1);
  870 +
  871 + i_mutex_unlock(mutex);
  872 +
  873 + return result;
860 874 }
861 875
862 876 static int
@@ -869,6 +883,9 @@ io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
869 883 i_img *
870 884 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
871 885 GifFileType *GifFile;
  886 + i_img *result;
  887 +
  888 + i_mutex_lock(mutex);
872 889
873 890 i_clear_error();
874 891
@@ -876,10 +893,15 @@ i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
876 893 gif_push_error();
877 894 i_push_error(0, "Cannot create giflib callback object");
878 895 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
  896 + i_mutex_unlock(mutex);
879 897 return NULL;
880 898 }
881 899
882   - return i_readgif_low(GifFile, color_table, colors);
  900 + result = i_readgif_low(GifFile, color_table, colors);
  901 +
  902 + i_mutex_unlock(mutex);
  903 +
  904 + return result;
883 905 }
884 906
885 907 /*
@@ -924,6 +946,7 @@ Returns NULL if the page isn't found.
924 946 i_img *
925 947 i_readgif_single_wiol(io_glue *ig, int page) {
926 948 GifFileType *GifFile;
  949 + i_img *result;
927 950
928 951 i_clear_error();
929 952 if (page < 0) {
@@ -931,14 +954,21 @@ i_readgif_single_wiol(io_glue *ig, int page) {
931 954 return NULL;
932 955 }
933 956
  957 + i_mutex_lock(mutex);
  958 +
934 959 if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
935 960 gif_push_error();
936 961 i_push_error(0, "Cannot create giflib callback object");
937 962 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
  963 + i_mutex_unlock(mutex);
938 964 return NULL;
939 965 }
940 966
941   - return i_readgif_single_low(GifFile, page);
  967 + result = i_readgif_single_low(GifFile, page);
  968 +
  969 + i_mutex_unlock(mutex);
  970 +
  971 + return result;
942 972 }
943 973
944 974 /*
@@ -1797,6 +1827,8 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1797 1827 GifFileType *GifFile;
1798 1828 int result;
1799 1829
  1830 + i_mutex_lock(mutex);
  1831 +
1800 1832 i_clear_error();
1801 1833
1802 1834 gif_set_version(quant, imgs, count);
@@ -1805,11 +1837,14 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1805 1837 gif_push_error();
1806 1838 i_push_error(0, "Cannot create giflib callback object");
1807 1839 mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
  1840 + i_mutex_unlock(mutex);
1808 1841 return 0;
1809 1842 }
1810 1843
1811 1844 result = i_writegif_low(quant, GifFile, imgs, count);
1812 1845
  1846 + i_mutex_unlock(mutex);
  1847 +
1813 1848 if (i_io_close(ig))
1814 1849 return 0;
1815 1850
@@ -1945,6 +1980,8 @@ EGifSetGifVersion(). See L<gif_set_version> for an explanation.
1945 1980
1946 1981 Arnar M. Hrafnkelsson, addi@umich.edu
1947 1982
  1983 +Tony Cook <tonyc@cpan.org>
  1984 +
1948 1985 =head1 SEE ALSO
1949 1986
1950 1987 perl(1), Imager(3)
1  GIF/imgif.h
@@ -3,6 +3,7 @@
3 3
4 4 #include "imext.h"
5 5
  6 +void i_init_gif(void);
6 7 double i_giflib_version(void);
7 8 i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours);
8 9 i_img *i_readgif_single_wiol(io_glue *ig, int page);
15 Imager.pm
@@ -4363,6 +4363,10 @@ L<Imager::ExtUtils> - tools to get access to Imager's C API.
4363 4363
4364 4364 L<Imager::Security> - brief security notes.
4365 4365
  4366 +=item *
  4367 +
  4368 +L<Imager::Threads> - brief information on working with threads.
  4369 +
4366 4370 =back
4367 4371
4368 4372 =head2 Basic Overview
@@ -4804,6 +4808,8 @@ text, wrapping text in an area - L<Imager::Font::Wrap>
4804 4808
4805 4809 text, measuring - L<Imager::Font/bounding_box()>, L<Imager::Font::BBox>
4806 4810
  4811 +threads - L<Imager::Threads>
  4812 +
4807 4813 tiles, color - L<Imager::Filters/mosaic>
4808 4814
4809 4815 transparent images - L<Imager::ImageTypes>,
@@ -4817,15 +4823,6 @@ watermark - L<Imager::Filters/watermark>
4817 4823
4818 4824 writing an image to a file - L<Imager::Files>
4819 4825
4820   -=head1 THREADS
4821   -
4822   -Imager doesn't support perl threads.
4823   -
4824   -Imager has limited code to prevent double frees if you create images,
4825   -colors etc, and then create a thread, but has no code to prevent two
4826   -threads entering Imager's error handling code, and none is likely to
4827   -be added.
4828   -
4829 4826 =head1 SUPPORT
4830 4827
4831 4828 The best place to get help with Imager is the mailing list.
95 Imager.xs
@@ -29,6 +29,69 @@ extern "C" {
29 29
30 30 #include "imperl.h"
31 31
  32 +/*
  33 +
  34 +Context object management
  35 +
  36 +*/
  37 +
  38 +typedef im_context_t Imager__Context;
  39 +
  40 +#define im_context_DESTROY(ctx) im_context_refdec((ctx), "DESTROY")
  41 +
  42 +#ifdef PERL_IMPLICIT_CONTEXT
  43 +
  44 +#define MY_CXT_KEY "Imager::_context" XS_VERSION
  45 +
  46 +typedef struct {
  47 + im_context_t ctx;
  48 +} my_cxt_t;
  49 +
  50 +START_MY_CXT
  51 +
  52 +im_context_t fallback_context;
  53 +
  54 +static void
  55 +start_context(pTHX) {
  56 + dMY_CXT;
  57 + MY_CXT.ctx = im_context_new();
  58 + sv_setref_pv(get_sv("Imager::_context", GV_ADD), "Imager::Context", MY_CXT.ctx);
  59 +
  60 + /* Ideally we'd free this reference, but the error message memory
  61 + was never released on exit, so the associated memory here is reasonable
  62 + to keep.
  63 + With logging enabled we always need at least one context, since
  64 + objects may be released fairly late and attempt to get the log file.
  65 + */
  66 + im_context_refinc(MY_CXT.ctx, "start_context");
  67 + fallback_context = MY_CXT.ctx;
  68 +}
  69 +
  70 +static im_context_t
  71 +perl_get_context(void) {
  72 + dTHX;
  73 + dMY_CXT;
  74 +
  75 + return MY_CXT.ctx ? MY_CXT.ctx : fallback_context;
  76 +}
  77 +
  78 +#else
  79 +
  80 +static im_context_t perl_context;
  81 +
  82 +static void
  83 +start_context(pTHX) {
  84 + perl_context = im_context_new();
  85 + im_context_refinc(perl_context, "start_context");
  86 +}
  87 +
  88 +static im_context_t
  89 +perl_get_context(void) {
  90 + return perl_context;
  91 +}
  92 +
  93 +#endif
  94 +
32 95 /* used to represent channel lists parameters */
33 96 typedef struct i_channel_list_tag {
34 97 int *channels;
@@ -727,7 +790,6 @@ validate_i_ppal(i_img *im, i_palidx const *indexes, int count) {
727 790 }
728 791 }
729 792
730   -
731 793 /* I don't think ICLF_* names belong at the C interface
732 794 this makes the XS code think we have them, to let us avoid
733 795 putting function bodies in the XS code
@@ -3989,6 +4051,37 @@ i_int_hlines_CLONE_SKIP(cls)
3989 4051
3990 4052 #endif
3991 4053
  4054 +MODULE = Imager PACKAGE = Imager::Context PREFIX=im_context_
  4055 +
  4056 +void
  4057 +im_context_DESTROY(ctx)
  4058 + Imager::Context ctx
  4059 +
  4060 +#ifdef PERL_IMPLICIT_CONTEXT
  4061 +
  4062 +void
  4063 +im_context_CLONE(...)
  4064 + CODE:
  4065 + MY_CXT_CLONE;
  4066 + (void)items;
  4067 + /* the following sv_setref_pv() will free this inc */
  4068 + im_context_refinc(MY_CXT.ctx, "CLONE");
  4069 + MY_CXT.ctx = im_context_clone(MY_CXT.ctx, "CLONE");
  4070 + sv_setref_pv(get_sv("Imager::_context", GV_ADD), "Imager::Context", MY_CXT.ctx);
  4071 +
  4072 +#endif
  4073 +
3992 4074 BOOT:
3993 4075 PERL_SET_GLOBAL_CALLBACKS;
3994 4076 PERL_PL_SET_GLOBAL_CALLBACKS;
  4077 +#ifdef PERL_IMPLICIT_CONTEXT
  4078 + {
  4079 + MY_CXT_INIT;
  4080 + (void)MY_CXT;
  4081 + }
  4082 +#endif
  4083 + start_context(aTHX);
  4084 + im_get_context = perl_get_context;
  4085 +#ifdef HAVE_LIBTT
  4086 + i_tt_start();
  4087 +#endif
2  JPEG/JPEG.pm
@@ -4,7 +4,7 @@ use Imager;
4 4 use vars qw($VERSION @ISA);
5 5
6 6 BEGIN {
7   - $VERSION = "0.84";
  7 + $VERSION = "0.85";
8 8
9 9 require XSLoader;
10 10 XSLoader::load('Imager::File::JPEG', $VERSION);
56 JPEG/imjpeg.c
@@ -28,12 +28,12 @@ Reads and writes JPEG images
28 28 #include <unistd.h>
29 29 #endif
30 30 #include <setjmp.h>
  31 +#include <string.h>
31 32
32 33 #include "jpeglib.h"
33 34 #include "jerror.h"
34 35 #include <errno.h>
35 36 #include <stdlib.h>
36   -#include <stdio.h>
37 37 #include "imexif.h"
38 38
39 39 #define JPEG_APP13 0xED /* APP13 marker code */
@@ -44,15 +44,8 @@ Reads and writes JPEG images
44 44
45 45 static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
46 46
47   -/* Bad design right here */
48   -
49   -static int tlength=0;
50   -static char **iptc_text=NULL;
51   -
52   -
53 47 /* Source and Destination managers */
54 48
55   -
56 49 typedef struct {
57 50 struct jpeg_source_mgr pub; /* public fields */
58 51 io_glue *data;
@@ -71,7 +64,6 @@ typedef struct {
71 64 typedef wiol_source_mgr *wiol_src_ptr;
72 65 typedef wiol_destination_mgr *wiol_dest_ptr;
73 66
74   -
75 67 /*
76 68 * Methods for io manager objects
77 69 *
@@ -271,38 +263,6 @@ jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) {
271 263 dest->pub.next_output_byte = dest->buffer;
272 264 }
273 265
274   -LOCAL(unsigned int)
275   -jpeg_getc (j_decompress_ptr cinfo)
276   -/* Read next byte */
277   -{
278   - struct jpeg_source_mgr * datasrc = cinfo->src;
279   -
280   - if (datasrc->bytes_in_buffer == 0) {
281   - if (! (*datasrc->fill_input_buffer) (cinfo))
282   - { fprintf(stderr,"Jpeglib: cant suspend.\n"); exit(3); }
283   - /* ERREXIT(cinfo, JERR_CANT_SUSPEND);*/
284   - }
285   - datasrc->bytes_in_buffer--;
286   - return GETJOCTET(*datasrc->next_input_byte++);
287   -}
288   -
289   -METHODDEF(boolean)
290   -APP13_handler (j_decompress_ptr cinfo) {
291   - INT32 length;
292   - unsigned int cnt=0;
293   -
294   - length = jpeg_getc(cinfo) << 8;
295   - length += jpeg_getc(cinfo);
296   - length -= 2; /* discount the length word itself */
297   -
298   - tlength=length;
299   -
300   - if ( ((*iptc_text)=mymalloc(length)) == NULL ) return FALSE;
301   - while (--length >= 0) (*iptc_text)[cnt++] = jpeg_getc(cinfo);
302   -
303   - return TRUE;
304   -}
305   -
306 266 METHODDEF(void)
307 267 my_output_message (j_common_ptr cinfo) {
308 268 char buffer[JMSG_LENGTH_MAX];
@@ -400,7 +360,9 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
400 360
401 361 i_clear_error();
402 362
403   - iptc_text = iptc_itext;
  363 + *iptc_itext = NULL;
  364 + *itlength = 0;
  365 +
404 366 cinfo.err = jpeg_std_error(&jerr.pub);
405 367 jerr.pub.error_exit = my_error_exit;
406 368 jerr.pub.output_message = my_output_message;
@@ -410,8 +372,6 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
410 372 if (src_set)
411 373 wiol_term_source(&cinfo);
412 374 jpeg_destroy_decompress(&cinfo);
413   - *iptc_itext=NULL;
414   - *itlength=0;
415 375 if (line_buffer)
416 376 myfree(line_buffer);
417 377 if (im)
@@ -420,7 +380,7 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
420 380 }
421 381
422 382 jpeg_create_decompress(&cinfo);
423   - jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
  383 + jpeg_save_markers(&cinfo, JPEG_APP13, 0xFFFF);
424 384 jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
425 385 jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
426 386 jpeg_wiol_src(&cinfo, data, length);
@@ -515,6 +475,11 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
515 475 else if (markerp->marker == JPEG_APP1 && !seen_exif) {
516 476 seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length);
517 477 }
  478 + else if (markerp->marker == JPEG_APP13) {
  479 + *iptc_itext = mymalloc(markerp->data_length);
  480 + memcpy(*iptc_itext, markerp->data, markerp->data_length);
  481 + *itlength = markerp->data_length;
  482 + }
518 483
519 484 markerp = markerp->next;
520 485 }
@@ -557,7 +522,6 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
557 522
558 523 (void) jpeg_finish_decompress(&cinfo);
559 524 jpeg_destroy_decompress(&cinfo);
560   - *itlength=tlength;
561 525
562 526 i_tags_set(&im->tags, "i_format", "jpeg", 4);
563 527
10 MANIFEST
@@ -7,6 +7,7 @@ Changes.old Old changes
7 7 color.c Color translation and handling
8 8 combine.im Channel combine
9 9 compose.im
  10 +context.c
10 11 conv.im
11 12 convert.im
12 13 CountColor/CountColor.pm sample XS access to API
@@ -49,7 +50,7 @@ Flines/Flines.xs
49 50 Flines/Makefile.PL
50 51 Flines/t/t00flines.t
51 52 flip.im
52   -font.c
  53 +fontft1.c
53 54 fontfiles/dodge.ttf
54 55 fontfiles/ExistenceTest.ttf generated using pfaedit
55 56 fontfiles/ImUgly.ttf
@@ -68,6 +69,7 @@ FT2/imft2.h
68 69 FT2/Makefile.PL
69 70 FT2/README
70 71 FT2/t/t10ft2.t
  72 +FT2/t/t20thread.t
71 73 FT2/typemap
72 74 gaussian.im
73 75 GIF/GIF.pm
@@ -199,6 +201,7 @@ lib/Imager/regmach.pod
199 201 lib/Imager/Regops.pm
200 202 lib/Imager/Security.pod
201 203 lib/Imager/Test.pm
  204 +lib/Imager/Threads.pod
202 205 lib/Imager/Transform.pm
203 206 lib/Imager/Transformations.pod
204 207 lib/Imager/Tutorial.pod
@@ -215,6 +218,9 @@ MANIFEST
215 218 MANIFEST.SKIP
216 219 map.c
217 220 maskimg.c
  221 +mutexnull.c
  222 +mutexpthr.c
  223 +mutexwin.c
218 224 palimg.c
219 225 paste.im
220 226 plug.h
@@ -349,6 +355,7 @@ t/t80texttools.t Test text wrapping
349 355 t/t81hlines.t Test hlines.c
350 356 t/t82inline.t Test Inline::C integration
351 357 t/t83extutil.t Test Imager::ExtUtils
  358 +t/t84inlinectx.t
352 359 t/t90cc.t
353 360 t/t91pod.t Test POD with Test::Pod
354 361 t/t92samples.t
@@ -373,6 +380,7 @@ T1/t/t10type1.t
373 380 T1/t/t20oo.t
374 381 T1/T1.pm
375 382 T1/T1.xs
  383 +T1/typemap
376 384 tags.c
377 385 testimg/alpha16.tga 16-bit/pixel TGA with alpha "channel" RT 32926
378 386 testimg/bad1oflow.bmp 1-bit/pixel, overflow integer on 32-bit machines
33 Makefile.PL
@@ -48,6 +48,7 @@ my @incpaths; # places to look for headers
48 48 my @libpaths; # places to look for libraries
49 49 my $coverage; # build for coverage testing
50 50 my $assert; # build with assertions
  51 +my $trace_context; # trace context management to stderr
51 52 GetOptions("help" => \$help,
52 53 "enable=s" => \@enable,
53 54 "disable=s" => \@disable,
@@ -56,7 +57,8 @@ GetOptions("help" => \$help,
56 57 "verbose|v" => \$VERBOSE,
57 58 "nolog" => \$NOLOG,
58 59 'coverage' => \$coverage,
59   - "assert|a" => \$assert);
  60 + "assert|a" => \$assert,
  61 + "tracecontext" => \$trace_context);
60 62
61 63 setenv();
62 64
@@ -159,19 +161,42 @@ my $OSDEF = "-DOS_$^O";
159 161 if ($^O eq 'hpux') { $OSLIBS .= ' -ldld'; }
160 162 if (defined $Config{'d_dlsymun'}) { $OSDEF .= ' -DDLSYMUN'; }
161 163
162   -my @objs = qw(Imager.o draw.o polygon.o image.o io.o iolayer.o
163   - log.o gaussian.o conv.o pnm.o raw.o feat.o font.o combine.o
  164 +my @objs = qw(Imager.o context.o draw.o polygon.o image.o io.o iolayer.o
  165 + log.o gaussian.o conv.o pnm.o raw.o feat.o combine.o
164 166 filters.o dynaload.o stackmach.o datatypes.o
165 167 regmach.o trans2.o quant.o error.o convert.o
166 168 map.o tags.o palimg.o maskimg.o img8.o img16.o rotate.o
167 169 bmp.o tga.o color.o fills.o imgdouble.o limits.o hlines.o
168 170 imext.o scale.o rubthru.o render.o paste.o compose.o flip.o);
169 171
  172 +if ($Config{useithreads}) {
  173 + if ($Config{i_pthread}) {
  174 + print "POSIX threads\n";
  175 + push @objs, "mutexpthr.o";
  176 + }
  177 + elsif ($^O eq 'MSWin32') {
  178 + print "Win32 threads\n";
  179 + push @objs, "mutexwin.o";
  180 + }
  181 + else {
  182 + print "Unsupported threading model\n";
  183 + push @objs, "mutexnull.o";
  184 + }
  185 +}
  186 +else {
  187 + print "No threads\n";
  188 + push @objs, "mutexnull.o";
  189 +}
  190 +
170 191 my @typemaps = qw(typemap.local typemap);
171 192 if ($] < 5.008) {
172 193 unshift @typemaps, "typemap.oldperl";
173 194 }
174 195
  196 +if ($trace_context) {
  197 + $CFLAGS .= " -DIMAGER_TRACE_CONTEXT";
  198 +}
  199 +
175 200 my %opts=
176 201 (
177 202 'NAME' => 'Imager',
@@ -521,7 +546,7 @@ sub init {
521 546 && !-e catfile($_[0], 'fterrors.h') },
522 547 libcheck=>sub { $_[0] eq "libttf$aext" or $_[0] eq "libttf.$lext" },
523 548 libfiles=>'-lttf',
524   - objfiles=>'',
  549 + objfiles=>'fontft1.o',
525 550 code => \&freetype1_probe,
526 551 docs=>q{
527 552 Truetype fonts are scalable fonts. They can include
9 T1/Changes
... ... @@ -1,3 +1,12 @@
  1 +Imager::Font::T1 1.018
  2 +======================
  3 +
  4 + - use mutexes to avoid re-entrancy into the thread-unsafe T1Lib
  5 +
  6 + - improve error handling and reporting
  7 +
  8 + - provide better control of the level of anti-aliasing
  9 +
1 10 Imager::Font::T1 1.017
2 11 ======================
3 12
1  T1/MANIFEST
@@ -16,3 +16,4 @@ t/t10type1.t
16 16 t/t20oo.t
17 17 T1.pm
18 18 T1.xs
  19 +typemap
91 T1/T1.pm
@@ -5,7 +5,7 @@ use vars qw(@ISA $VERSION);
5 5 @ISA = qw(Imager::Font);
6 6
7 7 BEGIN {
8   - $VERSION = "1.017";
  8 + $VERSION = "1.018";
9 9
10 10 require XSLoader;
11 11 XSLoader::load('Imager::Font::T1', $VERSION);
@@ -14,17 +14,7 @@ BEGIN {
14 14
15 15 *_first = \&Imager::Font::_first;
16 16
17   -my $t1aa;
18   -
19   -# $T1AA is in there because for some reason (probably cache related) antialiasing
20   -# is a system wide setting in t1 lib.
21   -
22   -sub t1_set_aa_level {
23   - if (!defined $t1aa or $_[0] != $t1aa) {
24   - i_t1_set_aa($_[0]);
25   - $t1aa=$_[0];
26   - }
27   -}
  17 +my $t1aa = 2;
28 18
29 19 sub new {
30 20 my $class = shift;
@@ -65,39 +55,42 @@ sub new {
65 55 $hsh{afm} = 0;
66 56 }
67 57
68   - my $id = i_t1_new($hsh{file},$hsh{afm});
69   - unless ($id >= 0) { # the low-level code may miss some error handling
  58 + my $font = Imager::Font::T1xs->new($hsh{file},$hsh{afm});
  59 + unless ($font) { # the low-level code may miss some error handling
70 60 Imager->_set_error(Imager->_error_as_msg);
71 61 return;
72 62 }
73 63 return bless {
74   - id => $id,
  64 + t1font => $font,
75 65 aa => $hsh{aa} || 0,
76 66 file => $hsh{file},
77 67 type => 't1',
78 68 size => $hsh{size},
79 69 color => $hsh{color},
  70 + t1aa => $t1aa,
80 71 }, $class;
81 72 }
82 73
83 74 sub _draw {
84 75 my $self = shift;
85 76 my %input = @_;
86   - t1_set_aa_level($input{aa});
87 77 my $flags = '';
88 78 $flags .= 'u' if $input{underline};
89 79 $flags .= 's' if $input{strikethrough};
90 80 $flags .= 'o' if $input{overline};
  81 + my $aa = $input{aa} ? $self->{t1aa} : 0;
91 82 if (exists $input{channel}) {
92   - i_t1_cp($input{image}{IMG}, $input{'x'}, $input{'y'},
93   - $input{channel}, $self->{id}, $input{size},
  83 + $self->{t1font}->cp($input{image}{IMG}, $input{'x'}, $input{'y'},
  84 + $input{channel}, $input{size},
94 85 $input{string}, length($input{string}), $input{align},
95   - $input{utf8}, $flags);
  86 + $input{utf8}, $flags, $aa)
  87 + or return;
96 88 } else {
97   - i_t1_text($input{image}{IMG}, $input{'x'}, $input{'y'},
98   - $input{color}, $self->{id}, $input{size},
  89 + $self->{t1font}->text($input{image}{IMG}, $input{'x'}, $input{'y'},
  90 + $input{color}, $input{size},
99 91 $input{string}, length($input{string}),
100   - $input{align}, $input{utf8}, $flags);
  92 + $input{align}, $input{utf8}, $flags, $aa)
  93 + or return;
101 94 }
102 95
103 96 return $self;
@@ -110,7 +103,7 @@ sub _bounding_box {
110 103 $flags .= 'u' if $input{underline};
111 104 $flags .= 's' if $input{strikethrough};
112 105 $flags .= 'o' if $input{overline};
113   - return i_t1_bbox($self->{id}, $input{size}, $input{string},
  106 + return $self->{t1font}->bbox($input{size}, $input{string},
114 107 length($input{string}), $input{utf8}, $flags);
115 108 }
116 109
@@ -122,8 +115,8 @@ sub has_chars {
122 115 $Imager::ERRSTR = "No string supplied to \$font->has_chars()";
123 116 return;
124 117 }
125   - return i_t1_has_chars($self->{id}, $hsh{string},
126   - _first($hsh{'utf8'}, $self->{utf8}, 0));
  118 + return $self->{t1font}->has_chars($hsh{string},
  119 + _first($hsh{'utf8'}, $self->{utf8}, 0));
127 120 }
128 121
129 122 sub utf8 {
@@ -133,7 +126,7 @@ sub utf8 {
133 126 sub face_name {
134 127 my ($self) = @_;
135 128
136   - i_t1_face_name($self->{id});
  129 + return $self->{t1font}->face_name();
137 130 }
138 131
139 132 sub glyph_names {
@@ -144,9 +137,27 @@ sub glyph_names {
144 137 or return Imager->_set_error("no string parameter passed to glyph_names");
145 138 my $utf8 = _first($input{utf8} || 0);
146 139
147   - i_t1_glyph_name($self->{id}, $string, $utf8);
  140 + return $self->{t1font}->glyph_name($string, $utf8);
148 141 }
149 142
  143 +sub set_aa_level {
  144 + my ($self, $new_t1aa) = @_;
  145 +
  146 + if (!defined $new_t1aa ||
  147 + ($new_t1aa != 1 && $new_t1aa != 2)) {
  148 + Imager->_set_error("set_aa_level: parameter must be 1 or 2");
  149 + return;
  150 + }
  151 +
  152 + if (ref $self) {
  153 + $self->{t1aa} = $new_t1aa;
  154 + }
  155 + else {
  156 + $t1aa = $new_t1aa;
  157 + }
  158 +
  159 + return 1;
  160 +}
150 161
151 162 1;
152 163
@@ -197,6 +208,32 @@ C<strikethrough> - Draw the text with a strikethrough.
197 208 Obviously, if you're calculating the bounding box the size of the line
198 209 is included in the box, and the line isn't drawn :)
199 210
  211 +=head2 Anti-aliasing
  212 +
  213 +T1Lib supports multiple levels of anti-aliasing, by default, if you
  214 +request anti-aliased output, Imager::Font::T1 will use the maximum
  215 +level.
  216 +
  217 +You can override this with the set_t1_aa() method:
  218 +
  219 +=over
  220 +
  221 +=item set_aa_level()
  222 +
  223 +Usage:
  224 +
  225 + $font->set_aa_level(1);
  226 + Imager::Font::T1->set_aa_level(2);
  227 +
  228 +Sets the T1Lib anti-aliasing level either for the specified font, or
  229 +for new font objects.
  230 +
  231 +The only parameter must be 1 or 2.
  232 +
  233 +Returns true on success.
  234 +
  235 +=back
  236 +
200 237 =head1 AUTHOR
201 238
202 239 Addi, Tony
62 T1/T1.xs
@@ -11,38 +11,43 @@ extern "C" {