I'd like to transform a pdf with some coloured text and images in another pdf with only black&white, in order to reduce its dimensions. Moreover, I would like to keep the text as text, without transforming the pages elements in pictures. I tried the following command:

convert -density 150 -threshold 50% input.pdf output.pdf

found in another question, a link, but it does what I don't want: the text in the output is transformed in a poor image and is no longer selectable. I tried with Ghostscript:

gs      -sOutputFile=output.pdf \
        -q -dNOPAUSE -dBATCH -dSAFER \
        -sDEVICE=pdfwrite \
        -dCompatibilityLevel=1.3 \
        -dPDFSETTINGS=/screen \
        -dEmbedAllFonts=true \
        -dSubsetFonts=true \
        -sColorConversionStrategy=/Mono \
        -sColorConversionStrategyForImages=/Mono \
        -sProcessColorModel=/DeviceGray \

but it gives me the following error message:

./script.sh: 19: ./script.sh: output.pdf: not found

Is there any other way to create the file?


7 Answers 7


The gs example

The gs command you're running above has a trailing $1 which is typically meant for passing command line arguments into a script. So I'm not sure what you actually tried but I'm guessing that you tried to put that command into a script, script.sh:


gs      -sOutputFile=output.pdf \
        -q -dNOPAUSE -dBATCH -dSAFER \
        -sDEVICE=pdfwrite \
        -dCompatibilityLevel=1.3 \
        -dPDFSETTINGS=/screen \
        -dEmbedAllFonts=true \
        -dSubsetFonts=true \
        -sColorConversionStrategy=/Mono \
        -sColorConversionStrategyForImages=/Mono \
        -sProcessColorModel=/DeviceGray \

And run it like this:

$ ./script.sh: 19: ./script.sh: output.pdf: not found

Not sure how you setup this script but it needs to executable.

$ chmod +x script.sh

Something definitely doesn't seem right with that script though. When I tried it I got this error instead:

Unrecoverable error: rangecheck in .putdeviceprops

An alternative

Instead of that script I'd use this one from the SU question instead.


gs \
 -sOutputFile=output.pdf \
 -sDEVICE=pdfwrite \
 -sColorConversionStrategy=Gray \
 -dProcessColorModel=/DeviceGray \
 -dCompatibilityLevel=1.4 \
 -dBATCH \

Then run it like this:

$ ./script.bash LeaseContract.pdf 
GPL Ghostscript 8.71 (2010-02-10)
Copyright (C) 2010 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 2.
Page 1
Page 2
  • 2
    You're right, there's something wrong with the script: "something" in this case would be sProcessColorModel which should be dProcessColorModel instead.
    – Sora.
    Dec 14, 2017 at 10:02
  • Automatic replacement suggestion for output file name : input.pdf => input_bw.pdf -sOutputFile=${1%%.*}_bw.pdf
    – douardo
    10 hours ago

I found a script here that can do this. It requires gs which you seem to have but also pdftk. You have not mentioned your distribution but on Debian-based systems, you should be able to install it with

sudo apt-get install pdftk

You can find RPMs for it here.

Once you have installed pdftk, save the script as graypdf.sh and run like so:

./greypdf.sh input.pdf

It will create a file called input-gray.pdf. I am including the whole script here to avoid link rot:

