Skip to content

Commit b2b81f8

Browse files
authored
Ghostscript (gstoraster): new halftone/dithering algorithms (#160)
- 8x8 ordered dithering algorithm - genordered halftone+dithering algorithm - spot halftone algorithm from PDF
1 parent 5adc7e1 commit b2b81f8

1 file changed

Lines changed: 155 additions & 1 deletion

File tree

cupsfilters/ghostscript.c

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,51 @@ typedef enum cups_halftone_type_e
4949
HALFTONE_DEFAULT,
5050
HALFTONE_STOCHASTIC,
5151
HALFTONE_FOO2ZJS,
52-
HALFTONE_BI_LEVEL
52+
HALFTONE_BI_LEVEL,
53+
HALFTONE_DITHERING,
54+
HALFTONE_GENORDERED,
55+
HALFTONE_SPOT
5356
} cups_halftone_type_t;
5457

58+
static const char *ht_spot_functions[] =
59+
{
60+
"{dup mul exch dup mul add 1 exch sub}", /* 0 SimpleDot */
61+
"{dup mul exch dup mul add 1 sub}", /* 1 InvertedSimpleDot */
62+
"{360 mul sin 2 div exch 360 mul sin 2 div add}",/* 2 DoubleDot */
63+
"{360 mul sin 2 div exch 360 mul sin 2 div add neg}",/* 3 InvertedDoubleDot */
64+
"{180 mul cos exch 180 mul cos add 2 div}", /* 4 CosineDot */
65+
"{360 mul sin 2 div exch 2 div 360 mul sin 2 div add}", /* 5 Double */
66+
"{360 mul sin 2 div exch 2 div 360 mul sin 2 div add neg}",/* 6 InvertedDouble */
67+
"{exch pop abs neg}", /* 7 Line */
68+
"{pop}", /* 8 LineX */
69+
"{exch pop}", /* 9 LineY */
70+
"{abs exch abs 2 copy add 1.0 le"
71+
" {dup mul exch dup mul add 1 exch sub}"
72+
" {1 sub dup mul exch 1 sub dup mul add 1 sub}"
73+
" ifelse}", /* 10 Round */
74+
"{abs exch abs 2 copy 3 mul exch 4 mul add 3 sub dup 0 lt"
75+
" {pop dup mul exch 0.75 div dup mul add 4 div 1 exch sub}"
76+
" {dup 1 gt"
77+
" {pop 1 exch sub dup mul exch 1 exch sub 0.75 div dup mul add 4 div 1 sub}"
78+
" {0.5 exch sub exch pop exch pop}}"
79+
" ifelse}ifelse}", /* 11 Ellipse */
80+
"{dup mul 0.9 mul exch dup mul add 1 exch sub}", /* 12 EllipseA */
81+
"{dup mul 0.9 mul exch dup mul add 1 sub}", /* 13 InvertedEllipseA */
82+
"{dup 5 mul 8 div mul exch dup mul exch add sqrt 1 exch sub}", /* 14 EllipseB */
83+
"{dup mul exch dup mul 0.9 mul add 1 exch sub}", /* 15 EllipseC */
84+
"{dup mul exch dup mul 0.9 mul add 1 sub}", /* 16 InvertedEllipseC */
85+
"{abs exch abs 2 copy lt {exch} if pop neg}", /* 17 Square */
86+
"{abs exch abs 2 copy gt {exch} if pop neg}", /* 18 Cross */
87+
"{abs exch abs 0.9 mul add 2 div}", /* 19 Rhomboid */
88+
"{abs exch abs 2 copy add 0.75 le"
89+
" {dup mul exch dup mul add 1 exch sub}"
90+
" {2 copy add 1.23 le"
91+
" {0.85 mul add 1 exch sub}"
92+
" {1 sub dup mul exch 1 sub dup mul add 1 sub}"
93+
" ifelse} ifelse}" /* 20 Diamond */
94+
};
95+
#define HT_SPOT_FUNCTIONS_COUNT ((int)(sizeof(ht_spot_functions)/sizeof(ht_spot_functions[0])))
96+
5597
static gs_doc_t
5698
parse_doc_type(FILE *fp)
5799
{
@@ -825,6 +867,7 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input
825867
cf_cm_calibration_t cm_calibrate;
826868
int pxlcolor = 0; // 1 if printer is color printer otherwise 0.
827869
cups_halftone_type_t halftonetype = HALFTONE_DEFAULT;
870+
int ht_frequency = 133, ht_angle = 45, ht_dotshape = 0;
828871
ipp_attribute_t *ipp_attr;
829872
cf_logfunc_t log = data->logfunc;
830873
void *ld = data->logdata;
@@ -1636,6 +1679,58 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input
16361679
halftonetype = HALFTONE_FOO2ZJS;
16371680
else if (!strcasecmp(t, "bi-level"))
16381681
halftonetype = HALFTONE_BI_LEVEL;
1682+
else if (!strcasecmp(t, "dithering"))
1683+
halftonetype = HALFTONE_DITHERING;
1684+
else if (!strncasecmp(t, "genordered", 10) &&
1685+
(t[10] == '-' || t[10] == '\0'))
1686+
{
1687+
halftonetype = HALFTONE_GENORDERED;
1688+
if (t[10] == '-')
1689+
{
1690+
const char *p = t + 11;
1691+
char *endp;
1692+
long v;
1693+
v = strtol(p, &endp, 10);
1694+
if (endp != p) { ht_frequency = (int)v; p = endp; }
1695+
if (*p == '-')
1696+
{
1697+
p++;
1698+
v = strtol(p, &endp, 10);
1699+
if (endp != p) { ht_angle = (int)v; p = endp; }
1700+
if (*p == '-')
1701+
{
1702+
p++;
1703+
v = strtol(p, &endp, 10);
1704+
if (endp != p) ht_dotshape = (int)v;
1705+
}
1706+
}
1707+
}
1708+
}
1709+
else if (!strncasecmp(t, "spot", 4) &&
1710+
(t[4] == '-' || t[4] == '\0'))
1711+
{
1712+
halftonetype = HALFTONE_SPOT;
1713+
if (t[4] == '-')
1714+
{
1715+
const char *p = t + 5;
1716+
char *endp;
1717+
long v;
1718+
v = strtol(p, &endp, 10);
1719+
if (endp != p) { ht_frequency = (int)v; p = endp; }
1720+
if (*p == '-')
1721+
{
1722+
p++;
1723+
v = strtol(p, &endp, 10);
1724+
if (endp != p) { ht_angle = (int)v; p = endp; }
1725+
if (*p == '-')
1726+
{
1727+
p++;
1728+
v = strtol(p, &endp, 10);
1729+
if (endp != p) ht_dotshape = (int)v;
1730+
}
1731+
}
1732+
}
1733+
}
16391734
}
16401735

