about summary refs log tree commit diff
path: root/doc/Netpbm.programming
blob: 62e4c569e69ceb703606a55a5a4213d09c780e44 (plain) (blame)
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
This file is an attempt to give some basic guidelines for those who
wish to write new Netpbm programs.  The guidelines ensure:

  A) Your program functions consistently with the rest of the package.

  B) Your program works in myriad environments on which you can't test it.

  C) You don't miss an important detail of the Netpbm formats.

  D) Your program is immune to small changes in the Netpbm formats.

The easiest way to write your own Netpbm program is to take an
existing one, similar to the one you want to write, and to modify
it.  This saves a lot of time, and ensures conformity with these rules.
But pick a recent one (check the file modification date and the
doc/HISTORY file), because many things you see in old programs are
grandfathered in.  Pamtopnm is good example of a thoroughly modern
Netpbm program.  Pamcut is another good one.


COLLECTION PARAMETERS
---------------------

The philosophy that guides what goes into Netpbm and what doesn't is
one of maximum distribution of function.  Contributions to Netpbm are
refused very rarely, and usually because there is already some better
way to do what the contribution attempts to do, so that the
contribution would just make the package more confusing and thus
harder to use.  The second most common reason for declining a
contribution is that it falls well outside Netpbm's scope.  That too
would make the package more confusing and thus harder to use.

"Nobody will use that" is not a reason for leaving something out of
Netpbm.

"That's different from how other Netpbm programs work" is similarly 
not an excuse for refusing distribution of a feature.  (But it's a
good reason to accept a change to make a feature consistent!)

The standard for adding something to Netpbm isn't that it be perfect,
or even a net improvement.  The standard is that it not make Netpbm
worse.  Poor quality additions are made all the time, because a
program that doesn't work well is usually no worse than no program at
all.


DEVELOPMENT PROCESS
-------------------

Just email your code to the Netpbm maintainer.  Base it on the most recent
Netpbm Development release if possible.

The preferred form for changes to existing files is a unified diff patch --
the conventional Unix means of communicating code changes.  A Subversion
'svn diff' creates that easily.

You should update or create documentation too.  But if you don't, the Netpbm
maintainer will do the update before releasing your code.  The source files
for the documentation are the HTML files in the 'userguide' directory of the
Netpbm Subversion repository: https://svn.code.sf.net/p/netpbm/code/userguide.
The identical files are at http://netpbm.sourceforge.net/doc/ .

There are some automated tests in the package - shell scripts in files
named such as "pbmtog3.test".  You can use those to verify your
changes.  You should also add to these tests and create new ones.  But
most developers don't.


CODING GUIDELINES
-----------------

The Netpbm maintainer accepts programs that do not meet these guidelines,
so don't feel you need to hold back a contribution because you wrote it
before you read these.

* See the Netpbm library documentation to see what library functions
  you should be using and how.  You can find it at:

  http://netpbm.sourceforge.net/doc/libnetpbm.html

* See the specifications of the Netpbm formats at:

  http://netpbm.sourceforge.net/doc/pbm.html
  http://netpbm.sourceforge.net/doc/pgm.html
  http://netpbm.sourceforge.net/doc/ppm.html
  http://netpbm.sourceforge.net/doc/pam.html

  but don't depend very much on these; use the library functions
  instead to read and write the image files.

* You should try to use the "pam" library functions, which are newer than the
  "pbm", "pgm", and "pnm" ones.  The "pam" functions read and write all four
  Netpbm formats and make code easier to write and to read.

* A new program should generate PAM output.  Note that any program that uses
  the modern Netpbm library can read PAM even if it was designed for the older
  formats.  For other programs, one can convert PAM to the older formats
  with 'pamtopnm'.

* If your program involves transparency (alpha masks), you have two
  alternatives.  The older method is to use a separate PGM file for the
  alpha mask, in the style of Pngtopnm/Pnmtopng and Giftopnm/Pnmtogif,
  and use command syntax like those programs.  See the PGM format spec
  for details.
  
  A newer method involves a PAM image with an alpha plane (tuple type
  RGB_ALPHA, etc.).  This is preferred because it's easier for users
  to pipe stuff around.  Pamtotga is an example of this.

