Code

Added bitmap transformer to fix blur with rotation and non-uniform scaling
authorkiirala <kiirala@users.sourceforge.net>
Wed, 3 Jan 2007 14:12:44 +0000 (14:12 +0000)
committerkiirala <kiirala@users.sourceforge.net>
Wed, 3 Jan 2007 14:12:44 +0000 (14:12 +0000)
src/display/Makefile_insert
src/display/nr-filter.cpp
src/display/pixblock-transform.cpp [new file with mode: 0644]
src/display/pixblock-transform.h [new file with mode: 0644]

index a6cecaffbdc1cf790054347628b197cca599635e..4d51a789107923f664a5de1fa80c1c2160faf660 100644 (file)
@@ -73,7 +73,9 @@ display_libspdisplay_a_SOURCES = \
        display/nr-filter-slot.h        \
        display/nr-filter-types.h       \
        display/pixblock-scaler.cpp     \
-       display/pixblock-scaler.h
+       display/pixblock-scaler.h       \
+       display/pixblock-transform.cpp  \
+       display/pixblock-transform.h
 
 display_bezier_utils_test_SOURCES = display/bezier-utils-test.cpp
 display_bezier_utils_test_LDADD = libnr/libnr.a -lglib-2.0
index 592623c495e0855a741be4b342e3aaced2bfd146..f00a1ef6b3c922183250b0d911be3f33388a40f5 100644 (file)
@@ -12,6 +12,7 @@
  */
 
 #include <glib.h>
+#include <cmath>
 
 #include "display/nr-filter.h"
 #include "display/nr-filter-primitive.h"
@@ -19,6 +20,7 @@
 #include "display/nr-filter-slot.h"
 #include "display/nr-filter-types.h"
 #include "display/pixblock-scaler.h"
+#include "display/pixblock-transform.h"
 
 #include "display/nr-arena-item.h"
 #include "libnr/nr-pixblock.h"
 
 //#include "display/nr-arena-shape.h"
 
+__attribute__ ((const))
+inline static int _max4(const double a, const double b,
+                        const double c, const double d) {
+    double ret = a;
+    if (b > ret) ret = b;
+    if (c > ret) ret = c;
+    if (d > ret) ret = d;
+    return (int)round(ret);
+}
+
+__attribute__ ((const))
+inline static int _min4(const double a, const double b,
+                        const double c, const double d) {
+    double ret = a;
+    if (b < ret) ret = b;
+    if (c < ret) ret = c;
+    if (d < ret) ret = d;
+    return (int)round(ret);
+}
+
 namespace NR {
 
 Filter::Filter()
@@ -90,12 +112,61 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb)
     }
 
     Matrix trans = *item->ctm;
+    Matrix paraller_trans = trans;
+    bool notparaller = false;
     FilterSlot slot(_slot_count, item);
     NRPixBlock *in = new NRPixBlock;
 