# convert pdf to grayscale, preserving metadata
# "AFAIK graphicx has no feature for manipulating colorspaces. " http://groups.google.com/group/latexusersgroup/browse_thread/thread/5ebbc3ff9978af05
# "> Is there an easy (or just standard) way with pdflatex to do a > conversion from color to grayscale when a PDF file is generated? No." ... "If you want to convert a multipage document then you better have pdftops from the xpdf suite installed because Ghostscript's pdf to ps doesn't produce nice Postscript." http://osdir.com/ml/tex.pdftex/2008-05/msg00006.html
# "Converting a color EPS to grayscale" - http://en.wikibooks.org/wiki/LaTeX/Importing_Graphics
# "\usepackage[monochrome]{color} .. I don't know of a neat automatic conversion to monochrome (there might be such a thing) although there was something in Tugboat a while back about mapping colors on the fly. I would probably make monochrome versions of the pictures, and name them consistently. Then conditionally load each one" http://newsgroups.derkeiler.com/Archive/Comp/comp.text.tex/2005-08/msg01864.html
# "Here comes optional.sty. By adding \usepackage{optional} ... \opt{color}{\includegraphics[width=0.4\textwidth]{intro/benzoCompounds_color}} \opt{grayscale}{\includegraphics[width=0.4\textwidth]{intro/benzoCompounds}} " - http://chem-bla-ics.blogspot.com/2008/01/my-phd-thesis-in-color-and-grayscale.html
# with gs:
# http://handyfloss.net/2008.09/making-a-pdf-grayscale-with-ghostscript/
# note - this strips metadata! so:
# http://etutorials.org/Linux+systems/pdf+hacks/Chapter+5.+Manipulating+PDF+Files/Hack+64+Get+and+Set+PDF+Metadata/
# NOTE: pdftk does not work with logical page numbers / pagination;
# gs kills it as well;
# so check for existence of 'pdfmarks' file in calling dir;
# if there, use it to correct gs logical pagination
# for example, see
# http://askubuntu.com/questions/32048/renumber-pages-of-a-pdf/65894#65894
if [ -e pdfmarks ] ; then
echo "$PDFMARKS exists, using..."
# convert to gray pdf - this strips metadata!
gs -sOutputFile=$FNAME-gs-gray.pdf -sDEVICE=pdfwrite \
-sColorConversionStrategy=Gray -dProcessColorModel=/DeviceGray \
-dCompatibilityLevel=1.4 -dNOPAUSE -dBATCH "$COLORFILENAME" "$PDFMARKS"
else # not really needed ?!
gs -sOutputFile=$FNAME-gs-gray.pdf -sDEVICE=pdfwrite \
-sColorConversionStrategy=Gray -dProcessColorModel=/DeviceGray \
-dCompatibilityLevel=1.4 -dNOPAUSE -dBATCH "$COLORFILENAME"
# dump metadata from original color pdf
## pdftk $COLORFILENAME dump_data output $FNAME.data.txt
# also: pdfinfo -meta $COLORFILENAME
# grep to avoid BookmarkTitle/Level/PageNumber:
pdftk $COLORFILENAME dump_data output | grep 'Info\|Pdf' > $FNAME.data.txt
# "pdftk can take a plain-text file of these same key/value pairs and update a PDF's Info dictionary to match. Currently, it does not update the PDF's XMP stream."
pdftk $FNAME-gs-gray.pdf update_info $FNAME.data.txt output $FNAME-gray.pdf
# (http://wiki.creativecommons.org/XMP_Implementations : Exempi ... allows reading/writing XMP metadata for various file formats, including PDF ... )
# clean up
rm $FNAME-gs-gray.pdf
rm $FNAME.data.txt
if [ "$OVERWRITE" == "y" ] ; then
echo "Overwriting $COLORFILENAME..."
# Mixing TEX & PostScript : The GEX Model - http://www.tug.org/TUGboat/Articles/tb21-3/tb68kost.pdf
# VTEX is a (commercial) extended version of TEX, sold by MicroPress, Inc. Free versions of VTEX have recently been made available, that work under OS/2 and Linux. This paper describes GEX, a fast fully-integrated PostScript interpreter which functions as part of the VTEX code-generator. Unless specified otherwise, this article describes the functionality in the free- ware version of the VTEX compiler, as available on CTAN sites in systems/vtex.
# GEX is a graphics counterpart to TEX. .. Since GEX may exercise subtle influence on TEX (load fonts, or change TEX registers), GEX is op- tional in VTEX implementations: the default oper- ation of the program is with GEX off; it is enabled by a command-line switch.
# \includegraphics[width=1.3in, colorspace=grayscale 256]{macaw.jpg}
# http://mail.tug.org/texlive/Contents/live/texmf-dist/doc/generic/FAQ-en/html/FAQ-TeXsystems.html
# A free version of the commercial VTeX extended TeX system is available for use under Linux, which among other things specialises in direct production of PDF from (La)TeX input. Sadly, it���s no longer supported, and the ready-built images are made for use with a rather ancient Linux kernel.
# NOTE: another way to capture metadata; if converting via ghostscript:
# http://compgroups.net/comp.text.pdf/How-to-specify-metadata-using-Ghostscript
# first:
# grep -a 'Keywo' orig.pdf
# /Author(xxx)/Title(ttt)/Subject()/Creator(LaTeX)/Producer(pdfTeX-1.40.12)/Keywords(kkkk)
# then - copy this data in a file prologue.ini:
#/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse
#/Creator(LaTeX with hyperref package + gs w/ prologue)
#/DOCINFO pdfmark
# finally, call gs on the orig file,
# asking to process pdfmarks in prologue.ini:
# gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 \
# -sOutputFile=out.pdf in.pdf prologue.ini
# then the metadata will be in output too (which is stripped otherwise;
# note bookmarks are preserved, however). 

I also had some scanned color pdfs and grayscale pdfs that I wanted to convert to bw. I tried using gs with the code listed here, and image quality is good with pdf text still there. However, that gs code only converts to grayscale (as asked in the question) and still has large file size. convert yields very poor results when used directly.

I wanted bw pdfs with good image quality and small file size. I would have tried terdon's solution, but I could not get pdftk on centOS 7 using yum (at time of writing).

