Port the border removal algorithm from IM to Qt

This commit is contained in:
Kovid Goyal 2016-05-05 18:17:50 +05:30
parent 955a83a320
commit b57ea0ffd0
6 changed files with 124 additions and 1 deletions

View File

@ -239,6 +239,13 @@ extensions = [
sip_files=['calibre/gui2/progress_indicator/QProgressIndicator.sip']
),
Extension('imageops',
['calibre/utils/imageops/imageops.cpp'],
inc_dirs=['calibre/utils/imageops'],
headers=['calibre/utils/imageops/imageops.h'],
sip_files=['calibre/utils/imageops/imageops.sip']
),
Extension('qt_hack',
['calibre/ebooks/pdf/render/qt_hack.cpp'],
inc_dirs=['calibre/ebooks/pdf/render'],

View File

@ -143,6 +143,7 @@ class Plugins(collections.Mapping):
'html',
'freetype',
'unrar',
'imageops',
'qt_hack',
'_regex',
'hunspell',

View File

@ -0,0 +1,76 @@
/*
* imageops.cpp
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "imageops.h"
#define SQUARE(x) (x)*(x)
#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))
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;
const QRgb *row = NULL, *pixel = NULL;
int *greens = NULL, *blues = NULL;
double red_average = 0, green_average = 0, blue_average = 0, distance = 0, first_red = 0, first_green = 0, first_blue = 0;
greens = reds + width + 1; blues = greens + width + 1;
start = top ? 0 : height - 1;
for (r = start; top ? height - r : r > 0; r += delta) {
row = reinterpret_cast<const QRgb*>(img.constScanLine(r));
red_average = 0; green_average = 0; blue_average = 0;
for (c = 0, pixel = row; c < width; c++, pixel++) {
reds[c] = qRed(*pixel); greens[c] = qGreen(*pixel); blues[c] = qBlue(*pixel);
red_average += reds[c]; green_average += greens[c]; blue_average += blues[c];
}
red_average /= MAX(1, width); green_average /= MAX(1, width); blue_average /= MAX(1, width);
distance = 0;
for (c = 0; c < width && distance <= fuzz; c++)
distance = MAX(distance, DISTANCE(reds[c], greens[c], blues[c]));
if (distance > fuzz) break; // row is not homogeneous
if (r == start) { first_red = red_average; first_green = green_average; first_blue = blue_average; }
else if (DISTANCE(first_red, first_green, first_blue) > fuzz) break; // this row's average color is far from the previous row's average color
ans += 1;
}
return ans;
}
QImage* remove_borders(const QImage &image, double fuzz) {
int *buf = NULL;
QImage* ans = NULL, img = image, timg;
QTransform transpose;
transpose.rotate(90);
unsigned int width = img.width(), height = img.height();
unsigned int top_border = 0, bottom_border = 0, left_border = 0, right_border = 0;
if (img.format() != QImage::Format_RGB32) {
img = img.convertToFormat(QImage::Format_RGB32);
if (img.isNull()) { PyErr_NoMemory(); return NULL; }
}
buf = new int[3*(MAX(width, height)+1)];
fuzz /= 255;
top_border = read_border_row(img, width, height, buf, fuzz, true);
if (top_border >= height - 1) goto end;
bottom_border = read_border_row(img, width, height, buf, fuzz, false);
if (bottom_border >= height - 1) goto end;
timg = img.transformed(transpose);
if (timg.isNull()) { PyErr_NoMemory(); goto end; }
left_border = read_border_row(timg, height, width, buf, fuzz, true);
if (left_border >= width - 1) goto end;
right_border = read_border_row(timg, height, width, buf, fuzz, false);
if (right_border >= width - 1) goto end;
if (left_border || right_border || top_border || bottom_border) {
// printf("111111 l=%d t=%d r=%d b=%d\n", left_border, top_border, right_border, bottom_border);
img = img.copy(left_border, top_border, width - left_border - right_border, height - top_border - bottom_border);
if (img.isNull()) { PyErr_NoMemory(); goto end; }
}
end:
delete[] buf;
if (!PyErr_Occurred()) ans = new QImage(img);
return ans;
}

View File

@ -0,0 +1,14 @@
/*
* imageops.h
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <QImage>
#include <Python.h>
QImage* remove_borders(const QImage &image, double fuzz);

View File

@ -0,0 +1,18 @@
//Define the SIP wrapper to the imageops code
//Author - Kovid Goyal <kovid@kovidgoyal.net>
%Module(name=imageops, version=1)
%Import QtCore/QtCoremod.sip
%Import QtGui/QtGuimod.sip
%ModuleCode
#include <imageops.h>
%End
QImage* remove_borders(const QImage &image, double fuzz);
%MethodCode
try {
sipRes = remove_borders(*a0, a1);
if (sipRes == NULL) return NULL;
} catch (std::bad_alloc) { PyErr_NoMemory(); return NULL; }
%End

View File

@ -10,9 +10,10 @@ from threading import Thread
from PyQt5.Qt import QImage, QByteArray, QBuffer, Qt, QPainter, QImageReader, QColor
from calibre import fit_image, force_unicode
from calibre.constants import iswindows
from calibre.constants import iswindows, plugins
from calibre.utils.config_base import tweaks
from calibre.utils.filenames import atomic_rename
imageops, imageops_err = plugins['imageops']
def get_exe_path(name):
from calibre.ebooks.pdf.pdftohtml import PDFTOHTML
@ -198,6 +199,12 @@ class Canvas(object):
def flip_image(img, horizontal=False, vertical=False):
return image_from_data(img).mirrored(horizontal, vertical)
def remove_borders(img, fuzz=None):
if imageops is None:
raise RuntimeError(imageops_err)
fuzz = tweaks['cover_trim_fuzz_value'] if fuzz is None else fuzz
return imageops.remove_borders(image_from_data(img), max(0, fuzz))
def run_optimizer(file_path, cmd, as_filter=False, input_data=None):
file_path = os.path.abspath(file_path)
cwd = os.path.dirname(file_path)