From 9debdee433496b4ca36c7b48b87dd2651ac65ae8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 May 2016 16:43:34 +0530 Subject: [PATCH] Implement fixed palette based quantization --- src/calibre/utils/imageops/imageops.h | 2 +- src/calibre/utils/imageops/imageops.sip | 4 +-- src/calibre/utils/imageops/quantize.cpp | 35 ++++++++++++++----------- src/calibre/utils/img.py | 9 +++++-- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/calibre/utils/imageops/imageops.h b/src/calibre/utils/imageops/imageops.h index 34fcfc7147..6d9a3539a3 100644 --- a/src/calibre/utils/imageops/imageops.h +++ b/src/calibre/utils/imageops/imageops.h @@ -18,7 +18,7 @@ QImage despeckle(const QImage &image); void overlay(const QImage &image, QImage &canvas, unsigned int left, unsigned int top); QImage normalize(const QImage &image); QImage oil_paint(const QImage &image, const float radius=-1, const bool high_quality=true); -QImage quantize(const QImage &image, unsigned int maximum_colors=256, bool dither=true); +QImage quantize(const QImage &image, unsigned int maximum_colors, bool dither, const QVector &palette); class ScopedGILRelease { public: diff --git a/src/calibre/utils/imageops/imageops.sip b/src/calibre/utils/imageops/imageops.sip index 745ccc13d9..f2e4c9af06 100644 --- a/src/calibre/utils/imageops/imageops.sip +++ b/src/calibre/utils/imageops/imageops.sip @@ -72,9 +72,9 @@ QImage oil_paint(const QImage &image, const float radius=-1, const bool high_qua sipRes = new QImage(oil_paint(*a0, a1, a2)); IMAGEOPS_SUFFIX %End -QImage quantize(const QImage &image, unsigned int maximum_colors=256, bool dither=true); +QImage quantize(const QImage &image, unsigned int maximum_colors, bool dither, const QVector &palette); %MethodCode IMAGEOPS_PREFIX - sipRes = new QImage(quantize(*a0, a1, a2)); + sipRes = new QImage(quantize(*a0, a1, a2, *a3)); IMAGEOPS_SUFFIX %End diff --git a/src/calibre/utils/imageops/quantize.cpp b/src/calibre/utils/imageops/quantize.cpp index 63aaebfbd3..5643fc50ee 100644 --- a/src/calibre/utils/imageops/quantize.cpp +++ b/src/calibre/utils/imageops/quantize.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "imageops.h" // Increasing this number improves quality but also increases running time and memory consumption @@ -24,7 +25,6 @@ static const size_t MAX_LEAVES = 2000; #ifdef _MSC_VER typedef unsigned __int64 uint64_t; typedef __int64 int64_t; -typedef unsigned __int32 uint32_t; #define UINT64_MAX _UI64_MAX #ifndef log2 static inline double log2(double x) { return log(x) / log((double)2) ; } @@ -37,7 +37,7 @@ static inline double log2(double x) { return log(x) / log((double)2) ; } #define MAX(x, y) ((x) > (y)) ? (x) : (y) #define MIN(x, y) ((x) < (y)) ? (x) : (y) static const unsigned char BIT_MASK[8] = { 1 << 7, 1 << 6, 1 << 5, 1 << 4, 1 << 3, 1 << 2, 1 << 1, 1 }; -static inline size_t get_index(const uint32_t r, const uint32_t g, const uint32_t b, const size_t level) { +static inline unsigned char get_index(const unsigned char r, const unsigned char g, const unsigned char b, const size_t level) { return ((((r & BIT_MASK[level]) >> (7 - level)) << 2) | (((g & BIT_MASK[level]) >> (7 - level)) << 1) | ((b & BIT_MASK[level]) >> (7 - level))); } template static inline T euclidean_distance(T r1, T g1, T b1, T r2, T g2, T b2) { @@ -130,7 +130,7 @@ public: this->avg.blue = (double)this->sum.blue / (double)this->pixel_count; } - void add_color(const uint32_t r, const uint32_t g, const uint32_t b, const size_t depth, const size_t level, unsigned int *leaf_count, Node **reducible_nodes, Pool &node_pool) { + void add_color(const unsigned char r, const unsigned char g, const unsigned char b, const size_t depth, const size_t level, unsigned int *leaf_count, Node **reducible_nodes, Pool &node_pool) { if (this->is_leaf) { this->pixel_count++; this->sum.red += r; @@ -141,7 +141,7 @@ public: this->error_sum.green += (g > this->avg.green) ? g - this->avg.green : this->avg.green - g; this->error_sum.blue += (b > this->avg.blue) ? b - this->avg.blue : this->avg.blue - b; } else { - size_t index = get_index(r, g, b, level); + unsigned char index = get_index(r, g, b, level); if (this->children[index] == NULL) this->children[index] = this->create_child(level, depth, leaf_count, reducible_nodes, node_pool); this->children[index]->add_color(r, g, b, depth, level + 1, leaf_count, reducible_nodes, node_pool); } @@ -273,10 +273,10 @@ public: } } // }}} - unsigned char index_for_nearest_color(const uint32_t r, const uint32_t g, const uint32_t b, const size_t level) { // {{{ + unsigned char index_for_nearest_color(const unsigned char r, const unsigned char g, const unsigned char b, const size_t level) { // {{{ /* Returns the color palette index for the nearest color to (r, g, b) */ if (this->is_leaf) return this->index; - size_t index = get_index(r, g, b, level); + unsigned char index = get_index(r, g, b, level); if (this->children[index] == NULL) { uint64_t min_distance = UINT64_MAX, distance; for(size_t i = 0; i < MAX_DEPTH; i++) { @@ -367,7 +367,7 @@ inline unsigned int read_colors(const QImage &img, Node &root, size_t depth, Nod inline unsigned int read_colors(const QVector &color_table, Node &root, size_t depth, Node **reducible_nodes, Pool &node_pool) { unsigned int leaf_count = 0; for (int i = 0; i < color_table.size(); i++) { - const QRgb pixel = color_table.at(i); + const QRgb pixel = color_table[i]; root.add_color(qRed(pixel), qGreen(pixel), qBlue(pixel), depth, 0, &leaf_count, reducible_nodes, node_pool); while (leaf_count > MAX_LEAVES) root.reduce(depth, &leaf_count, reducible_nodes, node_pool); @@ -399,9 +399,9 @@ static void write_image(const QImage &img, QImage &ans, Node &root, bool src_is_ } } -QImage quantize(const QImage &image, unsigned int maximum_colors, bool dither) { +QImage quantize(const QImage &image, unsigned int maximum_colors, bool dither, const QVector &palette) { ScopedGILRelease PyGILRelease; - size_t depth = 0; + size_t depth = MAX_DEPTH; int iwidth = image.width(), iheight = image.height(); QImage img(image), ans(iwidth, iheight, QImage::Format_Indexed8); unsigned int leaf_count = 0; @@ -422,12 +422,17 @@ QImage quantize(const QImage &image, unsigned int maximum_colors, bool dither) { } // There can be no more than MAX_LEAVES * 8 nodes. Add 1 in case there is an off by 1 error somewhere. Pool node_pool((MAX_LEAVES + 1) * 8); - - depth = (size_t)log2(maximum_colors); - depth = MAX(2, MIN(depth, MAX_DEPTH)); - - if (img.format() == QImage::Format_RGB32) leaf_count = read_colors(img, root, depth, reducible_nodes, node_pool); - else leaf_count = read_colors(img.colorTable(), root, depth, reducible_nodes, node_pool); + if (palette.size() > 0) { + // Quantizing to fixed palette + leaf_count = read_colors(palette, root, depth, reducible_nodes, node_pool); + maximum_colors = MAX(2, MIN(MAX_COLORS, leaf_count)); + } else if (img.format() == QImage::Format_RGB32) { + depth = (size_t)log2(maximum_colors); + depth = MAX(2, MIN(depth, MAX_DEPTH)); + leaf_count = read_colors(img, root, depth, reducible_nodes, node_pool); + } else { + leaf_count = read_colors(img.colorTable(), root, depth, reducible_nodes, node_pool); + } reduce_tree(root, depth, &leaf_count, maximum_colors, reducible_nodes, node_pool); color_table.resize(leaf_count); diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index 015c666a26..d138d8c359 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -279,13 +279,18 @@ def normalize(img): raise RuntimeError(imageops_err) return imageops.normalize(image_from_data(img)) -def quantize(img, colors=256, dither=True): +def quantize(img, colors=256, dither=True, palette=''): + ''' Quantize the image to contain a maximum of `colors` colors. By default a palette is chosen automatically, + if you want to use a fixed palette, then pass in a list of color names in the `palette` variable. If you, + specify a palette `colors` is ignored. For example: palette='red green blue #eee' ''' if imageops is None: raise RuntimeError(imageops_err) img = image_from_data(img) if img.hasAlphaChannel(): img = blend_image(img) - return imageops.quantize(img, colors, dither) + if palette and isinstance(palette, basestring): + palette = palette.split() + return imageops.quantize(img, colors, dither, [QColor(x).rgb() for x in palette]) # Optimization of images {{{