// SPDX-License-Identifier: GPL-2.0-or-later
/**
 * @file
 * SVG drawing for display.
 *//*
 * Authors:
 *   Krzysztof Kosiński <tweenk.pl@gmail.com>
 *   Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
 *
 * Copyright (C) 2011-2012 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <algorithm>
#include "display/drawing.h"
#include "nr-filter-gaussian.h"
#include "nr-filter-types.h"

//grayscale colormode:
#include "cairo-templates.h"
#include "drawing-context.h"
#include "canvas-arena.h"


namespace Inkscape {

// hardcoded grayscale color matrix values as default
static const gdouble grayscale_value_matrix[20] = {
    0.21, 0.72, 0.072, 0, 0,
    0.21, 0.72, 0.072, 0, 0,
    0.21, 0.72, 0.072, 0, 0,
    0   , 0   , 0    , 1, 0
};

Drawing::Drawing(SPCanvasArena *arena)
    : _root(nullptr)
    , outlinecolor(0x000000ff)
    , delta(0)
    , _exact(false)
    , _outline_sensitive(true)
    , _rendermode(RENDERMODE_NORMAL)
    , _colormode(COLORMODE_NORMAL)
    , _blur_quality(BLUR_QUALITY_BEST)
    , _filter_quality(Filters::FILTER_QUALITY_BEST)
    , _cache_score_threshold(50000.0)
    , _cache_budget(0)
    , _grayscale_colormatrix(std::vector<gdouble>(grayscale_value_matrix, grayscale_value_matrix + 20))
    , _canvasarena(arena)
{

}

Drawing::~Drawing()
{
    delete _root;
}

void
Drawing::setRoot(DrawingItem *item)
{
    delete _root;
    _root = item;
    if (item) {
        assert(item->_child_type == DrawingItem::CHILD_ORPHAN);
        item->_child_type = DrawingItem::CHILD_ROOT;
    }
}

RenderMode
Drawing::renderMode() const
{
    return _exact ? RENDERMODE_NORMAL : _rendermode;
}
ColorMode
Drawing::colorMode() const
{
    return (outline() || _exact) ? COLORMODE_NORMAL : _colormode;
}
bool
Drawing::outline() const
{
    return renderMode() == RENDERMODE_OUTLINE;
}
bool
Drawing::visibleHairlines() const
{
    return renderMode() == RENDERMODE_VISIBLE_HAIRLINES;
}
bool
Drawing::renderFilters() const
{
    return renderMode() == RENDERMODE_NORMAL || renderMode() == RENDERMODE_VISIBLE_HAIRLINES;
}
int
Drawing::blurQuality() const
{
    if (renderMode() == RENDERMODE_NORMAL) {
        return _exact ? BLUR_QUALITY_BEST : _blur_quality;
    } else {
        return BLUR_QUALITY_WORST;
    }
}
int
Drawing::filterQuality() const
{
    if (renderMode() == RENDERMODE_NORMAL) {
        return _exact ? Filters::FILTER_QUALITY_BEST : _filter_quality;
    } else {
        return Filters::FILTER_QUALITY_WORST;
    }
}

void
Drawing::setRenderMode(RenderMode mode)
{
    _rendermode = mode;
}
void
Drawing::setColorMode(ColorMode mode)
{
    _colormode = mode;
}
void
Drawing::setBlurQuality(int q)
{
    _blur_quality = q;
}
void
Drawing::setFilterQuality(int q)
{
    _filter_quality = q;
}
void
Drawing::setExact(bool e)
{
    _exact = e;
}

void Drawing::setOutlineSensitive(bool e) { _outline_sensitive = e; };

Geom::OptIntRect const &
Drawing::cacheLimit() const
{
    return _cache_limit;
}
void
Drawing::setCacheLimit(Geom::OptIntRect const &r, bool update_cache)
{
    _cache_limit = r;
    if (update_cache) {
        for (auto _cached_item : _cached_items)
        {
            _cached_item->_markForUpdate(DrawingItem::STATE_CACHE, false);
        }
    }
}
void
Drawing::setCacheBudget(size_t bytes)
{
    _cache_budget = bytes;
    _pickItemsForCaching();
}

void
Drawing::setGrayscaleMatrix(gdouble value_matrix[20]) {
    _grayscale_colormatrix = Filters::FilterColorMatrix::ColorMatrixMatrix( 
        std::vector<gdouble> (value_matrix, value_matrix + 20) );
}

void
Drawing::update(Geom::IntRect const &area, unsigned flags, unsigned reset)
{
    if (_root) {
        auto ctx = _canvasarena ? _canvasarena->ctx : UpdateContext();
        _root->update(area, ctx, flags, reset);
    }
    if ((flags & DrawingItem::STATE_CACHE) || (flags & DrawingItem::STATE_ALL)) {
        // process the updated cache scores
        _pickItemsForCaching();
    }
}

void
Drawing::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, int antialiasing)
{
    if (_root) {
        int prev_a = _root->_antialias;
        if(antialiasing >= 0)
            _root->setAntialiasing(antialiasing);
        _root->render(dc, area, flags);
        _root->setAntialiasing(prev_a);
    }

    if (colorMode() == COLORMODE_GRAYSCALE) {
        // apply grayscale filter on top of everything
        cairo_surface_t *input = dc.rawTarget();
        cairo_surface_t *out = ink_cairo_surface_create_identical(input);
        ink_cairo_surface_filter(input, out, _grayscale_colormatrix);
        Geom::Point origin = dc.targetLogicalBounds().min();
        dc.setSource(out, origin[Geom::X], origin[Geom::Y]);
        dc.setOperator(CAIRO_OPERATOR_SOURCE);
        dc.paint();
        dc.setOperator(CAIRO_OPERATOR_OVER);
    
        cairo_surface_destroy(out);
    }
}

DrawingItem *
Drawing::pick(Geom::Point const &p, double delta, unsigned flags)
{
    if (_root) {
        return _root->pick(p, delta, flags);
    }
    return nullptr;
}

void
Drawing::_pickItemsForCaching()
{
    // we cache the objects with the highest score until the budget is exhausted
    _candidate_items.sort(std::greater<CacheRecord>());
    size_t used = 0;
    CandidateList::iterator i;
    for (i = _candidate_items.begin(); i != _candidate_items.end(); ++i) {
        if (used + i->cache_size > _cache_budget) break;
        used += i->cache_size;
    }

    std::set<DrawingItem*> to_cache;
    for (CandidateList::iterator j = _candidate_items.begin(); j != i; ++j) {
        j->item->setCached(true);
        to_cache.insert(j->item);
    }
    // Everything which is now in _cached_items but not in to_cache must be uncached
    // Note that calling setCached on an item modifies _cached_items
    // TODO: find a way to avoid the set copy
    std::set<DrawingItem*> to_uncache;
    std::set_difference(_cached_items.begin(), _cached_items.end(),
                        to_cache.begin(), to_cache.end(),
                        std::inserter(to_uncache, to_uncache.end()));
    for (auto j : to_uncache) {
        j->setCached(false);
    }
}

} // end namespace Inkscape

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
