pxperfect.pro
11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
;+
; NAME:
; PXPERFECT
;
; AUTHOR:
; Craig B. Markwardt, NASA/GSFC Code 662, Greenbelt, MD 20770
; craigm@lheamail.gsfc.nasa.gov
;
; PURPOSE:
; Postscript device settings for "pixel perfect" matching to screen plot layout
;
; CALLING SEQUENCE:
; PS_EXTRA = PXPERFECT([/LANDSCAPE], [/INCHES], [THICK_FACTOR=tf], [SCALE=s])
;
; DESCRIPTION:
;
; PXPERFECT is designed to achieve nearly "pixel perfect" matching
; of plot layout when rendering a plot on the IDL Postscript device.
; The dimensions and character sizes of the current display device
; are used to construct a group of settings which can be passed to
; the Postscript device driver using the DEVICE procedure.
;
; The key capability of PXPERFECT is to determine the size of fonts
; to match the on-screen size. Once this size is determined, IDL
; will adjust other Postscript output dimensions such as plot
; margins, font sizes, symbol sizes, etc, to match exactly the
; on-screen layout.
;
; The current direct graphics device must be a screen display
; device, such as 'X' or 'WIN'. The user wouuld first call
; PXPERFECT() to determine what the appropriate settings for the
; Postscript device would be. At a later time, the user may switch
; to the Postscript device to render the plot for output.
;
; This is the approximate order of calling:
; ;; Prerequisite: current graphics device is display device
; SET_PLOT, 'X' ;; or 'WIN'
; TF = 1.0 ;; Thickness factor (see "Dealing with line thickness" below)
;
; ;; User adjusts the plot layout to taste
; PLOT, ... data ..., thick=1.0*TF
;
; ;; Capture layout settings and then initialize Postscript
; PS_EXTRA = PXPERFECT(THICK_FACTOR=TF)
; SET_PLOT, 'PS' ;; NOTE: PXPERFECT() called *before* SET_PLOT
; DEVICE, _EXTRA=PS_EXTRA
;
; ;; User calls same plot command(s) with no changes to layout
; PLOT, ... data ..., thick=1.0*TF
;
; ;; Close output plot file
; DEVICE, /CLOSE
;
; If the display window is resized, then PXPERFECT should be called
; again to capture the new layout settings.
;
; The value returned by PXPERFECT is an IDL structure, with fields
; that are meant to be passed to the IDL Postscript driver using the
; DEVICE, _EXTRA=(...) statement.
;
; Output Page Size. The dimensions of the Postscript output page
; will be set so that the output page exactly matches the displayed
; plot window. The algorithm does assume that the user's display
; density settings are correct, in particular, that !D.X_PX_CM is a
; correct reflection of the number of screen pixels per centimeter.
;
; The user can adjust the output page size in several ways.
; PXPERFECT accepts the XSIZE, YSIZE and SCALE_FACTOR keywords and
; interprets them in the same way that the standard procedure DEVICE
; does. In order to maintain the same layout, the user may specify
; XSIZE or YSIZE, but not both. If both XSIZE and YSIZE are
; specified, then the aspect ratio of the output page will not match
; the on-screen display window, and pixel-perfect layout matching
; cannot be attained in that case.
;
; Dealing with Line Thickness. The Postscript device has a
; different base line thickness compared to most on-screen display
; devices. The value returned in the THICK_FACTOR keyword is a
; scale factor which should be multiplied by all thicknesses when
; rendering to Postscript.
;
; Thus, if the desired on-screen line width is 2.0 units, then the
; Postscript line thickness will be 2.0*TF, where TF is the value
; returned in the THICK_FACTOR keyword.
;
; Passing Other Keywords to DEVICE. PXPERFECT() accepts all the
; keywords that the DEVICE procedure accepts. Any keywords that do
; not specifically affect PXPERFECT's operation are passed
; along to the output structure, and hence to DEVICE.
;
;
; POSITIONAL PARAMETERS:
;
; NONE
;
; KEYWORD PARAMETERS:
;
; INCHES - set this keyword if Postscript dimensions are to be
; specified in inches instead of centimeters. This keyword
; also specifies the units of the user-passed keywords
; XSIZE, YSIZE, XOFFSET, YOFFSET.
; Default: not set (i.e. centimeter units)
;
; LANDSCAPE - set this keyword to indicate landscape orientation instead
; of portrait orientation.
; Default: not set (i.e. portrait orientation)
;
; SCALE_FACTOR - a unitless scale factor which is used to scale the
; size of the Postscript page output. By default the
; output page size in inches or centimeters is scaled
; to match the on-screen size. Use this keyword to
; increase (>1.0) or decrease (<1.0) the size of the
; output page.
; Default: 1.0
;
; THICK_FACTOR - upon output, THICK_FACTOR, which contain a factor
; which should be used to multiply all line width
; thicknesses.
;
; XSIZE, YSIZE - user-requested output page size which may differ
; from default Postscript page size. The user should
; specify either XSIZE or YSIZE, but not both;
; specifying both will cause the output page layout to not
; exactly match the on-screen graphic layout. Also,
; XSIZE or YSIZE override the SCALE_FACTOR keyword.
; Default: not set (i.e. output page size will match
; on-screen size)
;
; XOFFSET, YOFFSET - user-requested page offsets.
; Default: plot at origin (landscape plots are
; adjusted appropriately)
;
; RETURNS:
;
; PXPERFECT returns a single IDL structure, which is meant to be
; passed to the IDL Postscript device. This structure is passed
; using the DEVICE procedure and the _EXTRA mechanism.
;
; SIDE EFFECTS:
;
; The graphics device must be set to a screen display device when
; PXPERFECT is called.
;
; Upon the first call to PXPERFECT, the graphics device is
; momentarily switched to 'PS' in order to retrieve Postscript
; device settings.
;
; EXAMPLE:
; ;; Plot to screen display
; PLOT, FINDGEN(10), charsize=1.5
;
; ;; Initialize Postscript
; PS = PXPERFECT()
; SET_PLOT, 'PS'
; DEVICE, _EXTRA=PS, FILENAME='outfile.ps'
;
; ;; Same plot, to Postscript page
; PLOT, FINDGEN(10), charsize=1.5
;
; ;; Finish output
; DEVICE, /CLOSE
;
;
; SEE ALSO:
;
; DEVICE, SET_PLOT
;
; MODIFICATION HISTORY:
; Written, CM, 2010
; Documented, CM, 2011-04-15
; Square bracket array notation, CM, 2011-12-21
; Logic fix for case when XSIZE & YSIZE given together, CM, 2012-09-27
;
; $Id: pxperfect.pro,v 1.5 2012/09/27 23:18:54 cmarkwar Exp $
;
;-
; Copyright (C) 2010-2011,2012 Craig Markwardt
; This software is provided as is without any warranty whatsoever.
; Permission to use, copy, modify, and distribute modified or
; unmodified copies is granted, provided this copyright and disclaimer
; are included unchanged.
;-
function pxperfect_ps_px_cm
COMPILE_OPT strictarr
common pxperfect_ps_px_cm_common, ps_px_cm
if n_elements(ps_px_cm) NE 0 then return, ps_px_cm
old_d = !d.name
set_plot, 'PS'
ps_px_cm = !d.x_px_cm
set_plot, old_d
return, ps_px_cm
end
function pxperfect, landscape=landscape, scale_factor=scale0, $
thick_factor=tf, inches=inches, $
xsize=xsize0, ysize=ysize0, xoffset=xoffset0, yoffset=yoffset0, $
color=color0, bits_per_pixel=bpp0, $
_EXTRA=extra
COMPILE_OPT strictarr
if !d.name EQ 'PS' then $
message, 'ERROR: you must run PXPERFECT before setting the PS driver'
if n_elements(scale0) EQ 0 then scale = 1d $
else scale = scale0[0]
;; Constants
;; The pixel densities of the Postscript and current display driver
;; in units of pixels per centimeter.
disp_px_cm = !d.x_px_cm ;; Expected px_cm for current display device
ps_px_cm = pxperfect_ps_px_cm() ;; Expected px_cm for postscript device
;; Global constants
cm_in = 2.54d ;; [centimeters/inch]
;; Conversion to inches if necessary
;; [in/cm] [cm/cm]
in = keyword_set(inches) ? (1/cm_in) : 1
;; Thickness factor to be used with subsequent plots
tf = 3 * scale
;; Determine the "scale" factor between screen pixels and IDL
;; postscript display pixels. This will be used to scale XSIZE,
;; YSIZE and character size.
sf = (disp_px_cm)/scale ;; [disp pix/cm]
;; Postscript character size to match exactly the size of 'X'
;; characters. We take the on-screen pixels, and convert to
;; Postscript pixel units.
;; Units are: [disp pix * (ps pix / cm) / (disp pix / cm)
ps_character_size = [!d.x_ch_size, !d.y_ch_size] * ps_px_cm / sf ;; [ps pix]
;; Compute the size of the output postscript page, assuming that we
;; will match the on-screen size.
;; Units are: [(disp pix)/(disp pix/cm)] (with optional conversion to inches)
xsize = !d.x_size / sf * in ;; [cm or in]
ysize = !d.y_size / sf * in ;; [cm or in]
;; If the user requested XSIZE, YSIZE or both, then rescale the
;; output page to match. Of course we also need to scale the
;; character size as well.
rescale_fact = 1
if n_elements(xsize0) NE 0 AND n_elements(ysize0) NE 0 then begin ;; XSIZE and YSIZE
;; Can't achieve original on-screen ratio, but we try to
;; make the character size come out as close as possible.
rescale_fact = sqrt(xsize0[0]*ysize0[0]/xsize/ysize)
xsize = xsize0[0]
ysize = ysize0[0]
ps_character_size = ps_character_size * rescale_fact
endif else if n_elements(xsize0) NE 0 then begin ;; XSIZE only
rescale_fact = xsize0[0]/xsize
xsize = xsize0[0]
ysize = ysize * rescale_fact
ps_character_size = ps_character_size * rescale_fact
endif else if n_elements(xsize0) NE 0 then begin ;; YSIZE only
rescale_fact = ysize0[0]/ysize
xsize = xsize * rescale_fact
ysize = ysize0[0]
ps_character_size = ps_character_size * rescale_fact
endif
;; User-requested XOFFSET or YOFFSET
xoffset = 0
if n_elements(xoffset0) NE 0 then xoffset = xoffset0[0]
yoffset = (keyword_set(landscape) ? xsize : 0) ;; Special case for landscape
if n_elements(yoffset0) NE 0 then yoffset = yoffset0[0]
;; Default settings for color are: "yes, 8-bit color!" (but allow
;; user override)
color = 1
if n_elements(color0) NE 0 then color = color0[0]
bpp = 8
if n_elements(bpp0) NE 0 then bpp = bpp0[0]
;; Set line width equal to one screen pixel width
;; (and divide by standard PS line width of 10 postscript pixels)
;; NOTE: doesn't work as expected since DEVICE, OUTPUT=string
;; always creates a new page. Sigh.
one_pix_width = 1d * ps_px_cm / sf * rescale_fact / 10d
ps_thickness_cmd = $
'/sys_setlinewidth /setlinewidth load def '+$
'/setlinewidth { '+$
string(one_pix_width,format='(D0)')+' mul /setlinewidth '+$
'} bind def '
;; DEVICE structure which enables these capabilities
ps = {xsize: xsize, xoff: xoffset, $
ysize: ysize, yoff: yoffset, $
set_character_size: ps_character_size, $
inches: keyword_set(inches), color: color, bits_per_pixel: bpp, $
landscape: keyword_set(landscape), $
scale_factor: 1.0 $
}
;; Combine with any other Postscript settings
if n_elements(extra) NE 0 then ps = create_struct(ps, extra)
return, ps
end