[comp.lang.postscript] Advice sought: drawing boxes faster

cosell@bbn.com (Bernie Cosell) (01/13/90)

I'm a pretty rotten PostScript programmer: although I can get my stuff work
and it isn't too hard to read or debug, it is invariably BEASTLY slow.

Well, my current bit of hackery is sufficiently slow that it is driving me
NUTS, and I thought I'd try here and maybe learn some trick and techniques 
for making things go faster [and also, what sorts of things are slow].

My problem is that I need to draw a LOT of filled-in squares.  like  >100,000
of 'em.  What I have
done is done a translate and a scale so that the edges of every box are
at integer coordinates [that is, (x,y), (x+1,y+1) are the corners of every
box].  Then to get the box drawn in the required shade of gray, I defined:
    /box
    {
	setgray
	moveto
	0 1 rlineto
	1 0 rlineto
	0 -1 rlineto
	closepath
	fill
    } def

and then the file has zillions of lines of the form:

    33 42 .35 box
    34 42  1  box
    34 43  .28 box

etc.  This is all generated from a program I wrote on Unix, so I can change 
what 'box' is, its args, the general PostSCript environment, and stuff like
that.  About the only thing I can't change is the order that the boxes are
drawn --- that is determined as a by-product of another whole big bunch of
processing [to *generate* what color each box should be].  Actually, now that
I write this, I see that what I'm doing is VERY VERY similar to generating a
raster-scan image with "megapixels".

Anyhow, how best to speed it up?  I've played with the following ideas.

   1) do run-length encoding...  if a run of boxes in a row are the same
      shade of gray, my UNIX program can notice that and compress them
      all into a single "rectangle" call.  THis'll actually help a *lot*!

   2) Make as a special case to notice "setgray = 1.0" and just have the UNIX
      program ignore those guys.

both of the above I understand and I will be hacking into the code over the
weekend.  Now comes the sort of thing where I don't have a clue what might be
clever:
 
    3) The UNIX program knows what the entire "gray palette" that it'll need
       is before it starts out.  Is there some way to "prebuild" a
       'sample square' of each shade of gray and have "box" just become
       "dump the square <there>", instad of having to calculate and fill
       the square each time?
    4) Can the above idea be extended to non-uniform areas?  For example,
       if I had a run of boxes with grays  .6, .6, .4, .4, .3, and that
       was repeated a hundred times here and there around the image,
       I can detect those kinds of sequences in my UNIX program, but is
       there some clever way to "precompile" regions like that?  [something
       like a BLIT, for example, might be useful for something like this].

Are there other general techniques for doing this kind of thing that I"m just
not thinking about right at all?   Thanks!!

   __
  /  )                              Bernie Cosell
 /--<  _  __  __   o _              BBN Sys & Tech, Cambridge, MA 02238
/___/_(<_/ (_/) )_(_(<_             cosell@bbn.com

kevin@kosman.UUCP (Kevin O'Gorman) (01/14/90)

In article <50851@bbn.COM> cosell@BBN.COM (Bernie Cosell) writes:
>I'm a pretty rotten PostScript programmer: although I can get my stuff work
>and it isn't too hard to read or debug, it is invariably BEASTLY slow.
 ....
>Anyhow, how best to speed it up?  I've played with the following ideas.
>
 ....
>    3) The UNIX program knows what the entire "gray palette" that it'll need
>       is before it starts out.  Is there some way to "prebuild" a
>       'sample square' of each shade of gray and have "box" just become
>       "dump the square <there>", instad of having to calculate and fill
>       the square each time?

This looks like a case for building a font with a few square "characters"
and letting postscript cache them.

It seems that with an alphabet of boxes in various shades of gray, you
would be able to do what you want and let the most optimized part of
postscript do the work for you.


-- 
Kevin O'Gorman ( kevin@kosman.UUCP, kevin%kosman.uucp@nrc.com )
voice: 805-984-8042 Vital Computer Systems, 5115 Beachcomber, Oxnard, CA  93035
Non-Disclaimer: my boss is me, and he stands behind everything I say.

wcs) (01/14/90)

In article <50851@bbn.COM> cosell@BBN.COM (Bernie Cosell) writes:
] My problem is that I need to draw a LOT of filled-in squares.
] like  >100,000 of 'em.  ... [(x,y), (x+1,y+1) are the corners of every box]
]     /box
]     {
] 	setgray
] 	moveto
] 	0 1 rlineto
] 	1 0 rlineto
] 	0 -1 rlineto
] 	closepath
] 	fill
]     } def
] and then the file has zillions of lines of the form:
]     33 42 .35 box
]     34 42  1  box