-    // First, if filter resolution is not set to automatic, we should
-    // scale the input image to correct resolution
-    if (_x_pixels >= 0) {
+    // If filter effects region is not paraller to viewport,
+    // we must first undo the rotation / shear.
+    // It will be redone after filtering.
+    // If there is only rotation and uniform scaling (zoom), let's skip this,
+    // as it will not make a difference with gaussian blur.
+    if ((fabs(trans[1]) > 1e-6 || fabs(trans[2]) > 1e-6) &&
+        !(fabs(trans[0] - trans[3]) < 1e-6 && fabs(trans[1] + trans[2]) < 1e-6)) {
+        notparaller = true;
+
+        // TODO: if filter resolution is specified, scaling should be set
+        // according to that
+        double scaling_factor = sqrt(trans.expansionX() * trans.expansionX() +
+                                     trans.expansionY() * trans.expansionY());
+        scale scaling(scaling_factor, scaling_factor);
+        scale scaling_inv(1.0 / scaling_factor, 1.0 / scaling_factor);
+        trans *= scaling_inv;
+        paraller_trans.set_identity();
+        paraller_trans *= scaling;
+
+        Matrix itrans = trans.inverse();
+        int x0 = pb->area.x0;
+        int y0 = pb->area.y0;
+        int x1 = pb->area.x1;
+        int y1 = pb->area.y1;
+        int min_x = _min4(itrans[0] * x0 + itrans[2] * y0 + itrans[4],
+                          itrans[0] * x0 + itrans[2] * y1 + itrans[4],
+                          itrans[0] * x1 + itrans[2] * y0 + itrans[4],
+                          itrans[0] * x1 + itrans[2] * y1 + itrans[4]);
+        int max_x = _max4(itrans[0] * x0 + itrans[2] * y0 + itrans[4],
+                          itrans[0] * x0 + itrans[2] * y1 + itrans[4],
+                          itrans[0] * x1 + itrans[2] * y0 + itrans[4],
+                          itrans[0] * x1 + itrans[2] * y1 + itrans[4]);
+        int min_y = _min4(itrans[1] * x0 + itrans[3] * y0 + itrans[5],
+                          itrans[1] * x0 + itrans[3] * y1 + itrans[5],
+                          itrans[1] * x1 + itrans[3] * y0 + itrans[5],
+                          itrans[1] * x1 + itrans[3] * y1 + itrans[5]);
+        int max_y = _max4(itrans[1] * x0 + itrans[3] * y0 + itrans[5],
+                          itrans[1] * x0 + itrans[3] * y1 + itrans[5],
+                          itrans[1] * x1 + itrans[3] * y0 + itrans[5],
+                          itrans[1] * x1 + itrans[3] * y1 + itrans[5]);
+        
+        nr_pixblock_setup_fast(in, pb->mode,
+                               min_x, min_y,
+                               max_x, max_y, true);
+        if (in->data.px == NULL) // memory allocation failed
+            return 0;
+        transform_nearest(in, pb, itrans);
+    } else if (_x_pixels >= 0) {
+        // If filter resolution is not set to automatic, we should
+        // scale the input image to correct resolution
         /* If filter resolution is zero, the object should not be rendered */
         if (_x_pixels == 0 || _y_pixels == 0) {
             int size = (pb->area.x1 - pb->area.x0)
@@ -125,7 +196,7 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb)
         scale_bicubic(in, pb);
         scale res_scaling(x_len / (double)(pb->area.x1 - pb->area.x0),
                           y_len / (double)(pb->area.y1 - pb->area.y0));
-        trans *= res_scaling;
+        paraller_trans *= res_scaling;
     } else {
         // If filter resolution is automatic, just make copy of input image
         nr_pixblock_setup_fast(in, pb->mode,
@@ -139,7 +210,7 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb)
     in = NULL; // in is now handled by FilterSlot, we should not touch it
 
     // TODO: loop through ALL the primitives and render them one at a time
-    _primitive[0]->render(slot, trans);
+    _primitive[0]->render(slot, paraller_trans);
     NRPixBlock *out = slot.get(_output_slot);
 
     // Clear the pixblock, where the output will be put
@@ -149,9 +220,11 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb)
         * NR_PIXBLOCK_BPP(pb);
     memset(NR_PIXBLOCK_PX(pb), 0, size);
 
-    // If the filter resolution is automatic, just copy our final image
-    // to output pixblock, otherwise use bicubic scaling
-    if (_x_pixels < 0) {
+    if (notparaller) {
+        transform_nearest(pb, out, trans);
+    } else if (_x_pixels < 0) {
+        // If the filter resolution is automatic, just copy our final image
+        // to output pixblock, otherwise use bicubic scaling
         nr_blit_pixblock_pixblock(pb, out);
     } else {
         scale_bicubic(pb, out);
diff --git a/src/display/pixblock-transform.cpp b/src/display/pixblock-transform.cpp
new file mode 100644 (file)
index 0000000..1a691b5
--- /dev/null
@@ -0,0 +1,98 @@
+#define __NR_PIXBLOCK_SCALER_CPP__
+
+/*
+ * Functions for blitting pixblocks using matrix transformation
+ *
+ * Author:
+ *   Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006 Niko Kiirala
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib.h>
+#include <cmath>
+using std::floor;
+
+#include "libnr/nr-pixblock.h"
+#include "libnr/nr-matrix.h"
+
+namespace NR {
+
+struct RGBA {
+    int r, g, b, a;
+};
+
+/**
+ * Sanity check function for indexing pixblocks.
+ * Catches reading and writing outside the pixblock area.
+ * When enabled, decreases filter rendering speed massively.
+ */
+inline void _check_index(NRPixBlock const * const pb, int const location, int const line)
+{
+    if(true) {
+        int max_loc = pb->rs * (pb->area.y1 - pb->area.y0);
+        if (location < 0 || (location + 4) >= max_loc)
+            g_warning("Location %d out of bounds (0 ... %d) at line %d", location, max_loc, line);
+    }
+}
+
+void transform_nearest(NRPixBlock *to, NRPixBlock *from, Matrix &trans)
+{
+    if (NR_PIXBLOCK_BPP(from) != 4 || NR_PIXBLOCK_BPP(to) != 4) {
+        g_warning("A non-32-bpp image passed to transform_nearest: scaling aborted.");
+        return;
+    }
+
+    // Precalculate sizes of source and destination pixblocks
+    int from_width = from->area.x1 - from->area.x0;
+    int from_height = from->area.y1 - from->area.y0;
+    int to_width = to->area.x1 - to->area.x0;
+    int to_height = to->area.y1 - to->area.y0;
+
+    Matrix itrans = trans.inverse();
+
+    // Loop through every pixel of destination image, a line at a time
+    for (int to_y = 0 ; to_y < to_height ; to_y++) {
+        for (int to_x = 0 ; to_x < to_width ; to_x++) {
+            RGBA result = {0,0,0,0};
+
+            int from_x = (int)round(itrans[0] * (to_x + to->area.x0)
+                                    + itrans[2] * (to_y + to->area.y0)
+                                    + itrans[4]);
+            from_x -= from->area.x0;
+            int from_y = (int)round(itrans[1] * (to_x + to->area.x0)
+                                    + itrans[3] * (to_y + to->area.y0)
+                                    + itrans[5]);
+            from_y -= from->area.y0;
+
+            if (from_x >= 0 && from_x < from_width
+                && from_y >= 0 && from_y < from_height) {
+                _check_index(from, from_y * from->rs + from_x * 4, __LINE__);
+                result.r = NR_PIXBLOCK_PX(from)[from_y * from->rs + from_x * 4];
+                result.g = NR_PIXBLOCK_PX(from)[from_y * from->rs + from_x * 4 + 1];
+                result.b = NR_PIXBLOCK_PX(from)[from_y * from->rs + from_x * 4 + 2];
+                result.a = NR_PIXBLOCK_PX(from)[from_y * from->rs + from_x * 4 + 3];
+            }
+
+            _check_index(to, to_y * to->rs + to_x * 4, __LINE__);
+            NR_PIXBLOCK_PX(to)[to_y * to->rs + to_x * 4] = result.r;
+            NR_PIXBLOCK_PX(to)[to_y * to->rs + to_x * 4 + 1] = result.g;
+            NR_PIXBLOCK_PX(to)[to_y * to->rs + to_x * 4 + 2] = result.b;
+            NR_PIXBLOCK_PX(to)[to_y * to->rs + to_x * 4 + 3] = result.a;
+        }
+    }
+}
+
+} /* namespace NR */
+/*
+  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:encoding=utf-8:textwidth=99 :
diff --git a/src/display/pixblock-transform.h b/src/display/pixblock-transform.h
new file mode 100644 (file)
index 0000000..6ade028
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef __NR_PIXBLOCK_TRANSFORM_H__
+#define __NR_PIXBLOCK_TRANSFORM_H__
+
+/*
+ * Functions for blitting pixblocks using matrix transfomation
+ *
+ * Author:
+ *   Niko Kiirala <niko@kiirala.com>
+ *
+ * Copyright (C) 2006 Niko Kiirala
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "libnr/nr-pixblock.h"
+#include "libnr/nr-matrix.h"
+
+namespace NR {
+
+void transform_nearest(NRPixBlock *to, NRPixBlock *from, Matrix &trans);
+
+} /* namespace NR */
+
+#endif // __NR_PIXBLOCK_TRANSFORM_H__
+/*
+  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:encoding=utf-8:textwidth=99 :