16411736
//
@@ -1771,6 +1866,65 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input
17711866
cupsArrayAdd(gs_args, strdup("{ .5 gt { 1 } { 0 } ifelse} settransfer"));
17721867
}
17731868

1869+
//
1870+
// Use 8x8 ordered dithering.
1871+
//
1872+
// This uses .setloresscreen Ghostscript setup function which is a part
1873+
// of dithering vs halftoning automatic selection code and is used only
1874+
// if DPI of the output device is low (< 150).
1875+
// To correctly force it, we need to call it in Install function of
1876+
// Page Device.
1877+
// DPI is auto-detected and passed for screen frequency calculation.
1878+
//
1879+
// Particularly useful for printing images on label printers (203 DPI).
1880+
//
1881+
if (halftonetype == HALFTONE_DITHERING)
1882+
{
1883+
if (log) log(ld, CF_LOGLEVEL_DEBUG,
1884+
"cfFilterGhostscript: Ghostscript using 8x8 ordered dithering.");
1885+
cupsArrayAdd(gs_args, strdup("<< /Install { 72 72 matrix defaultmatrix dtransform abs exch abs .min .setloresscreen } >> setpagedevice"));
1886+
}
1887+
1888+
//
1889+
// Ghostscript .genordered advanced ordered dithering
1890+
// PostScript HalftoneType 3 (threshold array based).
1891+
//
1892+
// This uses the .genordered operator to generate an ordered dither
1893+
// halftone screen with configurable frequency, angle and dot shape.
1894+
//
1895+
// Dot shapes:
1896+
// 0=CIRCLE, 1=REDBOOK, 2=INVERTED, 3=RHOMBOID, 4=LINE_X, 5=LINE_Y,
1897+
// 6=DIAMOND1, 7=DIAMOND2, 8=ROUNDSPOT */
1898+
//
1899+
if (halftonetype == HALFTONE_GENORDERED) {
1900+
if (log) log(ld, CF_LOGLEVEL_DEBUG,
1901+
"cfFilterGhostscript: Ghostscript using .genordered halftone (frequency=%d angle=%d dotshape=%d).",
1902+
ht_frequency, ht_angle, ht_dotshape);
1903+
snprintf(tmpstr, sizeof(tmpstr),
1904+
"<< /Frequency %d /Angle %d /DotShape %d >> .genordered /Default exch /Halftone defineresource sethalftone { } settransfer 0.003 setsmoothness",
1905+
ht_frequency, ht_angle, ht_dotshape);
1906+
cupsArrayAdd(gs_args, strdup(tmpstr));
1907+
}
1908+
1909+
//
1910+
// PostScript HalftoneType 1 spot-function halftone algorithm.
1911+
//
1912+
// Spot functions are from PDF specification, see ht_spot_functions array.
1913+
//
1914+
if (halftonetype == HALFTONE_SPOT)
1915+
{
1916+
int shape = ht_dotshape;
1917+
if (shape < 0) shape = 0;
1918+
if (shape >= HT_SPOT_FUNCTIONS_COUNT) shape = HT_SPOT_FUNCTIONS_COUNT - 1;
1919+
if (log) log(ld, CF_LOGLEVEL_DEBUG,
1920+
"cfFilterGhostscript: Ghostscript using Spot halftone (frequency=%d angle=%d dotshape=%d).\n",
1921+
ht_frequency, ht_angle, shape);
1922+
snprintf(tmpstr, sizeof(tmpstr),
1923+
"<< /HalftoneType 1 /Frequency %d /Angle %d /SpotFunction %s >> /Default exch /Halftone defineresource sethalftone",
1924+
ht_frequency, ht_angle, ht_spot_functions[shape]);
1925+
cupsArrayAdd(gs_args, strdup(tmpstr));
1926+
}
1927+
17741928
// Mark the end of PostScript commands supplied on the Ghostscript command
17751929
// line (with the "-c" option), so that we can supply the input file name
17761930
cupsArrayAdd(gs_args, strdup("-f"));

0 commit comments

Comments
 (0)