* Declare all your symbols except 'main' as static so that they won't
  cause problems to other programs when you do a "merge build" of 
  Netpbm.

* Always start the code in main() with a call to p?m_init().

* Use shhopt for option processing.  i.e. call optParseOptions3().
  This is really easy if you just copy the parseCommandLine() function
  and struct cmdlineInfo declaration from pamcut.c and adapt it to 
  your program. 

  When you do this, you get a command line syntax consistent with all the
  other Netpbm programs, you save coding time and debugging time, and it
  is trivial to add options later on.

  Do not use shhopt's short option alternative unless you need to be
  compatible with another program that has short options.  Short
  options are traditional one-character Unix options, which can be
  stacked up like "foo -cderx myfile", and they are far too unfriendly
  to be accepted by the Netpbm philosophy.  Note that long options in
  shhopt can always be abbreviated to the shortest unique prefix, even
  one character.

  In messages and examples in documentation, always refer to an option
  by its full name, not the abbreviation you usually use.  E.g. if you have
  a "-width" option which can be abbreviated "-w", don't use -w in 
  documentation.  -width is far clearer.
  
* Use pm_error() and pm_message() for error messages and other messages.
  Note that the universal -quiet option (processed by p?m_init()) causes
  messages issued via pm_message() to be suppressed.  And that's what
  Netpbm's architecture requires.

* The argument to pm_error() and pm_message() is a string of text, not
  a formatted message.  Don't put newlines or other formatting characters
  in it.  These subroutines are designed to be flexible in how they issue
  the messages.

* Use MALLOCARRAY() to allocate space for an array and MALLOCVAR to allocate
  space for a non-array variable.  In fact, you usually want to save some
  programming tedium and use the NOFAIL versions of these (they never fail
  because they abort the program if memory is not available).  These avoid
  array bounds violations.

* Use pm_tmpfile() to make a temporary file.  This avoids races that can
  be used to compromise security.

* Properly use maxvals.  As explained in the format specifications, every
  sample value in an image must be interpreted relative to the image's
  maxval.  For example, a pixel with value 24 in an image with maxval 24
  is the same brightness as a pixel with value 255 in an image with a
  maxval of 255.

  255 is a popular maxval (use the PPM_MAXMAXVAL etc. macros) because it
  makes samples fit in a single byte and at one time was the maximum 
  possible maxval in the format.

  Note that the Pamdepth program converts an image from one maxval to another.

* Don't include extra function.  If you can already do something by 
  piping the input or output of your program through another Netpbm
  program, don't make an option on your program to do it.  That's the
  Netpbm philosophy -- simple building blocks.

  Similarly, if your program does two things which would be useful
  separately, write two programs and advise users to pipe them
  together.  Or add a third program that simply runs the first two.

* Your program should, if appropriate, allow the user to use Standard
  Input and Output for images.  This is because Netpbm users commonly
  use pipes.  As an alternative to Standard Input, the user should be able
  to name the input file as a non-option program argument.  But the user
  should not be able to name the output file if Standard Output is
  feasible.  That's just the Netpbm tradition.

* Don't forget to write a proper html documentation page.  Get an
  example to use as a template from
  <http://netpbm.sourceforge.net/doc/directory.html>.

* No Netpbm source code may contain tab characters.  If you
  generate such a file, the Netpbm maintainer will convert it to spaces
  and possibly change all your indenting at the same time.  Tabs are not
  appropriate for code that multiple people must edit because they don't
  necessarily look the same in every editing environment, and in most
  editing environments, you can't even tell which spaces are actually in
  the file and which came from the editor, expanding tabs.  Spaces, on
  the other hand, look the same for everyone.  Modern editors let you
  compose code just as easily with spaces as with tabs.

* Limit your C code to original ANSI C.  Not C99.  Not C89.  Not C++.  Don't
  use // for comments or put declarations in the interior of block.
  Interpreted languages are OK, with Perl being preferred.


DISCONTINUED CODING GUIDELINES
------------------------------

Here are some things you will see in old Netpbm programs, but they are
obsolete and you shouldn't propagate them into a new program:

* Tolerating non-standard C libraries.  You may assume all users have
  ANSI and POSIX compliant C libraries.  E.g. use strrchr() and forget
  about rindex().

* pm_keymatch() for option processing.  Use shhopt instead, as described
  above.

* optParseOptions() and optParseOptions2().  These are obsoleted
  by optParseOptions3(), which is easier to use and more powerful.

* K&R C function declarations.  Always use ANSI function declarations
  exclusively (e.g. use 

      void foo(int arg1){} 

  instead of 

       void foo(arg1)
           int arg1;
           {}

* for (col=0, xP = row; col < cols; col++, xP++)
      foo(*xP);

  This was done in the days before optimizing compilers because it ran faster
  than the more readable:

    for (col=0; col < cols; col++)
        foo(row[col]);

  Now, just use the latter.


CODING STYLE
------------

We do not generally mandate any basic coding style in these
guidelines.  Where you put your braces is a matter of personal style
and other people working with your code will just have to live with
it.  However, people working with your code might just recode it into
another style if it makes it easier for them to read the code and
have confidence in their changes.

But if you have no preference, the following is what the Netpbm
maintainer currently prefers.  Essentially, it is clean, elegant
computer science-type code rather than brute force engineering-type
code.  Modular and structured above all.

* No gotos.  This includes all variations of goto:  break, continue, leave,
  mid-function return.  An exception: aborting the entire program in the 
  middle of something is allowed.

* No functions with side effects.  This is a tough one, since
  functions with side effects is highly traditional C.  In fact, the
  creators of C didn't even have a concept of a subroutine.  However,
  the average human brain, especially one trained in math, cannot
  easily follow code where a function both computes a value and does
  other stuff.
  
  For the purpose of this discussion, we say that a C function whose return
  type is void is not a "function," but a "subroutine."

  So a function should never change anything in the program.  All it does
  is compute a value.

  Where you have to call an external function that has side effects (virtually
  anything in the standard C library, for example), put it in a simple
  assignment function that captures its return value and otherwise consider
  it a subroutine:

    rc = fopen(...)
    if (rc ...)

* No reuse of variables.  Most variables should be set at most once.
  Don't say "A is 5" and then later on, "no, A is 6."  A reader
  should be able to take that first "A is 5" as a statement of fact
  and not hunt for all the places it might be contradicted.  A
  variable that represents the state of execution of an algorithm is an
  obvious exception.

  Use "const" everywhere you possibly can.

  Especially never modify the argument of a function.  All function arguments
  should be "const".

  Don't use initializers except for with constants.  The first value a
  variable has is a property of the algorithm that uses the variable, not of
  the variable.  A reader expects to see the whole algorithm in one place, as
  opposed to having part of it hidden in the declaration of the algorithm
  variables.

* Avoid global variables.  Sometimes, a value is truly global and
  passing it as a parameter just muddies the code.  But most of the
  time, any external value to which a function refers should be one of
  its arguments.  

  Declare a variable in the most local scope possible.

* Keep subroutines small.  Generally under 50 lines.  But if the
  routine is a long sequence of simple, similar things, it's OK for it
  run on ad infinitem.

* Use the type "bool" for boolean variables.  "bool" is defined in Netpbm
  headers.  Use TRUE and FALSE as its values.

* Do not say "if (a)" when you mean "if (a != 0)".  Use "if (a)" only if a is
  a boolean variable.  Or where it's defined so that zero is a special value
  that means ("doesn't exist").

* Do multiword variable names in camel case: "multiWordName".  Underscores
  waste valuable screen real estate.

* If a variable's value is a pointer to something, it's name should reflect
  that, as opposed to lying and saying the value is the thing pointed to.  A
  "P" on the end is the conventional way to say "pointer to."  E.g. if a
  variable's value is a pointer to a color value, name it "colorP", not
  "color".

* Put "const" as close as possible to the thing that is constant.
  "int const width", not "const int width".  When pointers to
  constants are involved, it makes it much easier to read.

* Free something in the same subroutine that allocates it.  Have exactly 
  one free per allocate (this comes naturally if you eliminate gotos).