From d0bea89e0d2638cb92875a5f43935949c47729f3 Mon Sep 17 00:00:00 2001 From: ValdikSS Date: Tue, 23 Jun 2026 01:28:41 +0300 Subject: [PATCH 1/3] Ghostscript filters (gsto...): 8x8 ordered dithering algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Controlled either with `halftone-type` job option or `cupsHalftoneType` PPD option. halftone-type=dithering or cupsHalftoneType=dithering Square 8×8 ordered dither, contributed to Ghostscript by Gregg Townsend. --- cupsfilters/ghostscript.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/cupsfilters/ghostscript.c b/cupsfilters/ghostscript.c index abcd09406..ce7e61300 100644 --- a/cupsfilters/ghostscript.c +++ b/cupsfilters/ghostscript.c @@ -49,7 +49,8 @@ typedef enum cups_halftone_type_e HALFTONE_DEFAULT, HALFTONE_STOCHASTIC, HALFTONE_FOO2ZJS, - HALFTONE_BI_LEVEL + HALFTONE_BI_LEVEL, + HALFTONE_DITHERING } cups_halftone_type_t; static gs_doc_t @@ -1636,6 +1637,8 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input halftonetype = HALFTONE_FOO2ZJS; else if (!strcasecmp(t, "bi-level")) halftonetype = HALFTONE_BI_LEVEL; + else if (!strcasecmp(t, "dithering")) + halftonetype = HALFTONE_DITHERING; } // @@ -1771,6 +1774,25 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input cupsArrayAdd(gs_args, strdup("{ .5 gt { 1 } { 0 } ifelse} settransfer")); } + // + // Use 8x8 ordered dithering. + // + // This uses .setloresscreen Ghostscript setup function which is a part + // of dithering vs halftoning automatic selection code and is used only + // if DPI of the output device is low (< 150). + // To correctly force it, we need to call it in Install function of + // Page Device. + // DPI is auto-detected and passed for screen frequency calculation. + // + // Particularly useful for printing images on label printers (203 DPI). + // + if (halftonetype == HALFTONE_DITHERING) + { + if (log) log(ld, CF_LOGLEVEL_DEBUG, + "cfFilterGhostscript: Ghostscript using 8x8 ordered dithering."); + cupsArrayAdd(gs_args, strdup("<< /Install { 72 72 matrix defaultmatrix dtransform abs exch abs .min .setloresscreen } >> setpagedevice")); + } + // Mark the end of PostScript commands supplied on the Ghostscript command // line (with the "-c" option), so that we can supply the input file name cupsArrayAdd(gs_args, strdup("-f")); From 8eaea0a843a384a303a85d111f24d6a37b4d2c5c Mon Sep 17 00:00:00 2001 From: ValdikSS Date: Tue, 23 Jun 2026 01:43:39 +0300 Subject: [PATCH 2/3] Ghostscript filters (gsto...): genordered halftone+dithering algorithm Advanced ordered dithering halftone screen algorithm, controlled either with `halftone-type` job option or `cupsHalftoneType` PPD option. halftone-type=genordered[-frequency][-angle][-dotshape] frequency - line-per-inch, number of halftone cells per inch. Lower produces larger dots, higher - smaller. Values good for 203DPI label printer: 50-60 600DPI laser printer: 106-150 angle - screen orientation, in degrees. dotshape - one of: 0=CIRCLE, 1=REDBOOK, 2=INVERTED, 3=RHOMBOID, 4=LINE_X, 5=LINE_Y, 6=DIAMOND1, 7=DIAMOND2, 8=ROUNDSPOT Examples halftone-type=genordered-60-45 halftone-type=genordered-133-135-8 More information could be found in Ghostscript documentation: https://ghostscript.readthedocs.io/en/gs10.07.1/Language.html#:~:text=%2Egenordered --- cupsfilters/ghostscript.c | 50 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/cupsfilters/ghostscript.c b/cupsfilters/ghostscript.c index ce7e61300..0934be17a 100644 --- a/cupsfilters/ghostscript.c +++ b/cupsfilters/ghostscript.c @@ -50,7 +50,8 @@ typedef enum cups_halftone_type_e HALFTONE_STOCHASTIC, HALFTONE_FOO2ZJS, HALFTONE_BI_LEVEL, - HALFTONE_DITHERING + HALFTONE_DITHERING, + HALFTONE_GENORDERED } cups_halftone_type_t; static gs_doc_t @@ -826,6 +827,7 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input cf_cm_calibration_t cm_calibrate; int pxlcolor = 0; // 1 if printer is color printer otherwise 0. cups_halftone_type_t halftonetype = HALFTONE_DEFAULT; + int ht_frequency = 133, ht_angle = 45, ht_dotshape = 0; ipp_attribute_t *ipp_attr; cf_logfunc_t log = data->logfunc; void *ld = data->logdata; @@ -1639,6 +1641,31 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input halftonetype = HALFTONE_BI_LEVEL; else if (!strcasecmp(t, "dithering")) halftonetype = HALFTONE_DITHERING; + else if (!strncasecmp(t, "genordered", 10) && + (t[10] == '-' || t[10] == '\0')) + { + halftonetype = HALFTONE_GENORDERED; + if (t[10] == '-') + { + const char *p = t + 11; + char *endp; + long v; + v = strtol(p, &endp, 10); + if (endp != p) { ht_frequency = (int)v; p = endp; } + if (*p == '-') + { + p++; + v = strtol(p, &endp, 10); + if (endp != p) { ht_angle = (int)v; p = endp; } + if (*p == '-') + { + p++; + v = strtol(p, &endp, 10); + if (endp != p) ht_dotshape = (int)v; + } + } + } + } } // @@ -1793,6 +1820,27 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input cupsArrayAdd(gs_args, strdup("<< /Install { 72 72 matrix defaultmatrix dtransform abs exch abs .min .setloresscreen } >> setpagedevice")); } + // + // Ghostscript .genordered advanced ordered dithering + // PostScript HalftoneType 3 (threshold array based). + // + // This uses the .genordered operator to generate an ordered dither + // halftone screen with configurable frequency, angle and dot shape. + // + // Dot shapes: + // 0=CIRCLE, 1=REDBOOK, 2=INVERTED, 3=RHOMBOID, 4=LINE_X, 5=LINE_Y, + // 6=DIAMOND1, 7=DIAMOND2, 8=ROUNDSPOT */ + // + if (halftonetype == HALFTONE_GENORDERED) { + if (log) log(ld, CF_LOGLEVEL_DEBUG, + "cfFilterGhostscript: Ghostscript using .genordered halftone (frequency=%d angle=%d dotshape=%d).", + ht_frequency, ht_angle, ht_dotshape); + snprintf(tmpstr, sizeof(tmpstr), + "<< /Frequency %d /Angle %d /DotShape %d >> .genordered /Default exch /Halftone defineresource sethalftone { } settransfer 0.003 setsmoothness", + ht_frequency, ht_angle, ht_dotshape); + cupsArrayAdd(gs_args, strdup(tmpstr)); + } + // Mark the end of PostScript commands supplied on the Ghostscript command // line (with the "-c" option), so that we can supply the input file name cupsArrayAdd(gs_args, strdup("-f")); From 17409b75fb409d4229be4f25b0f663dbe50ace45 Mon Sep 17 00:00:00 2001 From: ValdikSS Date: Tue, 23 Jun 2026 01:51:29 +0300 Subject: [PATCH 3/3] Ghostscript filters (gsto...): spot halftone algorithm from PDF Advanced ordered dithering halftone screen algorithm, controlled either with `halftone-type` job option or `cupsHalftoneType` PPD option. halftone-type=spot[-frequency][-angle][-dotshape] frequency - line-per-inch, number of halftone cells per inch. Lower produces larger dots, higher - smaller. Values good for 203DPI label printer: 50-60 600DPI laser printer: 106-150 angle - screen orientation, in degrees. dotshape - one of: 0=SimpleDot 1=InvertedSimpleDot 2=DoubleDot 3=InvertedDoubleDot 4=CosineDot 5=Double 6=InvertedDouble 7=Line 8=LineX 9=LineY 10=Round 11=Ellipse 12=EllipseA 13=InvertedEllipseA 14=EllipseB 15=EllipseC 16=InvertedEllipseC 17=Square 18=Cross 19=Rhomboid 20=Diamond Examples halftone-type=spot-60-45-20 halftone-type=spot-133-135-4 More information could be found in PDF specification, pages 303-307: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf --- cupsfilters/ghostscript.c | 86 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/cupsfilters/ghostscript.c b/cupsfilters/ghostscript.c index 0934be17a..3bd07dfec 100644 --- a/cupsfilters/ghostscript.c +++ b/cupsfilters/ghostscript.c @@ -51,9 +51,49 @@ typedef enum cups_halftone_type_e HALFTONE_FOO2ZJS, HALFTONE_BI_LEVEL, HALFTONE_DITHERING, - HALFTONE_GENORDERED + HALFTONE_GENORDERED, + HALFTONE_SPOT } cups_halftone_type_t; +static const char *ht_spot_functions[] = +{ + "{dup mul exch dup mul add 1 exch sub}", /* 0 SimpleDot */ + "{dup mul exch dup mul add 1 sub}", /* 1 InvertedSimpleDot */ + "{360 mul sin 2 div exch 360 mul sin 2 div add}",/* 2 DoubleDot */ + "{360 mul sin 2 div exch 360 mul sin 2 div add neg}",/* 3 InvertedDoubleDot */ + "{180 mul cos exch 180 mul cos add 2 div}", /* 4 CosineDot */ + "{360 mul sin 2 div exch 2 div 360 mul sin 2 div add}", /* 5 Double */ + "{360 mul sin 2 div exch 2 div 360 mul sin 2 div add neg}",/* 6 InvertedDouble */ + "{exch pop abs neg}", /* 7 Line */ + "{pop}", /* 8 LineX */ + "{exch pop}", /* 9 LineY */ + "{abs exch abs 2 copy add 1.0 le" + " {dup mul exch dup mul add 1 exch sub}" + " {1 sub dup mul exch 1 sub dup mul add 1 sub}" + " ifelse}", /* 10 Round */ + "{abs exch abs 2 copy 3 mul exch 4 mul add 3 sub dup 0 lt" + " {pop dup mul exch 0.75 div dup mul add 4 div 1 exch sub}" + " {dup 1 gt" + " {pop 1 exch sub dup mul exch 1 exch sub 0.75 div dup mul add 4 div 1 sub}" + " {0.5 exch sub exch pop exch pop}}" + " ifelse}ifelse}", /* 11 Ellipse */ + "{dup mul 0.9 mul exch dup mul add 1 exch sub}", /* 12 EllipseA */ + "{dup mul 0.9 mul exch dup mul add 1 sub}", /* 13 InvertedEllipseA */ + "{dup 5 mul 8 div mul exch dup mul exch add sqrt 1 exch sub}", /* 14 EllipseB */ + "{dup mul exch dup mul 0.9 mul add 1 exch sub}", /* 15 EllipseC */ + "{dup mul exch dup mul 0.9 mul add 1 sub}", /* 16 InvertedEllipseC */ + "{abs exch abs 2 copy lt {exch} if pop neg}", /* 17 Square */ + "{abs exch abs 2 copy gt {exch} if pop neg}", /* 18 Cross */ + "{abs exch abs 0.9 mul add 2 div}", /* 19 Rhomboid */ + "{abs exch abs 2 copy add 0.75 le" + " {dup mul exch dup mul add 1 exch sub}" + " {2 copy add 1.23 le" + " {0.85 mul add 1 exch sub}" + " {1 sub dup mul exch 1 sub dup mul add 1 sub}" + " ifelse} ifelse}" /* 20 Diamond */ +}; +#define HT_SPOT_FUNCTIONS_COUNT ((int)(sizeof(ht_spot_functions)/sizeof(ht_spot_functions[0]))) + static gs_doc_t parse_doc_type(FILE *fp) { @@ -1666,6 +1706,31 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input } } } + else if (!strncasecmp(t, "spot", 4) && + (t[4] == '-' || t[4] == '\0')) + { + halftonetype = HALFTONE_SPOT; + if (t[4] == '-') + { + const char *p = t + 5; + char *endp; + long v; + v = strtol(p, &endp, 10); + if (endp != p) { ht_frequency = (int)v; p = endp; } + if (*p == '-') + { + p++; + v = strtol(p, &endp, 10); + if (endp != p) { ht_angle = (int)v; p = endp; } + if (*p == '-') + { + p++; + v = strtol(p, &endp, 10); + if (endp != p) ht_dotshape = (int)v; + } + } + } + } } // @@ -1841,6 +1906,25 @@ cfFilterGhostscript(int inputfd, // I - File descriptor input cupsArrayAdd(gs_args, strdup(tmpstr)); } + // + // PostScript HalftoneType 1 spot-function halftone algorithm. + // + // Spot functions are from PDF specification, see ht_spot_functions array. + // + if (halftonetype == HALFTONE_SPOT) + { + int shape = ht_dotshape; + if (shape < 0) shape = 0; + if (shape >= HT_SPOT_FUNCTIONS_COUNT) shape = HT_SPOT_FUNCTIONS_COUNT - 1; + if (log) log(ld, CF_LOGLEVEL_DEBUG, + "cfFilterGhostscript: Ghostscript using Spot halftone (frequency=%d angle=%d dotshape=%d).\n", + ht_frequency, ht_angle, shape); + snprintf(tmpstr, sizeof(tmpstr), + "<< /HalftoneType 1 /Frequency %d /Angle %d /SpotFunction %s >> /Default exch /Halftone defineresource sethalftone", + ht_frequency, ht_angle, ht_spot_functions[shape]); + cupsArrayAdd(gs_args, strdup(tmpstr)); + } + // Mark the end of PostScript commands supplied on the Ghostscript command // line (with the "-c" option), so that we can supply the input file name cupsArrayAdd(gs_args, strdup("-f"));