Code

Fixed redraw-area dependent result when using filters with low quality
[inkscape.git] / src / display / nr-filter-slot.cpp
1 /*
2  * A container class for filter slots. Allows for simple getting and
3  * setting images in filter slots without having to bother with
4  * table indexes and such.
5  *
6  * Author:
7  *   Niko Kiirala <niko@kiirala.com>
8  *
9  * Copyright (C) 2006,2007 Niko Kiirala
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #include <assert.h>
15 #include <string.h>
17 #include "display/nr-arena-item.h"
18 #include "display/nr-filter-types.h"
19 #include "display/nr-filter-slot.h"
20 #include "display/nr-filter-getalpha.h"
21 #include "display/nr-filter-units.h"
22 #include "display/pixblock-scaler.h"
23 #include "display/pixblock-transform.h"
24 #include "libnr/nr-pixblock.h"
25 #include "libnr/nr-blit.h"
27 __attribute__ ((const))
28 inline static int _max4(const double a, const double b,
29                         const double c, const double d) {
30     double ret = a;
31     if (b > ret) ret = b;
32     if (c > ret) ret = c;
33     if (d > ret) ret = d;
34     return (int)round(ret);
35 }
37 __attribute__ ((const))
38 inline static int _min4(const double a, const double b,
39                         const double c, const double d) {
40     double ret = a;
41     if (b < ret) ret = b;
42     if (c < ret) ret = c;
43     if (d < ret) ret = d;
44     return (int)round(ret);
45 }
47 __attribute__ ((const))
48 inline static int _max2(const double a, const double b) {
49     if (a > b)
50         return (int)round(a);
51     else
52         return (int)round(b);
53 }
55 __attribute__ ((const))
56 inline static int _min2(const double a, const double b) {
57     if (a > b)
58         return (int)round(b);
59     else
60         return (int)round(a);
61 }
63 namespace Inkscape {
64 namespace Filters {
66 FilterSlot::FilterSlot(int slots, NRArenaItem const *item)
67     : _last_out(-1),
68       filterquality(FILTER_QUALITY_BEST),
69       _arena_item(item)
70 {
71     _slot_count = ((slots > 0) ? slots : 2);
72     _slot = new NRPixBlock*[_slot_count];
73     _slot_number = new int[_slot_count];
75     for (int i = 0 ; i < _slot_count ; i++) {
76         _slot[i] = NULL;
77         _slot_number[i] = NR_FILTER_SLOT_NOT_SET;
78     }
79 }
81 FilterSlot::~FilterSlot()
82 {
83     for (int i = 0 ; i < _slot_count ; i++) {
84         if (_slot[i]) {
85             nr_pixblock_release(_slot[i]);
86             delete _slot[i];
87         }
88     }
89     delete[] _slot;
90     delete[] _slot_number;
91 }
93 NRPixBlock *FilterSlot::get(int slot_nr)
94 {
95     int index = _get_index(slot_nr);
96     assert(index >= 0);
98     /* If we didn't have the specified image, but we could create it
99      * from the other information we have, let's do that */
100     if (_slot[index] == NULL
101         && (slot_nr == NR_FILTER_SOURCEALPHA
102             || slot_nr == NR_FILTER_BACKGROUNDIMAGE
103             || slot_nr == NR_FILTER_BACKGROUNDALPHA
104             || slot_nr == NR_FILTER_FILLPAINT
105             || slot_nr == NR_FILTER_STROKEPAINT))
106     {
107         /* If needed, fetch background */
108         if (slot_nr == NR_FILTER_BACKGROUNDIMAGE) {
109             NRPixBlock *pb;
110             pb = nr_arena_item_get_background(_arena_item);
111             if (pb) {
112                 pb->empty = false;
113                 this->set(NR_FILTER_BACKGROUNDIMAGE, pb);
114             } else {
115                 NRPixBlock *source = this->get(NR_FILTER_SOURCEGRAPHIC);
116                 pb = new NRPixBlock();
117                 if (!pb) return NULL; // Allocation failed
118                 nr_pixblock_setup_fast(pb, source->mode,
119                                        source->area.x0, source->area.y0,
120                                        source->area.x1, source->area.y1, true);
121                 if (pb->size != NR_PIXBLOCK_SIZE_TINY && pb->data.px == NULL) {
122                     // allocation failed
123                     delete pb;
124                     return NULL;
125                 }
126                 pb->empty = FALSE;
127                 this->set(NR_FILTER_BACKGROUNDIMAGE, pb);
128             }
129         } else if (slot_nr == NR_FILTER_SOURCEALPHA) {
130             /* If only a alpha channel is needed, strip it from full image */
131             NRPixBlock *src = get(NR_FILTER_SOURCEGRAPHIC);
132             NRPixBlock *sa = filter_get_alpha(src);
133             set(NR_FILTER_SOURCEALPHA, sa);
134         } else if (slot_nr == NR_FILTER_BACKGROUNDALPHA) {
135             NRPixBlock *src = get(NR_FILTER_BACKGROUNDIMAGE);
136             NRPixBlock *ba = filter_get_alpha(src);
137             set(NR_FILTER_BACKGROUNDALPHA, ba);
138         } else if (slot_nr == NR_FILTER_FILLPAINT) {
139             /* When a paint is needed, fetch it from arena item */
140             // TODO
141         } else if (slot_nr == NR_FILTER_STROKEPAINT) {
142             // TODO
143         }
144     }
146     if (_slot[index]) {
147         _slot[index]->empty = false;
148     }
150     assert(slot_nr == NR_FILTER_SLOT_NOT_SET ||_slot_number[index] == slot_nr);
151     return _slot[index];
154 void FilterSlot::get_final(int slot_nr, NRPixBlock *result) {
155     NRPixBlock *final_usr = get(slot_nr);
156     Geom::Matrix trans = units.get_matrix_pb2display();
158     int size = (result->area.x1 - result->area.x0)
159         * (result->area.y1 - result->area.y0)
160         * NR_PIXBLOCK_BPP(result);
161     memset(NR_PIXBLOCK_PX(result), 0, size);
163     if (fabs(trans[1]) > 1e-6 || fabs(trans[2]) > 1e-6) {
164         if (filterquality == FILTER_QUALITY_BEST) {
165             NR::transform_bicubic(result, final_usr, trans);
166         } else {
167             NR::transform_nearest(result, final_usr, trans);
168         }
169     } else if (fabs(trans[0] - 1) > 1e-6 || fabs(trans[3] - 1) > 1e-6) {
170         NR::scale_bicubic(result, final_usr, trans);
171     } else {
172         nr_blit_pixblock_pixblock(result, final_usr);
173     }
176 void FilterSlot::set(int slot_nr, NRPixBlock *pb)
178     /* Unnamed slot is for saving filter primitive results, when parameter
179      * 'result' is not set. Only the filter immediately after this one
180      * can access unnamed results, so we don't have to worry about overwriting
181      * previous results in filter chain. On the other hand, we may not
182      * overwrite any other image with this one, because they might be
183      * accessed later on. */
184     int index = ((slot_nr != NR_FILTER_SLOT_NOT_SET)
185                  ? _get_index(slot_nr)
186                  : _get_index(NR_FILTER_UNNAMED_SLOT));
187     assert(index >= 0);
188     // Unnamed slot is only for Inkscape::Filters::FilterSlot internal use.
189     assert(slot_nr != NR_FILTER_UNNAMED_SLOT);
190     assert(slot_nr == NR_FILTER_SLOT_NOT_SET ||_slot_number[index] == slot_nr);
192     if (slot_nr == NR_FILTER_SOURCEGRAPHIC || slot_nr == NR_FILTER_BACKGROUNDIMAGE) {
193         Geom::Matrix trans = units.get_matrix_display2pb();
194         if (fabs(trans[1]) > 1e-6 || fabs(trans[2]) > 1e-6) {
195             NRPixBlock *trans_pb = new NRPixBlock;
196             int x0 = pb->area.x0;
197             int y0 = pb->area.y0;
198             int x1 = pb->area.x1;
199             int y1 = pb->area.y1;
200             int min_x = _min4(trans[0] * x0 + trans[2] * y0 + trans[4],
201                               trans[0] * x0 + trans[2] * y1 + trans[4],
202                               trans[0] * x1 + trans[2] * y0 + trans[4],
203                               trans[0] * x1 + trans[2] * y1 + trans[4]);
204             int max_x = _max4(trans[0] * x0 + trans[2] * y0 + trans[4],
205                               trans[0] * x0 + trans[2] * y1 + trans[4],
206                               trans[0] * x1 + trans[2] * y0 + trans[4],
207                               trans[0] * x1 + trans[2] * y1 + trans[4]);
208             int min_y = _min4(trans[1] * x0 + trans[3] * y0 + trans[5],
209                               trans[1] * x0 + trans[3] * y1 + trans[5],
210                               trans[1] * x1 + trans[3] * y0 + trans[5],
211                               trans[1] * x1 + trans[3] * y1 + trans[5]);
212             int max_y = _max4(trans[1] * x0 + trans[3] * y0 + trans[5],
213                               trans[1] * x0 + trans[3] * y1 + trans[5],
214                               trans[1] * x1 + trans[3] * y0 + trans[5],
215                               trans[1] * x1 + trans[3] * y1 + trans[5]);
217             nr_pixblock_setup_fast(trans_pb, pb->mode,
218                                    min_x, min_y,
219                                    max_x, max_y, true);
220             if (trans_pb->size != NR_PIXBLOCK_SIZE_TINY && trans_pb->data.px == NULL) {
221                 /* TODO: this gets hit occasionally. Worst case scenario:
222                  * images are exported in horizontal stripes. One stripe
223                  * is not too high, but can get thousands of pixels wide.
224                  * Rotate this 45 degrees -> _huge_ image */
225                 g_warning("Memory allocation failed in Inkscape::Filters::FilterSlot::set (transform)");
226                 return;
227             }
228             if (filterquality == FILTER_QUALITY_BEST) {
229                 NR::transform_bicubic(trans_pb, pb, trans);
230             } else {
231                 NR::transform_nearest(trans_pb, pb, trans);
232             }
233             nr_pixblock_release(pb);
234             delete pb;
235             pb = trans_pb;
236         } else if (fabs(trans[0] - 1) > 1e-6 || fabs(trans[3] - 1) > 1e-6) {
237             NRPixBlock *trans_pb = new NRPixBlock;
239             int x0 = pb->area.x0;
240             int y0 = pb->area.y0;
241             int x1 = pb->area.x1;
242             int y1 = pb->area.y1;
243             int min_x = _min2(trans[0] * x0 + trans[4],
244                               trans[0] * x1 + trans[4]);
245             int max_x = _max2(trans[0] * x0 + trans[4],
246                               trans[0] * x1 + trans[4]);
247             int min_y = _min2(trans[3] * y0 + trans[5],
248                               trans[3] * y1 + trans[5]);
249             int max_y = _max2(trans[3] * y0 + trans[5],
250                               trans[3] * y1 + trans[5]);
252             nr_pixblock_setup_fast(trans_pb, pb->mode,
253                                    min_x, min_y, max_x, max_y, true);
254             if (trans_pb->size != NR_PIXBLOCK_SIZE_TINY && trans_pb->data.px == NULL) {
255                 g_warning("Memory allocation failed in Inkscape::Filters::FilterSlot::set (scaling)");
256                 return;
257             }
258             NR::scale_bicubic(trans_pb, pb, trans);
259             nr_pixblock_release(pb);
260             delete pb;
261             pb = trans_pb;
262         }
263     }
265     if(_slot[index]) {
266         nr_pixblock_release(_slot[index]);
267         delete _slot[index];
268     }
269     _slot[index] = pb;
270     _last_out = index;
273 int FilterSlot::get_slot_count()
275     int seek = _slot_count;
276     do {
277         seek--;
278     } while (!_slot[seek] && _slot_number[seek] == NR_FILTER_SLOT_NOT_SET);
280     return seek + 1;
283 NRArenaItem const* FilterSlot::get_arenaitem()
285         return _arena_item;
288 int FilterSlot::_get_index(int slot_nr)
290     assert(slot_nr >= 0 ||
291            slot_nr == NR_FILTER_SLOT_NOT_SET ||
292            slot_nr == NR_FILTER_SOURCEGRAPHIC ||
293            slot_nr == NR_FILTER_SOURCEALPHA ||
294            slot_nr == NR_FILTER_BACKGROUNDIMAGE ||
295            slot_nr == NR_FILTER_BACKGROUNDALPHA ||
296            slot_nr == NR_FILTER_FILLPAINT ||
297            slot_nr == NR_FILTER_STROKEPAINT ||
298            slot_nr == NR_FILTER_UNNAMED_SLOT);
300     int index = -1;
301     if (slot_nr == NR_FILTER_SLOT_NOT_SET) {
302         return _last_out;
303     }
304     /* Search, if the slot already exists */
305     for (int i = 0 ; i < _slot_count ; i++) {
306         if (_slot_number[i] == slot_nr) {
307             index = i;
308             break;
309         }
310     }
312     /* If the slot doesn't already exist, create it */
313     if (index == -1) {
314         int seek = _slot_count;
315         do {
316             seek--;
317         } while ((seek >= 0) && (_slot_number[seek] == NR_FILTER_SLOT_NOT_SET));
318         /* If there is no space for more slots, create more space */
319         if (seek == _slot_count - 1) {
320             NRPixBlock **new_slot = new NRPixBlock*[_slot_count * 2];
321             int *new_number = new int[_slot_count * 2];
322             for (int i = 0 ; i < _slot_count ; i++) {
323                 new_slot[i] = _slot[i];
324                 new_number[i] = _slot_number[i];
325             }
326             for (int i = _slot_count ; i < _slot_count * 2 ; i++) {
327                 new_slot[i] = NULL;
328                 new_number[i] = NR_FILTER_SLOT_NOT_SET;
329             }
330             delete[] _slot;
331             delete[] _slot_number;
332             _slot = new_slot;
333             _slot_number = new_number;
334             _slot_count *= 2;
335         }
336         /* Now that there is space, create the slot */
337         _slot_number[seek + 1] = slot_nr;
338         index = seek + 1;
339     }
340     return index;
343 void FilterSlot::set_units(FilterUnits const &units) {
344     this->units = units;
347 void FilterSlot::set_quality(FilterQuality const q) {
348     filterquality = q;
351 } /* namespace Filters */
352 } /* namespace Inkscape */
354 /*
355   Local Variables:
356   mode:c++
357   c-file-style:"stroustrup"
358   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
359   indent-tabs-mode:nil
360   fill-column:99
361   End:
362 */
363 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :