While investigating bug 692870, I've spotted a problem with the way the ghostscript PDF interpreter handles SMasks. Consider the following PDF stream: /smask1 gs /red Do /blue Do /smask2 gs /green Do Where: /smask1 is an external graphics state that sets up a soft mask (in the example I have the mask is a transparency group containing a solid white square, with an overall transparency of 0.33) /red is a transparency group xobject that draws a red square. /blue is a transparency group xobject that draws a blue square. /smask2 is an external graphics state that sets up a soft mask (in the example I have the mask is a transparency group containing a solid white square, with an overall transparency of 0.5) /green is a transparency group xobject that draws a green square. When ghostscript comes to render this, the pdf interpreter sets the smasks 'just in time' before the rendering, and resets the smask before every object that ues it. So we actually see: 0 begin_transparency_mask \ 1 draw smask1 > Call this SMask M1 2 end_transparency_mask / 3 begin_transparency_group 4 draw_red 5 end_transparency_group 6 begin_transparency_mask \ 7 draw smask1 > Call this SMask M2 8 end_transparency_mask / 9 begin_transparency_group 10 draw_green 11 end_transparency_group 12 begin_transparency_mask 13 draw smask2 14 end_transparency_mask 15 begin_transparency_group 16 draw_blue 17 end_transparency_group So, what are the problems with this? Firstly, and most obviously, rerendering smask1 before every object is slow. The SMask can be very complex, and/or we could be drawing many items under a single SMask. We should be able to just render the SMask once regardless of how many objects are painted under it. Secondly, due to Ghostscripts implementation of transparency masks, the first SMask (M1) does not stop being current until the end_transparency_mask of the second one is reached (line 8 above). This doesn't actually cause a problem, unless smask1 itself uses a transparency group, in which case the rendering of M2 ends up with its contents being masked by M1. After much discussion on irc, Ray, Michael and myself agreed that what we'd like to see was: 0 begin_transparency_group 1 begin_transparency_mask 2 draw smask1 3 end_transparency_mask 4 begin_transparency_group 5 draw_red 6 end_transparency_group 7 begin_transparency_group 8 draw_green 9 end_transparency_group 10 end_transparency_group 11 begin_transparency_group 12 begin_transparency_mask 13 draw smask2 14 end_transparency_mask 15 begin_transparency_group 16 draw_blue 17 end_transparency_group 18 end_transparency_group i.e. before the first use of an SMask we'd start a transparency group then render the SMask. This would remain in place for every object that is rendered under that SMask. When the SMask stops being current (or a new one starts) we'd then end that transparency group (and start a new one for the new SMask). This solves the inefficiency of repeatedly rendering masks, and solves the correctness issue of each new mask being rendered with the preceding one still in effect.
Created attachment 8681 [details] test_smask.pdf A file with a (slightly) more complex example: inline yellow square /smask1 gs /red Do /green Do /smask2 gs inline blue square This doesn't give any differences in rendering between acrobat/gs because smask1 does not itself use a group. You can see that smask1 gets drawn twice though. I will attempt to make an example file that shows a noticable rendering difference.
Created attachment 8682 [details] test_smask2.pdf Better example. In this, smask1 uses a transparency group to draw the white square. This gets composed with the first rendering of the smask, resulting in the alpha being much too light on the green (and later on the blue, I think). To see direct evidence of this working, change the RAW_DUMP and RAW_DUMP_AS_PAM lines at the top of gxblend.h to define them to both be 1, then run: gs/debugbin/gswin32c.exe test_smask2.pdf Then run the following bash fragment: for f in *.pam; do g=${f%.*}; echo $g; convert $g.pam $g.png ; done (in the msysgit window is fine - it needs convert from image magick on the path) This will give you a whole load of .png files. If you look at 11)eComposed.png it has a transparency of 28 (1/9th) rather than 84 (1/3rd) as we would hope it should have.
The proposed approach needs 2 passes over the content stream. .begintransparencygroup operator needs to know the bounding box of the drawing operations that follow it. Of course, one can use current clip path but this will increase memory consumption.
Ooh, good point. Current clippath sounds like the way to go to me - if we're high res, then we'll be using banding anyway, and so it'll end up being current clippath constrained to the current band, right?
This is not a regression so having this a blocker seems excessive. Also changed to P2 as it is a customer related problem.
Created attachment 9322 [details] CS3_test_smask2.png Output from Adobe Photoshop CS3 that so that we can see the target colors without the funky color management that Acrobat uses (I can't see how to get this output from Acrobat). The test_smask2.pdf has colors set using RGB, but Acrobat seems to want to "manage" them to some non-pure colors on the screen and when saving as a PNG. This is the target I am going to use for Ghostscript's png16m (RGB) output. I have a patch that fixes the yellow, green and red colors -- still working on the blue area (controlled by smask2).
Fixed in commit: http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=a4e3e55ca44958a72a3879a565255a2acba8eae5