rixmap 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +2 -2
- data/lib/rixmap/format/png/chunk.rb +12 -1
- data/lib/rixmap/format/png/imageio.rb +7 -1
- data/lib/rixmap/format/xpm.rb +3 -3
- data/lib/rixmap/image.rb +231 -0
- data/lib/rixmap/imageio.rb +125 -0
- data/lib/rixmap/version.rb +6 -6
- data/lib/rixmap.rb +3 -1
- data/src/rixmap/common.hxx +9 -2
- data/src/rixmap/converter.hxx +348 -0
- data/src/rixmap/deformer.hxx +325 -0
- data/src/rixmap/helper.hxx +347 -0
- data/src/rixmap/image.hxx +284 -39
- data/src/rixmap/interpolator.hxx +226 -0
- data/src/rixmap/palette.hxx +14 -2
- data/src/rixmap/quantizer.hxx +385 -0
- data/src/rixmap.hxx +4 -1
- data/src/rixmapcore.cxx +668 -272
- data/src/rixmapcore.hxx +8 -1
- data/src/rixmapdeformation.cxx +687 -0
- data/src/rixmapdeformation.hxx +30 -0
- data/src/rixmapio.cxx +93 -56
- data/src/rixmapmain.cxx +3 -1
- data/test/test_clone.rb +15 -0
- data/test/test_deformer.rb +46 -0
- data/test/test_quantize.rb +4 -0
- data/test/test_resize_cutter.rb +27 -0
- metadata +16 -2
@@ -0,0 +1,385 @@
|
|
1
|
+
// -*- coding: utf-8 -*-
|
2
|
+
/**
|
3
|
+
* 減色処理実装
|
4
|
+
*/
|
5
|
+
#pragma once
|
6
|
+
#include "common.hxx"
|
7
|
+
#include "mode.hxx"
|
8
|
+
#include "palette.hxx"
|
9
|
+
#include "image.hxx"
|
10
|
+
|
11
|
+
namespace Rixmap {
|
12
|
+
|
13
|
+
/**
|
14
|
+
* 減色処理ベースクラス.
|
15
|
+
*/
|
16
|
+
class Quantizer {
|
17
|
+
public:
|
18
|
+
virtual ~Quantizer() {}
|
19
|
+
|
20
|
+
public:
|
21
|
+
/**
|
22
|
+
* 減色を実行します.
|
23
|
+
*
|
24
|
+
* @param [ImageData&] input ベース画像
|
25
|
+
* @param [ImageData&] output 減色後画像データの格納先
|
26
|
+
*/
|
27
|
+
virtual void quantize(const ImageData& input, ImageData& output) = 0;
|
28
|
+
|
29
|
+
protected:
|
30
|
+
/**
|
31
|
+
* 入力画像と出力画像が処理できるかを調べます.
|
32
|
+
*/
|
33
|
+
virtual bool validate(const ImageData& input, const ImageData& output) {
|
34
|
+
return true;
|
35
|
+
}
|
36
|
+
};
|
37
|
+
|
38
|
+
/**
|
39
|
+
* メディアンカットによる減色処理実装
|
40
|
+
*/
|
41
|
+
class MedianCutQuantizer : public Quantizer {
|
42
|
+
private: // 内部クラス
|
43
|
+
class ColorRange {
|
44
|
+
public:
|
45
|
+
uint8_t maximum;
|
46
|
+
uint8_t minimum;
|
47
|
+
|
48
|
+
public:
|
49
|
+
inline long getLength() {
|
50
|
+
return static_cast<long>(this->maximum) - static_cast<long>(this->minimum);
|
51
|
+
}
|
52
|
+
|
53
|
+
inline long getMedian() {
|
54
|
+
return lround(this->getLength() / 2.0) + this->minimum;
|
55
|
+
}
|
56
|
+
};
|
57
|
+
|
58
|
+
/**
|
59
|
+
* ピクセルデータクラスタ
|
60
|
+
*/
|
61
|
+
class PixelCluster {/*{{{*/
|
62
|
+
private:
|
63
|
+
ColorArray _colors; // クラスタ内色データ
|
64
|
+
ColorRange _rrange; // 赤要素範囲
|
65
|
+
ColorRange _grange; // 緑要素範囲
|
66
|
+
ColorRange _brange; // 青要素範囲
|
67
|
+
size_t _index; // パレットインデックス
|
68
|
+
|
69
|
+
public:
|
70
|
+
PixelCluster() {
|
71
|
+
this->_rrange.minimum = UINT8_MAX;
|
72
|
+
this->_rrange.maximum = 0;
|
73
|
+
this->_grange.minimum = UINT8_MAX;
|
74
|
+
this->_grange.maximum = 0;
|
75
|
+
this->_brange.minimum = UINT8_MAX;
|
76
|
+
this->_brange.maximum = 0;
|
77
|
+
}
|
78
|
+
|
79
|
+
public: // プロパティメンバ関数
|
80
|
+
inline size_t getIndex() const { return this->_index; }
|
81
|
+
inline void setIndex(size_t index) { this->_index = index; }
|
82
|
+
|
83
|
+
/**
|
84
|
+
* 指定した色がクラスタに含まれているかを調べます.
|
85
|
+
*/
|
86
|
+
inline bool contains(const Color& color) {
|
87
|
+
return ((this->_rrange.minimum <= color.getRed() && color.getRed() <= this->_rrange.maximum)
|
88
|
+
&& (this->_grange.minimum <= color.getGreen() && color.getGreen() <= this->_grange.maximum)
|
89
|
+
&& (this->_brange.minimum <= color.getBlue() && color.getBlue() <= this->_brange.maximum));
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* クラスタ内要素数を返します.
|
94
|
+
*/
|
95
|
+
inline size_t getSize() {
|
96
|
+
return this->_colors.size();
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* 3次元空間としての体積を計算して返します.
|
101
|
+
*/
|
102
|
+
inline long getVolume() {
|
103
|
+
return this->_rrange.getLength() * this->_grange.getLength() * this->_brange.getLength();
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* 密度を計算して返します.
|
108
|
+
*/
|
109
|
+
inline double getDensity() {
|
110
|
+
if (this->_colors.size() > 0) {
|
111
|
+
return this->getVolume() / static_cast<double>(this->_colors.size());
|
112
|
+
} else {
|
113
|
+
return 0.0;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* クラスタへ色を追加します.
|
119
|
+
*/
|
120
|
+
inline void add(const Color& color) {
|
121
|
+
if (color.getRed() < this->_rrange.minimum) {
|
122
|
+
this->_rrange.minimum = color.getRed();
|
123
|
+
}
|
124
|
+
if (color.getRed() > this->_rrange.maximum) {
|
125
|
+
this->_rrange.maximum = color.getRed();
|
126
|
+
}
|
127
|
+
if (color.getGreen() < this->_grange.minimum) {
|
128
|
+
this->_grange.minimum = color.getGreen();
|
129
|
+
}
|
130
|
+
if (color.getGreen() > this->_grange.maximum) {
|
131
|
+
this->_grange.maximum = color.getGreen();
|
132
|
+
}
|
133
|
+
if (color.getBlue() < this->_brange.minimum) {
|
134
|
+
this->_brange.minimum = color.getBlue();
|
135
|
+
}
|
136
|
+
if (color.getBlue() > this->_brange.maximum) {
|
137
|
+
this->_brange.maximum = color.getBlue();
|
138
|
+
}
|
139
|
+
this->_colors.push_back(color);
|
140
|
+
}
|
141
|
+
|
142
|
+
/**
|
143
|
+
* 中央色を取得します.
|
144
|
+
*/
|
145
|
+
inline Color getMedianColor() {
|
146
|
+
if (this->_colors.size() > 0) {
|
147
|
+
return Color(ROUND2BYTE(this->_rrange.getMedian()), ROUND2BYTE(this->_grange.getMedian()), ROUND2BYTE(this->_brange.getMedian()));
|
148
|
+
} else {
|
149
|
+
return Color(0, 0, 0);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
/**
|
154
|
+
* 中央値でクラスタを分割します.
|
155
|
+
*/
|
156
|
+
bool divide(PixelCluster& left, PixelCluster& right) {
|
157
|
+
if (this->_colors.size() <= 1) {
|
158
|
+
// 分割不能
|
159
|
+
return false;
|
160
|
+
}
|
161
|
+
|
162
|
+
long rlen = this->_rrange.getLength();
|
163
|
+
long glen = this->_grange.getLength();
|
164
|
+
long blen = this->_brange.getLength();
|
165
|
+
|
166
|
+
// 最長辺で分割
|
167
|
+
if (rlen >= glen && rlen >= blen) {
|
168
|
+
// 赤で分割
|
169
|
+
double rmed = this->_rrange.getMedian();
|
170
|
+
for (auto it = this->_colors.begin(); it != this->_colors.end(); it++) {
|
171
|
+
Color& color = *it;
|
172
|
+
if (color.getRed() < rmed) {
|
173
|
+
left.add(color);
|
174
|
+
} else {
|
175
|
+
right.add(color);
|
176
|
+
}
|
177
|
+
}
|
178
|
+
} else if (glen >= blen) {
|
179
|
+
// 緑で分割
|
180
|
+
double gmed = this->_grange.getMedian();
|
181
|
+
for (auto it = this->_colors.begin(); it != this->_colors.end(); it++) {
|
182
|
+
Color& color = *it;
|
183
|
+
if (color.getGreen() < gmed) {
|
184
|
+
left.add(color);
|
185
|
+
} else {
|
186
|
+
right.add(color);
|
187
|
+
}
|
188
|
+
}
|
189
|
+
} else {
|
190
|
+
// 青で分割
|
191
|
+
double bmed = this->_brange.getMedian();
|
192
|
+
for (auto it = this->_colors.begin(); it != this->_colors.end(); it++) {
|
193
|
+
Color& color = *it;
|
194
|
+
if (color.getBlue() < bmed) {
|
195
|
+
left.add(color);
|
196
|
+
} else {
|
197
|
+
right.add(color);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
// 分割できたか
|
203
|
+
if (left.getSize() > 0 && right.getSize() > 0) {
|
204
|
+
return true;
|
205
|
+
} else {
|
206
|
+
return false;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
};/*}}}*/
|
210
|
+
|
211
|
+
// クラスタリスト
|
212
|
+
typedef std::vector<PixelCluster*> PixelClusterList;
|
213
|
+
|
214
|
+
protected: // 非公開メンバ
|
215
|
+
size_t _count; // 目標色数
|
216
|
+
|
217
|
+
public: // コンストラクタ
|
218
|
+
/**
|
219
|
+
* @param [size_t] count 目標色数
|
220
|
+
*/
|
221
|
+
MedianCutQuantizer(size_t count = 256) : _count(count) {};
|
222
|
+
|
223
|
+
public:
|
224
|
+
void quantize(const ImageData& input, ImageData& output) override {
|
225
|
+
// バリデーション
|
226
|
+
if (!this->validate(input, output)) {
|
227
|
+
rb_raise(rb_eRuntimeError, "unexpeced input or output image");
|
228
|
+
}
|
229
|
+
|
230
|
+
PixelClusterList clusters;
|
231
|
+
this->prepare(input, clusters);
|
232
|
+
this->reduce(input, output, clusters);
|
233
|
+
|
234
|
+
// 後始末
|
235
|
+
for (auto it = clusters.begin(); it != clusters.end(); it++) {
|
236
|
+
PixelCluster* cluster = *it;
|
237
|
+
delete cluster;
|
238
|
+
*it = NULL;
|
239
|
+
}
|
240
|
+
clusters.clear();
|
241
|
+
}
|
242
|
+
|
243
|
+
protected:
|
244
|
+
bool validate(const ImageData& input, const ImageData& output) override {
|
245
|
+
if (output.getMode() != Mode::INDEXED) {
|
246
|
+
rb_raise(rb_eArgError, "unexpected output image mode: %s", ModeInfo::GetModeName(input.getMode()).c_str());
|
247
|
+
}
|
248
|
+
if (!((input.getWidth() == output.getWidth()) && (input.getHeight() == output.getHeight()))) {
|
249
|
+
rb_raise(rb_eArgError, "input bounds does not matche to output bounds: input is [%d, %d], but output is [%d, %d]", input.getWidth(), input.getHeight(), output.getWidth(), output.getHeight());
|
250
|
+
}
|
251
|
+
return true;
|
252
|
+
}
|
253
|
+
|
254
|
+
/**
|
255
|
+
* パレット準備処理
|
256
|
+
*/
|
257
|
+
void prepare(const ImageData& input, PixelClusterList& clusters) {
|
258
|
+
// 画像情報
|
259
|
+
int32_t width = input.getWidth();
|
260
|
+
int32_t height = input.getHeight();
|
261
|
+
//const ModeInfo& mode = input.getModeInfo();
|
262
|
+
|
263
|
+
// 先頭クラスタ
|
264
|
+
PixelCluster* root = new PixelCluster();
|
265
|
+
{
|
266
|
+
// 全色を入れる
|
267
|
+
for (int32_t h = 0; h < height; h++) {
|
268
|
+
for (int32_t w = 0; w < width; w++) {
|
269
|
+
Color pixel = input.fetch(w, h);
|
270
|
+
root->add(pixel);
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
274
|
+
clusters.push_back(root);
|
275
|
+
|
276
|
+
// 目標色数まで分割
|
277
|
+
while (clusters.size() < this->_count) {
|
278
|
+
PixelCluster* left = new PixelCluster();
|
279
|
+
PixelCluster* right = new PixelCluster();
|
280
|
+
|
281
|
+
// 最後尾を取り出す
|
282
|
+
PixelCluster* last = clusters.back();
|
283
|
+
clusters.pop_back();
|
284
|
+
|
285
|
+
// 分割する
|
286
|
+
if (last->divide(*left, *right)) {
|
287
|
+
// 分割成功
|
288
|
+
delete last; // 分割済みは消す
|
289
|
+
clusters.push_back(left);
|
290
|
+
clusters.push_back(right);
|
291
|
+
|
292
|
+
// 要素順にソート
|
293
|
+
std::sort(clusters.begin(), clusters.end(), [](PixelCluster* left, PixelCluster* right) -> bool {
|
294
|
+
long lv = left->getVolume();
|
295
|
+
long rv = right->getVolume();
|
296
|
+
//size_t lsz = left->getSize();
|
297
|
+
//size_t rsz = right->getSize();
|
298
|
+
double ld = left->getDensity();
|
299
|
+
double rd = right->getDensity();
|
300
|
+
|
301
|
+
if (lv == rv) {
|
302
|
+
return (ld < rd);
|
303
|
+
} else {
|
304
|
+
return (lv < rv);
|
305
|
+
}
|
306
|
+
});
|
307
|
+
} else {
|
308
|
+
// 分割失敗
|
309
|
+
clusters.push_back(last); // 戻して
|
310
|
+
delete left; // 分割用を消す
|
311
|
+
delete right;
|
312
|
+
break; // ループ終了
|
313
|
+
}
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
/**
|
318
|
+
* 減色済みパレットと適合させる
|
319
|
+
*/
|
320
|
+
void reduce(const ImageData& input, ImageData& output, PixelClusterList& clusters) {
|
321
|
+
// クラスタリストからパレットを作成
|
322
|
+
PaletteData* palette = output.getPaletteData();
|
323
|
+
palette->resize(this->_count);
|
324
|
+
{
|
325
|
+
size_t offset = 0;
|
326
|
+
for (auto it = clusters.begin(); it != clusters.end(); it++) {
|
327
|
+
PixelCluster* cluster = *it;
|
328
|
+
Color color = cluster->getMedianColor();
|
329
|
+
palette->set(offset, color);
|
330
|
+
cluster->setIndex(offset++);
|
331
|
+
}
|
332
|
+
|
333
|
+
// 不足分を黒にする
|
334
|
+
// FIXME 変更できるようにするべきか
|
335
|
+
Color blk(0, 0, 0);
|
336
|
+
for (size_t off = offset; off < this->_count; off++) {
|
337
|
+
palette->set(offset, blk);
|
338
|
+
}
|
339
|
+
}
|
340
|
+
|
341
|
+
// 減色を実行
|
342
|
+
int32_t width = output.getWidth();
|
343
|
+
int32_t height = output.getHeight();
|
344
|
+
//const ModeInfo& mode = input.getModeInfo();
|
345
|
+
for (int32_t h = 0; h < height; h++) {
|
346
|
+
for (int32_t w = 0; w < width; w++) {
|
347
|
+
Color pixel = input.fetch(w, h);
|
348
|
+
|
349
|
+
// クラスタから探索
|
350
|
+
auto found = clusters.end();
|
351
|
+
for (auto it = clusters.begin(); it != clusters.end(); it++) {
|
352
|
+
PixelCluster* cluster = *it;
|
353
|
+
if (cluster->contains(pixel)) {
|
354
|
+
// 含んでいる
|
355
|
+
found = it;
|
356
|
+
break;
|
357
|
+
} else {
|
358
|
+
// 含んでいない
|
359
|
+
// FIXME 誤差拡散処理を入れるとどのクラスタにもマッチしないのが出るはず
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
// パレットインデックス化
|
364
|
+
size_t index = 0;
|
365
|
+
if (found == clusters.end()) {
|
366
|
+
// FIXME 誤差拡散とかするとどのクラスタにもない色になる可能性がある
|
367
|
+
// とおもうんだけどなぁ(´・ω・`)
|
368
|
+
index = palette->closest(pixel);
|
369
|
+
} else {
|
370
|
+
// クラスタにある
|
371
|
+
PixelCluster* cluster = *found;
|
372
|
+
index = cluster->getIndex();
|
373
|
+
}
|
374
|
+
|
375
|
+
output.set(Channel::PALETTE, w, h, index);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
}
|
379
|
+
};
|
380
|
+
}
|
381
|
+
|
382
|
+
|
383
|
+
//============================================================================//
|
384
|
+
// $Id: quantizer.hxx,v 753dbf70cab3 2014/05/16 16:13:38 chikuchikugonzalez $
|
385
|
+
// vim: set sts=4 ts=4 sw=4 expandtab foldmethod=marker:
|
data/src/rixmap.hxx
CHANGED
@@ -12,8 +12,11 @@
|
|
12
12
|
#include "rixmap/color.hxx"
|
13
13
|
#include "rixmap/palette.hxx"
|
14
14
|
#include "rixmap/image.hxx"
|
15
|
+
#include "rixmap/quantizer.hxx"
|
16
|
+
#include "rixmap/converter.hxx"
|
17
|
+
#include "rixmap/deformer.hxx"
|
15
18
|
|
16
19
|
|
17
20
|
//============================================================================//
|
18
|
-
// $Id: rixmap.hxx,v
|
21
|
+
// $Id: rixmap.hxx,v 753dbf70cab3 2014/05/16 16:13:38 chikuchikugonzalez $
|
19
22
|
// vim: set sts=4 ts=4 sw=4 expandtab foldmethod=marker:
|