Bug 693115 - PDF interpreter mishandles SMasks.
Summary: PDF interpreter mishandles SMasks.
Status: NOTIFIED FIXED
Alias: None
Product: Ghostscript
Classification: Unclassified
Component: PDF Interpreter (show other bugs)
Version: master
Hardware: PC Windows 7
: P1 critical
Assignee: Ray Johnston
URL:
Keywords:
Depends on:
Blocks: 692870
  Show dependency tree
 
Reported: 2012-06-12 17:00 UTC by Robin Watts
Modified: 2014-02-17 04:43 UTC (History)
3 users (show)

See Also:
Customer: 700
Word Size: ---


Attachments
test_smask.pdf (2.62 KB, application/pdf)
2012-06-12 17:16 UTC, Robin Watts
Details
test_smask2.pdf (2.79 KB, application/pdf)
2012-06-12 17:37 UTC, Robin Watts
Details
CS3_test_smask2.png (5.28 KB, image/png)
2013-02-25 18:46 UTC, Ray Johnston
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Robin Watts 2012-06-12 17:00:16 UTC
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.
Comment 1 Robin Watts 2012-06-12 17:16:00 UTC
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.
Comment 2 Robin Watts 2012-06-12 17:37:08 UTC
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.
Comment 3 Alex Cherepanov 2012-06-14 11:50:59 UTC
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.
Comment 4 Robin Watts 2012-06-14 12:48:18 UTC
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?
Comment 5 Henry Stiles 2012-07-30 15:09:06 UTC
This is not a regression so having this a blocker seems excessive.  Also changed to P2 as it is a customer related problem.
Comment 6 Ray Johnston 2013-02-25 18:46:29 UTC
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).