Well, I have only epsilon experience with Postscript programming, so
these ideas mostly will be about programming techniques,
and any syntax presented below will be incorrect :-)
1) use "bind def", not just "def" - you're always using the same
	basic routines, and you're not redefining them - so do the
	parsing and dictionary lookups for the routine once up front.
2) The important trick:
] About the only thing I can't change is the order that the boxes are drawn
	Not true - this is UNIX!  You're generating an ascii text file,
	with a simple structure on every line, so you can do
	standard UNIXy things like running it through sort and then
	through an awk/shell/C filter:  I assume your program generates
	boxes for almost the entire screen, and they're all the same size?
	Define a routine "nbox", which draws a box next to the
	previous one, using the syntax "grayness nbox" -
	it's the same program as box, only you do
		0 1 rmoveto
	instead of the "moveto" you had.  This means you won't have
	to transmit, parse, and evaluate the x and y parameters most
	of the time.  Make a filter like this pseudocode
		while read x y gray box ; do
			if ( x == oldx && y == oldy + 1 )
			then print "gray nbox"
			else print "x y gray box" ; fi
			oldx = x ; oldy = y
			done
	This means you almost always need only one parameter, the
	gray level.  It also makes it easy to do your run length encoding: 
	either define a "ngbox" to do the next box in the same gray,
	or have your program further crunch the output to do rectangles.

3) Cut down your transmission times and parsing a little more by
	naming your routines "b" and "n" instead of "box" and "nbox";
	it probably matters more if you're on an RS232 line than if
	you're on parallel or Appletalk.  Use >9600 baud if possible.
-- 
# Bill Stewart AT&T Bell Labs 4M312 Holmdel NJ 201-949-0705 erebus.att.com!wcs

# ho95c has gone the way of all VAX/785s, so I'm now on erebus.att.com

jmr@nada.kth.se (Jan Michael Rynning) (01/15/90)

In article <50851@bbn.COM> cosell@BBN.COM (Bernie Cosell) writes:
>[...]  My problem is that I need to draw a LOT of filled-in squares.
>like  >100,000 of 'em.  [...]  About the only thing I can't change
>is the order that the boxes are drawn --- that is determined as a
>by-product of another whole big bunch of processing [to *generate*
>what color each box should be].  [...]

I would use a large array (representing the drawing area) in the UNIX
program, to record the grey values for all the squares.  Then, after
I finished the grey value computations, I would send the grey values
for all the squares to the printer using the ``image'' operator.  If
95 grey values were enough, I would code them as ASCII characters to
save transmission time.  It takes less than two minutes to send that
data to the printer over a 9600 baud line.  Using AppleTalk, it took
24 seconds to print on our Apple LaserWriter II NTX.  Here's my test
program:

%!

/linestring 80 string def

systemdict /currentpacking known {currentpacking true setpacking} if
[
  32 255 div /sub cvx 255 94 div /mul cvx
  /dup cvx 0.0 /lt cvx
  [/pop cvx 0.0] cvx bind
  [/dup cvx 1.0 /gt cvx [/pop cvx 1.0] cvx bind /if cvx] cvx bind
  /ifelse cvx
  currenttransfer /exec cvx
]
cvx bind settransfer
systemdict /currentpacking known {setpacking} if

72 dup translate 6.5 72 mul dup scale

317 317 8 [317 0 0 317 0 0] {currentfile linestring readline pop} bind image
Here comes the data, coded as ASCII, with ASCII code 32=black, and 127=white.
You don't want to see more than 100,000 bytes of test data on the net, do you?

showpage

Jan Michael Rynning,			jmr@nada.kth.se
Department of Numerical Analysis	If you can't fully handle domains:
  and Computing Science,		ARPA: jmr%nada.kth.se@uunet.uu.net
Royal Institute of Technology,		UUCP: {uunet,mcvax,...}!nada.kth.se!jmr
S-100 44 Stockholm,			BITNET: jmr@sekth
Sweden.					Phone: +46-8-7906288

batcheldern@hannah.enet.dec.com (Ned Batchelder) (01/17/90)

You've gotten some good suggestions.

The "image" idea is probably the biggest win, but only works if your squares
really do line up properly. If they have to overlap each other, or if the 
result is sparse in some way, it may not be the best way to go.

If the data doesn't lend itself to "image"ing for some reason, then a font is
the right way to go. But, you can't create a font with each character a
different shade of gray as suggested. The font cache only remembers shapes,
not shades. So your best bet is a font with different sizes and shapes of 
boxes, like your idea of run length encoding. You then have to set the gray
explicitly.

With text, the way to go fast is to get as much done with each "show" as
possible. So if you need to do a mix of two grays, you might be better off
doing one string that is all of the light gray, with spaces for the dark, and
then another that is all the dark with spaces for the light, and they'll
mesh together on the page.

Ned Batchelder, Digital Equipment Corp., BatchelderN@Hannah.enet.DEC.com