FilterResultPtr Task::process(TaskStatus const& status, FilterData const& data) { status.throwIfCancelled(); Dependencies const deps(data.xform().resultingCropArea()); std::auto_ptr<Params> params(m_ptrSettings->getPageParams(m_pageId)); if (params.get() && !params->dependencies().matches(deps)) { params.reset(); } OptionsWidget::UiData ui_data; ui_data.setSizeCalc(PhysSizeCalc(data.xform())); if (params.get()) { ui_data.setContentRect(params->contentRect()); ui_data.setDependencies(params->dependencies()); ui_data.setMode(params->mode()); if (params->contentSizeMM().isEmpty() && !params->contentRect().isEmpty()) { // Backwards compatibilty: put the missing data where it belongs. Params const new_params( ui_data.contentRect(), ui_data.contentSizeMM(), params->dependencies(), params->mode() ); m_ptrSettings->setPageParams(m_pageId, new_params); } } else { QRectF const content_rect( ContentBoxFinder::findContentBox( status, data, m_ptrDbg.get() ) ); ui_data.setContentRect(content_rect); ui_data.setDependencies(deps); ui_data.setMode(MODE_AUTO); Params const new_params( ui_data.contentRect(), ui_data.contentSizeMM(), deps, MODE_AUTO ); m_ptrSettings->setPageParams(m_pageId, new_params); } status.throwIfCancelled(); if (m_ptrNextTask) { return m_ptrNextTask->process( status, FilterData(data, data.xform()), ui_data.contentRect() ); } else { return FilterResultPtr( new UiUpdater( m_ptrFilter, m_pageId, m_ptrDbg, data.origImage(), data.xform(), ui_data, m_batchProcessing ) ); } }
FilterResultPtr Task::process(TaskStatus const& status, FilterData const& data) { // This function is executed from the worker thread. status.throwIfCancelled(); ImageTransformation xform(data.xform()); xform.setPreRotation(m_ptrSettings->getRotationFor(m_imageId)); if (m_ptrNextTask) { return m_ptrNextTask->process(status, FilterData(data, xform)); } else { return FilterResultPtr( new UiUpdater( m_ptrFilter, data.origImage(), m_imageId, xform, m_batchProcessing ) ); } }
void Despeckle::despeckleInPlace( BinaryImage& image, Dpi const& dpi, Level const level, TaskStatus const& status, DebugImages* const dbg) { Settings const settings(Settings::get(level, dpi)); ConnectivityMap cmap(image, CONN8); if (cmap.maxLabel() == 0) { // Completely white image? return; } status.throwIfCancelled(); std::vector<Component> components(cmap.maxLabel() + 1); std::vector<BoundingBox> bounding_boxes(cmap.maxLabel() + 1); int const width = image.width(); int const height = image.height(); uint32_t* const cmap_data = cmap.data(); // Count the number of pixels and a bounding rect of each component. uint32_t* cmap_line = cmap_data; int const cmap_stride = cmap.stride(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t const label = cmap_line[x]; ++components[label].num_pixels; bounding_boxes[label].extend(x, y); } cmap_line += cmap_stride; } status.throwIfCancelled(); // Unify big components into one. std::vector<uint32_t> remapping_table(components.size()); uint32_t unified_big_component = 0; uint32_t next_avail_component = 1; for (uint32_t label = 1; label <= cmap.maxLabel(); ++label) { if (bounding_boxes[label].width() < settings.bigObjectThreshold && bounding_boxes[label].height() < settings.bigObjectThreshold) { components[next_avail_component] = components[label]; remapping_table[label] = next_avail_component; ++next_avail_component; } else { if (unified_big_component == 0) { unified_big_component = next_avail_component; ++next_avail_component; components[unified_big_component] = components[label]; // Set num_pixels to a large value so that canBeAttachedTo() // always allows attaching to any such component. components[unified_big_component].num_pixels = width * height; } remapping_table[label] = unified_big_component; } } components.resize(next_avail_component); std::vector<BoundingBox>().swap(bounding_boxes); // We don't need them any more. status.throwIfCancelled(); uint32_t const max_label = next_avail_component - 1; // Remapping individual pixels. cmap_line = cmap_data; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { cmap_line[x] = remapping_table[cmap_line[x]]; } cmap_line += cmap_stride; } if (dbg) { dbg->add(cmap.visualized(), "big_components_unified"); } status.throwIfCancelled(); // Build a Voronoi diagram. std::vector<Distance> distance_matrix; voronoi(cmap, distance_matrix); if (dbg) { dbg->add(cmap.visualized(), "voronoi"); } status.throwIfCancelled(); Distance* const distance_data = &distance_matrix[0] + width + 3; // Now build a bidirectional map of distances between neighboring // connected components. typedef std::map<Connection, uint32_t> Connections; // conn -> sqdist Connections conns; voronoiDistances(cmap, distance_matrix, conns); status.throwIfCancelled(); // Tag connected components with ANCHORED_TO_BIG or ANCHORED_TO_SMALL. BOOST_FOREACH(Connections::value_type const& pair, conns) { Connection const conn(pair.first); uint32_t const sqdist = pair.second; Component& comp1 = components[conn.lesser_label]; Component& comp2 = components[conn.greater_label]; tagSourceComponent(comp1, comp2, sqdist, settings); tagSourceComponent(comp2, comp1, sqdist, settings); }
FilterResultPtr Task::process( TaskStatus const& status, FilterData const& data, QPolygonF const& content_rect_phys) { status.throwIfCancelled(); Params params(m_ptrSettings->getParams(m_pageId)); RenderParams const render_params(params.colorParams()); QString const out_file_path(m_outFileNameGen.filePathFor(m_pageId)); QFileInfo const out_file_info(out_file_path); ImageTransformation new_xform(data.xform()); new_xform.postScaleToDpi(params.outputDpi()); QString const automask_dir(Utils::automaskDir(m_outFileNameGen.outDir())); QString const automask_file_path( QDir(automask_dir).absoluteFilePath(out_file_info.fileName()) ); QFileInfo automask_file_info(automask_file_path); QString const speckles_dir(Utils::specklesDir(m_outFileNameGen.outDir())); QString const speckles_file_path( QDir(speckles_dir).absoluteFilePath(out_file_info.fileName()) ); QFileInfo speckles_file_info(speckles_file_path); bool const need_picture_editor = render_params.mixedOutput() && !m_batchProcessing; bool const need_speckles_image = params.despeckleLevel() != DESPECKLE_OFF && params.colorParams().colorMode() != ColorParams::COLOR_GRAYSCALE && !m_batchProcessing; OutputGenerator const generator( params.outputDpi(), params.colorParams(), params.despeckleLevel(), new_xform, content_rect_phys ); OutputImageParams new_output_image_params( generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), params.colorParams(), params.dewarpingMode(), params.distortionModel(), params.depthPerception(), params.despeckleLevel() ); ZoneSet const new_picture_zones(m_ptrSettings->pictureZonesForPage(m_pageId)); ZoneSet const new_fill_zones(m_ptrSettings->fillZonesForPage(m_pageId)); bool need_reprocess = false; do { // Just to be able to break from it. std::auto_ptr<OutputParams> stored_output_params( m_ptrSettings->getOutputParams(m_pageId) ); if (!stored_output_params.get()) { need_reprocess = true; break; } if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { need_reprocess = true; break; } if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { need_reprocess = true; break; } if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { need_reprocess = true; break; } if (!out_file_info.exists()) { need_reprocess = true; break; } if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { need_reprocess = true; break; } if (need_picture_editor) { if (!automask_file_info.exists()) { need_reprocess = true; break; } if (!stored_output_params->automaskFileParams().matches(OutputFileParams(automask_file_info))) { need_reprocess = true; break; } } if (need_speckles_image) { if (!speckles_file_info.exists()) { need_reprocess = true; break; } if (!stored_output_params->specklesFileParams().matches(OutputFileParams(speckles_file_info))) { need_reprocess = true; break; } } } while (false); QImage out_img; BinaryImage automask_img; BinaryImage speckles_img; if (!need_reprocess) { QFile out_file(out_file_path); if (out_file.open(QIODevice::ReadOnly)) { out_img = ImageLoader::load(out_file, 0); } need_reprocess = out_img.isNull(); if (need_picture_editor && !need_reprocess) { QFile automask_file(automask_file_path); if (automask_file.open(QIODevice::ReadOnly)) { automask_img = BinaryImage(ImageLoader::load(automask_file, 0)); } need_reprocess = automask_img.isNull() || automask_img.size() != out_img.size(); } if (need_speckles_image && !need_reprocess) { QFile speckles_file(speckles_file_path); if (speckles_file.open(QIODevice::ReadOnly)) { speckles_img = BinaryImage(ImageLoader::load(speckles_file, 0)); } need_reprocess = speckles_img.isNull(); } } if (need_reprocess) { // Even in batch processing mode we should still write automask, because it // will be needed when we view the results back in interactive mode. // The same applies even more to speckles file, as we need it not only // for visualization purposes, but also for re-doing despeckling at // different levels without going through the whole output generation process. bool const write_automask = render_params.mixedOutput(); bool const write_speckles_file = params.despeckleLevel() != DESPECKLE_OFF && params.colorParams().colorMode() != ColorParams::COLOR_GRAYSCALE; automask_img = BinaryImage(); speckles_img = BinaryImage(); DistortionModel distortion_model; if (params.dewarpingMode() == DewarpingMode::MANUAL) { distortion_model = params.distortionModel(); } // OutputGenerator will write a new distortion model // there, if dewarping mode is AUTO. out_img = generator.process( status, data, new_picture_zones, new_fill_zones, params.dewarpingMode(), distortion_model, params.depthPerception(), write_automask ? &automask_img : 0, write_speckles_file ? &speckles_img : 0, m_ptrDbg.get() ); if (params.dewarpingMode() == DewarpingMode::AUTO && distortion_model.isValid()) { // A new distortion model was generated. // We need to save it to be able to modify it manually. params.setDistortionModel(distortion_model); m_ptrSettings->setParams(m_pageId, params); new_output_image_params.setDistortionModel(distortion_model); } if (write_speckles_file && speckles_img.isNull()) { // Even if despeckling didn't actually take place, we still need // to write an empty speckles file. Making it a special case // is simply not worth it. BinaryImage(out_img.size(), WHITE).swap(speckles_img); } bool invalidate_params = false; if (!TiffWriter::writeImage(out_file_path, out_img)) { invalidate_params = true; } else { deleteMutuallyExclusiveOutputFiles(); } if (write_automask) { // Note that QDir::mkdir() will fail if the parent directory, // that is $OUT/cache doesn't exist. We want that behaviour, // as otherwise when loading a project from a different machine, // a whole bunch of bogus directories would be created. QDir().mkdir(automask_dir); // Also note that QDir::mkdir() will fail if the directory already exists, // so we ignore its return value here. if (!TiffWriter::writeImage(automask_file_path, automask_img.toQImage())) { invalidate_params = true; } } if (write_speckles_file) { if (!QDir().mkpath(speckles_dir)) { invalidate_params = true; } else if (!TiffWriter::writeImage(speckles_file_path, speckles_img.toQImage())) { invalidate_params = true; } } if (invalidate_params) { m_ptrSettings->removeOutputParams(m_pageId); } else { // Note that we can't reuse *_file_info objects // as we've just overwritten those files. OutputParams const out_params( new_output_image_params, OutputFileParams(QFileInfo(out_file_path)), write_automask ? OutputFileParams(QFileInfo(automask_file_path)) : OutputFileParams(), write_speckles_file ? OutputFileParams(QFileInfo(speckles_file_path)) : OutputFileParams(), new_picture_zones, new_fill_zones ); m_ptrSettings->setOutputParams(m_pageId, out_params); } m_ptrThumbnailCache->recreateThumbnail(ImageId(out_file_path), out_img); } DespeckleState const despeckle_state( out_img, speckles_img, params.despeckleLevel(), params.outputDpi() ); DespeckleVisualization despeckle_visualization; if (m_lastTab == TAB_DESPECKLING) { // Because constructing DespeckleVisualization takes a noticeable // amount of time, we only do it if we are sure we'll need it. // Otherwise it will get constructed on demand. despeckle_visualization = despeckle_state.visualize(); } if (CommandLine::get().isGui()) { return FilterResultPtr( new UiUpdater( m_ptrFilter, m_ptrSettings, m_ptrDbg, params, new_xform, generator.outputContentRect(), m_pageId, data.origImage(), out_img, automask_img, despeckle_state, despeckle_visualization, m_batchProcessing, m_debug ) ); } else { return FilterResultPtr(0); } }