/* * A simple dialog for setting the parameters for autotracing a * bitmap into an svg * * Authors: * Bob Jamison * Stéphane Gimenez * Other dudes from The Inkscape Organization * * Copyright (C) 2004-2006 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include //for GTK_RESPONSE* types #include #include "tracedialog.h" #include "trace/potrace/inkscape-potrace.h" namespace Inkscape { namespace UI { namespace Dialog { //######################################################################### //## I M P L E M E N T A T I O N //######################################################################### /** * A dialog for adjusting bitmap->vector tracing parameters */ class TraceDialogImpl : public TraceDialog { public: /** * Constructor */ TraceDialogImpl(); /** * Destructor */ ~TraceDialogImpl(); /** * Callback from OK or Cancel */ void responseCallback(int response_id); private: /** * This is the big almighty McGuffin */ Inkscape::Trace::Tracer tracer; /** * This does potrace processing * Only preview if do_i_trace is false */ void potraceProcess(bool do_i_trace); /** * Abort processing */ void abort(); void potracePreviewCallback(); Gtk::HBox mainHBox; Gtk::Notebook notebook; Gtk::Tooltips tips; //########## General items Gtk::Frame sioxFrame; Gtk::VBox sioxVBox; Gtk::HBox sioxBox; Gtk::CheckButton sioxButton; //########## Potrace pages Gtk::VBox potraceModePageBox; Gtk::VBox potraceParamsPageBox; Gtk::RadioButtonGroup potraceGroup; Gtk::Button *potraceOkButton; Gtk::Button *potraceCancelButton; //#### Modes //## Single scan //brightness Gtk::Frame potraceBrightnessFrame; Gtk::VBox potraceBrightnessVBox; Gtk::HBox potraceBrightnessBox; Gtk::RadioButton potraceBrightnessRadioButton; Gtk::Label potraceBrightnessSpinnerLabel; Gtk::SpinButton potraceBrightnessSpinner; //edge detection Gtk::Frame potraceCannyFrame; Gtk::HBox potraceCannyBox; Gtk::VBox potraceCannyVBox; Gtk::RadioButton potraceCannyRadioButton; //Gtk::HSeparator potraceCannySeparator; //Gtk::Label potraceCannyLoSpinnerLabel; //Gtk::SpinButton potraceCannyLoSpinner; Gtk::Label potraceCannyHiSpinnerLabel; Gtk::SpinButton potraceCannyHiSpinner; //quantization Gtk::Frame potraceQuantFrame; Gtk::HBox potraceQuantBox; Gtk::VBox potraceQuantVBox; Gtk::RadioButton potraceQuantRadioButton; Gtk::Label potraceQuantNrColorLabel; Gtk::SpinButton potraceQuantNrColorSpinner; //params Gtk::CheckButton potraceInvertButton; Gtk::HBox potraceInvertBox; //## Multiple path scanning Gtk::Frame potraceMultiScanFrame; Gtk::VBox potraceMultiScanVBox; //brightness Gtk::HBox potraceMultiScanHBox1; Gtk::RadioButton potraceMultiScanBrightnessRadioButton; Gtk::SpinButton potraceMultiScanNrColorSpinner; //colors Gtk::HBox potraceMultiScanHBox2; Gtk::RadioButton potraceMultiScanColorRadioButton; //grays Gtk::HBox potraceMultiScanHBox3; Gtk::RadioButton potraceMultiScanMonoRadioButton; Gtk::Label potraceMultiScanNrColorLabel; //params Gtk::HBox potraceMultiScanHBox4; Gtk::CheckButton potraceMultiScanStackButton; Gtk::CheckButton potraceMultiScanSmoothButton; Gtk::CheckButton potraceMultiScanBackgroundButton; //#### Parameters Gtk::Frame potraceParamsFrame; Gtk::VBox potraceParamsVBox; Gtk::HBox potraceParamsSpecklesBox; Gtk::CheckButton potraceParamsSpecklesButton; Gtk::Label potraceParamsSpecklesSizeLabel; Gtk::SpinButton potraceParamsSpecklesSizeSpinner; Gtk::HBox potraceParamsCornersBox; Gtk::CheckButton potraceParamsCornersButton; Gtk::Label potraceParamsCornersThresholdLabel; Gtk::SpinButton potraceParamsCornersThresholdSpinner; Gtk::HBox potraceParamsOptimBox; Gtk::CheckButton potraceParamsOptimButton; Gtk::Label potraceParamsOptimToleranceLabel; Gtk::SpinButton potraceParamsOptimToleranceSpinner; //credits Gtk::Frame potraceCreditsFrame; Gtk::VBox potraceCreditsVBox; Gtk::Label potraceCreditsLabel; //########## Preview Gtk::Frame potracePreviewFrame; Gtk::VBox potracePreviewVBox; Gtk::Button potracePreviewButton; Gtk::Image potracePreviewImage; }; //######################################################################### //## E V E N T S //######################################################################### /** * This does potrace processing * Only preview if do_i_trace is false */ void TraceDialogImpl::potraceProcess(bool do_i_trace) { //##### Get the tracer and engine Inkscape::Trace::Potrace::PotraceTracingEngine pte; /* inversion */ bool invert = potraceInvertButton.get_active(); pte.setInvert(invert); //##### Get the preprocessor settings /* siox -- performed by Tracer, and before any of the others */ if (sioxButton.get_active()) tracer.enableSiox(true); else tracer.enableSiox(false); /* one of the following */ if (potraceBrightnessRadioButton.get_active()) pte.setTraceType(Inkscape::Trace::Potrace::TRACE_BRIGHTNESS); else if (potraceMultiScanBrightnessRadioButton.get_active()) pte.setTraceType(Inkscape::Trace::Potrace::TRACE_BRIGHTNESS_MULTI); else if (potraceCannyRadioButton.get_active()) pte.setTraceType(Inkscape::Trace::Potrace::TRACE_CANNY); else if (potraceQuantRadioButton.get_active()) pte.setTraceType(Inkscape::Trace::Potrace::TRACE_QUANT); else if (potraceMultiScanColorRadioButton.get_active()) { pte.setTraceType(Inkscape::Trace::Potrace::TRACE_QUANT_COLOR); pte.setInvert(false); } else if (potraceMultiScanMonoRadioButton.get_active()) { pte.setTraceType(Inkscape::Trace::Potrace::TRACE_QUANT_MONO); pte.setInvert(false); } /* params */ int paramsSpecklesSize = potraceParamsSpecklesButton.get_active() ? potraceParamsSpecklesSizeSpinner.get_value_as_int() : 0; pte.setParamsTurdSize(paramsSpecklesSize); double paramsCornersThreshold = potraceParamsCornersButton.get_active() ? potraceParamsCornersThresholdSpinner.get_value() : 0.; pte.setParamsAlphaMax(paramsCornersThreshold); bool paramsOptim = potraceParamsOptimButton.get_active(); pte.setParamsOptiCurve(paramsOptim); double paramsOptimTolerance = potraceParamsOptimToleranceSpinner.get_value(); pte.setParamsOptTolerance(paramsOptimTolerance); //##### Get the single-scan settings /* brightness */ double brightnessThreshold = potraceBrightnessSpinner.get_value(); pte.setBrightnessThreshold(brightnessThreshold); /* canny */ double cannyHighThreshold = potraceCannyHiSpinner.get_value(); pte.setCannyHighThreshold(cannyHighThreshold); /* quantization */ int quantNrColors = potraceQuantNrColorSpinner.get_value_as_int(); pte.setQuantizationNrColors(quantNrColors); //##### Get multiple-scan settings int multiScanNrColors = potraceMultiScanNrColorSpinner.get_value_as_int(); pte.setMultiScanNrColors(multiScanNrColors); bool do_i_stack = potraceMultiScanStackButton.get_active(); pte.setMultiScanStack(do_i_stack); bool do_i_smooth = potraceMultiScanSmoothButton.get_active(); pte.setMultiScanSmooth(do_i_smooth); bool do_i_remove_background = potraceMultiScanBackgroundButton.get_active(); pte.setMultiScanRemoveBackground(do_i_remove_background); //##### Get intermediate bitmap image Glib::RefPtr pixbuf = tracer.getSelectedImage(); if (pixbuf) { Glib::RefPtr preview = pte.preview(pixbuf); if (preview) { int width = preview->get_width(); int height = preview->get_height(); double scaleFX = 200.0 / (double)width; double scaleFY = 200.0 / (double)height; double scaleFactor = scaleFX > scaleFY ? scaleFY : scaleFX; int newWidth = (int) (((double)width) * scaleFactor); int newHeight = (int) (((double)height) * scaleFactor); Glib::RefPtr scaledPreview = preview->scale_simple(newWidth, newHeight, Gdk::INTERP_NEAREST); //g_object_unref(preview); potracePreviewImage.set(scaledPreview); } } //##### Convert if (do_i_trace) { if (potraceCancelButton) potraceCancelButton->set_sensitive(true); if (potraceOkButton) potraceOkButton->set_sensitive(false); tracer.trace(&pte); if (potraceCancelButton) potraceCancelButton->set_sensitive(false); if (potraceOkButton) potraceOkButton->set_sensitive(true); } } /** * Abort processing */ void TraceDialogImpl::abort() { //### Do some GUI thing //### Make the abort() call to the tracer tracer.abort(); } //######################################################################### //## E V E N T S //######################################################################### /** * Callback from the Preview button. Can be called from elsewhere. */ void TraceDialogImpl::potracePreviewCallback() { potraceProcess(false); } /** * Default response from the dialog. Let's intercept it */ void TraceDialogImpl::responseCallback(int response_id) { if (response_id == GTK_RESPONSE_OK) { // for now, we assume potrace, as it's the only one we have potraceProcess(true); } else if (response_id == GTK_RESPONSE_CANCEL) { abort(); } else { hide(); return; } } //######################################################################### //## C O N S T R U C T O R / D E S T R U C T O R //######################################################################### /** * Constructor */ TraceDialogImpl::TraceDialogImpl() { Gtk::VBox *mainVBox = get_vbox(); #define MARGIN 2 /*#### brightness ####*/ potraceBrightnessRadioButton.set_label(_("Brightness cutoff")); potraceGroup = potraceBrightnessRadioButton.get_group(); potraceBrightnessBox.pack_start(potraceBrightnessRadioButton, false, false, MARGIN); tips.set_tip(potraceBrightnessRadioButton, _("Trace by a given brightness level")); potraceBrightnessSpinner.set_digits(3); potraceBrightnessSpinner.set_increments(0.01, 0.1); potraceBrightnessSpinner.set_range(0.0, 1.0); potraceBrightnessSpinner.set_value(0.45); potraceBrightnessBox.pack_end(potraceBrightnessSpinner, false, false, MARGIN); tips.set_tip(potraceBrightnessSpinner, _("Brightness cutoff for black/white")); potraceBrightnessSpinnerLabel.set_label(_("Threshold:")); potraceBrightnessBox.pack_end(potraceBrightnessSpinnerLabel, false, false, MARGIN); potraceBrightnessVBox.pack_start(potraceBrightnessBox, false, false, MARGIN); potraceBrightnessFrame.set_label(_("Single scan: creates a path")); /*#### canny edge detection ####*/ // TRANSLATORS: "Canny" is the name of the inventor of this edge detection method potraceCannyRadioButton.set_label(_("Edge detection")); potraceCannyRadioButton.set_group(potraceGroup); potraceCannyBox.pack_start(potraceCannyRadioButton, false, false, MARGIN); tips.set_tip(potraceCannyRadioButton, _("Trace with optimal edge detection by J. Canny's algorithm")); /* potraceCannyBox.pack_start(potraceCannySeparator); potraceCannyLoSpinnerLabel.set_label(_("Low")); potraceCannyBox.pack_start(potraceCannyLoSpinnerLabel); potraceCannyLoSpinner.set_digits(5); potraceCannyLoSpinner.set_increments(0.01, 0.1); potraceCannyLoSpinner.set_range(0.0, 1.0); potraceCannyLoSpinner.set_value(0.1); potraceCannyBox.pack_start(potraceCannyLoSpinner); */ potraceCannyHiSpinner.set_digits(3); potraceCannyHiSpinner.set_increments(0.01, 0.1); potraceCannyHiSpinner.set_range(0.0, 1.0); potraceCannyHiSpinner.set_value(0.65); potraceCannyBox.pack_end(potraceCannyHiSpinner, false, false, MARGIN); tips.set_tip(potraceCannyHiSpinner, _("Brightness cutoff for adjacent pixels (determines edge thickness)")); potraceCannyHiSpinnerLabel.set_label(_("Threshold:")); potraceCannyBox.pack_end(potraceCannyHiSpinnerLabel, false, false, MARGIN); potraceBrightnessVBox.pack_start(potraceCannyBox, false, false, MARGIN); /*#### quantization ####*/ // TRANSLATORS: Color Quantization: the process of reducing the number of colors // in an image by selecting an optimized set of representative colors and then // re-applying this reduced set to the original image. potraceQuantRadioButton.set_label(_("Color quantization")); potraceQuantRadioButton.set_group(potraceGroup); potraceQuantBox.pack_start(potraceQuantRadioButton, false, false, MARGIN); tips.set_tip(potraceQuantRadioButton, _("Trace along the boundaries of reduced colors")); potraceQuantNrColorSpinner.set_digits(0); potraceQuantNrColorSpinner.set_increments(1.0, 4.0); potraceQuantNrColorSpinner.set_range(2.0, 64.0); potraceQuantNrColorSpinner.set_value(8.0); potraceQuantBox.pack_end(potraceQuantNrColorSpinner, false, false, MARGIN); tips.set_tip(potraceQuantNrColorSpinner, _("The number of reduced colors")); potraceQuantNrColorLabel.set_label(_("Colors:")); potraceQuantBox.pack_end(potraceQuantNrColorLabel, false, false, MARGIN); potraceBrightnessVBox.pack_start(potraceQuantBox, false, false, MARGIN); /*#### swap black and white ####*/ potraceInvertButton.set_label(_("Invert image")); potraceInvertButton.set_active(false); potraceInvertBox.pack_start(potraceInvertButton, false, false, MARGIN); potraceBrightnessVBox.pack_start(potraceInvertBox, false, false, MARGIN); tips.set_tip(potraceInvertButton, _("Invert black and white regions")); potraceBrightnessFrame.add(potraceBrightnessVBox); potraceModePageBox.pack_start(potraceBrightnessFrame, false, false, 0); /*#### Multiple scanning####*/ //----Hbox1 potraceMultiScanBrightnessRadioButton.set_label(_("Brightness steps")); potraceMultiScanBrightnessRadioButton.set_group(potraceGroup); potraceMultiScanHBox1.pack_start(potraceMultiScanBrightnessRadioButton, false, false, MARGIN); tips.set_tip(potraceMultiScanBrightnessRadioButton, _("Trace the given number of brightness levels")); potraceMultiScanNrColorSpinner.set_digits(0); potraceMultiScanNrColorSpinner.set_increments(1.0, 4.0); potraceMultiScanNrColorSpinner.set_range(2.0, 256.0); potraceMultiScanNrColorSpinner.set_value(8.0); potraceMultiScanHBox1.pack_end(potraceMultiScanNrColorSpinner, false, false, MARGIN); potraceMultiScanNrColorLabel.set_label(_("Scans:")); potraceMultiScanHBox1.pack_end(potraceMultiScanNrColorLabel, false, false, MARGIN); tips.set_tip(potraceMultiScanNrColorSpinner, _("The desired number of scans")); potraceMultiScanVBox.pack_start(potraceMultiScanHBox1, false, false, MARGIN); //----Hbox2 potraceMultiScanColorRadioButton.set_label(_("Colors")); potraceMultiScanColorRadioButton.set_group(potraceGroup); potraceMultiScanHBox2.pack_start(potraceMultiScanColorRadioButton, false, false, MARGIN); tips.set_tip(potraceMultiScanColorRadioButton, _("Trace the given number of reduced colors")); potraceMultiScanVBox.pack_start(potraceMultiScanHBox2, false, false, MARGIN); //---Hbox3 potraceMultiScanMonoRadioButton.set_label(_("Grays")); potraceMultiScanMonoRadioButton.set_group(potraceGroup); potraceMultiScanHBox3.pack_start(potraceMultiScanMonoRadioButton, false, false, MARGIN); tips.set_tip(potraceMultiScanMonoRadioButton, _("Same as Colors, but the result is converted to grayscale")); potraceMultiScanVBox.pack_start(potraceMultiScanHBox3, false, false, MARGIN); // TRANSLATORS: "Smooth" is a verb here potraceMultiScanSmoothButton.set_label(_("Smooth")); potraceMultiScanSmoothButton.set_active(true); potraceMultiScanHBox4.pack_start(potraceMultiScanSmoothButton, false, false, MARGIN); tips.set_tip(potraceMultiScanSmoothButton, _("Apply Gaussian blur to the bitmap before tracing")); // TRANSLATORS: "Stack" is a verb here potraceMultiScanStackButton.set_label(_("Stack scans")); potraceMultiScanStackButton.set_active(true); potraceMultiScanHBox4.pack_start(potraceMultiScanStackButton, false, false, MARGIN); tips.set_tip(potraceMultiScanStackButton, _("Stack scans on top of one another (no gaps) instead of or tiling (usually with gaps)")); // TRANSLATORS: "Layer" refers to one of the stacked paths in the multiscan potraceMultiScanBackgroundButton.set_label(_("Remove background")); potraceMultiScanBackgroundButton.set_active(false); potraceMultiScanHBox4.pack_start(potraceMultiScanBackgroundButton, false, false, MARGIN); tips.set_tip(potraceMultiScanBackgroundButton, _("Remove bottom (background) layer when done")); potraceMultiScanVBox.pack_start(potraceMultiScanHBox4, false, false, MARGIN); potraceMultiScanFrame.set_label(_("Multiple scans: creates a group of paths")); //potraceQuantFrame.set_shadow_type(Gtk::SHADOW_NONE); potraceMultiScanFrame.add(potraceMultiScanVBox); potraceModePageBox.pack_start(potraceMultiScanFrame, false, false, 0); notebook.append_page(potraceModePageBox, _("Mode")); //#### Parameters #### //## Potrace Parameters potraceParamsSpecklesButton.set_label(_("Suppress speckles")); tips.set_tip(potraceParamsSpecklesButton, _("Ignore small spots (speckles) in the bitmap")); potraceParamsSpecklesButton.set_active(true); //tips.set_tip(potraceParamsSpecklesButton, _("")); potraceParamsSpecklesBox.pack_start(potraceParamsSpecklesButton, false, false, MARGIN); potraceParamsSpecklesSizeSpinner.set_digits(0); potraceParamsSpecklesSizeSpinner.set_increments(1, 10); potraceParamsSpecklesSizeSpinner.set_range(0, 1000); potraceParamsSpecklesSizeSpinner.set_value(2); tips.set_tip(potraceParamsSpecklesSizeSpinner, _("Speckles of up to this many pixels will be suppressed")); potraceParamsSpecklesBox.pack_end(potraceParamsSpecklesSizeSpinner, false, false, MARGIN); potraceParamsSpecklesSizeLabel.set_label(_("Size:")); potraceParamsSpecklesBox.pack_end(potraceParamsSpecklesSizeLabel, false, false, MARGIN); potraceParamsCornersButton.set_label(_("Smooth corners")); tips.set_tip(potraceParamsCornersButton, _("Smooth out sharp corners of the trace")); potraceParamsCornersButton.set_active(true); //tips.set_tip(potraceParamsCornersButton, _("")); potraceParamsCornersBox.pack_start(potraceParamsCornersButton, false, false, MARGIN); potraceParamsCornersThresholdSpinner.set_digits(2); potraceParamsCornersThresholdSpinner.set_increments(0.01, 0.1); potraceParamsCornersThresholdSpinner.set_range(0.0, 1.34); potraceParamsCornersThresholdSpinner.set_value(1.0); potraceParamsCornersBox.pack_end(potraceParamsCornersThresholdSpinner, false, false, MARGIN); tips.set_tip(potraceParamsCornersThresholdSpinner, _("Increase this to smooth corners more")); potraceParamsCornersThresholdLabel.set_label(_("Threshold:")); potraceParamsCornersBox.pack_end(potraceParamsCornersThresholdLabel, false, false, MARGIN); potraceParamsOptimButton.set_label(_("Optimize paths")); potraceParamsOptimButton.set_active(true); tips.set_tip(potraceParamsOptimButton, _("Try to optimize paths by joining adjacent Bezier curve segments")); potraceParamsOptimBox.pack_start(potraceParamsOptimButton, false, false, MARGIN); potraceParamsOptimToleranceSpinner.set_digits(2); potraceParamsOptimToleranceSpinner.set_increments(0.05, 0.25); potraceParamsOptimToleranceSpinner.set_range(0.0, 5.0); potraceParamsOptimToleranceSpinner.set_value(0.2); potraceParamsOptimBox.pack_end(potraceParamsOptimToleranceSpinner, false, false, MARGIN); tips.set_tip(potraceParamsOptimToleranceSpinner, _("Increase this to reduce the number of nodes in the trace by more aggressive optimization")); potraceParamsOptimToleranceLabel.set_label(_("Tolerance:")); potraceParamsOptimBox.pack_end(potraceParamsOptimToleranceLabel, false, false, MARGIN); potraceParamsVBox.pack_start(potraceParamsSpecklesBox, false, false, MARGIN); potraceParamsVBox.pack_start(potraceParamsCornersBox, false, false, MARGIN); potraceParamsVBox.pack_start(potraceParamsOptimBox, false, false, MARGIN); potraceParamsFrame.set_label(_("Options")); potraceParamsFrame.add(potraceParamsVBox); potraceParamsPageBox.pack_start(potraceParamsFrame, false, false, 0); //## SIOX //for now, put at the top of the potrace box. something better later sioxButton.set_label(_("SIOX foreground selection")); sioxBox.pack_start(sioxButton, false, false, MARGIN); tips.set_tip(sioxButton, _("Cover the area you want to select as the foreground")); sioxVBox.pack_start(sioxBox, false, false, MARGIN); sioxFrame.set_label(_("SIOX")); sioxFrame.add(sioxVBox); potraceParamsPageBox.pack_start(sioxFrame, false, false, 0); //## Credits potraceCreditsLabel.set_text(_("Thanks to Peter Selinger, http://potrace.sourceforge.net")); potraceCreditsVBox.pack_start(potraceCreditsLabel, false, false, MARGIN); potraceCreditsFrame.set_label(_("Credits")); potraceCreditsFrame.set_shadow_type(Gtk::SHADOW_NONE); potraceCreditsFrame.add(potraceCreditsVBox); potraceParamsPageBox.pack_end(potraceCreditsFrame, false, false, 0); notebook.append_page(potraceParamsPageBox, _("Options")); mainHBox.pack_start(notebook, true, true, MARGIN); /*#### Preview ####*/ potracePreviewButton.set_label(_("Update")); potracePreviewButton.signal_clicked().connect( sigc::mem_fun(*this, &TraceDialogImpl::potracePreviewCallback) ); potracePreviewVBox.pack_end(potracePreviewButton, false, false, 0); tips.set_tip(potracePreviewButton, _("Preview the intermediate bitmap with the current settings, without actual tracing")); potracePreviewImage.set_size_request(200,200); //potracePreviewImage.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); potracePreviewVBox.pack_start(potracePreviewImage, true, true, 0); potracePreviewFrame.set_label(_("Preview")); // I guess it's correct to call the "intermediate bitmap" a preview of the trace //potracePreviewFrame.set_shadow_type(Gtk::SHADOW_NONE); potracePreviewFrame.add(potracePreviewVBox); mainHBox.pack_start(potracePreviewFrame, true, true, MARGIN); //attach contents to the main VBox mainVBox->pack_start(mainHBox); //#### Global Controls //## The OK button potraceCancelButton = add_button(Gtk::Stock::STOP, GTK_RESPONSE_CANCEL); if (potraceCancelButton) { tips.set_tip((*potraceCancelButton), _("Abort a trace in progress")); potraceCancelButton->set_sensitive(false); } potraceOkButton = add_button(Gtk::Stock::OK, GTK_RESPONSE_OK); tips.set_tip((*potraceOkButton), _("Execute the trace")); show_all_children(); //## Connect the signal signal_response().connect( sigc::mem_fun(*this, &TraceDialogImpl::responseCallback) ); } /** * Factory method. Use this to create a new TraceDialog */ TraceDialog *TraceDialog::create() { TraceDialog *dialog = new TraceDialogImpl(); return dialog; } /** * Constructor */ TraceDialogImpl::~TraceDialogImpl() { } } //namespace Dialog } //namespace UI } //namespace Inkscape //######################################################################### //## E N D O F F I L E //#########################################################################