QPDF_Stream::filterable(std::vector<std::string>& filters,
                        bool& specialized_compression,
                        bool& lossy_compression,
			int& predictor, int& columns,
                        int& colors, int& bits_per_component,
			bool& early_code_change)
{
    if (filter_abbreviations.empty())
    {
	// The PDF specification provides these filter abbreviations
	// for use in inline images, but according to table H.1 in the
	// pre-ISO versions of the PDF specification, Adobe Reader
	// also accepts them for stream filters.
	filter_abbreviations["/AHx"] = "/ASCIIHexDecode";
	filter_abbreviations["/A85"] = "/ASCII85Decode";
	filter_abbreviations["/LZW"] = "/LZWDecode";
	filter_abbreviations["/Fl"] = "/FlateDecode";
	filter_abbreviations["/RL"] = "/RunLengthDecode";
	filter_abbreviations["/CCF"] = "/CCITTFaxDecode";
	filter_abbreviations["/DCT"] = "/DCTDecode";
    }

    // Check filters

    QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter");
    bool filters_okay = true;

    if (filter_obj.isNull())
    {
	// No filters
    }
    else if (filter_obj.isName())
    {
	// One filter
	filters.push_back(filter_obj.getName());
    }
    else if (filter_obj.isArray())
    {
	// Potentially multiple filters
	int n = filter_obj.getArrayNItems();
	for (int i = 0; i < n; ++i)
	{
	    QPDFObjectHandle item = filter_obj.getArrayItem(i);
	    if (item.isName())
	    {
		filters.push_back(item.getName());
	    }
	    else
	    {
		filters_okay = false;
	    }
	}
    }
    else
    {
	filters_okay = false;
    }

    if (! filters_okay)
    {
	QTC::TC("qpdf", "QPDF_Stream invalid filter");
	warn(QPDFExc(qpdf_e_damaged_pdf, qpdf->getFilename(),
                     "", this->offset,
                     "stream filter type is not name or array"));
        return false;
    }

    bool filterable = true;

    for (std::vector<std::string>::iterator iter = filters.begin();
	 iter != filters.end(); ++iter)
    {
	std::string& filter = *iter;

	if (filter_abbreviations.count(filter))
	{
	    QTC::TC("qpdf", "QPDF_Stream expand filter abbreviation");
	    filter = filter_abbreviations[filter];
	}

        if (filter == "/RunLengthDecode")
        {
            specialized_compression = true;
        }
        else if (filter == "/DCTDecode")
        {
            specialized_compression = true;
            lossy_compression = true;
        }
	else if (! ((filter == "/Crypt") ||
                    (filter == "/FlateDecode") ||
                    (filter == "/LZWDecode") ||
                    (filter == "/ASCII85Decode") ||
                    (filter == "/ASCIIHexDecode")))
	{
	    filterable = false;
	}
    }

    if (! filterable)
    {
        return false;
    }

    // `filters' now contains a list of filters to be applied in
    // order.  See which ones we can support.

    // Initialize values to their defaults as per the PDF spec
    predictor = 1;
    columns = 0;
    colors = 1;
    bits_per_component = 8;
    early_code_change = true;

    // See if we can support any decode parameters that are specified.

    QPDFObjectHandle decode_obj = this->stream_dict.getKey("/DecodeParms");
    std::vector<QPDFObjectHandle> decode_parms;
    if (decode_obj.isArray() && (decode_obj.getArrayNItems() == 0))
    {
        decode_obj = QPDFObjectHandle::newNull();
    }
    if (decode_obj.isArray())
    {
        for (int i = 0; i < decode_obj.getArrayNItems(); ++i)
        {
            decode_parms.push_back(decode_obj.getArrayItem(i));
        }
    }
    else
    {
        for (unsigned int i = 0; i < filters.size(); ++i)
        {
            decode_parms.push_back(decode_obj);
        }
    }

    // Ignore /DecodeParms entirely if /Filters is empty.  At least
    // one case of a file whose /DecodeParms was [ << >> ] when
    // /Filters was empty has been seen in the wild.
    if ((filters.size() != 0) && (decode_parms.size() != filters.size()))
    {
        warn(QPDFExc(qpdf_e_damaged_pdf, qpdf->getFilename(),
                     "", this->offset,
                     "stream /DecodeParms length is"
                     " inconsistent with filters"));
        filterable = false;
    }

    if (! filterable)
    {
        return false;
    }

    for (unsigned int i = 0; i < filters.size(); ++i)
    {
        QPDFObjectHandle decode_item = decode_parms.at(i);
        if (decode_item.isNull())
        {
            // okay
        }
        else if (decode_item.isDictionary())
        {
            if (! understandDecodeParams(
                    filters.at(i), decode_item,
                    predictor, columns, colors, bits_per_component,
                    early_code_change))
            {
                filterable = false;
            }
        }
        else
        {
            filterable = false;
        }
    }

    if ((predictor > 1) && (columns == 0))
    {
	// invalid
	filterable = false;
    }

    if (! filterable)
    {
	return false;
    }

    return filterable;
}