The make Allegro better project
kazzmir

Id like to try an experiment. I think the Allegro source could be refactored somewhat and made more elegant so I propose that the community rewrites portions of Allegro. I( or anyone really ) will post a function or other snippet of code from Allegro and people can modify it in any way as long as its "better", be it by choosing better variable names or simply adding useful comments. For now the code will come from 4.2 since 4.3 isn't ready for mass consumption.

After people agree on the changes it will be sent to the Allegro developers list and if approved will be committed.

And if it needs justification I would say there are two main benefits.
1) Some parts of Allegro are extremely hard to follow
2) More people would become more intimate with the internals to Allegro

Ill start off with the gourad sprite routine in gsprite.c. I havent yet refactored this one, Ill post some modifications later on.

1/* draw_gouraud_sprite:
2 * Draws a lit or tinted sprite, interpolating the four corner colors
3 * over the surface of the image.
4 */
5void _soft_draw_gouraud_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, int c1, int c2, int c3, int c4)
6{
7 fixed mc1, mc2, mh;
8 fixed lc, rc, hc;
9 int x1 = x;
10 int y1 = y;
11 int x2 = x + sprite->w;
12 int y2 = y + sprite->h;
13 int i, j;
14 int pixel;
15 uintptr_t addr;
16 
17 ASSERT(bmp);
18 ASSERT(sprite);
19 ASSERT(bmp->vtable->color_depth == sprite->vtable->color_depth);
20 
21 bmp_select(bmp);
22 
23 /* set up vertical gradients for left and right sides */
24 mc1 = itofix(c4 - c1) / sprite->h;
25 mc2 = itofix(c3 - c2) / sprite->h;
26 lc = itofix(c1);
27 rc = itofix(c2);
28 
29 /* check clipping */
30 if (bmp->clip) {
31 if (y1 < bmp->ct) {
32 lc += mc1 * (bmp->ct - y1);
33 rc += mc2 * (bmp->ct - y1);
34 y1 = bmp->ct;
35 }
36 y2 = MIN(y2, bmp->cb);
37 x1 = MAX(x1, bmp->cl);
38 x2 = MIN(x2, bmp->cr);
39 }
40 
41 for (j=y1; j<y2; j++) {
42 /* set up horizontal gradient for line */
43 mh = (rc - lc) / sprite->w;
44 hc = lc;
45 
46 /* more clip checking */
47 if ((bmp->clip) && (x < bmp->cl))
48 hc += mh * (bmp->cl - x);
49 
50 #ifdef GFX_HAS_VGA
51 
52 /* modex version */
53 if (is_planar_bitmap(bmp)) {
54 addr = ((unsigned long)bmp->line[j]<<2) + x1;
55 for (i=x1; i<x2; i++) {
56 if (sprite->line[j-y][i-x]) {
57 outportw(0x3C4, (0x100<<(i&3))|2);
58 pixel = color_map->data[fixtoi(hc)][sprite->line[j-y][i-x]];
59 bmp_write8(addr>>2, pixel);
60 }
61 hc += mh;
62 addr++;
63 }
64 }
65 else {
66 
67 #else
68 
69 {
70 
71 #endif
72 
73 /* draw routines for all linear modes */
74 switch (bitmap_color_depth(bmp)) {
75 
76 #ifdef ALLEGRO_COLOR8
77 
78 case 8:
79 addr = bmp_write_line(bmp, j) + x1;
80 for (i=x1; i<x2; i++) {
81 if (sprite->line[j-y][i-x]) {
82 pixel = color_map->data[fixtoi(hc)][sprite->line[j-y][i-x]];
83 bmp_write8(addr, pixel);
84 }
85 hc += mh;
86 addr++;
87 }
88 break;
89 
90 #endif
91 
92 #ifdef ALLEGRO_COLOR16
93 
94 case 15:
95 case 16:
96 addr = bmp_write_line(bmp, j) + x1*sizeof(short);
97 for (i=x1; i<x2; i++) {
98 pixel = ((unsigned short *)sprite->line[j-y])[i-x];
99 if (pixel != bmp->vtable->mask_color) {
100 if (bitmap_color_depth(bmp) == 16)
101 pixel = _blender_func16(pixel, _blender_col_16, fixtoi(hc));
102 else
103 pixel = _blender_func15(pixel, _blender_col_15, fixtoi(hc));
104 bmp_write16(addr, pixel);
105 }
106 hc += mh;
107 addr += sizeof(short);
108 }
109 break;
110 
111 #endif
112 
113 #ifdef ALLEGRO_COLOR24
114 
115 case 24:
116 addr = bmp_write_line(bmp, j) + x1*3;
117 for (i=x1; i<x2; i++) {
118 bmp_select(sprite);
119 pixel = bmp_read24((unsigned long)(sprite->line[j-y] + (i-x)*3));
120 bmp_select(bmp);
121 if (pixel != MASK_COLOR_24) {
122 pixel = _blender_func24(pixel, _blender_col_24, fixtoi(hc));
123 bmp_write24(addr, pixel);
124 }
125 hc += mh;
126 addr += 3;
127 }
128 break;
129 
130 #endif
131 
132 #ifdef ALLEGRO_COLOR32
133 
134 case 32:
135 addr = bmp_write_line(bmp, j) + x1*sizeof(int32_t);
136 for (i=x1; i<x2; i++) {
137 pixel = ((unsigned long *)sprite->line[j-y])[i-x];
138 if (pixel != MASK_COLOR_32) {
139 pixel = _blender_func32(pixel, _blender_col_32, fixtoi(hc));
140 bmp_write32(addr, pixel);
141 }
142 hc += mh;
143 addr += sizeof(int32_t);
144 }
145 break;
146 
147 #endif
148 
149 }
150 }
151 
152 lc += mc1;
153 rc += mc2;
154 }
155 
156 bmp_unwrite_line(bmp);
157}

Andrei Ellman

I just thought that this would be a good time to post a link to the Allegro coding standards (it's the 'coding style' section) so that we know how to rename the variables/functions, and how to format the code, etc.

AE.

Elias

Note that we do not follow the tab standard described there anymore: Any new code should not contain any tab characters.

About the original idea, it sounds good to me. Also, improving documentation of how Allegro works internaly could be a good idea.. like a description how drivers work together, and how single drivers (e.g. DirectX) actually work.

Hm, and not sure how well this all can work for 4.3 as of yet, as some very base structures like AL_SYSTEM_DRIVER, AL_DISPLAY or AL_BITMAP are not implemented yet.

kazzmir
Quote:

Hm, and not sure how well this all can work for 4.3 as of yet, as some very base structures like AL_SYSTEM_DRIVER, AL_DISPLAY or AL_BITMAP are not implemented yet.

Thats why I think we should focus on 4.2 for now and only the parts of the code that can be used in 4.3 so the work isnt wasted once 4.2 isnt worked on anymore.

[edit]
Here is my first modification. Mostly I just renamed variables and added some comments at the top. I noticed that the old 'x' variable was being referenced in the code but since x1 = x I thought x1 should have been used instead so the parameter x is now x_ so we can be sure its not used accidentally. The same for y.

1/* draw_gouraud_sprite:
2 * Draws a lit or tinted sprite, interpolating the four corner colors
3 * over the surface of the image.
4 * destination - BITMAP where the sprite will be drawn to
5 * sprite - BITMAP of the sprite to draw
6 * x_ - upper left hand x coordinate on destination
7 * y_ - upper left hand y coordinate on destination
8 * color1 - upper left hand color
9 * color2 - upper right hand color
10 * color3 - lower left hand color
11 * color4 - lower right hand color
12 */
13void _soft_draw_gouraud_sprite(BITMAP * destination, BITMAP *sprite, int x_, int y_, int color1, int color2, int color3, int color4)
14{
15 double gradient1;
16 double gradient2;
17 double left_dx;
18 double right_dx;
19 int x1 = x_;
20 int y1 = y_;
21 int x2 = x_ + sprite->w;
22 int y2 = y_ + sprite->h;
23 int i, current_y;
24 int pixel;
25 uintptr_t addr;
26 
27 ASSERT(destination);
28 ASSERT(sprite);
29 ASSERT(destination->vtable->color_depth == sprite->vtable->color_depth);
30 
31 bmp_select(destination);
32 
33 /* set up vertical gradients for left and right sides */
34 gradient1 = (color4 - color1) / sprite->h;
35 gradient2 = (color3 - color2) / sprite->h;
36 left_dx = color1;
37 right_dx = color2;
38 
39 /* check clipping */
40 if (destination->clip) {
41 if (y1 < destination->ct) {
42 left_dx += gradient1 * (destination->ct - y1);
43 right_dx += gradient2 * (destination->ct - y1);
44 y1 = destination->ct;
45 }
46 y2 = MIN(y2, destination->cb);
47 x1 = MAX(x1, destination->cl);
48 x2 = MIN(x2, destination->cr);
49 }
50 
51 for (current_y=y1; current_y<y2; current_y++) {
52 /* set up horizontal gradient for line */
53 double horizontal_gradient = (right_dx - left_dx) / sprite->w;
54 double color = left_dx;
55 
56 /* more clip checking */
57 if ((destination->clip) && (x1 < destination->cl)) {
58 color += horizontal_gradient * (destination->cl - x1);
59 }
60 
61 #ifdef GFX_HAS_VGA
62 
63 /* modex version */
64 if (is_planar_bitmap(destination)) {
65 addr = ((unsigned long)destination->line[current_y]<<2) + x1;
66 for (i=x1; i<x2; i++) {
67 if (sprite->line[current_y-y1][i-x1]) {
68 outportw(0x3C4, (0x100<<(i&3))|2);
69 pixel = color_map->data[(int)color][sprite->line[current_y-y1][i-x1]];
70 bmp_write8(addr>>2, pixel);
71 }
72 color += horizontal_gradient;
73 addr++;
74 }
75 }
76 else {
77 
78 #else
79 
80 {
81 
82 #endif
83 
84 /* draw routines for all linear modes */
85 switch (bitmap_color_depth(destination)) {
86 
87 #ifdef ALLEGRO_COLOR8
88 
89 case 8:
90 addr = bmp_write_line(destination, current_y) + x1;
91 for (i=x1; i<x2; i++) {
92 if (sprite->line[current_y-y1][i-x1]) {
93 pixel = color_map->data[(int)color][sprite->line[current_y-y1][i-x1]];
94 bmp_write8(addr, pixel);
95 }
96 color += horizontal_gradient;
97 addr++;
98 }
99 break;
100 
101 #endif
102 
103 #ifdef ALLEGRO_COLOR16
104 
105 case 15:
106 case 16:
107 addr = bmp_write_line(destination, current_y) + x1*sizeof(short);
108 for (i=x1; i<x2; i++) {
109 pixel = ((unsigned short *)sprite->line[current_y-y1])[i-x1];
110 if (pixel != destination->vtable->mask_color) {
111 if (bitmap_color_depth(destination) == 16)
112 pixel = _blender_func16(pixel, _blender_col_16, (int)color );
113 else
114 pixel = _blender_func15(pixel, _blender_col_15, (int)color );
115 bmp_write16(addr, pixel);
116 }
117 color += horizontal_gradient;
118 addr += sizeof(short);
119 }
120 break;
121 
122 #endif
123 
124 #ifdef ALLEGRO_COLOR24
125 
126 case 24:
127 addr = bmp_write_line(destination, current_y) + x1*3;
128 for (i=x1; i<x2; i++) {
129 bmp_select(sprite);
130 pixel = bmp_read24((unsigned long)(sprite->line[current_y-y1] + (i-x1)*3));
131 bmp_select(destination);
132 if (pixel != MASK_COLOR_24) {
133 pixel = _blender_func24(pixel, _blender_col_24, (int)color );
134 bmp_write24(addr, pixel);
135 }
136 color += horizontal_gradient;
137 addr += 3;
138 }
139 break;
140 
141 #endif
142 
143 #ifdef ALLEGRO_COLOR32
144 
145 case 32:
146 addr = bmp_write_line(destination, current_y) + x1*sizeof(int32_t);
147 for (i=x1; i<x2; i++) {
148 pixel = ((unsigned long *)sprite->line[current_y-y1])[i-x1];
149 if (pixel != MASK_COLOR_32) {
150 pixel = _blender_func32(pixel, _blender_col_32, (int)color );
151 bmp_write32(addr, pixel);
152 }
153 color += horizontal_gradient;
154 addr += sizeof(int32_t);
155 }
156 break;
157 
158 #endif
159 
160 }
161 }
162 
163 left_dx += gradient1;
164 right_dx += gradient2;
165 }
166 
167 bmp_unwrite_line(destination);
168}

Evert

I think this is a nice way to encourage more people to have a look at and work on Allegro.
I'm not sure I fully agree that cosmetic rewrites are nescessarily a good thing, because it will clutter the change log and make it harder to go back to older revisions (which is why tab characters haven't been purged in one big sweep a while back). That doesn't nescessarily involve comment blocks or top line comments though. Anyway, we'll see how it goes, I suppose...

Peter Wang

I am all for getting more people involved with Allegro, but this is silly. Work on 4.3 instead. There's plenty to do.

kazzmir

For the changelog a single line description would suffice:

  • The following functions were changed cosmetically: foo, bar, baz

Quote:

I am all for getting more people involved with Allegro, but this is silly. Work on 4.3 instead. There's plenty to do.

4.3 is still under design and while it would be great to have more opinions on the design thats not something many people can readily contribute to simply because its fairly difficult and you already have to have a deep understanding of Allegro to do so.

I thought people would be interested to simply rewrite code so that it looks better and potentially spot any bugs. So far no one has done so, maybe its too early or its Sunday, I dont know.

Shouldnt Allegro strive to be perfect in every way?

Peter Wang
Quote:

  • The following functions were changed cosmetically: foo, bar, baz

Evert is talking about tracing when lines were last changed, e.g. using `svn annotate'. Large scale cosmetic changes mess that up for dubious value.

Btw, it takes far too much energy on this forum to check what changes have been made, and whether anything was broken. All I can tell is that you've renamed some variables and added whitespace where there shouldn't be.

         pixel = _blender_func24(pixel, _blender_col_24, (int)color );

Patching, verifying and committing will take even more energy. Count me out.

kazzmir

Well heres my last attempt for the night. Unfortunately its about 5 times slower than the current version, mostly because all the macros have been wrapped into functions. Of course I wouldnt want to slow down any functions so if there are any ideas how to speed this generic version up Id appreciate it.

1/* select a line from the bitmap for direct memory access
2 */
3static uintptr_t get_line( BITMAP * bitmap, int y, int x ){
4 switch (bitmap_color_depth(bitmap)) {
5 case 8:
6 return bmp_write_line(bitmap, y) + x;
7 case 15:
8 case 16:
9 return bmp_write_line(bitmap, y) + x*sizeof(short);
10 case 24:
11 return bmp_write_line(bitmap, y) + x*3;
12 case 32:
13 return bmp_write_line(bitmap, y) + x*sizeof(int32_t);
14 default:
15 return 0;
16 }
17}
18 
19/* read one pixel from the line pointer
20 */
21static int read_pixel( BITMAP * bitmap, int y, int x ){
22 switch (bitmap_color_depth(bitmap)) {
23 case 8:
24 return bitmap->line[y][x];
25 case 15:
26 case 16:
27 return ((unsigned short *)bitmap->line[y])[x];
28 case 24:
29 return bmp_read24((uintptr_t)(bitmap->line[y] + x*3));
30 case 32:
31 return ((unsigned long *)bitmap->line[y])[x];
32 default:
33 return 0;
34 }
35}
36 
37static void write_pixel( BITMAP * bitmap, uintptr_t address, int pixel ){
38 switch (bitmap_color_depth(bitmap)) {
39 case 8:
40 bmp_write8(address, pixel);
41 break;
42 case 15:
43 case 16:
44 bmp_write16(address, pixel);
45 break;
46 case 24:
47 bmp_write24(address, pixel);
48 case 32:
49 bmp_write32(address, pixel);
50 }
51}
52 
53/* return the size of 1 pixel in the line pointer
54 */
55static int get_address_size( BITMAP * bitmap ){
56 switch (bitmap_color_depth(bitmap)) {
57 case 8:
58 return 1;
59 case 15:
60 case 16:
61 return sizeof(short);
62 case 24:
63 return 3;
64 case 32:
65 return sizeof(int32_t);
66 default:
67 return 1;
68 }
69}
70 
71/* this blender returns the pixel original value
72 */
73static unsigned long blender_color_map( unsigned long x, unsigned long y, unsigned long n ){
74 return color_map->data[n][x];
75}
76 
77/* find the correct blender for the given color depth
78 */
79static BLENDER_FUNC get_blender( BITMAP * bitmap ){
80 switch (bitmap_color_depth(bitmap)) {
81 case 8:
82 return blender_color_map;
83 case 15:
84 return _blender_func15;
85 case 16:
86 return _blender_func16;
87 case 24:
88 return _blender_func24;
89 case 32:
90 return _blender_func32;
91 default:
92 return blender_color_map;
93 }
94}
95 
96static int get_blender_color( BITMAP * bitmap ){
97 switch (bitmap_color_depth(bitmap)) {
98 case 8: return 0;
99 case 15: return _blender_col_15;
100 case 16: return _blender_col_16;
101 case 24: return _blender_col_24;
102 case 32: return _blender_col_32;
103 default: return 0;
104 }
105}
106 
107/* generic method for drawing one line of a gouraud shaded sprite
108 */
109static void do_gouraud_line( BITMAP * destination, BITMAP * sprite, int dest_y, int sprite_y, int x1, int x2, double color, double horizontal_gradient ){
110 uintptr_t addr = get_line( destination, dest_y, x1 );
111 int address_size = get_address_size( destination );
112 int i;
113 int pixel;
114 BLENDER_FUNC blender = get_blender( destination );
115 int blender_color = get_blender_color( destination );
116 for (i=x1; i<x2; i++) {
117 bmp_select(sprite);
118 pixel = read_pixel(sprite,sprite_y,i-x1);
119 if ( pixel != destination->vtable->mask_color ) {
120 pixel = blender(pixel, blender_color, (int)color );
121 bmp_select(destination);
122 write_pixel(destination, addr, pixel);
123 }
124 color += horizontal_gradient;
125 addr += address_size;
126 }
127}
128 
129/* draw_gouraud_sprite:
130 * Draws a lit or tinted sprite, interpolating the four corner colors
131 * over the surface of the image.
132 * destination - BITMAP where the sprite will be drawn to
133 * sprite - BITMAP of the sprite to draw
134 * x_ - upper left hand x coordinate on destination
135 * y_ - upper left hand y coordinate on destination
136 * color1 - upper left hand color
137 * color2 - upper right hand color
138 * color3 - lower left hand color
139 * color4 - lower right hand color
140 */
141void _soft_draw_gouraud_sprite(BITMAP * destination, BITMAP *sprite, int x_, int y_, int color1, int color2, int color3, int color4)
142{
143 double gradient1;
144 double gradient2;
145 double left_dx;
146 double right_dx;
147 int x1 = x_;
148 int y1 = y_;
149 int x2 = x_ + sprite->w;
150 int y2 = y_ + sprite->h;
151 int i, current_y;
152 int pixel;
153 uintptr_t addr;
154 
155 ASSERT(destination);
156 ASSERT(sprite);
157 ASSERT(destination->vtable->color_depth == sprite->vtable->color_depth);
158 
159 bmp_select(destination);
160 
161 /* set up vertical gradients for left and right sides */
162 gradient1 = (color4 - color1) / sprite->h;
163 gradient2 = (color3 - color2) / sprite->h;
164 left_dx = color1;
165 right_dx = color2;
166 
167 /* check clipping */
168 if (destination->clip) {
169 if (y1 < destination->ct) {
170 left_dx += gradient1 * (destination->ct - y1);
171 right_dx += gradient2 * (destination->ct - y1);
172 y1 = destination->ct;
173 }
174 y2 = MIN(y2, destination->cb);
175 x1 = MAX(x1, destination->cl);
176 x2 = MIN(x2, destination->cr);
177 }
178 
179 for (current_y=y1; current_y<y2; current_y++) {
180 /* set up horizontal gradient for line */
181 double horizontal_gradient = (right_dx - left_dx) / sprite->w;
182 double color = left_dx;
183 
184 /* more clip checking */
185 if ((destination->clip) && (x1 < destination->cl)) {
186 color += horizontal_gradient * (destination->cl - x1);
187 }
188 
189 #ifdef GFX_HAS_VGA
190 
191 /* modex version */
192 if (is_planar_bitmap(destination)) {
193 addr = ((unsigned long)destination->line[current_y]<<2) + x1;
194 for (i=x1; i<x2; i++) {
195 if (sprite->line[current_y-y1][i-x1]) {
196 outportw(0x3C4, (0x100<<(i&3))|2);
197 pixel = color_map->data[(int)color][sprite->line[current_y-y1][i-x1]];
198 bmp_write8(addr>>2, pixel);
199 }
200 color += horizontal_gradient;
201 addr++;
202 }
203 }
204 else {
205 
206 #else
207 
208 {
209 
210 #endif
211 do_gouraud_line( destination, sprite, current_y, current_y - y1, x1, x2, color, horizontal_gradient );
212 }
213 
214 left_dx += gradient1;
215 right_dx += gradient2;
216 }
217 
218 bmp_unwrite_line(destination);
219}

HoHo
Quote:

Unfortunately its about 5 times slower than the current version

Forcing inlining might help but I'm not too sure about this.

In general I agree with Peter Wang. 4.2 series is pretty much "done" and all energy should go to 4.3. Most people don't know anything about Allegro source anyway so for them it doesn't matter much if they work on 4.2 or 4.3. Perhaps only 4.3 is a bit easier because of less garbage.

Fiddler
HoHo said:

In general I agree with Peter Wang. 4.2 series is pretty much "done" and all energy should go to 4.3. Most people don't know anything about Allegro source anyway so for them it doesn't matter much if they work on 4.2 or 4.3. Perhaps only 4.3 is a bit easier because of less garbage.

For 4.3 the problem is that little is known about what has to be done, what is already there, what is currently being worked on etc.

I am sure that if some specific tasks were posted in allegro.cc (for example: "implement the joystick driver for X") it would attract developers. If even one or two people responded and started contributing more seriously it would be worth it IMHO.

However, this means that a more or less finalised API is needed, at least for the core (event queues, error handling, driver API, I don't know what else). Unless this has been stabilized and made public somewhere, there is very little a non-developer can do to help.

Peter Wang

Ok, then. If anyone wants to finish off the mouse API and implementation, especially the Windows implementation, please tell me. It's sitting in a Subversion branch at the moment (I just need to merge the latest 4.3.0 changes into it). Any takers?

Also, it would be nice to have a force feedback API. Probably it will be a rather thin wrapper around whatever DirectX provides. Anyone with such a device want to work on that?

Chris Robinson has half a sound API designed and prototyped (if I remember). This would be quite a nice modular task to work on, I think.

The Mac port needs attention. Contact Peter Hull.

Thomas is working on his file hooks.

And then there's the display and graphics API... Elias and Evert seem to be jointly in charge of this. We were just discussing it recently on [AD].

gnolam

Are there actually any API (proposal) documents anywhere?

Fiddler

Take a look at the Allegro Wiki: http://awiki.tomasu.org/bin/view Although it seems it has been hit by spam bots recently, there is quite a lot of useful information there.

Also, the Allegro 5 documents (I think there is a link in the wiki to them). However, I don't think these are followed closely, if at all.

Evert
Quote:

However, I don't think these are followed closely, if at all.

It's certainly the idea that they're followed at least somewhat. I should point out though that some of the details were left blank or undecided in them.
It makes no sense to reinvent the wheel and try to come up with a new design if so much time, skill and discussion went into the existing documents.

Andrei Ellman

Actually, I think that the Allegro source could do with some more comments and clearer variable names. Although I think that re-writing the algorithms to be more readable, yet slower is going a bit too far. I recently fixed a bug in the Grabber, and found that I would have fixed it much faster had the source been more commented. Having a well-commented source would encourage more people to fiddle around with it.

Also, while we're on the subject of adding comments to Allegro source, now would be a good time to discuss the possibility of using a self-documenting sourcecode-comments based form of documentation such as Doxygen as a replacement for the current ._tx format. When I mentioned this on the [AD] list recently, I was told that Doxygen (along with it's alternatives) had already been discussed, so if anyone could link to the discussion, that would be appreciated.

The web page for Doxygen is at http://www.stack.nl/~dimitri/doxygen/, and a liat of alternatives is here

AE.

kazzmir

Did this get sent to the top or something? Anyway, I havent done much with this because I was waiting for 4.2.1 to come out.

About doxygen, I have no idea. It sounds nice, but the allegro docs are already so well written!

Thomas Fjellstrom
Quote:

Although it seems it has been hit by spam bots recently

Fixed all the ones I could find.

Please people, if you find any, TELL ME.

Thread #587133. Printed from Allegro.cc