diff options
Diffstat (limited to 'doc/dither.texi')
-rw-r--r-- | doc/dither.texi | 504 |
1 files changed, 0 insertions, 504 deletions
diff --git a/doc/dither.texi b/doc/dither.texi deleted file mode 100644 index f0b363e..0000000 --- a/doc/dither.texi +++ /dev/null @@ -1,504 +0,0 @@ -@node Dithering, Weaving, Appendices, Appendices -@appendix Dithering -@cindex dithering - -The dithering code in @file{print-dither.c} attempts to reproduce -various shades of gray (or all colors) from only a few different inks -(black, cyan, magenta, yellow, and sometimes light cyan and light -magenta). The dots can't vary in darkness or size (except for certain -special printers), and so we need to lay down a certain fraction of dots -to represent each distinct level. - -This sounds straightforward; in practice, it isn't. Completely random -distribution of dots (simple probabilistic dithering) would create -grainy clumps and light spots. The smoothest pattern results from an -equidistant spacing of dots. Approximating this requires sophisticated -algorithms. We have two dithering algorithms, an ordered dither -algorithm that uses a grid (matrix) to decide whether to print, and a -modified Floyd-Steinberg error diffusion algorithm that uses a grid in a -slightly different way. - - -We currently have three dithering functions: - -@enumerate -@item -@code{dither_fastblack} produces pure black or white from a pre-dithered -input. This is used for two purposes: for printing pure black and white -very quickly (e. g. text), and for printing pre-screened monochrome -output that was rasterized externally. - -@item -@code{dither_black} produces black from grayscale input. The new -dither_black can produce either a single or multiple levels of black, -for printers supporting variable dot size. - -@item -@code{dither_cmyk} produces 3, 4, 5, 6, or 7 color output (CMY, CMYK, -CcMmYK, CcMmYy, CcMmYyK, or any variants). The new routine can handle -single or multiple levels of each color. -@end enumerate - -There is a choice of dithering algorithms. Four of them are based on a -basic error diffusion, with a few tweaks of my own. The other one is -`ordered'. However, they all share the basic operation in common. -First, the algorithm picks what kind of dot (if there are multiple dot -sizes and/or tones that may be picked) is the candidate to be printed. -This decision is made based on the darkness at the point being dithered. -Then, it decides whether the dot will be printed at all. What this is -based on depends upon which algorithm family we use. This is all -described in more detail below. - - -Ordered dithering works by comparing the value at a given point with the -value of a tiled matrix. If the value at the point is greater than the -value in the matrix, the dot is printed. The matrix should consist of a -set of evenly spaced points between 0 and the upper limit. The choice -of matrix is very important for print quality. A good dither matrix -will emphasize high frequency components, which distributes dots evenly -with a minimum of clumping. The matrices used here are all simple -matrices that are expanded recursively to create larger matrices with -the same kind of even point distribution. This is described below. - -Note that it is important to use different matrices for the two -sub-operations, because otherwise the choice about whether to print and -the choice of dot size will be correlated. The usual result is that the -print is either too dark or too light, but there can be other problems. - -Ordered dithering works quite well on single dot size, four color -printers. It has not been well tested on four color, variable dot size -printers. It should be avoided on six color printers. - - -Error diffusion works by taking the output error at a given pixel and -``diffusing'' it into surrounding pixels. Output error is the -difference between the amount of ink output and the input level at each -pixel. For simple printers, with one or four ink colors and only one -dot size, the amount of ink output is either 65536 (i. e. full output) -or 0 (no output). The difference between this and the input level is -the error. Normal error diffusion adds part of this error to the -adjoining pixels in the next column and the next row (the algorithm -simply scans each row in turn, never backing up). The error adds up -until it reaches a threshold (half of the full output level, or 32768), -at which point a dot is output, the output is subtracted from the -current value, and the (now negative) error is diffused similarly. - -Error diffusion works quite well in general, but it tends to generate -artifacts which usually appear as worm-like lines or areas of anomalous -density. I have devised some ways, as described below, of ameliorating -these artifacts. - -There are two sub-classes of error diffusion that we use here, `random' -and `hybrid'. One of the techniques that we use to ameliorate the -artifacts is to use a fuzzy threshold rather than the hard threshold of -half of the output level. Random error diffusion uses a pseudo-random -number to perturb the threshold, while hybrid error diffusion uses a -matrix. Hybrid error diffusion worked very poorly in 3.1.3, and I -couldn't figure out why until I found a bug. It now works very well. - -There is one additional variant (on both sub-classes), called `adaptive -hybrid' and `adaptive random'. The adaptive variant takes advantage of -the fact that the patterns that ordered dithering create are less -visible at very low densities, while the artifacts created by error -diffusion are more objectionable at low densities. At low densities, -therefore, it uses ordered dithering; at higher densities it uses error -diffusion. - - -Handling multiple output levels makes life a bit more complicated. In -principle, it shouldn't be much harder: simply figure out what the ratio -between the available output levels is and have multiple thresholds. In -practice, getting these right involves a lot of trial and error. The -other thing that's important is to maximize the number of dots that have -some ink. This will reduce the amount of speckling. More on this -later. - -The next question: how do we handle black when printing in color? Black -ink is much darker than colored inks. It's possible to produce black by -adding some mixture of cyan, magenta, and yellow---in principle. In -practice, the black really isn't very black, and different inks and -different papers will produce different color casts. However, by using -CMY to produce gray, we can output a lot more dots! This makes for a much -smoother image. What's more, one cyan, one magenta, and one yellow dot -produce less darkness than one black dot, so we're outputting that many -more dots. Better yet, with 6 or 7 color printers, we have to output even -more light ink dots. So Epson Stylus Photo printers can produce really -smooth grays---if we do everything right. The right idea is to use -CMY at lower black levels, and gradually mix in black as the overall -amount of ink increases, so the black dots don't really become visible -within the ink mass. - -Variable dot sizes are handled by dividing the range between 0 and -65536 into segments. Each segment can either represent a range in -which all of one kind of ink (color and/or dot size) is used, with -varying amounts of ink, or a transition region between inks, in which -equal numbers of dots are printed but the amount of each ink will be -adjusted throughout the range. Each range is represented by four -numbers: - -@enumerate -@item -bottom of the range -@item -top of the range -@item -value of the lighter ink -@item -value of the darker ink -@end enumerate - -In addition, the bit patterns and which type of ink are also -represented, but they don't affect the actual algorithm. - - -As mentioned above, the basic algorithm is the same whether we use -ordered dither or error diffusion. We perform the following steps on -each color of each pixel: - -@enumerate -@item -Compute the value of the particular color we're printing. This isn't -usually the pure CMY value; it's adjusted to improve saturation and to -limit the use of black in light toned regions (to avoid speckling). - -@item -Find the range containing this value. - -@item -Compute where this value lies within the range. We scale the endpoints -between 0 and 65536 for this purpose. So for example, if the bottom of -the range is 10,000 and the top of the range is 20,000, and the value is -12,500, we're 1/4 of the way between the bottom and the top of the -range, so our scale point is 16384. - -@item -Compute the ``virtual value''. The virtual value is the distance between -the value of the lighter and the value of the darker ink. So if the -value of the light ink is 32768 and the dark ink is 65536, we compute a -virtual value scaled appropriately between these two values, which is -40960 in this case. - -@item -Using either error diffusion or ordered dither, the standard threshold -is 1/2 of the value (20480 in this case). Using ordered dither, we want -to compute a value between 0 and 40960 that we will compare the input -value against to decide whether to print. Using pure error diffusion, -we would compare the accumulated error against 20480 to decide whether -to print. In practice, we use the same matrix method to decide whether -to print. The correct amount of ink will be printed this way, but we -minimize the squiggly lines characteristic of error diffusion by -dithering the threshold in this fashion. A future enhancement will -allow us to control the amount of dithering applied to the threshold. -@end enumerate - -The matrices were generated by Thomas Tonino -@email{<ttonino@@bio.vu.nl>} with an algorithm of his devising. The -algorithm is designed to maximize the spacing between dots at any given -density by searching the matrix for holes and placing a dot in the -largest available hole. It requires careful selection of initial points -to achieve good results, and is very time consuming. For best results, -a different matrix must be used for modes with 2:1 aspect ratio -(e.g. 1440x720) than for 1:1 (e. g. 720x720). It is essential with any -of these matrices that every point be used. Skipping points generates -low-frequency noise. - -It's essential to use different matrices for deciding whether to print -and for deciding what color (dark or light) to print. This should be -obvious; the decision about whether to print at all should be as -independent as possible from the decision about what color to print, -because any bias will result in excess light or dark ink being -printed, shifting the tonal balance. We actually use the same -matrices, but we shift them vertically and horizontally. Assuming -that the matrices are not self-correlated, this will yield good -results. - -The ranges are computed from a list of ink values (between 0 and 1 for -each possible combination of dot size and ink tone, where the value -represents the darkness of the ink) and the desired maximum density of -the ink. This is done in dither_set_ranges, and needs more -documentation. - - -I stated earlier that I've tweaked the basic error diffusion algorithm. -Here's what I've done to improve it: - -@enumerate -@item -We use a variable threshold to decide when to print, as discussed above. -This does two things for us: it reduces the slightly squiggly diagonal -lines that are the mark of error diffusion; and it allows us to lay down -some ink even in very light areas near the edge of the image. The -squiggly lines that error diffusion algorithms tend to generate are -caused by the gradual accumulation of error. This error is partially -added horizontally and partially vertically. The horizontal -accumulation results in a dot eventually being printed. The vertical -accumulation results in a dot getting laid down in roughly the same -horizontal position in the next row. The diagonal squigglies result -from the error being added to pixels one forward and one below the -current pixel; these lines slope from the top right to the bottom left -of the image. - -Error diffusion also results in pale areas being completely white near -the top left of the image (the origin of the printing coordinates). -This is because enough error has to accumulate for anything at all to -get printed. In very pale areas it takes quite a long time to build up -anything printable at all; this results in the bare spots. - -Randomizing the threshold somewhat breaks up the diagonals to some -degree by randomizing the exact location that the accumulated output -crosses the threshold. It reduces the false white areas by allowing -some dots to be printed even when the accumulated output level is very -low. It doesn't result in excess ink because the full output level is -still subtracted and diffused. - -Excessive randomization leads to blobs at high densities. Therefore, as -the density increases, the degree of randomization decreases. - -@item -Alternating scan direction between rows (first row is scanned left to -right, second is scanned right to left, and so on). This also helps -break up white areas, and it also seems to break up squigglies a bit. -Furthermore, it eliminates directional biases in the horizontal -direction. This isn't necessary for ordered dither, but it doesn't hurt -either. - -@item -Diffusing the error into more pixels. Instead of diffusing the entire -error into @math{(X+1, Y)} and @math{(X, Y+1)}, we diffuse it into -@math{(X+1, Y)}, @math{(X+K, Y+1)}, @math{(X, Y+1)}, @math{(X-K, Y+1)} -where @math{K} depends upon the output level (it never exceeds about 10 -dots, and is greater at higher output levels). This really reduces -squigglies and graininess. The amount of this spread can be controlled; -for line art, it should be less than for photographs (of course, line -art doesn't usually contain much light color, but the @strong{error} -value can be small in places!) In addition to requiring more -computation, a wide ink spread results in patterning at high dot -densities (note that the dot density can be high even in fairly pale -regions if multiple dot sizes are in use). - -@item -Don't lay down any colored ink if we're laying down black ink. There's -no point; the colored ink won't show. We still pretend that we did for -purposes of error diffusion (otherwise excessive error will build up, -and will take a long time to clear, resulting in heavy bleeding of ink -into surrounding areas, which is very ugly indeed), but we don't bother -wasting the ink. How well this will do with variable dot size remains -to be seen. - -@item -Oversampling. This is how to print 1440x720 with Epson Stylus printers. -Printing full density at 1440x720 will result in excess ink being laid -down. The trick is to print only every other dot. We still compute the -error as though we printed every dot. It turns out that randomizing -which dots are printed results in very speckled output. This can be -taken too far; oversampling at 1440x1440 or 1440x2880 virtual resolution -results in other problems. However, at present 1440x1440 (which is more -accurately called "1440x720 enhanced", as the Epson printers cannot -print 1440 rows per inch) does quite well, although it's slow. -@end enumerate - -What about multiple output levels? For 6 and 7 color printers, simply -using different threshold levels has a problem: the pale inks have trouble -being seen when a lot of darker ink is being printed. So rather than -just using the output level of the particular color to decide which ink -to print, we look at the total density (sum of all output levels). -If the density's high enough, we prefer to use the dark ink. Speckling -is less visible when there's a lot of ink, anyway. I haven't yet figured -out what to do for multiple levels of one color. - -You'll note that I haven't quoted a single source on color or printing -theory. I simply did all of this empirically. - -There are various other tricks to reduce speckling. One that I've seen -is to reduce the amount of ink printed in regions where one color -(particularly cyan, which is perceived as the darkest) is very pale. -This does reduce speckling all right, but it also results in strange -tonal curves and weird (to my eye) colors. - - - -Before any dither routine is used, @code{init_dither()} must be called. -This takes three arguments: the input width (number of pixels in the -input), the output width (number of pixels in the output), and a -@code{vars_t} structure containing the parameters for the print job. - -@code{init_dither()} returns a pointer to an opaque object representing -the dither. This object is passed as the first argument to all of the -dither-related routines. - -After a page is fully dithered, @code{free_dither()} must be called to -free the dither object and perform any cleanup. In the future, this may -do more (such as flush output). This arrangement permits using these -routines with programs that create multiple output pages, such as -GhostScript. - -The dithering routines themselves have a number of control knobs that -control internal aspects of the dithering process. These knobs are -accessible via a number of functions that can be called after -@code{init_dither()}. - -@itemize @bullet -@item -@code{dither_set_density()} takes a double between 0 and 1 representing -the desired ink density for printing solid colors. This is used in a -number of places in the dithering routine to make decisions. - -@item -@code{dither_set_black_density()} takes a double between 0 and 1 -representing the desired ink density for printing black ink in color -printing. This is used to balance black against color ink. By default, -this is equal to the density set by @code{dither_set_density()}. By -setting it higher, more black ink will be printed. For example, if the -base density is .4 and the black density is .8, twice as much black ink -will be printed as would otherwise be called for. - -This is not used when printing in monochrome. When printing monochrome, -the base density (@code{dither_set_density}) should be adjusted -appropriately. - -@item -@code{dither_set_ink_budget()} takes an unsigned number representing the -most ink that may be deposited at a given point. This number is -arbitrary; the limit is computed by summing the size of each ink dot, -which is supplied as a parameter in @code{dither_set_@var{X}_ranges}. -By default, there is no limit. - -@item -@code{dither_set_black_lower()} takes a double that should be between 0 -and 1 that represents the lowest density level at which black ink will -start to mix in with colored ink to generate grays. The lower this is, -the less density is required to use black ink. Setting this too low -will result in speckling from black dots, particularly on 6 and 7 color -printers. Setting this too high will make it hard to get satisfactory -black or may result in sharp transition between blended colors and -black. Default: 0.0468. - -It is important to note that since the density scale is never linear -(and since this value is adjusted via other things happening during the -dithering process) that this does not mean that 95% gray will use any -black ink. At this setting, there will be no black ink used until about -50% gray. - -This only applies to color mode. - -This value should be set lower for printers capable of variable dot -size, since more dots can be laid down close to each other. - -@item -@code{dither_set_black_upper()} takes a double that should be between 0 -and 1 that represents the highest density level at which colored inks -will be mixed to create gray. Setting this too low will result in -speckly dark grays because there is not enough ink to fill all the -holes, or sharp transition between blended colors and black if it is too -close to the value of dither_set_black_upper(). Setting this too high -will result in poor black and dark tone quality. Default: 0.5. This -results in 10% and darker grays being printed with essentially all -black. - -This only applies to color mode. - -@item -@code{dither_set_black_levels()} takes three doubles that represent the -amount of cyan, magenta, and yellow respectively that are blended to -create gray. The defaults are 1.0 for each, which is probably too low -for most printers. These values are adjusted to create a good gray -balance. Setting these too low will result in pale light and midtone -grays, with a sharp transition to darker tones as black mixes in. -Setting them too high will result in overly dark grays and use of too -much ink, possibly creating bleed-through. - -This only applies to color mode. - -@item -@code{dither_set_randomizers()} takes four integer values representing -the degree of randomness used for cyan, magenta, yellow, and black. -This is used to allow some printing to take place in pale areas. Zero -is the most random; greater than 8 or so gives very little randomness at -all. Defaults are 0 for cyan, magenta, and yellow, and 4 for black. -Setting the value for black too low will result in black speckling in -pale areas. Setting values too high will result in pale areas getting -no ink at all. - -This currently only applies to single dot size in color and black. It -should be extended to operate in variable dot size mode, although -actually applying it correctly will be tricky. - -@item -@code{dither_set_ink_darkness()} takes three doubles representing the -contribution to perceived darkness of cyan, magenta, and yellow. This -is used to help decide when to switch between light and dark inks in 6 -and 7 color printers (with light cyan, light magenta, and possibly light -yellow). Setting these too low will result in too much light ink being -laid down, creating flat spots in the darkness curves and bleed-through. -Setting them too high will result in dark ink being used in pale areas, -creating speckle. The defaults are .4 for cyan, .3 for magenta, and .2 -for yellow. Dark cyan will show against yellow much more than dark -magenta will show against cyan, since the cyan appears much darker than -the yellow. - -@item -@code{dither_set_light_inks()} takes three doubles between 0 and 1 -representing the ratio in darkness between the light and dark versions -of the inks. Setting these too low will result in too much dark ink -being used in pale areas, creating speckling, while setting them too -high will result in very smooth texture but too much use of light ink, -resulting in flat spots in the density curves and ink bleed-through. -There are no defaults. Any light ink specified as zero indicates that -there is no light ink for that color. - -This only applies to 6 and 7 color printers in single dot size color -mode, and only to those inks which have light versions (usually cyan and -magenta). - -@item -@code{dither_set_ink_spread()} takes a small integer representing the -amount of ink spread in the dither. Larger numbers mean less spread. -Larger values are appropriate for line art and solid tones; they will -yield sharper transitions but more dither artifacts. Smaller values are -more appropriate for photos. They will reduce resolution and sharpness -but reduce dither artifacts up to a point. A value of 16 or higher -implies minimum ink spread at any resolution no matter what the -overdensity. A value of 14 is typical for photos on single dot size, 6 -color printers. For 4 color printers, subtract 1 (more spread; the dots -are farther apart). For variable dot size printers, add 1 (more small -dots are printed; less spread is desirable). - -@item -@code{dither_set_adaptive_divisor()} takes a float representing the -transition point between error diffusion and ordered dither if adaptive -dithering is used. The float is a fraction of the printing density. -For example, if you wish the transition to be at 1/4 of the maximum -density (which works well on simple 4-color printers), you would pass -.25 here. With six colors and/or with multiple dot sizes, the values -should be set lower. - -@item -@code{dither_set_transition()} takes a float representing the exponent -of the transition curve between light and dark inks/dot sizes. A value -less than 1 (typical when using error diffusion) mixes in less dark -ink/small dots at lower ends of the range, to reduce speckling. When -using ordered dithering, this must be set to 1. - -@item -@code{dither_set_@var{X}_ranges_simple} (@var{X}=@samp{c}, @samp{m}, -@samp{y}, or @samp{k}) describes the ink choices available for each -color. This is useful in typical cases where a four color printer with -variable dot sizes is in use. It is passed an array of doubles between -(0, 1] representing the relative darkness of each dot size. The dot -sizes are assigned bit patterns (and ink quantities, see -@code{dither_set_ink_budget()} above) from 1 to the number of levels. -This also requires a density, which is the desired density for this -color. This density need not equal the density specified in -@code{dither_set_density()}. Setting it lower will tend to print more -dark ink (because the curves are calculated for this color assuming a -lower density than is actually supplied). - -@item -@code{dither_set_@var{X}_ranges} (@var{X}=@samp{c}, @samp{m}, @samp{y}, -or @samp{k}) describes in a more general way the ink choices available -for each color. For each possible ink choice, a bit pattern, dot size, -value (i. e. relative darkness), and whether the ink is the dark or -light variant ink is specified. -@end itemize - ----Robert Krawitz @email{<rlk@@alum.mit.edu>} May 8, 2000 |