Image convolution for QImage

This commit is contained in:
Kovid Goyal 2016-05-06 00:56:52 +05:30
parent 2ce3aad03e
commit 8f973eb8a7
2 changed files with 132 additions and 7 deletions

View File

@ -6,9 +6,16 @@
*/ */
#include "imageops.h" #include "imageops.h"
#include <stdexcept>
#define SQUARE(x) (x)*(x) #define SQUARE(x) (x)*(x)
#define MAX(x, y) ((x) > (y)) ? (x) : (y) #define MAX(x, y) ((x) > (y)) ? (x) : (y)
#define DISTANCE(r, g, b) (SQUARE(r - red_average) + SQUARE(g - green_average) + SQUARE(b - blue_average)) #define DISTANCE(r, g, b) (SQUARE(r - red_average) + SQUARE(g - green_average) + SQUARE(b - blue_average))
#define M_EPSILON 1.0e-6
#define M_SQ2PI 2.50662827463100024161235523934010416269302368164062
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
unsigned int read_border_row(const QImage &img, const unsigned int width, const unsigned int height, int *reds, const double fuzz, const bool top) { unsigned int read_border_row(const QImage &img, const unsigned int width, const unsigned int height, int *reds, const double fuzz, const bool top) {
unsigned int r = 0, c = 0, start = 0, delta = top ? 1 : -1, ans = 0; unsigned int r = 0, c = 0, start = 0, delta = top ? 1 : -1, ans = 0;
@ -38,10 +45,10 @@ unsigned int read_border_row(const QImage &img, const unsigned int width, const
return ans; return ans;
} }
#define ENSURE32(img) \ #define ENSURE32(img, ret) \
if (img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_ARGB32) { \ if (img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_ARGB32) { \
img = img.convertToFormat(img.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32); \ img = img.convertToFormat(img.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32); \
if (img.isNull()) { PyErr_NoMemory(); return NULL; } \ if (img.isNull()) { PyErr_NoMemory(); return ret; } \
} \ } \
QImage* remove_borders(const QImage &image, double fuzz) { QImage* remove_borders(const QImage &image, double fuzz) {
@ -51,7 +58,7 @@ QImage* remove_borders(const QImage &image, double fuzz) {
unsigned int width = img.width(), height = img.height(); unsigned int width = img.width(), height = img.height();
unsigned int top_border = 0, bottom_border = 0, left_border = 0, right_border = 0; unsigned int top_border = 0, bottom_border = 0, left_border = 0, right_border = 0;
ENSURE32(img) ENSURE32(img, NULL)
buf = new int[3*(MAX(width, height)+1)]; buf = new int[3*(MAX(width, height)+1)];
fuzz /= 255; fuzz /= 255;
@ -83,7 +90,7 @@ QImage* grayscale(const QImage &image) {
QRgb *row = NULL, *pixel = NULL; QRgb *row = NULL, *pixel = NULL;
int r = 0, gray = 0, width = img.width(), height = img.height(); int r = 0, gray = 0, width = img.width(), height = img.height();
ENSURE32(img); ENSURE32(img, NULL);
for (r = 0; r < height; r++) { for (r = 0; r < height; r++) {
row = reinterpret_cast<QRgb*>(img.scanLine(r)); row = reinterpret_cast<QRgb*>(img.scanLine(r));
for (pixel = row; pixel < row + width; pixel++) { for (pixel = row; pixel < row + width; pixel++) {
@ -94,3 +101,121 @@ QImage* grayscale(const QImage &image) {
if (!PyErr_Occurred()) ans = new QImage(img); if (!PyErr_Occurred()) ans = new QImage(img);
return ans; return ans;
} }
#define CONVOLVE_ACC(weight, pixel) \
r+=((weight))*(qRed((pixel))); g+=((weight))*(qGreen((pixel))); \
b+=((weight))*(qBlue((pixel)));
QImage convolve(QImage &img, int matrix_size, float *matrix) {
int i, x, y, w, h, matrix_x, matrix_y;
int edge = matrix_size/2;
QRgb *dest, *src, *s, **scanblock;
float *m, *normalize_matrix, normalize, r, g, b;
if(!(matrix_size % 2))
throw std::out_of_range("Convolution kernel width must be an odd number");
w = img.width();
h = img.height();
if(w < 3 || h < 3) return img;
ENSURE32(img, img);
QImage buffer = QImage(w, h, img.format());
scanblock = new QRgb* [matrix_size];
normalize_matrix = new float[matrix_size*matrix_size];
Py_BEGIN_ALLOW_THREADS;
// create normalized matrix
normalize = 0.0;
for(i=0; i < matrix_size*matrix_size; ++i)
normalize += matrix[i];
if(std::abs(normalize) <= M_EPSILON)
normalize = 1.0;
normalize = 1.0/normalize;
for(i=0; i < matrix_size*matrix_size; ++i)
normalize_matrix[i] = normalize*matrix[i];
// apply
for(y=0; y < h; ++y){
src = (QRgb *)img.scanLine(y);
dest = (QRgb *)buffer.scanLine(y);
// Read in scanlines to pixel neighborhood. If the scanline is outside
// the image use the top or bottom edge.
for(x=y-edge, i=0; x <= y+edge; ++i, ++x){
scanblock[i] = (QRgb *)
img.scanLine((x < 0) ? 0 : (x > h-1) ? h-1 : x);
}
// Now we are about to start processing scanlines. First handle the
// part where the pixel neighborhood extends off the left edge.
for(x=0; x-edge < 0 ; ++x){
r = g = b = 0.0;
m = normalize_matrix;
for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){
s = scanblock[matrix_y];
matrix_x = -edge;
while(x+matrix_x < 0){
CONVOLVE_ACC(*m, *s);
++matrix_x; ++m;
}
while(matrix_x <= edge){
CONVOLVE_ACC(*m, *s);
++matrix_x; ++m; ++s;
}
}
r = r < 0.0 ? 0.0 : r > 255.0 ? 255.0 : r+0.5;
g = g < 0.0 ? 0.0 : g > 255.0 ? 255.0 : g+0.5;
b = b < 0.0 ? 0.0 : b > 255.0 ? 255.0 : b+0.5;
*dest++ = qRgba((unsigned char)r, (unsigned char)g,
(unsigned char)b, qAlpha(*src++));
}
// Okay, now process the middle part where the entire neighborhood
// is on the image.
for(; x+edge < w; ++x){
m = normalize_matrix;
r = g = b = 0.0;
for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){
s = scanblock[matrix_y] + (x-edge);
for(matrix_x = -edge; matrix_x <= edge; ++matrix_x, ++m, ++s){
CONVOLVE_ACC(*m, *s);
}
}
r = r < 0.0 ? 0.0 : r > 255.0 ? 255.0 : r+0.5;
g = g < 0.0 ? 0.0 : g > 255.0 ? 255.0 : g+0.5;
b = b < 0.0 ? 0.0 : b > 255.0 ? 255.0 : b+0.5;
*dest++ = qRgba((unsigned char)r, (unsigned char)g,
(unsigned char)b, qAlpha(*src++));
}
// Finally process the right part where the neighborhood extends off
// the right edge of the image
for(; x < w; ++x){
r = g = b = 0.0;
m = normalize_matrix;
for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){
s = scanblock[matrix_y];
s += x-edge;
matrix_x = -edge;
while(x+matrix_x < w){
CONVOLVE_ACC(*m, *s);
++matrix_x, ++m, ++s;
}
--s;
while(matrix_x <= edge){
CONVOLVE_ACC(*m, *s);
++matrix_x, ++m;
}
}
r = r < 0.0 ? 0.0 : r > 255.0 ? 255.0 : r+0.5;
g = g < 0.0 ? 0.0 : g > 255.0 ? 255.0 : g+0.5;
b = b < 0.0 ? 0.0 : b > 255.0 ? 255.0 : b+0.5;
*dest++ = qRgba((unsigned char)r, (unsigned char)g,
(unsigned char)b, qAlpha(*src++));
}
}
Py_END_ALLOW_THREADS;
delete[] scanblock;
delete[] normalize_matrix;
return buffer;
}

View File

@ -8,7 +8,9 @@
%ModuleCode %ModuleCode
#include <imageops.h> #include <imageops.h>
#define CATCH \ #define CATCH \
} catch (std::bad_alloc &) { PyErr_NoMemory(); return NULL; \ if (sipRes == NULL) return NULL; \
} catch (std::out_of_range &exc) { PyErr_SetString(PyExc_ValueError, exc.what()); return NULL; \
} catch (std::bad_alloc &) { PyErr_NoMemory(); return NULL; \
} catch (std::exception &exc) { PyErr_SetString(PyExc_RuntimeError, exc.what()); return NULL; \ } catch (std::exception &exc) { PyErr_SetString(PyExc_RuntimeError, exc.what()); return NULL; \
} catch (...) { PyErr_SetString(PyExc_RuntimeError, "unknown error"); return NULL;} } catch (...) { PyErr_SetString(PyExc_RuntimeError, "unknown error"); return NULL;}
%End %End
@ -17,7 +19,6 @@ QImage* remove_borders(const QImage &image, double fuzz);
%MethodCode %MethodCode
try { try {
sipRes = remove_borders(*a0, a1); sipRes = remove_borders(*a0, a1);
if (sipRes == NULL) return NULL;
CATCH CATCH
%End %End
@ -25,6 +26,5 @@ QImage* grayscale(const QImage &image);
%MethodCode %MethodCode
try { try {
sipRes = grayscale(*a0); sipRes = grayscale(*a0);
if (sipRes == NULL) return NULL;
CATCH CATCH
%End %End