My solution uses gs to extract grayscale bmp files from the pdf, convert to threshold those bmps to bw and save them as tiff files, and then img2pdf to compress the tiff images and merge them all into one pdf.

I tried going directly to tiff from the pdf but the quality is not the same so I save each page to bmp. For a one page pdf file, convert does a great job from bmp to pdf. Example:

gs -sDEVICE=bmpgray -dNOPAUSE -dBATCH -r300x300 \
   -sOutputFile=./pdf_image.bmp ./input.pdf

convert ./pdf_image.bmp -threshold 40% -compress zip ./bw_out.pdf

For multiple pages, gs can merge multiple pdf files into one, but img2pdf yields smaller file size than gs. The tiff files must be uncompressed as input to img2pdf. Keep in mind for large numbers of pages, the intermediate bmp and tiff files tend to be large in size. pdftk or joinpdf would be better if they can merge compressed pdf files from convert.

I imagine there is a more elegant solution. However, my method produces results with very good image quality and much smaller file size. To get text back in the bw pdf, run OCR again.

My shell script uses gs, convert, and img2pdf. Change the parameters (# of pages, scan dpi, threshold %, etc) listed in the beginning as needed, and run chmod +x ./pdf2bw.sh . Here is the full script (pdf2bw.sh):


gs -sDEVICE=bmpgray -dNOPAUSE -dBATCH -q -r$dpi_res \
   -sOutputFile=./%d.bmp ./$input_pdf_name
for file_num in `seq 1 $num_pages`
  convert ./$file_num.bmp -threshold $bw_threshold \

for file_num in `seq 1 $num_pages`
  input_files+="./$file_num.tif "

img2pdf -o ./$output_pdf_name --dpi $dpi_res $input_files
# clean up bmp and tif files used in conversion

for file_num in `seq 1 $num_pages`
  rm ./$file_num.bmp
  rm ./$file_num.tif

I get reliable results cleaning up scanned pdf's to good contrast with this script;

# $ sudo apt install poppler-utils img2pdf pdftk imagemagick
# Output is still greyscale, but lots of scanner light tone fuzz removed.

pdfimages $1 pages

ls ./pages*.ppm | xargs -L1 -I {} convert {}  -quality 100 -density 400 \
  -fill white -fuzz 80% -auto-level -depth 4 +opaque "#000000" {}.jpg

ls -1 ./pages*jpg | xargs -L1 -I {} img2pdf {} -o {}.pdf

pdftk pages*.pdf cat output ${1/.pdf/}_bw.pdf

rm pages*
  • Be careful of filenames with spaces with this script. Sadly it is beyond my bash skills to suggest a fix.
    – kuzzooroo
    Feb 4, 2021 at 18:34

RHEL6 and RHEL5, which both baseline Ghostscript on 8.70, couldn't use the forms of the command given above. Assuming a script or a function expecting the PDF file as the first argument "$1", the following should be more portable:

gs \
    -sOutputFile="grey_$1" \
    -sDEVICE=pdfwrite \
    -sColorConversionStrategy=Mono \
    -sColorConversionStrategyForImages=/Mono \
    -dProcessColorModel=/DeviceGray \
    -dCompatibilityLevel=1.3 \

Where the output file will be prefixed with "grey_".

RHEL6 and 5 can use CompatibilityLevel=1.4 which is much quicker, but I was aiming for portability.

  • Devs say (1,2,3,4) that there's no sColorConversionStrategyForImages switch.
    – Igor
    Mar 19, 2019 at 18:48
  • Thanks, @Igor -- I have no idea where I got that snippet from! I know for a fact that I tested it and it worked at the time. (And that, folks, is why you should always provide references for your code.)
    – Rich
    Mar 19, 2019 at 18:58
  • 1
    That "fake parameter" seem to be an incredibly popular among the web. GS ignores unknown switches (which is sad), so it works anyway.
    – Igor
    Mar 19, 2019 at 20:14

There are many great answers above. I took one of the answers above and added some human interfacing to it. Maybe someone will find it useful.


    if [ -z "$1" ]; then
        return 1

    if [ ! -f "$inputFile" ]; then
        echo "File not found"
        echo "$inputFile"
        return 2

    fileType="`file -b --mime-type \"$inputFile\"`"

    if [ "$fileType" != 'application/pdf' ]; then
        echo "This file is not a pdf"
        echo "$inputFile"
        return 3

    outFile="`basename -s .pdf \"$inputFile\"`-gray.pdf"

    if [ -f "$outFile" ]; then

        echo -en "File Exists, overwrite it? (Y/n) "
        read overWrite

        if [ -z "$overWrite" ]; then

        if [[ "$overWrite" != "y" && "$overWrite" != "Y" ]]; then
            return 4
gs \
-q \
-sOutputFile="$outFile" \
-sDEVICE=pdfwrite \
-sColorConversionStrategy=Gray \
-dProcessColorModel=/DeviceGray \
-dCompatibilityLevel=1.4 \

echo -en "\033[1;32m"
echo -n "$PWD/$outFile"
echo -e "\033[0m"


if [ -z $1 ]; then
    echo "usage:"
    echo "$0 file1.pdf file2.pdf file3.pdf ..."
    for file in "$@"
        pdf2Gray "$file"
        #echo $? #debug

Colored PDF to black-and-white PDF

The netpbm toolkit and mutool offer a flexible solution to get black-and-white PDF ¹. Typically, output-bw.pdf is about 1/3 smaller than input.pdf.

mutool draw -r 150 -F pam -c gray -o "" input.pdf \
     | pamexec "pamthreshold" | pamtotiff >tmp.tiff \
     && tiff2pdf -o output-bw.pdf tmp.tiff && rm tmp.tiff

First, mutool is used to rasterize input.pdf to a multi-image stream in pam grayscale format ². Then, pamexec run pamthreshold in order to threshold each pam image from grayscale to black-and-white. pamtotiff converts the stream to a temporary multipage tiff. This tmp.tiff is removed after conversion to .pdf by tiff2pdf.

[¹]: Embedded bilevel images, not grayscale.
[²]: Indeed, -F pam can be changed by -F pgm.

Make PDF searchable

To make output-bw.pdf searchable, use tesseract instead of tiff2pdf ³.

mutool draw -r 150 -F pam -c gray -o "" input.pdf \
     | pamexec "pamthreshold" | pamtotiff \
     | tesseract stdin "output-bws" --dpi 150 pdf

[³]: tesseract can be slower than tiff2pdf

Achieving high quality output

Adjust the rendering resolution as necessary, let's say 300 dpi instead of 150, and apply other filters from the netpbm toolkit.

mutool draw -r 300 -F pgm -c gray -o "" input.pdf \
     |  pamexec  "pamditherbw|pgmtopbm|pbmclean -minneighbors=2" \
     | pamtotiff -lzw >tmp.tiff \
     && tiff2pdf -o output-bw.pdf tmp.tiff && rm tmp.tiff

Check the man pages for results that fit your needs.

Memory Usage

Some Netpbm programs must keep the entire image in memory and when Netpbm keeps a pixel in memory, it normally uses a lot more space for it than it occupies in the Netpbm image file format.

On the other hand, because of the way the TIFF library works, when the pamtotiff writes to Standard Output, it generates the entire TIFF image in a temporary file and then copies it to Standard Output. If input.pdf is large enough, you may see negative performance effects.

Files with hundreds of pages can be processed page by page with the following bash script.

LANG="eng" #use "spa" for spanish
DPI=150 #resolution for mutool, dpi for tesseract
for I in $@; do
    [ "application/pdf" != $(file -b --mime-type "${1}") ] && continue
    BASE_NAME=$(basename "${I}" ".pdf")
    TMP_PAGE=$(mktemp -u "pag.XXXX")
    NUM_PAGES=$(mutool show "${I}" trailer/Root/Pages/Count 2>/dev/null)
    echo "${I}: rasterizing ${NUM_PAGES} pages to monochrome:";
    for PAGE in $(seq $NUM_PAGES); do
        printf "page %2d/%d........." "${PAGE}" "${NUM_PAGES}"
        mutool draw -r "${DPI}" -F pgm -c gray -o "" "${I}" ${PAGE} \
             | pamthreshold -quiet \
             | tesseract stdin "${TMP_PAGE}-${PAGE}" --dpi "${DPI}" -l "${LANG}" pdf 2>/dev/null
        [ $? -ne 0 ] && { echo "¡Error!\n"; exit 1; } || echo "[OK]";
    #Consider pdfunite or qpdf to concatenate the pages
    QPDF_CMD="qpdf --empty --pages "
    QPDF_CMD+=$(eval "echo ${TMP_PAGE}-{1..$NUM_PAGES}.pdf")
    QPDF_CMD+=" -- \"${BASE_NAME}-bw.pdf\""
    eval "${QPDF_CMD}" && rm ${TMP_PAGE}-*.pdf tesseract_*.dat

Colored PDF to grayscale PDF

mutool draw -r 150 -F pam -c gray -o "" input.pdf \
     | pamtotiff | tesseract stdin "output-gray" --dpi 150 pdf

However, Ghostscript offers better results in this case.

Reduce number of colors in a PDF

For example, to use at most 8 colors use:

mutool draw -r 150 -F pam -c rgb -o "" input.pdf \
     | pnmquant 8 | pamtotiff | tesseract stdin "output-rgb8" --dpi 150 pdf

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.