File indexing completed on 2024-12-15 04:01:21
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "trace.hpp" 0008 0009 #include "potracelib.h" 0010 0011 #include "utils/color.hpp" 0012 0013 using namespace glaxnimate; 0014 0015 0016 class utils::trace::TraceOptions::Private 0017 { 0018 public: 0019 potrace_param_s* params; 0020 static const constexpr qreal alphamax_max = 1.3334; 0021 }; 0022 0023 utils::trace::TraceOptions::TraceOptions() 0024 : d(std::make_unique<Private>()) 0025 { 0026 d->params = potrace_param_default(); 0027 } 0028 0029 utils::trace::TraceOptions::~TraceOptions() 0030 { 0031 potrace_param_free(d->params); 0032 } 0033 0034 int utils::trace::TraceOptions::min_area() const 0035 { 0036 return d->params->turdsize; 0037 } 0038 0039 void utils::trace::TraceOptions::set_min_area(int min_area) 0040 { 0041 d->params->turdsize = min_area; 0042 } 0043 0044 qreal utils::trace::TraceOptions::smoothness() const 0045 { 0046 return d->params->alphamax / Private::alphamax_max; 0047 } 0048 0049 void utils::trace::TraceOptions::set_smoothness(qreal smoothness) 0050 { 0051 d->params->alphamax = smoothness * Private::alphamax_max; 0052 } 0053 0054 static inline constexpr QRgb rgba888(const uchar* pixel) noexcept 0055 { 0056 return qRgba(pixel[0], pixel[1], pixel[2], pixel[3]); 0057 } 0058 0059 static inline constexpr uchar rgba888_alpha(const uchar* pixel) noexcept 0060 { 0061 return pixel[3]; 0062 } 0063 0064 class utils::trace::Tracer::Private 0065 { 0066 public: 0067 struct CurveWrapper 0068 { 0069 potrace_curve_s* curve; 0070 0071 QPointF point(int index, int off) const 0072 { 0073 const auto& p = curve->c[index][off]; 0074 return {p.x, p.y}; 0075 } 0076 0077 void to_bezier(math::bezier::MultiBezier& mbez) const 0078 { 0079 if ( curve->n < 2 ) 0080 return; 0081 0082 mbez.move_to(point(curve->n-1, 2)); 0083 for ( int i = 0; i < curve->n - 1; i++ ) 0084 { 0085 if ( curve->tag[i] == POTRACE_CURVETO ) 0086 { 0087 mbez.cubic_to(point(i, 0), point(i, 1), point(i, 2)); 0088 } 0089 else 0090 { 0091 mbez.line_to(point(i, 1)); 0092 mbez.line_to(point(i, 2)); 0093 } 0094 } 0095 0096 if ( curve->tag[curve->n - 1] == POTRACE_CURVETO ) 0097 { 0098 mbez.beziers().back().points().back().tan_out = point(curve->n - 1, 0); 0099 mbez.beziers().back()[0].tan_in = point(curve->n - 1, 1); 0100 } 0101 else 0102 { 0103 mbez.line_to(point(curve->n - 1, 1)); 0104 } 0105 0106 mbez.close(); 0107 0108 } 0109 }; 0110 0111 static void progress_callback(double progress, void* privdata) 0112 { 0113 reinterpret_cast<Tracer*>(privdata)->progress(progress); 0114 } 0115 0116 int get_bit_alpha(const uchar* pixel) const noexcept 0117 { 0118 return rgba888_alpha(pixel) >= target_alpha; 0119 } 0120 0121 int get_bit_alpha_neg(const uchar* pixel) const noexcept 0122 { 0123 return rgba888_alpha(pixel) < target_alpha; 0124 } 0125 0126 int get_bit_color(const uchar* pixel) const noexcept 0127 { 0128 return rgba888(pixel) == target_color; 0129 } 0130 0131 int get_bit_color_tolerance(const uchar* pixel) const noexcept 0132 { 0133 return utils::color::rgba_distance_squared(target_color, pixel[0], pixel[1], pixel[2], pixel[3]) <= target_tolerance; 0134 } 0135 0136 int get_bit_index(const uchar* pixel) const noexcept 0137 { 0138 return *pixel == target_color; 0139 } 0140 0141 using callback_type = int (Private::*)(const uchar*) const noexcept; 0142 callback_type callback = &Private::get_bit_alpha; 0143 int target_alpha = 128; 0144 QRgb target_color; 0145 qint32 target_tolerance = 0; 0146 QImage image; 0147 potrace_param_s params; 0148 }; 0149 0150 utils::trace::Tracer::Tracer(const QImage& image, const TraceOptions& options) 0151 : d(std::make_unique<Private>()) 0152 { 0153 d->image = image; 0154 d->params = *options.d->params; 0155 d->params.progress = { 0156 &Private::progress_callback, 0157 this, 0158 0, 0159 100, 0160 1 0161 }; 0162 } 0163 0164 utils::trace::Tracer::~Tracer() = default; 0165 0166 bool utils::trace::Tracer::trace(math::bezier::MultiBezier& mbez) 0167 { 0168 QImage::Format target_format = d->callback == &Private::get_bit_index ? QImage::Format_Indexed8 : QImage::Format_RGBA8888; 0169 if ( d->image.format() != target_format ) 0170 d->image = d->image.convertToFormat(target_format); 0171 0172 int line_len = d->image.width() / sizeof(potrace_word); 0173 if ( d->image.width() % sizeof(potrace_word) ) 0174 line_len += 1; 0175 0176 static constexpr int N = sizeof(potrace_word) * CHAR_BIT; 0177 const int x_off = d->callback == &Private::get_bit_index ? 1 : 4; 0178 std::vector<potrace_word> data(line_len * d->image.height(), 0); 0179 for ( int y = 0, h = d->image.height(), w = d->image.width(); y < h; y++ ) 0180 { 0181 auto line = d->image.constScanLine(y); 0182 for ( int x = 0; x < w; x++ ) 0183 { 0184 (data.data() + y*line_len)[x/N] |= (1ul << (N-1-x%N)) * (d.get()->*d->callback)(line+x*x_off); 0185 } 0186 } 0187 0188 potrace_bitmap_s bitmap{ 0189 d->image.width(), 0190 d->image.height(), 0191 line_len, 0192 data.data() 0193 }; 0194 0195 potrace_state_t *result = potrace_trace(&d->params, &bitmap); 0196 0197 if ( result->status == POTRACE_STATUS_OK ) 0198 { 0199 for ( auto path = result->plist; path; path = path->next ) 0200 { 0201 Private::CurveWrapper{&path->curve}.to_bezier(mbez); 0202 } 0203 potrace_state_free(result); 0204 return true; 0205 } 0206 0207 potrace_state_free(result); 0208 return false; 0209 } 0210 0211 QString utils::trace::Tracer::potrace_version() 0212 { 0213 return ::potrace_version(); 0214 } 0215 0216 void utils::trace::Tracer::set_progress_range(double min, double max) 0217 { 0218 d->params.progress.min = min; 0219 d->params.progress.max = max; 0220 } 0221 0222 void utils::trace::Tracer::set_target_alpha(int threshold, bool invert) 0223 { 0224 d->target_alpha = threshold; 0225 d->callback = invert ? &Private::get_bit_alpha_neg : &Private::get_bit_alpha; 0226 } 0227 0228 void utils::trace::Tracer::set_target_color(const QColor& color, qint32 tolerance) 0229 { 0230 d->target_color = color.rgba(); 0231 d->target_tolerance = tolerance; 0232 d->callback = tolerance > 0 ? &Private::get_bit_color_tolerance : &Private::get_bit_color; 0233 } 0234 0235 void utils::trace::Tracer::set_target_index(uchar index) 0236 { 0237 d->target_color = index; 0238 d->callback = &Private::get_bit_index; 0239 } 0240 0241 0242 struct PixelRect 0243 { 0244 QRectF rect; 0245 QRgb color; 0246 }; 0247 0248 struct PixelTraceData 0249 { 0250 std::map<int, PixelRect*> last_rects; 0251 QList<PixelRect> all_rects; 0252 0253 void merge_up(PixelRect* last_rect, std::map<int, PixelRect*>& rects) 0254 { 0255 if ( !last_rect ) 0256 return; 0257 0258 auto yrect = get_rect(last_rect->rect.left()); 0259 if ( yrect && yrect->rect.width() == last_rect->rect.width() && yrect->color == last_rect->color ) 0260 { 0261 yrect->rect.setBottom(yrect->rect.bottom()+1); 0262 rects[last_rect->rect.left()] = yrect; 0263 0264 for ( auto it = all_rects.begin(); it != all_rects.end(); ++it ) 0265 if ( &*it == last_rect ) 0266 { 0267 all_rects.erase(it); 0268 break; 0269 } 0270 } 0271 } 0272 0273 PixelRect* get_rect(int left) 0274 { 0275 auto yrect = last_rects.find(left); 0276 if ( yrect == last_rects.end() ) 0277 return nullptr; 0278 return &*yrect->second; 0279 } 0280 0281 PixelRect* add_rect(QRgb color, int x, int y) 0282 { 0283 all_rects.push_back({QRectF(x, y, 1, 1), color}); 0284 return &all_rects.back(); 0285 } 0286 0287 }; 0288 0289 0290 std::map<QRgb, std::vector<QRectF> > utils::trace::trace_pixels(QImage image) 0291 { 0292 if ( image.format() != QImage::Format_RGBA8888 ) 0293 image = image.convertToFormat(QImage::Format_RGBA8888); 0294 0295 int w = image.width(); 0296 int h = image.height(); 0297 0298 PixelTraceData data; 0299 0300 for ( int y = 0; y < h; y++ ) 0301 { 0302 std::map<int, PixelRect*> rects; 0303 QRgb last_color = 0; 0304 PixelRect* last_rect = nullptr; 0305 0306 auto line = image.constScanLine(y); 0307 for ( int x = 0; x < w; x++ ) 0308 { 0309 if ( rgba888_alpha(line+x*4) == 0 ) 0310 { 0311 last_color = 0; 0312 last_rect = nullptr; 0313 continue; 0314 } 0315 0316 QRgb colort = rgba888(line+x*4); 0317 0318 auto yrect = data.get_rect(x); 0319 0320 if ( colort == last_color ) 0321 { 0322 last_rect->rect.setRight(last_rect->rect.right() + 1); 0323 } 0324 else if ( yrect && colort == yrect->color && yrect->rect.width() == 1 ) 0325 { 0326 yrect->rect.setBottom(yrect->rect.bottom()+1); 0327 rects[x] = yrect; 0328 last_rect = nullptr; 0329 colort = 0; 0330 } 0331 else 0332 { 0333 data.merge_up(last_rect, rects); 0334 last_rect = data.add_rect(colort, x, y); 0335 rects.emplace(x, last_rect); 0336 } 0337 last_color = colort; 0338 } 0339 data.merge_up(last_rect, rects); 0340 std::swap(data.last_rects, rects); 0341 } 0342 0343 std::map<QRgb, std::vector<QRectF> > traced; 0344 for ( const auto& r : data.all_rects ) 0345 traced[r.color].push_back(r.rect); 0346 0347 return traced; 0348 }