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 }